Skip to content

Commit d5c90bf

Browse files
committed
RTCAudioStore implementation
1 parent 71456d3 commit d5c90bf

34 files changed

+1095
-167
lines changed

Sources/StreamVideo/Call.swift

Lines changed: 4 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,7 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
147147
notify: Bool = false,
148148
callSettings: CallSettings? = nil
149149
) async throws -> JoinCallResponse {
150+
let joinSource = await state.joinSource ?? .inApp
150151
let result: Any? = stateMachine.withLock { currentStage, transitionHandler in
151152
if
152153
currentStage.id == .joined,
@@ -194,6 +195,7 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
194195
options: options,
195196
ring: ring,
196197
notify: notify,
198+
source: joinSource,
197199
deliverySubject: deliverySubject
198200
)
199201
)
@@ -1371,8 +1373,8 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
13711373
/// - Parameter policy: A conforming `AudioSessionPolicy` that defines
13721374
/// the audio session configuration to be applied.
13731375
/// - Throws: An error if the update fails.
1374-
public func updateAudioSessionPolicy(_ policy: AudioSessionPolicy) async throws {
1375-
try await callController.updateAudioSessionPolicy(policy)
1376+
public func updateAudioSessionPolicy(_ policy: AudioSessionPolicy) async {
1377+
await callController.updateAudioSessionPolicy(policy)
13761378
}
13771379

13781380
/// Adds a proximity policy to manage device proximity behavior during the call.
@@ -1473,28 +1475,6 @@ public class Call: @unchecked Sendable, WSEventsSubscriber {
14731475
)
14741476
}
14751477

1476-
// MARK: - CallKit
1477-
1478-
/// Notifies the `Call` instance that CallKit has activated the system audio
1479-
/// session.
1480-
///
1481-
/// This method should be called when the system activates the `AVAudioSession`
1482-
/// as a result of an incoming or outgoing CallKit-managed call. It allows the
1483-
/// call to update the provided CallKit AVAudioSession based on the internal CallSettings.
1484-
///
1485-
/// - Parameter audioSession: The active `AVAudioSession` instance provided by
1486-
/// CallKit.
1487-
/// - Throws: An error if the call controller fails to handle the activation.
1488-
internal func callKitActivated(_ audioSession: AVAudioSessionProtocol) async throws {
1489-
try await callController.callKitActivated(audioSession)
1490-
didPerform(.didActivateAudioSession)
1491-
}
1492-
1493-
func callKitDeactivated(_ audioSession: AVAudioSessionProtocol) async throws {
1494-
try await callController.callKitDeactivated(audioSession)
1495-
didPerform(.didDeactivateAudioSession)
1496-
}
1497-
14981478
internal func didPerform(_ action: WebRTCTrace.CallKitAction) {
14991479
Task(disposableBag: disposableBag) { [weak callController] in
15001480
await callController?.didPerform(action)

Sources/StreamVideo/CallKit/CallKitAdapter.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ open class CallKitAdapter {
1212
@Injected(\.callKitPushNotificationAdapter) private var callKitPushNotificationAdapter
1313
@Injected(\.callKitService) private var callKitService
1414
@Injected(\.currentDevice) private var currentDevice
15+
@Injected(\.audioStore) private var audioStore
1516

1617
private var loggedInStateCancellable: AnyCancellable?
1718

@@ -73,6 +74,7 @@ open class CallKitAdapter {
7374
}
7475

7576
callKitService.streamVideo = streamVideo
77+
audioStore.add(CallKitAudioSessionReducer())
7678

7779
guard streamVideo != nil else {
7880
unregisterForIncomingCalls()

Sources/StreamVideo/CallKit/CallKitService.swift

Lines changed: 4 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
1515
@Injected(\.callCache) private var callCache
1616
@Injected(\.uuidFactory) private var uuidFactory
1717
@Injected(\.currentDevice) private var currentDevice
18+
@Injected(\.audioStore) private var audioStore
1819
private let disposableBag = DisposableBag()
1920

2021
/// Represents a call that is being managed by the service.
@@ -393,20 +394,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
393394
subsystems: .callKit
394395
)
395396

396-
guard
397-
let active,
398-
let call = callEntry(for: active)?.call
399-
else {
400-
return
401-
}
402-
403-
Task {
404-
do {
405-
try await call.callKitActivated(audioSession)
406-
} catch {
407-
log.error(error, subsystems: .callKit)
408-
}
409-
}
397+
audioStore.dispatch(.callKit(.activate(audioSession)))
410398
}
411399

412400
public func provider(
@@ -426,21 +414,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
426414
subsystems: .callKit
427415
)
428416

429-
guard
430-
let active,
431-
let call = callEntry(for: active)?.call
432-
else {
433-
RTCAudioSession.sharedInstance().audioSessionDidActivate(audioSession)
434-
return
435-
}
436-
437-
Task {
438-
do {
439-
try await call.callKitDeactivated(audioSession)
440-
} catch {
441-
log.error(error, subsystems: .callKit)
442-
}
443-
}
417+
audioStore.dispatch(.callKit(.deactivate(audioSession)))
444418
}
445419

446420
open func provider(
@@ -475,6 +449,7 @@ open class CallKitService: NSObject, CXProviderDelegate, @unchecked Sendable {
475449
}
476450

477451
do {
452+
callToJoinEntry.call.state.joinSource = .callKit
478453
try await callToJoinEntry.call.join(callSettings: callSettings)
479454
action.fulfill()
480455
} catch {

Sources/StreamVideo/CallState.swift

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,9 @@ public class CallState: ObservableObject {
155155
}
156156
}
157157
}
158-
158+
159+
var joinSource: JoinSource?
160+
159161
private var localCallSettingsUpdate = false
160162
private var durationCancellable: AnyCancellable?
161163
private nonisolated let disposableBag = DisposableBag()
@@ -524,3 +526,5 @@ public class CallState: ObservableObject {
524526
durationCancellable = nil
525527
}
526528
}
529+
530+
enum JoinSource { case inApp, callKit }

Sources/StreamVideo/CallStateMachine/Stages/Call+JoiningStage.swift

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ extension Call.StateMachine.Stage {
122122
callSettings: input.callSettings,
123123
options: input.options,
124124
ring: input.ring,
125-
notify: input.notify
125+
notify: input.notify,
126+
source: input.source
126127
)
127128

128129
if let callSettings = input.callSettings {

Sources/StreamVideo/CallStateMachine/Stages/Call+Stage.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ extension Call.StateMachine {
3030
var options: CreateCallOptions?
3131
var ring: Bool
3232
var notify: Bool
33+
var source: JoinSource
3334
var deliverySubject: PassthroughSubject<JoinCallResponse, Error>
3435

3536
var currentNumberOfRetries = 0

Sources/StreamVideo/Controllers/CallController.swift

Lines changed: 6 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ class CallController: @unchecked Sendable {
122122
callSettings: CallSettings?,
123123
options: CreateCallOptions? = nil,
124124
ring: Bool = false,
125-
notify: Bool = false
125+
notify: Bool = false,
126+
source: JoinSource
126127
) async throws -> JoinCallResponse {
127128
joinCallResponseSubject = .init(nil)
128129

@@ -131,7 +132,8 @@ class CallController: @unchecked Sendable {
131132
callSettings: callSettings,
132133
options: options,
133134
ring: ring,
134-
notify: notify
135+
notify: notify,
136+
source: source
135137
)
136138

137139
guard
@@ -479,8 +481,8 @@ class CallController: @unchecked Sendable {
479481
///
480482
/// - Parameter policy: The audio session policy to apply
481483
/// - Throws: An error if the policy update fails
482-
func updateAudioSessionPolicy(_ policy: AudioSessionPolicy) async throws {
483-
try await webRTCCoordinator.updateAudioSessionPolicy(policy)
484+
func updateAudioSessionPolicy(_ policy: AudioSessionPolicy) async {
485+
await webRTCCoordinator.updateAudioSessionPolicy(policy)
484486
}
485487

486488
/// Sets up observation of WebRTC state changes.
@@ -501,14 +503,6 @@ class CallController: @unchecked Sendable {
501503
.sink { [weak self] in self?.webRTCClientDidUpdateStage($0) }
502504
}
503505

504-
internal func callKitActivated(_ audioSession: AVAudioSessionProtocol) async throws {
505-
try await webRTCCoordinator.callKitActivated(audioSession)
506-
}
507-
508-
func callKitDeactivated(_ audioSession: AVAudioSessionProtocol) async throws {
509-
try await webRTCCoordinator.callKitDeactivated(audioSession)
510-
}
511-
512506
// MARK: - Client Capabilities
513507

514508
func enableClientCapabilities(_ capabilities: Set<ClientCapability>) async {

Sources/StreamVideo/StreamVideo.swift

Lines changed: 6 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
1616

1717
@Injected(\.callCache) private var callCache
1818
@Injected(\.screenProperties) private var screenProperties
19+
@Injected(\.audioStore) private var audioStore
1920

2021
private enum DisposableKey: String { case ringEventReceived }
2122

@@ -202,31 +203,11 @@ public class StreamVideo: ObservableObject, @unchecked Sendable {
202203
self?.token = userToken
203204
}
204205

205-
let configuration = RTCAudioSessionConfiguration.webRTC()
206-
configuration.category = AVAudioSession.Category.playAndRecord.rawValue
207-
configuration.mode = AVAudioSession.Mode.videoChat.rawValue
208-
configuration.categoryOptions = [.allowBluetooth]
209-
RTCAudioSessionConfiguration.setWebRTC(configuration)
210-
RTCAudioSession.sharedInstance().lockForConfiguration()
211-
try? RTCAudioSession.sharedInstance().setConfiguration(configuration)
212-
if #available(iOS 14.5, *) {
213-
do {
214-
try RTCAudioSession
215-
.sharedInstance()
216-
.session
217-
.setPrefersNoInterruptionsFromSystemAlerts(true)
218-
log.debug(
219-
"AudioSession setPrefersNoInterruptionsFromSystemAlerts:\(true) completed.",
220-
subsystems: .audioSession
221-
)
222-
} catch {
223-
log.error(
224-
"AudioSession was unable to setPrefersNoInterruptionsFromSystemAlerts:\(true). \(error)",
225-
subsystems: .audioSession
226-
)
227-
}
228-
}
229-
RTCAudioSession.sharedInstance().unlockForConfiguration()
206+
audioStore
207+
.publisher
208+
.log(.debug, subsystems: .audioSession) { "AudioStore state: \($0)" }
209+
.sink { _ in }
210+
.store(in: disposableBag)
230211

231212
// Warm up
232213
_ = eventNotificationCenter

Sources/StreamVideo/Utils/AudioSession/AudioRecorder/StreamCallAudioRecorder.swift

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ open class StreamCallAudioRecorder: @unchecked Sendable {
1515
private let processingQueue = OperationQueue(maxConcurrentOperationCount: 1)
1616

1717
@Injected(\.activeCallProvider) private var activeCallProvider
18-
@Injected(\.activeCallAudioSession) private var activeCallAudioSession
18+
@Injected(\.audioStore) private var audioStore
1919

2020
/// The builder used to create the AVAudioRecorder instance.
2121
let audioRecorderBuilder: AVAudioRecorderBuilder
@@ -38,7 +38,6 @@ open class StreamCallAudioRecorder: @unchecked Sendable {
3838

3939
@Atomic private(set) var isRecording: Bool = false {
4040
willSet {
41-
activeCallAudioSession?.isRecording = newValue
4241
_isRecordingSubject.send(newValue)
4342
}
4443
}
@@ -194,7 +193,7 @@ open class StreamCallAudioRecorder: @unchecked Sendable {
194193

195194
private func setUpAudioCaptureIfRequired() async throws -> AVAudioRecorder {
196195
guard
197-
await activeCallAudioSession?.requestRecordPermission() == true
196+
await audioStore.requestRecordPermission() == true
198197
else {
199198
throw ClientError("🎙️Permission denied.")
200199
}
@@ -219,11 +218,8 @@ open class StreamCallAudioRecorder: @unchecked Sendable {
219218
}
220219

221220
private func deferSessionActivation() async {
222-
guard let activeCallAudioSession else {
223-
return
224-
}
225-
_ = try? await activeCallAudioSession
226-
.$category
221+
_ = try? await audioStore
222+
.publisher(\.category)
227223
.filter { $0 == .playAndRecord }
228224
.nextValue(timeout: 1)
229225
}

Sources/StreamVideo/Utils/AudioSession/AudioSessionConfiguration.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,7 @@
55
import AVFoundation
66

77
/// Represents the audio session configuration.
8-
public struct AudioSessionConfiguration: ReflectiveStringConvertible,
9-
Equatable {
8+
public struct AudioSessionConfiguration: ReflectiveStringConvertible, Equatable, Sendable {
109
/// The audio session category.
1110
var category: AVAudioSession.Category
1211
/// The audio session mode.

0 commit comments

Comments
 (0)