diff --git a/functions/src/music.js b/functions/src/music.js index 1e0f409..f4e33b1 100644 --- a/functions/src/music.js +++ b/functions/src/music.js @@ -201,7 +201,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => { "Content-Type": "application/json", Authorization: `Bearer ${SUNO_API_KEY}`, }, - }, + } ); const parsed = response.data; @@ -220,7 +220,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => { } catch (error) { console.error("❌ Erreur lors de la génération de musique:", error); throw new Error( - `Erreur lors de la génération de musique: ${error.message}`, + `Erreur lors de la génération de musique: ${error.message}` ); } }); @@ -249,7 +249,7 @@ exports.getSunoStatus = onCall(async ({ data = {} }) => { Authorization: `Bearer ${SUNO_API_KEY}`, }, timeout: 30000, // 30 secondes de timeout - }, + } ); parsed = response.data; @@ -365,7 +365,7 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => { Authorization: `Bearer ${SUNO_API_KEY}`, }, timeout: 30000, - }, + } ); parsed = response.data; console.log("📊 Réponse timestamps Suno API:", parsed?.code, parsed?.msg); @@ -417,7 +417,7 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => { }, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }, - { merge: true }, + { merge: true } ); saved = true; console.log("💾 Timestamps sauvegardés dans Firestore", { @@ -427,7 +427,7 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => { } catch (e) { console.error( "❌ Échec sauvegarde timestamps Firestore:", - e?.message || e, + e?.message || e ); } @@ -453,6 +453,8 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => { } }); +// Removed Firestore onUpdate trigger for timestamps: using callable instead. + /** * Cloud Function pour recevoir les callbacks de l'API Suno * Cette fonction est appelée par l'API Suno lorsque la génération @@ -533,7 +535,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => { const audioUrls = tracks .map( (t) => - t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl, + t.audio_url || t.audioUrl || t.stream_audio_url || t.streamAudioUrl ) .filter(Boolean) .slice(0, 2); @@ -548,7 +550,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => { has_audio_url: !!t.audio_url, has_stream_audio_url: !!t.stream_audio_url, })), - }, + } ); } @@ -573,14 +575,14 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => { }, }); const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent( - path, + path )}?alt=media&token=${token}`; console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl); return { path, url: downloadUrl }; } catch (e) { console.error( `❌ [SunoCallback] Échec save piste ${index + 1}:`, - e.message, + e.message ); return null; } @@ -600,7 +602,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => { musicUrls, updatedAt: admin.firestore.FieldValue.serverTimestamp(), }, - { merge: true }, + { merge: true } ); console.log("🏷️ [SunoCallback] Projet marqué GENERATED", { projectId, diff --git a/src/screens/Playback/Playback.js b/src/screens/Playback/Playback.js index c785460..2930440 100644 --- a/src/screens/Playback/Playback.js +++ b/src/screens/Playback/Playback.js @@ -1,11 +1,9 @@ import React, { useCallback, useState } from "react"; -import { Alert, Image, StyleSheet, View } from "react-native"; -import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; +import { Image, StyleSheet, View } from "react-native"; import { ai, background } from "../../assets"; import BorderGradientButton from "../../components/BorderGradientButton"; import GradientButton from "../../components/GradientButton"; import MusicLandHeader from "../../components/MusicLandHeader"; -import firebase from "../../config/firebase"; import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { goBack, navigate } from "../../navigation/NavigationService"; @@ -14,52 +12,13 @@ import FullscreenIntroVideo from "../../components/FullscreenIntroVideo"; const Playback = ({ route }) => { const { project } = route.params; - const projectId = project?.id; - const songIndex = project?.songIndex; - const sunoTaskId = project?.sunoTaskId; - const { setIsLoading } = useMinuit(); const [showIntro, setShowIntro] = useState(false); - const onPressRecord = useCallback(async () => { - try { - if (!projectId || songIndex === undefined || !sunoTaskId) { - return; - // return navigate(Routes.RecordPlayback, { project }); - } - if (project?.musicTimestamps?.[songIndex]) { - console.log("exists"); - return navigate(Routes.RecordPlayback, { project }); - } - - await setIsLoading(true); - const callable = firebase - .functions() - .httpsCallable("music-getSunoTimestamps"); - const { data } = await callable({ - taskId: sunoTaskId, - musicIndex: Number(songIndex) || 0, - projectId: projectId, - }); - - if (!data?.success) { - console.log("getSunoTimestamps failed", data?.error || data); - Alert.alert( - "Timestamps", - "Impossible de récupérer les timestamps pour ce playback pour le moment. Tu peux quand même continuer.", - ); - } - - await setIsLoading(false); - navigate(Routes.RecordPlayback, { project }); - } catch (e) { - console.log("getSunoTimestamps error", e?.message || e); - Alert.alert( - "Timestamps", - "Une erreur est survenue lors de la génération des timestamps. Tu peux quand même continuer.", - ); - await setIsLoading(false); - } - }, [projectId, songIndex, sunoTaskId, project, setIsLoading]); + const onPressRecord = useCallback(() => { + // Les timestamps sont désormais générés automatiquement côté serveur + // lors de la mise à jour du document (si songUrl existe). + navigate(Routes.RecordPlayback, { project }); + }, [project]); return ( diff --git a/src/screens/Studio/GeneratingSong.js b/src/screens/Studio/GeneratingSong.js index 49dad27..ab7140a 100644 --- a/src/screens/Studio/GeneratingSong.js +++ b/src/screens/Studio/GeneratingSong.js @@ -1,22 +1,22 @@ +import { useIsFocused } from "@react-navigation/native"; +import { BlurView } from "expo-blur"; +import moment from "moment"; import React, { useEffect, useMemo, useRef, useState } from "react"; import { Alert, Image, Platform, Text, View } from "react-native"; -import Page from "../../layouts/Page"; -import { ai, background } from "../../assets"; -import MusicLandHeader from "../../components/MusicLandHeader"; -import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; -import { BlurView } from "expo-blur"; -import { Palette } from "../../styles"; -import ProgressBar from "../../components/ProgressBar"; -import GradientButton from "../../components/GradientButton"; -import { goBack, navigate } from "../../navigation/NavigationService"; -import { Routes } from "../../navigation"; -import firebase, { projectsRef } from "../../config/firebase"; import useMinuit from "react-native-minuit/src/hooks/useMinuit.js"; -import moment from "moment"; import { responsiveHeight } from "react-native-responsive-dimensions"; -import { FONT_FAMILY } from "../../styles/Fonts"; +import { ai, background } from "../../assets"; +import GradientButton from "../../components/GradientButton"; +import MusicLandHeader from "../../components/MusicLandHeader"; +import ProgressBar from "../../components/ProgressBar"; +import firebase, { projectsRef } from "../../config/firebase"; +import Page from "../../layouts/Page"; +import { Routes } from "../../navigation"; +import { navigate } from "../../navigation/NavigationService"; import { useUser } from "../../providers/UserDataProvider"; -import { useIsFocused } from "@react-navigation/native"; +import { Palette } from "../../styles"; +import { FONT_FAMILY } from "../../styles/Fonts"; +import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; const GeneratingSong = () => { const { selectedProjectId, selectedProject } = useUser(); @@ -146,22 +146,23 @@ const GeneratingSong = () => { if (canAsk && !askedRef.current) { askedRef.current = true; - Alert.alert( - "Attention", - "Une génération va être lancée. Continuer ?", - [ - { - text: "Non", - style: "cancel", - }, - { - text: "Oui", - onPress: () => startMusicGeneration(), - }, - ], - ); + Alert.alert("Attention", "Une génération va être lancée. Continuer ?", [ + { + text: "Non", + style: "cancel", + }, + { + text: "Oui", + onPress: () => startMusicGeneration(), + }, + ]); } - }, [isFocused, selectedProject?.title, selectedProject?.musicStatus, effectiveConfig]); + }, [ + isFocused, + selectedProject?.title, + selectedProject?.musicStatus, + effectiveConfig, + ]); return ( diff --git a/src/screens/Studio/SongReady.js b/src/screens/Studio/SongReady.js index eeaadea..30d4393 100644 --- a/src/screens/Studio/SongReady.js +++ b/src/screens/Studio/SongReady.js @@ -1,6 +1,7 @@ +import { useFocusEffect } from "@react-navigation/native"; import { useAudioPlayer } from "expo-audio"; import { BlurView } from "expo-blur"; -import React, { useEffect, useRef, useState, useCallback } from "react"; +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"; @@ -10,14 +11,13 @@ import ValidateModal from "../../components/modal/ValidateModal"; import MusicLandHeader from "../../components/MusicLandHeader"; import Slider from "../../components/Slider"; import firebase from "../../config/firebase"; -import { useUser } from "../../providers/UserDataProvider"; import Page from "../../layouts/Page"; import { Routes } from "../../navigation"; import { navigate } from "../../navigation/NavigationService"; +import { useUser } from "../../providers/UserDataProvider"; import { Style } from "../../styles"; import { gutters, size } from "../../styles/Style"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; -import { useFocusEffect } from "@react-navigation/native"; const SongReady = () => { const { @@ -34,10 +34,10 @@ const SongReady = () => { 1: { pos: 0, dur: 0 }, }); const player0 = useAudioPlayer( - musicUrls[0] ? { uri: musicUrls[0] } : undefined, + musicUrls[0] ? { uri: musicUrls[0] } : undefined ); const player1 = useAudioPlayer( - musicUrls[1] ? { uri: musicUrls[1] } : undefined, + musicUrls[1] ? { uri: musicUrls[1] } : undefined ); const wasPlayingBeforeSeek = useRef({ 0: false, 1: false }); @@ -113,12 +113,33 @@ const SongReady = () => { const validateSelection = async () => { try { const url = musicUrls[selectedIndex]; + if (!projectId || !url) return; await updateProjectData({ songIndex: selectedIndex, songUrl: url, updatedAt: firebase.firestore.FieldValue.serverTimestamp(), }); + // Déclenche la génération des timestamps immédiatement après avoir fixé la songUrl + try { + console.log("task id is : ", selectedProject?.sunoTaskId); + const taskId = selectedProject?.sunoTaskId || null; + if (taskId) { + const callable = firebase + .functions() + .httpsCallable("music-getSunoTimestamps"); + // Fire-and-forget (ne bloque pas l'UI) + callable({ + taskId, + musicIndex: Number(selectedIndex) || 0, + projectId, + }).catch((e) => + console.log("getSunoTimestamps (non-blocking)", e?.message) + ); + } + } catch (e) { + console.log("getSunoTimestamps init error", e?.message); + } await player0?.pause?.(); await player1?.pause?.(); navigate(Routes.FlowSelection); @@ -136,7 +157,7 @@ const SongReady = () => { player1?.pause?.(); } catch {} }; - }, [player0, player1]), + }, [player0, player1]) ); useEffect(() => { @@ -220,7 +241,7 @@ const SongReady = () => { } catch (e) { console.log( "SongReady pause on seek start", - e?.message, + e?.message ); } }}