diff --git a/src/App.jsx b/src/App.jsx index ab148a3b..9fc58cff 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -115,7 +115,7 @@ const App = memo(() => { const [usingDummyVideo, setUsingDummyVideo] = useState(false); const [playing, setPlaying] = useState(false); const [canvasPlayerEventId, setCanvasPlayerEventId] = useState(0); - const playingOnlySegmentIdRef = useRef(); + const playbackModeRef = useRef(); const [playerTime, setPlayerTime] = useState(); const [duration, setDuration] = useState(); const [rotation, setRotation] = useState(360); @@ -278,14 +278,19 @@ const App = memo(() => { setCanvasPlayerEventId((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) }, []); + const userSeekAbs = useCallback((val) => { + playbackModeRef.current = undefined; // If the user seeks, we clear any custom playback mode + return seekAbs(val); + }, [seekAbs]); + const commandedTimeRef = useRef(commandedTime); useEffect(() => { commandedTimeRef.current = commandedTime; }, [commandedTime]); const seekRel = useCallback((val) => { - seekAbs(videoRef.current.currentTime + val); - }, [seekAbs]); + userSeekAbs(videoRef.current.currentTime + val); + }, [userSeekAbs]); const seekRelPercent = useCallback((val) => { if (!isDurationValid(zoomedDuration)) return; @@ -299,8 +304,8 @@ const App = memo(() => { // try to align with frame const currentTimeNearestFrameNumber = getFrameCountRaw(fps, videoRef.current.currentTime); const nextFrame = currentTimeNearestFrameNumber + direction; - seekAbs(nextFrame / fps); - }, [seekAbs, detectedFps]); + userSeekAbs(nextFrame / fps); + }, [detectedFps, userSeekAbs]); // 360 means we don't modify rotation const isRotationSet = rotation !== 360; @@ -343,15 +348,15 @@ const App = memo(() => { }, [isFileOpened]); const { - cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, selectedSegmentsRaw, setCutTime, getSegApparentEnd, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, - } = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened }); + cutSegments, cutSegmentsHistory, createSegmentsFromKeyframes, shuffleSegments, detectBlackScenes, detectSilentScenes, detectSceneChanges, removeCutSegment, invertAllSegments, fillSegmentsGaps, combineOverlappingSegments, shiftAllSegmentTimes, alignSegmentTimesToKeyframes, onViewSegmentTags, updateSegOrder, updateSegOrders, reorderSegsByStartTime, addSegment, setCutStart, setCutEnd, onLabelSegment, splitCurrentSegment, createNumSegments, createFixedDurationSegments, createRandomSegments, apparentCutSegments, haveInvalidSegs, currentSegIndexSafe, currentCutSeg, currentApparentCutSeg, inverseCutSegments, clearSegments, loadCutSegments, selectedSegmentsRaw, setCutTime, getSegApparentEnd, setCurrentSegIndex, onLabelSelectedSegments, deselectAllSegments, selectAllSegments, selectOnlyCurrentSegment, toggleCurrentSegmentSelected, removeSelectedSegments, setDeselectedSegmentIds, onSelectSegmentsByLabel, toggleSegmentSelected, selectOnlySegment, getApparentCutSegmentById, selectedSegments, selectedSegmentsOrInverse, nonFilteredSegmentsOrInverse, + } = useSegments({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments }); - const jumpSegStart = useCallback((index) => seekAbs(apparentCutSegments[index].start), [apparentCutSegments, seekAbs]); - const jumpSegEnd = useCallback((index) => seekAbs(apparentCutSegments[index].end), [apparentCutSegments, seekAbs]); + const jumpSegStart = useCallback((index) => userSeekAbs(apparentCutSegments[index].start), [apparentCutSegments, userSeekAbs]); + const jumpSegEnd = useCallback((index) => userSeekAbs(apparentCutSegments[index].end), [apparentCutSegments, userSeekAbs]); const jumpCutStart = useCallback(() => jumpSegStart(currentSegIndexSafe), [currentSegIndexSafe, jumpSegStart]); const jumpCutEnd = useCallback(() => jumpSegEnd(currentSegIndexSafe), [currentSegIndexSafe, jumpSegEnd]); - const jumpTimelineStart = useCallback(() => seekAbs(0), [seekAbs]); - const jumpTimelineEnd = useCallback(() => seekAbs(durationSafe), [durationSafe, seekAbs]); + const jumpTimelineStart = useCallback(() => userSeekAbs(0), [userSeekAbs]); + const jumpTimelineEnd = useCallback(() => userSeekAbs(durationSafe), [durationSafe, userSeekAbs]); const getFrameCount = useCallback((sec) => getFrameCountRaw(detectedFps, sec), [detectedFps]); @@ -435,7 +440,7 @@ const App = memo(() => { const onStopPlaying = useCallback(() => { onPlayingChange(false); - playingOnlySegmentIdRef.current = undefined; + playbackModeRef.current = undefined; }, []); const onSartPlaying = useCallback(() => onPlayingChange(true), []); const onDurationChange = useCallback((e) => { @@ -629,7 +634,7 @@ const App = memo(() => { setPreviewFilePath(); setUsingDummyVideo(false); setPlaying(false); - playingOnlySegmentIdRef.current = undefined; + playbackModeRef.current = undefined; setCanvasPlayerEventId(0); setDuration(); cutSegmentsHistory.go(0); @@ -761,6 +766,9 @@ const App = memo(() => { const showPlaybackFailedMessage = () => errorToast(i18n.t('Unable to playback this file. Try to convert to supported format from the menu')); + const getNewJumpIndex = (oldIndex, direction) => Math.max(oldIndex + direction, 0); + const jumpSeg = useCallback((direction) => setCurrentSegIndex((old) => Math.min(getNewJumpIndex(old, direction), cutSegments.length - 1)), [cutSegments, setCurrentSegIndex]); + const pause = useCallback(() => { if (!filePath || !playing) return; videoRef.current.pause(); @@ -777,38 +785,56 @@ const App = memo(() => { }); }, [filePath, playing]); - const togglePlay = useCallback(({ resetPlaybackRate, onlyCurrentSegment } = {}) => { - playingOnlySegmentIdRef.current = undefined; + const togglePlay = useCallback(({ resetPlaybackRate, playbackMode } = {}) => { + playbackModeRef.current = undefined; if (playing) { pause(); return; } - if (onlyCurrentSegment != null) { - playingOnlySegmentIdRef.current = { segId: currentApparentCutSeg.segId, mode: onlyCurrentSegment }; - seekAbs(currentApparentCutSeg.start); + if (playbackMode != null) { + if (playbackMode === 'loop-selected-segments') { + const firstSelectedSegment = selectedSegments[0]; + playbackModeRef.current = { segId: firstSelectedSegment.segId, playbackMode }; + const index = apparentCutSegments.indexOf(firstSelectedSegment); + if (index >= 0) setCurrentSegIndex(index); + seekAbs(firstSelectedSegment.start); + } else { + playbackModeRef.current = { segId: currentApparentCutSeg.segId, playbackMode }; + seekAbs(currentApparentCutSeg.start); + } } play(resetPlaybackRate); - }, [playing, play, pause, currentApparentCutSeg.segId, currentApparentCutSeg.start, seekAbs]); + }, [playing, play, pause, selectedSegments, apparentCutSegments, setCurrentSegIndex, seekAbs, currentApparentCutSeg.segId, currentApparentCutSeg.start]); const onTimeUpdate = useCallback((e) => { const { currentTime } = e.target; if (playerTime === currentTime) return; setPlayerTime(currentTime); - if (playingOnlySegmentIdRef.current != null) { - const { segId, mode } = playingOnlySegmentIdRef.current; - const playingOnlySegment = getApparentCutSegmentById(segId); - - if (playingOnlySegment != null) { - const { seek, stop } = playOnlyCurrentSegment({ mode, currentTime, playingOnlySegment }); - if (seek) seekAbs(seek); + if (playbackModeRef.current != null) { + const { segId, playbackMode } = playbackModeRef.current; + const playingSegment = getApparentCutSegmentById(segId); + + if (playingSegment != null) { + const { seek, stop, nextSegment } = playOnlyCurrentSegment({ playbackMode, currentTime, playingSegment }); + // console.log({ seek, stop, nextSegment }); + + if (nextSegment != null) { + 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); + playbackModeRef.current.segId = nextSelectedSegment.segId; + } + if (seek != null) seekAbs(seek); if (stop) { - playingOnlySegmentIdRef.current = undefined; + playbackModeRef.current = undefined; pause(); } } } - }, [getApparentCutSegmentById, pause, playerTime, seekAbs]); + }, [getApparentCutSegmentById, pause, playerTime, seekAbs, selectedSegments]); const closeFileWithConfirm = useCallback(() => { if (!isFileOpened || workingRef.current) return; @@ -998,14 +1024,6 @@ const App = memo(() => { } }, [isFileOpened, cleanupChoices, askForCleanupChoices, cleanupFiles, setWorking]); - // For invertCutSegments we do not support filtering - const selectedSegmentsOrInverseRaw = useMemo(() => (invertCutSegments ? inverseCutSegments : selectedSegmentsRaw), [inverseCutSegments, invertCutSegments, selectedSegmentsRaw]); - - const nonFilteredSegments = useMemo(() => (invertCutSegments ? inverseCutSegments : apparentCutSegments), [invertCutSegments, inverseCutSegments, apparentCutSegments]); - - // If user has selected none to export, it makes no sense, so export all instead - const selectedSegmentsOrInverse = selectedSegmentsOrInverseRaw.length > 0 ? selectedSegmentsOrInverseRaw : nonFilteredSegments; - const segmentsToExport = useMemo(() => { if (!segmentsToChaptersOnly) return selectedSegmentsOrInverse; // segmentsToChaptersOnly is a special mode where all segments will be simply written out as chapters to one file: https://github.com/mifi/lossless-cut/issues/993#issuecomment-1037927595 @@ -1430,13 +1448,11 @@ const App = memo(() => { const toggleLastCommands = useCallback(() => setLastCommandsVisible(val => !val), []); const toggleSettings = useCallback(() => setSettingsVisible(val => !val), []); - const jumpSeg = useCallback((val) => setCurrentSegIndex((old) => Math.max(Math.min(old + val, cutSegments.length - 1), 0)), [cutSegments.length, setCurrentSegIndex]); - const seekClosestKeyframe = useCallback((direction) => { const time = findNearestKeyFrameTime({ time: getRelevantTime(), direction }); if (time == null) return; - seekAbs(time); - }, [findNearestKeyFrameTime, getRelevantTime, seekAbs]); + userSeekAbs(time); + }, [findNearestKeyFrameTime, getRelevantTime, userSeekAbs]); const seekAccelerationRef = useRef(1); @@ -1520,8 +1536,8 @@ const App = memo(() => { if (timeCode === undefined) return; - seekAbs(timeCode); - }, [filePath, seekAbs]); + userSeekAbs(timeCode); + }, [filePath, userSeekAbs]); const toggleStreamsSelector = useCallback(() => setStreamsSelectorShown((v) => !v), []); @@ -1767,6 +1783,8 @@ const App = memo(() => { setConcatDialogVisible(true); }, [batchFiles.length, openFilesDialog]); + const toggleLoopSelectedSegments = useCallback(() => togglePlay({ resetPlaybackRate: true, playbackMode: 'loop-selected-segments' }), [togglePlay]); + const onKeyPress = useCallback(({ action, keyup }) => { function seekReset() { seekAccelerationRef.current = 1; @@ -1777,9 +1795,10 @@ const App = memo(() => { const mainActions = { togglePlayNoResetSpeed: () => togglePlay(), togglePlayResetSpeed: () => togglePlay({ resetPlaybackRate: true }), - togglePlayOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'play' }), - toggleLoopOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'loop-full' }), - toggleLoopStartEndOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, onlyCurrentSegment: 'loop-start-end' }), + togglePlayOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, playbackMode: 'play-segment-once' }), + toggleLoopOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, playbackMode: 'loop-segment' }), + toggleLoopStartEndOnlyCurrentSegment: () => togglePlay({ resetPlaybackRate: true, playbackMode: 'loop-segment-start-end' }), + toggleLoopSelectedSegments, play: () => play(), pause, reducePlaybackRate: () => changePlaybackRate(-1), @@ -1907,7 +1926,7 @@ const App = memo(() => { if (match) return bubble; return true; // bubble the event - }, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, combineOverlappingSegments, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, exportConfirmVisible, extractAllStreams, extractCurrentSegmentFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegment, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); + }, [addSegment, alignSegmentTimesToKeyframes, askSetStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, cleanupFilesDialog, clearSegments, closeBatch, closeExportConfirm, combineOverlappingSegments, concatCurrentBatch, concatDialogVisible, convertFormatBatch, createFixedDurationSegments, createNumSegments, createRandomSegments, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, exportConfirmVisible, extractAllStreams, extractCurrentSegmentFramesAsImages, fillSegmentsGaps, goToTimecode, increaseRotation, invertAllSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, keyboardShortcutsVisible, onExportConfirm, onExportPress, onLabelSegment, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleStreamsSelector, toggleStripAudio, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); useKeyboard({ keyBindings, onKeyPress }); @@ -2277,7 +2296,7 @@ const App = memo(() => { commandedTimeRef={commandedTimeRef} startTimeOffset={startTimeOffset} zoom={zoom} - seekAbs={seekAbs} + seekAbs={userSeekAbs} durationSafe={durationSafe} apparentCutSegments={apparentCutSegments} setCurrentSegIndex={setCurrentSegIndex} @@ -2304,10 +2323,11 @@ const App = memo(() => { captureSnapshot={captureSnapshot} onExportPress={onExportPress} segmentsToExport={segmentsToExport} - seekAbs={seekAbs} + seekAbs={userSeekAbs} currentSegIndexSafe={currentSegIndexSafe} cutSegments={cutSegments} currentCutSeg={currentCutSeg} + selectedSegments={selectedSegments} setCutStart={setCutStart} setCutEnd={setCutEnd} setCurrentSegIndex={setCurrentSegIndex} @@ -2332,6 +2352,8 @@ const App = memo(() => { keyframesEnabled={keyframesEnabled} toggleKeyframesEnabled={toggleKeyframesEnabled} detectedFps={detectedFps} + toggleLoopSelectedSegments={toggleLoopSelectedSegments} + isFileOpened={isFileOpened} /> @@ -2371,7 +2393,7 @@ const App = memo(() => { )} - + { const { t } = useTranslation(); + // ok this is a bit over-engineered but what the hell! + const loopSelectedSegmentsButtonStyle = useMemo(() => { + // cannot have less than 1 gradient element: + const selectedSegmentsSafe = (selectedSegments.length > 1 ? selectedSegments : [selectedSegments[0], selectedSegments[0]]).slice(0, 10); + + const gradientColors = selectedSegmentsSafe.map((seg, i) => { + const segColor = getSegColor(seg); + // make colors stronger, the more segments + return `${segColor.alpha(Math.max(0.4, Math.min(0.8, selectedSegmentsSafe.length / 3))).string()} ${((i / (selectedSegmentsSafe.length - 1)) * 100).toFixed(1)}%`; + }).join(', '); + + return { + paddingLeft: 2, + backgroundOffset: 30, + background: `linear-gradient(90deg, ${gradientColors})`, + border: '1px solid rgb(200,200,200)', + margin: '2px 4px 0 0px', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + width: 20, + height: 24, + borderRadius: 4, + }; + }, [selectedSegments]); + const { invertCutSegments, setInvertCutSegments, simpleMode, toggleSimpleMode, exportConfirmEnabled } = useUserSettings(); const onYinYangClick = useCallback(() => { @@ -199,6 +225,7 @@ const BottomBar = memo(({ <> togglePlay()} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}> +
+ +
+ {!simpleMode && ( )} - {!simpleMode && ( + {!simpleMode && isFileOpened && ( -
+
{renderNumber()} - {timeStr} + {timeStr}
{seg.name}
diff --git a/src/components/KeyboardShortcuts.jsx b/src/components/KeyboardShortcuts.jsx index 804e485a..a563a106 100644 --- a/src/components/KeyboardShortcuts.jsx +++ b/src/components/KeyboardShortcuts.jsx @@ -113,6 +113,7 @@ const KeyboardShortcuts = memo(({ const { actionsMap, extraLinesPerCategory } = useMemo(() => { const playbackCategory = t('Playback'); + const selectivePlaybackCategory = t('Playback/preview segments only'); const seekingCategory = t('Seeking'); const segmentsAndCutpointsCategory = t('Segments and cut points'); const zoomOperationsCategory = t('Timeline/zoom operations'); @@ -156,18 +157,6 @@ const KeyboardShortcuts = memo(({ name: t('Play/pause (no reset speed)'), category: playbackCategory, }, - togglePlayOnlyCurrentSegment: { - name: t('Play/pause (only current segment)'), - category: playbackCategory, - }, - toggleLoopOnlyCurrentSegment: { - name: t('Loop/pause (only current segment)'), - category: playbackCategory, - }, - toggleLoopStartEndOnlyCurrentSegment: { - name: t('Loop/pause (only beginning and end of current segment)'), - category: playbackCategory, - }, play: { name: t('Play'), category: playbackCategory, @@ -201,6 +190,24 @@ const KeyboardShortcuts = memo(({ category: playbackCategory, }, + // selectivePlaybackCategory + togglePlayOnlyCurrentSegment: { + name: t('Play current segment once'), + category: selectivePlaybackCategory, + }, + toggleLoopOnlyCurrentSegment: { + name: t('Loop current segment'), + category: selectivePlaybackCategory, + }, + toggleLoopStartEndOnlyCurrentSegment: { + name: t('Loop beginning and end of current segment'), + category: selectivePlaybackCategory, + }, + toggleLoopSelectedSegments: { + name: t('Play selected segments in order'), + category: selectivePlaybackCategory, + }, + // seekingCategory seekPreviousFrame: { name: t('Step backward 1 frame'), diff --git a/src/components/SegmentCutpointButton.jsx b/src/components/SegmentCutpointButton.jsx index fb7d3d41..14cd4446 100644 --- a/src/components/SegmentCutpointButton.jsx +++ b/src/components/SegmentCutpointButton.jsx @@ -14,7 +14,7 @@ const SegmentCutpointButton = ({ currentCutSeg, side, Icon, onClick, title, styl size={13} title={title} role="button" - style={{ color: 'white', padding: start ? '4px 4px 4px 2px' : '4px 2px 4px 4px', borderLeft: start && border, borderRight: !start && border, background: backgroundColor, borderRadius: 6, ...style }} + style={{ flexShrink: 0, color: 'white', padding: start ? '4px 4px 4px 2px' : '4px 2px 4px 4px', borderLeft: start && border, borderRight: !start && border, background: backgroundColor, borderRadius: 6, ...style }} onClick={onClick} /> ); diff --git a/src/hooks/useSegments.js b/src/hooks/useSegments.js index d5b90294..0a2f97e9 100644 --- a/src/hooks/useSegments.js +++ b/src/hooks/useSegments.js @@ -18,7 +18,7 @@ import { maxSegmentsAllowed } from '../util/constants'; export default ({ filePath, workingRef, setWorking, setCutProgress, mainVideoStream, - duration, getRelevantTime, maxLabelLength, checkFileOpened, + duration, getRelevantTime, maxLabelLength, checkFileOpened, invertCutSegments, }) => { // Segment related state const segCounterRef = useRef(0); @@ -430,6 +430,13 @@ export default ({ })); }, [maxLabelLength, selectedSegmentsRaw, setCutSegments]); + // Guaranteed to have at least one segment (if user has selected none to export (selectedSegments empty), it makes no sense so select all instead.) + const selectedSegments = useMemo(() => (selectedSegmentsRaw.length > 0 ? selectedSegmentsRaw : apparentCutSegments), [apparentCutSegments, selectedSegmentsRaw]); + + // For invertCutSegments we do not support filtering (selecting) segments + const selectedSegmentsOrInverse = useMemo(() => (invertCutSegments ? inverseCutSegments : selectedSegments), [inverseCutSegments, invertCutSegments, selectedSegments]); + const nonFilteredSegmentsOrInverse = useMemo(() => (invertCutSegments ? inverseCutSegments : apparentCutSegments), [invertCutSegments, inverseCutSegments, apparentCutSegments]); + const removeSelectedSegments = useCallback(() => removeSegments(selectedSegmentsRaw.map((seg) => seg.segId)), [removeSegments, selectedSegmentsRaw]); const selectOnlySegment = useCallback((seg) => setDeselectedSegmentIds(Object.fromEntries(cutSegments.filter((s) => s.segId !== seg.segId).map((s) => [s.segId, true]))), [cutSegments]); @@ -476,7 +483,9 @@ export default ({ clearSegments, loadCutSegments, selectedSegmentsRaw, - setCutTime, + selectedSegments, + selectedSegmentsOrInverse, + nonFilteredSegmentsOrInverse, getSegApparentEnd, setCurrentSegIndex, diff --git a/src/segments.js b/src/segments.js index bc28a54f..2ec6e632 100644 --- a/src/segments.js +++ b/src/segments.js @@ -165,34 +165,49 @@ export function convertSegmentsToChapters(sortedSegments) { return sortSegments([...sortedSegments, ...invertedSegments]); } -export function playOnlyCurrentSegment({ mode, currentTime, playingOnlySegment }) { - if (mode === 'loop-start-end') { - const maxSec = 3; // max time each side (start/end) - const sec = Math.min(maxSec, (playingOnlySegment.end - playingOnlySegment.start) / 3) * 2; - - const startWindowEnd = playingOnlySegment.start + sec / 2; - const endWindowStart = playingOnlySegment.end - sec / 2; +export function playOnlyCurrentSegment({ playbackMode, currentTime, playingSegment }) { + switch (playbackMode) { + case 'loop-segment-start-end': { + const maxSec = 3; // max time each side (start/end) + const sec = Math.min(maxSec, (playingSegment.end - playingSegment.start) / 3) * 2; + + const startWindowEnd = playingSegment.start + sec / 2; + const endWindowStart = playingSegment.end - sec / 2; + + if (currentTime >= playingSegment.end) { + return { seek: playingSegment.start }; + } + if (currentTime < endWindowStart && currentTime >= startWindowEnd) { + return { seek: endWindowStart }; + } + break; + } - if (currentTime >= playingOnlySegment.end) { - return { seek: playingOnlySegment.start }; + case 'loop-segment': { + if (currentTime >= playingSegment.end) { + return { seek: playingSegment.start }; + } + break; } - if (currentTime < endWindowStart && currentTime >= startWindowEnd) { - return { seek: endWindowStart }; + + case 'play-segment-once': { + if (currentTime >= playingSegment.end) { + return { + seek: playingSegment.end, + stop: true, + }; + } + break; } - } - if (mode === 'loop-full') { - if (currentTime >= playingOnlySegment.end) { - return { seek: playingOnlySegment.start }; + case 'loop-selected-segments': { + if (currentTime >= playingSegment.end) { + return { nextSegment: true }; + } + break; } - } - // mode === 'play' - if (currentTime >= playingOnlySegment.end) { - return { - seek: playingOnlySegment.end, - stop: true, - }; + default: } return {};