renamed all references of playlist to queue
This commit is contained in:
parent
df47dd46c3
commit
b0afc1cf5b
|
|
@ -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
|
||||
|
|
|
|||
52
channel.ts
52
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<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,
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ window.MusicRoom = {
|
|||
|
||||
// Playback state
|
||||
localTimestamp: 0,
|
||||
playlist: [],
|
||||
queue: [],
|
||||
currentIndex: 0,
|
||||
|
||||
// User state
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -156,7 +156,7 @@
|
|||
const currentSize = M.cachedTracks.size;
|
||||
if (currentSize !== lastCacheSize) {
|
||||
lastCacheSize = currentSize;
|
||||
M.renderPlaylist();
|
||||
M.renderQueue();
|
||||
M.renderLibrary();
|
||||
}
|
||||
}, 5000);
|
||||
|
|
|
|||
|
|
@ -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];
|
||||
|
|
|
|||
Loading…
Reference in New Issue