Skip to content

Recording permission check at SDK level #688

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
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
2 changes: 1 addition & 1 deletion LiveKitClient.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ Pod::Spec.new do |spec|

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

spec.dependency("LiveKitWebRTC", "= 125.6422.29")
spec.dependency("LiveKitWebRTC", "= 125.6422.30")
spec.dependency("SwiftProtobuf")
spec.dependency("Logging", "= 1.5.4")
spec.dependency("DequeModule", "= 1.1.4")
Expand Down
2 changes: 1 addition & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ let package = Package(
],
dependencies: [
// LK-Prefixed Dynamic WebRTC XCFramework
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.29"),
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.30"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.26.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.5.4"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
Expand Down
2 changes: 1 addition & 1 deletion Package@swift-5.9.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let package = Package(
],
dependencies: [
// LK-Prefixed Dynamic WebRTC XCFramework
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.29"),
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.30"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.29.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"), // 1.6.x requires Swift >=5.8
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
Expand Down
2 changes: 1 addition & 1 deletion Package@swift-6.0.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ let package = Package(
],
dependencies: [
// LK-Prefixed Dynamic WebRTC XCFramework
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.29"),
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "125.6422.30"),
.package(url: "https://github.com/apple/swift-protobuf.git", from: "1.29.0"),
.package(url: "https://github.com/apple/swift-log.git", from: "1.6.2"), // 1.6.x requires Swift >=5.8
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
Expand Down
28 changes: 26 additions & 2 deletions Sources/LiveKit/Audio/AudioDeviceModuleDelegateAdapter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ internal import LiveKitWebRTC
#endif

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

func audioDeviceModule(_: LKRTCAudioDeviceModule, didReceiveSpeechActivityEvent speechActivityEvent: RTCSpeechActivityEvent) {
Expand All @@ -47,7 +47,31 @@ class AudioDeviceModuleDelegateAdapter: NSObject, LKRTCAudioDeviceModuleDelegate
func audioDeviceModule(_: LKRTCAudioDeviceModule, willEnableEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
guard let audioManager else { return 0 }
let entryPoint = audioManager.buildEngineObserverChain()
return entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
let result = entryPoint?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0

// At this point mic perms / session should be configured for recording.
if result == 0, isRecordingEnabled {
// This will block WebRTC's worker thread, but when instantiating AVAudioInput node it will block by showing a dialog anyways.
// Attempt to acquire mic perms at this point to return an error at SDK level.
let isAuthorized = LiveKitSDK.ensureDeviceAccessSync(for: [.audio])
log("AudioEngine pre-enable check, device permission: \(isAuthorized)")
if !isAuthorized {
return kAudioEngineErrorInsufficientDevicePermission
}

#if os(iOS) || os(visionOS) || os(tvOS)
// Additional check for audio session category.
let session = LKRTCAudioSession.sharedInstance()
log("AudioEngine pre-enable check, audio session: \(session.category)")
if ![AVAudioSession.Category.playAndRecord.rawValue,
AVAudioSession.Category.record.rawValue,].contains(session.category)
{
return kAudioEngineErrorAudioSessionCategoryRecordingRequired
}
#endif
}

return result
}

func audioDeviceModule(_: LKRTCAudioDeviceModule, willStartEngine engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
Expand Down
4 changes: 1 addition & 3 deletions Sources/LiveKit/Audio/DefaultAudioSessionObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,6 @@
* limitations under the License.
*/

let kFailedToConfigureAudioSessionErrorCode = -4100

#if os(iOS) || os(visionOS) || os(tvOS)

import AVFoundation
Expand Down Expand Up @@ -83,7 +81,7 @@ public class DefaultAudioSessionObserver: AudioEngineObserver, Loggable, @unchec
} catch {
log("AudioSession failed to configure with error: \(error)", .error)
// Pass error code to audio engine
return kFailedToConfigureAudioSessionErrorCode
return kAudioEngineErrorFailedToConfigureAudioSession
}

log("AudioSession activationCount: \(session.activationCount), webRTCSessionCount: \(session.webRTCSessionCount)")
Expand Down
10 changes: 9 additions & 1 deletion Sources/LiveKit/Audio/Manager/AudioManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,10 +106,10 @@
public var localTracksCount: Int = 0
public var remoteTracksCount: Int = 0
public var isSpeakerOutputPreferred: Bool = true
public var customConfigureFunc: ConfigureAudioSessionFunc?

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS,variant=Mac Catalyst)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS,variant=Mac Catalyst)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS,variant=Mac Catalyst)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS,variant=Mac Catalyst)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, tvOS Simulator,name=Apple TV)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, tvOS Simulator,name=Apple TV)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, iOS Simulator,OS=17.5,name=iPhone 15 Pro)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, iOS Simulator,OS=17.5,name=iPhone 15 Pro)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, visionOS Simulator,name=Apple Vision Pro)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 109 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, visionOS Simulator,name=Apple Vision Pro)

'ConfigureAudioSessionFunc' is deprecated
public var sessionConfiguration: AudioSessionConfiguration?

public var trackState: TrackState {

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS,variant=Mac Catalyst)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, macOS,variant=Mac Catalyst)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS,variant=Mac Catalyst)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, macOS,variant=Mac Catalyst)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, tvOS Simulator,name=Apple TV)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, tvOS Simulator,name=Apple TV)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, iOS Simulator,OS=17.5,name=iPhone 15 Pro)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, iOS Simulator,OS=17.5,name=iPhone 15 Pro)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.2, iOS Simulator,OS=18.1,name=iPhone 16 Pro, true)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, visionOS Simulator,name=Apple Vision Pro)

'TrackState' is deprecated

Check warning on line 112 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

GitHub Actions / Build & Test (macos-14, 15.4, visionOS Simulator,name=Apple Vision Pro)

'TrackState' is deprecated
switch (localTracksCount > 0, remoteTracksCount > 0) {
case (true, false): return .localOnly
case (false, true): return .remoteOnly
Expand Down Expand Up @@ -401,10 +401,18 @@
}
}

// SDK side AudioEngine error codes
let kAudioEngineErrorFailedToConfigureAudioSession = -4100

let kAudioEngineErrorInsufficientDevicePermission = -4101
let kAudioEngineErrorAudioSessionCategoryRecordingRequired = -4102

extension AudioManager {
func checkAdmResult(code: Int) throws {
if code == kFailedToConfigureAudioSessionErrorCode {
if code == kAudioEngineErrorFailedToConfigureAudioSession {
throw LiveKitError(.audioSession, message: "Failed to configure audio session")
} else if code == kAudioEngineErrorInsufficientDevicePermission {
throw LiveKitError(.deviceAccessDenied, message: "Device permissions are not granted")
} else if code != 0 {
throw LiveKitError(.audioEngine, message: "Audio engine returned error code: \(code)")
}
Expand Down
36 changes: 36 additions & 0 deletions Sources/LiveKit/LiveKit+DeviceHelpers.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,4 +40,40 @@ public extension LiveKitSDK {

return true
}

/// Blocking version of ensureDeviceAccess that uses DispatchGroup to wait for permissions.
static func ensureDeviceAccessSync(for types: Set<AVMediaType>) -> Bool {
let group = DispatchGroup()
var result = true

for type in types {
if ![.video, .audio].contains(type) {
logger.log("types must be .video or .audio", .error, type: LiveKitSDK.self)
}

let status = AVCaptureDevice.authorizationStatus(for: type)
switch status {
case .notDetermined:
group.enter()
AVCaptureDevice.requestAccess(for: type) { granted in
if !granted {
result = false
}
group.leave()
}
case .restricted, .denied:
return false
case .authorized:
continue // No action needed for authorized status
@unknown default:
logger.error("Unknown AVAuthorizationStatus")
return false
}
}

// Wait for all permission requests to complete
group.wait()

return result
}
}
9 changes: 1 addition & 8 deletions Sources/LiveKit/Track/Local/LocalAudioTrack.swift
Original file line number Diff line number Diff line change
Expand Up @@ -97,14 +97,7 @@ public class LocalAudioTrack: Track, LocalTrack, AudioTrack, @unchecked Sendable
override func startCapture() async throws {
// AudioDeviceModule's InitRecording() and StartRecording() automatically get called by WebRTC, but
// explicitly init & start it early to detect audio engine failures (mic not accessible for some reason, etc.).
do {
try AudioManager.shared.startLocalRecording()
} catch {
// Make sure internal state is updated to stopped state. (TODO: Remove if ADM reverts state automatically)
try? AudioManager.shared.stopLocalRecording()
// Rethrow
throw error
}
try AudioManager.shared.startLocalRecording()
}

override func stopCapture() async throws {
Expand Down
Loading