summaryrefslogtreecommitdiffstats
path: root/packages/meshbay-hub/src/meshbay_hub/static/music-player.js
diff options
context:
space:
mode:
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.js66
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)} />
+ `}
`;
}