(function () { const audio = document.getElementById('player-audio'); const titleEl = document.getElementById('player-title'); const artistEl = document.getElementById('player-artist'); const coverEl = document.getElementById('player-cover'); const queueInfoEl = document.getElementById('player-queue-info'); const nextBtn = document.getElementById('player-next'); const repeatBtn = document.getElementById('player-repeat'); const playerLikeBtn = document.getElementById('player-like'); const playerFavBtn = document.getElementById('player-favorite'); let repeatOn = false; function updatePlayerSocial(song) { if (!playerLikeBtn || !playerFavBtn) return; const active = !!song; playerLikeBtn.disabled = playerFavBtn.disabled = !active; playerLikeBtn.dataset.socialId = playerFavBtn.dataset.socialId = active ? song.id : ''; playerLikeBtn.classList.toggle('active', active && !!song.userLiked); playerLikeBtn.setAttribute('aria-pressed', String(active && !!song.userLiked)); playerFavBtn.classList.toggle('active', active && !!song.userFavorited); playerFavBtn.setAttribute('aria-pressed', String(active && !!song.userFavorited)); const lc = playerLikeBtn.querySelector('.social-count'); const fc = playerFavBtn.querySelector('.social-count'); if (lc) lc.textContent = active ? (song.likeCount ?? 0) : 0; if (fc) fc.textContent = active ? (song.favoriteCount ?? 0) : 0; } const state = { queue: [], // array of {id, title, artist, cover} index: -1, }; function render() { const cur = state.queue[state.index]; if (!cur) { titleEl.textContent = 'Nothing playing'; artistEl.textContent = ''; coverEl.src = '/static/vendor/cover-placeholder.svg'; queueInfoEl.textContent = ''; updatePlayerSocial(null); return; } titleEl.textContent = cur.title; artistEl.textContent = cur.artist || ''; coverEl.src = cur.cover || '/static/vendor/cover-placeholder.svg'; queueInfoEl.textContent = state.queue.length > 1 ? `${state.index + 1} / ${state.queue.length}` : ''; updatePlayerSocial(cur); } function highlightNowPlaying(id) { document.querySelectorAll('.song-row').forEach(row => { row.classList.toggle('now-playing', String(row.dataset.songId) === String(id)); }); } function highlightPlaylistCards() { const id = window.briTunesActivePlaylistId; document.querySelectorAll('[data-playlist-id]').forEach(el => { el.classList.toggle('playlist-active', !!id && String(el.dataset.playlistId) === String(id)); }); } 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() { const cur = state.queue[state.index]; if (!cur) return; window.briTunesNowPlaying = cur; audio.src = `/stream/${cur.id}`; audio.play() .then(() => window.dispatchEvent(new CustomEvent('briTunes:play', { detail: cur }))) .catch((err) => console.warn('play failed', err)); render(); highlightNowPlaying(cur.id); updateMediaSession(); } function playOne(song) { state.queue = [song]; state.index = 0; playCurrent(); } function playList(list) { if (!Array.isArray(list) || list.length === 0) return; state.queue = list; state.index = 0; window.dispatchEvent(new CustomEvent('briTunes:queue', { detail: { queue: list.slice() } })); playCurrent(); } function next() { if (state.index + 1 < state.queue.length) { state.index += 1; playCurrent(); } else if (repeatOn && state.queue.length > 0) { state.index = 0; playCurrent(); } } function prev() { if (state.index > 0) { state.index -= 1; playCurrent(); } } 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); 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', () => { repeatOn = !repeatOn; repeatBtn.classList.toggle('active', repeatOn); repeatBtn.setAttribute('aria-pressed', repeatOn); }); window.addEventListener('briTunes:jumpto', (e) => { const id = e.detail && e.detail.id; if (!id) return; const idx = state.queue.findIndex(s => String(s.id) === String(id)); if (idx === -1) return; state.index = idx; playCurrent(); }); window.addEventListener('briTunes:navigate', () => { if (window.briTunesNowPlaying) highlightNowPlaying(window.briTunesNowPlaying.id); highlightPlaylistCards(); }); // Delegated click handler for any [data-play-song] / [data-play-playlist] button. document.addEventListener('click', (e) => { const songBtn = e.target.closest('[data-play-song]'); if (songBtn) { window.briTunesActivePlaylistId = null; highlightPlaylistCards(); try { const song = JSON.parse(songBtn.getAttribute('data-play-song')); playOne(song); } catch (err) { console.error(err); } return; } const listBtn = e.target.closest('[data-play-playlist]'); if (listBtn) { window.briTunesActivePlaylistId = listBtn.dataset.playlistId || null; highlightPlaylistCards(); try { const list = JSON.parse(listBtn.getAttribute('data-play-playlist')); playList(list); } catch (err) { console.error(err); } return; } const shuffleBtn = e.target.closest('[data-shuffle-playlist]'); if (shuffleBtn) { window.briTunesActivePlaylistId = shuffleBtn.dataset.playlistId || null; highlightPlaylistCards(); try { const list = JSON.parse(shuffleBtn.getAttribute('data-shuffle-playlist')); for (let i = list.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [list[i], list[j]] = [list[j], list[i]]; } playList(list); } catch (err) { console.error(err); } } }); render(); })();