docs: move Phase 2.5 Stream Now to COMPLETED.md
This commit is contained in:
@@ -6,6 +6,58 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.5 — "Stream Now" — random-track instant play
|
||||
|
||||
**Status:** Fully landed on 2026-06-07 (feature complete, endpoints + service methods + menu wiring, merged to dev).
|
||||
|
||||
- **What:** The nav-bar "Stream Now ▶" CTA (desktop and mobile, in `DeepDrftMenu.razor`) today just navigates to `/tracks`. Change it to **pick a random track from the library and start playing it immediately**, in place, without forcing the user onto the gallery page.
|
||||
- **Why it matters:** It is the single most prominent call-to-action on the site and currently does the least interesting thing — it dumps the listener on a grid and asks them to choose. "Stream Now" should mean *now*: one click, music plays. It is also the lowest-friction way for a first-time visitor to hear the collective's output, which is the whole point of the public site. Borrowed pattern: the "shuffle play" / "I'm feeling lucky" affordance (Spotify's shuffle, Bandcamp's "play random").
|
||||
|
||||
#### UX flow
|
||||
|
||||
1. User clicks "Stream Now ▶" (desktop CTA or mobile menu item).
|
||||
2. Button enters a brief loading affordance (disabled + subtle pulse/spinner) while a track is selected — the selection requires at least one HTTP round-trip, so this is not instantaneous.
|
||||
3. A random track is chosen from the full library via `GET api/track/random` (server-side `ORDER BY RANDOM() LIMIT 1`).
|
||||
4. The player begins streaming that track via the existing `AudioPlayerBar` dock at the bottom of the layout. The dock is already cascaded into every page by `AudioPlayerProvider` in `MainLayout`, so it appears/animates in exactly as it does when a gallery card is clicked.
|
||||
5. The user does **not** navigate. They stay on whatever page they were on (most likely `Home`). Music plays; the dock is the player surface.
|
||||
6. On mobile, the menu closes (`CloseMobileMenu`) as part of the click, same as the existing nav links.
|
||||
|
||||
#### Edge cases
|
||||
|
||||
- **Empty library (`TotalCount == 0`):** No track to play. The button surfaces a non-blocking, transient message ("No tracks yet") and does nothing else. Does not navigate, does not error-toast aggressively. This is a legitimate cold-start state, not a failure.
|
||||
- **Metadata fetch fails (HTTP error):** Surfaces a transient error on the button ("Couldn't reach the library — try again"), re-enables the button, does not navigate. Reuses the existing `ApiResult` failure check pattern (`result is { Success: true, ... }`).
|
||||
- **Track fails to stream (selected track is valid metadata but the audio stream errors):** Already handled downstream by `StreamingAudioPlayerService` / error handlers and surfaced through `IPlayerService.ErrorMessage` and the dock. Stream Now does not duplicate stream-error handling in the menu; it hands off to the same `SelectTrackStreaming` path every other play uses, and inherits that path's error behavior.
|
||||
- **Player already playing something:** Stream Now interrupts it and starts the random track. No confirmation prompt — "Stream Now" is an explicit user command to play something new.
|
||||
- **Repeat clicks / same-track-twice:** Acceptable for v1 to occasionally re-pick the currently-playing track. If it becomes annoying, a cheap "exclude `PlayerService.CurrentTrack?.Id`" filter on the candidate set is a one-line follow-up; noted for future.
|
||||
|
||||
#### Implementation
|
||||
|
||||
**API endpoint (`DeepDrftAPI`):**
|
||||
- New `GET api/track/random` (unauthenticated, mirroring `GET api/track/page`) returning a single `TrackDto` via `ORDER BY RANDOM() LIMIT 1` (or the EF-Core equivalent) server-side.
|
||||
|
||||
**Service methods:**
|
||||
- New method on `ITrackDataService` / `TrackClientDataService`: `Task<ApiResult<TrackDto?>> GetRandomTrack()`, calling `GET api/track/random` via `TrackClient`.
|
||||
|
||||
**Menu wiring (`DeepDrftMenu.razor`):**
|
||||
- Injects `ITrackDataService` and cascaded `IStreamingPlayerService`. Click handler: calls `GetRandomTrack()`, on success calls `PlayerService.SelectTrackStreaming(track)`, on empty/failure shows transient message.
|
||||
|
||||
**AudioContext user-gesture constraint:**
|
||||
- Browsers (Safari most strictly) only allow an `AudioContext` to start inside a user-gesture call stack. `SelectTrackStreaming` starts the context. Stream Now does an `await GetRandomTrack()` (network) before calling `SelectTrackStreaming` — an intervening `await` can lose gesture context on Safari. Mitigation: `IStreamingPlayerService.WarmAudioContext()` method added, called synchronous with the gesture at the start of the click handler, before the network await.
|
||||
|
||||
#### Acceptance criteria — as implemented
|
||||
|
||||
- Clicking "Stream Now ▶" (desktop CTA) with a non-empty library selects a track uniformly at random (server-side) and begins streaming it via the existing dock, without navigating away.
|
||||
- Clicking "Stream Now ▶" in the mobile menu does the same and closes the mobile menu.
|
||||
- Selection issues **exactly one** HTTP request (`GET api/track/random`).
|
||||
- With an empty library, the button shows a transient "no tracks" message and does not navigate or throw.
|
||||
- With a failed metadata fetch, the button shows a transient error, re-enables, and does not navigate.
|
||||
- A track that streams-errors after selection surfaces through the *existing* player error path — no new error handling in the menu.
|
||||
- The menu component contains no track-fetch logic inline: selection goes through `ITrackDataService.GetRandomTrack()`; playback goes through `PlayerService.SelectTrackStreaming`. No duplication.
|
||||
- Audio plays on the first click after a cold load on Chrome and Safari — user-gesture/AudioContext constraint satisfied via `WarmAudioContext()` hook.
|
||||
- While selection is in flight, the button is disabled to prevent double-launch.
|
||||
|
||||
---
|
||||
|
||||
## Phase 2.1 — Cover art / image vault wired through
|
||||
|
||||
**Status:** Fully landed on 2026-06-07 across three waves (Wave 1: API + vault; Wave 2-A: public proxy + TrackCard; Wave 2-B: CMS upload UI), merged to dev.
|
||||
|
||||
Reference in New Issue
Block a user