music timestamps

This commit is contained in:
2025-09-05 12:20:33 +02:00
parent dd19172804
commit fdd667f092
4 changed files with 77 additions and 94 deletions
+13 -11
View File
@@ -201,7 +201,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
"Content-Type": "application/json", "Content-Type": "application/json",
Authorization: `Bearer ${SUNO_API_KEY}`, Authorization: `Bearer ${SUNO_API_KEY}`,
}, },
}, }
); );
const parsed = response.data; const parsed = response.data;
@@ -220,7 +220,7 @@ exports.generateMusic = onCall(async ({ data = {} }) => {
} catch (error) { } catch (error) {
console.error("❌ Erreur lors de la génération de musique:", error); console.error("❌ Erreur lors de la génération de musique:", error);
throw new 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}`, Authorization: `Bearer ${SUNO_API_KEY}`,
}, },
timeout: 30000, // 30 secondes de timeout timeout: 30000, // 30 secondes de timeout
}, }
); );
parsed = response.data; parsed = response.data;
@@ -365,7 +365,7 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => {
Authorization: `Bearer ${SUNO_API_KEY}`, Authorization: `Bearer ${SUNO_API_KEY}`,
}, },
timeout: 30000, timeout: 30000,
}, }
); );
parsed = response.data; parsed = response.data;
console.log("📊 Réponse timestamps Suno API:", parsed?.code, parsed?.msg); 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(), updatedAt: admin.firestore.FieldValue.serverTimestamp(),
}, },
{ merge: true }, { merge: true }
); );
saved = true; saved = true;
console.log("💾 Timestamps sauvegardés dans Firestore", { console.log("💾 Timestamps sauvegardés dans Firestore", {
@@ -427,7 +427,7 @@ exports.getSunoTimestamps = onCall(async ({ data = {} }) => {
} catch (e) { } catch (e) {
console.error( console.error(
"❌ Échec sauvegarde timestamps Firestore:", "❌ É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 * Cloud Function pour recevoir les callbacks de l'API Suno
* Cette fonction est appelée par l'API Suno lorsque la génération * 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 const audioUrls = tracks
.map( .map(
(t) => (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) .filter(Boolean)
.slice(0, 2); .slice(0, 2);
@@ -548,7 +550,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
has_audio_url: !!t.audio_url, has_audio_url: !!t.audio_url,
has_stream_audio_url: !!t.stream_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( const downloadUrl = `https://firebasestorage.googleapis.com/v0/b/${bucket.name}/o/${encodeURIComponent(
path, path
)}?alt=media&token=${token}`; )}?alt=media&token=${token}`;
console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl); console.log("✅ [SunoCallback] Sauvegardé:", path, "URL:", downloadUrl);
return { path, url: downloadUrl }; return { path, url: downloadUrl };
} catch (e) { } catch (e) {
console.error( console.error(
`❌ [SunoCallback] Échec save piste ${index + 1}:`, `❌ [SunoCallback] Échec save piste ${index + 1}:`,
e.message, e.message
); );
return null; return null;
} }
@@ -600,7 +602,7 @@ exports.sunoCallback = onRequest({ methods: ["POST"] }, async (req, res) => {
musicUrls, musicUrls,
updatedAt: admin.firestore.FieldValue.serverTimestamp(), updatedAt: admin.firestore.FieldValue.serverTimestamp(),
}, },
{ merge: true }, { merge: true }
); );
console.log("🏷️ [SunoCallback] Projet marqué GENERATED", { console.log("🏷️ [SunoCallback] Projet marqué GENERATED", {
projectId, projectId,
+5 -46
View File
@@ -1,11 +1,9 @@
import React, { useCallback, useState } from "react"; import React, { useCallback, useState } from "react";
import { Alert, Image, StyleSheet, View } from "react-native"; import { Image, StyleSheet, View } from "react-native";
import useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import { ai, background } from "../../assets"; import { ai, background } from "../../assets";
import BorderGradientButton from "../../components/BorderGradientButton"; import BorderGradientButton from "../../components/BorderGradientButton";
import GradientButton from "../../components/GradientButton"; import GradientButton from "../../components/GradientButton";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import firebase from "../../config/firebase";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService"; import { goBack, navigate } from "../../navigation/NavigationService";
@@ -14,52 +12,13 @@ import FullscreenIntroVideo from "../../components/FullscreenIntroVideo";
const Playback = ({ route }) => { const Playback = ({ route }) => {
const { project } = route.params; 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 [showIntro, setShowIntro] = useState(false);
const onPressRecord = useCallback(async () => { const onPressRecord = useCallback(() => {
try { // Les timestamps sont désormais générés automatiquement côté serveur
if (!projectId || songIndex === undefined || !sunoTaskId) { // lors de la mise à jour du document (si songUrl existe).
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 }); navigate(Routes.RecordPlayback, { project });
} catch (e) { }, [project]);
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]);
return ( return (
<Page headerType="NONE" backgroundImg={background.playbackBG2}> <Page headerType="NONE" backgroundImg={background.playbackBG2}>
+22 -21
View File
@@ -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 React, { useEffect, useMemo, useRef, useState } from "react";
import { Alert, Image, Platform, Text, View } from "react-native"; 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 useMinuit from "react-native-minuit/src/hooks/useMinuit.js";
import moment from "moment";
import { responsiveHeight } from "react-native-responsive-dimensions"; 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 { 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 GeneratingSong = () => {
const { selectedProjectId, selectedProject } = useUser(); const { selectedProjectId, selectedProject } = useUser();
@@ -146,10 +146,7 @@ const GeneratingSong = () => {
if (canAsk && !askedRef.current) { if (canAsk && !askedRef.current) {
askedRef.current = true; askedRef.current = true;
Alert.alert( Alert.alert("Attention", "Une génération va être lancée. Continuer ?", [
"Attention",
"Une génération va être lancée. Continuer ?",
[
{ {
text: "Non", text: "Non",
style: "cancel", style: "cancel",
@@ -158,10 +155,14 @@ const GeneratingSong = () => {
text: "Oui", text: "Oui",
onPress: () => startMusicGeneration(), onPress: () => startMusicGeneration(),
}, },
], ]);
);
} }
}, [isFocused, selectedProject?.title, selectedProject?.musicStatus, effectiveConfig]); }, [
isFocused,
selectedProject?.title,
selectedProject?.musicStatus,
effectiveConfig,
]);
return ( return (
<Page headerType="NONE" backgroundImg={background.studioBG2}> <Page headerType="NONE" backgroundImg={background.studioBG2}>
+28 -7
View File
@@ -1,6 +1,7 @@
import { useFocusEffect } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio"; import { useAudioPlayer } from "expo-audio";
import { BlurView } from "expo-blur"; 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 { Image, Platform, Pressable, Text, View } from "react-native";
import { responsiveHeight } from "react-native-responsive-dimensions"; import { responsiveHeight } from "react-native-responsive-dimensions";
import { background, icons } from "../../assets"; import { background, icons } from "../../assets";
@@ -10,14 +11,13 @@ import ValidateModal from "../../components/modal/ValidateModal";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import Slider from "../../components/Slider"; import Slider from "../../components/Slider";
import firebase from "../../config/firebase"; import firebase from "../../config/firebase";
import { useUser } from "../../providers/UserDataProvider";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
import { Routes } from "../../navigation"; import { Routes } from "../../navigation";
import { navigate } from "../../navigation/NavigationService"; import { navigate } from "../../navigation/NavigationService";
import { useUser } from "../../providers/UserDataProvider";
import { Style } from "../../styles"; import { Style } from "../../styles";
import { gutters, size } from "../../styles/Style"; import { gutters, size } from "../../styles/Style";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader"; import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
import { useFocusEffect } from "@react-navigation/native";
const SongReady = () => { const SongReady = () => {
const { const {
@@ -34,10 +34,10 @@ const SongReady = () => {
1: { pos: 0, dur: 0 }, 1: { pos: 0, dur: 0 },
}); });
const player0 = useAudioPlayer( const player0 = useAudioPlayer(
musicUrls[0] ? { uri: musicUrls[0] } : undefined, musicUrls[0] ? { uri: musicUrls[0] } : undefined
); );
const player1 = useAudioPlayer( const player1 = useAudioPlayer(
musicUrls[1] ? { uri: musicUrls[1] } : undefined, musicUrls[1] ? { uri: musicUrls[1] } : undefined
); );
const wasPlayingBeforeSeek = useRef({ 0: false, 1: false }); const wasPlayingBeforeSeek = useRef({ 0: false, 1: false });
@@ -113,12 +113,33 @@ const SongReady = () => {
const validateSelection = async () => { const validateSelection = async () => {
try { try {
const url = musicUrls[selectedIndex]; const url = musicUrls[selectedIndex];
if (!projectId || !url) return; if (!projectId || !url) return;
await updateProjectData({ await updateProjectData({
songIndex: selectedIndex, songIndex: selectedIndex,
songUrl: url, songUrl: url,
updatedAt: firebase.firestore.FieldValue.serverTimestamp(), 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 player0?.pause?.();
await player1?.pause?.(); await player1?.pause?.();
navigate(Routes.FlowSelection); navigate(Routes.FlowSelection);
@@ -136,7 +157,7 @@ const SongReady = () => {
player1?.pause?.(); player1?.pause?.();
} catch {} } catch {}
}; };
}, [player0, player1]), }, [player0, player1])
); );
useEffect(() => { useEffect(() => {
@@ -220,7 +241,7 @@ const SongReady = () => {
} catch (e) { } catch (e) {
console.log( console.log(
"SongReady pause on seek start", "SongReady pause on seek start",
e?.message, e?.message
); );
} }
}} }}