Commit Graph

6 Commits

Author SHA1 Message Date
bvandeusen b6b73fdd0c fix(flutter): live seek bar (~200ms) + breathing room above controls
Why the seek bar appeared frozen: it was reading
PlaybackState.updatePosition, which audio_service only updates on
event transitions (play/pause/buffer/seek). Between events it sits
unchanged, so the bar only jumped at intervals.

Expose just_audio's positionStream (~200ms cadence) from the audio
handler, wrap as positionProvider, and read that in both the mini bar
and the full player. Now the bar advances continuously while playing.

While we're here: spread the full player's vertical layout per
operator — gap to seek 20→32, seek-to-primary 8→24, primary-to-
secondary 16→24, plus 24 below to match. The full player had visible
slack above the controls; this redistributes it.
2026-05-11 08:15:58 -04:00
bvandeusen 19061cd10c feat(flutter): full parity for player bar — shuffle/repeat/volume + Like + kebab + 3-column layout (#356)
Mirrors web's compact PlayerBar both visually (3-column top + full-
width seek bottom) and functionally — closes parity gaps that had
been hidden because the prior mini-bar surfaced none of these
controls.

### audio_handler.dart

- Override `setShuffleMode` → delegates to just_audio's
  `setShuffleModeEnabled`.
- Override `setRepeatMode` → maps audio_service modes (none/one/all/
  group) to just_audio's LoopMode (off/one/all).
- New `setVolume(double)` + `volumeStream` getter (audio_service's
  PlaybackState doesn't carry volume — surface it separately).
- `_broadcastState` now populates PlaybackState's shuffleMode +
  repeatMode fields, and re-fires on shuffle/repeat/loop stream
  changes so UI subscribers stay in sync.

### player_provider.dart

- New `volumeProvider` (StreamProvider<double>).
- `PlayerActions` gains `toggleShuffle()`, `cycleRepeat()` (none →
  all → one → none), and `setVolume(double)`.

### player_bar.dart

Complete rebuild matching the operator-designed layout:

  ┌──────────┬───────────┬──────────────────┐
  │ art      │ ♥   ⋮     │ 🔀 🔁 ☰           │  ← short row
  │ title    │           │                  │
  │ artist   │ ⏮  ⏯  ⏭   │ volume slider    │  ← play row, full size
  ├──────────┴───────────┴──────────────────┤
  │ 0:42 ━━━━━●━━━━━━━━━━━━━━━━━━━━━ 3:21  │
  └─────────────────────────────────────────┘

- Column 1 (35%): cover + title + artist. Tap → /now-playing.
- Column 2 (intrinsic): like + kebab on top, play controls (40/48/40)
  on bottom.
- Column 3 (30%): shuffle/repeat/queue on top, volume slider on bottom.
- Bottom: full-width seek slider with mm:ss labels.

`TrackActionsButton` reused for the kebab; reconstructs a minimal
TrackRef from MediaItem extras (same pattern NowPlayingScreen
already uses).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 23:59:53 -04:00
bvandeusen 153e931490 feat(flutter/player): audio_handler reads from cache first; LockCaching fallback
_buildAudioSource is now async and cache-aware:
1. Cache hit  → AudioSource.uri(file://path)
2. Cache miss with manager → LockCachingAudioSource (cache-as-you-play)
3. No manager configured → plain AudioSource.uri (legacy fallback)

playerActionsProvider.playTracks now passes audioCacheManager into
configure() alongside coverCache. setQueueFromTracks awaits the source
build (Future.wait over the track list).

Out of scope: registering an index row when LockCachingAudioSource
finishes downloading (no clean hook from just_audio). Prefetcher /
Download buttons cover the index path.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 23:20:24 -04:00
bvandeusen 2499449c0b feat(flutter/player): playNext + enqueue + PlaylistsApi.appendTracks
Three small data-layer additions for the upcoming track-actions menu:

- PlaylistsApi.appendTracks(playlistId, trackIds) wraps
  POST /api/playlists/{id}/tracks for the "Add to playlist" action.
- audio_handler gains playNext (insertAudioSource at currentIndex+1)
  and enqueue (addAudioSource) — both also push the audio_service
  queue notifier so the queue-screen UI stays in sync.
- The AudioSource construction was extracted into a private
  _buildAudioSource helper so setQueueFromTracks / playNext / enqueue
  share one source-building path.
- PlayerActions exposes playNext / enqueue for menu use.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 13:40:17 -04:00
bvandeusen d70075c426 feat(flutter/player): albumCoverCacheProvider + pass cache through configure
Adds the Riverpod provider (constructed with dioFactory pulling from
the authenticated dioProvider) and extends PlayerActions.playTracks
to forward the cache to audio_handler.configure(). The handler
signature change lands in the next commit.

Intentional intermediate-broken state: audio_handler.configure
doesn't yet accept coverCache. Resolved by the next task in the same
push.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-09 12:54:05 -04:00
bvandeusen 80f98b3243 feat(flutter/player): MinstrelAudioHandler + just_audio + audio_service
audio_service runs the handler in a background isolate; UI watches
playbackState/mediaItem/queue streams through Riverpod. AndroidManifest
gets the foreground-service media-playback permission; Info.plist gets
UIBackgroundModes=audio. Bearer token attached to stream URLs via
just_audio's per-source headers.
2026-05-02 17:37:14 -04:00