diff --git a/CHANGELOG.md b/CHANGELOG.md index 32ac06cee..9dd19fb1b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). # Upcoming +### 🐞 Fixed +- Fix permission prompts appearing when not needed. [#988](https://github.com/GetStream/stream-video-swift/pull/988) + ### ✅ Added - SwiftUI modifiers that surface moderation blur and warning events in `CallView`. [#987](https://github.com/GetStream/stream-video-swift/pull/987) diff --git a/Sources/StreamVideo/WebRTC/PeerConnectionFactory.swift b/Sources/StreamVideo/WebRTC/PeerConnectionFactory.swift index 39feab1f8..8e49eabdf 100644 --- a/Sources/StreamVideo/WebRTC/PeerConnectionFactory.swift +++ b/Sources/StreamVideo/WebRTC/PeerConnectionFactory.swift @@ -20,7 +20,7 @@ final class PeerConnectionFactory: @unchecked Sendable { ) let decoderFactory = RTCDefaultVideoDecoderFactory() return RTCPeerConnectionFactory( - audioDeviceModuleType: .platformDefault, + audioDeviceModuleType: .audioEngine, bypassVoiceProcessing: false, encoderFactory: encoderFactory, decoderFactory: decoderFactory, diff --git a/Sources/StreamVideo/WebRTC/v2/WebRTCPermissionsAdapter.swift b/Sources/StreamVideo/WebRTC/v2/WebRTCPermissionsAdapter.swift index 7e9777cf8..3507b2254 100644 --- a/Sources/StreamVideo/WebRTC/v2/WebRTCPermissionsAdapter.swift +++ b/Sources/StreamVideo/WebRTC/v2/WebRTCPermissionsAdapter.swift @@ -52,6 +52,7 @@ final class WebRTCPermissionsAdapter: @unchecked Sendable { private weak var delegate: WebRTCPermissionsAdapterDelegate? private var requiredPermissions: Set = [] private var lastCallSettings: CallSettings? + private var ownCapabilities: Set = [] /// Creates an adapter and begins observing app/permission changes. /// @@ -155,6 +156,12 @@ final class WebRTCPermissionsAdapter: @unchecked Sendable { } } + func willSet(ownCapabilities: Set) { + processingQueue.addOperation { [weak self] in + self?.ownCapabilities = ownCapabilities + } + } + func cleanUp() { processingQueue.addOperation { [weak self] in // By emptying the Set we are saying that there are no permissions @@ -237,9 +244,11 @@ final class WebRTCPermissionsAdapter: @unchecked Sendable { switch permission { case .microphone: - return !permissions.hasMicrophonePermission && permissions.canRequestMicrophonePermission + return !permissions.hasMicrophonePermission && permissions.canRequestMicrophonePermission && ownCapabilities + .contains(.sendAudio) case .camera: - return !permissions.hasCameraPermission && permissions.canRequestCameraPermission + return !permissions.hasCameraPermission && permissions.canRequestCameraPermission && ownCapabilities + .contains(.sendVideo) } } } diff --git a/Sources/StreamVideo/WebRTC/v2/WebRTCStateAdapter.swift b/Sources/StreamVideo/WebRTC/v2/WebRTCStateAdapter.swift index c7e3eff9e..13f52b846 100644 --- a/Sources/StreamVideo/WebRTC/v2/WebRTCStateAdapter.swift +++ b/Sources/StreamVideo/WebRTC/v2/WebRTCStateAdapter.swift @@ -165,7 +165,10 @@ actor WebRTCStateAdapter: ObservableObject, StreamAudioSessionAdapterDelegate, W func set(connectOptions value: ConnectOptions) { self.connectOptions = value } /// Sets the own capabilities of the current user. - func set(ownCapabilities value: Set) { self.ownCapabilities = value } + func set(ownCapabilities value: Set) { + self.ownCapabilities = value + permissionsAdapter.willSet(ownCapabilities: value) + } /// Sets the WebRTC stats reporter. func set(statsAdapter value: WebRTCStatsAdapting?) { diff --git a/StreamVideoTests/WebRTC/v2/WebRTCPermissionsAdapter_Tests.swift b/StreamVideoTests/WebRTC/v2/WebRTCPermissionsAdapter_Tests.swift index 95b8e45d8..bf98c27b3 100644 --- a/StreamVideoTests/WebRTC/v2/WebRTCPermissionsAdapter_Tests.swift +++ b/StreamVideoTests/WebRTC/v2/WebRTCPermissionsAdapter_Tests.swift @@ -22,7 +22,7 @@ final class WebRTCPermissionsAdapter_Tests: StreamVideoTestCase, @unchecked Send super.tearDown() } - func test_willSet_audioOnTrue_withDeniedMic_downgradesAudioOff() async { + func test_willSet_audioOnTrue_withoutSendAudio_withDeniedMic_downgradesAudioOff() async { mockAppStateAdapter.makeShared() mockPermissions.stubMicrophonePermission(.denied) await fulfillment { self.mockPermissions.mockStore.state.microphonePermission == .denied } @@ -34,7 +34,33 @@ final class WebRTCPermissionsAdapter_Tests: StreamVideoTestCase, @unchecked Send XCTAssertEqual(output.videoOn, false) } - func test_willSet_videoOnTrue_withDeniedCamera_downgradesVideoOff() async { + func test_willSet_audioOnTrue_withSendAudio_withDeniedMic_downgradesAudioOff() async { + mockAppStateAdapter.makeShared() + mockPermissions.stubMicrophonePermission(.denied) + await fulfillment { self.mockPermissions.mockStore.state.microphonePermission == .denied } + + let input = CallSettings(audioOn: true, videoOn: false) + subject.willSet(ownCapabilities: [.sendAudio]) + let output = await subject.willSet(callSettings: input) + + XCTAssertEqual(output.audioOn, false) + XCTAssertEqual(output.videoOn, false) + } + + func test_willSet_videoOnTrue_withSendVideo_withDeniedCamera_downgradesVideoOff() async { + mockAppStateAdapter.makeShared() + mockPermissions.stubCameraPermission(.denied) + await fulfillment { self.mockPermissions.mockStore.state.cameraPermission == .denied } + + let input = CallSettings(audioOn: false, videoOn: true) + subject.willSet(ownCapabilities: [.sendVideo]) + let output = await subject.willSet(callSettings: input) + + XCTAssertEqual(output.audioOn, false) + XCTAssertEqual(output.videoOn, false) + } + + func test_willSet_videoOnTrue_withoutSendVideo_withDeniedCamera_downgradesVideoOff() async { mockAppStateAdapter.makeShared() mockPermissions.stubCameraPermission(.denied) await fulfillment { self.mockPermissions.mockStore.state.cameraPermission == .denied } @@ -46,10 +72,22 @@ final class WebRTCPermissionsAdapter_Tests: StreamVideoTestCase, @unchecked Send XCTAssertEqual(output.videoOn, false) } - func test_willSet_audioOnTrue_unknownMic_inForeground_requestsPermission_andKeepsAudioOnWhenGranted() async { + func test_willSet_audioOnTrue_unknownMic_inForeground_withoutSendAudio_requestsPermission_andKeepsAudioOnWhenGranted() async { + mockAppStateAdapter.makeShared() + mockAppStateAdapter.stubbedState = .foreground + mockPermissions.stubMicrophonePermission(.unknown) + await fulfillment { self.mockPermissions.mockStore.state.microphonePermission == .unknown } + + let input = CallSettings(audioOn: true, videoOn: false) + let output = await self.subject.willSet(callSettings: input) + XCTAssertEqual(output.audioOn, false) + } + + func test_willSet_audioOnTrue_unknownMic_inForeground_withSendAudio_requestsPermission_andKeepsAudioOnWhenGranted() async { mockAppStateAdapter.makeShared() mockAppStateAdapter.stubbedState = .foreground mockPermissions.stubMicrophonePermission(.unknown) + subject.willSet(ownCapabilities: [.sendAudio]) await fulfillment { self.mockPermissions.mockStore.state.microphonePermission == .unknown } await withTaskGroup(of: Void.self) { group in @@ -71,10 +109,22 @@ final class WebRTCPermissionsAdapter_Tests: StreamVideoTestCase, @unchecked Send } } - func test_willSet_videoOnTrue_unknownCamera_inForeground_requestsPermission_andKeepsVideoOnWhenGranted() async { + func test_willSet_videoOnTrue_unknownCamera_inForeground_withoutSendVideo_requestsPermission_andKeepsVideoOnWhenGranted() async { + mockAppStateAdapter.makeShared() + mockAppStateAdapter.stubbedState = .foreground + mockPermissions.stubCameraPermission(.unknown) + await fulfillment { self.mockPermissions.mockStore.state.cameraPermission == .unknown } + + let input = CallSettings(audioOn: false, videoOn: true) + let output = await self.subject.willSet(callSettings: input) + XCTAssertEqual(output.videoOn, false) + } + + func test_willSet_videoOnTrue_unknownCamera_inForeground_withSendVideo_requestsPermission_andKeepsVideoOnWhenGranted() async { mockAppStateAdapter.makeShared() mockAppStateAdapter.stubbedState = .foreground mockPermissions.stubCameraPermission(.unknown) + subject.willSet(ownCapabilities: [.sendVideo]) await fulfillment { self.mockPermissions.mockStore.state.cameraPermission == .unknown } await withTaskGroup(of: Void.self) { group in