fix playback mobile
This commit is contained in:
@@ -23,11 +23,16 @@ import { FONT_FAMILY } from "../../styles/Fonts";
|
||||
import CreateLyricsHeader from "../Writing/components/CreateLyricsHeader";
|
||||
|
||||
const TIME_BEFORE_INCREMENT_MS = 20000; // 20s
|
||||
const LOG_PREFIX = "[RecordPlayback]";
|
||||
const log =
|
||||
typeof __DEV__ === "undefined" || __DEV__
|
||||
? (...args) => console.log(LOG_PREFIX, ...args)
|
||||
: () => {};
|
||||
|
||||
const RecordPlayback = ({ route }) => {
|
||||
const { top, bottom } = useSafeAreaInsets();
|
||||
const { project } = route.params || {};
|
||||
console.log("project is : ", JSON.stringify(project, null, 2));
|
||||
log("Route params received", { hasProject: !!project });
|
||||
const projectId = project?.id;
|
||||
const songIndex = project?.songIndex;
|
||||
// Permissions
|
||||
@@ -41,6 +46,8 @@ const RecordPlayback = ({ route }) => {
|
||||
const stopRequestedRef = useRef(false);
|
||||
const startedRef = useRef(false); // empêche les doubles démarrages
|
||||
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 });
|
||||
|
||||
// Compteurs vues
|
||||
const listenedMsRef = useRef(0);
|
||||
@@ -69,6 +76,10 @@ const RecordPlayback = ({ route }) => {
|
||||
metadata: { projectId, screen: "RecordPlayback" },
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
log("Screen params", { projectId, songUrl, songIndex });
|
||||
}, [projectId, songUrl, songIndex]);
|
||||
|
||||
const musicIndex = useMemo(() => {
|
||||
const i = Number(songIndex);
|
||||
return Number.isFinite(i) && i >= 0 ? i : 0;
|
||||
@@ -140,6 +151,9 @@ const RecordPlayback = ({ route }) => {
|
||||
useEffect(() => {
|
||||
listenedMsRef.current = 0;
|
||||
incrementDoneRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
playbackStartedRef.current = false;
|
||||
log("Song URL changed, reset counters", { songUrl });
|
||||
}, [songUrl]);
|
||||
|
||||
// Poll player -> progress ring
|
||||
@@ -149,7 +163,39 @@ const RecordPlayback = ({ route }) => {
|
||||
try {
|
||||
const dur = (player?.duration || 0) * 1000;
|
||||
const pos = (player?.currentTime || 0) * 1000;
|
||||
setProgressInfo({ pos, dur });
|
||||
if (!playbackStartedRef.current && (player?.playing || pos > 0)) {
|
||||
playbackStartedRef.current = true;
|
||||
log("Playback detected", { pos, dur });
|
||||
}
|
||||
setProgressInfo((prev) => {
|
||||
if (prev.pos === pos && prev.dur === dur) return prev;
|
||||
return { pos, dur };
|
||||
});
|
||||
if (dur <= 0) {
|
||||
if (progressLogRef.current.lastDur !== 0) {
|
||||
progressLogRef.current = { bucket: -1, lastPos: pos, lastDur: 0 };
|
||||
log("Progress waiting for duration", {
|
||||
pos,
|
||||
playing: player?.playing,
|
||||
});
|
||||
} else {
|
||||
progressLogRef.current.lastPos = pos;
|
||||
}
|
||||
return;
|
||||
}
|
||||
const pct = Math.max(0, Math.min(1, pos / dur));
|
||||
const bucket = Math.floor(pct * 10);
|
||||
const { bucket: prevBucket } = progressLogRef.current;
|
||||
if (bucket !== prevBucket) {
|
||||
log("Progress update", {
|
||||
bucket,
|
||||
pct: Number.isFinite(pct) ? Number(pct.toFixed(2)) : pct,
|
||||
pos,
|
||||
dur,
|
||||
playing: player?.playing,
|
||||
});
|
||||
}
|
||||
progressLogRef.current = { bucket, lastPos: pos, lastDur: dur };
|
||||
} catch (_) {}
|
||||
}, 250);
|
||||
return () => clearInterval(id);
|
||||
@@ -172,7 +218,12 @@ const RecordPlayback = ({ route }) => {
|
||||
useEffect(() => {
|
||||
(async () => {
|
||||
try {
|
||||
if (!cameraPermission?.granted) await requestCameraPermission();
|
||||
if (!cameraPermission?.granted) {
|
||||
log("Requesting camera permission on mount");
|
||||
await requestCameraPermission();
|
||||
} else {
|
||||
log("Camera permission already granted on mount");
|
||||
}
|
||||
} catch (_) {}
|
||||
})();
|
||||
return () => {
|
||||
@@ -183,6 +234,7 @@ const RecordPlayback = ({ route }) => {
|
||||
countdownTimerRef.current = null;
|
||||
listenTimerRef.current = null;
|
||||
checkSongEndRef.current = null;
|
||||
log("Cleanup on unmount, cleared timers");
|
||||
} catch (_) {}
|
||||
};
|
||||
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
||||
@@ -197,10 +249,12 @@ const RecordPlayback = ({ route }) => {
|
||||
if (listenTimerRef.current) {
|
||||
clearInterval(listenTimerRef.current);
|
||||
listenTimerRef.current = null;
|
||||
log("listenTimerRef cleared");
|
||||
}
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
log("checkSongEndRef cleared");
|
||||
}
|
||||
|
||||
startedRef.current = false;
|
||||
@@ -208,6 +262,9 @@ const RecordPlayback = ({ route }) => {
|
||||
countdownActiveRef.current = false;
|
||||
listenedMsRef.current = 0;
|
||||
incrementDoneRef.current = false;
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
log("Session reset");
|
||||
|
||||
setIsPreparing(false);
|
||||
setIsRecording(false);
|
||||
@@ -225,6 +282,7 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
useFocusEffect(
|
||||
useCallback(() => {
|
||||
log("Screen focused, resetting session");
|
||||
void resetSession();
|
||||
return () => {};
|
||||
}, [resetSession])
|
||||
@@ -232,7 +290,11 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
// Lancer le compte à rebours (le tick décrémente uniquement)
|
||||
const startCountdownThenRecord = async () => {
|
||||
if (!songUrl) return;
|
||||
if (!songUrl) {
|
||||
log("startCountdownThenRecord aborted: missing song URL");
|
||||
return;
|
||||
}
|
||||
log("startCountdownThenRecord invoked", { projectId, songUrl });
|
||||
await resetSession();
|
||||
setIsPreparing(true);
|
||||
setShowProgress(false);
|
||||
@@ -247,6 +309,7 @@ const RecordPlayback = ({ route }) => {
|
||||
setCountdown((c) => Math.max(0, c - 1));
|
||||
}, 1000);
|
||||
countdownActiveRef.current = true;
|
||||
log("Countdown timer armed");
|
||||
};
|
||||
|
||||
// Quand le compteur a réellement démarré ET atteint 0, on démarre
|
||||
@@ -255,6 +318,7 @@ const RecordPlayback = ({ route }) => {
|
||||
if (!countdownActiveRef.current) return; // évite l'auto-start
|
||||
|
||||
if (countdown === 0 && !startedRef.current) {
|
||||
log("Countdown complete, launching recording sequence");
|
||||
startedRef.current = true;
|
||||
if (countdownTimerRef.current) {
|
||||
clearInterval(countdownTimerRef.current);
|
||||
@@ -278,20 +342,88 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
setIsRecording(true);
|
||||
setShowProgress(true);
|
||||
const recordPromise = cameraRef.current?.recordAsync?.({
|
||||
mute: true,
|
||||
maxDuration: 600,
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
log("startRecordingWithMusic", {
|
||||
hasPlayer: !!player,
|
||||
songUrl,
|
||||
hasCamera: !!cameraRef.current,
|
||||
});
|
||||
const recordPromise = (() => {
|
||||
const camera = cameraRef.current;
|
||||
if (!camera) throw new Error("Caméra indisponible");
|
||||
|
||||
if (typeof camera.recordAsync === "function") {
|
||||
log("Camera supports recordAsync");
|
||||
return camera.recordAsync({ mute: true, maxDuration: 600 });
|
||||
}
|
||||
|
||||
if (typeof camera.startRecording === "function") {
|
||||
// Expo CameraView (SDK 52) expose startRecording au lieu de recordAsync
|
||||
log("Camera using startRecording fallback");
|
||||
return new Promise((resolve, reject) => {
|
||||
let settled = false;
|
||||
try {
|
||||
camera.startRecording({
|
||||
mute: true,
|
||||
maxDuration: 600,
|
||||
onRecordingFinished: (video) => {
|
||||
settled = true;
|
||||
log("Camera onRecordingFinished", { hasUri: !!video?.uri });
|
||||
resolve(video);
|
||||
},
|
||||
onRecordingError: (error) => {
|
||||
if (settled) return;
|
||||
log("Camera onRecordingError", {
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
const err =
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(String(error || "Recording error"));
|
||||
reject(err);
|
||||
},
|
||||
});
|
||||
} catch (error) {
|
||||
log("Camera startRecording threw", {
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
reject(
|
||||
error instanceof Error
|
||||
? error
|
||||
: new Error(String(error || "Recording start failed"))
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"L'enregistrement vidéo n'est pas supporté sur cet appareil."
|
||||
);
|
||||
})();
|
||||
|
||||
if (player && songUrl) {
|
||||
try {
|
||||
log("Seeking player to start");
|
||||
await player.seekTo?.(0);
|
||||
} catch (_) {}
|
||||
await player.play?.();
|
||||
} catch (error) {
|
||||
log("player.seekTo failed", {
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
}
|
||||
try {
|
||||
await player.play?.();
|
||||
log("player.play invoked");
|
||||
} catch (error) {
|
||||
log("player.play failed", {
|
||||
message: error?.message || String(error || ""),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Incrément des vues
|
||||
if (!listenTimerRef.current && project?.id) {
|
||||
log("listenTimerRef armed", { projectId: project.id });
|
||||
listenTimerRef.current = setInterval(async () => {
|
||||
try {
|
||||
if (player?.playing) {
|
||||
@@ -301,10 +433,12 @@ const RecordPlayback = ({ route }) => {
|
||||
listenedMsRef.current >= TIME_BEFORE_INCREMENT_MS
|
||||
) {
|
||||
incrementDoneRef.current = true;
|
||||
log("Views increment threshold reached");
|
||||
try {
|
||||
await projectsRef
|
||||
.doc(project.id)
|
||||
.set({ views: increment(1) }, { merge: true });
|
||||
log("Views incremented");
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
@@ -314,15 +448,31 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
// Fin du morceau -> stop recording
|
||||
if (!checkSongEndRef.current) {
|
||||
log("Song end watcher armed");
|
||||
checkSongEndRef.current = setInterval(() => {
|
||||
try {
|
||||
if (!player) return;
|
||||
if (!player || stopRequestedRef.current) return;
|
||||
const duration = (player?.duration || 0) * 1000;
|
||||
const currentTime = (player?.currentTime || 0) * 1000;
|
||||
if (
|
||||
(!player.playing && !stopRequestedRef.current) ||
|
||||
(duration > 0 && currentTime >= duration - 600)
|
||||
!playbackStartedRef.current &&
|
||||
(player?.playing || currentTime > 0)
|
||||
) {
|
||||
playbackStartedRef.current = true;
|
||||
}
|
||||
const nearEnd = duration > 0 && currentTime >= duration - 600;
|
||||
const playbackEnded =
|
||||
playbackStartedRef.current &&
|
||||
!player.playing &&
|
||||
currentTime > 1200;
|
||||
|
||||
if (nearEnd || playbackEnded) {
|
||||
log("Requesting stopRecording", {
|
||||
reason: nearEnd ? "nearEnd" : "playbackEnded",
|
||||
duration,
|
||||
currentTime,
|
||||
playing: player?.playing,
|
||||
});
|
||||
stopRequestedRef.current = true;
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
@@ -330,6 +480,7 @@ const RecordPlayback = ({ route }) => {
|
||||
}
|
||||
try {
|
||||
cameraRef.current?.stopRecording?.();
|
||||
log("stopRecording triggered");
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {}
|
||||
@@ -337,10 +488,12 @@ const RecordPlayback = ({ route }) => {
|
||||
}
|
||||
|
||||
const video = await recordPromise;
|
||||
log("Recording promise resolved", { hasVideo: !!video?.uri });
|
||||
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
log("checkSongEndRef cleared");
|
||||
}
|
||||
try {
|
||||
if (player?.playing) await player.pause?.();
|
||||
@@ -348,15 +501,30 @@ const RecordPlayback = ({ route }) => {
|
||||
if (listenTimerRef.current) {
|
||||
clearInterval(listenTimerRef.current);
|
||||
listenTimerRef.current = null;
|
||||
log("listenTimerRef cleared");
|
||||
}
|
||||
|
||||
setIsRecording(false);
|
||||
setShowProgress(false);
|
||||
log("Recording flow completed", { hasVideo: !!video?.uri });
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
|
||||
if (video?.uri)
|
||||
if (video?.uri) {
|
||||
log("Navigating to RecordedPlayback with video", {
|
||||
uriLength: video.uri.length,
|
||||
});
|
||||
navigate(Routes.RecordedPlayback, { videoUri: video.uri, project });
|
||||
else navigate(Routes.RecordedPlayback, { project });
|
||||
} else {
|
||||
log("Navigating to RecordedPlayback without video", {
|
||||
hasVideo: !!video,
|
||||
});
|
||||
navigate(Routes.RecordedPlayback, { project });
|
||||
}
|
||||
} catch (e) {
|
||||
log("startRecordingWithMusic error", {
|
||||
message: e?.message || String(e || ""),
|
||||
});
|
||||
console.log("RecordPlayback error:", e);
|
||||
setIsRecording(false);
|
||||
setIsPreparing(false);
|
||||
@@ -364,11 +532,15 @@ const RecordPlayback = ({ route }) => {
|
||||
if (listenTimerRef.current) {
|
||||
clearInterval(listenTimerRef.current);
|
||||
listenTimerRef.current = null;
|
||||
log("listenTimerRef cleared (error path)");
|
||||
}
|
||||
if (checkSongEndRef.current) {
|
||||
clearInterval(checkSongEndRef.current);
|
||||
checkSongEndRef.current = null;
|
||||
log("checkSongEndRef cleared (error path)");
|
||||
}
|
||||
playbackStartedRef.current = false;
|
||||
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 };
|
||||
// Inform the user when using a simulator where recording isn't supported
|
||||
const msg = String(e?.message || e || "");
|
||||
if (/not supported on the simulator/i.test(msg)) {
|
||||
@@ -393,6 +565,10 @@ const RecordPlayback = ({ route }) => {
|
||||
|
||||
const permissionsGranted = !!cameraPermission?.granted;
|
||||
|
||||
useEffect(() => {
|
||||
log("Camera permission state updated", { granted: permissionsGranted });
|
||||
}, [permissionsGranted]);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<CameraView
|
||||
@@ -400,6 +576,7 @@ const RecordPlayback = ({ route }) => {
|
||||
style={{ flex: 1 }}
|
||||
facing="front"
|
||||
mode="video"
|
||||
mute
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
|
||||
Reference in New Issue
Block a user