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 TIME_BEFORE_INCREMENT_MS = 20000
|
||||||
const COUNTDOWN_SECONDS = __DEV__ ? 1 : 10
|
const COUNTDOWN_SECONDS = __DEV__ ? 1 : 10
|
||||||
const WEB_PREVIEW_WIDTH = 360
|
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 toSeconds = (v) => {
|
||||||
const n = Number(v ?? 0)
|
const n = Number(v ?? 0)
|
||||||
@@ -79,6 +82,9 @@ const RecordPlayback = ({ route }) => {
|
|||||||
const preserveRecordingForNextScreenRef = useRef(false)
|
const preserveRecordingForNextScreenRef = useRef(false)
|
||||||
const originalLoopingValueRef = useRef({ hasValue: false, value: false })
|
const originalLoopingValueRef = useRef({ hasValue: false, value: false })
|
||||||
const latestLoopingValueRef = useRef(isLooping ?? false)
|
const latestLoopingValueRef = useRef(isLooping ?? false)
|
||||||
|
const mediaInitInFlightRef = useRef(false)
|
||||||
|
const mediaRetryTimerRef = useRef(null)
|
||||||
|
const mediaInitAttemptsRef = useRef(0)
|
||||||
const [mediaReady, setMediaReady] = useState(false)
|
const [mediaReady, setMediaReady] = useState(false)
|
||||||
const [mediaError, setMediaError] = useState(null)
|
const [mediaError, setMediaError] = useState(null)
|
||||||
|
|
||||||
@@ -127,6 +133,13 @@ const RecordPlayback = ({ route }) => {
|
|||||||
progressTimerRef.current = listenTimerRef.current = countdownTimerRef.current = null
|
progressTimerRef.current = listenTimerRef.current = countdownTimerRef.current = null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const clearMediaRetry = useCallback(() => {
|
||||||
|
if (mediaRetryTimerRef.current) {
|
||||||
|
clearTimeout(mediaRetryTimerRef.current)
|
||||||
|
mediaRetryTimerRef.current = null
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
const resetUI = () => {
|
const resetUI = () => {
|
||||||
setIsPreparing(false)
|
setIsPreparing(false)
|
||||||
setIsRecording(false)
|
setIsRecording(false)
|
||||||
@@ -153,6 +166,102 @@ const RecordPlayback = ({ route }) => {
|
|||||||
preserveRecordingForNextScreenRef.current = false
|
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(() => {
|
const startRecorder = useCallback(() => {
|
||||||
if (!mediaStreamRef.current) {
|
if (!mediaStreamRef.current) {
|
||||||
return false
|
return false
|
||||||
@@ -330,45 +439,28 @@ const RecordPlayback = ({ route }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!cameraPermission?.granted) {
|
if (!cameraPermission?.granted) {
|
||||||
setMediaReady(false)
|
setMediaReady(false)
|
||||||
return
|
clearMediaRetry()
|
||||||
}
|
mediaInitAttemptsRef.current = 0
|
||||||
|
|
||||||
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)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let cancelled = false
|
let cancelled = false
|
||||||
; (async () => {
|
|
||||||
try {
|
const boot = async () => {
|
||||||
const stream = await navigator.mediaDevices.getUserMedia({
|
const ok = await ensureMediaStream()
|
||||||
video: { facingMode: 'user' },
|
if (!cancelled && ok) {
|
||||||
audio: false,
|
schedulePreviewRetry()
|
||||||
})
|
|
||||||
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 timeoutId = setTimeout(boot, MEDIA_BOOTSTRAP_DELAY_MS)
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
cancelled = true
|
cancelled = true
|
||||||
|
clearTimeout(timeoutId)
|
||||||
|
clearMediaRetry()
|
||||||
}
|
}
|
||||||
}, [cameraPermission?.granted])
|
}, [cameraPermission?.granted, clearMediaRetry, ensureMediaStream, schedulePreviewRetry])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const video = previewVideoRef.current
|
const video = previewVideoRef.current
|
||||||
@@ -376,21 +468,35 @@ const RecordPlayback = ({ route }) => {
|
|||||||
if (!video) return
|
if (!video) return
|
||||||
|
|
||||||
if (stream) {
|
if (stream) {
|
||||||
try {
|
attachPreview()
|
||||||
if (video.srcObject !== stream) {
|
|
||||||
video.srcObject = stream
|
|
||||||
}
|
|
||||||
const playPromise = video.play?.()
|
|
||||||
if (playPromise && typeof playPromise.catch === 'function') {
|
|
||||||
playPromise.catch(() => { })
|
|
||||||
}
|
|
||||||
} catch (e) { }
|
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
video.srcObject = null
|
video.srcObject = null
|
||||||
} catch { }
|
} 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(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
@@ -399,15 +505,13 @@ const RecordPlayback = ({ route }) => {
|
|||||||
mediaRecorderRef.current.stop()
|
mediaRecorderRef.current.stop()
|
||||||
}
|
}
|
||||||
} catch { }
|
} catch { }
|
||||||
if (mediaStreamRef.current) {
|
stopMediaStream()
|
||||||
mediaStreamRef.current.getTracks().forEach((track) => track.stop())
|
clearMediaRetry()
|
||||||
mediaStreamRef.current = null
|
|
||||||
}
|
|
||||||
if (!preserveRecordingForNextScreenRef.current) {
|
if (!preserveRecordingForNextScreenRef.current) {
|
||||||
releaseRecordingUrl()
|
releaseRecordingUrl()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}, [releaseRecordingUrl, preserveRecordingForNextScreenRef])
|
}, [clearMediaRetry, releaseRecordingUrl, preserveRecordingForNextScreenRef, stopMediaStream])
|
||||||
|
|
||||||
const stopAndNavigate = useCallback(async () => {
|
const stopAndNavigate = useCallback(async () => {
|
||||||
preserveRecordingForNextScreenRef.current = true
|
preserveRecordingForNextScreenRef.current = true
|
||||||
@@ -658,6 +762,15 @@ const RecordPlayback = ({ route }) => {
|
|||||||
transform: 'scaleX(-1)',
|
transform: 'scaleX(-1)',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
}}
|
}}
|
||||||
|
onLoadedMetadata={(event) => {
|
||||||
|
try {
|
||||||
|
const video = event?.currentTarget
|
||||||
|
const playPromise = video?.play?.()
|
||||||
|
if (playPromise && typeof playPromise.catch === 'function') {
|
||||||
|
playPromise.catch(() => { })
|
||||||
|
}
|
||||||
|
} catch { }
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Overlay */}
|
{/* Overlay */}
|
||||||
|
|||||||
Reference in New Issue
Block a user