Skip to content

57 — Player as Product — continuity, favorites & release-preview parity (envisioning)

Envisioning doc (product/behavior). Governed by 00-ROADMAP.mddefer 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; xcodebuild green, ⏳ device QA); P1+ ride doc-56 S2 + doc-30 Phase 2 (each with its own greenlight).


1. The idea, in three asks

  1. 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.
  2. 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.
  3. 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)

AnchorWhat it givesWhat doc-57 adds
doc-30 Phases 1–3Background audio ✅ · Phase 2 PlaybackManager + mini player · Phase 3 favorites-queue/loop/continuousThe behavior spec the mechanics serve: handoff rules (§3), favorites data model + queue semantics (§4)
doc-56Route-enforced seeds/prewarm (TrackRoute(track:) sole entry), AudioPrewarmer, TTFA metricThe loaded-paused "second deck" of §3 is the prewarmed item — same machinery, seen twice
doc-22 §4/§9 + doc-24The 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 1Motion covers: store · preview-as-store-renders · approve, in the asset cloudThe 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-32Approval dimensions; v1 = cover + tracklist, lyrics deferredNothing — §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 todayPlayerBar 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 pipelineThe 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?

ForkProsCons
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 modelTwo decks to understand; the wave page needs an unmistakable "this is loaded, not sounding" state
C. Intent-hybridExplicit play affordances (row ▶, approval-row CTA) auto-switch; navigational taps load-pausedSmartest 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:

SourceWhat it playsData it rides
Project / album(the fundamental one)the record top-to-bottom — the project's tracks in Track.order, each resolved to audioFinalexisting 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 orderthe new Favorite collection
Single (today's default)just this track, stop at end
Named playlist (deferred)a curated, ordered seta 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

ForkProsCons
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 collectionsLeaks 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 PlaybackManager state (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 (createdAt desc); 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 MPMediaItemAnimatedArtwork lets 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):

ForkProsCons
A. Typed asset in the doc-45 asset cloudA 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 themNeeds 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 plumbingNo 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 modelA 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:

ForkProsCons
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 documentsNew collection + editor UI
B. Canonical LRC/TTML File attachment, parse-on-readInteroperable; zero new schemaThe editor rewrites blobs (merge-hostile); no path to word-level; format quirks live in the app forever
C. Embedded field on TrackCheapest readRepeats 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.

ForkProsCons
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 onlyCheapest; zero in-app surfaceLyrics can't render there; fails "verify without leaving Sonnance"
C. Redesign the wave page around artworkOne surfaceRejected — 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):

StoreChannel
Apple MusicTTML via the distributor / iTunes Connect "Upload Lyrics" — Sonnance exports this file (§5.3)
Spotify · Instagram · Google · SnapchatMusixmatch 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

SliceWhatSizeGate
P0 — Lock-screen artworkMPMediaItemArtwork from the existing cover cascade into the existing Now Playing wiringtiny (hours)built 2026-07-06 (rode doc-56 S1; ⏳ device QA)
🟡 P1 — Handoff behaviorBrowse ≠ play at track and version altitude + the two-deck rules (§3.2) + the sounding-version indicator on transport/mini player/lock screensmall–medTrack-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 & modesAlbum/project play (Track.order + doc-56 S1's audio ref), repeat off/one/all, continuous vs stop-at-end, shuffle — client-only, no new backendsmall–meddoc-30 Phase 2 (+ doc-56 S1 for the list-query audio refs)
P3 — FavoritesFavorite collection + heart affordance + Favorites as a queue source; Home surfacingmediumP2
P4 — Synced lyricsLyrics collection + tap-along editor + scrolling player view + TTML/LRC exportlargebackend + editor can proceed post-MVP in parallel; the player view waits for Phase 2 (avoid rework)
P5 — Motion artwork, player sideAsset-cloud storage + video File kind/poster + listening-mode preview + MPMediaItemAnimatedArtworklargedoc-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)

  1. 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.
  2. 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-VersionCard play control.)
  3. Lyrics editing = any collaborator with edit rights + admin for now; per-role RBAC is a sensible later refinement, not now.
  4. Lyrics approval = deferred to the LEGO/workflow integration (doc-43 attributes + doc-46 workflow engine) — not a doc-32 dimension now.
  5. Musixmatch guidance = docs-only for now — the in-app publish-path explainer needs a stakeholder review/re-envision before building.
  6. Cover-model unification (§5.2 fork C) = its own slice.
  7. 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.
  8. 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

Ctrl-Audio Platform Documentation