karaoke
This commit is contained in:
@@ -15,24 +15,11 @@ const Playback = ({ route }) => {
|
|||||||
const { project } = route.params;
|
const { project } = route.params;
|
||||||
const projectId = project?.id;
|
const projectId = project?.id;
|
||||||
const songIndex = project?.songIndex;
|
const songIndex = project?.songIndex;
|
||||||
console.log("sonindex", songIndex);
|
|
||||||
const sunoTaskId = project?.sunoTaskId;
|
const sunoTaskId = project?.sunoTaskId;
|
||||||
const { setIsLoading } = useMinuit();
|
const { setIsLoading } = useMinuit();
|
||||||
// console.log("musicTimestamps avec songIndex:", {
|
|
||||||
// // Accès avec la clé dot notation
|
|
||||||
// musicTimeStamps: project?.[`musicTimestamps.${songIndex}`],
|
|
||||||
// // Toutes les clés qui commencent par musicTimestamps
|
|
||||||
// });
|
|
||||||
const onPressRecord = useCallback(async () => {
|
const onPressRecord = useCallback(async () => {
|
||||||
try {
|
try {
|
||||||
console.log(
|
|
||||||
"project",
|
|
||||||
songIndex,
|
|
||||||
"project id",
|
|
||||||
projectId,
|
|
||||||
"sunoTaskId",
|
|
||||||
sunoTaskId
|
|
||||||
);
|
|
||||||
if (!projectId || songIndex === undefined || !sunoTaskId) {
|
if (!projectId || songIndex === undefined || !sunoTaskId) {
|
||||||
return;
|
return;
|
||||||
// return navigate(Routes.RecordPlayback, { project });
|
// return navigate(Routes.RecordPlayback, { project });
|
||||||
|
|||||||
@@ -1,7 +1,13 @@
|
|||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { useAudioPlayer } from "expo-audio";
|
import { useAudioPlayer } from "expo-audio";
|
||||||
import { CameraView, useCameraPermissions } from "expo-camera";
|
import { CameraView, useCameraPermissions } from "expo-camera";
|
||||||
import React, { useCallback, useEffect, useRef, useState } from "react";
|
import React, {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useMemo,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from "react";
|
||||||
import { Text, View } from "react-native";
|
import { Text, View } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import Svg, { Circle } from "react-native-svg";
|
import Svg, { Circle } from "react-native-svg";
|
||||||
@@ -14,20 +20,13 @@ import { gutters, Palette } from "../../styles";
|
|||||||
import { FONT_FAMILY } from "../../styles/Fonts";
|
import { FONT_FAMILY } from "../../styles/Fonts";
|
||||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||||
|
|
||||||
/**
|
|
||||||
* RecordPlayback — refactor robuste du compteur
|
|
||||||
*
|
|
||||||
* Correction de l'auto-start : on garde un flag countdownActiveRef pour
|
|
||||||
* empêcher l'effet de se déclencher tant que le timer n'a pas réellement démarré.
|
|
||||||
* On affiche 5→1 puis on bascule (pas de 0 visible) pour éviter le "bloqué sur 1".
|
|
||||||
*/
|
|
||||||
|
|
||||||
const TIME_BEFORE_INCREMENT_MS = 20000; // 20s
|
const TIME_BEFORE_INCREMENT_MS = 20000; // 20s
|
||||||
|
|
||||||
const RecordPlayback = ({ route }) => {
|
const RecordPlayback = ({ route }) => {
|
||||||
const { top, bottom } = useSafeAreaInsets();
|
const { top, bottom } = useSafeAreaInsets();
|
||||||
const { project } = route.params || {};
|
const { project } = route.params || {};
|
||||||
|
const projectId = project?.id;
|
||||||
|
const songIndex = project?.songIndex;
|
||||||
// Permissions
|
// Permissions
|
||||||
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
|
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
|
||||||
|
|
||||||
@@ -55,6 +54,22 @@ const RecordPlayback = ({ route }) => {
|
|||||||
const songUrl = project?.songUrl || null;
|
const songUrl = project?.songUrl || null;
|
||||||
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
const player = useAudioPlayer(songUrl ? { uri: songUrl } : undefined);
|
||||||
|
|
||||||
|
const musicIndex = useMemo(() => {
|
||||||
|
const i = Number(songIndex);
|
||||||
|
return Number.isFinite(i) && i >= 0 ? i : 0;
|
||||||
|
}, [songIndex]);
|
||||||
|
|
||||||
|
const alignedWords = useMemo(() => {
|
||||||
|
const ts = project?.[`musicTimestamps.${songIndex}`];
|
||||||
|
null;
|
||||||
|
const arr = Array.isArray(ts?.alignedWords) ? ts.alignedWords : [];
|
||||||
|
return arr.map((w) => ({
|
||||||
|
word: String(w?.word ?? "").replace(/\n/g, " "),
|
||||||
|
startS: Number(w?.startS ?? 0),
|
||||||
|
endS: Number(w?.endS ?? 0),
|
||||||
|
}));
|
||||||
|
}, [project?.musicTimestamps, musicIndex]);
|
||||||
|
// console.log("alignedWords:", alignedWords);
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
listenedMsRef.current = 0;
|
listenedMsRef.current = 0;
|
||||||
incrementDoneRef.current = false;
|
incrementDoneRef.current = false;
|
||||||
@@ -73,6 +88,19 @@ const RecordPlayback = ({ route }) => {
|
|||||||
return () => clearInterval(id);
|
return () => clearInterval(id);
|
||||||
}, [player]);
|
}, [player]);
|
||||||
|
|
||||||
|
// Compute current word index from playback time
|
||||||
|
const currentTimeS = (progressInfo?.pos || 0) / 1000;
|
||||||
|
const currentIdx = useMemo(() => {
|
||||||
|
if (!alignedWords || alignedWords.length === 0) return -1;
|
||||||
|
for (let i = 0; i < alignedWords.length; i++) {
|
||||||
|
const w = alignedWords[i];
|
||||||
|
if (currentTimeS >= w.startS && currentTimeS <= w.endS) return i;
|
||||||
|
}
|
||||||
|
if (currentTimeS > (alignedWords[alignedWords.length - 1]?.endS || 0))
|
||||||
|
return alignedWords.length - 1;
|
||||||
|
return -1;
|
||||||
|
}, [alignedWords, currentTimeS]);
|
||||||
|
|
||||||
// Permissions au mount + cleanup
|
// Permissions au mount + cleanup
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -408,24 +436,25 @@ const RecordPlayback = ({ route }) => {
|
|||||||
position: "absolute",
|
position: "absolute",
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
bottom: bottom + 100,
|
bottom: bottom + 130,
|
||||||
paddingHorizontal: gutters,
|
paddingHorizontal: gutters,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<CreateLyricsHeader
|
<CreateLyricsHeader>
|
||||||
// disableBlur={Platform.OS !== "ios"}
|
{(isRecording || showProgress) && alignedWords?.length > 0 ? (
|
||||||
// containerStyle={{ backgroundColor: Palette.ultraLightBlack }}
|
<KaraokeLine words={alignedWords} currentIdx={currentIdx} />
|
||||||
>
|
) : (
|
||||||
<Text
|
<Text
|
||||||
style={{
|
style={{
|
||||||
fontSize: 16,
|
fontSize: 16,
|
||||||
color: Palette.white,
|
color: Palette.white,
|
||||||
fontFamily: FONT_FAMILY.InterMedium,
|
fontFamily: FONT_FAMILY.InterMedium,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
L'enregistrement de ta vidéo commencera lorsque tu lanceras ta
|
L'enregistrement de ta vidéo commencera lorsque tu lanceras ta
|
||||||
musique, et s'arrêtera à la fin du morceau.
|
musique, et s'arrêtera à la fin du morceau.
|
||||||
</Text>
|
</Text>
|
||||||
|
)}
|
||||||
</CreateLyricsHeader>
|
</CreateLyricsHeader>
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
@@ -464,3 +493,43 @@ const ProgressRing = ({ size = 50, strokeWidth = 12, progress = 0 }) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export default RecordPlayback;
|
export default RecordPlayback;
|
||||||
|
|
||||||
|
// Simple karaoke line renderer
|
||||||
|
const KaraokeLine = ({ words = [], currentIdx = -1 }) => {
|
||||||
|
const windowSize = 8;
|
||||||
|
const start = Math.max(0, currentIdx - 2);
|
||||||
|
const end = Math.min(
|
||||||
|
words.length,
|
||||||
|
currentIdx >= 0 ? currentIdx + windowSize : windowSize
|
||||||
|
);
|
||||||
|
const slice = words.slice(start, end);
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
style={{
|
||||||
|
color: Palette.white,
|
||||||
|
fontSize: 18,
|
||||||
|
lineHeight: 24,
|
||||||
|
textAlign: "center",
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{slice.map((w, i) => {
|
||||||
|
const idx = start + i;
|
||||||
|
const text = String(w.word || "").replace(/\s+/g, " ");
|
||||||
|
const isCurrent = idx === currentIdx;
|
||||||
|
return (
|
||||||
|
<Text
|
||||||
|
key={`${idx}-${text}`}
|
||||||
|
style={{
|
||||||
|
color: isCurrent ? Palette.white : "#FFFFFFAA",
|
||||||
|
fontFamily: isCurrent
|
||||||
|
? FONT_FAMILY.InterSemiBold
|
||||||
|
: FONT_FAMILY.InterMedium,
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{text + " "}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</Text>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user