fix: blur and music duration
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
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 { Image, Pressable, Text, View } from "react-native";
|
||||
import { responsiveHeight } from "react-native-responsive-dimensions";
|
||||
import { background, icons } from "../../assets";
|
||||
import alert from "../../components/Alert";
|
||||
@@ -35,6 +35,9 @@ const SongReady = () => {
|
||||
0: { pos: 0, dur: 0 },
|
||||
1: { pos: 0, dur: 0 },
|
||||
});
|
||||
|
||||
console.log("progress info", JSON.stringify(progressInfo, null, 2));
|
||||
|
||||
const player0 = useSharedAudioPlayer(
|
||||
musicUrls[0] ? { uri: musicUrls[0] } : undefined,
|
||||
{
|
||||
@@ -66,6 +69,10 @@ const SongReady = () => {
|
||||
}
|
||||
);
|
||||
const wasPlayingBeforeSeek = useRef({ 0: false, 1: false });
|
||||
const prefetchedRef = useRef({
|
||||
0: { url: null, done: false },
|
||||
1: { url: null, done: false },
|
||||
});
|
||||
|
||||
// Sync URLs from provider's selectedProject
|
||||
useEffect(() => {
|
||||
@@ -75,6 +82,100 @@ const SongReady = () => {
|
||||
setMusicUrls(urls);
|
||||
}, [selectedProject?.musicUrls]);
|
||||
|
||||
const firstUrl = musicUrls[0] ?? null;
|
||||
const secondUrl = musicUrls[1] ?? null;
|
||||
|
||||
useEffect(() => {
|
||||
let isCancelled = false;
|
||||
|
||||
const waitForDuration = async (index) => {
|
||||
const getDurationSeconds = () => {
|
||||
const targetPlayer = index === 0 ? player0 : player1;
|
||||
if (!targetPlayer) return 0;
|
||||
const raw = Number(targetPlayer.duration || 0);
|
||||
return Number.isFinite(raw) ? raw : 0;
|
||||
};
|
||||
|
||||
const start = Date.now();
|
||||
while (!isCancelled) {
|
||||
const durationSeconds = getDurationSeconds();
|
||||
if (durationSeconds > 0) {
|
||||
const durationMs = Math.round(durationSeconds * 1000);
|
||||
setProgressInfo((prev) => {
|
||||
const current = prev?.[index] || { pos: 0, dur: 0 };
|
||||
if (Math.abs(current.dur - durationMs) < 5) return prev;
|
||||
return {
|
||||
...prev,
|
||||
[index]: {
|
||||
...current,
|
||||
dur: durationMs,
|
||||
},
|
||||
};
|
||||
});
|
||||
return true;
|
||||
}
|
||||
if (Date.now() - start > 6000) {
|
||||
break;
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 150));
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
const prefetch = async () => {
|
||||
const entries = [
|
||||
[0, firstUrl, player0],
|
||||
[1, secondUrl, player1],
|
||||
];
|
||||
|
||||
for (const [idx, url, player] of entries) {
|
||||
const cacheEntry = prefetchedRef.current[idx];
|
||||
|
||||
if (!url || !player?.load) {
|
||||
if (cacheEntry) {
|
||||
cacheEntry.url = url ?? null;
|
||||
cacheEntry.done = false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cacheEntry && cacheEntry.url === url && cacheEntry.done) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (cacheEntry) {
|
||||
cacheEntry.url = url;
|
||||
cacheEntry.done = false;
|
||||
}
|
||||
|
||||
try {
|
||||
await player.load({ startPositionMs: 0 });
|
||||
const resolved = await waitForDuration(idx);
|
||||
if (cacheEntry && !isCancelled) {
|
||||
cacheEntry.done = resolved;
|
||||
}
|
||||
} catch (err) {
|
||||
if (cacheEntry) {
|
||||
cacheEntry.done = false;
|
||||
}
|
||||
if (!isCancelled) {
|
||||
console.log("SongReady preload error", err?.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (isCancelled) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
prefetch();
|
||||
|
||||
return () => {
|
||||
isCancelled = true;
|
||||
};
|
||||
}, [firstUrl, secondUrl, player0, player1]);
|
||||
|
||||
// Sync progression depuis les players
|
||||
useEffect(() => {
|
||||
// Ensure we consistently work with milliseconds whatever the platform
|
||||
@@ -264,7 +365,7 @@ const SongReady = () => {
|
||||
]}
|
||||
>
|
||||
<BlurView
|
||||
intensity={Platform.OS !== "ios" ? 10 : 40}
|
||||
intensity={40}
|
||||
tint="dark"
|
||||
style={{
|
||||
borderRadius: 18,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { View, Text, ScrollView, Pressable } from "react-native";
|
||||
import React, { useState } from "react";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import Page from "../../layouts/Page";
|
||||
import { background } from "../../assets";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
@@ -11,10 +11,15 @@ import { useUser } from "../../providers/UserDataProvider";
|
||||
import { responsiveHeight } from "react-native-responsive-dimensions";
|
||||
|
||||
const Studio = () => {
|
||||
const [selected, setSelected] = useState(null);
|
||||
const [selectedProjectId, setSelectedProjectId] = useState(null);
|
||||
|
||||
const { userProjects: projects, selectProject } = useUser();
|
||||
|
||||
const selectedProject = useMemo(() => {
|
||||
if (!Array.isArray(projects) || !projects.length) return null;
|
||||
return projects.find((p) => p?.id === selectedProjectId) ?? null;
|
||||
}, [projects, selectedProjectId]);
|
||||
|
||||
return (
|
||||
<Page
|
||||
headerType="NONE"
|
||||
@@ -41,7 +46,7 @@ const Studio = () => {
|
||||
{projects.map((p) => {
|
||||
const couplet = p?.lyrics?.couplet || "";
|
||||
const preview = couplet.split("\n").slice(0, 2).join(" ");
|
||||
const isSelected = selected === p;
|
||||
const isSelected = selectedProjectId === p?.id;
|
||||
const createdAt = p?.createdAt?.toDate
|
||||
? p.createdAt.toDate()
|
||||
: p?.createdAt
|
||||
@@ -54,13 +59,20 @@ const Studio = () => {
|
||||
return (
|
||||
<Pressable
|
||||
key={p.id}
|
||||
onPress={() => setSelected(p)}
|
||||
onPress={() => setSelectedProjectId(p?.id ?? null)}
|
||||
style={{
|
||||
backgroundColor: "#0F0C1933",
|
||||
backgroundColor: isSelected
|
||||
? "rgba(15, 12, 25, 0.75)"
|
||||
: "rgba(15, 12, 25, 0.2)",
|
||||
borderRadius: 12,
|
||||
padding: 12,
|
||||
borderWidth: isSelected ? 2 : 0,
|
||||
borderColor: isSelected ? "#F94697" : "transparent",
|
||||
shadowColor: "rgba(0, 0, 0, 0.55)",
|
||||
shadowOpacity: isSelected ? 0.6 : 0.35,
|
||||
shadowRadius: isSelected ? 8 : 4,
|
||||
shadowOffset: { width: 0, height: 4 },
|
||||
elevation: isSelected ? 6 : 1,
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
@@ -100,20 +112,21 @@ const Studio = () => {
|
||||
<View style={{ justifyContent: "flex-end" }}>
|
||||
<GradientButton
|
||||
title="Commencer"
|
||||
disabled={!selected}
|
||||
disabled={!selectedProject}
|
||||
onPress={() => {
|
||||
if (selected.musicStatus === "GENERATING") {
|
||||
if (selectedProject?.musicStatus === "GENERATING") {
|
||||
alert(
|
||||
"Attention",
|
||||
"La chanson est en cours de génération. Veuillez patienter.",
|
||||
);
|
||||
} else {
|
||||
selectProject(selected.id);
|
||||
if (!selectedProject?.id) return;
|
||||
selectProject(selectedProject.id);
|
||||
navigate(Routes.Compose);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
{selected?.musicUrls?.length > 0 && (
|
||||
{selectedProject?.musicUrls?.length > 0 && (
|
||||
<>
|
||||
<GradientButton
|
||||
title="Ecouter les audios"
|
||||
@@ -121,18 +134,20 @@ const Studio = () => {
|
||||
marginTop: responsiveHeight(2),
|
||||
}}
|
||||
onPress={() => {
|
||||
selectProject(selected.id);
|
||||
if (!selectedProject?.id) return;
|
||||
selectProject(selectedProject.id);
|
||||
navigate(Routes.SongReady);
|
||||
}}
|
||||
/>
|
||||
{!selected?.coverUrl && (
|
||||
{!selectedProject?.coverUrl && (
|
||||
<GradientButton
|
||||
title="Générer une pochette"
|
||||
containerStyle={{
|
||||
marginTop: responsiveHeight(2),
|
||||
}}
|
||||
onPress={() => {
|
||||
selectProject(selected.id);
|
||||
if (!selectedProject?.id) return;
|
||||
selectProject(selectedProject.id);
|
||||
navigate(Routes.PouchReady);
|
||||
}}
|
||||
/>
|
||||
|
||||
@@ -188,6 +188,36 @@ const CreateLyricsWithAi = () => {
|
||||
}
|
||||
}, [structure]);
|
||||
|
||||
const sanitizedCustomStructure = useMemo(() => {
|
||||
if (!Array.isArray(customStructure) || customStructure.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [customStructure]);
|
||||
|
||||
const sanitizedParsedStructure = useMemo(() => {
|
||||
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [parsedStructure]);
|
||||
|
||||
const fallbackProjectStructure = useMemo(() => {
|
||||
const raw = selectedProject?.config?.structure;
|
||||
if (!Array.isArray(raw) || raw.length === 0) return [];
|
||||
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [selectedProject]);
|
||||
|
||||
const baseStructureForCustomizer = useMemo(() => {
|
||||
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure;
|
||||
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure;
|
||||
return fallbackProjectStructure;
|
||||
}, [
|
||||
sanitizedCustomStructure,
|
||||
sanitizedParsedStructure,
|
||||
fallbackProjectStructure,
|
||||
]);
|
||||
|
||||
const lyricsConfig = useMemo(() => {
|
||||
const resolvedObjective = shouldUseOtherObjective
|
||||
? persistedOtherObjective || undefined
|
||||
@@ -195,13 +225,8 @@ const CreateLyricsWithAi = () => {
|
||||
const resolvedStyle = shouldUseOtherStyle
|
||||
? persistedOtherStyle || undefined
|
||||
: style || undefined;
|
||||
const sanitizedCustom =
|
||||
Array.isArray(customStructure) && customStructure.length > 0
|
||||
? sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
|
||||
: [];
|
||||
const sanitizedParsed = Array.isArray(parsedStructure)
|
||||
? sanitizeStructureList(parsedStructure)
|
||||
: [];
|
||||
const sanitizedCustom = sanitizedCustomStructure;
|
||||
const sanitizedParsed = sanitizedParsedStructure;
|
||||
|
||||
return {
|
||||
objective: resolvedObjective,
|
||||
@@ -230,9 +255,9 @@ const CreateLyricsWithAi = () => {
|
||||
emotion,
|
||||
style,
|
||||
audience,
|
||||
parsedStructure,
|
||||
sanitizedParsedStructure,
|
||||
rhymes,
|
||||
customStructure,
|
||||
sanitizedCustomStructure,
|
||||
]);
|
||||
|
||||
const isNextDisabled = React.useMemo(() => {
|
||||
@@ -444,14 +469,7 @@ const CreateLyricsWithAi = () => {
|
||||
}}
|
||||
>
|
||||
<CustomizeSongStructure
|
||||
baseStructure={
|
||||
parsedStructure ||
|
||||
(Array.isArray(customStructure)
|
||||
? customStructure
|
||||
: Array.isArray(selectedProject?.config?.structure)
|
||||
? selectedProject.config.structure
|
||||
: [])
|
||||
}
|
||||
baseStructure={baseStructureForCustomizer}
|
||||
onChange={setCustomStructure}
|
||||
/>
|
||||
</View>
|
||||
|
||||
@@ -188,6 +188,36 @@ const CreateLyricsWithAi = () => {
|
||||
}
|
||||
}, [structure]);
|
||||
|
||||
const sanitizedCustomStructure = useMemo(() => {
|
||||
if (!Array.isArray(customStructure) || customStructure.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [customStructure]);
|
||||
|
||||
const sanitizedParsedStructure = useMemo(() => {
|
||||
if (!Array.isArray(parsedStructure) || parsedStructure.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return sanitizeStructureList(parsedStructure, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [parsedStructure]);
|
||||
|
||||
const fallbackProjectStructure = useMemo(() => {
|
||||
const raw = selectedProject?.config?.structure;
|
||||
if (!Array.isArray(raw) || raw.length === 0) return [];
|
||||
return sanitizeStructureList(raw, CUSTOM_SANITIZE_OPTIONS);
|
||||
}, [selectedProject]);
|
||||
|
||||
const baseStructureForCustomizer = useMemo(() => {
|
||||
if (sanitizedCustomStructure.length > 0) return sanitizedCustomStructure;
|
||||
if (sanitizedParsedStructure.length > 0) return sanitizedParsedStructure;
|
||||
return fallbackProjectStructure;
|
||||
}, [
|
||||
sanitizedCustomStructure,
|
||||
sanitizedParsedStructure,
|
||||
fallbackProjectStructure,
|
||||
]);
|
||||
|
||||
const progress = useMemo(
|
||||
() => 16 + selectedIndex * 9,
|
||||
[selectedIndex]
|
||||
@@ -200,13 +230,8 @@ const CreateLyricsWithAi = () => {
|
||||
const resolvedStyle = shouldUseOtherStyle
|
||||
? persistedOtherStyle || undefined
|
||||
: style || undefined;
|
||||
const sanitizedCustom =
|
||||
Array.isArray(customStructure) && customStructure.length > 0
|
||||
? sanitizeStructureList(customStructure, CUSTOM_SANITIZE_OPTIONS)
|
||||
: [];
|
||||
const sanitizedParsed = Array.isArray(parsedStructure)
|
||||
? sanitizeStructureList(parsedStructure)
|
||||
: [];
|
||||
const sanitizedCustom = sanitizedCustomStructure;
|
||||
const sanitizedParsed = sanitizedParsedStructure;
|
||||
|
||||
return {
|
||||
objective: resolvedObjective,
|
||||
@@ -235,9 +260,9 @@ const CreateLyricsWithAi = () => {
|
||||
emotion,
|
||||
style,
|
||||
audience,
|
||||
parsedStructure,
|
||||
sanitizedParsedStructure,
|
||||
rhymes,
|
||||
customStructure,
|
||||
sanitizedCustomStructure,
|
||||
]);
|
||||
|
||||
const isNextDisabled = React.useMemo(() => {
|
||||
@@ -358,14 +383,7 @@ const CreateLyricsWithAi = () => {
|
||||
key: "customStructure",
|
||||
render: () => (
|
||||
<CustomizeSongStructure
|
||||
baseStructure={
|
||||
parsedStructure ||
|
||||
(Array.isArray(customStructure)
|
||||
? customStructure
|
||||
: Array.isArray(selectedProject?.config?.structure)
|
||||
? selectedProject.config.structure
|
||||
: [])
|
||||
}
|
||||
baseStructure={baseStructureForCustomizer}
|
||||
onChange={setCustomStructure}
|
||||
/>
|
||||
),
|
||||
|
||||
@@ -77,7 +77,10 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
||||
console.log("🚀 [CreatingLyrics] Lancement de la génération", {
|
||||
platform: Platform.OS,
|
||||
});
|
||||
const sanitizedStructure = sanitizeStructureList(config?.structure);
|
||||
const sanitizedStructure = sanitizeStructureList(
|
||||
config?.structure,
|
||||
CUSTOM_SANITIZE_OPTIONS
|
||||
);
|
||||
const callable = firebase
|
||||
.functions()
|
||||
.httpsCallable("lyrics-generateLyrics");
|
||||
@@ -168,7 +171,8 @@ const CreatingLyrics = ({ active, config, selections }) => {
|
||||
await setIsLoading(true);
|
||||
console.log("💾 [CreatingLyrics] Sauvegarde automatique des paroles");
|
||||
const sanitizedStructure = sanitizeStructureList(
|
||||
result?.structure || config?.structure
|
||||
result?.structure || config?.structure,
|
||||
CUSTOM_SANITIZE_OPTIONS
|
||||
);
|
||||
const baseConfig =
|
||||
config && typeof config === "object" && !Array.isArray(config)
|
||||
|
||||
Reference in New Issue
Block a user