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:
- 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

View File

@ -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<ServerWebSocket<WsData>> = 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<string, unknown> = {
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<WsData>) {
@ -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,

View File

@ -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

View File

@ -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

10
public/controls.js vendored
View File

@ -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();
}
}

View File

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

View File

@ -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 = '<div class="empty">Queue empty</div>';
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");

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; }
#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; }

View File

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

View File

@ -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<string[]> {
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];