Skip to content
Open
Show file tree
Hide file tree
Changes from 14 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
57 changes: 54 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,27 @@
const classes = useStyles({ appBarHidden, appBarHeight });
const calculateLength = () => trackLength(channelRef.current, subtitlesRef.current);

useEffect(() => {
setVideoAspectRatio(undefined);

if (!videoFileUrl) {
return;
}

const video = document.createElement('video');
video.preload = 'metadata';
video.onloadedmetadata = () => {
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 +283,16 @@
);

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

if (settings.videoSubtitleSplitBehavior === VideoSubtitleSplitBehavior.rememberSplitPosition) {
playbackPreferences.subtitlePlayerWidth = width;
}
},
[playbackPreferences, settings.videoSubtitleSplitBehavior]
);

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

Check warning on line 791 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 +938,7 @@
break;
}
});
}, [miningContext, settings, wasPlayingWhenMiningStarted, clock, mediaAdapter]);

Check warning on line 941 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 +1040,7 @@
setLastJumpToTopTimestamp(Date.now());
}, [videoPopOut, channelId, videoFileUrl, videoFrameRef, videoChannelRef, origin]);

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

Check warning on line 1043 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 +1072,7 @@
play(clock, mediaAdapter, true);
}
},
[clock, seek, mediaAdapter]

Check warning on line 1075 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 +1132,7 @@
play(clock, mediaAdapter, true);
}
},
[channel, clock, mediaAdapter, seek]

Check warning on line 1135 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 +1214,7 @@
);

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

Check warning on line 1217 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 +1317,21 @@
};
}, [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
? Math.max(minVideoPlayerWidth, Math.round(playerHeight * videoAspectRatio))
: undefined;
const autoSubtitlePlayerInitialWidth =
videoInWindow && aspectFitVideoWidth !== undefined ? Math.max(0, windowWidth - aspectFitVideoWidth) : undefined;
const subtitlePlayerInitialWidth = resolveVideoSubtitleSplitLayout({
behavior: settings.videoSubtitleSplitBehavior,
persistedWidth: playbackPreferences.subtitlePlayerWidth,
autoWidth: autoSubtitlePlayerInitialWidth,
});
const subtitlePlayerMaxResizeWidth = Math.max(0, windowWidth - minVideoPlayerWidth);
const notEnoughSpaceForSubtitlePlayer = subtitlePlayerMaxResizeWidth < minSubtitlePlayerWidth;
const actuallyHideSubtitlePlayer =
Expand Down Expand Up @@ -1318,6 +1367,7 @@
hidden={actuallyHideSubtitlePlayer}
style={{
flexGrow: videoInWindow ? 0 : 1,
flexShrink: 0,
width: 'auto',
}}
>
Expand Down Expand Up @@ -1385,6 +1435,7 @@
settings={settings}
keyBinder={keyBinder}
webSocketClient={webSocketClient}
initialWidth={subtitlePlayerInitialWidth}
/>
</Grid>
</Grid>
Expand Down
40 changes: 30 additions & 10 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,7 @@ interface SubtitlePlayerProps {
settings: AsbplayerSettings;
keyBinder: KeyBinder;
maxResizeWidth: number;
initialWidth?: number;
webSocketClient?: WebSocketClient;
}

Expand Down Expand Up @@ -418,6 +420,7 @@ export default function SubtitlePlayer({
settings,
keyBinder,
maxResizeWidth,
initialWidth,
webSocketClient,
}: SubtitlePlayerProps) {
const { t } = useTranslation();
Expand Down Expand Up @@ -1012,18 +1015,35 @@ export default function SubtitlePlayer({
onResizeEnd,
});

const appliedInitialWidthRef = useRef<number | undefined>(undefined);
useEffect(() => {
lastKnownWidth = width;
}, [width, maxResizeWidth]);
if (!resizable || initialWidth === undefined || maxResizeWidth < minSubtitlePlayerWidth) {
return;
}

const clampedInitialWidth = clampSubtitlePlayerWidth(initialWidth, minSubtitlePlayerWidth, maxResizeWidth);

if (appliedInitialWidthRef.current === clampedInitialWidth) {
return;
}

appliedInitialWidthRef.current = clampedInitialWidth;
setWidth(clampedInitialWidth);
lastKnownWidth = clampedInitialWidth;
}, [resizable, initialWidth, maxResizeWidth, setWidth]);

// Scroll to selected subtitle when layout changes
useEffect(() => {
const listener = () => {
lastKnownWidth = undefined;
setWidth(calculateInitialWidth());
};
screen.orientation.addEventListener('change', listener);
return () => screen.orientation.removeEventListener('change', listener);
}, [setWidth]);
// Small delay to allow layout to settle
const timer = setTimeout(() => {
scrollToCurrentSubtitle();
}, 50);
return () => clearTimeout(timer);
}, [width, scrollToCurrentSubtitle]);

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

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

Expand Down
36 changes: 36 additions & 0 deletions common/app/components/video-subtitle-split.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
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,
})
).toBe(420);
});

it('leaves the width unchanged in remember mode when nothing has been saved', () => {
expect(
resolveVideoSubtitleSplitLayout({
behavior: VideoSubtitleSplitBehavior.rememberSplitPosition,
autoWidth: 300,
})
).toBeUndefined();
});

it('ignores saved width in auto-maximize mode', () => {
expect(
resolveVideoSubtitleSplitLayout({
behavior: VideoSubtitleSplitBehavior.autoMaximizeVideo,
persistedWidth: 420,
autoWidth: 300,
})
).toBe(300);
});

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

interface ResolveVideoSubtitleSplitLayoutArgs {
behavior: VideoSubtitleSplitBehavior;
persistedWidth?: number;
autoWidth?: number;
}

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

export function resolveVideoSubtitleSplitLayout({
behavior,
persistedWidth,
autoWidth,
}: ResolveVideoSubtitleSplitLayoutArgs) {
if (behavior === VideoSubtitleSplitBehavior.rememberSplitPosition) {
return persistedWidth;
}

return autoWidth;
}
17 changes: 13 additions & 4 deletions common/app/hooks/use-resize.ts
Original file line number Diff line number Diff line change
@@ -1,28 +1,37 @@
import { useCallback, useEffect, useState } from 'react';
import { useCallback, useEffect, useRef, useState } from 'react';

interface Props {
initialWidth: () => number;
minWidth: number;
maxWidth: number;
onResizeStart?: () => void;
onResizeEnd?: () => void;
onResizeEnd?: (width: number) => void;
}

// https://stackoverflow.com/questions/49469834/recommended-way-to-have-drawer-resizable
export const useResize = ({ initialWidth, minWidth, maxWidth, onResizeStart, onResizeEnd }: Props) => {
const [isResizing, setIsResizing] = useState(false);
const [width, setWidth] = useState(initialWidth);
const [lastMouseDownClientX, setLastMouseDownClientX] = useState<number>(0);
const widthRef = useRef(width);

useEffect(() => {
widthRef.current = width;
}, [width]);

const enableResize = useCallback(() => {
setIsResizing(true);
onResizeStart?.();
}, [setIsResizing, onResizeStart]);

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

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

const recordLastMouseDownPosition = useCallback((e: MouseEvent) => {
setLastMouseDownClientX(e.clientX);
Expand Down
15 changes: 15 additions & 0 deletions common/app/services/playback-preferences.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ const theaterModeKey = 'theaterMode';
const offsetKey = 'offset';
const displaySubtitlesKey = 'displaySubtitles';
const hideSubtitleListKey = 'hideSubtitleList';
const subtitlePlayerWidthKey = 'subtitlePlayerWidth';
const defaultVolume = 100;

interface PlaybackPrefSettings {
Expand Down Expand Up @@ -96,4 +97,18 @@ export default class PlaybackPreferences {
set displaySubtitles(displaySubtitles: boolean) {
this._storage.set(displaySubtitlesKey, String(displaySubtitles));
}

get subtitlePlayerWidth(): number | undefined {
const value = this._storage.get(subtitlePlayerWidthKey);

if (value === null) {
return undefined;
}

return Number(value);
}

set subtitlePlayerWidth(width: number) {
this._storage.set(subtitlePlayerWidthKey, String(width));
}
}
2 changes: 2 additions & 0 deletions common/app/services/video-channel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -632,6 +632,7 @@ export default class VideoChannel {
miscSettings(settings: MiscSettings) {
const {
themeType,
videoSubtitleSplitBehavior,
copyToClipboardOnMine,
autoPausePreference,
seekDuration,
Expand All @@ -658,6 +659,7 @@ export default class VideoChannel {
command: 'miscSettings',
value: {
themeType,
videoSubtitleSplitBehavior,
copyToClipboardOnMine,
autoPausePreference,
seekDuration,
Expand Down
Loading
Loading