import React, { useCallback, useEffect, useMemo, useRef, useState, } from "react"; import { Image, Pressable, Text, View } from "react-native"; import { background, icons } from "../../assets"; import BorderGradientButton from "../../components/BorderGradientButton"; import GradientButton from "../../components/GradientButton"; import MusicLandHeader from "../../components/MusicLandHeader"; import Slider from "../../components/Slider"; import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { goBack, navigate } from "../../navigation/NavigationService"; import { gutters, Palette } from "../../styles"; import { isWeb } from "../../hooks/useLayoutType"; import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer"; import { releaseBlobUrl } from "../../utils/blobUrlCache"; const fmtSeconds = (s) => { const total = Math.max(0, Math.floor(Number(s || 0))); const m = Math.floor(total / 60).toString(); const sec = (total % 60).toString().padStart(2, "0"); return `${m}:${sec}`; }; const toSeconds = (value) => { const n = Number(value ?? 0); if (!Number.isFinite(n) || n < 0) return 0; return n > 10000 ? n / 1000 : n; // heuristique ms → s }; const WEB_PREVIEW_WIDTH = 360; const RecordedPlayback = ({ route }) => { const { videoUri, project } = route.params || {}; const songUrl = project?.songUrl || null; // AUDIO PLAYER (expo-audio → seconds) const audioPlayer = useSharedAudioPlayer( songUrl ? { uri: songUrl } : undefined, { id: project?.id ? `recorded-${project.id}` : songUrl ? `recorded-${songUrl}` : undefined, title: typeof project?.title === "string" ? project.title : "Sans titre", artist: typeof project?.userName === "string" ? project.userName : "MusicLand", artwork: project?.coverUrl || null, coverUrl: project?.coverUrl || null, metadata: { projectId: project?.id, screen: "RecordedPlayback" }, }, ); // Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source const videoElRef = useRef(null); const playbackEndedRef = useRef(false); const shouldPreserveBlobRef = useRef(false); const [progress, setProgress] = useState({ posS: 0, // secondes durS: 0, // secondes playing: false, }); const stopPlayback = useCallback(async () => { try { if (audioPlayer?.playing) await audioPlayer.pause?.(); } catch {} try { if (videoElRef.current && !videoElRef.current.paused) { videoElRef.current.pause(); } } catch {} }, [audioPlayer]); useEffect(() => { return () => { if (!shouldPreserveBlobRef.current) { releaseBlobUrl(videoUri || null); } }; }, [videoUri]); // Démarrage / arrêt useEffect(() => { playbackEndedRef.current = false; const start = async () => { try { if (audioPlayer && songUrl) { await audioPlayer.play?.(); } if (videoElRef.current && videoUri) { // Lecture vidéo HTML5 (muet pour éviter les policies) videoElRef.current.muted = true; videoElRef.current.play().catch(() => {}); } } catch {} }; start(); return () => { playbackEndedRef.current = false; void stopPlayback(); }; }, [audioPlayer, songUrl, stopPlayback, videoUri]); // Boucle de progression + éventuelle sync de la vidéo si fournie useEffect(() => { const id = setInterval(() => { try { const durS = toSeconds(audioPlayer?.duration); // secondes const posS = toSeconds(audioPlayer?.currentTime); // secondes setProgress({ posS, durS, playing: !!audioPlayer?.playing, }); // Sync vidéo si on a une source vidéo if ( videoElRef.current && videoUri && !Number.isNaN(videoElRef.current.currentTime) ) { const v = Number(videoElRef.current.currentTime || 0); const drift = Math.abs(v - posS); if (drift > 0.35) { videoElRef.current.currentTime = Math.max(0, posS); } } } catch {} }, 250); return () => clearInterval(id); }, [audioPlayer, videoUri]); // Slider: ratio 0..1 const sliderProgress = useMemo(() => { return progress.durS > 0 ? Math.min(1, progress.posS / progress.durS) : 0; }, [progress]); useEffect(() => { const duration = progress?.durS || 0; if (!duration) return; const position = progress?.posS || 0; if (playbackEndedRef.current && duration - position > 1) { playbackEndedRef.current = false; return; } const remaining = Math.max(0, duration - position); if (remaining <= 0.35 && !playbackEndedRef.current) { playbackEndedRef.current = true; void stopPlayback(); } }, [progress, stopPlayback]); const onSeek = async (ratio) => { try { const durS = Number(progress.durS || 0); const target = durS * ratio; // secondes if (audioPlayer && durS > 0) { await audioPlayer.seekTo?.(Math.floor(target)); // seek en secondes } if (videoElRef.current && videoUri) { videoElRef.current.currentTime = Math.max(0, target); } } catch {} }; const wasPlayingRef = useRef(false); const isSeekingRef = useRef(false); const onSeekStart = useCallback(async () => { try { if (isSeekingRef.current) return; isSeekingRef.current = true; wasPlayingRef.current = !!audioPlayer?.playing; playbackEndedRef.current = false; if (audioPlayer?.playing) await audioPlayer.pause?.(); if (videoElRef.current && !videoElRef.current.paused) { videoElRef.current.pause(); } } catch {} }, [audioPlayer]); const onSeekEnd = useCallback(async () => { try { if (!isSeekingRef.current) return; isSeekingRef.current = false; if (wasPlayingRef.current) { if (audioPlayer) await audioPlayer.play?.(); if (videoElRef.current && videoUri) videoElRef.current.play().catch(() => {}); } } catch {} }, [audioPlayer, videoUri]); const handleTogglePlayback = useCallback(async () => { try { const duration = Number(progress?.durS || 0); const position = Number(progress?.posS || 0); const isAtEnd = duration > 0 && duration - position < 0.35; const isCurrentlyPlaying = !!audioPlayer?.playing; if (isCurrentlyPlaying) { playbackEndedRef.current = false; await stopPlayback(); return; } if (isAtEnd) { if (audioPlayer) await audioPlayer.seekTo?.(0); if (videoElRef.current && videoUri) { videoElRef.current.currentTime = 0; } } playbackEndedRef.current = false; if (songUrl && audioPlayer) { await audioPlayer.play?.(); } if (videoElRef.current && videoUri) { videoElRef.current.muted = true; videoElRef.current.play().catch(() => {}); } } catch {} }, [audioPlayer, songUrl, stopPlayback, progress, videoUri]); return ( {/* Bloc vidéo optionnel si jamais tu as un videoUri sur web */} {videoUri ? ( ) : ( // Placeholder quand pas de vidéo sur web Aperçu vidéo non disponible sur le web )} { shouldPreserveBlobRef.current = true; navigate(Routes.PlaybackDownload, { action: "playback", uri: videoUri || null, // peut être null sur web project, }); }} /> { try { await stopPlayback(); } catch {} navigate(Routes.RecordPlayback, { project }); }} /> ); }; export default RecordedPlayback;