Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,45 @@ internal import LiveKitWebRTC
@_implementationOnly import LiveKitWebRTC
#endif

public class AudioEngineState: CustomDebugStringConvertible {
private let rtcState: LKRTCAudioEngineState

public var isOutputEnabled: Bool { rtcState.isOutputEnabled }
public var isOutputRunning: Bool { rtcState.isOutputRunning }
public var isInputEnabled: Bool { rtcState.isInputEnabled }
public var isInputRunning: Bool { rtcState.isInputRunning }
public var isInputMuted: Bool { rtcState.isInputMuted }
public var isLegacyMuteMode: Bool { rtcState.muteMode == .restartEngine }

init(fromRTCType rtcState: LKRTCAudioEngineState) {
self.rtcState = rtcState
}

public var debugDescription: String {
"AudioEngineState(isOutputEnabled: \(isOutputEnabled), isOutputRunning: \(isOutputRunning), isInputEnabled: \(isInputEnabled), isInputRunning: \(isInputRunning), isInputMuted: \(isInputMuted), isLegacyMuteMode: \(isLegacyMuteMode))"
}
}

public class AudioEngineStateTransition: CustomDebugStringConvertible {
private let rtcStateTransition: LKRTCAudioEngineStateTransition

public var prev: AudioEngineState { AudioEngineState(fromRTCType: rtcStateTransition.prev) }
public var next: AudioEngineState { AudioEngineState(fromRTCType: rtcStateTransition.next) }

init(fromRTCType rtcStateTransition: LKRTCAudioEngineStateTransition) {
self.rtcStateTransition = rtcStateTransition
}

public var debugDescription: String {
"AudioEngineStateTransition(prev: \(prev), next: \(next))"
}
}

// Invoked on WebRTC's worker thread, do not block.
class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate {
weak var audioManager: AudioManager?

func audioDeviceModule(_: LKRTCAudioDeviceModule, didReceiveSpeechActivityEvent speechActivityEvent: RTCSpeechActivityEvent) {
func audioDeviceModule(_: LKRTCAudioDeviceModule, didReceiveMutedSpeechActivityEvent speechActivityEvent: RTCSpeechActivityEvent) {
guard let audioManager else { return }
audioManager._state.onMutedSpeechActivity?(audioManager, speechActivityEvent.toLKType())
}
Expand All @@ -38,51 +72,61 @@ class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate

// Engine events

func audioDeviceModule(_: LKRTCAudioDeviceModule, didCreateEngine engine: AVAudioEngine) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, didCreateEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineDidCreate(engine) ?? 0
return entryPoint?.engineDidCreate(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
return entryPoint?.engineWillEnable(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
return entryPoint?.engineWillStart(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, didStopEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, didStopEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
return entryPoint?.engineDidStop(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, didDisableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, didDisableEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
return entryPoint?.engineDidDisable(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, willReleaseEngine engine: AVAudioEngine) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, willReleaseEngine engine: AVAudioEngine, stateTransition: LKRTCAudioEngineStateTransition) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillRelease(engine) ?? 0
return entryPoint?.engineWillRelease(engine, state: AudioEngineStateTransition(fromRTCType: stateTransition)) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat, stateTransition: LKRTCAudioEngineStateTransition, context: [AnyHashable: Any]) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
return entryPoint?.engineWillConnectInput(engine,
src: src,
dst: dst,
format: format,
state: AudioEngineStateTransition(fromRTCType: stateTransition),
context: context) ?? 0
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat, stateTransition: LKRTCAudioEngineStateTransition, context: [AnyHashable: Any]) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillConnectOutput(engine, src: src, dst: dst, format: format, context: context) ?? 0
return entryPoint?.engineWillConnectOutput(engine,
src: src,
dst: dst,
format: format,
state: AudioEngineStateTransition(fromRTCType: stateTransition),
context: context) ?? 0
}
}
80 changes: 56 additions & 24 deletions Sources/LiveKit/Audio/AudioEngineObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,54 +29,86 @@ public protocol AudioEngineObserver: NextInvokable, Sendable {
associatedtype Next = any AudioEngineObserver
var next: (any AudioEngineObserver)? { get set }

func engineDidCreate(_ engine: AVAudioEngine) -> Int
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
func engineWillRelease(_ engine: AVAudioEngine) -> Int
func engineDidCreate(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int
func engineWillEnable(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int
func engineWillStart(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int
func engineDidStop(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int
func engineDidDisable(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int
func engineWillRelease(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int

/// Provide custom implementation for internal AVAudioEngine's output configuration.
/// Buffers flow from `src` to `dst`. Preferred format to connect node is provided as `format`.
/// Return true if custom implementation is provided, otherwise default implementation will be used.
func engineWillConnectOutput(_ engine: AVAudioEngine, src: AVAudioNode, dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int
func engineWillConnectOutput(_ engine: AVAudioEngine,
src: AVAudioNode,
dst: AVAudioNode?,
format: AVAudioFormat,
state: AudioEngineStateTransition,
context: [AnyHashable: Any]) -> Int
/// Provide custom implementation for internal AVAudioEngine's input configuration.
/// Buffers flow from `src` to `dst`. Preferred format to connect node is provided as `format`.
/// Return true if custom implementation is provided, otherwise default implementation will be used.
func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int
func engineWillConnectInput(_ engine: AVAudioEngine,
src: AVAudioNode?,
dst: AVAudioNode,
format: AVAudioFormat,
state: AudioEngineStateTransition,
context: [AnyHashable: Any]) -> Int
}

/// Default implementation to make it optional.
public extension AudioEngineObserver {
func engineDidCreate(_ engine: AVAudioEngine) -> Int {
next?.engineDidCreate(engine) ?? 0
func engineDidCreate(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineDidCreate(engine, state: state) ?? 0
}

func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
func engineWillEnable(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineWillEnable(engine, state: state) ?? 0
}

func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
next?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
func engineWillStart(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineWillStart(engine, state: state) ?? 0
}

func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
next?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
func engineDidStop(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineDidStop(engine, state: state) ?? 0
}

func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
func engineDidDisable(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineDidDisable(engine, state: state) ?? 0
}

func engineWillRelease(_ engine: AVAudioEngine) -> Int {
next?.engineWillRelease(engine) ?? 0
func engineWillRelease(_ engine: AVAudioEngine, state: AudioEngineStateTransition) -> Int {
next?.engineWillRelease(engine, state: state) ?? 0
}

func engineWillConnectOutput(_ engine: AVAudioEngine, src: AVAudioNode, dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
next?.engineWillConnectOutput(engine, src: src, dst: dst, format: format, context: context) ?? 0
func engineWillConnectOutput(_ engine: AVAudioEngine,
src: AVAudioNode,
dst: AVAudioNode?,
format: AVAudioFormat,
state: AudioEngineStateTransition,
context: [AnyHashable: Any]) -> Int
{
next?.engineWillConnectOutput(engine,
src: src,
dst: dst,
format: format,
state: state,
context: context) ?? 0
}

func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
func engineWillConnectInput(_ engine: AVAudioEngine,
src: AVAudioNode?,
dst: AVAudioNode,
format: AVAudioFormat,
state: AudioEngineStateTransition,
context: [AnyHashable: Any]) -> Int
{
next?.engineWillConnectInput(engine,
src: src,
dst: dst,
format: format,
state: state,
context: context) ?? 0
}
}
Loading