/* Module capture photo - Preview live, compte a rebours, declenchement */ let previewActif = false; let previewTimeoutId = null; const PREVIEW_TIMEOUT_MS = 3000; const EMOJI_CAR = { 3: '🤪', 2: '😱', 1: '🔥' }; // --- Preview live --- function afficherErreurCapture(titre, detail = '') { arreterPreview(); const el = document.getElementById('preview-erreur-camera'); if (!el) { allerA('accueil'); return; } el.querySelector('p').textContent = titre; el.querySelector('span').textContent = detail || 'Retour dans 4 secondes...'; el.classList.remove('cache'); setTimeout(() => { el.classList.add('cache'); allerA('accueil'); }, 4000); } function afficherErreurPreview(visible) { const el = document.getElementById('preview-erreur-camera'); if (!el) return; if (visible) { el.querySelector('p').textContent = 'Appareil photo deconnecte'; el.querySelector('span').textContent = 'Reconnexion en cours...'; el.classList.remove('cache'); } else { el.classList.add('cache'); } } function resetPreviewTimeout() { if (previewTimeoutId) clearTimeout(previewTimeoutId); previewTimeoutId = setTimeout(() => { if (previewActif) afficherErreurPreview(true); }, PREVIEW_TIMEOUT_MS); } function lancerPreview() { if (previewActif) return; previewActif = true; afficherErreurPreview(false); wsEnvoyer({ type: 'preview_start' }); resetPreviewTimeout(); } function arreterPreview() { if (!previewActif) return; previewActif = false; wsEnvoyer({ type: 'preview_stop' }); if (previewInterval) { clearInterval(previewInterval); previewInterval = null; } if (previewTimeoutId) { clearTimeout(previewTimeoutId); previewTimeoutId = null; } afficherErreurPreview(false); } wsOnMessage('preview', (msg) => { if (!previewActif) return; const img = document.getElementById('img-preview'); if (img) img.src = msg.image; afficherErreurPreview(false); resetPreviewTimeout(); }); // Erreur camera pendant la preview wsOnMessage('camera_erreur', () => { if (previewActif) afficherErreurPreview(true); }); wsOnMessage('camera_ok', () => { afficherErreurPreview(false); }); // --- Capture --- async function lancerCapture() { photosSession = []; photoImpression = null; document.getElementById('photo-resultat').src = ''; document.getElementById('photo-partage').src = ''; majCompteurAccueil(); // Verifier l'etat de la camera avant de commencer try { const statut = await apiGet('/api/camera/statut'); if (!statut.connectee || statut.mode === 'erreur') { afficherErreurCapture('Appareil photo non disponible', 'Verifiez la connexion USB du DSLR'); return; } } catch (e) { afficherErreurCapture('Impossible de joindre le serveur', 'Verifiez que le photobooth est bien demarre'); return; } const nbPhotos = getNombrePhotos(); const compteurEl = document.getElementById('capture-compteur'); for (let i = 0; i < nbPhotos; i++) { if (nbPhotos > 1) { compteurEl.textContent = `Photo ${i + 1} / ${nbPhotos}`; } lancerPreview(); // Miroir live pendant le compte à rebours await compteARebours(); arreterPreview(); // Libère le bus USB gphoto2 pour la capture afficherFlash(); const promesseCapture = apiPost('/api/capturer').catch(() => null); let resultat = await promesseCapture; let erreurReseau = false; if (!resultat) { erreurReseau = true; } else if (resultat.erreur === 'Limite de photos atteinte') { afficherErreurCapture('Limite de photos atteinte', 'La session est terminee'); return; } if (erreurReseau) { afficherErreurCapture('Erreur reseau', 'Le serveur ne repond pas — redemarrez le photobooth'); return; } if (!resultat || resultat.erreur) { const msg = resultat?.erreur || 'Erreur inconnue'; afficherErreurCapture('Echec de la capture', msg.includes('Echec') ? 'Verifiez le DSLR et reessayez' : msg); return; } if (resultat.nom) { photosSession.push(resultat.nom); } } await traiterCapture(); } function getNombrePhotos() { if (modeActuel === 'simple') return 1; if (modeActuel === 'multi') return config.multi_shot?.nombre_photos || 4; return 1; } function choisirAnimationAleatoire() { // Construire la liste de toutes les animations actives (CSS + custom) const cssActives = config.camera?.animations_css_actives || ['classique']; const customActives = config.animations_custom?.actives || []; const pool = [ ...cssActives.map(id => ({ type: 'css', id })), ...customActives.map(nom => ({ type: 'custom', nom })), ]; if (pool.length === 0) return { type: 'css', id: 'classique' }; return pool[Math.floor(Math.random() * pool.length)]; } async function compteARebours() { const conteneur = document.getElementById('compte-a-rebours'); const chiffre = document.getElementById('chiffre-car'); const duree = config.camera?.compte_a_rebours || 3; // Choisir une animation au hasard parmi les actives const anim = choisirAnimationAleatoire(); if (anim.type === 'custom') { // Jouer la video/gif en plein ecran, shoot des qu'elle finit await jouerAnimationCustom(anim.nom); return; // pas de compte a rebours CSS } else { // Animation CSS classique conteneur.classList.remove('cache'); for (let i = duree; i > 0; i--) { if (anim.id === 'emoji') { chiffre.textContent = EMOJI_CAR[i] || i; } else { chiffre.textContent = i; } chiffre.className = 'anim-chiffre'; void chiffre.offsetWidth; chiffre.classList.add('anim-' + anim.id); await pause(1000); } conteneur.classList.add('cache'); } } async function jouerAnimationCustom(nom) { // Creer un overlay plein ecran pour la video/gif const overlay = document.createElement('div'); overlay.className = 'video-countdown-overlay'; const ext = nom.split('.').pop().toLowerCase(); if (ext === 'gif') { const img = document.createElement('img'); // Ajouter un timestamp pour forcer le rechargement (relancer le GIF depuis le debut) img.src = `/assets/animations/${nom}?t=${Date.now()}`; overlay.appendChild(img); document.body.appendChild(overlay); // Pour un GIF, on attend sa duree naturelle // Comme on ne peut pas detecter la fin d'un GIF, on utilise un fallback de 3s // L'utilisateur peut configurer la duree dans le nom du fichier (ex: countdown_5s.gif) const match = nom.match(/(\d+)s/); const dureeGif = match ? parseInt(match[1]) * 1000 : 3000; await pause(dureeGif); } else { // Video MP4/WebM : on attend la fin exacte de la video const video = document.createElement('video'); video.src = `/assets/animations/${nom}`; video.muted = true; video.playsInline = true; overlay.appendChild(video); document.body.appendChild(overlay); await new Promise(resolve => { video.onended = resolve; video.onerror = resolve; video.play().catch(resolve); }); } overlay.remove(); } function afficherFlash() { const flash = document.getElementById('flash-blanc'); flash.classList.remove('cache'); flash.style.animation = 'none'; flash.offsetHeight; flash.style.animation = 'flash 0.3s ease-out forwards'; setTimeout(() => flash.classList.add('cache'), 400); } let photoImpression = null; // Nom du fichier d'impression (2 bandes sur 10x15) async function traiterCapture() { if (photosSession.length === 0) { afficherErreurCapture('Aucune photo capturee', 'Une erreur inattendue s\'est produite'); return; } photoImpression = null; try { if (modeActuel === 'multi') { const mode = config.multi_shot?.mode || 'strip'; let resultat; if (mode === 'strip') { resultat = await apiPost('/api/strip', { photos: photosSession }); if (resultat.erreur) { afficherErreurCapture('Erreur creation de la planche', resultat.erreur); return; } if (resultat.impression) photoImpression = resultat.impression; } else { resultat = await apiPost('/api/collage', { photos: photosSession }); if (resultat.erreur) { afficherErreurCapture('Erreur creation du collage', resultat.erreur); return; } } if (!resultat.nom) { afficherErreurCapture('Erreur traitement des photos', 'Le fichier final n\'a pas ete genere'); return; } photoFinale = resultat.nom; afficherPreviewPhoto('/data/exports/' + resultat.nom); } else { photoFinale = photosSession[0]; afficherPreviewPhoto('/data/photos/' + photosSession[0]); } } catch (e) { afficherErreurCapture('Erreur reseau', 'Impossible de traiter les photos'); return; } allerA('preview'); } function afficherPreviewPhoto(chemin) { document.getElementById('photo-resultat').src = chemin; document.getElementById('photo-partage').src = chemin; afficherCadreOverlay(cadreChoisi); if (config.fonctionnalites?.filtres) { chargerFiltres(); } if (config.fonctionnalites?.overlays) { chargerOverlays(); } if (config.fonctionnalites?.chroma_key) { chargerFonds(); } } // --- Compteur accueil --- async function majCompteurAccueil() { const etat = await apiGet('/api/compteur'); const el = document.getElementById('compteur-accueil'); if (etat.actif) { el.classList.remove('cache'); document.getElementById('compteur-restant').textContent = etat.restantes; document.getElementById('compteur-limite').textContent = etat.limite; } else { el.classList.add('cache'); } } function pause(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }