/* global setInterval, clearInterval */ import { useFocusEffect } from "@react-navigation/native"; import { BlurView } from "expo-blur"; import React, { useCallback, useEffect, useRef, useState } from "react"; import { Image, Platform, Pressable, Text, View } from "react-native"; import { responsiveHeight } from "react-native-responsive-dimensions"; import { background, icons } from "../../assets"; import alert from "../../components/Alert"; import GradientButton from "../../components/GradientButton"; import ValidateModal from "../../components/modal/ValidateModal"; import MusicLandHeader from "../../components/MusicLandHeader"; import Slider from "../../components/Slider"; import { isWeb } from "../../hooks/useLayoutType"; import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer"; import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { navigate } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; import { Palette, Style } from "../../styles"; import { gutters, size } from "../../styles/Style"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; const SongReady = () => { const { selectedProjectId: projectId, selectedProject, updateProjectData, updateUserData, } = useUser(); const [showValidateModal, setShowValidateModal] = useState(false); const [musicUrls, setMusicUrls] = useState([]); const [selectedIndex, setSelectedIndex] = useState(0); const [isPlaying, setIsPlaying] = useState({ 0: false, 1: false }); const [progressInfo, setProgressInfo] = useState({ 0: { pos: 0, dur: 0 }, 1: { pos: 0, dur: 0 }, }); const player0 = useSharedAudioPlayer( musicUrls[0] ? { uri: musicUrls[0] } : undefined, { id: musicUrls[0] ? `songready-${musicUrls[0]}` : undefined, title: (Array.isArray(selectedProject?.musicTitles) ? selectedProject?.musicTitles?.[0] : null) || selectedProject?.title || "Option 1", artwork: selectedProject?.coverUrl || null, coverUrl: selectedProject?.coverUrl || null, metadata: { index: 0, projectId }, } ); const player1 = useSharedAudioPlayer( musicUrls[1] ? { uri: musicUrls[1] } : undefined, { id: musicUrls[1] ? `songready-${musicUrls[1]}` : undefined, title: (Array.isArray(selectedProject?.musicTitles) ? selectedProject?.musicTitles?.[1] : null) || selectedProject?.title || "Option 2", artwork: selectedProject?.coverUrl || null, coverUrl: selectedProject?.coverUrl || null, metadata: { index: 1, projectId }, } ); const wasPlayingBeforeSeek = useRef({ 0: false, 1: false }); // Sync URLs from provider's selectedProject useEffect(() => { const urls = Array.isArray(selectedProject?.musicUrls) ? selectedProject.musicUrls.slice(0, 2) : []; setMusicUrls(urls); }, [selectedProject?.musicUrls]); // Sync progression depuis les players useEffect(() => { // Ensure we consistently work with milliseconds whatever the platform const toMs = (t) => { const n = Number(t || 0); if (!isFinite(n) || n <= 0) return 0; return n >= 1000 ? n : n * 1000; }; const id = setInterval(() => { const d0 = toMs(player0?.duration); const p0 = toMs(player0?.currentTime); const d1 = toMs(player1?.duration); const p1 = toMs(player1?.currentTime); setProgressInfo({ 0: { pos: p0, dur: d0 }, 1: { pos: p1, dur: d1 } }); setIsPlaying({ 0: !!player0?.playing, 1: !!player1?.playing }); }, 300); return () => clearInterval(id); }, [player0, player1]); const togglePlay = async (idx) => { setSelectedIndex(idx); const url = musicUrls[idx]; if (!url) return; try { // Pause l'autre piste si elle joue const other = idx === 0 ? 1 : 0; if (isPlaying[other]) { if (other === 0) await player0?.pause?.(); else await player1?.pause?.(); setIsPlaying((p) => ({ ...p, [other]: false })); } const player = idx === 0 ? player0 : player1; if (!player) return; if (player.playing) { await player.pause?.(); setIsPlaying((p) => ({ ...p, [idx]: false })); } else { await player.play?.(); setIsPlaying((p) => ({ ...p, [idx]: true })); } } catch (e) { console.log("Audio error", e?.message); } }; const fmt = (ms) => { const total = Math.max(0, Math.floor((ms || 0) / 1000)); const m = Math.floor(total / 60) .toString() .padStart(1, "0"); const s = (total % 60).toString().padStart(2, "0"); return `${m}:${s}`; }; const onSeek = async (idx, ratio) => { try { const info = progressInfo[idx] || {}; const dur = info.dur || 0; const pos = Math.floor(dur * ratio); const player = idx === 0 ? player0 : player1; if (player && dur > 0) { // seekTo expects seconds await player.seekTo?.(Math.floor((pos || 0) / 1000)); } } catch (e) { console.log("Seek error", e?.message); } }; const validateSelection = async () => { try { const url = musicUrls[selectedIndex]; if (!projectId || !url) return; await updateProjectData({ songIndex: selectedIndex, songUrl: url, cover: null, coverUrl: null, coverStatus: null, playbackUrl: null, playbackStatus: null, playbackGenerating: null, }); // Incrémenter le compteur de chansons générées const currentGeneratedSongs = selectedProject?.user?.generatedSongs || 0; await updateUserData({ data: { generatedSongs: currentGeneratedSongs + 1, }, shouldSetTooltip: false, }); await player0?.pause?.(); await player1?.pause?.(); navigate(Routes.ChooseCoverType); } catch (e) { console.log("Validate error", e?.message); } }; // Pause audio when screen loses focus (navigate/reset) and on unmount useFocusEffect( useCallback(() => { return () => { try { player0?.pause?.(); player1?.pause?.(); } catch {} }; }, [player0, player1]) ); useEffect(() => { return () => { try { player0?.pause?.(); player1?.pause?.(); } catch {} }; }, [player0, player1]); const handleChooseTrack = () => { if (isWeb) { alert( "Attention !", "Lorsque tu clique sur valider, tu ne pourra plus changer ni le texte ni la mélodie.", [ { text: "Retour", style: "cancel", }, { text: "Valider", style: "confirm", onPress: () => { validateSelection(); }, }, ], { cancelable: false } ); return; } setShowValidateModal(true); }; return ( { try { await player0?.pause?.(); await player1?.pause?.(); } catch {} navigate(Routes.Home); }} progress={63} /> {[0, 1].map((idx) => { const isSelected = selectedIndex === idx; return ( setSelectedIndex(idx)} style={({ pressed }) => [ { borderRadius: 20, borderWidth: isSelected ? 2 : 1, borderColor: isSelected ? Palette.primary : Palette.ultraLightWhite, overflow: "hidden", }, pressed && { opacity: 0.96 }, ]} > togglePlay(idx)} style={{ ...Style.containerCenter, ...size({ size: 48 }), }} > {`Morceau ${idx + 1}`} { setSelectedIndex(idx); try { const player = idx === 0 ? player0 : player1; wasPlayingBeforeSeek.current[idx] = !!player?.playing; if (player?.playing) { await player.pause?.(); setIsPlaying((p) => ({ ...p, [idx]: false })); } } catch (e) { console.log( "SongReady pause on seek start", e?.message ); } }} onSeek={(ratio) => onSeek(idx, ratio)} onSeekEnd={async () => { try { const player = idx === 0 ? player0 : player1; if (player && wasPlayingBeforeSeek.current[idx]) { await player.play?.(); setIsPlaying((p) => ({ ...p, [idx]: true })); } wasPlayingBeforeSeek.current[idx] = false; } catch (e) { console.log( "SongReady resume after seek", e?.message ); } }} /> setSelectedIndex(idx)} style={{ ...Style.containerCenter, ...size({ size: 24 }), }} > {isSelected && ( )} ); })} {!isWeb && ( setShowValidateModal(false)} onPressValidate={validateSelection} /> )} ); }; export default SongReady;