diff --git a/package.json b/package.json index 8a1e5873..bb34bc6a 100644 --- a/package.json +++ b/package.json @@ -82,6 +82,7 @@ "react-sortablejs": "^6.1.4", "react-syntax-highlighter": "^15.4.3", "react-use": "^17.4.0", + "screenfull": "^6.0.2", "scroll-into-view-if-needed": "^2.2.28", "sharp": "^0.32.6", "smpte-timecode": "^1.2.3", diff --git a/public/configStore.js b/public/configStore.js index 8a621da8..48f90565 100644 --- a/public/configStore.js +++ b/public/configStore.js @@ -65,6 +65,8 @@ const defaultKeyBindings = [ { keys: 'ctrl+c', action: 'copySegmentsToClipboard' }, { keys: 'command+c', action: 'copySegmentsToClipboard' }, + { key: 'f', action: 'toggleFullscreenVideo' }, + { keys: 'enter', action: 'labelCurrentSegment' }, { keys: 'e', action: 'export' }, diff --git a/src/App.jsx b/src/App.jsx index 79ade467..4a538966 100644 --- a/src/App.jsx +++ b/src/App.jsx @@ -8,6 +8,7 @@ import { useDebounce } from 'use-debounce'; import i18n from 'i18next'; import { useTranslation } from 'react-i18next'; import { produce } from 'immer'; +import screenfull from 'screenfull'; import fromPairs from 'lodash/fromPairs'; import sortBy from 'lodash/sortBy'; @@ -1909,6 +1910,21 @@ const App = memo(() => { } }, [addStreamSourceFile]); + const toggleFullscreenVideo = useCallback(async () => { + if (!screenfull.isEnabled) { + console.warn('Fullscreen not allowed'); + return; + } + try { + if (videoRef.current == null) { + console.warn('No video tag to full screen'); + return; + } + await screenfull.toggle(videoRef.current, { navigationUI: 'hide' }); + } catch (err) { + console.error('Failed to toggle fullscreen', err); + } + }, []); const mainActions = useMemo(() => { async function exportYouTube() { @@ -2039,8 +2055,9 @@ const App = memo(() => { toggleShowThumbnails, toggleShowKeyframes, showIncludeExternalStreamsDialog, + toggleFullscreenVideo, }; - }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); + }, [addSegment, alignSegmentTimesToKeyframes, apparentCutSegments, askStartTimeOffset, batchFileJump, batchOpenSelectedFile, captureSnapshot, captureSnapshotAsCoverArt, changePlaybackRate, checkFileOpened, cleanupFilesDialog, clearSegments, closeBatch, closeFileWithConfirm, combineOverlappingSegments, combineSelectedSegments, concatBatch, convertFormatBatch, copySegmentsToClipboard, createFixedDurationSegments, createNumSegments, createRandomSegments, createSegmentsFromKeyframes, currentSegIndexSafe, cutSegmentsHistory, deselectAllSegments, detectBlackScenes, detectSceneChanges, detectSilentScenes, duplicateCurrentSegment, extractAllStreams, extractCurrentSegmentFramesAsImages, extractSelectedSegmentsFramesAsImages, fillSegmentsGaps, goToTimecode, handleShowStreamsSelectorClick, increaseRotation, invertAllSegments, invertSelectedSegments, jumpCutEnd, jumpCutStart, jumpSeg, jumpTimelineEnd, jumpTimelineStart, keyboardNormalSeekSpeed, keyboardSeekAccFactor, onExportPress, onLabelSegment, openFilesDialog, openSendReportDialogWithState, pause, play, removeCutSegment, removeSelectedSegments, reorderSegsByStartTime, seekClosestKeyframe, seekRel, seekRelPercent, selectAllSegments, selectOnlyCurrentSegment, setCutEnd, setCutStart, setPlaybackVolume, shiftAllSegmentTimes, shortStep, showIncludeExternalStreamsDialog, shuffleSegments, splitCurrentSegment, timelineToggleComfortZoom, toggleCaptureFormat, toggleCurrentSegmentSelected, toggleFullscreenVideo, toggleKeyboardShortcuts, toggleKeyframeCut, toggleLastCommands, toggleLoopSelectedSegments, togglePlay, toggleSegmentsList, toggleSettings, toggleShowKeyframes, toggleShowThumbnails, toggleStreamsSelector, toggleStripAudio, toggleWaveformMode, tryFixInvalidDuration, userHtml5ifyCurrentFile, zoomRel]); const getKeyboardAction = useCallback((action) => mainActions[action], [mainActions]); @@ -2342,6 +2359,7 @@ const App = memo(() => {
{/* eslint-disable-next-line jsx-a11y/media-has-caption */} diff --git a/src/components/KeyboardShortcuts.jsx b/src/components/KeyboardShortcuts.jsx index d3854d35..8da13d27 100644 --- a/src/components/KeyboardShortcuts.jsx +++ b/src/components/KeyboardShortcuts.jsx @@ -540,6 +540,10 @@ const KeyboardShortcuts = memo(({ name: t('Show keyframes'), category: otherCategory, }, + toggleFullscreenVideo: { + name: 'Toggle full screen video', + category: otherCategory, + }, toggleSettings: { name: t('Settings'), category: otherCategory, diff --git a/src/main.css b/src/main.css index f71d01b1..056f4019 100644 --- a/src/main.css +++ b/src/main.css @@ -83,3 +83,8 @@ code.highlighted { .button-unstyled:focus { outline: revert; } + +/* https://stackoverflow.com/questions/18270894/html5-video-does-not-hide-controls-in-fullscreen-mode-in-chrome */ +video.main-player::-webkit-media-controls { + display:none !important; +} diff --git a/yarn.lock b/yarn.lock index d60f8d46..5b76412f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6622,6 +6622,7 @@ __metadata: react-sortablejs: "npm:^6.1.4" react-syntax-highlighter: "npm:^15.4.3" react-use: "npm:^17.4.0" + screenfull: "npm:^6.0.2" scroll-into-view-if-needed: "npm:^2.2.28" semver: "npm:^7.5.2" sharp: "npm:^0.32.6" @@ -8705,6 +8706,13 @@ __metadata: languageName: node linkType: hard +"screenfull@npm:^6.0.2": + version: 6.0.2 + resolution: "screenfull@npm:6.0.2" + checksum: 13ad07683538a94a0dac99533ddf54755c9c10890850f726872a9dace69ad09457451b2e467efcbb270f19f201bbb9b6911ab7b12618222648a093f369be6fd2 + languageName: node + linkType: hard + "scroll-into-view-if-needed@npm:^2.2.28": version: 2.2.28 resolution: "scroll-into-view-if-needed@npm:2.2.28"