Skip to content

Commit

Permalink
fix: fixing iOS speakerphone initialization (#664)
Browse files Browse the repository at this point in the history
* fixing iOS speakerphone initialization

* extracted String extensions

---------

Co-authored-by: Deven Joshi <deven9852@gmail.com>
  • Loading branch information
Brazol and deven98 committed May 8, 2024
1 parent 7cfea49 commit 427630a
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 27 deletions.
12 changes: 8 additions & 4 deletions packages/stream_video/lib/src/call/call.dart
Expand Up @@ -20,6 +20,7 @@ import '../shared_emitter.dart';
import '../state_emitter.dart';
import '../utils/cancelable_operation.dart';
import '../utils/cancelables.dart';
import '../utils/extensions.dart';
import '../utils/future.dart';
import '../utils/standard.dart';
import '../webrtc/model/stats/rtc_ice_candidate_pair.dart';
Expand Down Expand Up @@ -1052,6 +1053,7 @@ class Call {
VideoSettingsRequestCameraFacingEnum.front
? FacingMode.user
: FacingMode.environment,
speakerDefaultOn: settings.audio.speakerDefaultOn,
);
}

Expand All @@ -1070,6 +1072,12 @@ class Call {

if (_connectOptions.audioOutputDevice != null) {
await setAudioOutputDevice(_connectOptions.audioOutputDevice!);
} else {
if (CurrentPlatform.isIos) {
await _session?.rtcManager?.setAppleAudioConfiguration(
speakerOn: _connectOptions.speakerDefaultOn,
);
}
}

_logger.v(() => '[applyConnectOptions] finished');
Expand Down Expand Up @@ -1963,7 +1971,3 @@ enum TrackType {
}
}
}

extension on String {
bool equalsIgnoreCase(String other) => toUpperCase() == other.toUpperCase();
}
9 changes: 8 additions & 1 deletion packages/stream_video/lib/src/call/call_connect_options.dart
Expand Up @@ -9,6 +9,7 @@ class CallConnectOptions with EquatableMixin {
this.screenShare = TrackDisabled._instance,
this.audioOutputDevice,
this.audioInputDevice,
this.speakerDefaultOn = false,
this.cameraFacingMode = FacingMode.user,
});

Expand All @@ -18,6 +19,7 @@ class CallConnectOptions with EquatableMixin {

final RtcMediaDevice? audioOutputDevice;
final RtcMediaDevice? audioInputDevice;
final bool speakerDefaultOn;

final FacingMode cameraFacingMode;

Expand All @@ -28,6 +30,7 @@ class CallConnectOptions with EquatableMixin {
RtcMediaDevice? audioOutputDevice,
RtcMediaDevice? audioInputDevice,
FacingMode? cameraFacingMode,
bool? speakerDefaultOn,
}) {
return CallConnectOptions(
camera: camera ?? this.camera,
Expand All @@ -36,6 +39,7 @@ class CallConnectOptions with EquatableMixin {
audioOutputDevice: audioOutputDevice ?? this.audioOutputDevice,
audioInputDevice: audioInputDevice ?? this.audioInputDevice,
cameraFacingMode: cameraFacingMode ?? this.cameraFacingMode,
speakerDefaultOn: speakerDefaultOn ?? this.speakerDefaultOn,
);
}

Expand All @@ -47,6 +51,7 @@ class CallConnectOptions with EquatableMixin {
audioOutputDevice: other.audioOutputDevice,
audioInputDevice: other.audioInputDevice,
cameraFacingMode: other.cameraFacingMode,
speakerDefaultOn: other.speakerDefaultOn,
);
}

Expand All @@ -58,6 +63,7 @@ class CallConnectOptions with EquatableMixin {
audioOutputDevice,
audioInputDevice,
cameraFacingMode,
speakerDefaultOn,
];

@override
Expand All @@ -68,7 +74,8 @@ class CallConnectOptions with EquatableMixin {
' screenShare: $screenShare, '
' audioOutput: $audioOutputDevice,'
' audioInput: $audioInputDevice, '
' cameraFacingMode: $cameraFacingMode'
' cameraFacingMode: $cameraFacingMode, '
' speakerDefaultOn: $speakerDefaultOn'
'}';
}
}
Expand Down
4 changes: 0 additions & 4 deletions packages/stream_video/lib/src/call/session/call_session.dart
Expand Up @@ -186,10 +186,6 @@ class CallSession extends Disposable {
);
});

if (CurrentPlatform.isIos) {
await rtcManager?.setAppleAudioConfiguration();
}

_logger.v(() => '[start] completed');
return const Result.success(none);
} catch (e, stk) {
Expand Down
5 changes: 5 additions & 0 deletions packages/stream_video/lib/src/utils/extensions.dart
@@ -0,0 +1,5 @@
/// Extensions on [String].
extension StringExtension on String {
bool equalsIgnoreCase(String other) => toUpperCase() == other.toUpperCase();
String capitalizeFirstLetter() => '${this[0].toUpperCase()}${substring(1)}';
}
22 changes: 20 additions & 2 deletions packages/stream_video/lib/src/webrtc/rtc_manager.dart
Expand Up @@ -2,6 +2,7 @@ import 'package:collection/collection.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:rxdart/rxdart.dart';

import '../../stream_video.dart';
import '../disposable.dart';
import '../errors/video_error_composer.dart';
import '../logger/impl/tagged_logger.dart';
Expand All @@ -10,6 +11,7 @@ import '../models/call_cid.dart';
import '../platform_detector/platform_detector.dart';
import '../sfu/data/models/sfu_model_parser.dart';
import '../sfu/data/models/sfu_track_type.dart';
import '../utils/extensions.dart';
import '../utils/none.dart';
import '../utils/result.dart';
import 'codecs_helper.dart' as codecs;
Expand Down Expand Up @@ -661,6 +663,17 @@ extension RtcManagerTrackHelper on RtcManager {
}

try {
if (CurrentPlatform.isIos &&
device.id.equalsIgnoreCase(
AudioSettingsRequestDefaultDeviceEnum.speaker.value,
)) {
await setAppleAudioConfiguration(
speakerOn: true,
);
} else {
await setAppleAudioConfiguration();
}

// Change the audio output device for all remote audio tracks.
await rtc.Helper.selectAudioOutput(device.id);
for (final audioTrack in audioTracks) {
Expand Down Expand Up @@ -847,13 +860,18 @@ extension RtcManagerTrackHelper on RtcManager {
return Result.error('Unsupported trackType $trackType');
}

Future<Result<None>> setAppleAudioConfiguration() async {
Future<Result<None>> setAppleAudioConfiguration({
bool speakerOn = false,
}) async {
try {
await rtc.Helper.setAppleAudioConfiguration(
rtc.AppleAudioConfiguration(
appleAudioMode: rtc.AppleAudioMode.videoChat,
appleAudioMode: speakerOn
? rtc.AppleAudioMode.videoChat
: rtc.AppleAudioMode.voiceChat,
appleAudioCategory: rtc.AppleAudioCategory.playAndRecord,
appleAudioCategoryOptions: {
if (speakerOn) rtc.AppleAudioCategoryOption.defaultToSpeaker,
rtc.AppleAudioCategoryOption.mixWithOthers,
rtc.AppleAudioCategoryOption.allowBluetooth,
rtc.AppleAudioCategoryOption.allowBluetoothA2DP,
Expand Down
@@ -1,7 +1,10 @@
import 'package:collection/collection.dart';
import 'package:flutter_webrtc/flutter_webrtc.dart' as rtc;
import 'package:rxdart/rxdart.dart';

import '../../../stream_video.dart';
import '../../errors/video_error_composer.dart';
import '../../utils/extensions.dart';
import '../../utils/result.dart';
import 'rtc_media_device.dart';

Expand Down Expand Up @@ -33,14 +36,29 @@ class RtcMediaDeviceNotifier {
try {
final devices = await rtc.navigator.mediaDevices.enumerateDevices();

final mediaDevices = devices.map((it) {
return RtcMediaDevice(
id: it.deviceId,
label: it.label,
groupId: it.groupId,
kind: RtcMediaDeviceKind.fromAlias(it.kind),
);
});
final mediaDevices = [
...devices.map((it) {
return RtcMediaDevice(
id: it.deviceId,
label: it.label,
groupId: it.groupId,
kind: RtcMediaDeviceKind.fromAlias(it.kind),
);
}),
if (CurrentPlatform.isIos &&
(kind == null || kind == RtcMediaDeviceKind.audioOutput) &&
devices.none(
(d) => d.deviceId.equalsIgnoreCase(
AudioSettingsRequestDefaultDeviceEnum.earpiece.value,
),
))
RtcMediaDevice(
id: AudioSettingsRequestDefaultDeviceEnum.earpiece.value,
label: AudioSettingsRequestDefaultDeviceEnum.earpiece.value
.capitalizeFirstLetter(),
kind: RtcMediaDeviceKind.audioOutput,
),
];

if (kind != null) {
final devices = mediaDevices.where((d) => d.kind == kind);
Expand Down
Expand Up @@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

import '../../../stream_video_flutter.dart';
import '../../utils/extensions.dart';

// These are eyeballed device IDs for the speaker and earpiece.
// based on Android and iOS enumerated devices.
Expand Down Expand Up @@ -112,7 +113,3 @@ class _ToggleSpeakerState extends State<ToggleSpeakerphoneOption> {
);
}
}

extension on String {
bool equalsIgnoreCase(String other) => toUpperCase() == other.toUpperCase();
}
Expand Up @@ -4,6 +4,7 @@ import 'package:collection/collection.dart';
import 'package:flutter/material.dart';

import '../../../stream_video_flutter.dart';
import '../utils/extensions.dart';

/// A widget that represents a call control option to toggle if the
/// speakerphone is on or off.
Expand Down Expand Up @@ -122,7 +123,3 @@ class _ToggleSpeakerState extends State<LivestreamSpeakerphoneOption> {
);
}
}

extension on String {
bool equalsIgnoreCase(String other) => toUpperCase() == other.toUpperCase();
}
2 changes: 2 additions & 0 deletions packages/stream_video_flutter/lib/src/utils/extensions.dart
Expand Up @@ -20,6 +20,8 @@ extension StringExtension on String {

return resultBuffer.toString();
}

bool equalsIgnoreCase(String other) => toUpperCase() == other.toUpperCase();
}

/// Extensions on [List].
Expand Down

0 comments on commit 427630a

Please sign in to comment.