document.addEventListener("DOMContentLoaded", () => { // Récup de tous les éléments du HTML const fileInput = document.getElementById('file-input'); const runBtn = document.getElementById('btn-run'); const nameInput = document.getElementById('recording-name'); const preview = document.getElementById('video-preview'); const placeholder = document.getElementById('video-placeholder'); const localVideo = document.getElementById('local-video'); const youtubeVideo = document.getElementById('youtube-video'); const videoLink = document.getElementById('video-link'); const loadLocal = document.getElementById('load-local'); const loadYoutube = document.getElementById('load-youtube'); let selectedFile = null; // Charge la bibliothèque des vidéos déjà analysées chargerBibliotheque(); // Affichage selection entre mode local et yt localVideo.addEventListener('click', () =>{ videoLink.style.display = "none"; loadLocal.style.display = "block"; loadYoutube.style.display = "none"; }); youtubeVideo.addEventListener('click', () =>{ videoLink.style.display = "block"; loadLocal.style.display = "none"; loadYoutube.style.display = "block"; }); // Permet de télécharger une vidéo yt loadYoutube.addEventListener('click', async () => { const link = videoLink.value.trim(); if (!link) return updateStatus("Entrez le lien d'une vidéo youtube", 'error'); updateStatus("Téléchargement en cours...", 'active'); setProgress(20); loadYoutube.disabled = true; try { // Appelle l'api python pour télécharger const response = await fetch('/telecharger', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ url: link }) // On envoie l'url au serveur }); const data = await response.json(); if (data.succes) { setProgress(60); updateStatus("Vidéo téléchargée — donnez un nom et lancez le pipeline", 'done'); runBtn.disabled = false; // le bouton lancer le pipeline est débloqué } else { setProgress(0); updateStatus(`Erreur téléchargement : ${data.message}`, 'error'); } } catch (err) { setProgress(0); updateStatus(`Erreur : ${err.message}`, 'error'); } finally { loadYoutube.disabled = false; } }); // Charger une vidéo sur son pc en local fileInput.addEventListener('change', (e) => { const file = e.target.files[0]; if (!file) return; selectedFile = file; // Permet de lire la vidéo dans le navigateur preview.src = URL.createObjectURL(file); preview.style.display = 'block'; placeholder.style.display = 'none'; runBtn.disabled = false; updateStatus(`Vidéo prête : ${file.name}`, 'active'); }); // Lancer le pipeline runBtn.addEventListener('click', async () => { // Différentes vérif : si la vidéo est chargée, a un nom ou si c'est un double const nom = nameInput.value.trim(); if (!nom) return updateStatus("Donnez un nom à l'enregistrement", 'error'); const isYoutube = loadYoutube.style.display === "block"; if (!isYoutube && !selectedFile) return updateStatus("Chargez une vidéo d'abord", 'error'); try { const responsescv = await fetch('/liste'); if (!responsescv.ok) throw new Error(); const listescv = await responsescv.json(); if (listescv.noms.includes(nom)) { return updateStatus("Ce nom est déjà dans la bibliothèque", 'error'); } } catch (err) { console.error("Erreur de vérification de la bibliothèque:", err); } runBtn.disabled = true; updateStatus("Extraction des landmarks et génération des mouvements...", 'active'); setProgress(30); let response; // Si vidéo yt if (isYoutube) { // lance la vidéo car déjà sur le serv response = await fetch('/lancer-youtube', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nom }) }); } else { // sinon fichier local donc il faut l'envoyer sur le serveur (upload) const formData = new FormData(); formData.append('video', selectedFile); formData.append('nom', nom); response = await fetch('/charger', { method: 'POST', body: formData }); } try { if (!response.ok) { const text = await response.text(); updateStatus(`Erreur serveur (${response.status}): ${text}`, 'error'); runBtn.disabled = false; return; } const data = await response.json(); if (data.succes) { setProgress(100); updateStatus(`Enregistrement "${nom}" ajouté ! Lancement de la vidéo...`, 'done'); // Mise à jour du cadre vidéo : le lecteur va lire la vidéo analysée avec les landmarks setTimeout(() => { preview.src = `/video/${encodeURIComponent(nom)}?t=${Date.now()}`; preview.style.display = 'block'; placeholder.style.display = 'none'; const playVideo = () => { preview.play().catch(() => { preview.controls = true; }); }; if (preview.readyState >= 3) { playVideo(); } else { preview.addEventListener('canplay', playVideo, { once: true }); } nameInput.value = ''; selectedFile = null; chargerBibliotheque(); // Bibliothèque rafraichieavec le nouvel enregistrement }, 500); } else { setProgress(0); updateStatus(`Erreur : ${data.message}`, 'error'); } } catch (err) { setProgress(0); updateStatus(`Erreur: ${err.message}`, 'error'); } finally { runBtn.disabled = false; } }); }); // Bibliothèque sur le côté droit // Récupère la liste des vidéos enregistrées sur le serveur pour ajouter des éléments li HTML async function chargerBibliotheque() { try { const response = await fetch('/liste'); if (!response.ok) throw new Error(); const data = await response.json(); const liste = document.getElementById('library-list'); liste.innerHTML = ''; if (data && data.noms) { data.noms.forEach((nom, i) => { const item = document.createElement('li'); item.className = 'library-item'; item.innerHTML = ` ${String(i + 1).padStart(2, '0')} ${nom} `; liste.appendChild(item); }); } } catch (err) { console.error("Erreur de chargement de la bibliothèque", err); } } // Jouer la vidéo qu'on veut sur le robot async function playRecording(nom, btn) { document.querySelectorAll('.library-item').forEach(el => el.classList.remove('playing')); document.querySelectorAll('.btn-play').forEach(b => { b.classList.remove('active'); b.textContent = 'play'; }); btn.classList.add('active'); btn.textContent = '...'; btn.closest('.library-item').classList.add('playing'); updateStatus(`Chargement pour : ${nom}`, 'active'); setProgress(50); const preview = document.getElementById('video-preview'); const placeholder = document.getElementById('video-placeholder'); preview.pause(); preview.removeAttribute('src'); preview.load(); // Le serveur prépare le robot avec le fichier qu'on veut try { const simResp = await fetch('/play', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nom }) }); const simData = await simResp.json(); if (!simData.succes) { setProgress(0); updateStatus(`Erreur simulation : ${simData.message}`, 'error'); btn.classList.remove('active'); btn.textContent = 'play'; btn.closest('.library-item').classList.remove('playing'); return; } } catch (err) { setProgress(0); updateStatus(`Erreur : ${err.message}`, 'error'); btn.classList.remove('active'); btn.textContent = 'play'; btn.closest('.library-item').classList.remove('playing'); return; } updateStatus(`En attente du signal robot...`, 'active'); // Toutes les 250ms on demande au serveur si il y'a un Reachy, ça va permettre d'avoir un rejeu de Reachy synchronisée avec le lancement de la vidéo const POLL_INTERVAL = 250; const POLL_TIMEOUT = 60000; const pollStart = Date.now(); try { await new Promise((resolve, reject) => { const poll = async () => { if (Date.now() - pollStart > POLL_TIMEOUT) { return reject(new Error("Timeout : le robot n'a pas répondu dans les 60s")); } try { const r = await fetch('/is-ready'); const d = await r.json(); const elapsed = ((Date.now() - pollStart) / 1000).toFixed(1); updateStatus(`En attente du signal robot… ${elapsed}s`, 'active'); if (d.ready) resolve(); // le robot est pret else setTimeout(poll, POLL_INTERVAL); // Sinon on lui redemande } catch (e) { setTimeout(poll, POLL_INTERVAL); } }; poll(); }); } catch (err) { setProgress(0); updateStatus(`Erreur : ${err.message}`, 'error'); btn.classList.remove('active'); btn.textContent = 'play'; btn.closest('.library-item').classList.remove('playing'); return; } // Le robot est pret donc on lance la vidéo landmarkée dans le navigateur preview.src = `/video/${encodeURIComponent(nom)}`; preview.style.display = 'block'; placeholder.style.display = 'none'; await new Promise((resolve, reject) => { preview.addEventListener('canplay', resolve, { once: true }); preview.addEventListener('error', () => reject(new Error('Erreur chargement vidéo')), { once: true }); preview.load(); }); preview.play().catch(e => { preview.controls = true; }); setProgress(100); updateStatus(`Lecture en cours...`, 'done'); // Quand la vidéo est finie, le robot s'arrête preview.onended = () => { btn.classList.remove('active'); btn.textContent = 'play'; btn.closest('.library-item').classList.remove('playing'); updateStatus(`Séquence "${nom}" terminée`, 'done'); fetch('/stop', { method: 'POST' }).catch(() => {}); }; } // Infos et barre de progression sur l'avancement de l'action qu'on effectue, function updateStatus(msg, type) { const el = document.getElementById('status-line'); if (el) { el.textContent = msg; el.className = `status-line ${type}`; } } function setProgress(pct) { const bar = document.getElementById('progress-bar'); if (bar) { bar.style.width = `${pct}%`; if (pct === 100) setTimeout(() => { bar.style.width = '0%'; }, 1200); } } // Supprimer un enregistrement de la bibliothèque async function deleteRecording(nom) { const preview = document.getElementById('video-preview'); const placeholder = document.getElementById('video-placeholder'); if (preview.src.includes(encodeURIComponent(nom))) { preview.pause(); preview.src = ''; preview.load(); preview.style.display = 'none'; placeholder.style.display = 'block'; } await new Promise(r => setTimeout(r, 300)); try { const response = await fetch('/supprimer', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ nom }) }); const data = await response.json(); if (data.succes) { updateStatus(`"${nom}" supprimé.`, 'done'); chargerBibliotheque(); // Refresh de la bibliothèque } else { updateStatus(`Erreur suppression : ${data.message}`, 'error'); } } catch (err) { updateStatus(`Erreur : ${err.message}`, 'error'); } }