Skip to content

Poll now-playing metadata for radio stations#151

Merged
phanan merged 2 commits into
masterfrom
feat/radio-now-playing-metadata
Mar 29, 2026
Merged

Poll now-playing metadata for radio stations#151
phanan merged 2 commits into
masterfrom
feat/radio-now-playing-metadata

Conversation

@phanan
Copy link
Copy Markdown
Member

@phanan phanan commented Mar 29, 2026

Summary

  • Polls GET radio/stations/{id}/now-playing every 15s while a radio station is playing (matching the web client's behavior)
  • Updates the OS media session (lock screen / Dynamic Island): stream title becomes the MediaItem title, station name moves to artist field
  • Updates the in-app mini player subtitle (already wired to streamTitle)
  • API failures are logged with debugPrint without affecting playback
  • Stale responses are discarded if the station changed between request and response
  • Polling stops on stop, station switch, and dispose

Test plan

  • Play a radio station and verify the stream title appears in the mini player and on the lock screen after ~15s
  • Verify the lock screen shows "Stream Title" as title and "Station Name" as artist when metadata is available
  • Verify fallback to "Station Name" / "Radio" when no stream title is available
  • Switch stations and verify the old poll stops and new metadata appears
  • Stop radio and verify polling stops (no network requests in logs)
  • Run flutter test

Summary by CodeRabbit

  • New Features
    • Mini-player and station list now show automatically-updated "now playing" metadata during playback, preferring stream titles when available and updating regularly.
  • Tests
    • Added tests covering how stream metadata affects displayed title, artist, ID and artwork behavior.

Fetch stream_title from the API every 15 seconds while a radio station is
playing. Updates both the in-app stream title and the OS media session
(lock screen / Dynamic Island) — when a stream title is available, it
becomes the MediaItem title and the station name moves to artist. Polling
stops on station stop/switch/dispose. API failures are logged silently
without affecting playback. Stale responses are discarded if the station
changed between request and response.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Mar 29, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b4ffdfe4-0091-4466-ae44-ba774636a5c8

📥 Commits

Reviewing files that changed from the base of the PR and between 2b23441 and 79e4588.

📒 Files selected for processing (1)
  • lib/ui/widgets/mini_player.dart

📝 Walkthrough

Walkthrough

Added a 15-second periodic polling mechanism to fetch "now playing" metadata while radio playback is active; polling updates the provider's _streamTitle, refreshes the active MediaItem, and is started on play() and stopped on stop()/dispose(). Also changed mediaItemForStation to accept an optional streamTitle.

Changes

Cohort / File(s) Summary
Radio player polling implementation
lib/providers/radio_player_provider.dart
Introduced _startNowPlayingPolling, _stopNowPlayingPolling, _fetchNowPlaying with a 15s Timer.periodic; removed public updateStreamTitle; start polling on successful play() and stop on stop()/dispose(); update _streamTitle and audioHandler.mediaItem when stream_title changes; changed mediaItemForStation signature to accept optional streamTitle.
Mini-player UI update
lib/ui/widgets/mini_player.dart
Mini-player title/secondary text now prefer radioPlayer.streamTitle when present (radioPlayer.streamTitle ?? station.name) and adjust rendering logic for connecting/loading states.
Radio now-playing metadata tests
test/ui/screens/radio_stations_test.dart
Added tests covering mediaItemForStation with omitted, provided, and explicitly null streamTitle; asserts title/artist fallback behavior and that id/artUri remain unchanged.

Sequence Diagram

sequenceDiagram
    participant User
    participant Provider as RadioPlayerProvider
    participant Timer as Timer/Scheduler
    participant API as Radio API
    participant Handler as AudioHandler

    User->>Provider: play(station)
    activate Provider
    Provider->>Provider: start playback succeeds
    Provider->>Timer: _startNowPlayingPolling(station)
    activate Timer
    Timer->>API: _fetchNowPlaying(station) [immediate]
    activate API
    API-->>Provider: { stream_title }
    deactivate API
    Provider->>Handler: update mediaItem (title/artist)
    Provider->>Provider: notifyListeners()
    
    Note over Timer: wait 15s
    Timer->>API: _fetchNowPlaying(station) [periodic]
    activate API
    API-->>Provider: { stream_title }
    deactivate API
    Provider->>Handler: update mediaItem (if changed)
    Provider->>Provider: notifyListeners()
    
    User->>Provider: stop()
    Provider->>Timer: _stopNowPlayingPolling()
    deactivate Timer
    deactivate Provider
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Poem

🐰 A timer hops each fifteen-second mile,

Fetching stream-titles with a floppy-eared smile,
When songs change the air, I twitch my nose,
Media items update as the current tune shows,
Hooray for fresh metadata — a rabbit's delight in code!

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main change: implementing periodic polling of now-playing metadata for radio stations, which is the primary focus of all code modifications.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/radio-now-playing-metadata

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
test/ui/screens/radio_stations_test.dart (1)

138-186: Consider adding tests for polling lifecycle behavior.

The current tests cover mediaItemForStation well, but there's no test coverage for:

  • Polling starts after play() completes
  • Polling stops on stop() or station switch
  • Stale responses are discarded when station changes mid-request

These behaviors are mentioned in the PR objectives but aren't validated by tests. This would require mocking the API and possibly using fake timers.

Would you like me to help draft integration tests for the polling lifecycle? This could use FakeAsync to control timer advancement and mock HTTP responses.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/ui/screens/radio_stations_test.dart` around lines 138 - 186, Add tests
in radio_stations_test.dart that validate the polling lifecycle: verify polling
begins only after RadioPlayerProvider.play() completes, verify polling stops on
RadioPlayerProvider.stop() and when switching stations, and verify stale HTTP
responses are ignored if the station changes mid-request. Implement these using
FakeAsync to control timers and a mocked HTTP client (or mock API) to return
controllable responses; advance timers to trigger polling and assert that the
mocked client is called (or not) as expected, and assert that state updates
correspond to the latest station (i.e., stale responses do not overwrite current
station metadata). Target tests to exercise RadioPlayerProvider.play(),
RadioPlayerProvider.stop(), and whatever internal polling method (e.g., _poll or
poll loop) by observing side-effects such as calls to the mocked API, media item
updates, and polling cancellation when switching stations.
lib/providers/radio_player_provider.dart (1)

149-168: Consider whether paused state should continue polling.

The polling continues during pause (only stop() cancels it). This matches the PR description but means metadata updates occur even when paused. If this is intentional, it's fine—just confirming the design choice.

Additionally, the stale response guard at line 155 correctly discards responses when the station changed mid-request.

One minor observation: togglePlayPause() (lines 128-134) doesn't affect polling. If the user pauses a radio station, polling continues. Consider whether pausing should suspend polling to reduce unnecessary network requests:

💡 Optional: Pause polling when playback is paused
  Future<void> togglePlayPause() async {
    if (_playing) {
+     _stopNowPlayingPolling();
      await _player.pause();
    } else if (_currentStation != null) {
      await _player.play();
+     _startNowPlayingPolling(_currentStation!);
    }
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/providers/radio_player_provider.dart` around lines 149 - 168, Polling
keeps running when playback is paused because togglePlayPause() doesn't change
the polling state; update togglePlayPause() to suspend metadata polling when
pausing and resume when playing by either toggling the existing active flag the
_fetchNowPlaying() guard uses or cancelling/creating the polling Timer (the
inverse of stop()). Specifically, in togglePlayPause() set active = false (or
cancel _pollingTimer) when pausing and set active = true (or restart the poller)
when resuming so _fetchNowPlaying() will early-return for paused playback;
ensure stop() still fully cancels the poller.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@lib/providers/radio_player_provider.dart`:
- Around line 149-168: Polling keeps running when playback is paused because
togglePlayPause() doesn't change the polling state; update togglePlayPause() to
suspend metadata polling when pausing and resume when playing by either toggling
the existing active flag the _fetchNowPlaying() guard uses or
cancelling/creating the polling Timer (the inverse of stop()). Specifically, in
togglePlayPause() set active = false (or cancel _pollingTimer) when pausing and
set active = true (or restart the poller) when resuming so _fetchNowPlaying()
will early-return for paused playback; ensure stop() still fully cancels the
poller.

In `@test/ui/screens/radio_stations_test.dart`:
- Around line 138-186: Add tests in radio_stations_test.dart that validate the
polling lifecycle: verify polling begins only after RadioPlayerProvider.play()
completes, verify polling stops on RadioPlayerProvider.stop() and when switching
stations, and verify stale HTTP responses are ignored if the station changes
mid-request. Implement these using FakeAsync to control timers and a mocked HTTP
client (or mock API) to return controllable responses; advance timers to trigger
polling and assert that the mocked client is called (or not) as expected, and
assert that state updates correspond to the latest station (i.e., stale
responses do not overwrite current station metadata). Target tests to exercise
RadioPlayerProvider.play(), RadioPlayerProvider.stop(), and whatever internal
polling method (e.g., _poll or poll loop) by observing side-effects such as
calls to the mocked API, media item updates, and polling cancellation when
switching stations.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 3673002e-7f9a-4266-98fa-357355693787

📥 Commits

Reviewing files that changed from the base of the PR and between 285d272 and 2b23441.

📒 Files selected for processing (2)
  • lib/providers/radio_player_provider.dart
  • test/ui/screens/radio_stations_test.dart

When a stream title is available, it displays as the main bold text with
the station name underneath as subtitle. Falls back to station name as
primary text when no stream title is available.
@phanan phanan merged commit 88ab469 into master Mar 29, 2026
2 checks passed
@phanan phanan deleted the feat/radio-now-playing-metadata branch March 29, 2026 13:30
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