diff --git a/AGENTS.md b/AGENTS.md index 429fe55..8fa80ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,7 +65,7 @@ All tracks are identified by a **content hash** (`sha256:` prefix + first 64KB h Benefits: - Deduplication (same file with different names = same track) -- Renaming files without breaking playlists +- Renaming files without breaking queues - Reliable client-side caching by content hash The client uses `track.id` for: @@ -149,7 +149,7 @@ interface Track { channelId: string, description: string, paused: boolean, - queue: Track[], // alias: playlist (for compatibility) + queue: Track[], currentIndex: number, listenerCount: number, listeners: string[], // usernames of connected users @@ -186,7 +186,7 @@ On WebSocket message: Available in browser console: ```js -MusicRoom.debugCacheMismatch() // Compare playlist IDs vs cached IDs +MusicRoom.debugCacheMismatch() // Compare queue IDs vs cached IDs MusicRoom.debugTrack(index) // Detailed cache state for track at index MusicRoom.debugCacheStatus() // Current track cache state MusicRoom.clearAllCaches() // Clear IndexedDB and in-memory caches diff --git a/channel.ts b/channel.ts index d6724e3..7dc69ae 100644 --- a/channel.ts +++ b/channel.ts @@ -22,7 +22,7 @@ export class Channel { id: string; name: string; description: string; - playlist: Track[]; + queue: Track[]; currentIndex: number = 0; startedAt: number = Date.now(); clients: Set> = new Set(); @@ -31,22 +31,22 @@ export class Channel { createdBy: number | null; createdAt: number; isDefault: boolean; - private lastPlaylistBroadcast: number = 0; - private playlistDirty: boolean = false; + private lastQueueBroadcast: number = 0; + private queueDirty: boolean = false; constructor(config: ChannelConfig) { this.id = config.id; this.name = config.name; this.description = config.description || ""; - this.playlist = config.tracks; + this.queue = config.tracks; this.createdBy = config.createdBy ?? null; this.createdAt = Date.now(); this.isDefault = config.isDefault ?? false; } get currentTrack(): Track | null { - if (this.playlist.length === 0) return null; - return this.playlist[this.currentIndex]; + if (this.queue.length === 0) return null; + return this.queue[this.currentIndex]; } get currentTimestamp(): number { @@ -66,13 +66,13 @@ export class Channel { } advance() { - if (this.playlist.length === 0) return; - this.currentIndex = (this.currentIndex + 1) % this.playlist.length; + if (this.queue.length === 0) return; + this.currentIndex = (this.currentIndex + 1) % this.queue.length; this.startedAt = Date.now(); this.broadcast(); } - getState(includePlaylist: boolean = false) { + getState(includeQueue: boolean = false) { const state: Record = { track: this.currentTrack, currentTimestamp: this.currentTimestamp, @@ -84,8 +84,8 @@ export class Channel { listenerCount: this.clients.size, isDefault: this.isDefault, }; - if (includePlaylist) { - state.playlist = this.playlist; + if (includeQueue) { + state.queue = this.queue; } return state; } @@ -105,7 +105,7 @@ export class Channel { } jumpTo(index: number) { - if (index < 0 || index >= this.playlist.length) return; + if (index < 0 || index >= this.queue.length) return; this.currentIndex = index; if (this.paused) { this.pausedAt = 0; @@ -127,27 +127,27 @@ export class Channel { this.broadcast(); } - markPlaylistDirty() { - this.playlistDirty = true; + markQueueDirty() { + this.queueDirty = true; } - setPlaylist(tracks: Track[]) { - this.playlist = tracks; + setQueue(tracks: Track[]) { + this.queue = tracks; this.currentIndex = 0; this.startedAt = Date.now(); this.pausedAt = 0; - this.playlistDirty = true; + this.queueDirty = true; this.broadcast(); } broadcast() { const now = Date.now(); - const includePlaylist = this.playlistDirty || (now - this.lastPlaylistBroadcast >= 60000); - if (includePlaylist) { - this.lastPlaylistBroadcast = now; - this.playlistDirty = false; + const includeQueue = this.queueDirty || (now - this.lastQueueBroadcast >= 60000); + if (includeQueue) { + this.lastQueueBroadcast = now; + this.queueDirty = false; } - const msg = JSON.stringify(this.getState(includePlaylist)); + const msg = JSON.stringify(this.getState(includeQueue)); for (const ws of this.clients) { ws.send(msg); @@ -158,10 +158,10 @@ export class Channel { this.clients.add(ws); console.log(`[Channel] "${this.name}" added client, now ${this.clients.size} clients`); - // Always send full state with playlist on connect + // Always send full state with queue on connect ws.send(JSON.stringify(this.getState(true))); - // Reset timer so next playlist broadcast is in 60s - this.lastPlaylistBroadcast = Date.now(); + // Reset timer so next queue broadcast is in 60s + this.lastQueueBroadcast = Date.now(); } removeClient(ws: ServerWebSocket) { @@ -175,7 +175,7 @@ export class Channel { id: this.id, name: this.name, description: this.description, - trackCount: this.playlist.length, + trackCount: this.queue.length, listenerCount: this.clients.size, listeners, isDefault: this.isDefault, diff --git a/public/audioCache.js b/public/audioCache.js index 5350804..ebccca7 100644 --- a/public/audioCache.js +++ b/public/audioCache.js @@ -37,7 +37,7 @@ // Update cache status indicator if (!M.cachedTracks.has(trackId)) { M.cachedTracks.add(trackId); - M.renderPlaylist(); + M.renderQueue(); M.renderLibrary(); } return blobUrl; @@ -84,7 +84,7 @@ // Update cache status and re-render lists console.log("[Cache] Track cached:", trackId.slice(0, 16) + "...", "| size:", (data.byteLength / 1024 / 1024).toFixed(2) + "MB"); M.cachedTracks.add(trackId); - M.renderPlaylist(); + M.renderQueue(); M.renderLibrary(); // Update download speed diff --git a/public/channelSync.js b/public/channelSync.js index 68e3a3c..740d3ba 100644 --- a/public/channelSync.js +++ b/public/channelSync.js @@ -236,14 +236,14 @@ const wasServerPaused = M.serverPaused; M.serverPaused = data.paused ?? true; - // Update playlist if provided - if (data.playlist) { - M.playlist = data.playlist; + // Update queue if provided + if (data.queue) { + M.queue = data.queue; M.currentIndex = data.currentIndex ?? 0; - M.renderPlaylist(); + M.renderQueue(); } else if (data.currentIndex !== undefined && data.currentIndex !== M.currentIndex) { M.currentIndex = data.currentIndex; - M.renderPlaylist(); + M.renderQueue(); } // Cache track info for local mode - use track.id (content hash) as the identifier diff --git a/public/controls.js b/public/controls.js index 2aacf9b..269ac53 100644 --- a/public/controls.js +++ b/public/controls.js @@ -39,8 +39,8 @@ // Jump to a specific track index async function jumpToTrack(index) { - if (M.playlist.length === 0) return; - const newIndex = (index + M.playlist.length) % M.playlist.length; + if (M.queue.length === 0) return; + const newIndex = (index + M.queue.length) % M.queue.length; if (M.synced && M.currentChannelId) { const res = await fetch("/api/channels/" + M.currentChannelId + "/jump", { @@ -49,9 +49,9 @@ body: JSON.stringify({ index: newIndex }) }); if (res.status === 403) M.flashPermissionDenied(); - if (res.status === 400) console.warn("Jump failed: 400 - newIndex:", newIndex, "playlist length:", M.playlist.length); + if (res.status === 400) console.warn("Jump failed: 400 - newIndex:", newIndex, "queue length:", M.queue.length); } else { - const track = M.playlist[newIndex]; + const track = M.queue[newIndex]; const trackId = track.id || track.filename; M.currentIndex = newIndex; M.currentTrackId = trackId; @@ -63,7 +63,7 @@ M.audio.currentTime = 0; M.localTimestamp = 0; M.audio.play(); - M.renderPlaylist(); + M.renderQueue(); } } diff --git a/public/core.js b/public/core.js index ebf9585..036ee90 100644 --- a/public/core.js +++ b/public/core.js @@ -28,7 +28,7 @@ window.MusicRoom = { // Playback state localTimestamp: 0, - playlist: [], + queue: [], currentIndex: 0, // User state diff --git a/public/queue.js b/public/queue.js index be572e7..3cd71d7 100644 --- a/public/queue.js +++ b/public/queue.js @@ -56,7 +56,7 @@ console.log("M.trackBlobs keys:", [...M.trackBlobs.keys()]); console.log("M.bulkDownloadStarted:", M.bulkDownloadStarted); console.log("=== Queue Tracks ==="); - M.playlist.forEach((t, i) => { + M.queue.forEach((t, i) => { const id = t.id || t.filename; console.log(` [${i}] ${t.title?.slice(0, 30)} | id: ${id?.slice(0, 12)}... | cached: ${M.cachedTracks.has(id)}`); }); @@ -68,7 +68,7 @@ // Debug: detailed info for a specific track M.debugTrack = function(index) { - const track = M.playlist[index]; + const track = M.queue[index]; if (!track) { console.log("[Debug] No track at index", index); return; @@ -99,26 +99,26 @@ console.log("[Cache] All caches cleared. Refresh the page."); }; - // Render the current queue (channel's playlist) + // Render the current queue M.renderQueue = function() { const container = M.$("#queue"); if (!container) return; container.innerHTML = ""; - if (M.playlist.length === 0) { + if (M.queue.length === 0) { container.innerHTML = '
Queue empty
'; return; } // Debug: log first few track cache statuses - if (M.playlist.length > 0 && M.cachedTracks.size > 0) { - const sample = M.playlist.slice(0, 3).map(t => { + if (M.queue.length > 0 && M.cachedTracks.size > 0) { + const sample = M.queue.slice(0, 3).map(t => { const id = t.id || t.filename; return { title: t.title?.slice(0, 20), id: id?.slice(0, 12), cached: M.cachedTracks.has(id) }; }); console.log("[Queue Render] Sample tracks:", sample, "| cachedTracks sample:", [...M.cachedTracks].slice(0, 3).map(s => s.slice(0, 12))); } - M.playlist.forEach((track, i) => { + M.queue.forEach((track, i) => { const div = document.createElement("div"); const trackId = track.id || track.filename; const isCached = M.cachedTracks.has(trackId); @@ -135,7 +135,7 @@ body: JSON.stringify({ index: i }) }); if (res.status === 403) M.flashPermissionDenied(); - if (res.status === 400) console.warn("Jump failed: 400 - index:", i, "queue length:", M.playlist.length); + if (res.status === 400) console.warn("Jump failed: 400 - index:", i, "queue length:", M.queue.length); } else { M.currentIndex = i; M.currentTrackId = trackId; @@ -155,9 +155,6 @@ }); }; - // Alias for backward compatibility - M.renderPlaylist = M.renderQueue; - // Render the library M.renderLibrary = function() { const container = M.$("#library"); diff --git a/public/styles.css b/public/styles.css index ba23280..939a310 100644 --- a/public/styles.css +++ b/public/styles.css @@ -15,7 +15,7 @@ h3 { font-size: 0.9rem; color: #666; margin-bottom: 0.5rem; text-transform: uppe #auth-section .admin-badge { background: #c4f; color: #111; padding: 0.1rem 0.4rem; border-radius: 3px; font-size: 0.7rem; } #stream-select select { background: #222; color: #eee; border: 1px solid #333; padding: 0.4rem 0.8rem; border-radius: 4px; font-size: 0.9rem; } -/* Main content - library and playlist */ +/* Main content - library and queue */ #main-content { display: flex; gap: 1rem; flex: 1; min-height: 0; margin-bottom: 1rem; } #channels-panel { flex: 0 0 180px; background: #1a1a1a; border-radius: 8px; padding: 1rem; display: flex; flex-direction: column; min-height: 300px; max-height: 60vh; } #channels-list { flex: 1; overflow-y: auto; } diff --git a/public/ui.js b/public/ui.js index c81ba1e..3c6d851 100644 --- a/public/ui.js +++ b/public/ui.js @@ -156,7 +156,7 @@ const currentSize = M.cachedTracks.size; if (currentSize !== lastCacheSize) { lastCacheSize = currentSize; - M.renderPlaylist(); + M.renderQueue(); M.renderLibrary(); } }, 5000); diff --git a/server.ts b/server.ts index 28e9529..702e9ff 100644 --- a/server.ts +++ b/server.ts @@ -48,7 +48,7 @@ console.log(`Config loaded: port=${config.port}, musicDir=${MUSIC_DIR}, allowGue // Initialize library const library = new Library(MUSIC_DIR); -// Auto-discover tracks if playlist is empty +// Auto-discover tracks if queue is empty async function discoverTracks(): Promise { try { const files = await readdir(MUSIC_DIR); @@ -480,7 +480,7 @@ serve({ } } - // API: jump to track in playlist + // API: jump to track in queue const jumpMatch = path.match(/^\/api\/channels\/([^/]+)\/jump$/); if (jumpMatch && req.method === "POST") { const channelId = jumpMatch[1];