Skip to content

Commit 0dd5a09

Browse files
authored
Fail to publish instead of crash when mic not available (#615)
WebRTC patches: webrtc-sdk/webrtc@4a5ac4c webrtc-sdk/webrtc@b1074f3 webrtc-sdk/webrtc@0b84894 Possibly related to: #577
1 parent 68969d5 commit 0dd5a09

12 files changed

+183
-115
lines changed

.nanpa/audioengine-avoid-crash.kdl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
patch type="fix" "Avoid audio engine crash"

LiveKitClient.podspec

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Pod::Spec.new do |spec|
1616

1717
spec.source_files = "Sources/**/*"
1818

19-
spec.dependency("LiveKitWebRTC", "= 125.6422.19")
19+
spec.dependency("LiveKitWebRTC", "= 125.6422.22")
2020
spec.dependency("SwiftProtobuf")
2121
spec.dependency("Logging")
2222
spec.dependency("DequeModule", "= 1.1.4")

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ let package = Package(
1818
],
1919
dependencies: [
2020
// LK-Prefixed Dynamic WebRTC XCFramework
21-
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.19"),
21+
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.22"),
2222
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0"),
2323
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
2424
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),

Package@swift-5.9.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ let package = Package(
2020
],
2121
dependencies: [
2222
// LK-Prefixed Dynamic WebRTC XCFramework
23-
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.19"),
23+
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.22"),
2424
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0"),
2525
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
2626
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),

Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -38,51 +38,51 @@ class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate
3838

3939
// Engine events
4040

41-
func audioDeviceModule(_: LKRTCAudioDeviceModule, didCreateEngine engine: AVAudioEngine) {
42-
guard let audioManager else { return }
41+
func audioDeviceModule(_: LKRTCAudioDeviceModule, didCreateEngine engine: AVAudioEngine) -> Int {
42+
guard let audioManager else { return 0 }
4343
let entryPoint = audioManager.buildEngineObserverChain()
44-
entryPoint?.engineDidCreate(engine)
44+
return entryPoint?.engineDidCreate(engine) ?? 0
4545
}
4646

47-
func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
48-
guard let audioManager else { return }
47+
func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
48+
guard let audioManager else { return 0 }
4949
let entryPoint = audioManager.buildEngineObserverChain()
50-
entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
50+
return entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
5151
}
5252

53-
func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
54-
guard let audioManager else { return }
53+
func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
54+
guard let audioManager else { return 0 }
5555
let entryPoint = audioManager.buildEngineObserverChain()
56-
entryPoint?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
56+
return entryPoint?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
5757
}
5858

59-
func audioDeviceModule(_: LKRTCAudioDeviceModule, didStopEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
60-
guard let audioManager else { return }
59+
func audioDeviceModule(_: LKRTCAudioDeviceModule, didStopEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
60+
guard let audioManager else { return 0 }
6161
let entryPoint = audioManager.buildEngineObserverChain()
62-
entryPoint?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
62+
return entryPoint?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
6363
}
6464

65-
func audioDeviceModule(_: LKRTCAudioDeviceModule, didDisableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
66-
guard let audioManager else { return }
65+
func audioDeviceModule(_: LKRTCAudioDeviceModule, didDisableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
66+
guard let audioManager else { return 0 }
6767
let entryPoint = audioManager.buildEngineObserverChain()
68-
entryPoint?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
68+
return entryPoint?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
6969
}
7070

71-
func audioDeviceModule(_: LKRTCAudioDeviceModule, willReleaseEngine engine: AVAudioEngine) {
72-
guard let audioManager else { return }
71+
func audioDeviceModule(_: LKRTCAudioDeviceModule, willReleaseEngine engine: AVAudioEngine) -> Int {
72+
guard let audioManager else { return 0 }
7373
let entryPoint = audioManager.buildEngineObserverChain()
74-
entryPoint?.engineWillRelease(engine)
74+
return entryPoint?.engineWillRelease(engine) ?? 0
7575
}
7676

77-
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) {
78-
guard let audioManager else { return }
77+
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureInputFromSource src: AVAudioNode?, toDestination dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
78+
guard let audioManager else { return 0 }
7979
let entryPoint = audioManager.buildEngineObserverChain()
80-
entryPoint?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context)
80+
return entryPoint?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
8181
}
8282

83-
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) {
84-
guard let audioManager else { return }
83+
func audioDeviceModule(_: LKRTCAudioDeviceModule, engine: AVAudioEngine, configureOutputFromSource src: AVAudioNode, toDestination dst: AVAudioNode?, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
84+
guard let audioManager else { return 0 }
8585
let entryPoint = audioManager.buildEngineObserverChain()
86-
entryPoint?.engineWillConnectOutput(engine, src: src, dst: dst, format: format, context: context)
86+
return entryPoint?.engineWillConnectOutput(engine, src: src, dst: dst, format: format, context: context) ?? 0
8787
}
8888
}

Sources/LiveKit/Audio/AudioEngineObserver.swift

Lines changed: 24 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -29,54 +29,54 @@ public protocol AudioEngineObserver: NextInvokable, Sendable {
2929
associatedtype Next = any AudioEngineObserver
3030
var next: (any AudioEngineObserver)? { get set }
3131

32-
func engineDidCreate(_ engine: AVAudioEngine)
33-
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool)
34-
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool)
35-
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool)
36-
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool)
37-
func engineWillRelease(_ engine: AVAudioEngine)
32+
func engineDidCreate(_ engine: AVAudioEngine) -> Int
33+
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
34+
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
35+
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
36+
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int
37+
func engineWillRelease(_ engine: AVAudioEngine) -> Int
3838

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

4949
/// Default implementation to make it optional.
5050
public extension AudioEngineObserver {
51-
func engineDidCreate(_ engine: AVAudioEngine) {
52-
next?.engineDidCreate(engine)
51+
func engineDidCreate(_ engine: AVAudioEngine) -> Int {
52+
next?.engineDidCreate(engine) ?? 0
5353
}
5454

55-
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
56-
next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
55+
func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
56+
next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
5757
}
5858

59-
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
60-
next?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
59+
func engineWillStart(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
60+
next?.engineWillStart(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
6161
}
6262

63-
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
64-
next?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
63+
func engineDidStop(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
64+
next?.engineDidStop(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
6565
}
6666

67-
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
68-
next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
67+
func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
68+
next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
6969
}
7070

71-
func engineWillRelease(_ engine: AVAudioEngine) {
72-
next?.engineWillRelease(engine)
71+
func engineWillRelease(_ engine: AVAudioEngine) -> Int {
72+
next?.engineWillRelease(engine) ?? 0
7373
}
7474

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

79-
func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) {
80-
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context)
79+
func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
80+
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
8181
}
8282
}

Sources/LiveKit/Audio/DefaultAudioSessionObserver.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
* limitations under the License.
1515
*/
1616

17+
let kFailedToConfigureAudioSessionErrorCode = -4100
18+
1719
#if os(iOS) || os(visionOS) || os(tvOS)
1820

1921
import AVFoundation
@@ -56,26 +58,32 @@ public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchec
5658
}
5759
}
5860

59-
public func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
61+
public func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
6062
if AudioManager.shared._state.customConfigureFunc == nil {
6163
log("Configuring audio session...")
6264
let session = LKRTCAudioSession.sharedInstance()
6365
session.lockForConfiguration()
6466
defer { session.unlockForConfiguration() }
6567

66-
let config: AudioSessionConfiguration = isRecordingEnabled ? .playAndRecordSpeaker : .playback
67-
do {
68-
if _state.isSessionActive {
68+
if _state.isSessionActive {
69+
do {
6970
log("AudioSession deactivating due to category switch")
7071
try session.setActive(false) // Deactivate first
7172
_state.mutate { $0.isSessionActive = false }
73+
} catch {
74+
log("Failed to deactivate AudioSession with error: \(error)", .error)
7275
}
76+
}
7377

78+
let config: AudioSessionConfiguration = isRecordingEnabled ? .playAndRecordSpeaker : .playback
79+
do {
7480
log("AudioSession activating category to: \(config.category)")
7581
try session.setConfiguration(config.toRTCType(), active: true)
7682
_state.mutate { $0.isSessionActive = true }
7783
} catch {
7884
log("AudioSession failed to configure with error: \(error)", .error)
85+
// Pass error code to audio engine
86+
return kFailedToConfigureAudioSessionErrorCode
7987
}
8088

8189
log("AudioSession activationCount: \(session.activationCount), webRTCSessionCount: \(session.webRTCSessionCount)")
@@ -87,12 +95,12 @@ public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchec
8795
}
8896

8997
// Call next last
90-
_state.next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
98+
return _state.next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
9199
}
92100

93-
public func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) {
101+
public func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
94102
// Call next first
95-
_state.next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
103+
let nextResult = _state.next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
96104

97105
_state.mutate {
98106
$0.isPlayoutEnabled = isPlayoutEnabled
@@ -111,7 +119,7 @@ public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchec
111119
log("AudioSession switching category to: \(config.category)")
112120
try session.setConfiguration(config.toRTCType())
113121
}
114-
if !isPlayoutEnabled, !isRecordingEnabled {
122+
if !isPlayoutEnabled, !isRecordingEnabled, _state.isSessionActive {
115123
log("AudioSession deactivating")
116124
try session.setActive(false)
117125
_state.mutate { $0.isSessionActive = false }
@@ -122,6 +130,8 @@ public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchec
122130

123131
log("AudioSession activationCount: \(session.activationCount), webRTCSessionCount: \(session.webRTCSessionCount)")
124132
}
133+
134+
return nextResult ?? 0
125135
}
126136
}
127137

Sources/LiveKit/Audio/DefaultMixerAudioObserver.swift

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ public final class DefaultMixerAudioObserver: AudioEngineObserver, Loggable {
7575
next = handler
7676
}
7777

78-
public func engineDidCreate(_ engine: AVAudioEngine) {
78+
public func engineDidCreate(_ engine: AVAudioEngine) -> Int {
7979
let (appNode, appMixerNode, micNode, micMixerNode) = _state.read {
8080
($0.appNode, $0.appMixerNode, $0.micNode, $0.micMixerNode)
8181
}
@@ -86,12 +86,12 @@ public final class DefaultMixerAudioObserver: AudioEngineObserver, Loggable {
8686
engine.attach(micMixerNode)
8787

8888
// Invoke next
89-
next?.engineDidCreate(engine)
89+
return next?.engineDidCreate(engine) ?? 0
9090
}
9191

92-
public func engineWillRelease(_ engine: AVAudioEngine) {
92+
public func engineWillRelease(_ engine: AVAudioEngine) -> Int {
9393
// Invoke next
94-
next?.engineWillRelease(engine)
94+
let nextResult = next?.engineWillRelease(engine)
9595

9696
let (appNode, appMixerNode, micNode, micMixerNode) = _state.read {
9797
($0.appNode, $0.appMixerNode, $0.micNode, $0.micMixerNode)
@@ -101,14 +101,15 @@ public final class DefaultMixerAudioObserver: AudioEngineObserver, Loggable {
101101
engine.detach(appMixerNode)
102102
engine.detach(micNode)
103103
engine.detach(micMixerNode)
104+
105+
return nextResult ?? 0
104106
}
105107

106-
public func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) {
108+
public func engineWillConnectInput(_ engine: AVAudioEngine, src: AVAudioNode?, dst: AVAudioNode, format: AVAudioFormat, context: [AnyHashable: Any]) -> Int {
107109
// Get the main mixer
108110
guard let mainMixerNode = context[kRTCAudioEngineInputMixerNodeKey] as? AVAudioMixerNode else {
109111
// If failed to get main mixer, call next and return.
110-
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context)
111-
return
112+
return next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
112113
}
113114

114115
// Read nodes from state lock.
@@ -140,7 +141,7 @@ public final class DefaultMixerAudioObserver: AudioEngineObserver, Loggable {
140141
}
141142

142143
// Invoke next
143-
next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context)
144+
return next?.engineWillConnectInput(engine, src: src, dst: dst, format: format, context: context) ?? 0
144145
}
145146
}
146147

Sources/LiveKit/Errors.swift

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,10 @@ public enum LiveKitErrorType: Int, Sendable {
5353
case unableToResolveFPSRange = 703
5454
case capturerDimensionsNotResolved = 704
5555
case deviceAccessDenied = 705
56+
57+
// Audio
58+
case audioEngine = 801
59+
case audioSession = 802
5660
}
5761

5862
extension LiveKitErrorType: CustomStringConvertible {
@@ -96,6 +100,10 @@ extension LiveKitErrorType: CustomStringConvertible {
96100
return "Unable to resolve FPS range"
97101
case .capturerDimensionsNotResolved:
98102
return "Capturer dimensions not resolved"
103+
case .audioEngine:
104+
return "Audio Engine Error"
105+
case .audioSession:
106+
return "Audio Session Error"
99107
default: return "Unknown"
100108
}
101109
}

0 commit comments

Comments
 (0)