Skip to content
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 70 additions & 3 deletions common/app/components/Player.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@
TokenizedSubtitleModel,
VideoTabModel,
} from '@project/common';
import { ApplyStrategy, AsbplayerSettings, SettingsProvider, TokenState } from '@project/common/settings';
import {
ApplyStrategy,
AsbplayerSettings,
SettingsProvider,
TokenState,
VideoSubtitleSplitBehavior,
} from '@project/common/settings';
import { DictionaryProvider } from '@project/common/dictionary-db';
import { SubtitleCollection } from '@project/common/subtitle-collection';
import { renderRichTextOntoSubtitles, HoveredToken, SubtitleColoring } from '@project/common/subtitle-coloring';
Expand All @@ -40,6 +46,7 @@
import { MiningContext } from '../services/mining-context';
import { SeekTimestampCommand, WebSocketClient } from '../../web-socket-client';
import { ensureStoragePersisted } from '../../util';
import { resolveVideoSubtitleSplitLayout } from './video-subtitle-split';

const minVideoPlayerWidth = 300;

Expand Down Expand Up @@ -199,6 +206,7 @@
const flattenSubtitleFiles = sources?.flattenSubtitleFiles;
const videoFile = sources?.videoFile;
const videoFileUrl = sources?.videoFileUrl;
const [videoAspectRatio, setVideoAspectRatio] = useState<number>();
const playModeEnabled = subtitles && subtitles.length > 0 && Boolean(videoFileUrl);
const [subtitlePlayerResizing, setSubtitlePlayerResizing] = useState<boolean>(false);
const [loadingSubtitles, setLoadingSubtitles] = useState<boolean>(false);
Expand Down Expand Up @@ -232,6 +240,29 @@
const classes = useStyles({ appBarHidden, appBarHeight });
const calculateLength = () => trackLength(channelRef.current, subtitlesRef.current);

useEffect(() => {
Comment thread
khajiitvaper2017 marked this conversation as resolved.
Outdated
setVideoAspectRatio(undefined);

if (!videoFileUrl) {
return;
}

const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = () => {
if (video.videoWidth > 0 && video.videoHeight > 0) {
setVideoAspectRatio(video.videoWidth / video.videoHeight);
}
};
video.src = videoFileUrl;

return () => {
video.onloadedmetadata = null;
video.removeAttribute('src');
video.load();
};
}, [videoFileUrl]);

useEffect(() => {
playModesRef.current = playModes;
}, [playModes]);
Expand All @@ -254,7 +285,21 @@
);

const handleSubtitlePlayerResizeStart = useCallback(() => setSubtitlePlayerResizing(true), []);
const handleSubtitlePlayerResizeEnd = useCallback(() => setSubtitlePlayerResizing(false), []);
const handleSubtitlePlayerResizeEnd = useCallback(
(width: number) => {
setSubtitlePlayerResizing(false);

if (
settings.videoSubtitleSplitBehavior !== VideoSubtitleSplitBehavior.rememberSplitPosition ||
settings.subtitlePlayerWidth === width
) {
return;
}

settingsProvider.set({ subtitlePlayerWidth: width }).catch(onError);
},
[onError, settings.subtitlePlayerWidth, settings.videoSubtitleSplitBehavior, settingsProvider]
);

const handleOnStartedShowingSubtitle = useCallback(() => {
if (
Expand Down Expand Up @@ -750,7 +795,7 @@
);
useEffect(
() => channel?.onPlay((forwardToMedia) => play(clock, mediaAdapter, forwardToMedia)),
[channel, mediaAdapter, clock]

Check warning on line 798 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useEffect has a missing dependency: 'play'. Either include it or remove the dependency array
);
useEffect(
() => channel?.onPause((forwardToMedia) => pause(clock, mediaAdapter, forwardToMedia)),
Expand Down Expand Up @@ -900,7 +945,7 @@
break;
}
});
}, [miningContext, settings, wasPlayingWhenMiningStarted, clock, mediaAdapter]);

Check warning on line 948 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useEffect has a missing dependency: 'play'. Either include it or remove the dependency array

useEffect(() => {
return miningContext.onEvent('started-mining', () => {
Expand Down Expand Up @@ -1002,7 +1047,7 @@
setLastJumpToTopTimestamp(Date.now());
}, [videoPopOut, channelId, videoFileUrl, videoFrameRef, videoChannelRef, origin]);

const handlePlay = useCallback(() => play(clock, mediaAdapter, true), [clock, mediaAdapter]);

Check warning on line 1050 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useCallback has a missing dependency: 'play'. Either include it or remove the dependency array
const handlePause = useCallback(() => pause(clock, mediaAdapter, true), [clock, mediaAdapter]);
const handleSeek = useCallback(
async (progress: number) => {
Expand Down Expand Up @@ -1034,7 +1079,7 @@
play(clock, mediaAdapter, true);
}
},
[clock, seek, mediaAdapter]

Check warning on line 1082 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useCallback has a missing dependency: 'play'. Either include it or remove the dependency array
);

const handleCopyFromSubtitlePlayer = useCallback(
Expand Down Expand Up @@ -1094,7 +1139,7 @@
play(clock, mediaAdapter, true);
}
},
[channel, clock, mediaAdapter, seek]

Check warning on line 1142 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useCallback has a missing dependency: 'play'. Either include it or remove the dependency array
);

const handleOffsetChange = useCallback(
Expand Down Expand Up @@ -1176,7 +1221,7 @@
);

return () => unbind();
}, [keyBinder, clock, mediaAdapter, disableKeyEvents]);

Check warning on line 1224 in common/app/components/Player.tsx

View workflow job for this annotation

GitHub Actions / build (22.x)

React Hook useEffect has a missing dependency: 'play'. Either include it or remove the dependency array

useEffect(() => {
return keyBinder.bindAdjustPlaybackRate(
Expand Down Expand Up @@ -1279,10 +1324,26 @@
};
}, [webSocketClient, extension, seek, clock]);

const [windowWidth] = useWindowSize(true);
const [windowWidth, windowHeight] = useWindowSize(true);

const loaded = videoFileUrl || subtitles;
const videoInWindow = Boolean(loaded && videoFileUrl && !videoPopOut);
const playerHeight = appBarHidden ? windowHeight : Math.max(0, windowHeight - appBarHeight);
const aspectFitVideoWidth =
videoAspectRatio && videoAspectRatio > 0
? Math.max(minVideoPlayerWidth, Math.round(playerHeight * videoAspectRatio))
: undefined;
const autoSubtitlePlayerInitialWidth =
videoInWindow && aspectFitVideoWidth !== undefined ? Math.max(0, windowWidth - aspectFitVideoWidth) : undefined;
const { initialWidth: subtitlePlayerInitialWidth, initialWidthKey: subtitlePlayerInitialWidthKey } =
resolveVideoSubtitleSplitLayout({
behavior: settings.videoSubtitleSplitBehavior,
persistedWidth: settings.subtitlePlayerWidth,
autoWidth: autoSubtitlePlayerInitialWidth,
videoFileUrl: videoInWindow ? videoFileUrl : undefined,
appBarHidden,
appBarHeight,
});
const subtitlePlayerMaxResizeWidth = Math.max(0, windowWidth - minVideoPlayerWidth);
const notEnoughSpaceForSubtitlePlayer = subtitlePlayerMaxResizeWidth < minSubtitlePlayerWidth;
const actuallyHideSubtitlePlayer =
Expand Down Expand Up @@ -1318,6 +1379,7 @@
hidden={actuallyHideSubtitlePlayer}
style={{
flexGrow: videoInWindow ? 0 : 1,
flexShrink: 0,
width: 'auto',
}}
>
Expand Down Expand Up @@ -1385,6 +1447,11 @@
settings={settings}
keyBinder={keyBinder}
webSocketClient={webSocketClient}
initialWidth={subtitlePlayerInitialWidth}
initialWidthKey={subtitlePlayerInitialWidthKey}
resetWidthOnOrientationChange={
settings.videoSubtitleSplitBehavior === VideoSubtitleSplitBehavior.autoMaximizeVideo
}
/>
</Grid>
</Grid>
Expand Down
40 changes: 38 additions & 2 deletions common/app/components/SubtitlePlayer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ import { MineSubtitleParams } from '../hooks/use-app-web-socket-client';
import { isMobile } from 'react-device-detect';
import ChromeExtension, { ExtensionMessage } from '../services/chrome-extension';
import { MineSubtitleCommand, WebSocketClient } from '../../web-socket-client';
import { clampSubtitlePlayerWidth } from './video-subtitle-split';
import './subtitles.css';

let lastKnownWidth: number | undefined;
Expand Down Expand Up @@ -362,7 +363,7 @@ interface SubtitlePlayerProps {
onMouseOver: (e: React.MouseEvent) => void;
onMouseOut: (e: React.MouseEvent) => void;
onResizeStart?: () => void;
onResizeEnd?: () => void;
onResizeEnd?: (width: number) => void;
autoPauseContext: AutoPauseContext;
subtitles?: DisplaySubtitleModel[];
subtitleCollection: SubtitleColoring | SubtitleCollection<DisplaySubtitleModel>;
Expand All @@ -383,6 +384,9 @@ interface SubtitlePlayerProps {
settings: AsbplayerSettings;
keyBinder: KeyBinder;
maxResizeWidth: number;
initialWidth?: number;
initialWidthKey?: string;
resetWidthOnOrientationChange?: boolean;
webSocketClient?: WebSocketClient;
}

Expand Down Expand Up @@ -418,6 +422,9 @@ export default function SubtitlePlayer({
settings,
keyBinder,
maxResizeWidth,
initialWidth,
initialWidthKey,
resetWidthOnOrientationChange = true,
webSocketClient,
}: SubtitlePlayerProps) {
const { t } = useTranslation();
Expand Down Expand Up @@ -1012,18 +1019,47 @@ export default function SubtitlePlayer({
onResizeEnd,
});

const appliedInitialWidthKeyRef = useRef<string | undefined>(undefined);
useEffect(() => {
if (!resizable || initialWidth === undefined || maxResizeWidth < minSubtitlePlayerWidth) {
return;
}

if (initialWidthKey && appliedInitialWidthKeyRef.current === initialWidthKey) {
return;
}

const clampedInitialWidth = clampSubtitlePlayerWidth(initialWidth, minSubtitlePlayerWidth, maxResizeWidth);
setWidth(clampedInitialWidth);
lastKnownWidth = clampedInitialWidth;
appliedInitialWidthKeyRef.current = initialWidthKey;
}, [resizable, initialWidth, initialWidthKey, maxResizeWidth, setWidth]);

// Scroll to selected subtitle when layout changes
useEffect(() => {
// Small delay to allow layout to settle
const timer = setTimeout(() => {
scrollToCurrentSubtitle();
Comment thread
killergerbah marked this conversation as resolved.
}, 50);
return () => clearTimeout(timer);
}, [width, scrollToCurrentSubtitle]);

useEffect(() => {
lastKnownWidth = width;
}, [width, maxResizeWidth]);

useEffect(() => {
if (!resetWidthOnOrientationChange) {
return;
}

const listener = () => {
lastKnownWidth = undefined;
setWidth(calculateInitialWidth());
};
screen.orientation.addEventListener('change', listener);
return () => screen.orientation.removeEventListener('change', listener);
}, [setWidth]);
}, [resetWidthOnOrientationChange, setWidth]);

const { dragging, draggingStartLocation, draggingCurrentLocation } = useDragging({ holdToDragMs: 750 });

Expand Down
55 changes: 55 additions & 0 deletions common/app/components/video-subtitle-split.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { VideoSubtitleSplitBehavior } from '@project/common/settings';
import { clampSubtitlePlayerWidth, resolveVideoSubtitleSplitLayout } from './video-subtitle-split';

it('uses the saved split width in remember mode', () => {
expect(
resolveVideoSubtitleSplitLayout({
behavior: VideoSubtitleSplitBehavior.rememberSplitPosition,
persistedWidth: 420,
autoWidth: 300,
videoFileUrl: 'blob:video',
appBarHidden: false,
appBarHeight: 64,
})
).toEqual({
initialWidth: 420,
initialWidthKey: 'remember:420',
});
});

it('falls back to the auto width in remember mode when nothing has been saved', () => {
expect(
resolveVideoSubtitleSplitLayout({
behavior: VideoSubtitleSplitBehavior.rememberSplitPosition,
persistedWidth: -1,
autoWidth: 300,
videoFileUrl: 'blob:video',
appBarHidden: false,
appBarHeight: 64,
})
).toEqual({
initialWidth: 300,
initialWidthKey: 'remember:-1',
});
});

it('ignores saved width in auto-maximize mode', () => {
expect(
resolveVideoSubtitleSplitLayout({
behavior: VideoSubtitleSplitBehavior.autoMaximizeVideo,
persistedWidth: 420,
autoWidth: 300,
videoFileUrl: 'blob:video',
appBarHidden: true,
appBarHeight: 0,
})
).toEqual({
initialWidth: 300,
initialWidthKey: 'auto:blob:video|true|0',
});
});

it('clamps split width to the current bounds', () => {
expect(clampSubtitlePlayerWidth(150, 200, 500)).toBe(200);
expect(clampSubtitlePlayerWidth(600, 200, 500)).toBe(500);
});
44 changes: 44 additions & 0 deletions common/app/components/video-subtitle-split.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { VideoSubtitleSplitBehavior } from '../../settings';

interface ResolveVideoSubtitleSplitLayoutArgs {
behavior: VideoSubtitleSplitBehavior;
persistedWidth: number;
autoWidth?: number;
videoFileUrl?: string;
appBarHidden: boolean;
appBarHeight: number;
}

interface VideoSubtitleSplitLayout {
initialWidth?: number;
initialWidthKey?: string;
}

export function clampSubtitlePlayerWidth(width: number, minWidth: number, maxWidth: number) {
return Math.min(maxWidth, Math.max(minWidth, width));
}

export function resolveVideoSubtitleSplitLayout({
behavior,
persistedWidth,
autoWidth,
videoFileUrl,
appBarHidden,
appBarHeight,
}: ResolveVideoSubtitleSplitLayoutArgs): VideoSubtitleSplitLayout {
if (!videoFileUrl) {
return {};
}

if (behavior === VideoSubtitleSplitBehavior.rememberSplitPosition) {
return {
initialWidth: persistedWidth > 0 ? persistedWidth : autoWidth,
initialWidthKey: `remember:${persistedWidth}`,
};
}

return {
initialWidth: autoWidth,
initialWidthKey: `auto:${videoFileUrl}|${appBarHidden}|${appBarHeight}`,
};
}
10 changes: 7 additions & 3 deletions common/app/hooks/use-resize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ interface Props {
minWidth: number;
maxWidth: number;
onResizeStart?: () => void;
onResizeEnd?: () => void;
onResizeEnd?: (width: number) => void;
}

// https://stackoverflow.com/questions/49469834/recommended-way-to-have-drawer-resizable
Expand All @@ -20,9 +20,13 @@ export const useResize = ({ initialWidth, minWidth, maxWidth, onResizeStart, onR
}, [setIsResizing, onResizeStart]);

const disableResize = useCallback(() => {
if (!isResizing) {
return;
}

setIsResizing(false);
onResizeEnd?.();
}, [setIsResizing, onResizeEnd]);
onResizeEnd?.(width);
}, [isResizing, setIsResizing, onResizeEnd, width]);

const recordLastMouseDownPosition = useCallback((e: MouseEvent) => {
setLastMouseDownClientX(e.clientX);
Expand Down
4 changes: 4 additions & 0 deletions common/app/services/video-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,8 @@ export default class VideoChannel {
miscSettings(settings: MiscSettings) {
const {
themeType,
videoSubtitleSplitBehavior,
subtitlePlayerWidth,
copyToClipboardOnMine,
autoPausePreference,
seekDuration,
Expand All @@ -658,6 +660,8 @@ export default class VideoChannel {
command: 'miscSettings',
value: {
themeType,
videoSubtitleSplitBehavior,
subtitlePlayerWidth,
copyToClipboardOnMine,
autoPausePreference,
seekDuration,
Expand Down
Loading
Loading