loop/node-server/assets/script.js
2025-06-08 10:29:30 +02:00

305 lines
8.0 KiB
JavaScript

/* // 1. Sélection des éléments DOM
const vinyl = document.getElementById('vinyl');
const audio = document.getElementById('audio');
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 idleTimeout = null;
let lastPosition = null;
let lastTime = null;
const rotationsPerTrack = 3; // tours complets du vinyle sur toute la durée
let currentRotation = 0; // rotation en degrés actuelle du vinyle
// 3. Connexion Socket.io
const socket = io();
socket.on('position', (position) => {
const now = Date.now();
if (lastPosition !== null && lastTime !== null) {
const deltaPos = position - lastPosition;
const deltaTime = (now - lastTime) / 1000;
const speed = deltaPos / deltaTime; // vitesse de rotation
clearTimeout(idleTimeout);
// Seuil de mouvement pour arrêt
if (Math.abs(speed) < 1) {
stopPlayback();
return;
}
if (speed < 0) {
// Rotation inverse : on recule la timeline
audio.pause();
isPlaying = false;
// recule proportionnellement à la vitesse (sensibilité ajustable)
audio.currentTime = Math.max(0, audio.currentTime + speed * 0.0025);
} else {
// Rotation avant : jouer la piste et ajuster vitesse
if (!isPlaying) {
audio.play();
isPlaying = true;
}
// SENSIBILITÉ : le facteur diviseur est plus grand → plus doux
let playbackSpeed = 0.5 + speed / 900;
playbackSpeed = Math.min(Math.max(playbackSpeed, 0.5), 1.1); // plage limitée à x1 max
audio.playbackRate = playbackSpeed;
}
}
lastPosition = position;
lastTime = now;
// Détection d'inactivité
idleTimeout = setTimeout(() => {
stopPlayback();
}, 300);
});
// 4. Fonctions
function togglePlay() {
if (isPlaying) {
audio.pause();
} else {
audio.play();
}
isPlaying = !isPlaying;
}
function updateProgress() {
if (!isNaN(audio.duration)) {
const progressPercent = (audio.currentTime / audio.duration) * 100;
progress.value = progressPercent;
currentTimeEl.textContent = formatTime(audio.currentTime);
durationEl.textContent = formatTime(audio.duration);
updateVinylRotation();
}
}
function formatTime(time) {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
}
function setProgress(e) {
const width = progress.clientWidth;
const clickX = e.offsetX;
const duration = audio.duration;
audio.currentTime = (clickX / width) * duration;
}
function stopPlayback() {
audio.pause();
isPlaying = false;
audio.playbackRate = 1;
}
// La rotation est synchronisée avec la timeline audio
function updateVinylRotation() {
if (audio.duration && audio.currentTime >= 0) {
// Rotation proportionnelle au temps courant
currentRotation = (audio.currentTime / audio.duration) * 360 * rotationsPerTrack;
vinyl.style.transform = `rotate(${currentRotation}deg)`;
console.log('Rotation:', currentRotation); // Debug
}
}
// 5. Événements
vinyl.addEventListener('click', togglePlay);
audio.addEventListener('timeupdate', updateProgress);
audio.addEventListener('loadedmetadata', () => {
durationEl.textContent = formatTime(audio.duration);
console.log('Audio duration:', audio.duration);
});
progress.addEventListener('click', setProgress);
backwardBtn.addEventListener('click', () => {
audio.currentTime = Math.max(0, audio.currentTime - 10);
});
forwardBtn.addEventListener('click', () => {
audio.currentTime = Math.min(audio.duration, audio.currentTime + 10);
});
loopBtn.addEventListener('click', () => {
isLooping = !isLooping;
loopBtn.style.backgroundColor = isLooping ? 'white' : 'transparent';
loopBtn.style.color = isLooping ? '#18344b' : 'white';
});
audio.addEventListener('ended', () => {
if (isLooping) {
audio.currentTime = 0;
audio.play();
} else {
isPlaying = false;
}
}); */
// === Variables DOM ===
const vinyl = document.getElementById('vinyl');
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');
// === Web Audio ===
let audioCtx;
let buffer;
let source;
let startTime = 0;
let offset = 0;
let isPlaying = false;
let playbackRate = 1;
let isLooping = false;
// === Socket + Vitesse ===
let idleTimeout = null;
let lastPosition = null;
let lastTime = null;
// === Initialisation Audio (doit être déclenchée par une interaction utilisateur) ===
async function initAudio() {
audioCtx = new (window.AudioContext || window.webkitAudioContext)();
const response = await fetch('assets/muddy_files.mp3'); // adapte ici
const arrayBuffer = await response.arrayBuffer();
buffer = await audioCtx.decodeAudioData(arrayBuffer);
durationEl.textContent = formatTime(buffer.duration);
}
// === Lecture ===
function startPlayback() {
if (isPlaying || !buffer) return;
source = audioCtx.createBufferSource();
source.buffer = buffer;
source.playbackRate.value = playbackRate;
source.loop = isLooping;
source.connect(audioCtx.destination);
source.start(0, offset);
startTime = audioCtx.currentTime;
isPlaying = true;
source.onended = () => {
isPlaying = false;
if (isLooping) {
offset = 0;
startPlayback();
}
};
}
function stopPlayback() {
if (!isPlaying) return;
source.stop();
offset += (audioCtx.currentTime - startTime) * playbackRate;
isPlaying = false;
}
function updateSpeed(speed) {
const MIN_SPEED = 1;
const MAX_SPEED = 100;
if (Math.abs(speed) < MIN_SPEED) {
stopPlayback();
return;
}
let normSpeed = Math.min(Math.max(speed, MIN_SPEED), MAX_SPEED);
playbackRate = 0.5 + ((normSpeed - MIN_SPEED) / (MAX_SPEED - MIN_SPEED)) * 0.5;
if (isPlaying) {
stopPlayback();
startPlayback();
}
}
// === Socket.io ===
const socket = io();
socket.on('position', (position) => {
const now = Date.now();
if (lastPosition !== null && lastTime !== null) {
const deltaPos = position - lastPosition;
const deltaTime = (now - lastTime) / 1000;
const speed = deltaPos / deltaTime;
clearTimeout(idleTimeout);
if (Math.abs(speed) < 1) {
stopPlayback();
return;
}
if (speed < 0) {
stopPlayback();
offset = Math.max(0, offset + speed * 0.01); // ajuste sensibilité
} else {
updateSpeed(speed);
}
}
lastPosition = position;
lastTime = now;
idleTimeout = setTimeout(() => {
stopPlayback();
}, 300);
});
// === Contrôles UI ===
vinyl.addEventListener('click', () => {
if (!audioCtx) initAudio().then(startPlayback);
else if (isPlaying) stopPlayback();
else startPlayback();
});
backwardBtn.addEventListener('click', () => {
offset = Math.max(0, offset - 10);
});
forwardBtn.addEventListener('click', () => {
offset = Math.min(buffer.duration, offset + 10);
});
loopBtn.addEventListener('click', () => {
isLooping = !isLooping;
loopBtn.style.backgroundColor = isLooping ? 'white' : 'transparent';
loopBtn.style.color = isLooping ? '#18344b' : 'white';
});
// === Affichage rotation & temps ===
function formatTime(time) {
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? '0' : ''}${seconds}`;
}
function updateVinylRotation() {
if (!buffer) return;
currentRotation = (offset / buffer.duration) * 360 * 3;
vinyl.style.transform = `rotate(${currentRotation}deg)`;
}
setInterval(() => {
if (isPlaying) updateVinylRotation();
currentTimeEl.textContent = formatTime(offset + (audioCtx.currentTime - startTime) * playbackRate);
}, 100);