1
0
mirror of https://github.com/mifi/lossless-cut.git synced 2024-11-21 18:02:35 +01:00

allow selecting track when only one

this allows the user to enable ffmpeg assisted playback when audio track is not supported
closes #2144
This commit is contained in:
Mikael Finstad 2024-09-29 11:52:56 +02:00
parent cc202b4845
commit be5dadcc84
No known key found for this signature in database
GPG Key ID: 25AB36E3E81CBC26
3 changed files with 36 additions and 25 deletions

View File

@ -1,5 +1,5 @@
import { memo, useEffect, useState, useCallback, useRef, useMemo, CSSProperties, ReactEventHandler, FocusEventHandler } from 'react'; 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 { MdRotate90DegreesCcw } from 'react-icons/md';
import { AnimatePresence } from 'framer-motion'; import { AnimatePresence } from 'framer-motion';
import { ThemeProvider } from 'evergreen-ui'; import { ThemeProvider } from 'evergreen-ui';
@ -60,7 +60,7 @@ import {
RefuseOverwriteError, extractSubtitleTrackToSegments, RefuseOverwriteError, extractSubtitleTrackToSegments,
mapRecommendedDefaultFormat, mapRecommendedDefaultFormat,
} from './ffmpeg'; } 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 { exportEdlFile, readEdlFile, loadLlcProject, askForEdlImport } from './edlStore';
import { formatYouTube, getFrameCountRaw, formatTsv } from './edlFormats'; import { formatYouTube, getFrameCountRaw, formatTsv } from './edlFormats';
import { 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 zoomRel = useCallback((rel: number) => setZoom((z) => Math.min(Math.max(z + (rel * (1 + (z / 10))), 1), zoomMax)), []);
const compatPlayerRequired = usingDummyVideo; 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 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(() => { 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); if (compatPlayerEnabled) setHideMediaSourcePlayer(false);
}, [compatPlayerEnabled]); }, [compatPlayerEnabled]);
@ -505,17 +509,17 @@ function App() {
} }
}, [subtitlesByStreamId, subtitleStreams, workingRef, setWorking, filePath, loadSubtitle]); }, [subtitlesByStreamId, subtitleStreams, workingRef, setWorking, filePath, loadSubtitle]);
const onActiveVideoStreamChange = useCallback((index?: number) => { const onActiveVideoStreamChange = useCallback((videoStreamIndex?: number) => {
invariant(videoRef.current); invariant(videoRef.current);
setHideMediaSourcePlayer(index == null || getVideoTrackForStreamIndex(videoRef.current, index) != null); setHideMediaSourcePlayer(false);
enableVideoTrack(videoRef.current, index); enableVideoTrack(videoRef.current, videoStreamIndex);
setActiveVideoStreamIndex(index); setActiveVideoStreamIndex(videoStreamIndex);
}, [videoRef]); }, [videoRef]);
const onActiveAudioStreamChange = useCallback((index?: number) => { const onActiveAudioStreamChange = useCallback((audioStreamIndex?: number) => {
invariant(videoRef.current); invariant(videoRef.current);
setHideMediaSourcePlayer(index == null || getAudioTrackForStreamIndex(videoRef.current, index) != null); setHideMediaSourcePlayer(false);
enableAudioTrack(videoRef.current, index); enableAudioTrack(videoRef.current, audioStreamIndex);
setActiveAudioStreamIndex(index); setActiveAudioStreamIndex(audioStreamIndex);
}, [videoRef]); }, [videoRef]);
const allFilesMeta = useMemo(() => ({ const allFilesMeta = useMemo(() => ({
@ -2421,7 +2425,9 @@ function App() {
</> </>
)} )}
{!compatPlayerRequired && <FaWindowClose role="button" style={{ cursor: 'pointer', pointerEvents: 'initial', verticalAlign: 'middle', padding: 10 }} onClick={() => setHideMediaSourcePlayer(true)} />} <div style={{ cursor: 'pointer', pointerEvents: 'initial', color: 'white', opacity: 0.7, padding: '.2em', marginLeft: '.5em' }} role="button" onClick={() => incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}</div>
{!compatPlayerRequired && <FaRegTimesCircle role="button" style={{ cursor: 'pointer', pointerEvents: 'initial', verticalAlign: 'middle', padding: '.2em' }} onClick={() => setHideMediaSourcePlayer(true)} />}
</div> </div>
)} )}
@ -2433,8 +2439,6 @@ function App() {
<PlaybackStreamSelector subtitleStreams={subtitleStreams} videoStreams={videoStreams} audioStreams={audioStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} activeVideoStreamIndex={activeVideoStreamIndex} activeAudioStreamIndex={activeAudioStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} onActiveVideoStreamChange={onActiveVideoStreamChange} onActiveAudioStreamChange={onActiveAudioStreamChange} /> <PlaybackStreamSelector subtitleStreams={subtitleStreams} videoStreams={videoStreams} audioStreams={audioStreams} activeSubtitleStreamIndex={activeSubtitleStreamIndex} activeVideoStreamIndex={activeVideoStreamIndex} activeAudioStreamIndex={activeAudioStreamIndex} onActiveSubtitleChange={onActiveSubtitleChange} onActiveVideoStreamChange={onActiveVideoStreamChange} onActiveAudioStreamChange={onActiveAudioStreamChange} />
)} )}
{compatPlayerEnabled && <div style={{ color: 'white', opacity: 0.7, padding: '.5em' }} role="button" onClick={() => incrementMediaSourceQuality()} title={t('Select playback quality')}>{mediaSourceQualities[mediaSourceQuality]}</div>}
{!showRightBar && ( {!showRightBar && (
<FaAngleLeft <FaAngleLeft
title={t('Show sidebar')} title={t('Show sidebar')}

View File

@ -1,4 +1,4 @@
import { memo, useState, useCallback, useRef, useEffect } from 'react'; import { memo, useState, useCallback, useRef, useEffect, ChangeEventHandler, ChangeEvent } from 'react';
import { MdSubtitles } from 'react-icons/md'; import { MdSubtitles } from 'react-icons/md';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import Select from './Select'; import Select from './Select';
@ -36,16 +36,16 @@ function PlaybackStreamSelector({
timeoutRef.current = window.setTimeout(() => setControlVisible(false), 7000); timeoutRef.current = window.setTimeout(() => setControlVisible(false), 7000);
}, []); }, []);
const onChange = useCallback((e, fn) => { const onChange = useCallback((e: ChangeEvent<HTMLSelectElement>, fn: (a: number | undefined) => void) => {
resetTimer(); resetTimer();
const index = e.target.value ? parseInt(e.target.value, 10) : undefined; const index = e.target.value ? parseInt(e.target.value, 10) : undefined;
fn(index); fn(index);
e.target.blur(); e.target.blur();
}, [resetTimer]); }, [resetTimer]);
const onActiveSubtitleChange2 = useCallback((e) => onChange(e, onActiveSubtitleChange), [onActiveSubtitleChange, onChange]); const onActiveSubtitleChange2 = useCallback<ChangeEventHandler<HTMLSelectElement>>((e) => onChange(e, onActiveSubtitleChange), [onActiveSubtitleChange, onChange]);
const onActiveVideoStreamChange2 = useCallback((e) => onChange(e, onActiveVideoStreamChange), [onActiveVideoStreamChange, onChange]); const onActiveVideoStreamChange2 = useCallback<ChangeEventHandler<HTMLSelectElement>>((e) => onChange(e, onActiveVideoStreamChange), [onActiveVideoStreamChange, onChange]);
const onActiveAudioStreamChange2 = useCallback((e) => onChange(e, onActiveAudioStreamChange), [onActiveAudioStreamChange, onChange]); const onActiveAudioStreamChange2 = useCallback<ChangeEventHandler<HTMLSelectElement>>((e) => onChange(e, onActiveAudioStreamChange), [onActiveAudioStreamChange, onChange]);
const onIconClick = useCallback(() => { const onIconClick = useCallback(() => {
resetTimer(); resetTimer();
@ -71,7 +71,7 @@ function PlaybackStreamSelector({
</Select> </Select>
)} )}
{videoStreams.length > 1 && ( {videoStreams.length > 0 && (
<Select <Select
value={activeVideoStreamIndex ?? ''} value={activeVideoStreamIndex ?? ''}
onChange={onActiveVideoStreamChange2} onChange={onActiveVideoStreamChange2}
@ -84,7 +84,7 @@ function PlaybackStreamSelector({
</Select> </Select>
)} )}
{audioStreams.length > 1 && ( {audioStreams.length > 0 && (
<Select <Select
value={activeAudioStreamIndex ?? ''} value={activeAudioStreamIndex ?? ''}
onChange={onActiveAudioStreamChange2} onChange={onActiveAudioStreamChange2}

View File

@ -263,8 +263,15 @@ const getHtml5TrackId = (ffmpegTrackIndex: number) => String(ffmpegTrackIndex +
const getHtml5VideoTracks = (video: ChromiumHTMLVideoElement) => [...(video.videoTracks ?? [])]; const getHtml5VideoTracks = (video: ChromiumHTMLVideoElement) => [...(video.videoTracks ?? [])];
const getHtml5AudioTracks = (audio: ChromiumHTMLAudioElement) => [...(audio.audioTracks ?? [])]; const getHtml5AudioTracks = (audio: ChromiumHTMLAudioElement) => [...(audio.audioTracks ?? [])];
export const getVideoTrackForStreamIndex = (video: ChromiumHTMLVideoElement, index) => getHtml5VideoTracks(video).find((videoTrack) => videoTrack.id === getHtml5TrackId(index)); const getVideoTrackForStreamIndex = (video: ChromiumHTMLVideoElement, index: number) => getHtml5VideoTracks(video).find((videoTrack) => videoTrack.id === getHtml5TrackId(index));
export const getAudioTrackForStreamIndex = (audio: ChromiumHTMLAudioElement, index) => getHtml5AudioTracks(audio).find((audioTrack) => audioTrack.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) { function resetVideoTrack(video: ChromiumHTMLVideoElement) {
console.log('Resetting video track'); console.log('Resetting video track');