diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index d1ef2be1..5ee65eab 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -335,7 +335,7 @@ function App() { }, [isFileOpened]); const { - cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, focusSegmentAtCursor, createNumSegments, createFixedDurationSegments, createRandomSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onMutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, getCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, getSegApparentEnd, getApparentCutSegments, + cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, combineSelectedSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, focusSegmentAtCursor, createNumSegments, createFixedDurationSegments, createRandomSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, isSegmentSelected, setCutTime, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, invertSelectedSegments, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, onSelectSegmentsByExpr, onMutateSegmentsByExpr, toggleSegmentSelected, selectOnlySegment, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, segmentsToExport, duplicateCurrentSegment, duplicateSegment, updateSegAtIndex, getSegApparentEnd, getApparentCutSegments, } = useSegments({ filePath, workingRef, setWorking, setProgress, videoStream: activeVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, segmentsToChaptersOnly, timecodePlaceholder, parseTimecode, appendFfmpegCommandLog }); const { getEdlFilePath, getEdlFilePathOld, projectFileSavePath, getProjectFileSavePath } = useSegmentsAutoSave({ autoSaveProjectFile, storeProjectInWorkingDir, filePath, customOutDir, cutSegments }); @@ -698,17 +698,20 @@ function App() { return; } + // If we are using a special playback mode, we might need to do more: if (playbackModeRef.current != null) { const selectedSegmentAtCursor = selectedSegments.find((selectedSegment) => selectedSegment.segId === segmentAtCursorRef.current?.segId); const isSomeSegmentAtCursor = selectedSegmentAtCursor != null && commandedTimeRef.current != null && getSegApparentEnd(selectedSegmentAtCursor) - commandedTimeRef.current > 0.1; if (!isSomeSegmentAtCursor) { // if a segment is already at cursor, don't do anything + // if no segment at cursor, and looping playback mode, continue looping if (playbackModeRef.current === 'loop-selected-segments') { const firstSelectedSegment = selectedSegments[0]; - if (firstSelectedSegment == null) throw new Error(); + invariant(firstSelectedSegment != null); const index = cutSegments.indexOf(firstSelectedSegment); if (index >= 0) setCurrentSegIndex(index); seekAbs(firstSelectedSegment.start); } else { + // for all other playback modes, seek to start of current segment seekAbs(getSegApparentStart(currentCutSeg)); } } @@ -722,31 +725,33 @@ function App() { setPlayerTime(currentTime); const playbackMode = playbackModeRef.current; - if (playbackMode != null && segmentAtCursorRef.current != null) { // todo and is currently playing? - const playingSegment = getCutSegmentById(segmentAtCursorRef.current.segId); - if (playingSegment != null) { - const nextAction = getPlaybackMode({ playbackMode, currentTime, playingSegment: { start: getSegApparentStart(playingSegment), end: getSegApparentEnd(playingSegment) } }); - if (nextAction != null) { - console.log(nextAction); - if (nextAction.nextSegment) { - const index = selectedSegments.indexOf(playingSegment); - let newIndex = getNewJumpIndex(index >= 0 ? index : 0, 1); - if (newIndex > selectedSegments.length - 1) newIndex = 0; // have reached end of last segment, start over - const nextSelectedSegment = selectedSegments[newIndex]; - if (nextSelectedSegment != null) seekAbs(nextSelectedSegment.start); - } - if (nextAction.seekTo != null) { - seekAbs(nextAction.seekTo); - } - if (nextAction.exit) { - playbackModeRef.current = undefined; - pause(); - } + const segmentsAtCursorIndexes = findSegmentsAtCursor(cutSegments, commandedTimeRef.current); + const firstSegmentAtCursorIndex = segmentsAtCursorIndexes[0]; + const playingSegment = firstSegmentAtCursorIndex != null ? cutSegments[firstSegmentAtCursorIndex] : undefined; + + if (playbackMode != null && playingSegment) { // todo and is currently playing? + const nextAction = getPlaybackMode({ playbackMode, currentTime, playingSegment: { start: getSegApparentStart(playingSegment), end: getSegApparentEnd(playingSegment) } }); + + if (nextAction != null) { + console.log(nextAction); + if (nextAction.nextSegment) { + const index = selectedSegments.indexOf(playingSegment); + let newIndex = getNewJumpIndex(index >= 0 ? index : 0, 1); + if (newIndex > selectedSegments.length - 1) newIndex = 0; // have reached end of last segment, start over + const nextSelectedSegment = selectedSegments[newIndex]; + if (nextSelectedSegment != null) seekAbs(nextSelectedSegment.start); + } + if (nextAction.seekTo != null) { + seekAbs(nextAction.seekTo); + } + if (nextAction.exit) { + playbackModeRef.current = undefined; + pause(); } } } - }, [getCutSegmentById, getSegApparentEnd, pause, playbackModeRef, playerTime, seekAbs, selectedSegments, setPlayerTime]); + }, [commandedTimeRef, cutSegments, getSegApparentEnd, pause, playbackModeRef, playerTime, seekAbs, selectedSegments, setPlayerTime]); const closeFileWithConfirm = useCallback(() => { if (!isFileOpened || workingRef.current) return; diff --git a/src/renderer/src/SegmentList.tsx b/src/renderer/src/SegmentList.tsx index 4d40d316..5fe48fc8 100644 --- a/src/renderer/src/SegmentList.tsx +++ b/src/renderer/src/SegmentList.tsx @@ -330,7 +330,7 @@ function SegmentList({ if (cutSegments.length < 2) return; const { value } = await Swal.fire({ title: `${t('Change order of segment')} ${index + 1}`, - text: t('Please enter a number from 1 to {{n}} to be the new order for the current segment', { n: cutSegments.length}), + text: t('Please enter a number from 1 to {{n}} to be the new order for the current segment', { n: cutSegments.length }), input: 'text', inputValue: index + 1, showCancelButton: true, diff --git a/src/renderer/src/hooks/useSegments.ts b/src/renderer/src/hooks/useSegments.ts index 54034d9c..b9d3e880 100644 --- a/src/renderer/src/hooks/useSegments.ts +++ b/src/renderer/src/hooks/useSegments.ts @@ -148,8 +148,6 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea end: getSegApparentEnd(cutSegment), })), [getSegApparentEnd]); - const getCutSegmentById = useCallback((id: string) => cutSegments.find((s) => s.segId === id), [cutSegments]); - const haveInvalidSegs = useMemo(() => cutSegments.some((cutSegment) => getSegApparentStart(cutSegment) >= getSegApparentEnd(cutSegment)), [cutSegments, getSegApparentEnd]); const currentSegIndexSafe = Math.min(currentSegIndex, cutSegments.length - 1); @@ -683,7 +681,6 @@ function useSegments({ filePath, workingRef, setWorking, setProgress, videoStrea createNumSegments, createFixedDurationSegments, createRandomSegments, - getCutSegmentById, getApparentCutSegments, getSegApparentEnd, haveInvalidSegs, diff --git a/src/renderer/src/hooks/useVideo.ts b/src/renderer/src/hooks/useVideo.ts index d245a005..c78954f8 100644 --- a/src/renderer/src/hooks/useVideo.ts +++ b/src/renderer/src/hooks/useVideo.ts @@ -1,10 +1,10 @@ -import { ReactEventHandler, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ReactEventHandler, useCallback, useMemo, useRef, useState } from 'react'; import { ChromiumHTMLVideoElement, PlaybackMode } from '../types'; import { isDurationValid } from '../segments'; import { showPlaybackFailedMessage } from '../swal'; export default ({ filePath }: { filePath: string | undefined }) => { - const [commandedTime, setCommandedTime] = useState(0); + const [commandedTime, setCommandedTimeRaw] = useState(0); const [compatPlayerEventId, setCompatPlayerEventId] = useState(0); const [playbackRate, setPlaybackRateState] = useState(1); const [outputPlaybackRate, setOutputPlaybackRateState] = useState(1); @@ -54,6 +54,13 @@ export default ({ filePath }: { filePath: string | undefined }) => { } }, []); + const commandedTimeRef = useRef(commandedTime); + + const setCommandedTime = useCallback((t: number) => { + commandedTimeRef.current = t; + setCommandedTimeRaw(t); + }, []); + const seekAbs = useCallback((val: number | undefined) => { const video = videoRef.current; if (video == null || val == null || Number.isNaN(val)) return; @@ -64,12 +71,7 @@ export default ({ filePath }: { filePath: string | undefined }) => { smoothSeek(outVal); setCommandedTime(outVal); setCompatPlayerEventId((id) => id + 1); // To make sure that we can seek even to the same commanded time that we are already add (e.g. loop current segment) - }, [smoothSeek]); - - const commandedTimeRef = useRef(commandedTime); - useEffect(() => { - commandedTimeRef.current = commandedTime; - }, [commandedTime]); + }, [setCommandedTime, smoothSeek]); // Relevant time is the player's playback position if we're currently playing - if not, it's the user's commanded time. const relevantTime = useMemo(() => (playing ? playerTime : commandedTime) || 0, [commandedTime, playerTime, playing]); @@ -86,7 +88,7 @@ export default ({ filePath }: { filePath: string | undefined }) => { if (!val) { setCommandedTime(videoRef.current!.currentTime); } - }, []); + }, [setCommandedTime]); const onStopPlaying = useCallback(() => { onPlayingChange(false); diff --git a/src/renderer/src/segments.ts b/src/renderer/src/segments.ts index 1090acba..9019960b 100644 --- a/src/renderer/src/segments.ts +++ b/src/renderer/src/segments.ts @@ -53,7 +53,7 @@ export function findSegmentsAtCursor(segments: SegmentBase[], currentTime: numbe segments.forEach((segment, index) => { if ((segment.start == null || segment.start <= currentTime) && (segment.end == null || segment.end >= currentTime)) indexes.push(index); }); - return indexes; + return indexes.reverse(); // if we are on multiple, select the last. That way, auto play selected segments won't go on repeat on the same segment } // in the past we had non-string tags