clear lot of tickets
This commit is contained in:
@@ -30,15 +30,15 @@ const Playback = ({ route }) => {
|
||||
}}
|
||||
>
|
||||
<View style={{ width: "80%", alignSelf: "center", gap: 12 }}>
|
||||
<GradientButton
|
||||
title="Enregistrer mon Playback"
|
||||
onPress={onPressRecord}
|
||||
/>
|
||||
<BorderGradientButton
|
||||
title="Guide du Playbacker"
|
||||
onPress={() => setShowIntro(true)}
|
||||
/>
|
||||
{/* <BorderGradientButton title="Importer une vidéo" /> */}
|
||||
<GradientButton
|
||||
title="Enregistrer mon Playback"
|
||||
onPress={onPressRecord}
|
||||
/>
|
||||
</View>
|
||||
</View>
|
||||
<FullscreenIntroVideo
|
||||
|
||||
@@ -15,6 +15,7 @@ import GradientButton from "../../components/GradientButton";
|
||||
import KaraokeLyrics from "../../components/KaraokeLyrics";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import { increment, projectsRef } from "../../config/firebase";
|
||||
import usePlayer from "../../hooks/usePlayer";
|
||||
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
|
||||
import { Routes } from "../../navigation";
|
||||
import { goBack, navigate } from "../../navigation/NavigationService";
|
||||
@@ -35,6 +36,7 @@ const RecordPlayback = ({ route }) => {
|
||||
log("Route params received", { hasProject: !!project });
|
||||
const projectId = project?.id;
|
||||
const songIndex = project?.songIndex;
|
||||
const { setLooping, isLooping } = usePlayer() || {};
|
||||
// Permissions
|
||||
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
|
||||
|
||||
@@ -48,6 +50,8 @@ const RecordPlayback = ({ route }) => {
|
||||
const countdownActiveRef = useRef(false); // évite le déclenchement avant 1er tick
|
||||
const playbackStartedRef = useRef(false); // devient vrai lorsque l'audio progresse réellement
|
||||
const progressLogRef = useRef({ bucket: -1, lastPos: -1, lastDur: -1 });
|
||||
const originalLoopingValueRef = useRef({ hasValue: false, value: false });
|
||||
const latestLoopingValueRef = useRef(isLooping ?? false);
|
||||
|
||||
// Compteurs vues
|
||||
const listenedMsRef = useRef(0);
|
||||
@@ -80,6 +84,10 @@ const RecordPlayback = ({ route }) => {
|
||||
log("Screen params", { projectId, songUrl, songIndex });
|
||||
}, [projectId, songUrl, songIndex]);
|
||||
|
||||
useEffect(() => {
|
||||
latestLoopingValueRef.current = isLooping ?? false;
|
||||
}, [isLooping]);
|
||||
|
||||
const musicIndex = useMemo(() => {
|
||||
const i = Number(songIndex);
|
||||
return Number.isFinite(i) && i >= 0 ? i : 0;
|
||||
@@ -280,12 +288,42 @@ const RecordPlayback = ({ route }) => {
|
||||
} catch (_) {}
|
||||
}, [player]);
|
||||
|
||||
const resetSessionRef = useRef(resetSession);
|
||||
useEffect(() => {
|
||||
resetSessionRef.current = resetSession;
|
||||
}, [resetSession]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
log("Screen focused, resetting session");
|
||||
void resetSession();
|
||||
return () => {};
|
||||
}, [resetSession])
|
||||
void resetSessionRef.current?.();
|
||||
return () => {
|
||||
log("Screen blurred, stopping playback");
|
||||
void resetSessionRef.current?.();
|
||||
};
|
||||
}, [])
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
if (typeof setLooping === "function") {
|
||||
originalLoopingValueRef.current = {
|
||||
hasValue: true,
|
||||
value: latestLoopingValueRef.current,
|
||||
};
|
||||
if (latestLoopingValueRef.current) {
|
||||
setLooping(false);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (
|
||||
typeof setLooping === "function" &&
|
||||
originalLoopingValueRef.current?.hasValue
|
||||
) {
|
||||
setLooping(!!originalLoopingValueRef.current.value);
|
||||
}
|
||||
};
|
||||
}, [setLooping])
|
||||
);
|
||||
|
||||
// Lancer le compte à rebours (le tick décrémente uniquement)
|
||||
|
||||
@@ -16,6 +16,7 @@ import KaraokeLyrics from "../../components/KaraokeLyrics";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
import { increment, projectsRef } from "../../config/firebase";
|
||||
import { isWeb } from "../../hooks/useLayoutType";
|
||||
import usePlayer from "../../hooks/usePlayer";
|
||||
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
|
||||
import Page from "../../layouts/Page";
|
||||
import { Routes } from "../../navigation";
|
||||
@@ -59,6 +60,7 @@ const RecordPlayback = ({ route }) => {
|
||||
const { project } = route.params || {};
|
||||
const songIndex = Number(project?.songIndex ?? 0) || 0;
|
||||
const [cameraPermission, requestCameraPermission] = useCameraPermissions();
|
||||
const { setLooping, isLooping } = usePlayer() || {};
|
||||
|
||||
// Player
|
||||
const songUrl = project?.songUrl || null;
|
||||
@@ -80,6 +82,8 @@ const RecordPlayback = ({ route }) => {
|
||||
const stopRecordingPromiseRef = useRef(null);
|
||||
const stopRecordingResolveRef = useRef(null);
|
||||
const recordedUrlRef = useRef(null);
|
||||
const originalLoopingValueRef = useRef({ hasValue: false, value: false });
|
||||
const latestLoopingValueRef = useRef(isLooping ?? false);
|
||||
const [mediaReady, setMediaReady] = useState(false);
|
||||
const [mediaError, setMediaError] = useState(null);
|
||||
|
||||
@@ -100,6 +104,10 @@ const RecordPlayback = ({ route }) => {
|
||||
const correctedInitialJumpRef = useRef(false);
|
||||
const progressDebugCounterRef = useRef(0);
|
||||
|
||||
useEffect(() => {
|
||||
latestLoopingValueRef.current = isLooping ?? false;
|
||||
}, [isLooping]);
|
||||
|
||||
// Lyrics
|
||||
const alignedWords = useMemo(() => {
|
||||
const ts = project?.musicTimestamps?.[songIndex];
|
||||
@@ -328,11 +336,41 @@ const RecordPlayback = ({ route }) => {
|
||||
console.log(LOG_PREFIX, "resetSession:end");
|
||||
}, [player, releaseRecordingUrl, stopRecorderAndGetUrl]);
|
||||
|
||||
const resetSessionRef = useRef(resetSession);
|
||||
useEffect(() => {
|
||||
resetSessionRef.current = resetSession;
|
||||
}, [resetSession]);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
void resetSession();
|
||||
return () => {};
|
||||
}, [resetSession])
|
||||
if (typeof setLooping === "function") {
|
||||
originalLoopingValueRef.current = {
|
||||
hasValue: true,
|
||||
value: latestLoopingValueRef.current,
|
||||
};
|
||||
if (latestLoopingValueRef.current) {
|
||||
setLooping(false);
|
||||
}
|
||||
}
|
||||
return () => {
|
||||
if (
|
||||
typeof setLooping === "function" &&
|
||||
originalLoopingValueRef.current?.hasValue
|
||||
) {
|
||||
setLooping(!!originalLoopingValueRef.current.value);
|
||||
}
|
||||
};
|
||||
}, [setLooping])
|
||||
);
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
void resetSessionRef.current?.();
|
||||
return () => {
|
||||
console.log(LOG_PREFIX, "focusEffect:cleanup");
|
||||
void resetSessionRef.current?.();
|
||||
};
|
||||
}, [])
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
|
||||
import * as FileSystem from "expo-file-system";
|
||||
import { VideoView, useVideoPlayer } from "expo-video";
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { View } from "react-native";
|
||||
import { background } from "../../assets";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Image, Pressable, View } from "react-native";
|
||||
import { background, icons } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
import GradientButton from "../../components/GradientButton";
|
||||
import MusicLandHeader from "../../components/MusicLandHeader";
|
||||
@@ -30,8 +30,13 @@ const RecordedPlayback = ({ route }) => {
|
||||
p.timeUpdateEventInterval = 0.2;
|
||||
});
|
||||
|
||||
const [progressInfo, setProgressInfo] = useState({ pos: 0, dur: 0 });
|
||||
const [progressInfo, setProgressInfo] = useState({
|
||||
pos: 0,
|
||||
dur: 0,
|
||||
isPlaying: false,
|
||||
});
|
||||
const wasPlayingBeforeSeek = useRef(false);
|
||||
const playbackEndedRef = useRef(false);
|
||||
|
||||
// Format mm:ss
|
||||
const fmt = (ms) => {
|
||||
@@ -44,7 +49,17 @@ const RecordedPlayback = ({ route }) => {
|
||||
};
|
||||
|
||||
// Start both players on mount
|
||||
const stopPlayback = useCallback(async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
} catch (e) {}
|
||||
}, [audioPlayer, videoPlayer]);
|
||||
|
||||
useEffect(() => {
|
||||
playbackEndedRef.current = false;
|
||||
const start = async () => {
|
||||
try {
|
||||
if (audioPlayer && songUrl) await audioPlayer.play?.();
|
||||
@@ -53,12 +68,10 @@ const RecordedPlayback = ({ route }) => {
|
||||
};
|
||||
start();
|
||||
return () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) audioPlayer.pause?.();
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
} catch (e) {}
|
||||
playbackEndedRef.current = false;
|
||||
void stopPlayback();
|
||||
};
|
||||
}, [audioPlayer, videoPlayer, songUrl]);
|
||||
}, [audioPlayer, songUrl, stopPlayback, videoPlayer]);
|
||||
|
||||
// Poll from audio player for progress display; keep video in sync if drifting
|
||||
useEffect(() => {
|
||||
@@ -66,7 +79,8 @@ const RecordedPlayback = ({ route }) => {
|
||||
try {
|
||||
const dur = (audioPlayer?.duration || 0) * 1000;
|
||||
const pos = (audioPlayer?.currentTime || 0) * 1000;
|
||||
setProgressInfo({ pos, dur });
|
||||
const playing = !!audioPlayer?.playing || !!videoPlayer?.playing;
|
||||
setProgressInfo({ pos, dur, isPlaying: playing });
|
||||
|
||||
// basic drift correction: if desync > 300ms, align video
|
||||
if (videoPlayer && !Number.isNaN(videoPlayer.currentTime)) {
|
||||
@@ -94,6 +108,7 @@ const RecordedPlayback = ({ route }) => {
|
||||
const onSeekStart = async () => {
|
||||
try {
|
||||
wasPlayingBeforeSeek.current = !!audioPlayer?.playing;
|
||||
playbackEndedRef.current = false;
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
} catch (e) {}
|
||||
@@ -113,6 +128,47 @@ const RecordedPlayback = ({ route }) => {
|
||||
: 0;
|
||||
}, [progressInfo]);
|
||||
|
||||
useEffect(() => {
|
||||
const duration = progressInfo?.dur || 0;
|
||||
if (!duration) return;
|
||||
const position = progressInfo?.pos || 0;
|
||||
if (playbackEndedRef.current && duration - position > 1000) {
|
||||
playbackEndedRef.current = false;
|
||||
return;
|
||||
}
|
||||
const remaining = Math.max(0, duration - position);
|
||||
if (remaining <= 400 && !playbackEndedRef.current) {
|
||||
playbackEndedRef.current = true;
|
||||
void stopPlayback();
|
||||
}
|
||||
}, [progressInfo, stopPlayback]);
|
||||
|
||||
const handleTogglePlayback = async () => {
|
||||
try {
|
||||
const duration = progressInfo?.dur || 0;
|
||||
const position = progressInfo?.pos || 0;
|
||||
const isAtEnd = duration > 0 && duration - position < 350;
|
||||
const isCurrentlyPlaying =
|
||||
!!audioPlayer?.playing || !!videoPlayer?.playing;
|
||||
|
||||
if (isCurrentlyPlaying) {
|
||||
playbackEndedRef.current = false;
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
return;
|
||||
}
|
||||
|
||||
if (isAtEnd) {
|
||||
if (audioPlayer) await audioPlayer.seekTo?.(0);
|
||||
if (videoPlayer) videoPlayer.currentTime = 0;
|
||||
}
|
||||
|
||||
playbackEndedRef.current = false;
|
||||
if (songUrl && audioPlayer) await audioPlayer.play?.();
|
||||
if (videoPlayer) videoPlayer.play();
|
||||
} catch (e) {}
|
||||
};
|
||||
|
||||
return (
|
||||
<Page backgroundImg={background.playbackBG2} headerType="NONE">
|
||||
<MusicLandHeader progress={19} onPressBack={goBack} />
|
||||
@@ -144,6 +200,26 @@ const RecordedPlayback = ({ route }) => {
|
||||
onSeekStart={onSeekStart}
|
||||
onSeekEnd={onSeekEnd}
|
||||
/>
|
||||
<Pressable
|
||||
onPress={handleTogglePlayback}
|
||||
style={{
|
||||
width: 72,
|
||||
height: 72,
|
||||
borderRadius: 36,
|
||||
alignSelf: "center",
|
||||
marginTop: 4,
|
||||
alignItems: "center",
|
||||
justifyContent: "center",
|
||||
backgroundColor: "rgba(10, 5, 24, 0.65)",
|
||||
borderWidth: 1,
|
||||
borderColor: "#F94697",
|
||||
}}
|
||||
>
|
||||
<Image
|
||||
source={progressInfo.isPlaying ? icons.pause : icons.play}
|
||||
style={{ width: 26, height: 26 }}
|
||||
/>
|
||||
</Pressable>
|
||||
</View>
|
||||
<View
|
||||
style={{ width: "80%", alignSelf: "center", marginTop: 4, gap: 12 }}
|
||||
@@ -162,8 +238,7 @@ const RecordedPlayback = ({ route }) => {
|
||||
title="Recommencer"
|
||||
onPress={async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
if (videoPlayer?.playing) videoPlayer.pause();
|
||||
await stopPlayback();
|
||||
} catch (e) {}
|
||||
try {
|
||||
if (videoUri) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
||||
import { Text, View } from "react-native";
|
||||
import { background } from "../../assets";
|
||||
import BorderGradientButton from "../../components/BorderGradientButton";
|
||||
@@ -43,6 +43,7 @@ const RecordedPlayback = ({ route }) => {
|
||||
|
||||
// Optionnel: si tu veux quand même un rendu vidéo sur web si tu as une source
|
||||
const videoElRef = useRef(null);
|
||||
const playbackEndedRef = useRef(false);
|
||||
|
||||
const [progress, setProgress] = useState({
|
||||
posS: 0, // secondes
|
||||
@@ -50,9 +51,20 @@ const RecordedPlayback = ({ route }) => {
|
||||
playing: false,
|
||||
});
|
||||
|
||||
const stopPlayback = useCallback(async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
} catch {}
|
||||
try {
|
||||
if (videoElRef.current && !videoElRef.current.paused) {
|
||||
videoElRef.current.pause();
|
||||
}
|
||||
} catch {}
|
||||
}, [audioPlayer]);
|
||||
|
||||
// Démarrage / arrêt
|
||||
useEffect(() => {
|
||||
let mounted = true;
|
||||
playbackEndedRef.current = false;
|
||||
const start = async () => {
|
||||
try {
|
||||
if (audioPlayer && songUrl) {
|
||||
@@ -68,17 +80,10 @@ const RecordedPlayback = ({ route }) => {
|
||||
start();
|
||||
|
||||
return () => {
|
||||
mounted = false;
|
||||
try {
|
||||
if (audioPlayer?.playing) audioPlayer.pause?.();
|
||||
} catch {}
|
||||
try {
|
||||
if (videoElRef.current) {
|
||||
videoElRef.current.pause();
|
||||
}
|
||||
} catch {}
|
||||
playbackEndedRef.current = false;
|
||||
void stopPlayback();
|
||||
};
|
||||
}, [audioPlayer, songUrl, videoUri]);
|
||||
}, [audioPlayer, songUrl, stopPlayback, videoUri]);
|
||||
|
||||
// Boucle de progression + éventuelle sync de la vidéo si fournie
|
||||
useEffect(() => {
|
||||
@@ -115,6 +120,21 @@ const RecordedPlayback = ({ route }) => {
|
||||
return progress.durS > 0 ? Math.min(1, progress.posS / progress.durS) : 0;
|
||||
}, [progress]);
|
||||
|
||||
useEffect(() => {
|
||||
const duration = progress?.durS || 0;
|
||||
if (!duration) return;
|
||||
const position = progress?.posS || 0;
|
||||
if (playbackEndedRef.current && duration - position > 1) {
|
||||
playbackEndedRef.current = false;
|
||||
return;
|
||||
}
|
||||
const remaining = Math.max(0, duration - position);
|
||||
if (remaining <= 0.35 && !playbackEndedRef.current) {
|
||||
playbackEndedRef.current = true;
|
||||
void stopPlayback();
|
||||
}
|
||||
}, [progress, stopPlayback]);
|
||||
|
||||
const onSeek = async (ratio) => {
|
||||
try {
|
||||
const durS = Number(progress.durS || 0);
|
||||
@@ -132,6 +152,7 @@ const RecordedPlayback = ({ route }) => {
|
||||
const onSeekStart = async () => {
|
||||
try {
|
||||
wasPlayingRef.current = !!audioPlayer?.playing;
|
||||
playbackEndedRef.current = false;
|
||||
if (audioPlayer?.playing) await audioPlayer.pause?.();
|
||||
if (videoElRef.current && !videoElRef.current.paused) {
|
||||
videoElRef.current.pause();
|
||||
@@ -244,13 +265,9 @@ const RecordedPlayback = ({ route }) => {
|
||||
/>
|
||||
<BorderGradientButton
|
||||
title="Recommencer"
|
||||
onPress={() => {
|
||||
onPress={async () => {
|
||||
try {
|
||||
if (audioPlayer?.playing) audioPlayer.pause?.();
|
||||
} catch {}
|
||||
try {
|
||||
if (videoElRef.current && !videoElRef.current.paused)
|
||||
videoElRef.current.pause();
|
||||
await stopPlayback();
|
||||
} catch {}
|
||||
navigate(Routes.RecordPlayback, { project });
|
||||
}}
|
||||
|
||||
Reference in New Issue
Block a user