Part VI β The Blueprint Β· Lesson 11.6
HLD β Music Streaming (Spotify)
In one lineThe player must survive navigation, so it lives above the router β everything else follows from that.
Where you've seen itClicking through albums, artists, and search while playback never pauses for even a frame.
Diagram
1β2 Β· SCOPE AND SCALE
in scope browse Β· search Β· album/playlist Β· player Β· queue Β· offline
out podcasts Β· social Β· ads Β· artist tools
scale 600M users Β· tracks ~3MB at 160kbps
sessions measured in hours, not minutes3 Β· THE ARCHITECTURAL DECISION β player OUTSIDE the router
βββββββββββββββββββββββββ App shell ββββββββββββββββββββββββββ
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β <Router> β everything here unmounts on navigate β β
β β /browse /album/:id /search /playlist/:id β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
β β <Player> β single <audio> element, NEVER unmounts β β
β β play/pause Β· seek Β· queue Β· volume β β
β βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ β
ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
Put <audio> inside a route and every navigation restarts the song.
This single decision is most of the answer.6a Β· DEEP DIVE β QUEUE STATE MACHINE
[idle] ββplay(track)βββΊ [loading] ββcanplayβββΊ [playing]
β β β
β pause β β ended
βΌ βΌ βΌ
[error] [paused] [next?]
retry / skip β
βββββββββββββ΄βββββββββ
queue empty more
β β
[idle] [loading]
queue = { context: 'album:42', items: [...],
index: 3, shuffle: false, repeat: 'off'|'one'|'all' }
Shuffle stores a shuffled ORDER, not a shuffled array β so
turning it off restores the original sequence.6b Β· DEEP DIVE β GAPLESS PLAYBACK AND PREFETCH
now playing ββββββββββββββββββββββββ 80%
β
βββΊ start fetching next track
(two <audio> elements,
swap on ended β no gap)
offline (9.1) downloaded tracks β IndexedDB (6.5)
served by the service worker on request
licence/expiry checked before playbackHow it works
- Single audio element above the router, driven by a global player store (10.5) β routes only dispatch intents.
- Queue as explicit state: context, items, index, shuffle order, repeat mode.
- Prefetch the next track at ~80% and use a second audio element for gapless transitions.
- Media Session API wires lock-screen controls, hardware keys, and metadata.
- Offline stores downloaded audio in IndexedDB and serves it through the service worker.
Trade-offs
| Choice | Cost |
|---|---|
| Player outside router | App shell can't be fully route-code-split |
| Two audio elements | More memory; gapless transitions |
| Prefetch next track | Wasted data if the user skips |
| Offline downloads | Storage quota, licensing, eviction handling |
Interview angle
"Music stops when the user navigates. Why, and how do you fix it?"
- The
<audio>element is inside a route, so navigation unmounts and recreates it. - Hoist a single player instance above the router and drive it from a global store; routes dispatch play/pause intents only.
- Then add Media Session so hardware and lock-screen controls work, which is expected behaviour for anything audio.
Recap
- The player lives above the router β that's the design.
- Queue is explicit state: context, index, shuffle order, repeat mode.
- Prefetch at ~80% and swap audio elements for truly gapless playback.