diff options
Diffstat (limited to 'packages/meshbay-hub/src/meshbay_hub/static/music-player.js')
| -rw-r--r-- | packages/meshbay-hub/src/meshbay_hub/static/music-player.js | 66 |
1 files changed, 65 insertions, 1 deletions
diff --git a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js index 386d3f6..eac011a 100644 --- a/packages/meshbay-hub/src/meshbay_hub/static/music-player.js +++ b/packages/meshbay-hub/src/meshbay_hub/static/music-player.js @@ -86,7 +86,39 @@ function saveRepeat(v) { try { localStorage.setItem('meshbay_music_repeat', v); } catch { /* per-device only */ } } -function MusicPlayerBar({ transportRef, gekRef, queue }) { +// The current queue, tracklist form -- "go back to what's playing" without +// switching tabs or hunting for the album/folder it came from. Works the +// same regardless of how the queue was built (an album, a consolidated +// misc/loose bucket, a single standalone track). +function QueuePanel({ tracks, order, pos, onSelect, onClose }) { + return html` + <div class="video-overlay" onClick=${(e) => { + if (e.target.classList.contains('video-overlay')) onClose(); + }}> + <div class="music-detail"> + <div class="video-top-bar"> + <span class="video-title">${t('music.queue_title')}</span> + <button class="video-close" onClick=${onClose} title=${t('video.close')}> + <${Icon} name="close" /></button> + </div> + <div class="music-detail-body"> + <div class="music-tracklist"> + ${order.map((idx, i) => html` + <button class="music-track-row ${i === pos ? 'active' : ''}" key=${tracks[idx].id} + onClick=${() => { onSelect(i); onClose(); }}> + <span class="music-track-no">${i + 1}</span> + <span class="music-track-title">${tracks[idx].display_title || tracks[idx].name}</span> + <span class="music-track-duration">${formatTime(tracks[idx].duration || 0)}</span> + </button> + `)} + </div> + </div> + </div> + </div> + `; +} + +function MusicPlayerBar({ transportRef, gekRef, queue, onClose }) { const audioRef = useRef(null); const blobCacheRef = useRef(new Map()); // file id -> { url, order: insertion index } const blobInsertRef = useRef(0); @@ -103,9 +135,23 @@ function MusicPlayerBar({ transportRef, gekRef, queue }) { const [volume, setVolume] = useState(loadVolume); const [currentTime, setCurrentTime] = useState(0); const [duration, setDuration] = useState(0); + const [showQueue, setShowQueue] = useState(false); const currentTrack = tracks[order[pos]] || null; + // Stops playback the moment this bar goes away for any reason -- the + // close button below, or the shell tearing it down on its own (leaving + // the group, switching to a different one). A component removed from the + // DOM should already stop an <audio> element, but that's the browser's + // behaviour to rely on, not this app's to assert; pausing explicitly, and + // releasing every cached blob URL rather than leaving them for the tab's + // lifetime, costs nothing and isn't optional either way. + useEffect(() => () => { + const audio = audioRef.current; + if (audio) { audio.pause(); audio.src = ''; } + for (const { url } of blobCacheRef.current.values()) URL.revokeObjectURL(url); + }, []); + const evictOldBlobs = useCallback(() => { const cache = blobCacheRef.current; while (cache.size > MAX_CACHED_BLOBS) { @@ -258,6 +304,12 @@ function MusicPlayerBar({ transportRef, gekRef, queue }) { if (audio && isFinite(audio.duration)) audio.currentTime = parseFloat(e.target.value); }, []); + const handleClose = useCallback(() => { + const audio = audioRef.current; + if (audio) audio.pause(); + if (onClose) onClose(); + }, [onClose]); + if (!currentTrack) return null; const title = currentTrack.display_title || currentTrack.name; @@ -312,8 +364,20 @@ function MusicPlayerBar({ transportRef, gekRef, queue }) { title=${t('music.player_volume')} onInput=${(e) => setVolume(parseFloat(e.target.value))} /> </div> + <div class="music-player-extra"> + <button class="music-player-btn" title=${t('music.player_queue')} onClick=${() => setShowQueue(true)}> + <${Icon} name="menu" /> + </button> + <button class="music-player-btn" title=${t('music.player_close')} onClick=${handleClose}> + <${Icon} name="close" /> + </button> + </div> ${error && html`<div class="music-player-error">${error}</div>`} </div> + ${showQueue && html` + <${QueuePanel} tracks=${tracks} order=${order} pos=${pos} + onSelect=${skipTo} onClose=${() => setShowQueue(false)} /> + `} `; } |