Describe the bug
While a decoded source is playing (Local Files, Subsonic, Internet Radio, YouTube, Qobuz), losing the audio output device — Bluetooth headphones disconnecting, AirPods going back in their case, a USB DAC unplugged — does not just make playback silent. It freezes the whole app: the UI still draws and the playbar still says "playing", but search stops returning results and the playback controls do nothing. Only a restart recovers it.
The cause is that these five sources share one audio engine (src/infra/audio/player.rs), and rodio's Player::clear() and try_seek() wait on the audio callback with no timeout (sleep_until_end() → recv(), and the seek feedback channel). Once the device is gone that callback never runs again, so the wait never ends. Those calls are made straight from the serial IoEvent pump (play_file, play_prepared and stop all call clear()), so the one that blocks takes every unrelated request queued behind it down with it — which is why searching dies along with playback.
Worth noting that this has two shapes, and the one that bites is the one nothing reports: cpal notices a device being removed (DeviceNotAvailable), but it cannot notice the OS simply moving its default output elsewhere. In that case the device we opened is still perfectly alive and the stream keeps feeding it, so there is no error anywhere — just silence, then a freeze at the next transport call.
To Reproduce
- Build with any decoded source, e.g.
cargo run --features all-sources
- Start playing a local file, a YouTube track, or a radio station through Bluetooth headphones
- Disconnect the headphones mid-track (put AirPods back in their case, or unplug a USB DAC)
- Audio goes silent while the playbar still shows "playing"
- Try to search, or press play/pause, or skip a track — nothing responds from here on
Expected behavior
Losing the output device should not be able to freeze the app. Ideally playback continues on whatever output the system moved to (paused, the way macOS itself behaves when AirPods come out); at minimum the app stays responsive and says what happened.
Desktop (please complete the following information):
- OS: macOS 26.6.2 (arm64) — but see below, this is not platform-specific
- Terminal: any (not terminal-specific)
- Version: 0.41.0 (current
main)
Additional context
Not a macOS-specific bug, even though that is where I hit it. LocalPlayer is shared by all five sources on all platforms, and the blocking calls are unguarded on main (src/infra/audio/player.rs lines 91, 164, 188 and 233). A USB DAC unplugged on Linux, or Bluetooth headphones disconnecting on Windows, goes down the identical path, so the released Linux and Windows binaries have the same exposure today. (macOS release binaries do not ship the decoded sources yet, which is the only reason this has not been reported from there.)
I have a fix and will open a PR referencing this issue.
Describe the bug
While a decoded source is playing (Local Files, Subsonic, Internet Radio, YouTube, Qobuz), losing the audio output device — Bluetooth headphones disconnecting, AirPods going back in their case, a USB DAC unplugged — does not just make playback silent. It freezes the whole app: the UI still draws and the playbar still says "playing", but search stops returning results and the playback controls do nothing. Only a restart recovers it.
The cause is that these five sources share one audio engine (
src/infra/audio/player.rs), and rodio'sPlayer::clear()andtry_seek()wait on the audio callback with no timeout (sleep_until_end()→recv(), and the seek feedback channel). Once the device is gone that callback never runs again, so the wait never ends. Those calls are made straight from the serial IoEvent pump (play_file,play_preparedandstopall callclear()), so the one that blocks takes every unrelated request queued behind it down with it — which is why searching dies along with playback.Worth noting that this has two shapes, and the one that bites is the one nothing reports: cpal notices a device being removed (
DeviceNotAvailable), but it cannot notice the OS simply moving its default output elsewhere. In that case the device we opened is still perfectly alive and the stream keeps feeding it, so there is no error anywhere — just silence, then a freeze at the next transport call.To Reproduce
cargo run --features all-sourcesExpected behavior
Losing the output device should not be able to freeze the app. Ideally playback continues on whatever output the system moved to (paused, the way macOS itself behaves when AirPods come out); at minimum the app stays responsive and says what happened.
Desktop (please complete the following information):
main)Additional context
Not a macOS-specific bug, even though that is where I hit it.
LocalPlayeris shared by all five sources on all platforms, and the blocking calls are unguarded onmain(src/infra/audio/player.rslines 91, 164, 188 and 233). A USB DAC unplugged on Linux, or Bluetooth headphones disconnecting on Windows, goes down the identical path, so the released Linux and Windows binaries have the same exposure today. (macOS release binaries do not ship the decoded sources yet, which is the only reason this has not been reported from there.)I have a fix and will open a PR referencing this issue.