// 1. Sélection des éléments du DOM const vinyl = document.getElementById('vinyl'); const audioForward = document.getElementById('audio-forward'); const audioReverse = document.getElementById('audio-reverse'); const progress = document.getElementById('progress'); const currentTimeEl = document.getElementById('currentTime'); const durationEl = document.getElementById('duration'); const backwardBtn = document.getElementById('backward'); const forwardBtn = document.getElementById('forward'); const loopBtn = document.getElementById('loop'); // 2. Variables utiles let isPlaying = false; let isLooping = false; let lastPosition = 0; let stopTimeout = null; // socket.io & arduino serial info const socket = io(); const MIN_SPEED = 0.1; // seuil minimal pour commencer la lecture const MAX_SPEED = 2; // vitesse max const SENSITIVITY = 0.01; // ajuste la sensibilité du mapping // Fonction pour formater le temps (min:sec) function formatTime(time) { const minutes = Math.floor(time / 60); const seconds = Math.floor(time % 60); return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`; } // Mise à jour de la barre de progression à partir de la piste active function updateProgress() { let activeAudio = isPlaying ? (audioForward.paused ? audioReverse : audioForward) : null; if (activeAudio && activeAudio.duration) { const progressPercent = (activeAudio.currentTime / activeAudio.duration) * 100; progress.value = progressPercent; currentTimeEl.textContent = formatTime(activeAudio.currentTime); durationEl.textContent = formatTime(activeAudio.duration); } } // Met à jour l’animation du vinyle selon lecture en cours function updateVinylAnimation() { if (!audioForward.paused || !audioReverse.paused) { vinyl.style.animationPlayState = 'running'; } else { vinyl.style.animationPlayState = 'paused'; } } // Fonction pour arrêter les deux audios function stopAudio() { audioForward.pause(); audioReverse.pause(); isPlaying = false; updateVinylAnimation(); } // Fonction pour contrôler la lecture en fonction de la vitesse (peut être négative) function controlPlayback(speed) { // Nettoyer l’ancien timeout d’arrêt if (stopTimeout) clearTimeout(stopTimeout); // Si vitesse proche de 0, on arrête la lecture après 1.5s d’inactivité if (Math.abs(speed) < MIN_SPEED) { // Lance le timeout d'arrêt si plus de mouvement stopTimeout = setTimeout(() => { stopAudio(); }, 1500); return; } // Ici, on a une vitesse suffisante => on arrête les deux audios d'abord audioForward.pause(); audioReverse.pause(); // Calcule la vitesse normalisée (sensibilité appliquée) let normalizedSpeed = Math.min(MAX_SPEED, Math.max(0.5, Math.abs(speed) * SENSITIVITY)); // On lit dans le bon sens if (speed > 0) { audioForward.playbackRate = normalizedSpeed; // Redémarrer la lecture si elle est arrivée à la fin if (audioForward.currentTime >= audioForward.duration) { audioForward.currentTime = 0; } audioForward.play(); } else { audioReverse.playbackRate = normalizedSpeed; if (audioReverse.currentTime >= audioReverse.duration) { audioReverse.currentTime = 0; } audioReverse.play(); } isPlaying = true; updateVinylAnimation(); } // socket.io reçoit la position de l’encodeur socket.on('position', (position) => { // Calcule la vitesse par rapport au dernier mouvement let speed = position - lastPosition; lastPosition = position; controlPlayback(speed); }); // 3. Événements généraux // Clique sur le vinyle pour basculer lecture/pauses sur les deux audios vinyl.addEventListener('click', () => { if (isPlaying) { stopAudio(); } else { audioForward.playbackRate = 1; audioForward.play(); isPlaying = true; updateVinylAnimation(); } }); // Mise à jour de la barre de progression à chaque changement de temps (sur les deux audios) audioForward.addEventListener('timeupdate', updateProgress); audioReverse.addEventListener('timeupdate', updateProgress); // Mise à jour de la durée du titre dès que l'audio est chargé (sur les deux) audioForward.addEventListener('loadedmetadata', () => { durationEl.textContent = formatTime(audioForward.duration); }); audioReverse.addEventListener('loadedmetadata', () => { durationEl.textContent = formatTime(audioReverse.duration); }); // Permet de cliquer sur la barre de progression (pour avancer dans la piste forward) progress.addEventListener('click', (e) => { const width = progress.clientWidth; const clickX = e.offsetX; const duration = audioForward.duration; audioForward.currentTime = (clickX / width) * duration; // En cas d'avancer, on arrête le reverse audioReverse.pause(); updateProgress(); }); // Bouton reculer de 10s (sur piste forward) backwardBtn.addEventListener('click', () => { audioForward.currentTime = Math.max(0, audioForward.currentTime - 10); audioReverse.pause(); updateProgress(); }); // Bouton avancer de 10s (sur piste forward) forwardBtn.addEventListener('click', () => { audioForward.currentTime = Math.min(audioForward.duration, audioForward.currentTime + 10); audioReverse.pause(); updateProgress(); }); // Bouton pour activer/désactiver la boucle (LOOP) loopBtn.addEventListener('click', () => { isLooping = !isLooping; loopBtn.style.backgroundColor = isLooping ? 'white' : 'transparent'; loopBtn.style.color = isLooping ? '#18344b' : 'white'; audioForward.loop = isLooping; audioReverse.loop = isLooping; }); // Lorsque l'audio est terminé, on répète si la boucle est activée audioForward.addEventListener('ended', () => { if (isLooping) { audioForward.currentTime = 0; audioForward.play(); } else { vinyl.style.animationPlayState = 'paused'; isPlaying = false; } }); audioReverse.addEventListener('ended', () => { if (isLooping) { audioReverse.currentTime = 0; audioReverse.play(); } else { vinyl.style.animationPlayState = 'paused'; isPlaying = false; } });