From be5dadcc84fdfb086c5294dbaa8cc78e15908215 Mon Sep 17 00:00:00 2001 From: Mikael Finstad Date: Sun, 29 Sep 2024 11:52:56 +0200 Subject: [PATCH] allow selecting track when only one this allows the user to enable ffmpeg assisted playback when audio track is not supported closes #2144 --- src/renderer/src/App.tsx | 36 ++++++++++--------- .../src/components/PlaybackStreamSelector.tsx | 14 ++++---- src/renderer/src/util/streams.ts | 11 ++++-- 3 files changed, 36 insertions(+), 25 deletions(-) diff --git a/src/renderer/src/App.tsx b/src/renderer/src/App.tsx index 69dfa5dab5..3b4c73d455 100644 --- a/src/renderer/src/App.tsx +++ b/src/renderer/src/App.tsx @@ -1,5 +1,5 @@ import { memo, useEffect, useState, useCallback, useRef, useMemo, CSSProperties, ReactEventHandler, FocusEventHandler } from 'react'; -import { FaAngleLeft, FaWindowClose } from 'react-icons/fa'; +import { FaAngleLeft, FaRegTimesCircle } from 'react-icons/fa'; import { MdRotate90DegreesCcw } from 'react-icons/md'; import { AnimatePresence } from 'framer-motion'; import { ThemeProvider } from 'evergreen-ui'; @@ -60,7 +60,7 @@ import { RefuseOverwriteError, extractSubtitleTrackToSegments, mapRecommendedDefaultFormat, } from './ffmpeg'; -import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, getSubtitleStreams, getVideoTrackForStreamIndex, getAudioTrackForStreamIndex, enableVideoTrack, enableAudioTrack } from './util/streams'; +import { shouldCopyStreamByDefault, getAudioStreams, getRealVideoStreams, isAudioDefinitelyNotSupported, willPlayerProperlyHandleVideo, doesPlayerSupportHevcPlayback, getSubtitleStreams, enableVideoTrack, enableAudioTrack, canHtml5PlayerPlayStreams } from './util/streams'; import { exportEdlFile, readEdlFile, loadLlcProject, askForEdlImport } from './edlStore'; import { formatYouTube, getFrameCountRaw, formatTsv } from './edlFormats'; import { @@ -299,13 +299,17 @@ function App() { const zoomRel = useCallback((rel: number) => setZoom((z) => Math.min(Math.max(z + (rel * (1 + (z / 10))), 1), zoomMax)), []); const compatPlayerRequired = usingDummyVideo; - const compatPlayerWanted = (isRotationSet || activeVideoStreamIndex != null || activeAudioStreamIndex != null) && !hideMediaSourcePlayer; + const compatPlayerWanted = ( + isRotationSet + // if user selected a custom video/audio stream, use compat player if the html5 player does not have any track index corresponding to the selected stream indexes + || ((activeVideoStreamIndex != null || activeAudioStreamIndex != null) && videoRef.current != null && !canHtml5PlayerPlayStreams(videoRef.current, activeVideoStreamIndex, activeAudioStreamIndex)) + ) && !hideMediaSourcePlayer; const compatPlayerEnabled = (compatPlayerRequired || compatPlayerWanted) && (activeVideoStream != null || activeAudioStream != null); - const shouldShowPlaybackStreamSelector = videoStreams.length > 1 || audioStreams.length > 1 || (subtitleStreams.length > 0 && !compatPlayerEnabled); + const shouldShowPlaybackStreamSelector = videoStreams.length > 0 || audioStreams.length > 0 || (subtitleStreams.length > 0 && !compatPlayerEnabled); useEffect(() => { - // Reset the user preference when the state changes to true + // Reset the user preference when we go from not having compat player to having it if (compatPlayerEnabled) setHideMediaSourcePlayer(false); }, [compatPlayerEnabled]); @@ -505,17 +509,17 @@ function App() { } }, [subtitlesByStreamId, subtitleStreams, workingRef, setWorking, filePath, loadSubtitle]); - const onActiveVideoStreamChange = useCallback((index?: number) => { + const onActiveVideoStreamChange = useCallback((videoStreamIndex?: number) => { invariant(videoRef.current); - setHideMediaSourcePlayer(index == null || getVideoTrackForStreamIndex(videoRef.current, index) != null); - enableVideoTrack(videoRef.current, index); - setActiveVideoStreamIndex(index); + setHideMediaSourcePlayer(false); + enableVideoTrack(videoRef.current, videoStreamIndex); + setActiveVideoStreamIndex(videoStreamIndex); }, [videoRef]); - const onActiveAudioStreamChange = useCallback((index?: number) => { + const onActiveAudioStreamChange = useCallback((audioStreamIndex?: number) => { invariant(videoRef.current); - setHideMediaSourcePlayer(index == null || getAudioTrackForStreamIndex(videoRef.current, index) != null); - enableAudioTrack(videoRef.current, index); - setActiveAudioStreamIndex(index); + setHideMediaSourcePlayer(false); + enableAudioTrack(videoRef.current, audioStreamIndex); + setActiveAudioStreamIndex(audioStreamIndex); }, [videoRef]); const allFilesMeta = useMemo(() => ({ @@ -2421,7 +2425,9 @@ function App() { )} - {!compatPlayerRequired && setHideMediaSourcePlayer(true)} />} +
incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}
+ + {!compatPlayerRequired && setHideMediaSourcePlayer(true)} />} )} @@ -2433,8 +2439,6 @@ function App() { )} - {compatPlayerEnabled &&
incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}
} - {!showRightBar && ( setControlVisible(false), 7000); }, []); - const onChange = useCallback((e, fn) => { + const onChange = useCallback((e: ChangeEvent, fn: (a: number | undefined) => void) => { resetTimer(); const index = e.target.value ? parseInt(e.target.value, 10) : undefined; fn(index); e.target.blur(); }, [resetTimer]); - const onActiveSubtitleChange2 = useCallback((e) => onChange(e, onActiveSubtitleChange), [onActiveSubtitleChange, onChange]); - const onActiveVideoStreamChange2 = useCallback((e) => onChange(e, onActiveVideoStreamChange), [onActiveVideoStreamChange, onChange]); - const onActiveAudioStreamChange2 = useCallback((e) => onChange(e, onActiveAudioStreamChange), [onActiveAudioStreamChange, onChange]); + const onActiveSubtitleChange2 = useCallback>((e) => onChange(e, onActiveSubtitleChange), [onActiveSubtitleChange, onChange]); + const onActiveVideoStreamChange2 = useCallback>((e) => onChange(e, onActiveVideoStreamChange), [onActiveVideoStreamChange, onChange]); + const onActiveAudioStreamChange2 = useCallback>((e) => onChange(e, onActiveAudioStreamChange), [onActiveAudioStreamChange, onChange]); const onIconClick = useCallback(() => { resetTimer(); @@ -71,7 +71,7 @@ function PlaybackStreamSelector({ )} - {videoStreams.length > 1 && ( + {videoStreams.length > 0 && ( String(ffmpegTrackIndex + const getHtml5VideoTracks = (video: ChromiumHTMLVideoElement) => [...(video.videoTracks ?? [])]; const getHtml5AudioTracks = (audio: ChromiumHTMLAudioElement) => [...(audio.audioTracks ?? [])]; -export const getVideoTrackForStreamIndex = (video: ChromiumHTMLVideoElement, index) => getHtml5VideoTracks(video).find((videoTrack) => videoTrack.id === getHtml5TrackId(index)); -export const getAudioTrackForStreamIndex = (audio: ChromiumHTMLAudioElement, index) => getHtml5AudioTracks(audio).find((audioTrack) => audioTrack.id === getHtml5TrackId(index)); +const getVideoTrackForStreamIndex = (video: ChromiumHTMLVideoElement, index: number) => getHtml5VideoTracks(video).find((videoTrack) => videoTrack.id === getHtml5TrackId(index)); +const getAudioTrackForStreamIndex = (audio: ChromiumHTMLAudioElement, index: number) => getHtml5AudioTracks(audio).find((audioTrack) => audioTrack.id === getHtml5TrackId(index)); + +// although not technically correct, if video and audio index is null, assume that we can play +// the user can select an audio/video track if they want ffmpeg assisted playback +export const canHtml5PlayerPlayStreams = (videoEl: ChromiumHTMLVideoElement, videoIndex: number | undefined, audioIndex: number | undefined) => ( + (videoIndex == null || getVideoTrackForStreamIndex(videoEl, videoIndex) != null) + && (audioIndex == null || getAudioTrackForStreamIndex(videoEl, audioIndex) != null) +); function resetVideoTrack(video: ChromiumHTMLVideoElement) { console.log('Resetting video track');