more fixes web + mobile

This commit is contained in:
Thomas Demirdjian
2025-11-20 11:26:24 +01:00
parent fe9b2b9e8d
commit 70c94c97b4
32 changed files with 1214 additions and 226 deletions
+121 -13
View File
@@ -25,6 +25,7 @@ import { FONT_FAMILY } from "../../styles/Fonts";
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
import { background } from "../../assets";
import RestartSpinnerIcon from "../../assets/UI/RestartSpinnerIcon";
import { registerBlobUrl, releaseBlobUrl } from "../../utils/blobUrlCache";
const TIME_BEFORE_INCREMENT_MS = 20000;
const WEB_PREVIEW_WIDTH = 360;
@@ -82,6 +83,7 @@ const RecordPlayback = ({ route }) => {
const stopRecordingPromiseRef = useRef(null);
const stopRecordingResolveRef = useRef(null);
const recordedUrlRef = useRef(null);
const preserveRecordingForNextScreenRef = useRef(false);
const originalLoopingValueRef = useRef({ hasValue: false, value: false });
const latestLoopingValueRef = useRef(isLooping ?? false);
const [mediaReady, setMediaReady] = useState(false);
@@ -91,14 +93,18 @@ const RecordPlayback = ({ route }) => {
const [countdown, setCountdown] = useState(0);
const [isPreparing, setIsPreparing] = useState(false);
const [isRecording, setIsRecording] = useState(false);
const [isPaused, setIsPaused] = useState(false);
const [pos, setPos] = useState(0);
const [dur, setDur] = useState(0);
// timers / refs
const isPausedRef = useRef(false);
const progressTimerRef = useRef(null);
const listenTimerRef = useRef(null);
const countdownTimerRef = useRef(null);
const perfStartRef = useRef(null);
const pausedAtRef = useRef(null);
const pausedMsRef = useRef(0);
const listenedMsRef = useRef(0);
const viewsIncrementedRef = useRef(false);
const correctedInitialJumpRef = useRef(false);
@@ -106,6 +112,9 @@ const RecordPlayback = ({ route }) => {
useEffect(() => {
latestLoopingValueRef.current = isLooping ?? false;
}, [isLooping]);
useEffect(() => {
isPausedRef.current = isPaused;
}, [isPaused]);
// Lyrics
const alignedWords = useMemo(() => {
@@ -131,6 +140,10 @@ const RecordPlayback = ({ route }) => {
const resetUI = () => {
setIsPreparing(false);
setIsRecording(false);
setIsPaused(false);
isPausedRef.current = false;
pausedAtRef.current = null;
pausedMsRef.current = 0;
setCountdown(0);
setPos(0);
setDur(0);
@@ -141,12 +154,13 @@ const RecordPlayback = ({ route }) => {
};
const releaseRecordingUrl = useCallback(() => {
if (recordedUrlRef.current) {
try {
URL.revokeObjectURL(recordedUrlRef.current);
} catch {}
recordedUrlRef.current = null;
if (!recordedUrlRef.current) {
preserveRecordingForNextScreenRef.current = false;
return;
}
releaseBlobUrl(recordedUrlRef.current);
recordedUrlRef.current = null;
preserveRecordingForNextScreenRef.current = false;
}, []);
const startRecorder = useCallback(() => {
@@ -203,6 +217,7 @@ const RecordPlayback = ({ route }) => {
});
url = URL.createObjectURL(blob);
recordedUrlRef.current = url;
registerBlobUrl(url, blob);
} else {
releaseRecordingUrl();
}
@@ -273,8 +288,10 @@ const RecordPlayback = ({ route }) => {
} catch (e) {}
try {
await stopRecorderAndGetUrl();
releaseRecordingUrl();
} catch (e) {}
if (!preserveRecordingForNextScreenRef.current) {
releaseRecordingUrl();
}
}, [player, releaseRecordingUrl, stopRecorderAndGetUrl]);
const resetSessionRef = useRef(resetSession);
@@ -410,11 +427,14 @@ const RecordPlayback = ({ route }) => {
mediaStreamRef.current.getTracks().forEach((track) => track.stop());
mediaStreamRef.current = null;
}
releaseRecordingUrl();
if (!preserveRecordingForNextScreenRef.current) {
releaseRecordingUrl();
}
};
}, [releaseRecordingUrl]);
}, [releaseRecordingUrl, preserveRecordingForNextScreenRef]);
const stopAndNavigate = useCallback(async () => {
preserveRecordingForNextScreenRef.current = true;
clearAllTimers();
try {
await player?.pause?.();
@@ -429,6 +449,10 @@ const RecordPlayback = ({ route }) => {
} catch (e) {}
setIsRecording(false);
setIsPaused(false);
isPausedRef.current = false;
pausedAtRef.current = null;
pausedMsRef.current = 0;
navigate(Routes.RecordedPlayback, { project, videoUri: videoUrl || null });
}, [dur, player, pos, project, stopRecorderAndGetUrl]);
@@ -438,11 +462,19 @@ const RecordPlayback = ({ route }) => {
progressTimerRef.current = setInterval(async () => {
const rawDur = toSeconds(player?.duration);
const rawPos = toSeconds(player?.currentTime);
const pausedSince =
pausedAtRef.current != null
? Math.max(0, performance.now() - pausedAtRef.current)
: 0;
const pausedTotal = pausedMsRef.current + pausedSince;
// fallback monotone si le player ne donne rien
const fallback =
perfStartRef.current != null
? Math.max(0, (performance.now() - perfStartRef.current) / 1000)
? Math.max(
0,
(performance.now() - perfStartRef.current - pausedTotal) / 1000
)
: 0;
let nextDur = rawDur > 0 ? rawDur : dur || 0;
@@ -464,7 +496,7 @@ const RecordPlayback = ({ route }) => {
const timeSinceStart =
perfStartRef.current != null
? performance.now() - perfStartRef.current
? Math.max(0, performance.now() - perfStartRef.current - pausedTotal)
: null;
const shouldTrustFallback =
@@ -484,6 +516,10 @@ const RecordPlayback = ({ route }) => {
setDur(nextDur);
setPos(nextPos);
if (isPausedRef.current) {
return;
}
if (nextDur > 0 && nextPos >= nextDur - 0.3) {
void stopAndNavigate();
}
@@ -516,6 +552,10 @@ const RecordPlayback = ({ route }) => {
const startPlayback = useCallback(async () => {
try {
setIsPaused(false);
isPausedRef.current = false;
pausedAtRef.current = null;
pausedMsRef.current = 0;
await player?.pause?.(); // s'assure qu'on repart propre
await player?.seekTo?.(0); // tente un seek d'amorçage
const recorderStarted = startRecorder();
@@ -530,6 +570,9 @@ const RecordPlayback = ({ route }) => {
} catch (e) {
setIsRecording(false);
setIsPreparing(false);
setIsPaused(false);
isPausedRef.current = false;
pausedAtRef.current = null;
const msg = String(e?.message || e || "");
if (/not supported on the simulator/i.test(msg)) {
alert(
@@ -557,6 +600,8 @@ const RecordPlayback = ({ route }) => {
const startCountdownThenRecord = useCallback(async () => {
if (!songUrl || !canRecord) return;
await resetSession();
setIsPaused(false);
isPausedRef.current = false;
setIsPreparing(true);
setCountdown(5);
if (countdownTimerRef.current) clearInterval(countdownTimerRef.current);
@@ -575,6 +620,43 @@ const RecordPlayback = ({ route }) => {
}, 1000);
}, [songUrl, canRecord, resetSession, startPlayback]);
const handleTogglePause = useCallback(async () => {
try {
if (!isRecording) return;
if (!isPausedRef.current) {
isPausedRef.current = true;
setIsPaused(true);
pausedAtRef.current = performance.now();
try {
await player?.pause?.();
} catch (e) {}
try {
const recorder = mediaRecorderRef.current;
if (recorder && recorder.state === "recording") {
recorder.pause?.();
}
} catch (e) {}
return;
}
const pausedAt = pausedAtRef.current;
if (pausedAt != null) {
pausedMsRef.current += performance.now() - pausedAt;
pausedAtRef.current = null;
}
isPausedRef.current = false;
setIsPaused(false);
try {
const recorder = mediaRecorderRef.current;
if (recorder && recorder.state === "paused") {
recorder.resume?.();
}
} catch (e) {}
try {
await player?.play?.();
} catch (e) {}
} catch (e) {}
}, [isRecording, player]);
const handleRestartRecording = useCallback(async () => {
try {
await startCountdownThenRecord();
@@ -586,9 +668,7 @@ const RecordPlayback = ({ route }) => {
return (
<Page
style={{ flex: 1 }}
width={"100%"}
maxWidth={800}
containerStyle={{ margin: 0, padding: 0, backgroundColor: Palette.tran }}
containerStyle={{ backgroundColor: Palette.tran }}
headerType="NONE"
backgroundImg={background.playbackBG2}
>
@@ -766,6 +846,34 @@ const RecordPlayback = ({ route }) => {
<RestartSpinnerIcon />
</TouchableOpacity>
</View>
{isRecording && (
<TouchableOpacity
activeOpacity={0.9}
onPress={handleTogglePause}
style={{
marginTop: 14,
paddingVertical: 10,
paddingHorizontal: 18,
borderRadius: 999,
backgroundColor: Palette.white,
shadowColor: "#000000",
shadowOpacity: 0.12,
shadowRadius: 10,
shadowOffset: { width: 0, height: 4 },
elevation: 4,
}}
>
<Text
style={{
color: Palette.black,
fontFamily: FONT_FAMILY.InterSemiBold,
fontSize: 14,
}}
>
{isPaused ? "Reprendre" : "Mettre en pause"}
</Text>
</TouchableOpacity>
)}
</View>
)}