Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: respecting default audio output setting #608

Merged
merged 5 commits into from Mar 25, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
29 changes: 27 additions & 2 deletions packages/stream_video/lib/src/call/call.dart
Expand Up @@ -270,8 +270,7 @@ class Call {

set connectOptions(CallConnectOptions connectOptions) {
final status = _status.value;
if (status == _ConnectionStatus.connecting ||
status == _ConnectionStatus.connected) {
if (status == _ConnectionStatus.connected) {
_logger.w(
() => '[setConnectOptions] rejected (connectOptions must be'
' set before invoking `connect`)',
Expand Down Expand Up @@ -976,6 +975,17 @@ class Call {
return [...?_session?.getTracks(trackIdPrefix)];
}

void _setDefaultConnectOptions(CallSettings settings) {
connectOptions = connectOptions.copyWith(
camera: TrackOption.fromSetting(
enabled: settings.video.cameraDefaultOn,
),
microphone: TrackOption.fromSetting(
enabled: settings.audio.micDefaultOn,
),
);
}

Future<void> _applyConnectOptions() async {
_logger.d(() => '[applyConnectOptions] connectOptions: $_connectOptions');
await _applyCameraOption(_connectOptions.camera);
Expand Down Expand Up @@ -1192,11 +1202,26 @@ class Call {
custom: custom,
);

final mediaDevicesResult =
await RtcMediaDeviceNotifier.instance.enumerateDevices();
final mediaDevices = mediaDevicesResult.fold(
success: (success) => success.data,
failure: (failure) => <RtcMediaDevice>[],
);

return response.fold(
success: (it) {
_setDefaultConnectOptions(it.data.data.metadata.settings);

_stateManager.lifecycleCallCreated(
CallCreated(it.data.data),
ringing: ringing,
audioOutputs: mediaDevices
.where((d) => d.kind == RtcMediaDeviceKind.audioOutput)
.toList(),
audioInputs: mediaDevices
.where((d) => d.kind == RtcMediaDeviceKind.audioInput)
.toList(),
);
_logger.v(() => '[getOrCreate] completed: ${it.data}');
return it;
Expand Down
Expand Up @@ -18,7 +18,6 @@ class CallConnectOptions with EquatableMixin {
TrackOption? camera,
TrackOption? microphone,
TrackOption? screenShare,
Duration? dropTimeout,
}) {
return CallConnectOptions(
camera: camera ?? this.camera,
Expand All @@ -43,6 +42,9 @@ class CallConnectOptions with EquatableMixin {
abstract class TrackOption with EquatableMixin {
const TrackOption();

factory TrackOption.fromSetting({required bool enabled}) =>
enabled ? TrackOption.enabled() : TrackOption.disabled();

factory TrackOption.enabled() {
return TrackEnabled._instance;
}
Expand Down
Expand Up @@ -167,6 +167,13 @@ class CallSession extends Disposable {
if (CurrentPlatform.isIos) {
await rtcManager?.setAppleAudioConfiguration();
}

unawaited(
Future.delayed(const Duration(milliseconds: 250), () async {
Brazol marked this conversation as resolved.
Show resolved Hide resolved
await _applyCurrentAudioOutputDevice();
}),
);

_logger.v(() => '[start] completed');
return const Result.success(none);
} catch (e, stk) {
Expand Down
@@ -1,4 +1,5 @@
import 'package:state_notifier/state_notifier.dart';
import 'package:collection/collection.dart';

import '../../../../stream_video.dart';
import '../../../action/internal/lifecycle_action.dart';
Expand Down Expand Up @@ -101,7 +102,26 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
void lifecycleCallCreated(
CallCreated stage, {
bool ringing = false,
List<RtcMediaDevice>? audioOutputs,
List<RtcMediaDevice>? audioInputs,
}) {
final defaultAudioOutput = audioOutputs?.firstWhereOrNull((device) {
if (stage.data.metadata.settings.audio.defaultDevice ==
AudioSettingsRequestDefaultDeviceEnum.speaker) {
return device.id.equalsIgnoreCase(
AudioSettingsRequestDefaultDeviceEnum.speaker.value,
);
}

return !device.id.equalsIgnoreCase(
AudioSettingsRequestDefaultDeviceEnum.speaker.value,
);
});

final defaultAudioInput = audioInputs
?.firstWhereOrNull((d) => d.label == defaultAudioOutput?.label) ??
Brazol marked this conversation as resolved.
Show resolved Hide resolved
audioInputs?.firstOrNull;

_logger.d(() => '[lifecycleCallCreated] ringing: $ringing, state: $state');
state = state.copyWith(
status: stage.data.toCallStatus(state: state, ringing: ringing),
Expand All @@ -118,6 +138,8 @@ mixin StateLifecycleMixin on StateNotifier<CallState> {
isBackstage: stage.data.metadata.details.backstage,
isBroadcasting: stage.data.metadata.details.broadcasting,
isRecording: stage.data.metadata.details.recording,
audioOutputDevice: defaultAudioOutput,
audioInputDevice: defaultAudioInput,
);
}

Expand Down Expand Up @@ -361,3 +383,7 @@ extension on CallRingingData {
}
}
}

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

import '../../../../open_api/video/coordinator/api.dart' as open;
import '../../../stream_video.dart';
import '../../errors/video_error.dart';
import '../../logger/stream_log.dart';
import '../../models/call_cid.dart';
Expand Down Expand Up @@ -176,6 +177,9 @@ extension CallSettingsExt on open.CallSettingsResponse {
accessRequestEnabled: audio.accessRequestEnabled,
opusDtxEnabled: audio.opusDtxEnabled,
redundantCodingEnabled: audio.redundantCodingEnabled,
defaultDevice: audio.defaultDevice.toDomain(),
micDefaultOn: audio.micDefaultOn,
speakerDefaultOn: audio.speakerDefaultOn,
),
video: StreamVideoSettings(
accessRequestEnabled: video.accessRequestEnabled,
Expand Down Expand Up @@ -208,6 +212,16 @@ extension CallSettingsExt on open.CallSettingsResponse {
}
}

extension on open.AudioSettingsDefaultDeviceEnum {
AudioSettingsRequestDefaultDeviceEnum toDomain() {
if (this == open.AudioSettingsDefaultDeviceEnum.speaker) {
return AudioSettingsRequestDefaultDeviceEnum.speaker;
} else {
return AudioSettingsRequestDefaultDeviceEnum.earpiece;
}
}
}

extension on open.TranscriptionSettingsModeEnum {
TranscriptionSettingsMode toDomain() {
if (this == open.TranscriptionSettingsModeEnum.autoOn) {
Expand Down
13 changes: 13 additions & 0 deletions packages/stream_video/lib/src/models/call_settings.dart
Expand Up @@ -77,17 +77,24 @@ class StreamAudioSettings extends MediaSettings {
this.opusDtxEnabled = false,
this.redundantCodingEnabled = false,
this.defaultDevice = AudioSettingsRequestDefaultDeviceEnum.speaker,
this.micDefaultOn = true,
this.speakerDefaultOn = true,
});

final bool opusDtxEnabled;
final bool redundantCodingEnabled;
final AudioSettingsRequestDefaultDeviceEnum defaultDevice;
final bool micDefaultOn;
final bool speakerDefaultOn;

@override
List<Object?> get props => [
accessRequestEnabled,
opusDtxEnabled,
redundantCodingEnabled,
defaultDevice,
micDefaultOn,
speakerDefaultOn,
];

AudioSettingsRequest toOpenDto() {
Expand All @@ -96,6 +103,8 @@ class StreamAudioSettings extends MediaSettings {
accessRequestEnabled: accessRequestEnabled,
opusDtxEnabled: opusDtxEnabled,
redundantCodingEnabled: redundantCodingEnabled,
micDefaultOn: micDefaultOn,
speakerDefaultOn: speakerDefaultOn,
);
}
}
Expand All @@ -104,20 +113,24 @@ class StreamVideoSettings extends MediaSettings {
const StreamVideoSettings({
super.accessRequestEnabled = false,
this.enabled = false,
this.cameraDefaultOn = true,
});

final bool enabled;
final bool cameraDefaultOn;

@override
List<Object?> get props => [
accessRequestEnabled,
enabled,
cameraDefaultOn,
];

VideoSettingsRequest toOpenDto() {
return VideoSettingsRequest(
enabled: enabled,
accessRequestEnabled: accessRequestEnabled,
cameraDefaultOn: cameraDefaultOn,
);
}
}
Expand Down
Expand Up @@ -26,17 +26,19 @@ class RtcMediaDevice with EquatableMixin {
required this.id,
required this.label,
required this.kind,
this.groupId,
});

final String id;
final String label;
final String? groupId;
final RtcMediaDeviceKind kind;

@override
String toString() {
return 'RtcMediaDevice{id: $id, label: $label, kind: $kind}';
return 'RtcMediaDevice{id: $id, label: $label, groupId: $groupId, kind: $kind}';
}

@override
List<Object?> get props => [id, kind, label];
List<Object?> get props => [id, kind, groupId, label];
}
Expand Up @@ -37,6 +37,7 @@ class RtcMediaDeviceNotifier {
return RtcMediaDevice(
id: it.deviceId,
label: it.label,
groupId: it.groupId,
kind: RtcMediaDeviceKind.fromAlias(it.kind),
);
});
Expand Down
Expand Up @@ -47,7 +47,7 @@ class StreamCallContainer extends StatefulWidget {
const StreamCallContainer({
super.key,
required this.call,
this.callConnectOptions = const CallConnectOptions(),
this.callConnectOptions,
this.onBackPressed,
this.onLeaveCallTap,
this.onAcceptCallTap,
Expand All @@ -62,7 +62,7 @@ class StreamCallContainer extends StatefulWidget {
final Call call;

/// Options used while connecting to the call.
final CallConnectOptions callConnectOptions;
final CallConnectOptions? callConnectOptions;

/// The action to perform when the back button is pressed.
final VoidCallback? onBackPressed;
Expand Down Expand Up @@ -161,7 +161,9 @@ class _StreamCallContainerState extends State<StreamCallContainer> {
Future<void> _connect() async {
try {
_logger.d(() => '[connect] no args');
call.connectOptions = widget.callConnectOptions;
if (widget.callConnectOptions != null) {
call.connectOptions = widget.callConnectOptions!;
}
final result = await call.join();
_logger.v(() => '[connect] completed: $result');
} catch (e) {
Expand Down
Expand Up @@ -41,6 +41,22 @@ class _StreamLobbyVideoState extends State<StreamLobbyVideo> {
RtcLocalAudioTrack? _microphoneTrack;
RtcLocalCameraTrack? _cameraTrack;

@override
void initState() {
super.initState();

Future.delayed(Duration.zero, () {
final callSettings = widget.call.state.value.settings;
if (callSettings.audio.micDefaultOn) {
toggleMicrophone();
}

if (callSettings.video.cameraDefaultOn) {
toggleCamera();
}
});
}

Future<void> toggleCamera() async {
if (_cameraTrack != null) {
await _cameraTrack?.stop();
Expand Down