Skip to content

Commit 0f20448

Browse files
committed
feat: prevent race conditions in trim finalization and snap cursor within virtual trim bounds
1 parent ba23d9a commit 0f20448

1 file changed

Lines changed: 24 additions & 6 deletions

File tree

electron-app/src/viewer/components/VideoPlayer.jsx

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -714,7 +714,10 @@ function VideoPlayer({
714714

715715
const finalizeTrim = async () => {
716716
// Wait one tick so React commits the src removal before finalize starts.
717+
// Check cancelled here too so the StrictMode double-invocation doesn't
718+
// fire two simultaneous trim-finalize requests and cause a race condition.
717719
await new Promise((resolve) => setTimeout(resolve, 0))
720+
if (cancelled) return
718721

719722
try {
720723
const finalRes = await apiPost('/api/clips/trim-finalize', { source_path: trimFinalizePath })
@@ -760,6 +763,20 @@ function VideoPlayer({
760763
videoRef.current.load()
761764
}, [suppressVideoSrc, videoReloadToken])
762765

766+
// When virtual trim bounds are applied, snap cursor into the trim region if it's outside
767+
useEffect(() => {
768+
if (virtualTrimStart === null || virtualTrimEnd === null) return
769+
if (!videoRef.current) return
770+
const t = videoRef.current.currentTime
771+
if (t < virtualTrimStart) {
772+
videoRef.current.currentTime = virtualTrimStart
773+
setCurrentTime(virtualTrimStart)
774+
} else if (t > virtualTrimEnd) {
775+
videoRef.current.currentTime = virtualTrimEnd
776+
setCurrentTime(virtualTrimEnd)
777+
}
778+
}, [virtualTrimStart, virtualTrimEnd])
779+
763780
// Subscribe to per-stage progress events from the backend
764781
useEffect(() => {
765782
const unsub = api.onOrganizeProgress?.((p) => setOrganizeProgress(p))
@@ -807,6 +824,8 @@ function VideoPlayer({
807824

808825
// Share state: { phase: 'compressing'|'uploading'|'done'|'error', url, error, percent } or null
809826
const [shareModal, setShareModal] = useState(persistedShareState)
827+
const shareModalRef = useRef(shareModal)
828+
shareModalRef.current = shareModal
810829
const [shareCopied, setShareCopied] = useState(false)
811830
const isSharing = shareModal?.phase === 'uploading' || shareModal?.phase === 'compressing'
812831

@@ -824,12 +843,11 @@ function VideoPlayer({
824843
useEffect(() => {
825844
if (!api.onShareProgress) return
826845
const unsub = api.onShareProgress((data) => {
827-
setShareModal((prev) => {
828-
if (!prev || prev.phase === 'done' || prev.phase === 'error') return prev
829-
const next = { ...prev, phase: data.phase, percent: data.percent }
830-
onShareStateChange?.(media?.path, next)
831-
return next
832-
})
846+
const prev = shareModalRef.current
847+
if (!prev || prev.phase === 'done' || prev.phase === 'error') return
848+
const next = { ...prev, phase: data.phase, percent: data.percent }
849+
setShareModal(next)
850+
onShareStateChange?.(media?.path, next)
833851
})
834852
return () => unsub()
835853
// eslint-disable-next-line react-hooks/exhaustive-deps

0 commit comments

Comments
 (0)