1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-25 19:52:44 +01:00

implement play only current segment

closes #574
This commit is contained in:
Mikael Finstad 2023-02-16 17:29:46 +08:00
parent bc4edf9829
commit ff56c44a32
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
5 changed files with 88 additions and 13 deletions

View File

@ -76,7 +76,7 @@ import { askForHtml5ifySpeed } from './dialogs/html5ify';
import { askForOutDir, askForImportChapters, promptTimeOffset, askForFileOpenAction, confirmExtractAllStreamsDialog, showCleanupFilesDialog, showDiskFull, showExportFailedDialog, showConcatFailedDialog, openYouTubeChaptersDialog, openAbout, showRefuseToOverwrite, openDirToast, openCutFinishedToast, openConcatFinishedToast } from './dialogs';
import { openSendReportDialog } from './reporting';
import { fallbackLng } from './i18n';
import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid } from './segments';
import { createSegment, getCleanCutSegments, findSegmentsAtCursor, sortSegments, getSegmentTags, convertSegmentsToChapters, hasAnySegmentOverlap, isDurationValid, playOnlyCurrentSegment } from './segments';
import { getOutSegError as getOutSegErrorRaw } from './util/outputNameTemplate';
import { rightBarWidth, leftBarWidth, ffmpegExtractWindow, zoomMax } from './util/constants';
@ -114,6 +114,7 @@ const App = memo(() => {
const [working, setWorkingState] = useState();
const [usingDummyVideo, setUsingDummyVideo] = useState(false);
const [playing, setPlaying] = useState(false);
const playingOnlySegmentIdRef = useRef();
const [playerTime, setPlayerTime] = useState();
const [duration, setDuration] = useState();
const [rotation, setRotation] = useState(360);
@ -341,7 +342,7 @@ 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,
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 });
const jumpSegStart = useCallback((index) => seekAbs(apparentCutSegments[index].start), [apparentCutSegments, seekAbs]);
@ -431,7 +432,10 @@ const App = memo(() => {
}
}
const onStopPlaying = useCallback(() => onPlayingChange(false), []);
const onStopPlaying = useCallback(() => {
onPlayingChange(false);
playingOnlySegmentIdRef.current = undefined;
}, []);
const onSartPlaying = useCallback(() => onPlayingChange(true), []);
const onDurationChange = useCallback((e) => {
// Some files report duration infinity first, then proper duration later
@ -441,12 +445,6 @@ const App = memo(() => {
if (isDurationValid(durationNew)) setDuration(durationNew);
}, []);
const onTimeUpdate = useCallback((e) => {
const { currentTime } = e.target;
if (playerTime === currentTime) return;
setPlayerTime(currentTime);
}, [playerTime]);
const increaseRotation = useCallback(() => {
setRotation((r) => (r + 90) % 450);
setHideCanvasPreview(false);
@ -630,6 +628,7 @@ const App = memo(() => {
setPreviewFilePath();
setUsingDummyVideo(false);
setPlaying(false);
playingOnlySegmentIdRef.current = undefined;
setDuration();
cutSegmentsHistory.go(0);
clearSegments(); // TODO this will cause two history items
@ -775,13 +774,38 @@ const App = memo(() => {
});
}, [filePath, playing]);
const togglePlay = useCallback((resetPlaybackRate) => {
const togglePlay = useCallback(({ resetPlaybackRate, onlyCurrentSegment } = {}) => {
playingOnlySegmentIdRef.current = undefined;
if (playing) {
pause();
return;
}
if (onlyCurrentSegment != null) {
playingOnlySegmentIdRef.current = { segId: currentApparentCutSeg.segId, mode: onlyCurrentSegment };
seekAbs(currentApparentCutSeg.start);
}
play(resetPlaybackRate);
}, [playing, play, pause]);
}, [playing, play, pause, currentApparentCutSeg.segId, currentApparentCutSeg.start, seekAbs]);
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 (stop) {
playingOnlySegmentIdRef.current = undefined;
pause();
}
}
}
}, [getApparentCutSegmentById, pause, playerTime, seekAbs]);
const closeFileWithConfirm = useCallback(() => {
if (!isFileOpened || workingRef.current) return;
@ -1710,7 +1734,10 @@ const App = memo(() => {
// For actions, see also KeyboardShortcuts.jsx
const mainActions = {
togglePlayNoResetSpeed: () => togglePlay(),
togglePlayResetSpeed: () => togglePlay(true),
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' }),
play: () => play(),
pause,
reducePlaybackRate: () => changePlaybackRate(-1),

View File

@ -232,7 +232,7 @@ const BottomBar = memo(({
/>
)}
<div role="button" onClick={togglePlay} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}>
<div role="button" onClick={() => togglePlay()} style={{ background: primaryColor, margin: '2px 5px 0 5px', display: 'flex', alignItems: 'center', justifyContent: 'center', width: 34, height: 34, borderRadius: 17 }}>
<PlayPause
style={{ marginLeft: playing ? 0 : 2 }}
size={16}

View File

@ -156,6 +156,18 @@ 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,

View File

@ -106,6 +106,8 @@ export default ({
// These are segments guaranteed to have a start and end time
const apparentCutSegments = useMemo(() => getApparentCutSegments(cutSegments), [cutSegments, getApparentCutSegments]);
const getApparentCutSegmentById = useCallback((id) => apparentCutSegments.find((s) => s.segId === id), [apparentCutSegments]);
const haveInvalidSegs = useMemo(() => apparentCutSegments.some((cutSegment) => cutSegment.start >= cutSegment.end), [apparentCutSegments]);
const currentSegIndexSafe = Math.min(currentSegIndex, cutSegments.length - 1);
@ -465,6 +467,7 @@ export default ({
createFixedDurationSegments,
createRandomSegments,
apparentCutSegments,
getApparentCutSegmentById,
haveInvalidSegs,
currentSegIndexSafe,
currentCutSeg,

View File

@ -164,3 +164,36 @@ export function convertSegmentsToChapters(sortedSegments) {
// inverted segments will be "gap" segments. Merge together with normal segments
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;
if (currentTime >= playingOnlySegment.end) {
return { seek: playingOnlySegment.start };
}
if (currentTime < endWindowStart && currentTime >= startWindowEnd) {
return { seek: endWindowStart };
}
}
if (mode === 'loop-full') {
if (currentTime >= playingOnlySegment.end) {
return { seek: playingOnlySegment.start };
}
}
// mode === 'play'
if (currentTime >= playingOnlySegment.end) {
return {
seek: playingOnlySegment.end,
stop: true,
};
}
return {};
}