music on atproto
plyr.fm
queue design#
overview#
The queue is a cross-device, server-authoritative data model with optimistic local updates. Every device performs queue mutations locally, pushes a full snapshot to the API, and receives hydrated track metadata back. Servers keep an in-memory cache (per process) in sync via Postgres LISTEN/NOTIFY so horizontally scaled instances observe the latest queue state without adding Redis or similar infra.
server implementation#
queue_statetable (did,state,revision,updated_at).stateis JSONB containingtrack_ids,current_index,current_track_id,shuffle,repeat_mode,original_order_ids.QueueServicekeeps a TTL LRU cache (maxsize 100,ttl 5m). Cache entries include both the raw state and the hydrated track list.- On startup the service opens an asyncpg connection, registers a
queue_changeslistener, and reconnects on failure. Notifications simply invalidate the cache entry; consumers fetch on demand. GET /queue/returns{ state, revision, tracks }.tracksis hydrated server-side by joining againsttracks+artists. Duplicate queue entries are preserved—hydration walks thetrack_idsarray by index so the samefile_idcan appear multiple times. Response includes an ETag ("revision").PUT /queue/expects an optionalIf-Match: "revision". Mismatched revisions return 409. Successful writes increment the revision, emit LISTEN/NOTIFY, and rehydrate so the response mirrors GET semantics.- Hydration preserves order even when duplicates exist by pairing each
track_idposition with the track returned by the DB. We never de-duplicate on the server.
client implementation (Svelte 5)#
- Global queue store (
frontend/src/lib/queue.svelte.ts) uses runes-backed$statefields fortracks,currentIndex,shuffle, etc. Methods mutate these states synchronously so the UI remains responsive. - A 250 ms debounce batches PUTs. We skip background GETs while a PUT is pending/in-flight to avoid stomping optimistic state.
- Conflict handling: on 409 the client performs a forced
fetchQueue(true)which ignores local ETag and applies the server snapshot if the revision is newer. Older revisions received out-of-order are ignored. - Before unload / visibility change flushes pending work to reduce data loss when navigating away.
- Helper getters (
getCurrentTrack,getUpNextEntries) supplement state but UI components bind directly to$stateso Svelte reactivity tracks mutations correctly. - Duplicates: adding the same track repeatedly simply appends another copy. Removal is disabled for the currently playing entry (conceptually index 0); the queue sidebar only allows removing future items.
UI behavior#
- sidebar shows "now playing" card with prev/next buttons
- shuffle control in player footer (always visible)
- "up next" lists tracks beyond
currentIndex - drag-and-drop reordering supported for upcoming tracks
- removing a track updates local state and syncs to server
queue.playNow(track)inserts track at position 0, preserves existing up-next order- duplicate tracks allowed - same track can appear multiple times in queue
- auto-play preference controls automatic advancement to next track
- persisted via
/preferences/API and localStorage - queue toggle button opens/closes sidebar
- responsive positioning for mobile viewports
- cannot remove currently playing track (index 0)
shuffle#
- shuffle is an action, not a toggle mode
- each shuffle operation randomly reorders upcoming tracks (after current track)
- preserves everything before and including the current track
- uses fisher-yates algorithm with retry logic to ensure different permutation
- original order preserved in
original_order_idsfor server persistence
cross-tab synchronization#
- uses BroadcastChannel API for same-browser tab sync
- each tab has unique
tabIdstored in sessionStorage - queue updates broadcast to other tabs via
queue-updatedmessage - tabs ignore their own broadcasts and duplicate revisions
- receiving tabs fetch latest state from server
lastUpdateWasLocalflag tracks update origin
future work#
- realtime push via SSE/WebSocket for instant cross-device updates
- UI affordances for "queue updated on another device" notifications
- repeat modes (currently not implemented)
- clear up-next functionality exposed in UI