feat: integrate lightbox for media images and improve media session handling

- Add lightbox support to song/playlist cover images by adding lb-trigger class and data-lightbox attributes
- Prevent default click behavior in lightbox.js to avoid navigation when opening image modal
- Update player UI to conditionally enable/disable lightbox on album art based on cover presence
- Set navigator.mediaSession.playbackState proactively during audio reassignment to prevent OS throttling
- Wrap media session seek handlers in try-catch blocks for compatibility with mobile streaming sources
This commit is contained in:
Brian Fertig 2026-04-15 21:24:54 -06:00
parent 5eadf57068
commit df14aa8975
5 changed files with 22 additions and 7 deletions

View File

@ -46,6 +46,8 @@
document.addEventListener('click', (e) => {
const trigger = e.target.closest('[data-lightbox]');
if (trigger) {
e.preventDefault();
e.stopPropagation();
open(trigger.src || trigger.dataset.src, trigger.alt);
return;
}

View File

@ -36,6 +36,8 @@
titleEl.textContent = 'Nothing playing';
artistEl.textContent = '';
coverEl.src = '/static/vendor/cover-placeholder.svg';
coverEl.removeAttribute('data-lightbox');
coverEl.classList.remove('lb-trigger');
queueInfoEl.textContent = '';
updatePlayerSocial(null);
return;
@ -43,6 +45,13 @@
titleEl.textContent = cur.title;
artistEl.textContent = cur.artist || '';
coverEl.src = cur.cover || '/static/vendor/cover-placeholder.svg';
if (cur.cover) {
coverEl.setAttribute('data-lightbox', '');
coverEl.classList.add('lb-trigger');
} else {
coverEl.removeAttribute('data-lightbox');
coverEl.classList.remove('lb-trigger');
}
queueInfoEl.textContent = state.queue.length > 1
? `${state.index + 1} / ${state.queue.length}`
: '';
@ -73,6 +82,9 @@
? [{ src: new URL(cur.cover, window.location.origin).href, sizes: '512x512', type: 'image/jpeg' }]
: [],
});
// Proactively mark as playing so the OS doesn't throttle during the
// brief gap when audio.src is reassigned (which fires a pause event).
navigator.mediaSession.playbackState = 'playing';
}
function playCurrent() {
@ -126,9 +138,10 @@
navigator.mediaSession.setActionHandler('pause', () => audio.pause());
navigator.mediaSession.setActionHandler('nexttrack', () => next());
navigator.mediaSession.setActionHandler('previoustrack', () => prev());
navigator.mediaSession.setActionHandler('seekto', (d) => { audio.currentTime = d.seekTime; });
navigator.mediaSession.setActionHandler('seekbackward', (d) => { audio.currentTime = Math.max(0, audio.currentTime - (d.seekOffset || 10)); });
navigator.mediaSession.setActionHandler('seekforward', (d) => { audio.currentTime = Math.min(audio.duration || Infinity, audio.currentTime + (d.seekOffset || 10)); });
// seek handlers may throw on some mobile browsers for streaming sources
try { navigator.mediaSession.setActionHandler('seekto', (d) => { audio.currentTime = d.seekTime; }); } catch (_) {}
try { navigator.mediaSession.setActionHandler('seekbackward', (d) => { audio.currentTime = Math.max(0, audio.currentTime - (d.seekOffset || 10)); }); } catch (_) {}
try { navigator.mediaSession.setActionHandler('seekforward', (d) => { audio.currentTime = Math.min(audio.duration || Infinity, audio.currentTime + (d.seekOffset || 10)); }); } catch (_) {}
}
repeatBtn.addEventListener('click', () => {

View File

@ -11,7 +11,7 @@
<input type="checkbox" class="song-select" value="<%= s.id %>" aria-label="Select <%= s.title %>">
<% } %>
<% if (s.coverPath) { %>
<img src="/media/<%= s.coverPath %>" alt="" class="song-cover" />
<img src="/media/<%= s.coverPath %>" alt="" class="song-cover lb-trigger" data-lightbox />
<% } else { %>
<div class="song-cover cover-fallback">♪</div>
<% } %>

View File

@ -12,7 +12,7 @@
<% featured.forEach((p) => { %>
<a class="card" href="/playlists/<%= p.slug %>" data-playlist-id="<%= p.id %>">
<% if (p.coverPath) { %>
<img src="/media/<%= p.coverPath %>" alt="" />
<img src="/media/<%= p.coverPath %>" alt="" class="lb-trigger" data-lightbox />
<% } else { %>
<div class="cover-fallback">🎧</div>
<% } %>

View File

@ -7,7 +7,7 @@
<% recentlyLiked.forEach((s) => { %>
<a href="/songs/<%= s.slug %>" class="liked-card">
<% if (s.coverPath) { %>
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover">
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover lb-trigger" data-lightbox>
<% } else { %>
<div class="liked-card-cover liked-card-cover--fallback">♪</div>
<% } %>
@ -53,7 +53,7 @@
<% mostPopular.forEach((s) => { %>
<a href="/songs/<%= s.slug %>" class="liked-card">
<% if (s.coverPath) { %>
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover">
<img src="/media/<%= s.coverPath %>" alt="" class="liked-card-cover lb-trigger" data-lightbox>
<% } else { %>
<div class="liked-card-cover liked-card-cover--fallback">♪</div>
<% } %>