music timestamps
This commit is contained in:
+13
-11
@@ -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,
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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}>
|
||||||
|
|||||||
@@ -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
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
|
|||||||
Reference in New Issue
Block a user