3.1 KiB
3.1 KiB
MusicRoom
Synchronized music streaming server built with Bun. Manages "streams" (virtual radio stations) that play through playlists sequentially. Clients connect, receive now-playing state, download audio, and sync playback locally.
Architecture
The server does NOT decode or play audio. It tracks time:
currentTimestamp = (Date.now() - stream.startedAt) / 1000- When
currentTimestamp >= track.duration, advance to next track, resetstartedAt - A 1s
setIntervalchecks if tracks need advancing and broadcasts state every 30s
Content-Addressed Tracks
All tracks are identified by a content hash (SHA-256 of first 64KB), not by filename:
track.id= Content hash (primary key in database, used for caching, API requests)track.filename= Original filename (display only)track.title= Metadata title or filename without extension (display only)
This allows:
- Deduplication (same file with different names = same track)
- Renaming files without breaking playlists
- Reliable client-side caching by content hash
The client must use track.id for:
- Caching tracks in IndexedDB (
TrackStorage.set(track.id, blob)) - Fetching audio (
/api/tracks/:id) - Checking cache status
Routes
GET / → Serves public/index.html
GET /api/streams → List active streams (id, name, trackCount)
GET /api/streams/:id → Current stream state (track, currentTimestamp, streamName)
WS /api/streams/:id/ws → WebSocket: pushes state on connect, every 30s, and on track change
GET /api/tracks/:id → Serve audio file by content hash with Range request support
GET /api/library → List all tracks with id, filename, title, duration
Files
- server.ts — Bun entrypoint. HTTP routes and WebSocket handlers.
- stream.ts —
Streamclass. Playlist, current index, time tracking, broadcasting. - library.ts —
Libraryclass. Scans music directory, computes content hashes, caches metadata. - db.ts — SQLite database for users, sessions, playlists, tracks.
- playlist.json — Config file. Stream definitions.
- public/ — Client files (modular JS: core.js, utils.js, audioCache.js, etc.)
- music/ — Directory for audio files (.mp3, .ogg, .flac, .wav, .m4a, .aac).
Key types
interface Track {
id: string; // Content hash (primary key)
filename: string; // Original filename
title: string; // Display title
duration: number;
}
// Stream.getState() returns:
{ track: Track | null, currentTimestamp: number, streamName: string, paused: boolean }
Client sync logic
On WebSocket message:
- New track → load audio, seek to server timestamp, play
- Same track, drift < 2s → ignore
- Same track, drift >= 2s → seek to server timestamp
Progress bar updates from audio.currentTime when playing, from extrapolated server time when not playing (grey vs green color).
Config
Default port 3001 (override with PORT env var). Track durations read from file metadata on startup with music-metadata (duration: true for full-file scan, needed for accurate OGG durations).