Skip to content
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
60 changes: 54 additions & 6 deletions Runtime/Plugins/iOS/LiveKitAudioSession.mm
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,32 @@

#import <AVFoundation/AVFoundation.h>

// Snapshot of the AVAudioSession configuration as it was the first time LiveKit
// touched the session (i.e. whatever Unity set up from its iOS Player Settings).
// Captured lazily in LiveKit_ConfigureAudioSessionForVoIP and re-applied by
// LiveKit_RestoreDefaultAudioSession when the last PlatformAudio is disposed.
static BOOL s_hasCachedState = NO;
static NSString* s_cachedCategory = nil;
static NSString* s_cachedMode = nil;
static AVAudioSessionCategoryOptions s_cachedCategoryOptions = 0;

/// Captures the current audio session category/mode/options exactly once, before
/// LiveKit reconfigures the session for VoIP. Subsequent calls are no-ops so the
/// snapshot always reflects the pristine, pre-LiveKit (Unity-configured) state.
static void LiveKit_CacheSessionStateIfNeeded() {
if (s_hasCachedState) {

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.

curiously, should we continue caching the latest session rather than simply return here ?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Sorry this is supposed to be a draft, not ready for review. We have some issues with teardown but this one does not solve it all, I am still investigating.

return;
}
AVAudioSession* session = [AVAudioSession sharedInstance];
// copy so the strings persist for the app lifetime regardless of ARC/MRC.
s_cachedCategory = [session.category copy];
s_cachedMode = [session.mode copy];
s_cachedCategoryOptions = session.categoryOptions;
s_hasCachedState = YES;
NSLog(@"LiveKit: cached audio session state: category=%@, mode=%@, options=%lu",
s_cachedCategory, s_cachedMode, (unsigned long)s_cachedCategoryOptions);
}

extern "C" {

/// Configures the iOS audio session for VoIP/WebRTC use.
Expand All @@ -28,6 +54,9 @@
/// Call this before creating PlatformAudio to ensure WebRTC can
/// properly initialize the microphone and speaker.
void LiveKit_ConfigureAudioSessionForVoIP() {
// Snapshot the pristine (Unity Player Settings) session before we change it.
LiveKit_CacheSessionStateIfNeeded();

AVAudioSession* session = [AVAudioSession sharedInstance];
NSError* error = nil;

Expand All @@ -54,16 +83,35 @@ void LiveKit_ConfigureAudioSessionForVoIP() {
NSLog(@"LiveKit: Audio session configured for VoIP (PlayAndRecord + VoiceChat mode)");
}

/// Restores the audio session to the default ambient category.
/// Call this when PlatformAudio is disposed if you want to restore
/// the original audio behavior.
/// Restores the audio session to the state cached before LiveKit first configured
/// it for VoIP (see LiveKit_CacheSessionStateIfNeeded), and reactivates it so Unity
/// audio plays normally again. Call this when the last PlatformAudio is disposed.
/// Falls back to the ambient category if nothing was ever cached.
void LiveKit_RestoreDefaultAudioSession() {
AVAudioSession* session = [AVAudioSession sharedInstance];
NSError* error = nil;

[session setCategory:AVAudioSessionCategoryAmbient error:&error];
if (error) {
NSLog(@"LiveKit: Failed to restore default audio session: %@", error.localizedDescription);
if (s_hasCachedState) {
if (![session setCategory:s_cachedCategory
mode:s_cachedMode
options:s_cachedCategoryOptions
error:&error] || error) {
NSLog(@"LiveKit: Failed to restore cached audio session (category=%@, mode=%@): %@",
s_cachedCategory, s_cachedMode, error.localizedDescription);
}
} else {
// Configure was never called, so we have nothing to restore to; fall back
// to the ambient category.
[session setCategory:AVAudioSessionCategoryAmbient error:&error];
if (error) {
NSLog(@"LiveKit: Failed to restore default audio session: %@", error.localizedDescription);
}
}

// Hand an active session back to Unity so its audio output resumes.
error = nil;
if (![session setActive:YES error:&error] || error) {
NSLog(@"LiveKit: Failed to reactivate audio session: %@", error.localizedDescription);
}
}

Expand Down
19 changes: 19 additions & 0 deletions Runtime/Scripts/Audio/PlatformAudio.cs
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,11 @@ public sealed class PlatformAudio : IDisposable
internal readonly FfiHandle Handle;
private readonly PlatformAudioInfo _info;
private bool _disposed = false;
#if UNITY_IOS && !UNITY_EDITOR
// Tracks live PlatformAudio instances so the iOS audio session is restored
// only when the last one is disposed (aligned with the native ADM ref-count).
private static int _instanceCount;

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.

nit, initialize it to 0 as default value ?

#endif

/// <summary>
/// Number of available recording (microphone) devices.
Expand Down Expand Up @@ -119,6 +124,12 @@ public PlatformAudio()
_info = platformAudio.Info;

Utils.Debug($"PlatformAudio created: {RecordingDeviceCount} recording devices, {PlayoutDeviceCount} playout devices");

#if UNITY_IOS && !UNITY_EDITOR
// Count this instance only after successful construction so a failed
// ctor never leaves the counter stuck above zero.
System.Threading.Interlocked.Increment(ref _instanceCount);
#endif
}

/// <summary>
Expand Down Expand Up @@ -365,6 +376,14 @@ public void Dispose()
if (_disposed) return;
Handle.Dispose();
_disposed = true;

#if UNITY_IOS && !UNITY_EDITOR
// Restore the audio session Unity had before VoIP, but only once the
// last PlatformAudio is gone (the native ADM is likewise ref-counted).
if (System.Threading.Interlocked.Decrement(ref _instanceCount) == 0)
IOSAudioSessionHelper.LiveKit_RestoreDefaultAudioSession();

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.

nit, add a {} for style

#endif

Utils.Debug("PlatformAudio disposed");
}
}
Expand Down
Loading