Skip to content

Commit f4617eb

Browse files
authored
[Fix]Respect dashboard audio settings (#758)
1 parent d76270e commit f4617eb

File tree

11 files changed

+270
-40
lines changed

11 files changed

+270
-40
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
1212

1313
### 🐞 Fixed
1414
- Sound resources weren't loaded correctly when the SDK was linked via SPM. [#757](https://github.com/GetStream/stream-video-swift/pull/757)
15+
- Redefined the priorities by which dashboard audio settings will be applied. [#758](https://github.com/GetStream/stream-video-swift/pull/758)
1516

1617
# [1.20.0](https://github.com/GetStream/stream-video-swift/releases/tag/1.20.0)
1718
_April 07, 2025_

Sources/StreamVideo/CallSettings/SpeakerManager.swift

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,16 @@ import Foundation
88
/// Handles the speaker state during a call.
99
public final class SpeakerManager: ObservableObject, CallSettingsManager, @unchecked Sendable {
1010

11+
private struct Settings: Hashable {
12+
var speakerOn: Bool
13+
var audioOutputOn: Bool
14+
15+
init(_ callSettings: CallSettings) {
16+
speakerOn = callSettings.speakerOn
17+
audioOutputOn = callSettings.audioOutputOn
18+
}
19+
}
20+
1121
@Published public internal(set) var status: CallSettingsStatus
1222
@Published public internal(set) var audioOutputStatus: CallSettingsStatus
1323

@@ -96,8 +106,8 @@ public final class SpeakerManager: ObservableObject, CallSettingsManager, @unche
96106
call
97107
.state
98108
.$callSettings
109+
.map { Settings($0) }
99110
.removeDuplicates()
100-
.map { (speakerOn: $0.speakerOn, audioOutputOn: $0.audioOutputOn) }
101111
.log(.debug) { "\(typeOfSelf) callSettings updated speakerOn:\($0.speakerOn) audioOutputOn:\($0.audioOutputOn)." }
102112
.receive(on: DispatchQueue.main)
103113
.sink { [weak self] in

Sources/StreamVideo/CallState.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ public class CallState: ObservableObject {
394394
ingress = Ingress(rtmp: rtmp)
395395

396396
if !localCallSettingsUpdate {
397-
callSettings = response.settings.toCallSettings
397+
callSettings = .init(response.settings)
398398
}
399399
}
400400

Sources/StreamVideo/Models/CallSettings.swift

Lines changed: 81 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,34 @@ public final class CallSettings: ObservableObject, Sendable, Equatable, Reflecti
1717
public let audioOutputOn: Bool
1818
/// The camera position for the current user.
1919
public let cameraPosition: CameraPosition
20-
20+
21+
public convenience init(
22+
_ response: CallSettingsResponse,
23+
file: StaticString = #file,
24+
function: StaticString = #function,
25+
line: UInt = #line
26+
) {
27+
self.init(
28+
audioOn: response.audio.micDefaultOn,
29+
videoOn: response.video.cameraDefaultOn,
30+
speakerOn: response.speakerOnWithSettingsPriority,
31+
audioOutputOn: true, // We always have audioOutputOn
32+
cameraPosition: response.video.cameraFacing == .back ? .back : .front,
33+
file: file,
34+
function: function,
35+
line: line
36+
)
37+
}
38+
2139
public init(
2240
audioOn: Bool = true,
2341
videoOn: Bool = true,
2442
speakerOn: Bool = true,
2543
audioOutputOn: Bool = true,
26-
cameraPosition: CameraPosition = .front
44+
cameraPosition: CameraPosition = .front,
45+
file: StaticString = #file,
46+
function: StaticString = #function,
47+
line: UInt = #line
2748
) {
2849
self.audioOn = audioOn
2950
self.speakerOn = speakerOn
@@ -44,8 +65,15 @@ public final class CallSettings: ObservableObject, Sendable, Equatable, Reflecti
4465
}
4566
self.videoOn = false
4667
}
68+
69+
log.debug(
70+
"CallSettings created.",
71+
functionName: function,
72+
fileName: file,
73+
lineNumber: line
74+
)
4775
}
48-
76+
4977
public static func == (lhs: CallSettings, rhs: CallSettings) -> Bool {
5078
lhs.audioOn == rhs.audioOn &&
5179
lhs.videoOn == rhs.videoOn &&
@@ -69,67 +97,94 @@ public enum CameraPosition: Sendable, Equatable {
6997
}
7098
}
7199

72-
extension CallSettingsResponse {
73-
74-
public var toCallSettings: CallSettings {
75-
CallSettings(
76-
audioOn: audio.micDefaultOn,
77-
videoOn: video.cameraDefaultOn,
78-
speakerOn: video.cameraDefaultOn ? true : audio.defaultDevice == .speaker,
79-
audioOutputOn: audio.speakerDefaultOn,
80-
cameraPosition: video.cameraFacing == .back ? .back : .front
81-
)
82-
}
83-
}
84-
85100
public extension CallSettings {
86-
func withUpdatedCameraPosition(_ cameraPosition: CameraPosition) -> CallSettings {
101+
func withUpdatedCameraPosition(
102+
_ cameraPosition: CameraPosition,
103+
file: StaticString = #file,
104+
function: StaticString = #function,
105+
line: UInt = #line
106+
) -> CallSettings {
87107
CallSettings(
88108
audioOn: audioOn,
89109
videoOn: videoOn,
90110
speakerOn: speakerOn,
91111
audioOutputOn: audioOutputOn,
92-
cameraPosition: cameraPosition
112+
cameraPosition: cameraPosition,
113+
file: file,
114+
function: function,
115+
line: line
93116
)
94117
}
95118

96-
func withUpdatedAudioState(_ audioOn: Bool) -> CallSettings {
119+
func withUpdatedAudioState(
120+
_ audioOn: Bool,
121+
file: StaticString = #file,
122+
function: StaticString = #function,
123+
line: UInt = #line
124+
) -> CallSettings {
97125
CallSettings(
98126
audioOn: audioOn,
99127
videoOn: videoOn,
100128
speakerOn: speakerOn,
101129
audioOutputOn: audioOutputOn,
102-
cameraPosition: cameraPosition
130+
cameraPosition: cameraPosition,
131+
file: file,
132+
function: function,
133+
line: line
103134
)
104135
}
105136

106-
func withUpdatedVideoState(_ videoOn: Bool) -> CallSettings {
137+
func withUpdatedVideoState(
138+
_ videoOn: Bool,
139+
file: StaticString = #file,
140+
function: StaticString = #function,
141+
line: UInt = #line
142+
) -> CallSettings {
107143
CallSettings(
108144
audioOn: audioOn,
109145
videoOn: videoOn,
110146
speakerOn: speakerOn,
111147
audioOutputOn: audioOutputOn,
112-
cameraPosition: cameraPosition
148+
cameraPosition: cameraPosition,
149+
file: file,
150+
function: function,
151+
line: line
113152
)
114153
}
115154

116-
func withUpdatedSpeakerState(_ speakerOn: Bool) -> CallSettings {
155+
func withUpdatedSpeakerState(
156+
_ speakerOn: Bool,
157+
file: StaticString = #file,
158+
function: StaticString = #function,
159+
line: UInt = #line
160+
) -> CallSettings {
117161
CallSettings(
118162
audioOn: audioOn,
119163
videoOn: videoOn,
120164
speakerOn: speakerOn,
121165
audioOutputOn: audioOutputOn,
122-
cameraPosition: cameraPosition
166+
cameraPosition: cameraPosition,
167+
file: file,
168+
function: function,
169+
line: line
123170
)
124171
}
125172

126-
func withUpdatedAudioOutputState(_ audioOutputOn: Bool) -> CallSettings {
173+
func withUpdatedAudioOutputState(
174+
_ audioOutputOn: Bool,
175+
file: StaticString = #file,
176+
function: StaticString = #function,
177+
line: UInt = #line
178+
) -> CallSettings {
127179
CallSettings(
128180
audioOn: audioOn,
129181
videoOn: videoOn,
130182
speakerOn: speakerOn,
131183
audioOutputOn: audioOutputOn,
132-
cameraPosition: cameraPosition
184+
cameraPosition: cameraPosition,
185+
file: file,
186+
function: function,
187+
line: line
133188
)
134189
}
135190
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import Foundation
6+
7+
extension CallSettingsResponse {
8+
9+
/// Determines if the speaker should be enabled based on a priority hierarchy of
10+
/// settings.
11+
///
12+
/// The priority order is as follows:
13+
/// 1. If video camera is set to be on by default, speaker is enabled
14+
/// 2. If audio speaker is set to be on by default, speaker is enabled
15+
/// 3. If the default audio device is set to speaker, speaker is enabled
16+
///
17+
/// This ensures that the speaker state aligns with the most important user
18+
/// preference or system requirement.
19+
var speakerOnWithSettingsPriority: Bool {
20+
if video.cameraDefaultOn {
21+
return true
22+
} else if audio.speakerDefaultOn {
23+
return true
24+
} else {
25+
return audio.defaultDevice == .speaker
26+
}
27+
}
28+
}

Sources/StreamVideo/WebRTC/v2/WebRTCAuthenticator.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,7 +81,7 @@ struct WebRTCAuthenticator: WebRTCAuthenticating {
8181
)
8282
} else {
8383
await coordinator.stateAdapter.set(
84-
callSettings: response.call.settings.toCallSettings
84+
callSettings: .init(response.call.settings)
8585
)
8686
}
8787

Sources/StreamVideoSwiftUI/CallViewModel.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -424,7 +424,7 @@ open class CallViewModel: ObservableObject {
424424
do {
425425
let call = streamVideo.call(callType: callType, callId: callId)
426426
let info = try await call.get()
427-
self.callSettings = info.call.settings.toCallSettings
427+
self.callSettings = .init(info.call.settings)
428428
} catch {
429429
log.error(error)
430430
}

StreamVideo.xcodeproj/project.pbxproj

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,8 @@
209209
40429D612C779B7000AC7FFF /* SFUSignalService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 40C689192C64F74F0054528A /* SFUSignalService.swift */; };
210210
4045D9D52DAD35790077A660 /* Resource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4045D9D42DAD35790077A660 /* Resource.swift */; };
211211
4045D9DD2DAD5B110077A660 /* Resource_Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4045D9DC2DAD5B110077A660 /* Resource_Tests.swift */; };
212+
4045D9D92DAD4BD50077A660 /* CallSettingsResponse+SettingsPriority.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4045D9D82DAD4BD50077A660 /* CallSettingsResponse+SettingsPriority.swift */; };
213+
4045D9DB2DAD57570077A660 /* CallSettingsResponse+SettingsPriorityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4045D9DA2DAD57570077A660 /* CallSettingsResponse+SettingsPriorityTests.swift */; };
212214
4046DEE92A9E381F00CA6D2F /* AppIntentVocabulary.plist in Resources */ = {isa = PBXBuildFile; fileRef = 4046DEE82A9E381F00CA6D2F /* AppIntentVocabulary.plist */; };
213215
4046DEF02A9F469100CA6D2F /* GDPerformanceView-Swift in Frameworks */ = {isa = PBXBuildFile; productRef = 4046DEEF2A9F469100CA6D2F /* GDPerformanceView-Swift */; settings = {ATTRIBUTES = (Required, ); }; };
214216
4046DEF22A9F510C00CA6D2F /* DebugMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4046DEF12A9F510C00CA6D2F /* DebugMenu.swift */; };
@@ -1701,6 +1703,8 @@
17011703
40429D5E2C779B3D00AC7FFF /* ScreenShareSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ScreenShareSession.swift; sourceTree = "<group>"; };
17021704
4045D9D42DAD35790077A660 /* Resource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resource.swift; sourceTree = "<group>"; };
17031705
4045D9DC2DAD5B110077A660 /* Resource_Tests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Resource_Tests.swift; sourceTree = "<group>"; };
1706+
4045D9D82DAD4BD50077A660 /* CallSettingsResponse+SettingsPriority.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallSettingsResponse+SettingsPriority.swift"; sourceTree = "<group>"; };
1707+
4045D9DA2DAD57570077A660 /* CallSettingsResponse+SettingsPriorityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CallSettingsResponse+SettingsPriorityTests.swift"; sourceTree = "<group>"; };
17041708
4046DEE82A9E381F00CA6D2F /* AppIntentVocabulary.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = AppIntentVocabulary.plist; sourceTree = "<group>"; };
17051709
4046DEEA2A9E38DC00CA6D2F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
17061710
4046DEF12A9F510C00CA6D2F /* DebugMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugMenu.swift; sourceTree = "<group>"; };
@@ -4305,6 +4309,7 @@
43054309
40ADB8592D64AFEC00B06AAF /* Stream_Video_Sfu_Models_Participant+CallParticipant.swift */,
43064310
40DFA88C2CC10FEF003DCE05 /* Stream_Video_Sfu_Models_AppleThermalState+Convenience.swift */,
43074311
40C2B5BA2C2C41DA00EC2C2D /* RejectCallRequest+Reason.swift */,
4312+
4045D9D82DAD4BD50077A660 /* CallSettingsResponse+SettingsPriority.swift */,
43084313
);
43094314
path = Extensions;
43104315
sourceTree = "<group>";
@@ -5080,6 +5085,7 @@
50805085
842747E829EED0AB00E063AD /* Models */ = {
50815086
isa = PBXGroup;
50825087
children = (
5088+
4045D9DA2DAD57570077A660 /* CallSettingsResponse+SettingsPriorityTests.swift */,
50835089
4029E9542CB9436F00E1D571 /* IncomingVideoQualitySettings_Tests.swift */,
50845090
842747EB29EED59000E063AD /* JSONDecoder_Tests.swift */,
50855091
8414081429F28FFC00FF2D7C /* CallSettings_Tests.swift */,
@@ -7027,6 +7033,7 @@
70277033
84BAD77E2A6BFFB200733156 /* BroadcastSampleHandler.swift in Sources */,
70287034
40C2B5BB2C2C41DA00EC2C2D /* RejectCallRequest+Reason.swift in Sources */,
70297035
40C9E4482C94743800802B28 /* Stream_Video_Sfu_Signal_TrackSubscriptionDetails+Convenience.swift in Sources */,
7036+
4045D9D92DAD4BD50077A660 /* CallSettingsResponse+SettingsPriority.swift in Sources */,
70307037
40034C282CFE156800A318B1 /* CallKitAvailabilityPolicy.swift in Sources */,
70317038
40F1016C2D5A654300C49481 /* DefaultAudioSessionPolicy.swift in Sources */,
70327039
840042C92A6FF9A200917B30 /* BroadcastConstants.swift in Sources */,
@@ -7522,6 +7529,7 @@
75227529
40B48C212D14CB1B002C4EAB /* Int_DefaultValuesTests.swift in Sources */,
75237530
40F017612BBEF15E00E89FD1 /* CallParticipantResponse+Dummy.swift in Sources */,
75247531
40034C2E2CFE15AC00A318B1 /* CallKitRegionBasedAvailabilityPolicy.swift in Sources */,
7532+
4045D9DB2DAD57570077A660 /* CallSettingsResponse+SettingsPriorityTests.swift in Sources */,
75257533
406B3C552C92031000FC93A1 /* WebRTCCoordinatorStateMachine_JoiningStageTests.swift in Sources */,
75267534
40C9E4642C99886900802B28 /* WebRTCCoorindator_Tests.swift in Sources */,
75277535
40F017772BBEF43B00E89FD1 /* CallSessionParticipantLeftEvent+Dummy.swift in Sources */,

StreamVideoSwiftUITests/CallView/CallControls/Stateless/StatelessAudioOutputIconView_Tests.swift

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@ final class StatelessAudioOutputIconView_Tests: StreamVideoUITestCase, @unchecke
3838

3939
@MainActor
4040
private func makeSubject(
41-
_ speakerOn: Bool,
41+
_ audioOutputOn: Bool,
4242
actionHandler: (() -> Void)? = nil,
4343
file: StaticString = #file,
4444
line: UInt = #line
@@ -51,13 +51,8 @@ final class StatelessAudioOutputIconView_Tests: StreamVideoUITestCase, @unchecke
5151
file: file,
5252
line: line
5353
)
54-
call.state.update(
55-
from: .dummy(
56-
settings: .dummy(
57-
audio: .dummy(speakerDefaultOn: speakerOn)
58-
)
59-
)
60-
)
54+
55+
call.state.callSettings = .init(audioOutputOn: audioOutputOn)
6156

6257
return .init(call: call, actionHandler: actionHandler)
6358
}

0 commit comments

Comments
 (0)