feat: Add Media Session API support for browser media controls

- Implement `updateMediaSession()` to sync track metadata (title, artist, artwork) with browser's media session
- Update playback state (`playing`/`paused`) via `mediaSession.playbackState`
- Register action handlers for common media controls: play, pause, next/prev track, seek operations
- Integrate `updateMediaSession()` call into `playCurrent()` to show metadata when playback starts

Enables native browser media notification and lock screen controls on supported platforms.
This commit is contained in:
Brian Fertig 2026-04-14 22:42:37 -06:00
parent 3586e1da9a
commit 5eadf57068
1 changed files with 30 additions and 0 deletions

View File

@ -62,6 +62,19 @@
}); });
} }
function updateMediaSession() {
if (!('mediaSession' in navigator)) return;
const cur = state.queue[state.index];
if (!cur) return;
navigator.mediaSession.metadata = new MediaMetadata({
title: cur.title,
artist: cur.artist || '',
artwork: cur.cover
? [{ src: new URL(cur.cover, window.location.origin).href, sizes: '512x512', type: 'image/jpeg' }]
: [],
});
}
function playCurrent() { function playCurrent() {
const cur = state.queue[state.index]; const cur = state.queue[state.index];
if (!cur) return; if (!cur) return;
@ -72,6 +85,7 @@
.catch((err) => console.warn('play failed', err)); .catch((err) => console.warn('play failed', err));
render(); render();
highlightNowPlaying(cur.id); highlightNowPlaying(cur.id);
updateMediaSession();
} }
function playOne(song) { function playOne(song) {
@ -98,9 +112,25 @@
} }
} }
function prev() {
if (state.index > 0) { state.index -= 1; playCurrent(); }
}
audio.addEventListener('ended', next); audio.addEventListener('ended', next);
audio.addEventListener('play', () => { if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'playing'; });
audio.addEventListener('pause', () => { if ('mediaSession' in navigator) navigator.mediaSession.playbackState = 'paused'; });
nextBtn.addEventListener('click', next); nextBtn.addEventListener('click', next);
if ('mediaSession' in navigator) {
navigator.mediaSession.setActionHandler('play', () => audio.play());
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)); });
}
repeatBtn.addEventListener('click', () => { repeatBtn.addEventListener('click', () => {
repeatOn = !repeatOn; repeatOn = !repeatOn;
repeatBtn.classList.toggle('active', repeatOn); repeatBtn.classList.toggle('active', repeatOn);