205 lines
7.7 KiB
JavaScript
205 lines
7.7 KiB
JavaScript
(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';
|
|
coverEl.removeAttribute('data-lightbox');
|
|
coverEl.classList.remove('lb-trigger');
|
|
queueInfoEl.textContent = '';
|
|
updatePlayerSocial(null);
|
|
return;
|
|
}
|
|
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}`
|
|
: '';
|
|
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' }]
|
|
: [],
|
|
});
|
|
// 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() {
|
|
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());
|
|
// 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', () => {
|
|
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();
|
|
})();
|