Skip to content

Commit 528ff62

Browse files
MaxHeimbrockclaude
andauthored
Recover AudioStream when system audio output device changes (#274)
* Restart AudioSource when system audio output device changes Unity stops every AudioSource during the audio engine reset that follows a system output device change (per AudioSettings.OnAudioConfigurationChanged docs). Without re-playing the source, OnAudioFilterRead stops firing and the AudioStream goes silent until recreated. Subscribe to the event, re-Play() the source, and clear the ring buffer so we don't drain stale frames accumulated during the device-change blackout. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * Re-attach AudioProbe on audio config change, not just re-Play Calling _audioSource.Play() alone isn't enough to recover after Unity tears down the audio engine: the OnAudioFilterRead node from the existing AudioProbe stays detached from the rebuilt audio graph and no callbacks fire. Destroy the old probe, AddComponent a fresh one, and Stop()/Play() the source so Unity re-registers the filter node. Also drop the deviceWasChanged gate (programmatic Reset() needs the same recovery) and switch to a visible Debug.Log so the event firing can be confirmed in the Console. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 2d7509e commit 528ff62

2 files changed

Lines changed: 36 additions & 1 deletion

File tree

Runtime/Scripts/AudioStream.cs

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ public sealed class AudioStream : IDisposable
1515
{
1616
internal readonly FfiHandle Handle;
1717
private readonly AudioSource _audioSource;
18-
private readonly AudioProbe _probe;
18+
private AudioProbe _probe;
1919
private RingBuffer _buffer;
2020
private short[] _tempBuffer;
2121
private short[] _crossfadeScratch;
@@ -73,6 +73,11 @@ public AudioStream(RemoteAudioTrack audioTrack, AudioSource source)
7373

7474
// Subscribe to application pause events to handle background/foreground transitions
7575
MonoBehaviourContext.OnApplicationPauseEvent += OnApplicationPause;
76+
77+
// Unity stops every AudioSource when the system audio output device changes
78+
// (e.g. headphones unplugged). Without re-playing the source, OnAudioFilterRead
79+
// stops firing and the stream goes silent until the AudioStream is recreated.
80+
AudioSettings.OnAudioConfigurationChanged += OnAudioConfigurationChanged;
7681
}
7782

7883
// Called on Unity audio thread
@@ -211,6 +216,34 @@ static float S16ToFloat(short v)
211216
}
212217
}
213218

219+
// Called when the system audio output device changes (e.g. plug/unplug headphones)
220+
// or AudioSettings.Reset is invoked. Unity tears down its audio engine in both cases,
221+
// which stops every AudioSource and detaches the AudioProbe filter from the rebuilt
222+
// audio graph. Just calling Play() on the existing source isn't always enough; we
223+
// additionally recreate the AudioProbe so Unity re-registers the OnAudioFilterRead
224+
// node on the new graph.
225+
private void OnAudioConfigurationChanged(bool deviceWasChanged)
226+
{
227+
if (_disposed) return;
228+
229+
lock (_lock)
230+
{
231+
_buffer?.Clear();
232+
_isPrimed = false;
233+
}
234+
235+
if (_probe != null)
236+
{
237+
_probe.AudioRead -= OnAudioRead;
238+
UnityEngine.Object.Destroy(_probe);
239+
}
240+
_probe = _audioSource.gameObject.AddComponent<AudioProbe>();
241+
_probe.AudioRead += OnAudioRead;
242+
243+
_audioSource.Stop();
244+
_audioSource.Play();
245+
}
246+
214247
// Called when application goes to background or returns to foreground
215248
internal void OnApplicationPause(bool pause)
216249
{
@@ -285,6 +318,7 @@ private void Dispose(bool disposing)
285318
// touching partially disposed state.
286319
FfiClient.Instance.AudioStreamEventReceived -= OnAudioStreamEvent;
287320
MonoBehaviourContext.OnApplicationPauseEvent -= OnApplicationPause;
321+
AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;
288322

289323
lock (_lock)
290324
{

Tests/EditMode/MediaStreamLifetimeTests.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ public void AudioStream_Dispose_UnsubscribesAndReleasesOwnedResources()
122122

123123
StringAssert.Contains("FfiClient.Instance.AudioStreamEventReceived -= OnAudioStreamEvent;", source);
124124
StringAssert.Contains("_probe.AudioRead -= OnAudioRead;", source);
125+
StringAssert.Contains("AudioSettings.OnAudioConfigurationChanged -= OnAudioConfigurationChanged;", source);
125126
StringAssert.Contains("_buffer?.Dispose();", source);
126127
StringAssert.Contains("_resampler?.Dispose();", source);
127128
StringAssert.Contains("Handle.Dispose();", source);

0 commit comments

Comments
 (0)