207 lines
7.5 KiB
JavaScript
207 lines
7.5 KiB
JavaScript
// 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.playlist.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.playlist[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 (channel's playlist)
|
|
M.renderQueue = function() {
|
|
const container = M.$("#queue");
|
|
if (!container) return;
|
|
container.innerHTML = "";
|
|
if (M.playlist.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 => {
|
|
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) => {
|
|
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 = `<span class="cache-indicator"></span><span class="track-title">${title}</span><span class="track-actions"><span class="duration">${M.fmt(track.duration)}</span></span>`;
|
|
|
|
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.playlist.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);
|
|
});
|
|
};
|
|
|
|
// Alias for backward compatibility
|
|
M.renderPlaylist = M.renderQueue;
|
|
|
|
// Render the library
|
|
M.renderLibrary = function() {
|
|
const container = M.$("#library");
|
|
if (!container) return;
|
|
container.innerHTML = "";
|
|
if (M.library.length === 0) {
|
|
container.innerHTML = '<div class="empty">No tracks discovered</div>';
|
|
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 = `<span class="cache-indicator"></span><span class="track-title">${title}</span><span class="track-actions"><span class="duration">${M.fmt(track.duration)}</span></span>`;
|
|
|
|
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");
|
|
}
|
|
};
|
|
})();
|