Summary
Replace the bare audio dialog on the web app (files.fx.land/app/) with a full mobile-style audio player, and let users add tracks to playlists / create playlists from the web (cloud-backed, cross-device with the native app).
Today web audio plays in MediaPreviewDialog (just_audio setUrl(blobUrl) + play/pause + a position label only). The web playlists page is view-only ("create in the app"). This brings web to parity with the native audio_player_screen.dart.
Native reference
lib/features/viewer/screens/audio_player_screen.dart + lib/core/services/audio_player_service.dart: now-playing, seek slider, play/pause, skip prev/next, rewind/forward 10s, repeat (off/one/all), shuffle (keeps current track), reorderable queue, add-to-playlist. Engine: just_audio (web-supported) with a hand-rolled queue (not ConcatenatingAudioSource); repeat/shuffle are framework-agnostic logic.
- Playlists are cloud-backed encrypted JSON at
user-playlists/<id>.json in playlists-v8 (write) / playlists (legacy, read-merged), format = Playlist.toJson(). Native writes via FulaApiService.encryptAndUpload(...). Web already READS them (WebFeatures.loadPlaylists()).
Web plan
- New full-screen
WebAudioPlayer (via Dialog.fullscreen, consistent with the image/text previews) replacing MediaPreviewDialog for AUDIO (video stays on Chewie). Now-playing, seek slider, play/pause, skip prev/next, rewind/forward 10s, repeat toggle, shuffle toggle, queue list, add-to-playlist, download.
WebAudioController (ChangeNotifier + just_audio): a QUEUE = the current folder's audio files (start at the tapped track); on play/skip download bytes → Blob URL → setUrl → play; aggressively revoke old blob URLs (queue means many over time); prefetch the next track. On complete, advance per repeat/shuffle. Pure queue/repeat/shuffle transitions extracted to a VM-tested logic file.
- Cloud playlist WRITE (additive web service): "create playlist" and "add to playlist" build
Playlist.toJson() and call the SAME shared encryptAndUpload(writeBucket('playlists'), 'user-playlists/<id>.json', data, kek, contentType: 'application/json') native uses — so native + web read each other's playlists. "Add to existing" = load → append (dedup by path) → bump updatedAt → re-upload the single per-id object. Never deletes, never touches tombstones.
- Unify the playlist-detail play path onto the new player (queue = the playlist's tracks) so there aren't two divergent audio UIs.
Safety
- Write path is encryption/data-format-adjacent + read-modify-write — additive only (calls the shared method with native-identical bucket/key/format/contentType), guardrails: only ever PUT one
user-playlists/<id>.json, never delete, never write tombstones. Independent review by Copilot (Codex/Cursor rate-limited).
- Pure logic (queue transitions, playlist JSON build/append) VM-unit-tested; the just_audio/blob glue is verified live.
Acceptance
- Tapping an audio file opens a full player: seek, play/pause, skip prev/next across the folder queue, rewind/forward, repeat, shuffle.
- "Add to playlist" (create new / add to existing) persists to cloud; the playlist then shows the track on web AND in the native app.
- No regression to video playback, downloads, or the Recent strip. analyze + unit tests + web build green; live.
Part of the mobile->web parity work.
Summary
Replace the bare audio dialog on the web app (files.fx.land/app/) with a full mobile-style audio player, and let users add tracks to playlists / create playlists from the web (cloud-backed, cross-device with the native app).
Today web audio plays in
MediaPreviewDialog(just_audiosetUrl(blobUrl)+ play/pause + a position label only). The web playlists page is view-only ("create in the app"). This brings web to parity with the nativeaudio_player_screen.dart.Native reference
lib/features/viewer/screens/audio_player_screen.dart+lib/core/services/audio_player_service.dart: now-playing, seek slider, play/pause, skip prev/next, rewind/forward 10s, repeat (off/one/all), shuffle (keeps current track), reorderable queue, add-to-playlist. Engine: just_audio (web-supported) with a hand-rolled queue (not ConcatenatingAudioSource); repeat/shuffle are framework-agnostic logic.user-playlists/<id>.jsoninplaylists-v8(write) /playlists(legacy, read-merged), format =Playlist.toJson(). Native writes viaFulaApiService.encryptAndUpload(...). Web already READS them (WebFeatures.loadPlaylists()).Web plan
WebAudioPlayer(viaDialog.fullscreen, consistent with the image/text previews) replacingMediaPreviewDialogfor AUDIO (video stays on Chewie). Now-playing, seek slider, play/pause, skip prev/next, rewind/forward 10s, repeat toggle, shuffle toggle, queue list, add-to-playlist, download.WebAudioController(ChangeNotifier + just_audio): a QUEUE = the current folder's audio files (start at the tapped track); on play/skip download bytes → Blob URL →setUrl→ play; aggressively revoke old blob URLs (queue means many over time); prefetch the next track. On complete, advance per repeat/shuffle. Pure queue/repeat/shuffle transitions extracted to a VM-tested logic file.Playlist.toJson()and call the SAME sharedencryptAndUpload(writeBucket('playlists'), 'user-playlists/<id>.json', data, kek, contentType: 'application/json')native uses — so native + web read each other's playlists. "Add to existing" = load → append (dedup by path) → bump updatedAt → re-upload the single per-id object. Never deletes, never touches tombstones.Safety
user-playlists/<id>.json, never delete, never write tombstones. Independent review by Copilot (Codex/Cursor rate-limited).Acceptance
Part of the mobile->web parity work.