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');
}
}