feat: try sync audio to video

This commit is contained in:
2026-01-20 11:16:36 +01:00
parent 4343cee298
commit 490698cb92
5 changed files with 131 additions and 110 deletions
+24 -9
View File
@@ -31,11 +31,26 @@ async function downloadToFile(url, destPath) {
const SCALE_FILTER = const SCALE_FILTER =
"scale='trunc(min(1920,iw)/2)*2':'trunc(min(1920,ih)/2)*2':force_original_aspect_ratio=decrease" "scale='trunc(min(1920,iw)/2)*2':'trunc(min(1920,ih)/2)*2':force_original_aspect_ratio=decrease"
async function muxAudioIntoVideo({ videoPath, audioPath, outPath }) { async function muxAudioIntoVideo({ videoPath, audioPath, outPath, syncOffset = 0 }) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
ffmpeg() const command = ffmpeg()
.input(videoPath) // 0:v
.input(audioPath) // 1:a const offset = Number(syncOffset) || 0
// Si offset > 0 : la vidéo est en avance (ou capturée en retard dans le passé relatif ?), on veut la retarder pour qu'elle commence plus tard
// c-à-d on décale le flux vidéo.
// Si offset < 0 : on décale l'audio.
if (offset > 0) {
command.inputOptions(['-itsoffset', String(offset)])
}
command.input(videoPath) // 0:v
if (offset < 0) {
command.inputOptions(['-itsoffset', String(Math.abs(offset))])
}
command.input(audioPath) // 1:a
command
.outputOptions([ .outputOptions([
'-map', '-map',
'0:v:0', // garder la 1re piste vidéo de l'entrée 0 '0:v:0', // garder la 1re piste vidéo de l'entrée 0
@@ -72,7 +87,7 @@ async function muxAudioIntoVideo({ videoPath, audioPath, outPath }) {
}) })
} }
async function uploadPlaybackAsset({ videoUrl, audioUrl, storagePath }) { async function uploadPlaybackAsset({ videoUrl, audioUrl, storagePath, syncOffset = 0 }) {
const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'merge-')) const tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), 'merge-'))
const videoPath = path.join(tmpDir, 'video.mp4') const videoPath = path.join(tmpDir, 'video.mp4')
const audioPath = path.join(tmpDir, 'audio.mp3') const audioPath = path.join(tmpDir, 'audio.mp3')
@@ -84,8 +99,8 @@ async function uploadPlaybackAsset({ videoUrl, audioUrl, storagePath }) {
await downloadToFile(videoUrl, videoPath) await downloadToFile(videoUrl, videoPath)
await downloadToFile(audioUrl, audioPath) await downloadToFile(audioUrl, audioPath)
logger.info('[merge] transcodage/mux ffmpeg') logger.info('[merge] transcodage/mux ffmpeg', { syncOffset })
await muxAudioIntoVideo({ videoPath, audioPath, outPath }) await muxAudioIntoVideo({ videoPath, audioPath, outPath, syncOffset })
const bucket = admin.storage().bucket() const bucket = admin.storage().bucket()
const downloadToken = crypto.randomUUID() const downloadToken = crypto.randomUUID()
@@ -141,7 +156,7 @@ exports.mergeVideoAndAudio = onCall(
const uid = auth?.uid const uid = auth?.uid
if (!uid) throw new HttpsError('unauthenticated', 'Authentification requise') if (!uid) throw new HttpsError('unauthenticated', 'Authentification requise')
const { videoUrl, audioUrl, storagePath, projectId } = data || {} const { videoUrl, audioUrl, storagePath, projectId, syncOffset } = data || {}
if (!videoUrl || !audioUrl || !storagePath) { if (!videoUrl || !audioUrl || !storagePath) {
throw new HttpsError('invalid-argument', 'Requis: { videoUrl, audioUrl, storagePath }') throw new HttpsError('invalid-argument', 'Requis: { videoUrl, audioUrl, storagePath }')
@@ -152,7 +167,7 @@ exports.mergeVideoAndAudio = onCall(
throw new HttpsError('permission-denied', `storagePath doit commencer par ${expectedPrefix}`) throw new HttpsError('permission-denied', `storagePath doit commencer par ${expectedPrefix}`)
} }
const result = await uploadPlaybackAsset({ videoUrl, audioUrl, storagePath }) const result = await uploadPlaybackAsset({ videoUrl, audioUrl, storagePath, syncOffset })
if (projectId) { if (projectId) {
await markPlaybackCompatibility({ projectId, initiatorUid: uid }) await markPlaybackCompatibility({ projectId, initiatorUid: uid })
} }
+35 -31
View File
@@ -25,7 +25,7 @@ const LOG_PREFIX = '[RecordPlayback]'
const log = const log =
typeof __DEV__ === 'undefined' || __DEV__ typeof __DEV__ === 'undefined' || __DEV__
? (...args) => console.log(LOG_PREFIX, ...args) ? (...args) => console.log(LOG_PREFIX, ...args)
: () => {} : () => { }
const CAMERA_FACING_OPTIONS = [ const CAMERA_FACING_OPTIONS = [
{ label: 'Avant', value: 'front' }, { label: 'Avant', value: 'front' },
@@ -211,7 +211,7 @@ const RecordPlayback = ({ route }) => {
}) })
} }
progressLogRef.current = { bucket, lastPos: pos, lastDur: dur } progressLogRef.current = { bucket, lastPos: pos, lastDur: dur }
} catch (_) {} } catch (_) { }
}, 250) }, 250)
return () => clearInterval(id) return () => clearInterval(id)
}, [player]) }, [player])
@@ -232,7 +232,7 @@ const RecordPlayback = ({ route }) => {
// Permissions au mount + cleanup // Permissions au mount + cleanup
useEffect(() => { useEffect(() => {
;(async () => { ; (async () => {
try { try {
if (!cameraPermission?.granted) { if (!cameraPermission?.granted) {
log('Requesting camera permission on mount') log('Requesting camera permission on mount')
@@ -240,7 +240,7 @@ const RecordPlayback = ({ route }) => {
} else { } else {
log('Camera permission already granted on mount') log('Camera permission already granted on mount')
} }
} catch (_) {} } catch (_) { }
})() })()
return () => { return () => {
try { try {
@@ -251,7 +251,7 @@ const RecordPlayback = ({ route }) => {
listenTimerRef.current = null listenTimerRef.current = null
checkSongEndRef.current = null checkSongEndRef.current = null
log('Cleanup on unmount, cleared timers') log('Cleanup on unmount, cleared timers')
} catch (_) {} } catch (_) { }
} }
}, []) // eslint-disable-line react-hooks/exhaustive-deps }, []) // eslint-disable-line react-hooks/exhaustive-deps
@@ -298,15 +298,15 @@ const RecordPlayback = ({ route }) => {
setCountdown(0) setCountdown(0)
try { try {
await cameraRef.current?.resumePreview?.() await cameraRef.current?.resumePreview?.()
} catch (_) {} } catch (_) { }
if (player) { if (player) {
try { try {
if (player.playing) await player.pause?.() if (player.playing) await player.pause?.()
await player.seekTo?.(0) await player.seekTo?.(0)
} catch (_) {} } catch (_) { }
} }
} catch (_) {} } catch (_) { }
}, },
[player] [player]
) )
@@ -442,7 +442,7 @@ const RecordPlayback = ({ route }) => {
isPausedRef.current = false isPausedRef.current = false
try { try {
await cameraRef.current?.resumePreview?.() await cameraRef.current?.resumePreview?.()
} catch (_) {} } catch (_) { }
playbackStartedRef.current = false playbackStartedRef.current = false
progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 } progressLogRef.current = { bucket: -1, lastPos: -1, lastDur: -1 }
log('startRecordingWithMusic', { log('startRecordingWithMusic', {
@@ -521,6 +521,10 @@ const RecordPlayback = ({ route }) => {
message: error?.message || String(error || ''), message: error?.message || String(error || ''),
}) })
} }
// Add artificial delay to compensate for camera startup latency (Audio usually starts faster than video recording)
await new Promise(r => setTimeout(r, 250))
try { try {
await player.play?.() await player.play?.()
log('player.play invoked') log('player.play invoked')
@@ -544,10 +548,10 @@ const RecordPlayback = ({ route }) => {
try { try {
await projectsRef.doc(project.id).set({ views: increment(1) }, { merge: true }) await projectsRef.doc(project.id).set({ views: increment(1) }, { merge: true })
log('Views incremented') log('Views incremented')
} catch (_) {} } catch (_) { }
} }
} }
} catch (_) {} } catch (_) { }
}, 500) }, 500)
} }
@@ -565,7 +569,7 @@ const RecordPlayback = ({ route }) => {
try { try {
cameraRef.current?.stopRecording?.() cameraRef.current?.stopRecording?.()
log('stopRecording retried while awaiting stop') log('stopRecording retried while awaiting stop')
} catch (_) {} } catch (_) { }
} }
return return
} }
@@ -590,9 +594,9 @@ const RecordPlayback = ({ route }) => {
try { try {
cameraRef.current?.stopRecording?.() cameraRef.current?.stopRecording?.()
log('stopRecording triggered') log('stopRecording triggered')
} catch (_) {} } catch (_) { }
} }
} catch (_) {} } catch (_) { }
}, 500) }, 500)
} }
@@ -610,7 +614,7 @@ const RecordPlayback = ({ route }) => {
} }
try { try {
if (player?.playing) await player.pause?.() if (player?.playing) await player.pause?.()
} catch (_) {} } catch (_) { }
if (listenTimerRef.current) { if (listenTimerRef.current) {
clearInterval(listenTimerRef.current) clearInterval(listenTimerRef.current)
listenTimerRef.current = null listenTimerRef.current = null
@@ -643,7 +647,7 @@ const RecordPlayback = ({ route }) => {
await discardRecordingFile(video?.uri, 'too_short') await discardRecordingFile(video?.uri, 'too_short')
try { try {
goBack() goBack()
} catch (_) {} } catch (_) { }
return return
} }
} }
@@ -744,7 +748,7 @@ const RecordPlayback = ({ route }) => {
onPress: () => { onPress: () => {
try { try {
goBack() goBack()
} catch (_) {} } catch (_) { }
}, },
}, },
], ],
@@ -771,21 +775,21 @@ const RecordPlayback = ({ route }) => {
if (player?.playing) { if (player?.playing) {
const maybePromise = player.pause?.() const maybePromise = player.pause?.()
if (maybePromise && typeof maybePromise.catch === 'function') { if (maybePromise && typeof maybePromise.catch === 'function') {
maybePromise.catch(() => {}) maybePromise.catch(() => { })
} }
} }
} catch (_) {} } catch (_) { }
try { try {
if (isRecording) { if (isRecording) {
cameraRef.current?.stopRecording?.() cameraRef.current?.stopRecording?.()
} }
} catch (_) {} } catch (_) { }
} else { } else {
exitRequestedRef.current = false exitRequestedRef.current = false
} }
try { try {
goBack() goBack()
} catch (_) {} } catch (_) { }
return true return true
}, [goBack, isPreparing, isRecording, player]) }, [goBack, isPreparing, isRecording, player])
@@ -808,21 +812,21 @@ const RecordPlayback = ({ route }) => {
stopRequestedAtRef.current = 0 stopRequestedAtRef.current = 0
try { try {
await player?.pause?.() await player?.pause?.()
} catch (_) {} } catch (_) { }
try { try {
await cameraRef.current?.pausePreview?.() await cameraRef.current?.pausePreview?.()
} catch (_) {} } catch (_) { }
return return
} }
setIsPaused(false) setIsPaused(false)
isPausedRef.current = false isPausedRef.current = false
try { try {
await cameraRef.current?.resumePreview?.() await cameraRef.current?.resumePreview?.()
} catch (_) {} } catch (_) { }
try { try {
await player?.play?.() await player?.play?.()
} catch (_) {} } catch (_) { }
} catch (_) {} } catch (_) { }
}, [isRecording, player]) }, [isRecording, player])
const handleRestartRecording = async () => { const handleRestartRecording = async () => {
@@ -837,7 +841,7 @@ const RecordPlayback = ({ route }) => {
isPausedRef.current = false isPausedRef.current = false
try { try {
await cameraRef.current?.resumePreview?.() await cameraRef.current?.resumePreview?.()
} catch (_) {} } catch (_) { }
if (isPreparing) { if (isPreparing) {
if (hasRecordingPending) { if (hasRecordingPending) {
await startCountdownThenRecord({ await startCountdownThenRecord({
@@ -864,16 +868,16 @@ const RecordPlayback = ({ route }) => {
stopRequestedAtRef.current = Date.now() stopRequestedAtRef.current = Date.now()
try { try {
if (player?.playing) await player.pause?.() if (player?.playing) await player.pause?.()
} catch (_) {} } catch (_) { }
try { try {
cameraRef.current?.stopRecording?.() cameraRef.current?.stopRecording?.()
} catch (_) {} } catch (_) { }
await startCountdownThenRecord({ await startCountdownThenRecord({
preserveRestartFlag: true, preserveRestartFlag: true,
preserveSongEndWatcher: true, preserveSongEndWatcher: true,
preserveStopRequest: true, preserveStopRequest: true,
}) })
} catch (_) {} } catch (_) { }
} }
const permissionsGranted = !!cameraPermission?.granted const permissionsGranted = !!cameraPermission?.granted
@@ -945,7 +949,7 @@ const RecordPlayback = ({ route }) => {
onPress={async () => { onPress={async () => {
try { try {
if (!cameraPermission?.granted) await requestCameraPermission() if (!cameraPermission?.granted) await requestCameraPermission()
} catch (_) {} } catch (_) { }
}} }}
/> />
</View> </View>
+44 -43
View File
@@ -44,7 +44,7 @@ const pickRecorderMimeType = () => {
for (const mimeType of candidates) { for (const mimeType of candidates) {
try { try {
if (MediaRecorder.isTypeSupported(mimeType)) return mimeType if (MediaRecorder.isTypeSupported(mimeType)) return mimeType
} catch {} } catch { }
} }
return undefined return undefined
@@ -162,7 +162,7 @@ const RecordPlayback = ({ route }) => {
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') { if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') {
mediaRecorderRef.current.stop() mediaRecorderRef.current.stop()
} }
} catch (e) {} } catch (e) { }
recordedChunksRef.current = [] recordedChunksRef.current = []
releaseRecordingUrl() releaseRecordingUrl()
@@ -248,7 +248,7 @@ const RecordPlayback = ({ route }) => {
} }
recorder.stop() recorder.stop()
} }
} catch (e) {} } catch (e) { }
if (!waitPromise) { if (!waitPromise) {
return recordedUrlRef.current || null return recordedUrlRef.current || null
@@ -272,10 +272,10 @@ const RecordPlayback = ({ route }) => {
try { try {
await player?.pause?.() await player?.pause?.()
await player?.seekTo?.(0) await player?.seekTo?.(0)
} catch (e) {} } catch (e) { }
try { try {
await stopRecorderAndGetUrl() await stopRecorderAndGetUrl()
} catch (e) {} } catch (e) { }
if (!preserveRecordingForNextScreenRef.current) { if (!preserveRecordingForNextScreenRef.current) {
releaseRecordingUrl() releaseRecordingUrl()
} }
@@ -315,12 +315,12 @@ const RecordPlayback = ({ route }) => {
) )
useEffect(() => { useEffect(() => {
;(async () => { ; (async () => {
try { try {
if (!cameraPermission?.granted) { if (!cameraPermission?.granted) {
await requestCameraPermission() await requestCameraPermission()
} }
} catch (e) {} } catch (e) { }
})() })()
return () => { return () => {
clearAllTimers() clearAllTimers()
@@ -345,25 +345,25 @@ const RecordPlayback = ({ route }) => {
} }
let cancelled = false let cancelled = false
;(async () => { ; (async () => {
try { try {
const stream = await navigator.mediaDevices.getUserMedia({ const stream = await navigator.mediaDevices.getUserMedia({
video: { facingMode: 'user' }, video: { facingMode: 'user' },
audio: false, audio: false,
}) })
if (cancelled) { if (cancelled) {
stream.getTracks().forEach((track) => track.stop()) stream.getTracks().forEach((track) => track.stop())
return return
}
mediaStreamRef.current = stream
setMediaReady(true)
setMediaError(null)
} catch (e) {
if (cancelled) return
setMediaError(e instanceof Error ? e : new Error(String(e || '')))
setMediaReady(false)
} }
mediaStreamRef.current = stream })()
setMediaReady(true)
setMediaError(null)
} catch (e) {
if (cancelled) return
setMediaError(e instanceof Error ? e : new Error(String(e || '')))
setMediaReady(false)
}
})()
return () => { return () => {
cancelled = true cancelled = true
@@ -382,13 +382,13 @@ const RecordPlayback = ({ route }) => {
} }
const playPromise = video.play?.() const playPromise = video.play?.()
if (playPromise && typeof playPromise.catch === 'function') { if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => {}) playPromise.catch(() => { })
} }
} catch (e) {} } catch (e) { }
} else { } else {
try { try {
video.srcObject = null video.srcObject = null
} catch {} } catch { }
} }
}, [mediaReady, mediaError]) }, [mediaReady, mediaError])
@@ -398,7 +398,7 @@ const RecordPlayback = ({ route }) => {
if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') { if (mediaRecorderRef.current && mediaRecorderRef.current.state !== 'inactive') {
mediaRecorderRef.current.stop() mediaRecorderRef.current.stop()
} }
} catch {} } catch { }
if (mediaStreamRef.current) { if (mediaStreamRef.current) {
mediaStreamRef.current.getTracks().forEach((track) => track.stop()) mediaStreamRef.current.getTracks().forEach((track) => track.stop())
mediaStreamRef.current = null mediaStreamRef.current = null
@@ -414,7 +414,7 @@ const RecordPlayback = ({ route }) => {
clearAllTimers() clearAllTimers()
try { try {
await player?.pause?.() await player?.pause?.()
} catch (e) {} } catch (e) { }
let videoUrl = null let videoUrl = null
try { try {
@@ -422,7 +422,7 @@ const RecordPlayback = ({ route }) => {
if (!videoUrl) { if (!videoUrl) {
videoUrl = recordedUrlRef.current || null videoUrl = recordedUrlRef.current || null
} }
} catch (e) {} } catch (e) { }
setIsRecording(false) setIsRecording(false)
setIsPaused(false) setIsPaused(false)
@@ -457,7 +457,7 @@ const RecordPlayback = ({ route }) => {
await player?.seekTo?.(0) await player?.seekTo?.(0)
correctedInitialJumpRef.current = true correctedInitialJumpRef.current = true
nextPos = 0 nextPos = 0
} catch (e) {} } catch (e) { }
} }
const timeSinceStart = const timeSinceStart =
@@ -501,11 +501,11 @@ const RecordPlayback = ({ route }) => {
if (project?.id) { if (project?.id) {
try { try {
await projectsRef.doc(project.id).set({ views: increment(1) }, { merge: true }) await projectsRef.doc(project.id).set({ views: increment(1) }, { merge: true })
} catch (e) {} } catch (e) { }
} }
} }
} }
} catch (e) {} } catch (e) { }
}, 500) }, 500)
}, [player, project?.id]) }, [player, project?.id])
@@ -520,6 +520,7 @@ const RecordPlayback = ({ route }) => {
const recorderStarted = startRecorder() const recorderStarted = startRecorder()
if (!recorderStarted) { if (!recorderStarted) {
} }
await new Promise(r => setTimeout(r, 250))
await player?.play?.() // attend la promesse → l'élément est prêt await player?.play?.() // attend la promesse → l'élément est prêt
perfStartRef.current = perfStartRef.current ?? performance.now() perfStartRef.current = perfStartRef.current ?? performance.now()
correctedInitialJumpRef.current = false // autorise la correction 1-shot correctedInitialJumpRef.current = false // autorise la correction 1-shot
@@ -543,7 +544,7 @@ const RecordPlayback = ({ route }) => {
onPress: () => { onPress: () => {
try { try {
goBack() goBack()
} catch {} } catch { }
}, },
}, },
], ],
@@ -588,13 +589,13 @@ const RecordPlayback = ({ route }) => {
pausedAtRef.current = performance.now() pausedAtRef.current = performance.now()
try { try {
await player?.pause?.() await player?.pause?.()
} catch (e) {} } catch (e) { }
try { try {
const recorder = mediaRecorderRef.current const recorder = mediaRecorderRef.current
if (recorder && recorder.state === 'recording') { if (recorder && recorder.state === 'recording') {
recorder.pause?.() recorder.pause?.()
} }
} catch (e) {} } catch (e) { }
return return
} }
const pausedAt = pausedAtRef.current const pausedAt = pausedAtRef.current
@@ -609,17 +610,17 @@ const RecordPlayback = ({ route }) => {
if (recorder && recorder.state === 'paused') { if (recorder && recorder.state === 'paused') {
recorder.resume?.() recorder.resume?.()
} }
} catch (e) {} } catch (e) { }
try { try {
await player?.play?.() await player?.play?.()
} catch (e) {} } catch (e) { }
} catch (e) {} } catch (e) { }
}, [isRecording, player]) }, [isRecording, player])
const handleRestartRecording = useCallback(async () => { const handleRestartRecording = useCallback(async () => {
try { try {
await startCountdownThenRecord() await startCountdownThenRecord()
} catch (e) {} } catch (e) { }
}, [startCountdownThenRecord]) }, [startCountdownThenRecord])
const progressRatio = dur > 0 ? Math.min(1, pos / dur) : 0 const progressRatio = dur > 0 ? Math.min(1, pos / dur) : 0
@@ -630,7 +631,7 @@ const RecordPlayback = ({ route }) => {
// containerStyle={{ backgroundColor: Palette.gray }} // containerStyle={{ backgroundColor: Palette.gray }}
headerType="NONE" headerType="NONE"
backgroundColor={Palette.grayMid} backgroundColor={Palette.grayMid}
// backgroundImg={background.playbackBG2} // backgroundImg={background.playbackBG2}
> >
<MusicLandHeader progress={9} onPressBack={goBack} /> <MusicLandHeader progress={9} onPressBack={goBack} />
@@ -848,7 +849,7 @@ const RecordPlayback = ({ route }) => {
onPress={async () => { onPress={async () => {
try { try {
if (!cameraPermission?.granted) await requestCameraPermission() if (!cameraPermission?.granted) await requestCameraPermission()
} catch {} } catch { }
}} }}
/> />
</View> </View>
+13 -12
View File
@@ -12,6 +12,7 @@ import Page from '../../layouts/Page'
import { Routes } from '../../navigation' import { Routes } from '../../navigation'
import { goBack, navigate } from '../../navigation/NavigationService' import { goBack, navigate } from '../../navigation/NavigationService'
import { gutters, Palette } from '../../styles' import { gutters, Palette } from '../../styles'
import { FONT_FAMILY } from '../../styles/Fonts'
const RecordedPlayback = ({ route }) => { const RecordedPlayback = ({ route }) => {
const { videoUri, project } = route.params || {} const { videoUri, project } = route.params || {}
const songUrl = project?.songUrl || null const songUrl = project?.songUrl || null
@@ -40,10 +41,10 @@ const RecordedPlayback = ({ route }) => {
const stopPlayback = useCallback(async () => { const stopPlayback = useCallback(async () => {
try { try {
if (audioPlayer?.playing) await audioPlayer.pause?.() if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch (e) {} } catch (e) { }
try { try {
if (videoPlayer?.playing) videoPlayer.pause() if (videoPlayer?.playing) videoPlayer.pause()
} catch (e) {} } catch (e) { }
}, [audioPlayer, videoPlayer]) }, [audioPlayer, videoPlayer])
useEffect(() => { useEffect(() => {
@@ -52,7 +53,7 @@ const RecordedPlayback = ({ route }) => {
try { try {
if (audioPlayer && songUrl) await audioPlayer.play?.() if (audioPlayer && songUrl) await audioPlayer.play?.()
if (videoPlayer) videoPlayer.play() if (videoPlayer) videoPlayer.play()
} catch (e) {} } catch (e) { }
} }
start() start()
return () => { return () => {
@@ -78,7 +79,7 @@ const RecordedPlayback = ({ route }) => {
videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000) videoPlayer.currentTime = Math.max(0, (pos || 0) / 1000)
} }
} }
} catch (e) {} } catch (e) { }
}, 250) }, 250)
return () => global.clearInterval(id) return () => global.clearInterval(id)
}, [audioPlayer, videoPlayer]) }, [audioPlayer, videoPlayer])
@@ -89,7 +90,7 @@ const RecordedPlayback = ({ route }) => {
const pos = Math.max(0, Math.min(dur, Math.floor(targetMs))) const pos = Math.max(0, Math.min(dur, Math.floor(targetMs)))
if (audioPlayer && dur > 0) await audioPlayer.seekTo?.(Math.floor(pos / 1000)) if (audioPlayer && dur > 0) await audioPlayer.seekTo?.(Math.floor(pos / 1000))
if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000) if (videoPlayer) videoPlayer.currentTime = Math.max(0, pos / 1000)
} catch (e) {} } catch (e) { }
} }
const onSeekStart = useCallback(() => { const onSeekStart = useCallback(() => {
@@ -99,19 +100,19 @@ const RecordedPlayback = ({ route }) => {
const pauseDuringSeek = useCallback(async () => { const pauseDuringSeek = useCallback(async () => {
try { try {
if (audioPlayer?.playing) await audioPlayer.pause?.() if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch (e) {} } catch (e) { }
try { try {
if (videoPlayer?.playing) videoPlayer.pause() if (videoPlayer?.playing) videoPlayer.pause()
} catch (e) {} } catch (e) { }
}, [audioPlayer, videoPlayer]) }, [audioPlayer, videoPlayer])
const resumeAfterSeek = useCallback(async () => { const resumeAfterSeek = useCallback(async () => {
try { try {
if (audioPlayer) await audioPlayer.play?.() if (audioPlayer) await audioPlayer.play?.()
} catch (e) {} } catch (e) { }
try { try {
if (videoPlayer) videoPlayer.play() if (videoPlayer) videoPlayer.play()
} catch (e) {} } catch (e) { }
}, [audioPlayer, videoPlayer]) }, [audioPlayer, videoPlayer])
const sliderProgress = useMemo(() => { const sliderProgress = useMemo(() => {
@@ -155,7 +156,7 @@ const RecordedPlayback = ({ route }) => {
playbackEndedRef.current = false playbackEndedRef.current = false
if (songUrl && audioPlayer) await audioPlayer.play?.() if (songUrl && audioPlayer) await audioPlayer.play?.()
if (videoPlayer) videoPlayer.play() if (videoPlayer) videoPlayer.play()
} catch (e) {} } catch (e) { }
} }
return ( return (
@@ -225,7 +226,7 @@ const RecordedPlayback = ({ route }) => {
onPress={async () => { onPress={async () => {
try { try {
await stopPlayback() await stopPlayback()
} catch (e) {} } catch (e) { }
try { try {
if (videoUri) { if (videoUri) {
const info = await FileSystem.getInfoAsync(videoUri) const info = await FileSystem.getInfoAsync(videoUri)
@@ -234,7 +235,7 @@ const RecordedPlayback = ({ route }) => {
idempotent: true, idempotent: true,
}) })
} }
} catch (e) {} } catch (e) { }
navigate(Routes.RecordPlayback, { project }) navigate(Routes.RecordPlayback, { project })
}} }}
/> />
+15 -15
View File
@@ -55,12 +55,12 @@ const RecordedPlayback = ({ route }) => {
const stopPlayback = useCallback(async () => { const stopPlayback = useCallback(async () => {
try { try {
if (audioPlayer?.playing) await audioPlayer.pause?.() if (audioPlayer?.playing) await audioPlayer.pause?.()
} catch {} } catch { }
try { try {
if (videoElRef.current && !videoElRef.current.paused) { if (videoElRef.current && !videoElRef.current.paused) {
videoElRef.current.pause() videoElRef.current.pause()
} }
} catch {} } catch { }
}, [audioPlayer]) }, [audioPlayer])
useEffect(() => { useEffect(() => {
@@ -82,9 +82,9 @@ const RecordedPlayback = ({ route }) => {
if (videoElRef.current && videoUri) { if (videoElRef.current && videoUri) {
// Lecture vidéo HTML5 (muet pour éviter les policies) // Lecture vidéo HTML5 (muet pour éviter les policies)
videoElRef.current.muted = true videoElRef.current.muted = true
videoElRef.current.play().catch(() => {}) videoElRef.current.play().catch(() => { })
} }
} catch {} } catch { }
} }
start() start()
@@ -115,7 +115,7 @@ const RecordedPlayback = ({ route }) => {
videoElRef.current.currentTime = Math.max(0, posS) videoElRef.current.currentTime = Math.max(0, posS)
} }
} }
} catch {} } catch { }
}, 250) }, 250)
return () => clearInterval(id) return () => clearInterval(id)
}, [audioPlayer, videoUri]) }, [audioPlayer, videoUri])
@@ -141,12 +141,12 @@ const RecordedPlayback = ({ route }) => {
await stopPlayback() await stopPlayback()
try { try {
if (audioPlayer) await audioPlayer.seekTo?.(0) if (audioPlayer) await audioPlayer.seekTo?.(0)
} catch {} } catch { }
try { try {
if (videoElRef.current) { if (videoElRef.current) {
videoElRef.current.currentTime = 0 videoElRef.current.currentTime = 0
} }
} catch {} } catch { }
})() })()
} }
}, [progress, stopPlayback, audioPlayer]) }, [progress, stopPlayback, audioPlayer])
@@ -163,7 +163,7 @@ const RecordedPlayback = ({ route }) => {
if (videoElRef.current && videoUri) { if (videoElRef.current && videoUri) {
videoElRef.current.currentTime = Math.max(0, target) videoElRef.current.currentTime = Math.max(0, target)
} }
} catch {} } catch { }
})() })()
pendingSeekRef.current = promise pendingSeekRef.current = promise
await promise await promise
@@ -177,7 +177,7 @@ const RecordedPlayback = ({ route }) => {
if (!promise) return if (!promise) return
try { try {
await promise await promise
} catch {} } catch { }
}, []) }, [])
const wasPlayingRef = useRef(false) const wasPlayingRef = useRef(false)
@@ -193,7 +193,7 @@ const RecordedPlayback = ({ route }) => {
if (videoElRef.current && !videoElRef.current.paused) { if (videoElRef.current && !videoElRef.current.paused) {
videoElRef.current.pause() videoElRef.current.pause()
} }
} catch {} } catch { }
}, [audioPlayer]) }, [audioPlayer])
const onSeekEnd = useCallback(async () => { const onSeekEnd = useCallback(async () => {
try { try {
@@ -202,9 +202,9 @@ const RecordedPlayback = ({ route }) => {
isSeekingRef.current = false isSeekingRef.current = false
if (wasPlayingRef.current) { if (wasPlayingRef.current) {
if (audioPlayer) await audioPlayer.resume?.() if (audioPlayer) await audioPlayer.resume?.()
if (videoElRef.current && videoUri) videoElRef.current.play().catch(() => {}) if (videoElRef.current && videoUri) videoElRef.current.play().catch(() => { })
} }
} catch {} } catch { }
}, [audioPlayer, videoUri, waitForPendingSeek]) }, [audioPlayer, videoUri, waitForPendingSeek])
const handleTogglePlayback = useCallback(async () => { const handleTogglePlayback = useCallback(async () => {
@@ -233,9 +233,9 @@ const RecordedPlayback = ({ route }) => {
} }
if (videoElRef.current && videoUri) { if (videoElRef.current && videoUri) {
videoElRef.current.muted = true videoElRef.current.muted = true
videoElRef.current.play().catch(() => {}) videoElRef.current.play().catch(() => { })
} }
} catch {} } catch { }
}, [audioPlayer, songUrl, stopPlayback, progress, videoUri]) }, [audioPlayer, songUrl, stopPlayback, progress, videoUri])
return ( return (
@@ -377,7 +377,7 @@ const RecordedPlayback = ({ route }) => {
onPress={async () => { onPress={async () => {
try { try {
await stopPlayback() await stopPlayback()
} catch {} } catch { }
navigate(Routes.RecordPlayback, { project }) navigate(Routes.RecordPlayback, { project })
}} }}
/> />