Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
3e48207
sp2
hiroshihorie Mar 10, 2026
6e9cb19
modes
hiroshihorie Mar 10, 2026
d52174c
dst1
hiroshihorie Mar 10, 2026
070a5fb
Merge branch 'main' into hiroshi/sound-player-2
hiroshihorie Mar 24, 2026
1f0b191
organize tuple
hiroshihorie Mar 24, 2026
2b86f39
port session requirement
hiroshihorie Mar 24, 2026
b187051
Merge remote-tracking branch 'origin/main' into hiroshi/sound-player-2
hiroshihorie Mar 24, 2026
44cb48c
propagate
hiroshihorie Mar 24, 2026
603d5f7
simplify
hiroshihorie Mar 24, 2026
008cb87
fixes
hiroshihorie Mar 24, 2026
439906f
fix 2
hiroshihorie Mar 24, 2026
735c133
fix 3
hiroshihorie Mar 24, 2026
798b970
ref 1
hiroshihorie Mar 24, 2026
cb0c4c3
stop destination
hiroshihorie Mar 24, 2026
a4c62cf
fixes
hiroshihorie Mar 24, 2026
aa08a44
read
hiroshihorie Mar 24, 2026
53b68c5
fix ci
hiroshihorie Mar 24, 2026
5a66b25
Merge branch 'main' into hiroshi/sound-player-2
hiroshihorie Mar 24, 2026
a994a6b
Merge branch 'main' into hiroshi/sound-player-2
hiroshihorie Mar 30, 2026
05f4c29
Merge branch 'main' into hiroshi/sound-player-2
hiroshihorie Mar 31, 2026
ff883ef
remove noise
hiroshihorie Mar 31, 2026
ceff523
fix qos warning
hiroshihorie Mar 31, 2026
57eb6e5
changes
hiroshihorie Apr 2, 2026
21781e0
Serialize player node pool reuse
hiroshihorie Apr 2, 2026
4098e53
Propagate render frame limits to sound players
hiroshihorie Apr 2, 2026
aaf8e81
actor
hiroshihorie Apr 2, 2026
c4cca0b
docs 1
hiroshihorie Apr 2, 2026
50ead0c
node state
hiroshihorie Apr 2, 2026
0ede69b
handle
hiroshihorie Apr 2, 2026
7a655de
optimize
hiroshihorie Apr 2, 2026
41b4598
refactor session requirement
hiroshihorie Apr 2, 2026
49520ba
optionset
hiroshihorie Apr 2, 2026
82c5251
Merge branch 'main' into hiroshi/sound-player-2
hiroshihorie Apr 2, 2026
ecb5194
fix ci 1
hiroshihorie Apr 2, 2026
754931b
Document SoundPlayer usage and behavior
hiroshihorie Apr 6, 2026
e4fbb2b
Auto-release session requirement handles
hiroshihorie Apr 6, 2026
6c063a1
Warn when remote sound playback is unavailable
hiroshihorie Apr 6, 2026
10aa5b2
Isolate SoundPlayer with a global actor
hiroshihorie Apr 6, 2026
7304166
Cache converted local sound buffers
hiroshihorie Apr 6, 2026
ae1a9c7
Rebuild SoundPlayer after engine config changes
hiroshihorie Apr 6, 2026
9b7a3c9
Clarify remote sound warning reasons
hiroshihorie Apr 6, 2026
fadaad3
Align MixerEngineObserver state binding names
hiroshihorie Apr 6, 2026
57c5ed9
Group SoundPlayer local engine state
hiroshihorie Apr 6, 2026
4fe7a52
Extract SoundPlayer local reset helpers
hiroshihorie Apr 6, 2026
060275f
Simplify AudioSessionEngineObserver config flow
hiroshihorie Apr 6, 2026
568b9ae
format
hiroshihorie Apr 6, 2026
3df0d71
Split SoundPlayer types from implementation
hiroshihorie Apr 6, 2026
5f4a07e
Introduce SoundHandle named sound lookup
hiroshihorie Apr 7, 2026
bf5328d
Tighten SoundPlayer engine start helper
hiroshihorie Apr 7, 2026
957dae4
Avoid restoring released sound state
hiroshihorie Apr 7, 2026
8539c01
Use SoundPlayer error category
hiroshihorie Apr 7, 2026
6b94dad
Document SoundHandle API surface
hiroshihorie Apr 7, 2026
1ba4361
Rename sound playback options
hiroshihorie Apr 7, 2026
97e44ba
Replace force-unwrapped AVAudioFormat with safe alternatives
hiroshihorie Apr 7, 2026
20af8c6
Use reference type for PreparedSound to prevent stale copies across a…
hiroshihorie Apr 7, 2026
ffa27f3
format
hiroshihorie Apr 7, 2026
121ae9f
changes
hiroshihorie Apr 7, 2026
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
1 change: 1 addition & 0 deletions .changes/sound-api-2
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
minor type="added" "SoundPlayer API for prepared audio clips with local playback and best-effort remote playback"
110 changes: 76 additions & 34 deletions Sources/LiveKit/Audio/AudioSessionEngineObserver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,13 +69,15 @@ public class AudioSessionEngineObserver: AudioEngineObserver, Loggable, @uncheck

var isAutomaticConfigurationEnabled: Bool = true
var isAutomaticDeactivationEnabled: Bool = true
var isPlayoutEnabled: Bool = false
var isRecordingEnabled: Bool = false
var isSpeakerOutputPreferred: Bool = true

var sessionRequirements: [UUID: SessionRequirement] = [:]
}

let _state = StateSync(State())

private let sessionRequirementId = UUID()

public var next: (any AudioEngineObserver)? {
get { _state.next }
set { _state.mutate { $0.next = newValue } }
Expand All @@ -85,14 +87,64 @@ public class AudioSessionEngineObserver: AudioEngineObserver, Loggable, @uncheck
_state.onDidMutate = { [weak self] new, old in
guard let self,
new.isSpeakerOutputPreferred != old.isSpeakerOutputPreferred else { return }
_ = configureIfNeeded(oldState: old, newState: new)
do {
try configureIfNeeded(oldState: old, newState: new)
} catch {
log("Failed to configure audio session after speaker preference change: \(error)", .error)
}
}
}

/// Acquires an audio session requirement handle for external ownership.
///
/// Use this to keep the audio session active from external components
/// (e.g., ``SoundPlayer``) that need playout or recording independently
/// of the WebRTC engine lifecycle.
///
/// - Throws: ``LiveKitError`` if the audio session fails to configure or activate.
public func acquire(requirement: SessionRequirement) throws -> SessionRequirementHandle {
let id = UUID()
try set(requirement: requirement, for: id)
return SessionRequirementHandle(releaseImpl: { [weak self] in
guard let self else { return }
try removeRequirement(for: id)
})
}

private func set(requirement: SessionRequirement, for id: UUID) throws {
try updateRequirements {
if requirement == .none {
$0.removeValue(forKey: id)
} else {
$0[id] = requirement
}
}
}

fileprivate func removeRequirement(for id: UUID) throws {
try updateRequirements {
$0.removeValue(forKey: id)
}
}

private func updateRequirements(_ block: (inout [UUID: SessionRequirement]) -> Void) throws {
try _state.mutate {
let oldState = $0
block(&$0.sessionRequirements)
guard $0.sessionRequirements != oldState.sessionRequirements else { return }
do {
try configureIfNeeded(oldState: oldState, newState: $0)
} catch {
$0 = oldState
throw LiveKitError(.audioSession, message: "Failed to configure audio session")
}
}
}

// MARK: - Audio Session Configuration

private func configureIfNeeded(oldState: State, newState: State) -> Int {
guard newState.isAutomaticConfigurationEnabled else { return 0 }
private func configureIfNeeded(oldState: State, newState: State) throws {
guard newState.isAutomaticConfigurationEnabled else { return }

// Deprecated: `customConfigureAudioSessionFunc` overrides the default configuration.
// This path does not support error propagation since the legacy func returns Void.
Expand All @@ -101,20 +153,17 @@ public class AudioSessionEngineObserver: AudioEngineObserver, Loggable, @uncheck
let oldLegacy = AudioManager.State(localTracksCount: oldState.isRecordingEnabled ? 1 : 0, remoteTracksCount: oldState.isPlayoutEnabled ? 1 : 0)
let newLegacy = AudioManager.State(localTracksCount: newState.isRecordingEnabled ? 1 : 0, remoteTracksCount: newState.isPlayoutEnabled ? 1 : 0)
legacyConfigFunc(newLegacy, oldLegacy)
return 0
return
}

do {
try configureAudioSession(oldState: oldState, newState: newState)
return 0
} catch {
return kAudioEngineErrorFailedToConfigureAudioSession
}
try configureAudioSession(oldState: oldState, newState: newState)
}

@Sendable private func configureAudioSession(oldState: State, newState: State) throws {
let session = AVAudioSession.sharedInstance()

log("configure isRecordingEnabled: \(newState.isRecordingEnabled), isPlayoutEnabled: \(newState.isPlayoutEnabled)")

if (!newState.isPlayoutEnabled && !newState.isRecordingEnabled) && (oldState.isPlayoutEnabled || oldState.isRecordingEnabled) {
if newState.isAutomaticDeactivationEnabled {
do {
Expand Down Expand Up @@ -166,38 +215,31 @@ public class AudioSessionEngineObserver: AudioEngineObserver, Loggable, @uncheck
// MARK: - AudioEngineObserver

public func engineWillEnable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
let result: Int = _state.mutate {
let oldState = $0
$0.isPlayoutEnabled = isPlayoutEnabled
$0.isRecordingEnabled = isRecordingEnabled
let result = configureIfNeeded(oldState: oldState, newState: $0)
if result != 0 {
// Rollback state on failure so it stays consistent with WebRTC's rollback.
$0 = oldState
}
return result
let requirement = SessionRequirement(isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
do {
try set(requirement: requirement, for: sessionRequirementId)
} catch {
return kAudioEngineErrorFailedToConfigureAudioSession
}
guard result == 0 else { return result }
return _state.next?.engineWillEnable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0
}

public func engineDidDisable(_ engine: AVAudioEngine, isPlayoutEnabled: Bool, isRecordingEnabled: Bool) -> Int {
let nextResult = _state.next?.engineDidDisable(engine, isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled) ?? 0

let result: Int = _state.mutate {
let oldState = $0
$0.isPlayoutEnabled = isPlayoutEnabled
$0.isRecordingEnabled = isRecordingEnabled
let result = configureIfNeeded(oldState: oldState, newState: $0)
if result != 0 {
// Rollback state on failure so it stays consistent with WebRTC's rollback.
$0 = oldState
}
return result
let requirement = SessionRequirement(isPlayoutEnabled: isPlayoutEnabled, isRecordingEnabled: isRecordingEnabled)
do {
try set(requirement: requirement, for: sessionRequirementId)
} catch {
return kAudioEngineErrorFailedToConfigureAudioSession
}
guard result == 0 else { return result }
return nextResult
}
}

extension AudioSessionEngineObserver.State {
var isPlayoutEnabled: Bool { sessionRequirements.values.contains(where: \.isPlayoutEnabled) }
var isRecordingEnabled: Bool { sessionRequirements.values.contains(where: \.isRecordingEnabled) }
}

#endif
86 changes: 86 additions & 0 deletions Sources/LiveKit/Audio/Manager/AudioManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,81 @@

internal import LiveKitWebRTC

/// Represents an audio session requirement from a specific component.
///
/// Multiple components can independently register their requirements. On platforms that use
/// `AVAudioSession`, the session stays active as long as any component requires playout or recording.
public struct SessionRequirement: OptionSet, Sendable {
public let rawValue: UInt8

public static let playout = Self(rawValue: 1 << 0)
public static let recording = Self(rawValue: 1 << 1)

public static let none: Self = []
public static let playbackOnly: Self = [.playout]
public static let recordingOnly: Self = [.recording]
public static let playbackAndRecording: Self = [.playout, .recording]

public init(rawValue: UInt8) {
self.rawValue = rawValue
}

public init(isPlayoutEnabled: Bool = false, isRecordingEnabled: Bool = false) {
var rawValue: UInt8 = 0
if isPlayoutEnabled {
rawValue |= Self.playout.rawValue
}
if isRecordingEnabled {
rawValue |= Self.recording.rawValue
}
self.init(rawValue: rawValue)
}

public var isPlayoutEnabled: Bool {
contains(.playout)
}

public var isRecordingEnabled: Bool {
contains(.recording)
}
}

/// Opaque handle for an acquired audio session requirement.
///
/// Call ``release()`` when the requirement is no longer needed.
/// If not released explicitly, the requirement is released automatically on deinit.
public final class SessionRequirementHandle: @unchecked Sendable {
private struct State {
var releaseImpl: (@Sendable () throws -> Void)?
}

private let _state: StateSync<State>

init(releaseImpl: @escaping @Sendable () throws -> Void) {
_state = StateSync(State(releaseImpl: releaseImpl))
}

deinit {
try? releaseIfNeeded()
}

/// Releases the associated audio session requirement.
///
/// Releasing the same handle multiple times is a no-op.
public func release() throws {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This probably should be called on deinit.

try releaseIfNeeded()
}

private func releaseIfNeeded() throws {
let releaseImpl = _state.mutate { state -> (@Sendable () throws -> Void)? in
let releaseImpl = state.releaseImpl
state.releaseImpl = nil
return releaseImpl
}
try releaseImpl?()
}
}

// Audio Session Configuration related
public class AudioManager: Loggable {
// MARK: - Public
Expand Down Expand Up @@ -103,10 +178,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 181 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 181 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 181 in Sources/LiveKit/Audio/Manager/AudioManager.swift

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 181 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 181 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 181 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)

'ConfigureAudioSessionFunc' is deprecated

Check warning on line 181 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)

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated

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

View workflow job for this annotation

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

'ConfigureAudioSessionFunc' is deprecated
public var sessionConfiguration: AudioSessionConfiguration?

public var trackState: TrackState {

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

View workflow job for this annotation

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

'TrackState' is deprecated

Check warning on line 184 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 184 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 184 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)

'TrackState' is deprecated

Check warning on line 184 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)

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated

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

View workflow job for this annotation

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

'TrackState' is deprecated
switch (localTracksCount > 0, remoteTracksCount > 0) {
case (true, false): .localOnly
case (false, true): .remoteOnly
Expand Down Expand Up @@ -375,6 +450,17 @@
RTC.audioDeviceModule.isEngineRunning
}

/// Acquires an audio session requirement for external ownership.
///
/// On platforms without `AVAudioSession`, this returns a no-op handle.
public func acquireSessionRequirement(_ requirement: SessionRequirement) throws -> SessionRequirementHandle {
#if os(iOS) || os(visionOS) || os(tvOS)
try audioSession.acquire(requirement: requirement)
#else
SessionRequirementHandle(releaseImpl: {})
#endif
}

/// The mute state of internal audio engine which uses Voice Processing I/O mute API ``AVAudioInputNode.isVoiceProcessingInputMuted``.
/// Normally, you do not need to set this manually since it will be handled automatically.
public var isMicrophoneMuted: Bool {
Expand Down
Loading
Loading