Files
CARIA-AUTOMOTIVE/ui/music.js
T

89 lines
4.8 KiB
JavaScript

// Local playlist and shared audio controls. Files never leave the browser.
export function initMusic({ storage, toast }) {
const $ = selector => document.querySelector(selector);
const audio = $('#audio');
let tracks = [], selected = -1;
let shuffle = storage.get('shuffle') === 'true', shuffleBag = [], playbackHistory = [];
$('#immersive-shuffle').setAttribute('aria-pressed', String(shuffle));
$('#immersive-shuffle').onclick = () => {
shuffle = !shuffle; shuffleBag = []; playbackHistory = [];
storage.set('shuffle', String(shuffle));
$('#immersive-shuffle').setAttribute('aria-pressed', String(shuffle));
toast(shuffle ? 'Lecture aléatoire activée' : 'Lecture aléatoire désactivée');
};
function nextTrack() {
if (!shuffle || tracks.length < 2) return selectTrack(selected + 1, true);
if (!shuffleBag.length) {
shuffleBag = tracks.map((_, index) => index).filter(index => index !== selected);
for (let i = shuffleBag.length - 1; i > 0; i--) {
const j = Math.floor(Math.random() * (i + 1));
[shuffleBag[i], shuffleBag[j]] = [shuffleBag[j], shuffleBag[i]];
}
}
selectTrack(shuffleBag.pop(), true);
}
function renderPlaylist() {
$('#playlist').replaceChildren();
tracks.forEach((track, index) => {
const li = document.createElement('li'), button = document.createElement('button');
const number = document.createElement('span'), name = document.createElement('b');
number.textContent = String(index + 1).padStart(2, '0'); name.textContent = track.name;
button.append(number, name); button.classList.toggle('selected', index === selected);
button.setAttribute('aria-pressed', String(index === selected)); button.onclick = () => selectTrack(index, true);
li.append(button); $('#playlist').append(li);
});
$('#track-count').textContent = `${tracks.length} TITRE${tracks.length > 1 ? 'S' : ''}`;
$('#empty-library').hidden = tracks.length > 0;
['#play', '#previous', '#next'].forEach(id => $(id).disabled = tracks.length === 0);
}
async function playAudio() {
try { await audio.play(); $('#audio-message').textContent = 'Lecture locale · aucun fichier envoyé au serveur.'; }
catch { $('#audio-message').textContent = 'Lecture impossible : réessayez ou choisissez un format audio pris en charge.'; }
}
function selectTrack(index, play = false, remember = true) {
if (!tracks.length) return;
previousTap = -Infinity;
if (remember && selected >= 0 && index !== selected) {
playbackHistory.push(selected);
if (playbackHistory.length > 256) playbackHistory.shift();
}
selected = (index + tracks.length) % tracks.length;
shuffleBag = shuffleBag.filter(index => index !== selected);
audio.src = tracks[selected].url;
$('#track-title').textContent = tracks[selected].name;
$('#home-track').textContent = tracks[selected].name;
$('#track-subtitle').textContent = `Morceau ${selected + 1} sur ${tracks.length} · Bibliothèque locale`;
$('#home-track-sub').textContent = 'Bibliothèque locale';
renderPlaylist(); if (play) playAudio();
}
$('#music-files').addEventListener('change', event => {
let added = 0;
for (const file of event.target.files) {
if (!file.type.startsWith('audio/') && !/\.(mp3|flac|ogg|wav|m4a|aac|opus)$/i.test(file.name)) continue;
tracks.push({ name: file.name.replace(/\.[^.]+$/, ''), url: URL.createObjectURL(file) }); added++;
}
shuffleBag = [];
renderPlaylist();
if (selected === -1 && tracks.length) selectTrack(0);
toast(added ? `${added} morceau${added > 1 ? 'x' : ''} ajouté${added > 1 ? 's' : ''}` : 'Aucun fichier audio reconnu');
event.target.value = '';
});
$('#play').onclick = () => { if (selected === -1) return; if (audio.paused) playAudio(); else audio.pause(); };
let previousTap = -Infinity;
$('#previous').onclick = () => {
const now = performance.now(), twice = now - previousTap < 600;
previousTap = twice ? -Infinity : now;
if (!twice) { audio.currentTime = 0; return; }
if (shuffle) { shuffleBag = []; selectTrack(playbackHistory.pop() ?? selected, true, false); }
else selectTrack(selected - 1, true);
};
$('#next').onclick = nextTrack;
audio.addEventListener('play', () => $('#play').textContent = 'Pause');
audio.addEventListener('pause', () => $('#play').textContent = 'Lecture');
audio.addEventListener('ended', () => { if (audio.loop) return; if (shuffle || selected + 1 < tracks.length) nextTrack(); else $('#play').textContent = 'Lecture'; });
audio.addEventListener('error', () => $('#audio-message').textContent = 'Ce fichier ne peut pas être lu. Essayez un autre format audio.');
window.addEventListener('pagehide', event => { if (!event.persisted) tracks.forEach(track => URL.revokeObjectURL(track.url)); });
renderPlaylist();
}