dev/playlists #13
|
|
@ -404,6 +404,9 @@
|
||||||
M.setTrackTitle(data.track.title);
|
M.setTrackTitle(data.track.title);
|
||||||
M.loadingSegments.clear();
|
M.loadingSegments.clear();
|
||||||
|
|
||||||
|
// Auto-scroll queue to current track
|
||||||
|
setTimeout(() => M.scrollToCurrentTrack(), 100);
|
||||||
|
|
||||||
// Debug: log cache state for this track
|
// Debug: log cache state for this track
|
||||||
const trackCache = M.trackCaches.get(trackId);
|
const trackCache = M.trackCaches.get(trackId);
|
||||||
console.log("[Playback] Starting track:", data.track.title, {
|
console.log("[Playback] Starting track:", data.track.title, {
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@
|
||||||
M.localTimestamp = 0;
|
M.localTimestamp = 0;
|
||||||
M.audio.play();
|
M.audio.play();
|
||||||
M.renderQueue();
|
M.renderQueue();
|
||||||
|
setTimeout(() => M.scrollToCurrentTrack(), 100);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="queue-panel">
|
<div id="queue-panel">
|
||||||
<h3 id="queue-title">Queue</h3>
|
<h3 id="queue-title">Queue <span id="queue-duration"></span></h3>
|
||||||
<div id="now-playing-bar" class="now-playing-bar hidden" title="Click to scroll to current track"></div>
|
<div id="now-playing-bar" class="now-playing-bar hidden" title="Click to scroll to current track"></div>
|
||||||
<div id="queue"></div>
|
<div id="queue"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -231,8 +231,27 @@
|
||||||
if (queueContainer) {
|
if (queueContainer) {
|
||||||
queueContainer.render();
|
queueContainer.render();
|
||||||
}
|
}
|
||||||
|
updateQueueDuration();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function updateQueueDuration() {
|
||||||
|
const el = M.$("#queue-duration");
|
||||||
|
if (!el) return;
|
||||||
|
const totalSecs = M.queue.reduce((sum, t) => sum + (t.duration || 0), 0);
|
||||||
|
if (totalSecs === 0) {
|
||||||
|
el.textContent = "";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hours = Math.floor(totalSecs / 3600);
|
||||||
|
const mins = Math.floor((totalSecs % 3600) / 60);
|
||||||
|
const secs = Math.floor(totalSecs % 60);
|
||||||
|
let text = "";
|
||||||
|
if (hours > 0) text = `${hours}h ${mins}m`;
|
||||||
|
else if (mins > 0) text = `${mins}m ${secs}s`;
|
||||||
|
else text = `${secs}s`;
|
||||||
|
el.textContent = `(${M.queue.length} tracks · ${text})`;
|
||||||
|
}
|
||||||
|
|
||||||
M.renderLibrary = function() {
|
M.renderLibrary = function() {
|
||||||
initContainers();
|
initContainers();
|
||||||
if (libraryContainer) {
|
if (libraryContainer) {
|
||||||
|
|
|
||||||
|
|
@ -129,6 +129,7 @@ h3 { font-size: 0.8rem; color: #666; margin-bottom: 0.3rem; text-transform: uppe
|
||||||
.upload-dropzone.hidden { display: none; }
|
.upload-dropzone.hidden { display: none; }
|
||||||
.dropzone-content { color: #4e8; font-size: 1.2rem; font-weight: 600; }
|
.dropzone-content { color: #4e8; font-size: 1.2rem; font-weight: 600; }
|
||||||
#queue-title { margin: 0 0 0.3rem 0; }
|
#queue-title { margin: 0 0 0.3rem 0; }
|
||||||
|
#queue-duration { font-size: 0.75rem; color: #888; font-weight: normal; }
|
||||||
.now-playing-bar { font-size: 0.75rem; color: #4e8; padding: 0.3rem 0.5rem; background: #1a2a1a; border: 1px solid #2a4a3a; border-radius: 4px; margin-bottom: 0.3rem; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
.now-playing-bar { font-size: 0.75rem; color: #4e8; padding: 0.3rem 0.5rem; background: #1a2a1a; border: 1px solid #2a4a3a; border-radius: 4px; margin-bottom: 0.3rem; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
|
||||||
.now-playing-bar:hover { background: #2a3a2a; }
|
.now-playing-bar:hover { background: #2a3a2a; }
|
||||||
.now-playing-bar.hidden { display: none; }
|
.now-playing-bar.hidden { display: none; }
|
||||||
|
|
|
||||||
|
|
@ -261,16 +261,19 @@
|
||||||
const trackId = track.id || track.filename;
|
const trackId = track.id || track.filename;
|
||||||
const index = type === 'queue' ? originalIndex : filteredIndex;
|
const index = type === 'queue' ? originalIndex : filteredIndex;
|
||||||
|
|
||||||
// Click - handle selection (Ctrl = toggle, Shift = range, plain = select only this)
|
// Click/double-click handling - delay render to allow double-click detection
|
||||||
|
let clickTimeout = null;
|
||||||
div.onclick = (e) => {
|
div.onclick = (e) => {
|
||||||
if (e.target.closest('.track-actions')) return;
|
if (e.target.closest('.track-actions')) return;
|
||||||
toggleSelection(index, trackId, e.shiftKey, e.ctrlKey || e.metaKey);
|
toggleSelection(index, trackId, e.shiftKey, e.ctrlKey || e.metaKey);
|
||||||
render();
|
clearTimeout(clickTimeout);
|
||||||
|
clickTimeout = setTimeout(() => render(), 200);
|
||||||
};
|
};
|
||||||
|
|
||||||
// Double-click - queue: jump to track, library/playlist: add to queue
|
// Double-click - queue: jump to track, library/playlist: add to queue
|
||||||
div.ondblclick = (e) => {
|
div.ondblclick = (e) => {
|
||||||
if (e.target.closest('.track-actions')) return;
|
if (e.target.closest('.track-actions')) return;
|
||||||
|
clearTimeout(clickTimeout);
|
||||||
if (type === 'queue') {
|
if (type === 'queue') {
|
||||||
M.jumpToTrack(originalIndex);
|
M.jumpToTrack(originalIndex);
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue