diff options
| author | Paul Buetow <paul@buetow.org> | 2026-05-01 16:45:55 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-05-01 16:45:55 +0300 |
| commit | 048d16b8aa253c93d4ae0cd1c313bf8d8b12f368 (patch) | |
| tree | ab7b288399e5e0b9ed9ebdfcc157b061cf5bbc7a /web/js | |
| parent | 65ee2e4d7f2be8036457e3cdca0e26e09eb56e0b (diff) | |
Add player stage collapse, multi-set selection, grid nav, m4b support, content-type fixes, thumbnail refresh, and verbose logging
Diffstat (limited to 'web/js')
| -rw-r--r-- | web/js/app.js | 11 | ||||
| -rw-r--r-- | web/js/keyboard.js | 7 | ||||
| -rw-r--r-- | web/js/player.js | 33 |
3 files changed, 47 insertions, 4 deletions
diff --git a/web/js/app.js b/web/js/app.js index 8f8ca84..cd5ded4 100644 --- a/web/js/app.js +++ b/web/js/app.js @@ -462,6 +462,17 @@ async function regenThumb(mediaId) { try { await API.regenThumbnail(mediaId); toast('Thumbnail regenerated'); + // Force refresh any thumbnail images for this media in the grid + document.querySelectorAll(`#media-grid [data-id="${mediaId}"] img`).forEach((img) => { + const base = img.src.split('?')[0]; + img.src = `${base}?t=${Date.now()}`; + }); + // If the same media is currently loaded in the player, refresh cover art too + const e = document.getElementById('cover-art'); + if (e && !e.classList.contains('hidden')) { + const base = e.src.split('?')[0]; + e.src = `${base}?t=${Date.now()}`; + } } catch (err) { toast(err.message || 'Thumbnail failed', 'error'); } } diff --git a/web/js/keyboard.js b/web/js/keyboard.js index de67208..cad695c 100644 --- a/web/js/keyboard.js +++ b/web/js/keyboard.js @@ -35,6 +35,8 @@ export function initKeyboard(handlers) { return; } + if (e.ctrlKey || e.metaKey || e.altKey) return; // don't intercept browser shortcuts + switch (e.key) { // Navigation case 'ArrowUp': @@ -68,7 +70,10 @@ export function initKeyboard(handlers) { break; case 'Enter': handlers.enter?.(e); break; case 'p': handlers.playPause?.(e); break; - case 'c': handlers.toggleStage?.(e); break; + case 'c': + case 'C': + handlers.toggleStage?.(e); + break; case 'f': handlers.fullscreen?.(e); break; case 'Escape': handlers.escape?.(e); break; case 'r': handlers.shuffle?.(e); break; diff --git a/web/js/player.js b/web/js/player.js index 002a781..88ef404 100644 --- a/web/js/player.js +++ b/web/js/player.js @@ -70,6 +70,23 @@ export function initPlayer() { m.addEventListener('pause', () => { updateUI(false); isPlaying = false; stopProgressTimer(); }); }); + function setupVideoDebug(m) { + const events = ['loadstart','loadeddata','loadedmetadata','canplay','canplaythrough','playing','waiting','stalled','suspend','error','abort','emptied','ended']; + events.forEach(event => { + m.addEventListener(event, () => { + console.log('[video-debug]', event, + 'readyState=', m.readyState, + 'networkState=', m.networkState, + 'paused=', m.paused, + 'src=', m.src?.slice(-40), + 'error=', m.error?.code || 'none', + 'errorMsg=', m.error?.message || ''); + }); + }); + } + setupVideoDebug(e.video); + setupVideoDebug(e.audio); + let seeking = false; const seekToFraction = (frac) => { const m = currentMediaElement(); @@ -112,7 +129,13 @@ export function selectAndPlay(media, index, resumeFrom = 0) { currentMediaIndex = index ?? -1; loadMedia(media, resumeFrom); isPlaying = true; - currentMediaElement()?.play().catch(() => {}); + const m = currentMediaElement(); + if (m) { + console.log('selectAndPlay: type=', media.type, 'src=', m.src, 'readyState=', m.readyState); + m.play().catch((err) => { console.error('play() failed:', err); }); + } else { + console.error('selectAndPlay: no media element found for type', media.type); + } highlightPlayingCard(); } @@ -121,16 +144,19 @@ function loadMedia(media, resumeFrom = 0) { const isVideo = media.type === 'video'; const src = `/api/media/${media.id}/stream`; if (isVideo) { + e.video.pause(); + e.audio.pause(); e.audio.src = ''; e.video.style.display = ''; e.audio.style.display = 'none'; e.coverArt?.classList.add('hidden'); - e.audio.pause(); e.audio.src = ''; e.video.src = src; + e.video.load(); e.video.currentTime = resumeFrom; } else { + e.audio.pause(); + e.video.pause(); e.video.src = ''; e.video.style.display = 'none'; e.audio.style.display = 'none'; /* keep playing but invisible so cover-art fills stage */ - e.video.pause(); e.video.src = ''; e.audio.src = src; e.audio.currentTime = resumeFrom; if (e.coverArt) { @@ -195,6 +221,7 @@ export function toggleFullscreen() { export function toggleStage() { const e = els(); e.player?.classList.toggle('collapsed'); + console.log('toggleStage: collapsed =', e.player?.classList.contains('collapsed')); } export function exitFullscreenIfNeeded() { |
