@@ -101,6 +101,7 @@ class Call {
101101 RetryPolicy ? retryPolicy,
102102 SdpPolicy ? sdpPolicy,
103103 CallPreferences ? preferences,
104+ RtcMediaDeviceNotifier ? rtcMediaDeviceNotifier,
104105 }) {
105106 streamLog.i (_tag, () => '<factory> callCid: $callCid ' );
106107 return Call ._internal (
@@ -111,6 +112,7 @@ class Call {
111112 retryPolicy: retryPolicy,
112113 sdpPolicy: sdpPolicy,
113114 preferences: preferences,
115+ rtcMediaDeviceNotifier: rtcMediaDeviceNotifier,
114116 );
115117 }
116118
@@ -125,6 +127,7 @@ class Call {
125127 RetryPolicy ? retryPolicy,
126128 SdpPolicy ? sdpPolicy,
127129 CallPreferences ? preferences,
130+ RtcMediaDeviceNotifier ? rtcMediaDeviceNotifier,
128131 }) {
129132 streamLog.i (_tag, () => '<factory> created: $data ' );
130133 return Call ._internal (
@@ -135,6 +138,7 @@ class Call {
135138 retryPolicy: retryPolicy,
136139 sdpPolicy: sdpPolicy,
137140 preferences: preferences,
141+ rtcMediaDeviceNotifier: rtcMediaDeviceNotifier,
138142 ).also (
139143 (it) => it._stateManager.updateFromCallCreatedData (
140144 data,
@@ -154,6 +158,7 @@ class Call {
154158 RetryPolicy ? retryPolicy,
155159 SdpPolicy ? sdpPolicy,
156160 CallPreferences ? preferences,
161+ RtcMediaDeviceNotifier ? rtcMediaDeviceNotifier,
157162 }) {
158163 streamLog.i (_tag, () => '<factory> created: $data ' );
159164 return Call ._internal (
@@ -164,6 +169,7 @@ class Call {
164169 retryPolicy: retryPolicy,
165170 sdpPolicy: sdpPolicy,
166171 preferences: preferences,
172+ rtcMediaDeviceNotifier: rtcMediaDeviceNotifier,
167173 ).also ((it) => it._stateManager.lifecycleCallRinging (data));
168174 }
169175
@@ -176,6 +182,7 @@ class Call {
176182 SdpPolicy ? sdpPolicy,
177183 CallPreferences ? preferences,
178184 CallCredentials ? credentials,
185+ RtcMediaDeviceNotifier ? rtcMediaDeviceNotifier,
179186 }) {
180187 final finalCallPreferences = preferences ?? DefaultCallPreferences ();
181188 final finalRetryPolicy = retryPolicy ?? const RetryPolicy ();
@@ -204,6 +211,8 @@ class Call {
204211 retryPolicy: finalRetryPolicy,
205212 sdpPolicy: finalSdpPolicy,
206213 permissionManager: permissionManager,
214+ rtcMediaDeviceNotifier:
215+ rtcMediaDeviceNotifier ?? RtcMediaDeviceNotifier .instance,
207216 );
208217 }
209218
@@ -215,6 +224,7 @@ class Call {
215224 required this .networkMonitor,
216225 required RetryPolicy retryPolicy,
217226 required SdpPolicy sdpPolicy,
227+ required RtcMediaDeviceNotifier rtcMediaDeviceNotifier,
218228 CallCredentials ? credentials,
219229 }) : _sessionFactory = CallSessionFactory (
220230 callCid: stateManager.callState.callCid,
@@ -228,6 +238,7 @@ class Call {
228238 _streamVideo = streamVideo,
229239 _retryPolicy = retryPolicy,
230240 _credentials = credentials,
241+ _rtcMediaDeviceNotifier = rtcMediaDeviceNotifier,
231242 dynascaleManager = DynascaleManager (stateManager: stateManager) {
232243 streamLog.i (_tag, () => '<init> state: ${stateManager .callState }' );
233244
@@ -252,6 +263,7 @@ class Call {
252263 final PermissionsManager _permissionsManager;
253264 final DynascaleManager dynascaleManager;
254265 final InternetConnection networkMonitor;
266+ final RtcMediaDeviceNotifier _rtcMediaDeviceNotifier;
255267
256268 CallCredentials ? _credentials;
257269 CallSession ? _session;
@@ -1458,9 +1470,7 @@ class Call {
14581470 }
14591471
14601472 Future <void > _applyCallSettingsToConnectOptions (CallSettings settings) async {
1461- // Apply defaul audio output and input devices
1462- final mediaDevicesResult =
1463- await RtcMediaDeviceNotifier .instance.enumerateDevices ();
1473+ final mediaDevicesResult = await _rtcMediaDeviceNotifier.enumerateDevices ();
14641474
14651475 final mediaDevices = mediaDevicesResult.fold (
14661476 success: (success) => success.data,
@@ -1477,38 +1487,65 @@ class Call {
14771487 .where ((d) => d.kind == RtcMediaDeviceKind .videoInput)
14781488 .toList ();
14791489
1480- var defaultAudioOutput = audioOutputs.firstWhereOrNull ((device) {
1481- if (settings.audio.defaultDevice ==
1482- AudioSettingsRequestDefaultDeviceEnum .speaker) {
1483- return device.id.equalsIgnoreCase (
1484- AudioSettingsRequestDefaultDeviceEnum .speaker.value,
1490+ /// Determines if the speaker should be enabled based on a priority hierarchy of
1491+ /// settings.
1492+ ///
1493+ /// The priority order is as follows:
1494+ /// 1. If video camera is set to be on by default, speaker is enabled
1495+ /// 2. If audio speaker is set to be on by default, speaker is enabled
1496+ /// 3. If the default audio device is set to speaker, speaker is enabled
1497+ final speakerOnWithSettingsPriority = settings.video.cameraDefaultOn ||
1498+ settings.audio.speakerDefaultOn ||
1499+ settings.audio.defaultDevice ==
1500+ AudioSettingsRequestDefaultDeviceEnum .speaker;
1501+
1502+ // Determine default audio output with priority:
1503+ // 1. External device (if available)
1504+ var defaultAudioOutput =
1505+ audioOutputs.firstWhereOrNull ((device) => device.isExternal);
1506+
1507+ if (defaultAudioOutput == null ) {
1508+ // 2. Speaker (if settings indicate it should be used)
1509+ if (speakerOnWithSettingsPriority) {
1510+ defaultAudioOutput = audioOutputs.firstWhereOrNull (
1511+ (device) => device.id.equalsIgnoreCase (
1512+ AudioSettingsRequestDefaultDeviceEnum .speaker.value,
1513+ ),
1514+ );
1515+ } else {
1516+ // 3. First non-speaker device
1517+ defaultAudioOutput = audioOutputs.firstWhereOrNull (
1518+ (device) => ! device.id.equalsIgnoreCase (
1519+ AudioSettingsRequestDefaultDeviceEnum .speaker.value,
1520+ ),
14851521 );
14861522 }
1523+ }
14871524
1488- return ! device.id.equalsIgnoreCase (
1489- AudioSettingsRequestDefaultDeviceEnum .speaker.value,
1490- );
1491- });
1525+ final defaultAudioOutputIsExternal =
1526+ defaultAudioOutput? .isExternal ?? false ;
14921527
1493- if (defaultAudioOutput == null && audioOutputs.isNotEmpty) {
1494- defaultAudioOutput = audioOutputs.first;
1528+ // iOS doesn't allow implicitly setting the default audio output,
1529+ // if external device is connected we trust the OS to set it as default.
1530+ if (defaultAudioOutputIsExternal && CurrentPlatform .isIos) {
1531+ defaultAudioOutput = null ;
14951532 }
14961533
1534+ // Match the default audio input with the default audio output if possible
1535+ final defaultAudioInput = audioInputs
1536+ .firstWhereOrNull ((d) => d.label == defaultAudioOutput? .label);
1537+
14971538 var defaultVideoInput = videoInputs.firstWhereOrNull (
14981539 (device) => device.label
14991540 .toLowerCase ()
15001541 .contains (settings.video.cameraFacing.value.toLowerCase ()),
15011542 );
15021543
1544+ // If it's not front or back then take one of the external cameras
15031545 if (defaultVideoInput == null && videoInputs.length > 2 ) {
1504- // If it's not front or back then take one of the external cameras
15051546 defaultVideoInput = videoInputs.last;
15061547 }
15071548
1508- final defaultAudioInput = audioInputs
1509- .firstWhereOrNull ((d) => d.label == defaultAudioOutput? .label) ??
1510- audioInputs.firstOrNull;
1511-
15121549 _connectOptions = connectOptions.copyWith (
15131550 camera: TrackOption .fromSetting (
15141551 enabled: settings.video.cameraDefaultOn,
@@ -1523,7 +1560,8 @@ class Call {
15231560 VideoSettingsRequestCameraFacingEnum .front
15241561 ? FacingMode .user
15251562 : FacingMode .environment,
1526- speakerDefaultOn: settings.audio.speakerDefaultOn,
1563+ speakerDefaultOn:
1564+ ! defaultAudioOutputIsExternal && speakerOnWithSettingsPriority,
15271565 targetResolution: settings.video.targetResolution,
15281566 screenShareTargetResolution: settings.screenShare.targetResolution,
15291567 );
@@ -2203,7 +2241,7 @@ class Call {
22032241 await result.fold (
22042242 success: (success) async {
22052243 final mediaDevicesResult =
2206- await RtcMediaDeviceNotifier .instance .enumerateDevices ();
2244+ await _rtcMediaDeviceNotifier .enumerateDevices ();
22072245
22082246 final mediaDevices = mediaDevicesResult.fold (
22092247 success: (success) => success.data,
@@ -2385,10 +2423,6 @@ class Call {
23852423 _connectOptions = _connectOptions.copyWith (
23862424 microphone: enabled ? TrackOption .enabled () : TrackOption .disabled (),
23872425 );
2388-
2389- if (_connectOptions.audioOutputDevice != null ) {
2390- await setAudioOutputDevice (_connectOptions.audioOutputDevice! );
2391- }
23922426 }
23932427
23942428 return result.map ((_) => none);
0 commit comments