timeout for record playback
This commit is contained in:
@@ -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
|
||||
|
||||
const boot = async () => {
|
||||
const ok = await ensureMediaStream()
|
||||
if (!cancelled && ok) {
|
||||
schedulePreviewRetry()
|
||||
}
|
||||
mediaStreamRef.current = stream
|
||||
setMediaReady(true)
|
||||
setMediaError(null)
|
||||
} catch (e) {
|
||||
if (cancelled) return
|
||||
setMediaError(e instanceof Error ? e : new Error(String(e || '')))
|
||||
setMediaReady(false)
|
||||
}
|
||||
})()
|
||||
|
||||
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 */}
|
||||
|
||||
Reference in New Issue
Block a user