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
36 changes: 35 additions & 1 deletion Runtime/Scripts/AudioStream.cs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ public sealed class AudioStream : IDisposable
{
internal readonly FfiHandle Handle;
private readonly AudioSource _audioSource;
private readonly AudioProbe _probe;
private AudioProbe _probe;
private RingBuffer _buffer;
private short[] _tempBuffer;
private short[] _crossfadeScratch;
Expand Down Expand Up @@ -73,6 +73,11 @@ public AudioStream(RemoteAudioTrack audioTrack, AudioSource source)

// Subscribe to application pause events to handle background/foreground transitions
MonoBehaviourContext.OnApplicationPauseEvent += OnApplicationPause;

// Unity stops every AudioSource when the system audio output device changes
// (e.g. headphones unplugged). Without re-playing the source, OnAudioFilterRead
// stops firing and the stream goes silent until the AudioStream is recreated.
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
}

// Called on Unity audio thread
Expand Down Expand Up @@ -211,6 +216,34 @@ static float S16ToFloat(short v)
}
}

// Called when the system audio output device changes (e.g. plug/unplug headphones)
// or AudioSettings.Reset is invoked. Unity tears down its audio engine in both cases,
// which stops every AudioSource and detaches the AudioProbe filter from the rebuilt
// audio graph. Just calling Play() on the existing source isn't always enough; we
// additionally recreate the AudioProbe so Unity re-registers the OnAudioFilterRead
// node on the new graph.
private void OnAudioConfigurationChanged(bool deviceWasChanged)
{
if (_disposed) return;

lock (_lock)
{
_buffer?.Clear();
_isPrimed = false;
}

if (_probe != null)
{
_probe.AudioRead -= OnAudioRead;
UnityEngine.Object.Destroy(_probe);
}
_probe = _audioSource.gameObject.AddComponent<AudioProbe>();
_probe.AudioRead += OnAudioRead;

_audioSource.Stop();
_audioSource.Play();
}

// Called when application goes to background or returns to foreground
internal void OnApplicationPause(bool pause)
{
Expand Down Expand Up @@ -285,6 +318,7 @@ private void Dispose(bool disposing)
// touching partially disposed state.
FfiClient.Instance.AudioStreamEventReceived -= OnAudioStreamEvent;
MonoBehaviourContext.OnApplicationPauseEvent -= OnApplicationPause;
AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;

lock (_lock)
{
Expand Down
1 change: 1 addition & 0 deletions Tests/EditMode/MediaStreamLifetimeTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ public void AudioStream_Dispose_UnsubscribesAndReleasesOwnedResources()

StringAssert.Contains("FfiClient.Instance.AudioStreamEventReceived -= OnAudioStreamEvent;", source);
StringAssert.Contains("_probe.AudioRead -= OnAudioRead;", source);
StringAssert.Contains("AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;", source);
StringAssert.Contains("_buffer?.Dispose();", source);
StringAssert.Contains("_resampler?.Dispose();", source);
StringAssert.Contains("Handle.Dispose();", source);
Expand Down
Loading