blastoise-archive/AGENTS.md

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, reset startedAt
  • A 1s setInterval checks 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.tsStream class. Playlist, current index, time tracking, broadcasting.
  • library.tsLibrary class. 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:

  1. New track → load audio, seek to server timestamp, play
  2. Same track, drift < 2s → ignore
  3. 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).