timeout for record playback

This commit is contained in:
2026-02-12 14:02:08 +01:00
parent 6ccac084dc
commit 9d7421e3d8
+159 -46
View File
@@ -24,6 +24,9 @@ import { registerBlobUrl, releaseBlobUrl } from '../../utils/blobUrlCache'
const TIME_BEFORE_INCREMENT_MS = 20000
const COUNTDOWN_SECONDS = __DEV__ ? 1 : 10
const WEB_PREVIEW_WIDTH = 360
const MEDIA_BOOTSTRAP_DELAY_MS = 250
const MEDIA_RETRY_DELAY_MS = 700
const MEDIA_MAX_RETRIES = 2
const toSeconds = (v) => {
const n = Number(v ?? 0)
@@ -79,6 +82,9 @@ const RecordPlayback = ({ route }) => {
const preserveRecordingForNextScreenRef = useRef(false)
const originalLoopingValueRef = useRef({ hasValue: false, value: false })
const latestLoopingValueRef = useRef(isLooping ?? false)
const mediaInitInFlightRef = useRef(false)
const mediaRetryTimerRef = useRef(null)
const mediaInitAttemptsRef = useRef(0)
const [mediaReady, setMediaReady] = useState(false)
const [mediaError, setMediaError] = useState(null)
@@ -127,6 +133,13 @@ const RecordPlayback = ({ route }) => {
progressTimerRef.current = listenTimerRef.current = countdownTimerRef.current = null
}
const clearMediaRetry = useCallback(() => {
if (mediaRetryTimerRef.current) {
clearTimeout(mediaRetryTimerRef.current)
mediaRetryTimerRef.current = null
}
}, [])
const resetUI = () => {
setIsPreparing(false)
setIsRecording(false)
@@ -153,6 +166,102 @@ const RecordPlayback = ({ route }) => {
preserveRecordingForNextScreenRef.current = false
}, [])
const stopMediaStream = useCallback(() => {
if (mediaStreamRef.current) {
mediaStreamRef.current.getTracks().forEach((track) => track.stop())
mediaStreamRef.current = null
}
}, [])
const attachPreview = useCallback(() => {
const video = previewVideoRef.current
const stream = mediaStreamRef.current
if (!video || !stream) return false
try {
if (video.srcObject !== stream) {
video.srcObject = stream
}
} catch (e) { }
try {
const playPromise = video.play?.()
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => { })
}
} catch (e) { }
return true
}, [])
const ensureMediaStream = useCallback(
async ({ force = false } = {}) => {
if (!cameraPermission?.granted) return false
if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
setMediaError(new Error("La capture vidéo n'est pas supportée sur ce navigateur"))
setMediaReady(false)
return false
}
if (mediaInitInFlightRef.current) return false
if (!force && mediaStreamRef.current) {
setMediaReady(true)
setMediaError(null)
attachPreview()
return true
}
mediaInitInFlightRef.current = true
try {
if (force && mediaStreamRef.current) {
stopMediaStream()
}
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false,
})
mediaStreamRef.current = stream
setMediaReady(true)
setMediaError(null)
mediaInitAttemptsRef.current = 0
attachPreview()
return true
} catch (e) {
setMediaError(e instanceof Error ? e : new Error(String(e || '')))
setMediaReady(false)
return false
} finally {
mediaInitInFlightRef.current = false
}
},
[attachPreview, cameraPermission?.granted, stopMediaStream]
)
const schedulePreviewRetry = useCallback(
function schedulePreviewRetry() {
clearMediaRetry()
if (mediaInitAttemptsRef.current >= MEDIA_MAX_RETRIES) return
mediaRetryTimerRef.current = setTimeout(async () => {
const video = previewVideoRef.current
const previewOk =
!!video &&
video.readyState >= 2 &&
video.videoWidth > 0 &&
video.videoHeight > 0 &&
!video.paused
if (previewOk) return
mediaInitAttemptsRef.current += 1
const ok = await ensureMediaStream({ force: true })
if (ok) schedulePreviewRetry()
}, MEDIA_RETRY_DELAY_MS)
},
[clearMediaRetry, ensureMediaStream]
)
const startRecorder = useCallback(() => {
if (!mediaStreamRef.current) {
return false
@@ -330,45 +439,28 @@ const RecordPlayback = ({ route }) => {
useEffect(() => {
if (!cameraPermission?.granted) {
setMediaReady(false)
return
}
if (mediaStreamRef.current) {
setMediaReady(true)
return
}
if (typeof navigator === 'undefined' || !navigator.mediaDevices?.getUserMedia) {
setMediaError(new Error("La capture vidéo n'est pas supportée sur ce navigateur"))
setMediaReady(false)
clearMediaRetry()
mediaInitAttemptsRef.current = 0
return
}
let cancelled = false
; (async () => {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' },
audio: false,
})
if (cancelled) {
stream.getTracks().forEach((track) => track.stop())
return
}
mediaStreamRef.current = stream
setMediaReady(true)
setMediaError(null)
} catch (e) {
if (cancelled) return
setMediaError(e instanceof Error ? e : new Error(String(e || '')))
setMediaReady(false)
}
})()
const boot = async () => {
const ok = await ensureMediaStream()
if (!cancelled && ok) {
schedulePreviewRetry()
}
}
const timeoutId = setTimeout(boot, MEDIA_BOOTSTRAP_DELAY_MS)
return () => {
cancelled = true
clearTimeout(timeoutId)
clearMediaRetry()
}
}, [cameraPermission?.granted])
}, [cameraPermission?.granted, clearMediaRetry, ensureMediaStream, schedulePreviewRetry])
useEffect(() => {
const video = previewVideoRef.current
@@ -376,21 +468,35 @@ const RecordPlayback = ({ route }) => {
if (!video) return
if (stream) {
try {
if (video.srcObject !== stream) {
video.srcObject = stream
}
const playPromise = video.play?.()
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => { })
}
} catch (e) { }
attachPreview()
} else {
try {
video.srcObject = null
} catch { }
}
}, [mediaReady, mediaError])
}, [attachPreview, mediaReady, mediaError])
useFocusEffect(
useCallback(() => {
let active = true
const timeoutId = setTimeout(() => {
if (!active) return
if (mediaStreamRef.current) {
attachPreview()
schedulePreviewRetry()
return
}
if (cameraPermission?.granted) {
void ensureMediaStream()
}
}, MEDIA_BOOTSTRAP_DELAY_MS)
return () => {
active = false
clearTimeout(timeoutId)
}
}, [attachPreview, cameraPermission?.granted, ensureMediaStream, schedulePreviewRetry])
)
useEffect(() => {
return () => {
@@ -399,15 +505,13 @@ const RecordPlayback = ({ route }) => {
mediaRecorderRef.current.stop()
}
} catch { }
if (mediaStreamRef.current) {
mediaStreamRef.current.getTracks().forEach((track) => track.stop())
mediaStreamRef.current = null
}
stopMediaStream()
clearMediaRetry()
if (!preserveRecordingForNextScreenRef.current) {
releaseRecordingUrl()
}
}
}, [releaseRecordingUrl, preserveRecordingForNextScreenRef])
}, [clearMediaRetry, releaseRecordingUrl, preserveRecordingForNextScreenRef, stopMediaStream])
const stopAndNavigate = useCallback(async () => {
preserveRecordingForNextScreenRef.current = true
@@ -658,6 +762,15 @@ const RecordPlayback = ({ route }) => {
transform: 'scaleX(-1)',
backgroundColor: 'transparent',
}}
onLoadedMetadata={(event) => {
try {
const video = event?.currentTarget
const playPromise = video?.play?.()
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => { })
}
} catch { }
}}
/>
{/* Overlay */}