// MusicRoom - Queue module // Queue rendering and library display (function() { const M = window.MusicRoom; // Update cache status for all tracks M.updateCacheStatus = async function() { const cached = await TrackStorage.list(); // Migration: remove old filename-based cache entries (keep only sha256: prefixed) const oldEntries = cached.filter(id => !id.startsWith("sha256:")); if (oldEntries.length > 0) { console.log("[Cache] Migrating: removing", oldEntries.length, "old filename-based entries"); for (const oldId of oldEntries) { await TrackStorage.remove(oldId); } // Re-fetch after cleanup const updated = await TrackStorage.list(); M.cachedTracks = new Set(updated); } else { M.cachedTracks = new Set(cached); } console.log("[Cache] Updated cache status:", M.cachedTracks.size, "tracks cached"); }; // Debug: log cache status for current track M.debugCacheStatus = function() { if (!M.currentTrackId) { console.log("[Cache Debug] No current track"); return; } const trackCache = M.getTrackCache(M.currentTrackId); const segmentsPct = Math.round((trackCache.size / M.SEGMENTS) * 100); const inCachedTracks = M.cachedTracks.has(M.currentTrackId); const hasBlobUrl = M.trackBlobs.has(M.currentTrackId); const bulkStarted = M.bulkDownloadStarted.get(M.currentTrackId); console.log("[Cache Debug]", { trackId: M.currentTrackId.slice(0, 16) + "...", segments: `${trackCache.size}/${M.SEGMENTS} (${segmentsPct}%)`, inCachedTracks, hasBlobUrl, bulkStarted, loadingSegments: [...M.loadingSegments], cachedTracksSize: M.cachedTracks.size }); }; // Debug: compare queue track IDs with cached track IDs M.debugCacheMismatch = function() { console.log("[Cache Mismatch Debug]"); console.log("=== Raw State ==="); console.log("M.cachedTracks:", M.cachedTracks); console.log("M.trackCaches:", M.trackCaches); console.log("M.trackBlobs keys:", [...M.trackBlobs.keys()]); console.log("M.bulkDownloadStarted:", M.bulkDownloadStarted); console.log("=== Queue Tracks ==="); 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)}`); }); console.log("=== Cached Track IDs ==="); [...M.cachedTracks].forEach(id => { console.log(` ${id.slice(0, 20)}...`); }); }; // Debug: detailed info for a specific track M.debugTrack = function(index) { const track = M.queue[index]; if (!track) { console.log("[Debug] No track at index", index); return; } const id = track.id || track.filename; console.log("[Debug Track]", { index, title: track.title, id, idPrefix: id?.slice(0, 16), inCachedTracks: M.cachedTracks.has(id), inTrackCaches: M.trackCaches.has(id), segmentCount: M.trackCaches.get(id)?.size || 0, inTrackBlobs: M.trackBlobs.has(id), bulkStarted: M.bulkDownloadStarted.get(id) }); }; // Clear all caches (for debugging) M.clearAllCaches = async function() { await TrackStorage.clear(); M.cachedTracks.clear(); M.trackCaches.clear(); M.trackBlobs.clear(); M.bulkDownloadStarted.clear(); M.renderQueue(); M.renderLibrary(); console.log("[Cache] All caches cleared. Refresh the page."); }; // Render the current queue M.renderQueue = function() { const container = M.$("#queue"); if (!container) return; container.innerHTML = ""; if (M.queue.length === 0) { container.innerHTML = '
Queue empty
'; return; } // Debug: log first few track cache statuses 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.queue.forEach((track, i) => { const div = document.createElement("div"); const trackId = track.id || track.filename; const isCached = M.cachedTracks.has(trackId); div.className = "track" + (i === M.currentIndex ? " active" : "") + (isCached ? " cached" : " not-cached"); const title = track.title?.trim() || (track.id || track.filename || "Unknown").replace(/\.[^.]+$/, ""); div.innerHTML = `${title}${M.fmt(track.duration)}`; div.querySelector(".track-title").onclick = async () => { if (M.synced && M.currentChannelId) { const res = await fetch("/api/channels/" + M.currentChannelId + "/jump", { method: "POST", headers: { "Content-Type": "application/json" }, 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.queue.length); } else { M.currentIndex = i; M.currentTrackId = trackId; M.serverTrackDuration = track.duration; M.$("#track-title").textContent = title; M.loadingSegments.clear(); const cachedUrl = await M.loadTrackBlob(trackId); M.audio.src = cachedUrl || M.getTrackUrl(trackId); M.audio.currentTime = 0; M.localTimestamp = 0; M.audio.play(); M.renderQueue(); } }; container.appendChild(div); }); }; // Render the library M.renderLibrary = function() { const container = M.$("#library"); if (!container) return; container.innerHTML = ""; if (M.library.length === 0) { container.innerHTML = '
No tracks discovered
'; return; } M.library.forEach((track) => { const div = document.createElement("div"); const isCached = M.cachedTracks.has(track.id); div.className = "track" + (isCached ? " cached" : " not-cached"); const title = track.title?.trim() || track.filename.replace(/\.[^.]+$/, ""); div.innerHTML = `${title}${M.fmt(track.duration)}`; div.querySelector(".track-title").onclick = async () => { // Play directly from library (uses track ID) - only in local mode if (!M.synced) { M.currentTrackId = track.id; M.serverTrackDuration = track.duration; M.$("#track-title").textContent = title; M.loadingSegments.clear(); const cachedUrl = await M.loadTrackBlob(track.id); M.audio.src = cachedUrl || M.getTrackUrl(track.id); M.audio.currentTime = 0; M.localTimestamp = 0; M.audio.play(); } }; container.appendChild(div); }); }; // Load library from server M.loadLibrary = async function() { try { const res = await fetch("/api/library"); M.library = await res.json(); M.renderLibrary(); } catch (e) { console.warn("Failed to load library"); } }; })();