Skip to content

Commit 23ac124

Browse files
committed
Add tests
1 parent b3ccfdd commit 23ac124

23 files changed

+1551
-648
lines changed

Sources/StreamVideo/Utils/AudioSession/Policies/LivestreamAudioSessionPolicy.swift

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,9 @@ public struct LivestreamAudioSessionPolicy: AudioSessionPolicy {
1111
public init() {}
1212

1313
/// Builds the configuration used when a call toggles livestream mode.
14-
/// Stereo playout is preferred, but the policy falls back to playback if
15-
/// the current user cannot transmit audio.
14+
/// Stereo playout is preferred (thus the category and the options), but the policy falls back to playback
15+
/// category if the current user cannot transmit audio. A2DP is required to allow external devices
16+
/// to play stereo.
1617
public func configuration(
1718
for callSettings: CallSettings,
1819
ownCapabilities: Set<OwnCapability>

Sources/StreamVideo/Utils/AudioSession/RTCAudioStore/Components/RTCAudioSessionPublisher.swift

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,6 @@ final class RTCAudioSessionPublisher: NSObject, RTCAudioSessionDelegate, @unchec
2828

2929
private let source: RTCAudioSession
3030
private let subject: PassthroughSubject<Event, Never> = .init()
31-
private let disposableBag = DisposableBag()
3231

3332
/// Creates a publisher for the provided WebRTC audio session.
3433
/// - Parameter source: The session to observe.
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ extension RTCAudioStore {
1010

1111
/// Converts audio session interruption callbacks into store actions so the
1212
/// audio pipeline can gracefully pause and resume.
13-
final class InterruptionsMiddleware: Middleware<RTCAudioStore.Namespace>, @unchecked Sendable {
13+
final class InterruptionsEffect: StoreEffect<RTCAudioStore.Namespace>, @unchecked Sendable {
1414

1515
private let audioSessionObserver: RTCAudioSessionPublisher
1616
private let disposableBag = DisposableBag()

Sources/StreamVideo/Utils/AudioSession/RTCAudioStore/Namespace/Effects/RTCAudioStore+StereoPlayoutEffect.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ extension RTCAudioStore {
2121
) {
2222
audioDeviceModuleCancellable?.cancel()
2323
audioDeviceModuleCancellable = nil
24+
processingQueue.cancelAllOperations()
25+
disposableBag.removeAll()
2426

2527
guard let statePublisher else {
2628
return

Sources/StreamVideo/Utils/AudioSession/RTCAudioStore/Namespace/RTCAudioStore+Namespace.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,13 +27,13 @@ extension RTCAudioStore {
2727

2828
static func middleware(audioSession: RTCAudioSession) -> [Middleware<RTCAudioStore.Namespace>] {
2929
[
30-
InterruptionsMiddleware(audioSession),
3130
AudioDeviceModuleMiddleware()
3231
]
3332
}
3433

3534
static func effects(audioSession: RTCAudioSession) -> Set<StoreEffect<RTCAudioStore.Namespace>> {
3635
[
36+
InterruptionsEffect(audioSession),
3737
StereoPlayoutEffect(),
3838
RouteChangeEffect(audioSession),
3939
AVAudioSessionEffect()

StreamVideo.xcodeproj/project.pbxproj

Lines changed: 58 additions & 12 deletions
Large diffs are not rendered by default.
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 Combine
6+
import Foundation
7+
@testable import StreamVideo
8+
9+
extension StoreNamespace {
10+
11+
static func makeMockDispatcher() -> MockStoreDispatcher<Self> {
12+
.init()
13+
}
14+
}
15+
16+
struct MockStoreDispatcher<Namespace: StoreNamespace>: @unchecked Sendable {
17+
18+
var recordedActions: [StoreActionBox<Namespace.Action>] { subject.value }
19+
var publisher: AnyPublisher<[StoreActionBox<Namespace.Action>], Never> { subject.eraseToAnyPublisher() }
20+
private let subject: CurrentValueSubject<[StoreActionBox<Namespace.Action>], Never> = .init([])
21+
22+
func handle(
23+
actions: [StoreActionBox<Namespace.Action>]
24+
) {
25+
let value = subject.value
26+
subject.send(value + actions)
27+
}
28+
}
Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,130 @@
1+
//
2+
// Copyright © 2025 Stream.io Inc. All rights reserved.
3+
//
4+
5+
import AVFoundation
6+
import Foundation
7+
@testable import StreamVideo
8+
9+
extension RTCAudioStore.StoreState.AudioRoute {
10+
11+
static func dummy(
12+
inputs: [RTCAudioStore.StoreState.AudioRoute.Port] = [],
13+
outputs: [RTCAudioStore.StoreState.AudioRoute.Port] = [],
14+
reason: AVAudioSession.RouteChangeReason = .unknown
15+
) -> RTCAudioStore.StoreState.AudioRoute {
16+
.init(
17+
inputs: inputs,
18+
outputs: outputs,
19+
reason: reason
20+
)
21+
}
22+
}
23+
24+
extension RTCAudioStore.StoreState.AudioRoute.Port {
25+
26+
static func dummy(
27+
type: String = .unique,
28+
name: String = .unique,
29+
id: String = .unique,
30+
isExternal: Bool = false,
31+
isSpeaker: Bool = false,
32+
isReceiver: Bool = false,
33+
channels: Int = 0
34+
) -> RTCAudioStore.StoreState.AudioRoute.Port {
35+
.init(
36+
type: type,
37+
name: name,
38+
id: id,
39+
isExternal: isExternal,
40+
isSpeaker: isSpeaker,
41+
isReceiver: isReceiver,
42+
channels: channels
43+
)
44+
}
45+
}
46+
47+
extension RTCAudioStore.StoreState.AVAudioSessionConfiguration {
48+
49+
static func dummy(
50+
category: AVAudioSession.Category = .soloAmbient,
51+
mode: AVAudioSession.Mode = .default,
52+
options: AVAudioSession.CategoryOptions = [],
53+
overrideOutputAudioPort: AVAudioSession.PortOverride = .none
54+
) -> RTCAudioStore.StoreState.AVAudioSessionConfiguration {
55+
.init(
56+
category: category,
57+
mode: mode,
58+
options: options,
59+
overrideOutputAudioPort: overrideOutputAudioPort
60+
)
61+
}
62+
}
63+
64+
extension RTCAudioStore.StoreState.WebRTCAudioSessionConfiguration {
65+
66+
static func dummy(
67+
isAudioEnabled: Bool = false,
68+
useManualAudio: Bool = false,
69+
prefersNoInterruptionsFromSystemAlerts: Bool = false
70+
) -> RTCAudioStore.StoreState.WebRTCAudioSessionConfiguration {
71+
.init(
72+
isAudioEnabled: isAudioEnabled,
73+
useManualAudio: useManualAudio,
74+
prefersNoInterruptionsFromSystemAlerts: prefersNoInterruptionsFromSystemAlerts
75+
)
76+
}
77+
}
78+
79+
extension RTCAudioStore.StoreState.StereoConfiguration {
80+
81+
static func dummy(
82+
playout: RTCAudioStore.StoreState.StereoConfiguration.Playout = .dummy()
83+
) -> RTCAudioStore.StoreState.StereoConfiguration {
84+
.init(
85+
playout: playout
86+
)
87+
}
88+
}
89+
90+
extension RTCAudioStore.StoreState.StereoConfiguration.Playout {
91+
92+
static func dummy(
93+
preferred: Bool = false,
94+
enabled: Bool = false
95+
) -> RTCAudioStore.StoreState.StereoConfiguration.Playout {
96+
.init(
97+
preferred: preferred,
98+
enabled: enabled
99+
)
100+
}
101+
}
102+
103+
extension RTCAudioStore.StoreState {
104+
105+
static func dummy(
106+
isActive: Bool = false,
107+
isInterrupted: Bool = false,
108+
isRecording: Bool = false,
109+
isMicrophoneMuted: Bool = false,
110+
hasRecordingPermission: Bool = false,
111+
audioDeviceModule: AudioDeviceModule? = nil,
112+
currentRoute: AudioRoute = .dummy(),
113+
audioSessionConfiguration: AVAudioSessionConfiguration = .dummy(),
114+
webRTCAudioSessionConfiguration: WebRTCAudioSessionConfiguration = .dummy(),
115+
stereoConfiguration: StereoConfiguration = .dummy()
116+
) -> RTCAudioStore.StoreState {
117+
.init(
118+
isActive: isActive,
119+
isInterrupted: isInterrupted,
120+
isRecording: isRecording,
121+
isMicrophoneMuted: isMicrophoneMuted,
122+
hasRecordingPermission: hasRecordingPermission,
123+
audioDeviceModule: audioDeviceModule,
124+
currentRoute: currentRoute,
125+
audioSessionConfiguration: audioSessionConfiguration,
126+
webRTCAudioSessionConfiguration: webRTCAudioSessionConfiguration,
127+
stereoConfiguration: stereoConfiguration
128+
)
129+
}
130+
}

0 commit comments

Comments
 (0)