Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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", "= 137.7151.04")
spec.dependency("LiveKitWebRTC", "= 137.7151.08")
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 @@ -19,7 +19,7 @@ let package = Package(
],
dependencies: [
// LK-Prefixed Dynamic WebRTC XCFramework
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.05"),
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.08"),
.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"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
Expand Down
2 changes: 1 addition & 1 deletion [email protected]
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: "137.7151.05"),
.package(url: "https://github.com/livekit/webrtc-xcframework.git", exact: "137.7151.08"),
.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"),
.package(url: "https://github.com/apple/swift-collections.git", from: "1.1.0"),
Expand Down
24 changes: 24 additions & 0 deletions Sources/LiveKit/Audio/Manager/AudioManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,10 +101,10 @@
// Keep this var within State so it's protected by UnfairLock
public var localTracksCount: Int = 0
public var remoteTracksCount: Int = 0
public var customConfigureFunc: ConfigureAudioSessionFunc?

Check warning on line 104 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 104 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 104 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,OS=17.5)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.4, tvOS Simulator,name=Apple TV,OS=18.5)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, tvOS Simulator,name=Apple TV,OS=26.0)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, iOS Simulator,name=iPhone 17 Pro,OS=26.0)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.4, visionOS Simulator,name=Apple Vision Pro,OS=2.5)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, visionOS Simulator,name=Apple Vision Pro,OS=26.0)

'ConfigureAudioSessionFunc' is deprecated
public var sessionConfiguration: AudioSessionConfiguration?

public var trackState: TrackState {

Check warning on line 107 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 107 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

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

'TrackState' is deprecated

Check warning on line 107 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,OS=17.5)

'TrackState' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.4, tvOS Simulator,name=Apple TV,OS=18.5)

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, tvOS Simulator,name=Apple TV,OS=26.0)

'TrackState' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, iOS Simulator,name=iPhone 17 Pro,OS=26.0)

'TrackState' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-15, 16.4, visionOS Simulator,name=Apple Vision Pro,OS=2.5)

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

GitHub Actions / Build & Test (macos-26, latest, visionOS Simulator,name=Apple Vision Pro,OS=26.0)

'TrackState' is deprecated
switch (localTracksCount > 0, remoteTracksCount > 0) {
case (true, false): .localOnly
case (false, true): .remoteOnly
Expand Down Expand Up @@ -309,6 +309,30 @@
try checkAdmResult(code: result)
}

/// Sets whether the internal `AVAudioEngine` is allowed to run.
///
/// This flag has the highest priority over any API that may start the engine
/// (e.g., enabling the mic, ``startLocalRecording()``, or starting playback).
///
/// - Behavior:
/// - When set to a disabled availability, the engine will stop if running,
/// and it will not start, even if recording or playback is requested.
/// - When set back to enabled, the engine will start as soon as possible
/// if recording and/or playback had been previously requested while disabled
/// (i.e., pending requests are honored once availability allows).
///
/// This is useful when you need to set up connections without touching the audio
/// device yet (e.g., CallKit flows), or to guarantee the engine remains off
/// regardless of subscription/publication requests.
public func setEngineAvailability(_ availability: AudioEngineAvailability) throws {
let result = RTC.audioDeviceModule.setEngineAvailability(availability.toRTCType())
try checkAdmResult(code: result)
}

public var engineAvailability: AudioEngineAvailability {
RTC.audioDeviceModule.engineAvailability.toLKType()
}

/// Set a chain of ``AudioEngineObserver``s.
/// Defaults to having a single ``AudioSessionEngineObserver`` initially.
///
Expand Down
7 changes: 5 additions & 2 deletions Sources/LiveKit/Participant/LocalParticipant.swift
Original file line number Diff line number Diff line change
Expand Up @@ -693,8 +693,11 @@ extension LocalParticipant {

// At this point at least 1 audio frame should be generated to continue
if let track = track as? LocalAudioTrack {
log("[Publish] Waiting for audio frame...")
try await track.startWaitingForFrames()
// Only wait for frames if audio engine is allowed to start
if AudioManager.shared.engineAvailability.isInputAvailable {
log("[Publish] Waiting for audio frame...")
try await track.startWaitingForFrames()
}
}

if track is LocalVideoTrack {
Expand Down
44 changes: 44 additions & 0 deletions Sources/LiveKit/Types/AudioEngineAvailability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2025 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

internal import LiveKitWebRTC

public struct AudioEngineAvailability: Sendable {
public static let `default` = AudioEngineAvailability(isInputAvailable: true, isOutputAvailable: true)
public static let none = AudioEngineAvailability(isInputAvailable: false, isOutputAvailable: false)

public let isInputAvailable: Bool
public let isOutputAvailable: Bool

public init(isInputAvailable: Bool, isOutputAvailable: Bool) {
self.isInputAvailable = isInputAvailable
self.isOutputAvailable = isOutputAvailable
}
}

extension LKRTCAudioEngineAvailability {
func toLKType() -> AudioEngineAvailability {
AudioEngineAvailability(isInputAvailable: isInputAvailable.boolValue,
isOutputAvailable: isOutputAvailable.boolValue)
}
}

extension AudioEngineAvailability {
func toRTCType() -> LKRTCAudioEngineAvailability {
LKRTCAudioEngineAvailability(isInputAvailable: ObjCBool(isInputAvailable),
isOutputAvailable: ObjCBool(isOutputAvailable))
}
}
44 changes: 44 additions & 0 deletions Tests/LiveKitTests/Audio/AudioEngineAvailability.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright 2025 LiveKit
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

@preconcurrency import AVFoundation
@testable import LiveKit
import XCTest

class AudioEngineAvailabilityTests: LKTestCase {
// Check if audio engine will stop when availability is set to .none,
// then resume (restart) when availability is set back to .default.
func testRecording() async throws {
// First check
XCTAssertFalse(AudioManager.shared.isEngineRunning)

// Start
try AudioManager.shared.startLocalRecording()
XCTAssertTrue(AudioManager.shared.isEngineRunning)

// Disable both input & output
try AudioManager.shared.setEngineAvailability(.none)
XCTAssertFalse(AudioManager.shared.isEngineRunning)

// Re-enable both input & output (default)
try AudioManager.shared.setEngineAvailability(.default)
XCTAssertTrue(AudioManager.shared.isEngineRunning)

// Stop
try AudioManager.shared.stopLocalRecording()
XCTAssertFalse(AudioManager.shared.isEngineRunning)
}
}
6 changes: 6 additions & 0 deletions Tests/LiveKitTests/LKTestCase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,9 @@ class LKTestCase: XCTestCase {
super.setUp()
}
}

extension LKTestCase {
func sleep(forSeconds seconds: UInt) async {
try? await Task.sleep(nanoseconds: UInt64(seconds) * 1_000_000_000)
}
}
10 changes: 5 additions & 5 deletions Tests/LiveKitTests/MuteTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,23 +69,23 @@ func applyEngineTransition(_ transition: TestEngineTransition) {
var engineState = adm.engineState

if case let .value(value) = transition.outputEnabled {
engineState.outputEnabled = value
engineState.outputEnabled = ObjCBool(value)
}

if case let .value(value) = transition.outputRunning {
engineState.outputRunning = value
engineState.outputRunning = ObjCBool(value)
}

if case let .value(value) = transition.inputEnabled {
engineState.inputEnabled = value
engineState.inputEnabled = ObjCBool(value)
}

if case let .value(value) = transition.inputRunning {
engineState.inputRunning = value
engineState.inputRunning = ObjCBool(value)
}

if case let .value(value) = transition.inputMuted {
engineState.inputMuted = value
engineState.inputMuted = ObjCBool(value)
}

if case let .value(value) = transition.legacyMuteMode {
Expand Down
Loading