Skip to content

Internet radio - #246

Open
rueczek wants to merge 5 commits into
ghenry22:masterfrom
rueczek:internet-radio
Open

Internet radio#246
rueczek wants to merge 5 commits into
ghenry22:masterfrom
rueczek:internet-radio

Conversation

@rueczek

@rueczek rueczek commented Jul 24, 2026

Copy link
Copy Markdown

Summary

Brief description of what this PR does and why.

Changes

Testing

  • npx tsc --noEmit passes
  • npx jest --no-coverage passes
  • New/updated tests added (if applicable)
  • Tested on iOS
  • Tested on Android

Related Issues

Closes #

rueczek and others added 5 commits July 24, 2026 23:12
Fetch stations via Subsonic getInternetRadioStations and play them as
live streams: new radio screen and store, player helpers for stream
tracks, scrobbling skipped for radio. Add EAS preview profile for
sideloadable APK builds and point app config at the fork's Expo project.
Live streams get a dedicated player treatment (LIVE pill instead of the
seek bar; favorite/bookmark/rate/skip controls hidden via guards in the
shared components) and automatic reconnect with exponential backoff.

Station management moves into the app: create/edit/delete server stations
(Subsonic CRUD, admin errors surfaced inline), local pinning + filtering,
favicon-derived station logos (list + lock screen), a continue-listening
card on Home, and a radio-browser.info catalog screen with preview play
and one-tap add-to-server.

While a station plays, an ICY side-channel polls the stream every 20s
(foreground only) and shows the current song title in the player.
Radio CDNs commonly serve http-only streams (e.g. ic1.smcdn.pl); Android
blocks cleartext by default, so both playback and the ICY side-channel
failed for any http:// station. Enable usesCleartextTraffic via
expo-build-properties.
The ICY title poll opens a second HTTP connection to the live stream
every 20s. On combo Wi-Fi/BT chips the resulting network burst can
starve an active A2DP/SCO link long enough to produce an audible
stutter in playback, so skip the poll while the current audio route
is Bluetooth.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01X9MULGH4qQhmySoD697TVa
Tapping a station replayed audio from an earlier listen instead of
reconnecting live. RNQP routes every remote read through a Media3
CacheDataSource keyed on the track URL (the player's own
MediaSource.Factory, not just the prefetcher), and Media3 only skips
that cache for unset-length responses when
FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS is set, which RNQP does
not set. A live stream was therefore written to disk while it played
and served back from those bytes on the next open.

Four guards:

- patch RNQP so the playback path reads the cache but never writes it
  (setCacheWriteDataSinkFactory(null)); the prefetcher stays the only
  writer, so an hours-long radio session no longer evicts prefetched
  music. The unset-length flag is not usable instead: progressive
  playback opens its DataSpec with C.LENGTH_UNSET, so it would disable
  the cache for every streamed track.
- liveStreamUrl(): stamp a one-shot #live- fragment on every radio
  track built. Fragments never reach the server, so only the cache key
  changes - covers entries older builds already wrote, and iOS.
- applyLookaheadCacheConfig(): force prefetch off while the queue holds
  radio, applied after every queue load before the native setQueue.
- track the stream URLs already played under the current queue and
  reload the queue when a skip lands on one again.

Radio reconnect follows the same rule: tp.retry() rebuilds the failed
item at the pre-failure position (retryFailedItem -> seekTo(idx,
resumeMs), constant-bitrate seeking enabled), which is old audio on a
live stream. Radio drops now reload the station through playTrack;
tp.retry() stays as the offline-mode fallback.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Wu6WBtV1fFvhyVYsnNXGVM
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant