Describe the bug
Some internet radio stations do not just fail to play — they freeze the app. Selecting one plays nothing and reports no error, and from that moment search and the playback controls stop responding (audio that was already playing keeps going). Only a restart recovers it.
Starting a station ends by working out the stream's audio format, and rodio's symphonia does that by scanning the stream for a start-of-stream marker it recognises. A live stream has no end to stop that scan. So a station in a format symphonia cannot identify leaves it reading forever — and since play_stream is awaited on the serial IoEvent pump (src/infra/radio/dispatch.rs:276 on main), everything queued behind it stops too.
The specific format that triggered it here is MPEG-2 ADTS AAC, which is common on European radio. symphonia's AdtsReader registers only the MPEG-4 ADTS marker ff f1, not MPEG-2's ff f9 (symphonia-codec-aac/src/adts.rs), so a stream starting with ff f9 never matches anything. Worth knowing that a mime hint cannot rescue this: Probe::format takes the hint as _hint and ignores it.
To Reproduce
- Build with radio, e.g.
cargo run --features internet-radio
- Search the station directory for
radio bruno
- Select any of the results — for example
https://stream3.xdevel.com/audio6s975355-281/stream/icecast.audio (Content-Type: audio/aacp, first bytes ff f9)
- Nothing plays and no error appears
- Try to search again or change track — the app no longer responds
Measured directly: open_radio_stream returns in under a second (content type and ICY station name both read correctly), and LocalPlayer::prepare_stream then never returns. A thread sample shows it parked inside read, waiting on stream-download for bytes it will never be able to make sense of.
Expected behavior
A station that cannot be decoded should fail quickly with a message, and leave the app usable. src/infra/radio/stream.rs already caps the connect phase (CONNECT_TIMEOUT) and the header wait (HEADER_TIMEOUT) for exactly this reason — the format probe is the third step of the same tune-in sequence and is the one left unbounded.
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 platform-specific: the unbounded await is in shared code on main, so the released Linux and Windows binaries freeze on these stations today.
Two things I found while tracking it down that are worth recording:
- Giving up has to stop the download, not the reader. A cancellation flag the reader checks between reads is never reached, because the probe parks inside
read. Cancelling the stream-download task marks the stream done and wakes every waiter, so the read returns, the probe hits end-of-stream, and the thread and its download are released together.
- Making these stations actually play is an upstream matter: adding
ff f9 to symphonia's ADTS markers is a one-line change there and would cover a good slice of European radio. Bounding the probe here only turns the freeze into a clean error.
I have a fix for the freeze and will open a PR referencing this issue.
Describe the bug
Some internet radio stations do not just fail to play — they freeze the app. Selecting one plays nothing and reports no error, and from that moment search and the playback controls stop responding (audio that was already playing keeps going). Only a restart recovers it.
Starting a station ends by working out the stream's audio format, and rodio's symphonia does that by scanning the stream for a start-of-stream marker it recognises. A live stream has no end to stop that scan. So a station in a format symphonia cannot identify leaves it reading forever — and since
play_streamis awaited on the serial IoEvent pump (src/infra/radio/dispatch.rs:276onmain), everything queued behind it stops too.The specific format that triggered it here is MPEG-2 ADTS AAC, which is common on European radio. symphonia's
AdtsReaderregisters only the MPEG-4 ADTS markerff f1, not MPEG-2'sff f9(symphonia-codec-aac/src/adts.rs), so a stream starting withff f9never matches anything. Worth knowing that a mime hint cannot rescue this:Probe::formattakes the hint as_hintand ignores it.To Reproduce
cargo run --features internet-radioradio brunohttps://stream3.xdevel.com/audio6s975355-281/stream/icecast.audio(Content-Type: audio/aacp, first bytesff f9)Measured directly:
open_radio_streamreturns in under a second (content type and ICY station name both read correctly), andLocalPlayer::prepare_streamthen never returns. A thread sample shows it parked insideread, waiting on stream-download for bytes it will never be able to make sense of.Expected behavior
A station that cannot be decoded should fail quickly with a message, and leave the app usable.
src/infra/radio/stream.rsalready caps the connect phase (CONNECT_TIMEOUT) and the header wait (HEADER_TIMEOUT) for exactly this reason — the format probe is the third step of the same tune-in sequence and is the one left unbounded.Desktop (please complete the following information):
main)Additional context
Not platform-specific: the unbounded await is in shared code on
main, so the released Linux and Windows binaries freeze on these stations today.Two things I found while tracking it down that are worth recording:
read. Cancelling the stream-download task marks the stream done and wakes every waiter, so the read returns, the probe hits end-of-stream, and the thread and its download are released together.ff f9to symphonia's ADTS markers is a one-line change there and would cover a good slice of European radio. Bounding the probe here only turns the freeze into a clean error.I have a fix for the freeze and will open a PR referencing this issue.