57 — Player as Product — continuity, favorites & release-preview parity (envisioning)
Envisioning doc (product/behavior). Governed by
00-ROADMAP.md— defer to it on any conflict. Created 2026-07-05 (owner-raised, + stakeholder ask): the player should grow from a review surface into the place an artist lives with their own music — audio that survives navigation with a calm, explicit handoff; a favorites queue so the app doubles as the player of your own catalog; and release-preview parity — cover, motion artwork, and synced lyrics rendered the way Apple Music will render them, "so the user can be sure what would be published without needing to use Apple Music."Builds on: doc-30 (playback mechanics — Phases 1–3) · doc-56 (first-play pipeline; gates the player rework) · doc-22/doc-24 (the locked waveform-centric player) · doc-45 (motion covers, release side) · doc-32 (approval dimensions) · doc-26 (lenses).
The split: doc-30 keeps the playback mechanics (PlaybackManager, mini player, queue plumbing); doc-57 owns the player-as-product behavior — what the machinery should feel like and the features that make it a product. Status: locked — owner-confirmed 2026-07-06 ("good to go"): every fork and §9 item is resolved (forks pre-marked 2026-07-05; §9 resolved 2026-07-06). Build follows §8 sequencing — P0 shipped 2026-07-06 (with doc-56 S1;
xcodebuildgreen, ⏳ device QA); P1+ ride doc-56 S2 + doc-30 Phase 2 (each with its own greenlight).
1. The idea, in three asks
- Continuity + the handoff. Audio keeps playing while you move through the app (doc-30 Phase 2). The new question is behavior: when something is sounding and you open a different track/version, what happens? Owner's instinct: don't yank the listening session — surface an explicit affordance to switch. Specified in §3 as browse ≠ play.
- Favorites — the app as the player of your own music (stakeholder-raised). Heart tracks/versions → a "play my favorites" queue, so Sonnance is also where you enjoy the catalog you're making, not only where you approve it. Specified in §4. Named playlists explicitly deferred.
- Release-preview parity. Square cover (already the norm), motion artwork previewed as Apple Music renders it — including the iOS 26 full-screen animated lock screen — and line-synced lyrics with TTML export, so "what will the release look/read like on the store?" is answerable in-app. Specified in §5–§6.
Musixmatch stance (owner-decided 2026-07-05): replicate, don't integrate — build the sync/preview experience in-house for the artist's own lyrics; document Musixmatch Pro + distributors as the publishing channel. Rationale in §7.
2. What already anchors this (synthesis, not invention)
| Anchor | What it gives | What doc-57 adds |
|---|---|---|
| doc-30 Phases 1–3 | Background audio ✅ · Phase 2 PlaybackManager + mini player · Phase 3 favorites-queue/loop/continuous | The behavior spec the mechanics serve: handoff rules (§3), favorites data model + queue semantics (§4) |
| doc-56 | Route-enforced seeds/prewarm (TrackRoute(track:) sole entry), AudioPrewarmer, TTFA metric | The loaded-paused "second deck" of §3 is the prewarmed item — same machinery, seen twice |
| doc-22 §4/§9 + doc-24 | The locked player design: waveform-centric, cover lives in PlayerBar, "no big cover frame", motion = "nothing pops" | A separate, explicitly-entered listening surface (§6) — the wave-page lock stands |
| doc-45 thread 1 | Motion covers: store · preview-as-store-renders · approve, in the asset cloud | The player side: in-app artwork mode + lock-screen preview via iOS 26 API (§5.2); answers doc-45's "formats TBD" with Apple's actual spec |
| doc-32 | Approval dimensions; v1 = cover + tracklist, lyrics deferred | Nothing — §5.3 explicitly does not create a lyrics-approval dimension |
| doc-26 | "One graph, many lenses; lenses filter, roles gate" | Favorites = a private per-user lens over nodes you can already read (§4) |
| Code today | PlayerBar self-describes as the doc-30 mini-player precursor · no MPMediaItemArtwork anywhere (lock screen shows transport, no art) · Track covers = image[]/imageFinal File refs, Project/Artist covers = inline URL strings · lyrics = opaque blobs in a Lyrics attachment folder · 'video' uploads are folded into type:'audio', no poster/variant pipeline | The gaps §5 fills — one of them (lock-screen static artwork) is hours-sized and gate-free (P0) |
3. Continuity & the handoff — browse ≠ play
Mechanism recap (doc-30 Phase 2): one app-scoped PlaybackManager owns the AVPlayer; the wave page becomes a view onto playback; a floating glass mini player docks above the tab bar when audio is active outside the wave page. Doc-57 specifies only what that machinery should do.
3.1 The fork — what happens when you open track B while track A is sounding?
| Fork | Pros | Cons |
|---|---|---|
| A. Auto-switch on open (Spotify-ish; closest to today) | One deck, zero rules to learn; "what you see is what you hear" | Destroys the listening session on every navigation; browsing = roulette; wrong for both the review flow (compare-while-inspecting) and the "enjoy my catalog" flow |
| B. Browse ≠ play ✅ (owner-marked 2026-07-05) | Track B opens loaded-but-paused with a prominent play affordance; A keeps sounding in the mini player. Navigation is safe — you can inspect anything without losing your place; matches Apple Music's browse model | Two decks to understand; the wave page needs an unmistakable "this is loaded, not sounding" state |
| C. Intent-hybrid | Explicit play affordances (row ▶, approval-row CTA) auto-switch; navigational taps load-paused | Smartest per-surface, but the rule set is invisible — users must learn which taps switch |
Recommendation: B. The affordance the owner sketched ("a play button or switch to confirm the hearing change") is fork B: the loaded track's play control is the switch, and pressing it is the only thing that changes what's sounding. C stays open as a later refinement (an approval-row CTA that plays directly is compatible with B's rules — it's an explicit play).
3.2 Rules of truth (the invariant set)
- The mini player and the lock screen always show what's sounding — never the loaded deck.
- The wave page shows the loaded track/version; if it isn't the sounding one, its transport renders the paused/play-prominent state (seeded per doc-56 §4.3 — pressable immediately).
- Explicit play is the only switch. Pause/scrub on the mini player never navigates; tap on the mini player returns to the sounding track's wave page (doc-30 Phase 2 behavior, unchanged).
- Version/variant switch = browse ≠ play too (owner-decided 2026-07-06). Selecting V3 while V2 sounds loads V3 paused on the wave page — audio keeps playing V2 until you press play. So the transport, mini player and lock screen always show the sounding version's number/label, even while you inspect another (the wave-page body shows the viewed version). Fast A/B auditioning is preserved by a play control on each
VersionCard(tap card = view/load-paused, tap ▶ = switch audio). (This reverses today's shipped immediate-switch — tracked in §9.)
3.3 Why this is nearly free (feasibility)
doc-56's AudioPrewarmer already holds a muted second AVPlayer buffering the tapped track before the push lands. Under fork B, that prewarmed item is the loaded-paused deck — browse ≠ play and the First-Play Pipeline are the same machinery seen twice. PlaybackManager rides the enforced TrackRoute(track:) layer (doc-56 §4.1-B) — it never replaces it, and a surface that can't seed can't regress the handoff either.
4. The queue — album play, favorites & playback modes
The stakeholder ask ("use the app as the player of my own music") and the owner's follow-up ("play a whole project/album; play/repeat/loop") point at the thing doc-30 Phase 3 only sketched in bullets: the queue is a first-class object, and Favorites is one of several sources that fill it.
4.1 The queue and its sources
PlaybackManager owns a queue = an ordered list of resolved versions + a current index + a repeat/shuffle mode. It's filled from a source:
| Source | What it plays | Data it rides |
|---|---|---|
| Project / album ✅ (the fundamental one) | the record top-to-bottom — the project's tracks in Track.order, each resolved to audioFinal | existing Track.order + the lean audio-final ref doc-56 S1 already adds to the list queries → no new backend |
| Favorites | "play my favorites" — the Favorite collection (§4.2), recency order | the new Favorite collection |
| Single (today's default) | just this track, stop at end | — |
| Named playlist (deferred) | a curated, ordered set | a future Playlist entity — out of scope |
Album play is the most fundamental listening behavior and the cheapest (derived from data we already have + what doc-56 S1 populates), so it leads; Favorites is the second source, gated only on its store.
Interaction with browse ≠ play (§3): starting an album sets the sounding queue; next/prev advance the sounding queue, never what you're inspecting. Tapping into a track to look at it loads it paused (browse ≠ play) without disturbing the queue. Pressing play on a track that's in the currently-sounding album jumps the queue to it; on a track outside it, play starts a fresh queue from that track. (Jump-the-queue confirmed by the owner 2026-07-06.)
4.2 Favorites store shape
| Fork | Pros | Cons |
|---|---|---|
A. User.favorites[] embedded array (the recentAccess precedent) | Cheapest; pattern exists (user.service.ts dedupe→unshift→cap) | Caps-shaped for a thing that shouldn't cap; unbounded User doc growth; weak for lens queries ("my favorites in this project") |
B. Dedicated Favorite collection ✅ (owner-marked 2026-07-05) | { user, resource: { type: 'track'|'file', id }, createdAt }, unique index (user, resource.type, resource.id). Queue source = find-by-user, sorted createdAt desc. Lens-compatible (doc-26); private-per-user by construction; the natural substrate if named playlists ever un-defer (Favorites = the one implicit playlist) | A new collection + module (mirrors the existing module shape; small) |
C. On-node arrays (the comment.likes[] precedent) | Zero new collections | Leaks a personal lens onto shared docs; every heart writes a shared node; scales badly |
Recommendation: B. Lenses filter, roles gate (doc-26): hearting requires only read access to the node; favorites never grant anything and are visible only to their owner. Track-level hearts v1 resolve to the track's audioFinal at play time; the schema already carries type:'file', so per-version hearts ("that unreleased V2 demo I love") are a later flip, not a migration.
4.3 Playback modes
- Repeat: off / one / all — off = stop at the end of the queue; one = loop the current version; all = loop the queue. Standard controls, pure
PlaybackManagerstate (no persistence). - Continuous vs stop-at-end is just repeat = all vs off over whichever source is loaded — so doc-30 Phase 3's "continuous play through the project's tracks" = album source + repeat-all.
- Shuffle — natural for Favorites and albums; a queue-level toggle. (Confirmed in the v1 mode set — owner 2026-07-06.)
4.4 Ordering & surfacing
- Favorites ordering v1 = recency (
createdAtdesc); manual reorder deferred (it's the first step down the named-playlists road, explicitly deferred — owner 2026-07-05). - Surfacing (owner-decided 2026-07-06): the Favorites entry point lives on Home, riding the context-aware Resume area — doc-54's Home v2 is a non-scrollable, budgeted single screen, so "play your music" sits naturally next to Resume rather than as a new competing tile (full list via See-all). The heart affordance itself sits on the wave page + track rows.
5. Release-preview parity — see and hear it like the release
5.1 Lock-screen artwork — the P0 gap
The lock screen today shows title + transport and no artwork: nothing in the codebase sets MPMediaItemArtwork, while the cover cascade (coverThumbURL/coverMediumURL, CachedImage) and the Now Playing wiring both already exist. Feeding the cover into MPNowPlayingInfoCenter is hours-sized, additive, and independent of every gate in this doc — flagged as P0, buildable now (§8). It is also the first visible taste of "Apple-Music-grade".
5.2 Motion artwork (the player side; doc-45 keeps store · preview · approve)
Apple's actual spec (answers doc-45 Open Q1 "formats to be researched"):
Two files required — 1:1 (3840×3840 recommended) and 3:4 (2048×2732) · 23.976–30 fps · 45–100 Mbps, Rec 709/sRGB · animation/motion-graphics, not video cuts · first frame must match the static cover · seamless loop (no fade-to-color) · safe areas for UI overlays · available on EP/Album/Artist pages only — never singles.
Two consequences fall out:
- Placement: in Sonnance terms motion artwork is a Project (album/EP) and ArtistSpace asset — not a per-Track one. (A track-level motion loop could still exist as an in-app-only delight, but the release-preview object lives at project/artist altitude, matching Apple.)
- The preview can be literal: iOS 26's
MPMediaItemAnimatedArtworklets third-party apps hand the system a placeholder image + video clip for the full-screen animated lock screen — the exact surface Apple Music uses. Sonnance targets iOS 26, so "preview your motion cover as the store renders it" includes the real lock screen, not a mock. (First-year API — degrade gracefully to P0's static artwork.)
Storage fork (complicated by the two cover models: Track covers = File refs with versions + approval; Project/Artist covers = inline URL strings, no versions):
| Fork | Pros | Cons |
|---|---|---|
| A. Typed asset in the doc-45 asset cloud ✅ | A File with a motion-cover kind, resourceType:'project'|'artistSpace', metadata:{aspect, fps, duration, posterUrl}; candidates/history + doc-32 verdicts ride the cloud unchanged; spans both cover models without touching them | Needs a real video File kind (today 'video' is folded into 'audio' at upload) + poster-frame extraction — net-new pipeline |
| B. Inline URL strings on Project/ArtistSpace (matches today's inline covers) | Matches the existing project-cover plumbing | No versions, no candidates, no approval — exactly what doc-45 says motion covers need |
| C. Unify the cover models first (Project/Artist covers become File refs like Track's) | The right end-state; kills the dual model | A real migration; must not gate motion artwork on it — named as a separable prerequisite fork, §9.6 |
Recommendation: A. Validation belongs to the preview: "first frame == static cover" is a preview-time check the artist sees (a genuine doc-45 "preview as the store renders" differentiator), not an upload rejection.
5.3 Synced lyrics — from opaque blob to release surface
Today lyrics are opaque attachment blobs in the Lyrics folder, opened via a browser handoff. The envisioned end-state: the artist reads their lyrics scrolling in sync on the player — Apple-Music-style — and exports the exact artifact the store needs.
- The sync editor: tap-along while the track plays — the wave page is already a precise scrubber; each tap stamps the current line with
currentTime. Line-level first; this is Musixmatch Pro's core interaction, rebuilt over our own player (§7). - Export = TTML (what Apple accepts via distributor/iTunes Connect) + LRC (the generic sidecar every other tool reads). Generated on demand from structured data — never hand-authored.
Storage fork:
| Fork | Pros | Cons |
|---|---|---|
A. Structured Lyrics collection ✅ | { track, file? (version), language, lines: [{ t, text }], syncedBy, updatedAt } — editable line-by-line, word-level extensible (lines[].words[] later), TTML/LRC generated on export; the Lyrics-folder blobs stay as source documents | New collection + editor UI |
B. Canonical LRC/TTML File attachment, parse-on-read | Interoperable; zero new schema | The editor rewrites blobs (merge-hostile); no path to word-level; format quirks live in the app forever |
| C. Embedded field on Track | Cheapest read | Repeats the legacy embedded-attachments-map mistake currently being backfilled away |
Recommendation: A, line-level v1 (Apple accepts line-timed TTML; word-level is Musixmatch-parity polish, later).
⚠️ doc-32 boundary — no smuggled approval dimension. Approval dimensions v1 = cover + tracklist (confirmed 2026-06-20); lyrics sign-off is explicitly after. The synced-lyrics preview is a craft/preview feature: it does not create a lyrics-approval dimension. If the owner wants lyric sign-off (incl. the Legal clearance doc-32 sketches), that is a named doc-32 extension, decided separately (§9.4).
6. The new surface — where full-screen artwork & lyrics live
The tension, stated honestly: doc-22 §9 and doc-24 lock a waveform-centric player — cover in the PlayerBar, an explicit backlog decision against a big cover frame, and the motion rule "nothing pops". Full-screen artwork/lyrics must not relitigate that.
| Fork | Pros | Cons |
|---|---|---|
| A. Additive "listening mode" ✅ | A separate, explicitly-entered full-screen surface (artwork · synced lyrics · minimal transport), reachable from the wave page (later: mini-player long-press). The wave page stays the untouched default craft surface; the user's own gesture is the transition ("nothing pops" honored) | One more surface to design/maintain |
| B. Lock-screen / Now-Playing only | Cheapest; zero in-app surface | Lyrics can't render there; fails "verify without leaving Sonnance" |
| C. Redesign the wave page around artwork | One surface | Rejected — reverses the locked doc-22/24 decision for no reviewer benefit |
Recommendation: A. Review stays waveform-first; listening gets its own room. This is also where the motion-artwork preview (§5.2) and scrolling lyrics (§5.3) naturally live.
7. Musixmatch — replicate, don't integrate (owner-decided 2026-07-05)
What it is: the dominant lyrics platform — the catalog + sync layer behind Spotify's and Instagram's lyrics — and Musixmatch Pro, the artist-facing tool: verify your artist profile, transcribe + sync lyrics line- and word-level (AI-assisted), distribute them to the stores, make lyric videos.
Why not integrate:
- Their developer API licenses catalog display of other people's lyrics to third-party apps — paid tiers, preview-only free tier. That's not our problem: our artists own their lyrics; syncing and rendering them in-house needs no license from anyone.
- There is no public artist-account OAuth — "connect your Musixmatch account" isn't an available primitive today.
- The dependency would sit in the middle of a release-preview flow we want to control end-to-end.
How lyrics actually reach the stores (what Sonnance documents for artists — and can link to in-app; §9.5):
| Store | Channel |
|---|---|
| Apple Music | TTML via the distributor / iTunes Connect "Upload Lyrics" — Sonnance exports this file (§5.3) |
| Spotify · Instagram · Google · Snapchat | Musixmatch is the de-facto pipeline — the artist publishes via Musixmatch Pro (free verification), pasting/exporting the lyrics Sonnance already synced |
What to borrow (replicate the experience, on our own catalog): the tap-along sync editor, word-level karaoke mode (later), lyric-video generation (much later, a promo-asset idea for the doc-45 cloud).
8. Phases & slices
| Slice | What | Size | Gate |
|---|---|---|---|
| ✅ P0 — Lock-screen artwork | MPMediaItemArtwork from the existing cover cascade into the existing Now Playing wiring | tiny (hours) | built 2026-07-06 (rode doc-56 S1; ⏳ device QA) |
| 🟡 P1 — Handoff behavior | Browse ≠ play at track and version altitude + the two-deck rules (§3.2) + the sounding-version indicator on transport/mini player/lock screen | small–med | Track-level core ✅ built 2026-07-06 (shipped inside doc-30 Phase 2: opening another track never interrupts; explicit play = the deck switch; mini player/lock screen show what's sounding). Remaining: the §9.2 version-altitude reversal (version switch → loaded-paused + per-VersionCard play) + the sounding-version # indicator |
| P2 — Queue & modes | Album/project play (Track.order + doc-56 S1's audio ref), repeat off/one/all, continuous vs stop-at-end, shuffle — client-only, no new backend | small–med | doc-30 Phase 2 (+ doc-56 S1 for the list-query audio refs) |
| P3 — Favorites | Favorite collection + heart affordance + Favorites as a queue source; Home surfacing | medium | P2 |
| P4 — Synced lyrics | Lyrics collection + tap-along editor + scrolling player view + TTML/LRC export | large | backend + editor can proceed post-MVP in parallel; the player view waits for Phase 2 (avoid rework) |
| P5 — Motion artwork, player side | Asset-cloud storage + video File kind/poster + listening-mode preview + MPMediaItemAnimatedArtwork | large | doc-45 asset side; last |
P2 + P3 together realize doc-30 Phase 3 (favorites queue / loop / continuous play).
Sequencing against the rest of the corpus: doc-56 S1 → S2 → doc-30 Phase 2 → P1 → P2 (queue/album, client-only) → P3 (favorites); P4 lyrics in parallel post-MVP; P5 motion last. The current roadmap phase (Review & Approve MVP + Contact ID + Discovery) is untouched — this doc is the post-MVP player track, plus one P0 cherry.
9. Decisions (all resolved — owner, 2026-07-06)
- Favorites entry point → Home, riding the context-aware Resume area (§4.4) — not a new competing tile on doc-54's budgeted, non-scrollable Home.
- Version/variant switch = browse ≠ play — selecting a version/variant loads it paused; audio does not switch until you press play; the transport / mini player / lock screen show the sounding version number (§3.2). (Reverses today's shipped immediate-switch — a tracked future change; fast A/B preserved by a per-
VersionCardplay control.) - Lyrics editing = any collaborator with edit rights + admin for now; per-role RBAC is a sensible later refinement, not now.
- Lyrics approval = deferred to the LEGO/workflow integration (doc-43 attributes + doc-46 workflow engine) — not a doc-32 dimension now.
- Musixmatch guidance = docs-only for now — the in-app publish-path explainer needs a stakeholder review/re-envision before building.
- Cover-model unification (§5.2 fork C) = its own slice.
- Play-jump rule (§4.1) = jump the queue — play on a track inside the currently-sounding album jumps the queue to it; outside it, play starts a fresh queue from that track.
- Shuffle (§4.3) = in the v1 mode set.
Nothing open. The doc is locked; changes from here are new envisioning rounds ("Updated by" pointers), not edits to these decisions.
10. Cross-references
- Playback mechanics & phases:
30-GLOBAL-PLAYBACK.md - First-play pipeline (gates P1):
56-FIRST-PLAY-PIPELINE.md - Player design lock + backlog:
22-IOS-DESIGN-SYSTEM.md·24-UX-BACKLOG.md - Motion covers, release side (store · preview · approve):
45-RELEASE-PIPELINE-ASSETS.md - Approval dimensions (lyrics boundary):
32-APPROVAL-MODEL.md - Lenses / favorites pattern:
26-IDENTITY-SHARING-NAVIGATION.md - Image delivery (cover variants the artwork rides):
34-IMAGE-DELIVERY.md