feat: clean playback web

This commit is contained in:
2025-11-17 11:13:26 +01:00
parent add6a707a2
commit d2094b453e
+24 -194
View File
@@ -15,7 +15,6 @@ import GradientButton from "../../components/GradientButton";
import KaraokeLyrics from "../../components/KaraokeLyrics"; import KaraokeLyrics from "../../components/KaraokeLyrics";
import MusicLandHeader from "../../components/MusicLandHeader"; import MusicLandHeader from "../../components/MusicLandHeader";
import { increment, projectsRef } from "../../config/firebase"; import { increment, projectsRef } from "../../config/firebase";
import { isWeb } from "../../hooks/useLayoutType";
import usePlayer from "../../hooks/usePlayer"; import usePlayer from "../../hooks/usePlayer";
import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer"; import useSharedAudioPlayer from "../../hooks/useSharedAudioPlayer";
import Page from "../../layouts/Page"; import Page from "../../layouts/Page";
@@ -25,11 +24,9 @@ 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";
import { background } from "../../assets"; import { background } from "../../assets";
import playbackBG2 from "../../assets/UI/playbackBG2.png";
import RestartSpinnerIcon from "../../assets/UI/RestartSpinnerIcon"; import RestartSpinnerIcon from "../../assets/UI/RestartSpinnerIcon";
const TIME_BEFORE_INCREMENT_MS = 20000; const TIME_BEFORE_INCREMENT_MS = 20000;
const LOG_PREFIX = "[RecordPlayback.web]";
const WEB_PREVIEW_WIDTH = 360; const WEB_PREVIEW_WIDTH = 360;
const toSeconds = (v) => { const toSeconds = (v) => {
@@ -105,7 +102,6 @@ const RecordPlayback = ({ route }) => {
const listenedMsRef = useRef(0); const listenedMsRef = useRef(0);
const viewsIncrementedRef = useRef(false); const viewsIncrementedRef = useRef(false);
const correctedInitialJumpRef = useRef(false); const correctedInitialJumpRef = useRef(false);
const progressDebugCounterRef = useRef(0);
useEffect(() => { useEffect(() => {
latestLoopingValueRef.current = isLooping ?? false; latestLoopingValueRef.current = isLooping ?? false;
@@ -123,7 +119,6 @@ const RecordPlayback = ({ route }) => {
}, [project?.musicTimestamps, songIndex]); }, [project?.musicTimestamps, songIndex]);
const clearAllTimers = () => { const clearAllTimers = () => {
console.log(LOG_PREFIX, "clearAllTimers");
if (progressTimerRef.current) clearInterval(progressTimerRef.current); if (progressTimerRef.current) clearInterval(progressTimerRef.current);
if (listenTimerRef.current) clearInterval(listenTimerRef.current); if (listenTimerRef.current) clearInterval(listenTimerRef.current);
if (countdownTimerRef.current) clearInterval(countdownTimerRef.current); if (countdownTimerRef.current) clearInterval(countdownTimerRef.current);
@@ -134,7 +129,6 @@ const RecordPlayback = ({ route }) => {
}; };
const resetUI = () => { const resetUI = () => {
console.log(LOG_PREFIX, "resetUI");
setIsPreparing(false); setIsPreparing(false);
setIsRecording(false); setIsRecording(false);
setCountdown(0); setCountdown(0);
@@ -157,7 +151,6 @@ const RecordPlayback = ({ route }) => {
const startRecorder = useCallback(() => { const startRecorder = useCallback(() => {
if (!mediaStreamRef.current) { if (!mediaStreamRef.current) {
console.log(LOG_PREFIX, "mediaRecorder:start:noStream");
return false; return false;
} }
@@ -168,13 +161,7 @@ const RecordPlayback = ({ route }) => {
) { ) {
mediaRecorderRef.current.stop(); mediaRecorderRef.current.stop();
} }
} catch (e) { } catch (e) {}
console.log(
LOG_PREFIX,
"mediaRecorder:stopPrevious:error",
String(e?.message || e || ""),
);
}
recordedChunksRef.current = []; recordedChunksRef.current = [];
releaseRecordingUrl(); releaseRecordingUrl();
@@ -187,11 +174,6 @@ const RecordPlayback = ({ route }) => {
? new MediaRecorder(mediaStreamRef.current, { mimeType }) ? new MediaRecorder(mediaStreamRef.current, { mimeType })
: new MediaRecorder(mediaStreamRef.current); : new MediaRecorder(mediaStreamRef.current);
} catch (e) { } catch (e) {
console.log(
LOG_PREFIX,
"mediaRecorder:createError",
String(e?.message || e || ""),
);
setMediaError(e instanceof Error ? e : new Error(String(e || ""))); setMediaError(e instanceof Error ? e : new Error(String(e || "")));
return false; return false;
} }
@@ -209,11 +191,6 @@ const RecordPlayback = ({ route }) => {
recorder.onerror = (event) => { recorder.onerror = (event) => {
const err = event?.error || event; const err = event?.error || event;
console.log(
LOG_PREFIX,
"mediaRecorder:error",
String(err?.message || err || ""),
);
setMediaError(err instanceof Error ? err : new Error(String(err || ""))); setMediaError(err instanceof Error ? err : new Error(String(err || "")));
}; };
@@ -230,11 +207,6 @@ const RecordPlayback = ({ route }) => {
releaseRecordingUrl(); releaseRecordingUrl();
} }
} catch (e) { } catch (e) {
console.log(
LOG_PREFIX,
"mediaRecorder:onstop:createBlobError",
String(e?.message || e || ""),
);
releaseRecordingUrl(); releaseRecordingUrl();
} }
if (stopRecordingResolveRef.current) { if (stopRecordingResolveRef.current) {
@@ -248,16 +220,8 @@ const RecordPlayback = ({ route }) => {
try { try {
recorder.start(1000); recorder.start(1000);
console.log(LOG_PREFIX, "mediaRecorder:start", {
mimeType: recorder.mimeType,
});
return true; return true;
} catch (e) { } catch (e) {
console.log(
LOG_PREFIX,
"mediaRecorder:startError",
String(e?.message || e || ""),
);
if (stopRecordingResolveRef.current) { if (stopRecordingResolveRef.current) {
stopRecordingResolveRef.current(null); stopRecordingResolveRef.current(null);
stopRecordingResolveRef.current = null; stopRecordingResolveRef.current = null;
@@ -282,13 +246,7 @@ const RecordPlayback = ({ route }) => {
} }
recorder.stop(); recorder.stop();
} }
} catch (e) { } catch (e) {}
console.log(
LOG_PREFIX,
"mediaRecorder:stopError",
String(e?.message || e || ""),
);
}
if (!waitPromise) { if (!waitPromise) {
return recordedUrlRef.current || null; return recordedUrlRef.current || null;
@@ -298,11 +256,6 @@ const RecordPlayback = ({ route }) => {
try { try {
url = await waitPromise; url = await waitPromise;
} catch (e) { } catch (e) {
console.log(
LOG_PREFIX,
"mediaRecorder:waitStopError",
String(e?.message || e || ""),
);
} finally { } finally {
stopRecordingPromiseRef.current = null; stopRecordingPromiseRef.current = null;
stopRecordingResolveRef.current = null; stopRecordingResolveRef.current = null;
@@ -312,31 +265,16 @@ const RecordPlayback = ({ route }) => {
}, []); }, []);
const resetSession = useCallback(async () => { const resetSession = useCallback(async () => {
console.log(LOG_PREFIX, "resetSession:start");
clearAllTimers(); clearAllTimers();
resetUI(); resetUI();
try { try {
await player?.pause?.(); await player?.pause?.();
await player?.seekTo?.(0); await player?.seekTo?.(0);
console.log(LOG_PREFIX, "resetSession:playerReset"); } catch (e) {}
} catch (e) {
console.log(
LOG_PREFIX,
"resetSession:error",
String(e?.message || e || ""),
);
}
try { try {
await stopRecorderAndGetUrl(); await stopRecorderAndGetUrl();
releaseRecordingUrl(); releaseRecordingUrl();
} catch (e) { } catch (e) {}
console.log(
LOG_PREFIX,
"resetSession:recorderError",
String(e?.message || e || ""),
);
}
console.log(LOG_PREFIX, "resetSession:end");
}, [player, releaseRecordingUrl, stopRecorderAndGetUrl]); }, [player, releaseRecordingUrl, stopRecorderAndGetUrl]);
const resetSessionRef = useRef(resetSession); const resetSessionRef = useRef(resetSession);
@@ -363,34 +301,25 @@ const RecordPlayback = ({ route }) => {
setLooping(!!originalLoopingValueRef.current.value); setLooping(!!originalLoopingValueRef.current.value);
} }
}; };
}, [setLooping]), }, [setLooping])
); );
useFocusEffect( useFocusEffect(
useCallback(() => { useCallback(() => {
void resetSessionRef.current?.(); void resetSessionRef.current?.();
return () => { return () => {
console.log(LOG_PREFIX, "focusEffect:cleanup");
void resetSessionRef.current?.(); void resetSessionRef.current?.();
}; };
}, []), }, [])
); );
useEffect(() => { useEffect(() => {
(async () => { (async () => {
try { try {
if (!cameraPermission?.granted) { if (!cameraPermission?.granted) {
console.log(LOG_PREFIX, "requestingCameraPermission");
await requestCameraPermission(); await requestCameraPermission();
console.log(LOG_PREFIX, "requestingCameraPermission:done");
}
} catch (e) {
console.log(
LOG_PREFIX,
"requestingCameraPermission:error",
String(e?.message || e || ""),
);
} }
} catch (e) {}
})(); })();
return () => { return () => {
clearAllTimers(); clearAllTimers();
@@ -413,7 +342,7 @@ const RecordPlayback = ({ route }) => {
!navigator.mediaDevices?.getUserMedia !navigator.mediaDevices?.getUserMedia
) { ) {
setMediaError( setMediaError(
new Error("La capture vidéo n'est pas supportée sur ce navigateur"), new Error("La capture vidéo n'est pas supportée sur ce navigateur")
); );
setMediaReady(false); setMediaReady(false);
return; return;
@@ -459,13 +388,7 @@ const RecordPlayback = ({ route }) => {
if (playPromise && typeof playPromise.catch === "function") { if (playPromise && typeof playPromise.catch === "function") {
playPromise.catch(() => {}); playPromise.catch(() => {});
} }
} catch (e) { } catch (e) {}
console.log(
LOG_PREFIX,
"previewVideo:attachError",
String(e?.message || e || ""),
);
}
} else { } else {
try { try {
video.srcObject = null; video.srcObject = null;
@@ -492,22 +415,10 @@ const RecordPlayback = ({ route }) => {
}, [releaseRecordingUrl]); }, [releaseRecordingUrl]);
const stopAndNavigate = useCallback(async () => { const stopAndNavigate = useCallback(async () => {
console.log(LOG_PREFIX, "stopAndNavigate:start", {
pos,
dur,
listenedMs: listenedMsRef.current,
});
clearAllTimers(); clearAllTimers();
try { try {
await player?.pause?.(); await player?.pause?.();
console.log(LOG_PREFIX, "stopAndNavigate:playerPaused"); } catch (e) {}
} catch (e) {
console.log(
LOG_PREFIX,
"stopAndNavigate:pauseError",
String(e?.message || e || ""),
);
}
let videoUrl = null; let videoUrl = null;
try { try {
@@ -515,26 +426,14 @@ const RecordPlayback = ({ route }) => {
if (!videoUrl) { if (!videoUrl) {
videoUrl = recordedUrlRef.current || null; videoUrl = recordedUrlRef.current || null;
} }
} catch (e) { } catch (e) {}
console.log(
LOG_PREFIX,
"stopAndNavigate:recorderError",
String(e?.message || e || ""),
);
}
setIsRecording(false); setIsRecording(false);
console.log(LOG_PREFIX, "stopAndNavigate:navigate", {
route: Routes.RecordedPlayback,
hasVideo: !!videoUrl,
});
navigate(Routes.RecordedPlayback, { project, videoUri: videoUrl || null }); navigate(Routes.RecordedPlayback, { project, videoUri: videoUrl || null });
}, [dur, player, pos, project, stopRecorderAndGetUrl]); }, [dur, player, pos, project, stopRecorderAndGetUrl]);
const startProgressLoop = useCallback(() => { const startProgressLoop = useCallback(() => {
if (progressTimerRef.current) clearInterval(progressTimerRef.current); if (progressTimerRef.current) clearInterval(progressTimerRef.current);
progressDebugCounterRef.current = 0;
console.log(LOG_PREFIX, "startProgressLoop");
progressTimerRef.current = setInterval(async () => { progressTimerRef.current = setInterval(async () => {
const rawDur = toSeconds(player?.duration); const rawDur = toSeconds(player?.duration);
@@ -560,14 +459,7 @@ const RecordPlayback = ({ route }) => {
await player?.seekTo?.(0); await player?.seekTo?.(0);
correctedInitialJumpRef.current = true; correctedInitialJumpRef.current = true;
nextPos = 0; nextPos = 0;
console.log(LOG_PREFIX, "progressLoop:correctedInitialJump"); } catch (e) {}
} catch (e) {
console.log(
LOG_PREFIX,
"progressLoop:correctInitialJumpError",
String(e?.message || e || ""),
);
}
} }
const timeSinceStart = const timeSinceStart =
@@ -584,12 +476,6 @@ const RecordPlayback = ({ route }) => {
nextPos > fallback + 1)); nextPos > fallback + 1));
if (shouldTrustFallback) { if (shouldTrustFallback) {
console.log(LOG_PREFIX, "progressLoop:useFallback", {
rawPos,
fallback,
nextPosBeforeFallback: nextPos,
timeSinceStart,
});
nextPos = fallback; nextPos = fallback;
} }
@@ -598,28 +484,7 @@ const RecordPlayback = ({ route }) => {
setDur(nextDur); setDur(nextDur);
setPos(nextPos); setPos(nextPos);
progressDebugCounterRef.current += 1;
if (
progressDebugCounterRef.current <= 12 ||
nextPos >= nextDur - 0.5 ||
progressDebugCounterRef.current % 20 === 0
) {
console.log(LOG_PREFIX, "progressLoop:tick", {
tick: progressDebugCounterRef.current,
rawDur,
rawPos,
fallback,
nextDur,
nextPos,
playing: !!player?.playing,
});
}
if (nextDur > 0 && nextPos >= nextDur - 0.3) { if (nextDur > 0 && nextPos >= nextDur - 0.3) {
console.log(LOG_PREFIX, "progressLoop:willStop", {
nextDur,
nextPos,
});
void stopAndNavigate(); void stopAndNavigate();
} }
}, 250); }, 250);
@@ -636,58 +501,33 @@ const RecordPlayback = ({ route }) => {
listenedMsRef.current >= TIME_BEFORE_INCREMENT_MS listenedMsRef.current >= TIME_BEFORE_INCREMENT_MS
) { ) {
viewsIncrementedRef.current = true; viewsIncrementedRef.current = true;
console.log(LOG_PREFIX, "listenLoop:incrementViews");
if (project?.id) { if (project?.id) {
try { try {
await projectsRef await projectsRef
.doc(project.id) .doc(project.id)
.set({ views: increment(1) }, { merge: true }); .set({ views: increment(1) }, { merge: true });
console.log(LOG_PREFIX, "listenLoop:incrementViews:done"); } catch (e) {}
} catch (e) {
console.log(
LOG_PREFIX,
"listenLoop:incrementViews:error",
String(e?.message || e || ""),
);
} }
} }
} }
} } catch (e) {}
} catch (e) {
console.log(
LOG_PREFIX,
"listenLoop:error",
String(e?.message || e || ""),
);
}
}, 500); }, 500);
}, [player, project?.id]); }, [player, project?.id]);
const startPlayback = useCallback(async () => { const startPlayback = useCallback(async () => {
console.log(LOG_PREFIX, "startPlayback:start");
try { try {
await player?.pause?.(); // s'assure qu'on repart propre await player?.pause?.(); // s'assure qu'on repart propre
await player?.seekTo?.(0); // tente un seek d'amorçage await player?.seekTo?.(0); // tente un seek d'amorçage
const recorderStarted = startRecorder(); const recorderStarted = startRecorder();
if (!recorderStarted) { if (!recorderStarted) {
console.log(LOG_PREFIX, "startPlayback:recorderNotStarted");
} }
await player?.play?.(); // attend la promesse → l'élément est prêt await player?.play?.(); // attend la promesse → l'élément est prêt
perfStartRef.current = perfStartRef.current ?? performance.now(); perfStartRef.current = perfStartRef.current ?? performance.now();
correctedInitialJumpRef.current = false; // autorise la correction 1-shot correctedInitialJumpRef.current = false; // autorise la correction 1-shot
setIsRecording(true); setIsRecording(true);
console.log(LOG_PREFIX, "startPlayback:playing", {
duration: toSeconds(player?.duration),
currentTime: toSeconds(player?.currentTime),
});
startProgressLoop(); startProgressLoop();
startListenLoop(); startListenLoop();
} catch (e) { } catch (e) {
console.log(
LOG_PREFIX,
"startPlayback:error",
String(e?.message || e || ""),
);
setIsRecording(false); setIsRecording(false);
setIsPreparing(false); setIsPreparing(false);
const msg = String(e?.message || e || ""); const msg = String(e?.message || e || "");
@@ -705,7 +545,7 @@ const RecordPlayback = ({ route }) => {
}, },
}, },
], ],
{ cancelable: false }, { cancelable: false }
); );
} }
} }
@@ -716,7 +556,6 @@ const RecordPlayback = ({ route }) => {
const startCountdownThenRecord = useCallback(async () => { const startCountdownThenRecord = useCallback(async () => {
if (!songUrl || !canRecord) return; if (!songUrl || !canRecord) return;
console.log(LOG_PREFIX, "startCountdownThenRecord");
await resetSession(); await resetSession();
setIsPreparing(true); setIsPreparing(true);
setCountdown(5); setCountdown(5);
@@ -724,7 +563,6 @@ const RecordPlayback = ({ route }) => {
countdownTimerRef.current = setInterval(() => { countdownTimerRef.current = setInterval(() => {
setCountdown((c) => { setCountdown((c) => {
if (c <= 1) { if (c <= 1) {
console.log(LOG_PREFIX, "countdown:launchRecording");
clearAllTimers(); clearAllTimers();
setIsPreparing(false); setIsPreparing(false);
requestAnimationFrame(() => { requestAnimationFrame(() => {
@@ -732,27 +570,16 @@ const RecordPlayback = ({ route }) => {
}); });
return 0; return 0;
} }
console.log(LOG_PREFIX, "countdown:tick", c - 1);
return c - 1; return c - 1;
}); });
}, 1000); }, 1000);
}, [songUrl, canRecord, resetSession, startPlayback]); }, [songUrl, canRecord, resetSession, startPlayback]);
const handleRestartRecording = useCallback(async () => { const handleRestartRecording = useCallback(async () => {
console.log(LOG_PREFIX, "restartButton:pressed", {
isPreparing,
isRecording,
});
try { try {
await startCountdownThenRecord(); await startCountdownThenRecord();
} catch (e) { } catch (e) {}
console.log( }, [startCountdownThenRecord]);
LOG_PREFIX,
"restartButton:error",
String(e?.message || e || ""),
);
}
}, [isPreparing, isRecording, startCountdownThenRecord]);
const progressRatio = dur > 0 ? Math.min(1, pos / dur) : 0; const progressRatio = dur > 0 ? Math.min(1, pos / dur) : 0;
@@ -761,20 +588,20 @@ const RecordPlayback = ({ route }) => {
style={{ flex: 1 }} style={{ flex: 1 }}
width={"100%"} width={"100%"}
maxWidth={800} maxWidth={800}
containerStyle={{ margin: 0, padding: 0, backgroundColor: Palette.black }} containerStyle={{ margin: 0, padding: 0, backgroundColor: Palette.tran }}
headerType="NONE" headerType="NONE"
backgroundImg={background.playbackBG2} backgroundImg={background.playbackBG2}
> >
<MusicLandHeader progress={9} onPressBack={goBack} /> <MusicLandHeader progress={9} onPressBack={goBack} />
<View style={{ flex: 1, alignItems: "center", justifyContent: "center" }}>
<View <View
style={{ style={{
width: isWeb ? WEB_PREVIEW_WIDTH : "100%", width: WEB_PREVIEW_WIDTH,
maxWidth: "100%", maxWidth: "100%",
aspectRatio: 9 / 16, aspectRatio: 9 / 16,
overflow: "hidden", overflow: "hidden",
borderRadius: 12, borderRadius: 12,
alignSelf: "center",
position: "relative", position: "relative",
}} }}
> >
@@ -898,7 +725,8 @@ const RecordPlayback = ({ route }) => {
position: "absolute", position: "absolute",
left: 0, left: 0,
right: 0, right: 0,
bottom: gutters, // bottom: gutters,
bottom: 0,
alignItems: "center", alignItems: "center",
justifyContent: "center", justifyContent: "center",
}} }}
@@ -994,6 +822,8 @@ const RecordPlayback = ({ route }) => {
</CreateLyricsHeader> </CreateLyricsHeader>
</View> </View>
</View> </View>
</View>
{/* </View> */}
</Page> </Page>
); );
}; };