From 545cace829402904cfff091afd7bb3e3e1c7cfb0 Mon Sep 17 00:00:00 2001 From: davidliu Date: Sun, 11 Oct 2026 17:59:16 +0900 Subject: [PATCH] Fix AudioSwitchHandler leaving audio active when stopped during start If stop() was called while the audio thread was still creating the AudioSwitch in start(), stop() saw no switch to tear down, and the new switch was then started and activated with nothing left to stop it. start() now records the handler it posted to. The creation job checks under the handler's lock that this start is still current before publishing the switch, and drops it otherwise. Once published, a subsequent stop() posts its teardown behind the running job. Co-Authored-By: Claude Opus 5.5 --- .changeset/audioswitch-start-stop-guard.md | 5 +++++ .../livekit/android/audio/AudioSwitchHandler.kt | 17 +++++++++++++---- 2 files changed, 18 insertions(+), 4 deletions(-) create mode 100644 .changeset/audioswitch-start-stop-guard.md diff --git a/.changeset/audioswitch-start-stop-guard.md b/.changeset/audioswitch-start-stop-guard.md new file mode 100644 index 00000000..2a1dce65 --- /dev/null +++ b/.changeset/audioswitch-start-stop-guard.md @@ -0,0 +1,5 @@ +--- +"client-sdk-android": patch +--- + +Fix AudioSwitchHandler leaving audio routing active if stop() is called while start() is still setting up the audio switch diff --git a/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt index 1ab517a6..5c232415 100644 --- a/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt +++ b/livekit-android-sdk/src/main/java/io/livekit/android/audio/AudioSwitchHandler.kt @@ -221,9 +221,10 @@ constructor(private val context: Context) : AudioHandler { handler = Handler(thread!!.looper) } - if (audioSwitch == null) { - handler?.removeCallbacksAndMessages(null) - handler?.postAtFrontOfQueue { + val startHandler = handler + if (audioSwitch == null && startHandler != null) { + startHandler.removeCallbacksAndMessages(null) + startHandler.postAtFrontOfQueue { val switch = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { CommDeviceAudioSwitch( @@ -255,7 +256,15 @@ constructor(private val context: Context) : AudioHandler { switch.audioAttributeContentType = audioAttributeContentType switch.forceHandleAudioRouting = forceHandleAudioRouting - audioSwitch = switch + // stop() may have run while this switch was being created, and its teardown + // saw no switch to stop. Only publish the switch if this start is still current; + // otherwise drop it, since nothing is registered until switch.start() below. + synchronized(this@AudioSwitchHandler) { + if (handler !== startHandler) { + return@postAtFrontOfQueue + } + audioSwitch = switch + } switch.start(audioDeviceChangeDispatcher) switch.activate() }