From 048d16b8aa253c93d4ae0cd1c313bf8d8b12f368 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Fri, 1 May 2026 16:45:55 +0300 Subject: Add player stage collapse, multi-set selection, grid nav, m4b support, content-type fixes, thumbnail refresh, and verbose logging --- web/js/app.js | 11 +++++++++++ web/js/keyboard.js | 7 ++++++- web/js/player.js | 33 ++++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 4 deletions(-) (limited to 'web/js') 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() { -- cgit v1.2.3