renamed all references of playlist to queue

This commit is contained in:
peterino2 2026-02-02 23:02:51 -08:00
parent df47dd46c3
commit b0afc1cf5b
10 changed files with 54 additions and 57 deletions

View File

@ -65,7 +65,7 @@ All tracks are identified by a **content hash** (`sha256:` prefix + first 64KB h
Benefits: Benefits:
- Deduplication (same file with different names = same track) - 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 - Reliable client-side caching by content hash
The client uses `track.id` for: The client uses `track.id` for:
@ -149,7 +149,7 @@ interface Track {
channelId: string, channelId: string,
description: string, description: string,
paused: boolean, paused: boolean,
queue: Track[], // alias: playlist (for compatibility) queue: Track[],
currentIndex: number, currentIndex: number,
listenerCount: number, listenerCount: number,
listeners: string[], // usernames of connected users listeners: string[], // usernames of connected users
@ -186,7 +186,7 @@ On WebSocket message:
Available in browser console: Available in browser console:
```js ```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.debugTrack(index) // Detailed cache state for track at index
MusicRoom.debugCacheStatus() // Current track cache state MusicRoom.debugCacheStatus() // Current track cache state
MusicRoom.clearAllCaches() // Clear IndexedDB and in-memory caches MusicRoom.clearAllCaches() // Clear IndexedDB and in-memory caches

View File

@ -22,7 +22,7 @@ export class Channel {
id: string; id: string;
name: string; name: string;
description: string; description: string;
playlist: Track[]; queue: Track[];
currentIndex: number = 0; currentIndex: number = 0;
startedAt: number = Date.now(); startedAt: number = Date.now();
clients: Set<ServerWebSocket<WsData>> = new Set(); clients: Set<ServerWebSocket<WsData>> = new Set();
@ -31,22 +31,22 @@ export class Channel {
createdBy: number | null; createdBy: number | null;
createdAt: number; createdAt: number;
isDefault: boolean; isDefault: boolean;
private lastPlaylistBroadcast: number = 0; private lastQueueBroadcast: number = 0;
private playlistDirty: boolean = false; private queueDirty: boolean = false;
constructor(config: ChannelConfig) { constructor(config: ChannelConfig) {
this.id = config.id; this.id = config.id;
this.name = config.name; this.name = config.name;
this.description = config.description || ""; this.description = config.description || "";
this.playlist = config.tracks; this.queue = config.tracks;
this.createdBy = config.createdBy ?? null; this.createdBy = config.createdBy ?? null;
this.createdAt = Date.now(); this.createdAt = Date.now();
this.isDefault = config.isDefault ?? false; this.isDefault = config.isDefault ?? false;
} }
get currentTrack(): Track | null { get currentTrack(): Track | null {
if (this.playlist.length === 0) return null; if (this.queue.length === 0) return null;
return this.playlist[this.currentIndex]; return this.queue[this.currentIndex];
} }
get currentTimestamp(): number { get currentTimestamp(): number {
@ -66,13 +66,13 @@ export class Channel {
} }
advance() { advance() {
if (this.playlist.length === 0) return; if (this.queue.length === 0) return;
this.currentIndex = (this.currentIndex + 1) % this.playlist.length; this.currentIndex = (this.currentIndex + 1) % this.queue.length;
this.startedAt = Date.now(); this.startedAt = Date.now();
this.broadcast(); this.broadcast();
} }
getState(includePlaylist: boolean = false) { getState(includeQueue: boolean = false) {
const state: Record<string, unknown> = { const state: Record<string, unknown> = {
track: this.currentTrack, track: this.currentTrack,
currentTimestamp: this.currentTimestamp, currentTimestamp: this.currentTimestamp,
@ -84,8 +84,8 @@ export class Channel {
listenerCount: this.clients.size, listenerCount: this.clients.size,
isDefault: this.isDefault, isDefault: this.isDefault,
}; };
if (includePlaylist) { if (includeQueue) {
state.playlist = this.playlist; state.queue = this.queue;
} }
return state; return state;
} }
@ -105,7 +105,7 @@ export class Channel {
} }
jumpTo(index: number) { jumpTo(index: number) {
if (index < 0 || index >= this.playlist.length) return; if (index < 0 || index >= this.queue.length) return;
this.currentIndex = index; this.currentIndex = index;
if (this.paused) { if (this.paused) {
this.pausedAt = 0; this.pausedAt = 0;
@ -127,27 +127,27 @@ export class Channel {
this.broadcast(); this.broadcast();
} }
markPlaylistDirty() { markQueueDirty() {
this.playlistDirty = true; this.queueDirty = true;
} }
setPlaylist(tracks: Track[]) { setQueue(tracks: Track[]) {
this.playlist = tracks; this.queue = tracks;
this.currentIndex = 0; this.currentIndex = 0;
this.startedAt = Date.now(); this.startedAt = Date.now();
this.pausedAt = 0; this.pausedAt = 0;
this.playlistDirty = true; this.queueDirty = true;
this.broadcast(); this.broadcast();
} }
broadcast() { broadcast() {
const now = Date.now(); const now = Date.now();
const includePlaylist = this.playlistDirty || (now - this.lastPlaylistBroadcast >= 60000); const includeQueue = this.queueDirty || (now - this.lastQueueBroadcast >= 60000);
if (includePlaylist) { if (includeQueue) {
this.lastPlaylistBroadcast = now; this.lastQueueBroadcast = now;
this.playlistDirty = false; this.queueDirty = false;
} }
const msg = JSON.stringify(this.getState(includePlaylist)); const msg = JSON.stringify(this.getState(includeQueue));
for (const ws of this.clients) { for (const ws of this.clients) {
ws.send(msg); ws.send(msg);
@ -158,10 +158,10 @@ export class Channel {
this.clients.add(ws); this.clients.add(ws);
console.log(`[Channel] "${this.name}" added client, now ${this.clients.size} clients`); 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))); ws.send(JSON.stringify(this.getState(true)));
// Reset timer so next playlist broadcast is in 60s // Reset timer so next queue broadcast is in 60s
this.lastPlaylistBroadcast = Date.now(); this.lastQueueBroadcast = Date.now();
} }
removeClient(ws: ServerWebSocket<WsData>) { removeClient(ws: ServerWebSocket<WsData>) {
@ -175,7 +175,7 @@ export class Channel {
id: this.id, id: this.id,
name: this.name, name: this.name,
description: this.description, description: this.description,
trackCount: this.playlist.length, trackCount: this.queue.length,
listenerCount: this.clients.size, listenerCount: this.clients.size,
listeners, listeners,
isDefault: this.isDefault, isDefault: this.isDefault,

View File

@ -37,7 +37,7 @@
// Update cache status indicator // Update cache status indicator
if (!M.cachedTracks.has(trackId)) { if (!M.cachedTracks.has(trackId)) {
M.cachedTracks.add(trackId); M.cachedTracks.add(trackId);
M.renderPlaylist(); M.renderQueue();
M.renderLibrary(); M.renderLibrary();
} }
return blobUrl; return blobUrl;
@ -84,7 +84,7 @@
// Update cache status and re-render lists // Update cache status and re-render lists
console.log("[Cache] Track cached:", trackId.slice(0, 16) + "...", "| size:", (data.byteLength / 1024 / 1024).toFixed(2) + "MB"); console.log("[Cache] Track cached:", trackId.slice(0, 16) + "...", "| size:", (data.byteLength / 1024 / 1024).toFixed(2) + "MB");
M.cachedTracks.add(trackId); M.cachedTracks.add(trackId);
M.renderPlaylist(); M.renderQueue();
M.renderLibrary(); M.renderLibrary();
// Update download speed // Update download speed

View File

@ -236,14 +236,14 @@
const wasServerPaused = M.serverPaused; const wasServerPaused = M.serverPaused;
M.serverPaused = data.paused ?? true; M.serverPaused = data.paused ?? true;
// Update playlist if provided // Update queue if provided
if (data.playlist) { if (data.queue) {
M.playlist = data.playlist; M.queue = data.queue;
M.currentIndex = data.currentIndex ?? 0; M.currentIndex = data.currentIndex ?? 0;
M.renderPlaylist(); M.renderQueue();
} else if (data.currentIndex !== undefined && data.currentIndex !== M.currentIndex) { } else if (data.currentIndex !== undefined && data.currentIndex !== M.currentIndex) {
M.currentIndex = data.currentIndex; M.currentIndex = data.currentIndex;
M.renderPlaylist(); M.renderQueue();
} }
// Cache track info for local mode - use track.id (content hash) as the identifier // Cache track info for local mode - use track.id (content hash) as the identifier

10
public/controls.js vendored
View File

@ -39,8 +39,8 @@
// Jump to a specific track index // Jump to a specific track index
async function jumpToTrack(index) { async function jumpToTrack(index) {
if (M.playlist.length === 0) return; if (M.queue.length === 0) return;
const newIndex = (index + M.playlist.length) % M.playlist.length; const newIndex = (index + M.queue.length) % M.queue.length;
if (M.synced && M.currentChannelId) { if (M.synced && M.currentChannelId) {
const res = await fetch("/api/channels/" + M.currentChannelId + "/jump", { const res = await fetch("/api/channels/" + M.currentChannelId + "/jump", {
@ -49,9 +49,9 @@
body: JSON.stringify({ index: newIndex }) body: JSON.stringify({ index: newIndex })
}); });
if (res.status === 403) M.flashPermissionDenied(); 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 { } else {
const track = M.playlist[newIndex]; const track = M.queue[newIndex];
const trackId = track.id || track.filename; const trackId = track.id || track.filename;
M.currentIndex = newIndex; M.currentIndex = newIndex;
M.currentTrackId = trackId; M.currentTrackId = trackId;
@ -63,7 +63,7 @@
M.audio.currentTime = 0; M.audio.currentTime = 0;
M.localTimestamp = 0; M.localTimestamp = 0;
M.audio.play(); M.audio.play();
M.renderPlaylist(); M.renderQueue();
} }
} }

View File

@ -28,7 +28,7 @@ window.MusicRoom = {
// Playback state // Playback state
localTimestamp: 0, localTimestamp: 0,
playlist: [], queue: [],
currentIndex: 0, currentIndex: 0,
// User state // User state

View File

@ -56,7 +56,7 @@
console.log("M.trackBlobs keys:", [...M.trackBlobs.keys()]); console.log("M.trackBlobs keys:", [...M.trackBlobs.keys()]);
console.log("M.bulkDownloadStarted:", M.bulkDownloadStarted); console.log("M.bulkDownloadStarted:", M.bulkDownloadStarted);
console.log("=== Queue Tracks ==="); console.log("=== Queue Tracks ===");
M.playlist.forEach((t, i) => { M.queue.forEach((t, i) => {
const id = t.id || t.filename; const id = t.id || t.filename;
console.log(` [${i}] ${t.title?.slice(0, 30)} | id: ${id?.slice(0, 12)}... | cached: ${M.cachedTracks.has(id)}`); 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 // Debug: detailed info for a specific track
M.debugTrack = function(index) { M.debugTrack = function(index) {
const track = M.playlist[index]; const track = M.queue[index];
if (!track) { if (!track) {
console.log("[Debug] No track at index", index); console.log("[Debug] No track at index", index);
return; return;
@ -99,26 +99,26 @@
console.log("[Cache] All caches cleared. Refresh the page."); console.log("[Cache] All caches cleared. Refresh the page.");
}; };
// Render the current queue (channel's playlist) // Render the current queue
M.renderQueue = function() { M.renderQueue = function() {
const container = M.$("#queue"); const container = M.$("#queue");
if (!container) return; if (!container) return;
container.innerHTML = ""; container.innerHTML = "";
if (M.playlist.length === 0) { if (M.queue.length === 0) {
container.innerHTML = '<div class="empty">Queue empty</div>'; container.innerHTML = '<div class="empty">Queue empty</div>';
return; return;
} }
// Debug: log first few track cache statuses // Debug: log first few track cache statuses
if (M.playlist.length > 0 && M.cachedTracks.size > 0) { if (M.queue.length > 0 && M.cachedTracks.size > 0) {
const sample = M.playlist.slice(0, 3).map(t => { const sample = M.queue.slice(0, 3).map(t => {
const id = t.id || t.filename; const id = t.id || t.filename;
return { title: t.title?.slice(0, 20), id: id?.slice(0, 12), cached: M.cachedTracks.has(id) }; 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))); 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 div = document.createElement("div");
const trackId = track.id || track.filename; const trackId = track.id || track.filename;
const isCached = M.cachedTracks.has(trackId); const isCached = M.cachedTracks.has(trackId);
@ -135,7 +135,7 @@
body: JSON.stringify({ index: i }) body: JSON.stringify({ index: i })
}); });
if (res.status === 403) M.flashPermissionDenied(); 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 { } else {
M.currentIndex = i; M.currentIndex = i;
M.currentTrackId = trackId; M.currentTrackId = trackId;
@ -155,9 +155,6 @@
}); });
}; };
// Alias for backward compatibility
M.renderPlaylist = M.renderQueue;
// Render the library // Render the library
M.renderLibrary = function() { M.renderLibrary = function() {
const container = M.$("#library"); const container = M.$("#library");

View File

@ -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; } #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; } #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; } #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-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; } #channels-list { flex: 1; overflow-y: auto; }

View File

@ -156,7 +156,7 @@
const currentSize = M.cachedTracks.size; const currentSize = M.cachedTracks.size;
if (currentSize !== lastCacheSize) { if (currentSize !== lastCacheSize) {
lastCacheSize = currentSize; lastCacheSize = currentSize;
M.renderPlaylist(); M.renderQueue();
M.renderLibrary(); M.renderLibrary();
} }
}, 5000); }, 5000);

View File

@ -48,7 +48,7 @@ console.log(`Config loaded: port=${config.port}, musicDir=${MUSIC_DIR}, allowGue
// Initialize library // Initialize library
const library = new Library(MUSIC_DIR); const library = new Library(MUSIC_DIR);
// Auto-discover tracks if playlist is empty // Auto-discover tracks if queue is empty
async function discoverTracks(): Promise<string[]> { async function discoverTracks(): Promise<string[]> {
try { try {
const files = await readdir(MUSIC_DIR); 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$/); const jumpMatch = path.match(/^\/api\/channels\/([^/]+)\/jump$/);
if (jumpMatch && req.method === "POST") { if (jumpMatch && req.method === "POST") {
const channelId = jumpMatch[1]; const channelId = jumpMatch[1];