fix(embed): wire MediaSession metadata + action handlers (#1340)
Empirical finding from iOS lock-screen testing: embed surfaces
(CollectionEmbed.svelte for albums/playlists, embed/track/[id]/+page.svelte
for single tracks) set NOTHING on navigator.mediaSession. Result on iOS
Safari and Android Chrome lock-screen controls: generic placeholder title,
no cover art, next/previous buttons either greyed out or routing to nothing.
The main app's Player.svelte has the right behavior; the embeds were
just missing it.
Adds `lib/media-session.ts` — small helper module that wraps the four
MediaSession APIs we use (metadata, playbackState, positionState,
action handlers) with no-op fallbacks on platforms without the API and
a try/catch around setPositionState (which throws on stale
duration/position during track transitions).
Wires the helpers into both embed surfaces:
- Metadata effect: re-runs on track change. Pulls title/artist from
the track and falls back through track image → collection image
for artwork (single-track embed uses trackCoverUrl directly).
- PlaybackState effect: re-runs on paused change.
- PositionState effect: re-runs on time/duration change.
- Action handlers: registered ONCE on mount with cleanup on unmount.
Single-track embed explicitly nulls previoustrack/nexttrack so the
OS greys them out instead of inheriting stale handlers.
- Cleanup on unmount: clears metadata, sets playbackState to 'none',
nulls all handlers. Prevents stale lock-screen entries when the
user navigates away from an embed mid-playback.
Does NOT touch Player.svelte — it has its own (older, inline)
MediaSession setup that works. Refactoring it to use these helpers
is a separate dedup concern.
Validated via svelte:svelte-file-editor agent: zero autofixer issues,
reactivity correct (each effect reads only its deps), unmount cleanup
fires correctly, and `$state` closures inside the action handlers
read the current value at handler-call time (not a mount-time
snapshot).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-authored-by: Claude Opus 4 (1M context) <noreply@anthropic.com>
authored by