record playback

This commit is contained in:
2025-10-01 17:10:40 +02:00
parent 1fd63c254d
commit f78dc64ec9
4 changed files with 714 additions and 72 deletions
+79 -67
View File
@@ -1,25 +1,25 @@
import { useFocusEffect } from '@react-navigation/native';
import { useAudioPlayer } from 'expo-audio';
import { CameraView, useCameraPermissions } from 'expo-camera';
import { useFocusEffect } from "@react-navigation/native";
import { useAudioPlayer } from "expo-audio";
import { CameraView, useCameraPermissions } from "expo-camera";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from 'react';
import { Alert, Text, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import Svg, { Circle } from 'react-native-svg';
import GradientButton from '../../components/GradientButton';
import KaraokeLyrics from '../../components/KaraokeLyrics';
import MusicLandHeader from '../../components/MusicLandHeader';
import { increment, projectsRef } from '../../config/firebase';
import { Routes } from '../../navigation';
import { goBack, navigate } from '../../navigation/NavigationService';
import { gutters, Palette } from '../../styles';
import { FONT_FAMILY } from '../../styles/Fonts';
import CreateLyricsHeader from '../Writing/components/CreateLyricsHeader';
} from "react";
import { Alert, Text, View } from "react-native";
import { useSafeAreaInsets } from "react-native-safe-area-context";
import Svg, { Circle } from "react-native-svg";
import GradientButton from "../../components/GradientButton";
import KaraokeLyrics from "../../components/KaraokeLyrics";
import MusicLandHeader from "../../components/MusicLandHeader";
import { increment, projectsRef } from "../../config/firebase";
import { Routes } from "../../navigation";
import { goBack, navigate } from "../../navigation/NavigationService";
import { gutters, Palette } from "../../styles";
import { FONT_FAMILY } from "../../styles/Fonts";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
const TIME_BEFORE_INCREMENT_MS = 20000; // 20s
@@ -65,7 +65,7 @@ const RecordPlayback = ({ route }) => {
const arr = Array.isArray(ts?.alignedWords) ? ts.alignedWords : [];
return arr.map((w) => ({
word: String(w?.word ?? ''),
word: String(w?.word ?? ""),
startS: Number(w?.startS ?? 0),
endS: Number(w?.endS ?? 0),
}));
@@ -77,16 +77,16 @@ const RecordPlayback = ({ route }) => {
let buf = [];
let start = null;
const clean = (txt) =>
String(txt || '')
.replace(/\s+/g, ' ')
String(txt || "")
.replace(/\s+/g, " ")
.trim();
const isSentenceEnd = (txt) => /[.!?]$/.test((txt || '').trim());
const isSentenceEnd = (txt) => /[.!?]$/.test((txt || "").trim());
const isSectionTag = (txt) =>
/^\s*\[[^\]]+\]\s*$/i.test((txt || '').trim());
/^\s*\[[^\]]+\]\s*$/i.test((txt || "").trim());
for (let i = 0; i < alignedWords.length; i++) {
const w = alignedWords[i];
const original = String(w.word || '');
const textNoNewline = original.replace(/\n/g, ' ');
const original = String(w.word || "");
const textNoNewline = original.replace(/\n/g, " ");
const gapToNext =
i < alignedWords.length - 1
? Math.max(0, alignedWords[i + 1].startS - w.endS)
@@ -113,7 +113,7 @@ const RecordPlayback = ({ route }) => {
const isLast = i === alignedWords.length - 1;
if (eolByNewline || eolByPause || eolByPunct || isLast) {
const joined = clean(buf.join(' '));
const joined = clean(buf.join(" "));
if (joined)
out.push({ text: joined, startS: start ?? w.startS, endS: w.endS });
buf = [];
@@ -343,7 +343,7 @@ const RecordPlayback = ({ route }) => {
navigate(Routes.RecordedPlayback, { videoUri: video.uri, project });
else navigate(Routes.RecordedPlayback, { project });
} catch (e) {
console.log('RecordPlayback error:', e);
console.log("RecordPlayback error:", e);
setIsRecording(false);
setIsPreparing(false);
setShowProgress(false);
@@ -356,14 +356,14 @@ const RecordPlayback = ({ route }) => {
checkSongEndRef.current = null;
}
// Inform the user when using a simulator where recording isn't supported
const msg = String(e?.message || e || '');
const msg = String(e?.message || e || "");
if (/not supported on the simulator/i.test(msg)) {
Alert.alert(
'Indisponible sur simulateur',
'Lenregistrement vidéo nest pas disponible sur le simulateur. Merci dutiliser un appareil réel.',
"Indisponible sur simulateur",
"Lenregistrement vidéo nest pas disponible sur le simulateur. Merci dutiliser un appareil réel.",
[
{
text: 'OK',
text: "OK",
onPress: () => {
try {
goBack();
@@ -384,15 +384,17 @@ const RecordPlayback = ({ route }) => {
<CameraView
ref={cameraRef}
style={{ flex: 1 }}
facing='front'
mode='video'>
facing="front"
mode="video"
>
<View
style={{
paddingHorizontal: gutters,
paddingTop: top,
flex: 1,
paddingBottom: gutters * 2,
}}>
}}
>
<MusicLandHeader progress={9} onPressBack={goBack} />
<View style={{ flex: 1, marginTop: 11 }}>
@@ -400,20 +402,22 @@ const RecordPlayback = ({ route }) => {
{isPreparing && countdown >= 1 && !showProgress && (
<View
style={{
position: 'absolute',
position: "absolute",
top: 0,
left: 0,
right: 0,
bottom: 0,
alignItems: 'center',
justifyContent: 'center',
}}>
alignItems: "center",
justifyContent: "center",
}}
>
<Text
style={{
fontSize: 72,
color: Palette.white,
fontFamily: FONT_FAMILY.HelveticaNeueBold,
}}>
}}
>
{countdown}
</Text>
</View>
@@ -423,14 +427,15 @@ const RecordPlayback = ({ route }) => {
{!permissionsGranted && (
<View
style={{
position: 'absolute',
position: "absolute",
left: 0,
right: 0,
bottom: 0,
padding: gutters,
}}>
}}
>
<GradientButton
title='Autoriser la caméra'
title="Autoriser la caméra"
onPress={async () => {
try {
if (!cameraPermission?.granted)
@@ -445,8 +450,8 @@ const RecordPlayback = ({ route }) => {
{/* Start button */}
{permissionsGranted && !isPreparing && !isRecording && (
<GradientButton
title='Lancer ma musique'
containerStyle={{ width: '80%', alignSelf: 'center' }}
title="Lancer ma musique"
containerStyle={{ width: "80%", alignSelf: "center" }}
disabled={!songUrl}
onPress={startCountdownThenRecord}
/>
@@ -456,21 +461,23 @@ const RecordPlayback = ({ route }) => {
{permissionsGranted && (isRecording || showProgress) && (
<View
style={{
position: 'absolute',
position: "absolute",
left: 0,
right: 0,
bottom: gutters,
alignItems: 'center',
justifyContent: 'center',
}}>
alignItems: "center",
justifyContent: "center",
}}
>
<View
style={{
width: 100,
height: 100,
borderRadius: 105,
alignItems: 'center',
justifyContent: 'center',
}}>
alignItems: "center",
justifyContent: "center",
}}
>
<ProgressRing
size={100}
strokeWidth={8}
@@ -482,7 +489,7 @@ const RecordPlayback = ({ route }) => {
/>
<View
style={{
position: 'absolute',
position: "absolute",
width: 50,
height: 50,
borderRadius: 55,
@@ -496,14 +503,15 @@ const RecordPlayback = ({ route }) => {
</CameraView>
{/* CreateLyricsHeader overlay outside of CameraView */}
<View
pointerEvents='box-none'
pointerEvents="box-none"
style={{
position: 'absolute',
position: "absolute",
left: 0,
right: 0,
bottom: bottom + 130,
paddingHorizontal: gutters,
}}>
}}
>
<CreateLyricsHeader>
{(isRecording || showProgress) && alignedWords?.length > 0 ? (
<KaraokeLyrics
@@ -516,7 +524,8 @@ const RecordPlayback = ({ route }) => {
fontSize: 16,
color: Palette.white,
fontFamily: FONT_FAMILY.InterMedium,
}}>
}}
>
L'enregistrement de ta vidéo commencera lorsque tu lanceras ta
musique, et s'arrêtera à la fin du morceau.
</Text>
@@ -538,20 +547,21 @@ const ProgressRing = ({ size = 50, strokeWidth = 12, progress = 0 }) => {
width={size}
height={size}
style={{
transform: [{ rotate: '-90deg' }],
backgroundColor: '#ffffff3d',
transform: [{ rotate: "-90deg" }],
backgroundColor: "#ffffff3d",
borderRadius: 55,
}}>
}}
>
<Circle
cx={size / 2}
cy={size / 2}
r={r}
stroke={Palette.white}
strokeWidth={strokeWidth}
strokeLinecap='round'
strokeLinecap="round"
strokeDasharray={`${c} ${c}`}
strokeDashoffset={offset}
fill='transparent'
fill="transparent"
/>
</Svg>
);
@@ -561,10 +571,10 @@ export default RecordPlayback;
// Render previous/current/next line to reduce jumpiness
const KaraokeLines = ({ lines = [], currentLineIdx = -1 }) => {
const prev = currentLineIdx > 0 ? lines[currentLineIdx - 1]?.text : '';
const curr = currentLineIdx >= 0 ? lines[currentLineIdx]?.text : '';
const prev = currentLineIdx > 0 ? lines[currentLineIdx - 1]?.text : "";
const curr = currentLineIdx >= 0 ? lines[currentLineIdx]?.text : "";
const next =
currentLineIdx + 1 < lines.length ? lines[currentLineIdx + 1]?.text : '';
currentLineIdx + 1 < lines.length ? lines[currentLineIdx + 1]?.text : "";
return (
<View style={{ gap: 4 }}>
{/* {prev ? (
@@ -576,19 +586,21 @@ const KaraokeLines = ({ lines = [], currentLineIdx = -1 }) => {
style={{
color: Palette.white,
fontSize: 18,
textAlign: 'center',
textAlign: "center",
fontFamily: FONT_FAMILY.InterSemiBold,
}}>
}}
>
{curr}
</Text>
{next ? (
<Text
style={{
color: '#FFFFFF99',
color: "#FFFFFF99",
fontSize: 14,
textAlign: 'center',
textAlign: "center",
fontFamily: FONT_FAMILY.InterMedium,
}}>
}}
>
{next}
</Text>
) : null}