Skip to content

Commit 20f8c3e

Browse files
authored
Audio engine observer fixes (#571)
1 parent 9e750d1 commit 20f8c3e

File tree

6 files changed

+62
-35
lines changed

6 files changed

+62
-35
lines changed

Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -40,49 +40,49 @@ class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate
4040

4141
func audioDeviceModule(_: LKRTCAudioDeviceModule, didCreateEngine engine: AVAudioEngine) {
4242
guard let audioManager else { return }
43-
let entryPoint = audioManager._state.engineObservers.buildChain()
43+
let entryPoint = audioManager.buildEngineObserverChain()
4444
entryPoint?.engineDidCreate(engine)
4545
}
4646

4747
func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
4848
guard let audioManager else { return }
49-
let entryPoint = audioManager._state.engineObservers.buildChain()
49+
let entryPoint = audioManager.buildEngineObserverChain()
5050
entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
5151
}
5252

5353
func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
5454
guard let audioManager else { return }
55-
let entryPoint = audioManager._state.engineObservers.buildChain()
55+
let entryPoint = audioManager.buildEngineObserverChain()
5656
entryPoint?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
5757
}
5858

5959
func audioDeviceModule(_: LKRTCAudioDeviceModule, didStopEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
6060
guard let audioManager else { return }
61-
let entryPoint = audioManager._state.engineObservers.buildChain()
61+
let entryPoint = audioManager.buildEngineObserverChain()
6262
entryPoint?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
6363
}
6464

6565
func audioDeviceModule(_: LKRTCAudioDeviceModule, didDisableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
6666
guard let audioManager else { return }
67-
let entryPoint = audioManager._state.engineObservers.buildChain()
67+
let entryPoint = audioManager.buildEngineObserverChain()
6868
entryPoint?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
6969
}
7070

7171
func audioDeviceModule(_: LKRTCAudioDeviceModule, willReleaseEngine engine: AVAudioEngine) {
7272
guard let audioManager else { return }
73-
let entryPoint = audioManager._state.engineObservers.buildChain()
73+
let entryPoint = audioManager.buildEngineObserverChain()
7474
entryPoint?.engineWillRelease(engine)
7575
}
7676

7777
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat) -> Bool {
7878
guard let audioManager else { return false }
79-
let entryPoint = audioManager._state.engineObservers.buildChain()
79+
let entryPoint = audioManager.buildEngineObserverChain()
8080
return entryPoint?.engineWillConnectInput(engine, src: src, dst: dst, format: format) ?? false
8181
}
8282

8383
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat) -> Bool {
8484
guard let audioManager else { return false }
85-
let entryPoint = audioManager._state.engineObservers.buildChain()
85+
let entryPoint = audioManager.buildEngineObserverChain()
8686
return entryPoint?.engineWillConnectOutput(engine, src: src, dst: dst, format: format) ?? false
8787
}
8888
}

Sources/LiveKit/Audio/AudioEngineObserver.swift

Lines changed: 29 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ import AVFAudio
1818

1919
/// Do not retain the engine object.
2020
public protocol AudioEngineObserver: NextInvokable, Sendable {
21-
func setNext(_ handler: any AudioEngineObserver)
21+
associatedtype Next = any AudioEngineObserver
22+
var next: (any AudioEngineObserver)? { get set }
2223

2324
func engineDidCreate(_ engine: AVAudioEngine)
2425
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool)
@@ -39,25 +40,35 @@ public protocol AudioEngineObserver: NextInvokable, Sendable {
3940

4041
/// Default implementation to make it optional.
4142
public extension AudioEngineObserver {
42-
func engineDidCreate(_: AVAudioEngine) {}
43-
func engineWillEnable(_: AVAudioEngine, isPlayoutEnabled _: Bool, isRecordingEnabled _: Bool) {}
44-
func engineWillStart(_: AVAudioEngine, isPlayoutEnabled _: Bool, isRecordingEnabled _: Bool) {}
45-
func engineDidStop(_: AVAudioEngine, isPlayoutEnabled _: Bool, isRecordingEnabled _: Bool) {}
46-
func engineDidDisable(_: AVAudioEngine, isPlayoutEnabled _: Bool, isRecordingEnabled _: Bool) {}
47-
func engineWillRelease(_: AVAudioEngine) {}
48-
49-
func engineWillConnectOutput(_: AVAudioEngine, src _: AVAudioNode, dst _: AVAudioNode?, format _: AVAudioFormat) -> Bool { false }
50-
func engineWillConnectInput(_: AVAudioEngine, src _: AVAudioNode?, dst _: AVAudioNode, format _: AVAudioFormat) -> Bool { false }
51-
}
43+
func engineDidCreate(_ engine: AVAudioEngine) {
44+
next?.engineDidCreate(engine)
45+
}
46+
47+
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
48+
next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
49+
}
50+
51+
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
52+
next?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
53+
}
5254

53-
extension [any AudioEngineObserver] {
54-
func buildChain() -> Element? {
55-
guard let first else { return nil }
55+
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
56+
next?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
57+
}
58+
59+
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
60+
next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
61+
}
5662

57-
for i in 0 ..< count - 1 {
58-
self[i].setNext(self[i + 1])
59-
}
63+
func engineWillRelease(_ engine: AVAudioEngine) {
64+
next?.engineWillRelease(engine)
65+
}
66+
67+
func engineWillConnectOutput(_ engine: AVAudioEngine, src: AVAudioNode, dst: AVAudioNode?, format: AVAudioFormat) -> Bool {
68+
next?.engineWillConnectOutput(engine, src: src, dst: dst, format: format) ?? false
69+
}
6070

61-
return first
71+
func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat) -> Bool {
72+
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format) ?? false
6273
}
6374
}

Sources/LiveKit/Audio/DefaultAudioSessionObserver.swift

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ internal import LiveKitWebRTC
2424
@_implementationOnly import LiveKitWebRTC
2525
#endif
2626

27-
public final class DefaultAudioSessionObserver: AudioEngineObserver, Loggable {
27+
public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchecked Sendable {
2828
struct State {
2929
var isSessionActive = false
3030
var next: (any AudioEngineObserver)?
@@ -36,7 +36,12 @@ public final class DefaultAudioSessionObserver: AudioEngineObserver, Loggable {
3636

3737
let _state = StateSync(State())
3838

39-
init() {
39+
public var next: (any AudioEngineObserver)? {
40+
get { _state.next }
41+
set { _state.mutate { $0.next = newValue } }
42+
}
43+
44+
public init() {
4045
// Backward compatibility with `customConfigureAudioSessionFunc`.
4146
_state.onDidMutate = { new_, old_ in
4247
if let config_func = AudioManager.shared._state.customConfigureFunc,
@@ -51,10 +56,6 @@ public final class DefaultAudioSessionObserver: AudioEngineObserver, Loggable {
5156
}
5257
}
5358

54-
public func setNext(_ nextHandler: any AudioEngineObserver) {
55-
_state.mutate { $0.next = nextHandler }
56-
}
57-
5859
public func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
5960
if AudioManager.shared._state.customConfigureFunc == nil {
6061
log("Configuring audio session...")

Sources/LiveKit/Protocols/NextInvokable.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,5 +18,5 @@ import Foundation
1818

1919
public protocol NextInvokable {
2020
associatedtype Next
21-
func setNext(_ handler: Next)
21+
var next: Next? { get set }
2222
}

Sources/LiveKit/Track/AudioManager.swift

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,3 +341,16 @@ public extension AudioManager {
341341
renderPreProcessingDelegateAdapter.remove(delegate: delegate)
342342
}
343343
}
344+
345+
extension AudioManager {
346+
func buildEngineObserverChain() -> (any AudioEngineObserver)? {
347+
var objects = _state.engineObservers
348+
guard !objects.isEmpty else { return nil }
349+
350+
for i in 0 ..< objects.count - 1 {
351+
objects[i].next = objects[i + 1]
352+
}
353+
354+
return objects.first
355+
}
356+
}

Tests/LiveKitTests/AudioEngineTests.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,10 @@ class AudioEngineTests: XCTestCase {
282282
}
283283

284284
final class SineWaveNodeHook: AudioEngineObserver {
285+
var next: (any LiveKit.AudioEngineObserver)?
286+
285287
let sineWaveNode = SineWaveSourceNode()
286288

287-
func setNext(_: any LiveKit.AudioEngineObserver) {}
288289
func engineDidCreate(_ engine: AVAudioEngine) {
289290
engine.attach(sineWaveNode)
290291
}
@@ -301,6 +302,8 @@ final class SineWaveNodeHook: AudioEngineObserver {
301302
}
302303

303304
final class PlayerNodeHook: AudioEngineObserver {
305+
var next: (any LiveKit.AudioEngineObserver)?
306+
304307
public let playerNode = AVAudioPlayerNode()
305308
public let playerMixerNode = AVAudioMixerNode()
306309
public let playerNodeFormat: AVAudioFormat
@@ -309,7 +312,6 @@ final class PlayerNodeHook: AudioEngineObserver {
309312
self.playerNodeFormat = playerNodeFormat
310313
}
311314

312-
func setNext(_: any LiveKit.AudioEngineObserver) {}
313315
public func engineDidCreate(_ engine: AVAudioEngine) {
314316
engine.attach(playerNode)
315317
engine.attach(playerMixerNode)

0 commit comments

Comments
 (0)