mirror of
https://github.com/Stremio/stremio-web.git
synced 2026-05-24 12:32:30 +00:00
Merge 467b049ef3 into 87ccc591df
This commit is contained in:
commit
a1859f4525
6 changed files with 158 additions and 3 deletions
|
|
@ -48,6 +48,16 @@
|
|||
margin-left: 1rem;
|
||||
background-color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
.secondary-icon {
|
||||
flex: none;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 100%;
|
||||
margin-left: 1rem;
|
||||
border: 2px solid var(--secondary-accent-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.context-menu-option {
|
||||
|
|
|
|||
|
|
@ -75,6 +75,18 @@ const SubtitlesMenu = React.memo(React.forwardRef((props, ref) => {
|
|||
:
|
||||
null;
|
||||
}, [subtitlesTracks, extraSubtitlesTracks, props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId]);
|
||||
const selectedSecondarySubtitlesLanguage = React.useMemo(() => {
|
||||
return typeof props.selectedSecondarySubtitlesTrackId === 'string' ?
|
||||
allSubtitles
|
||||
.reduce((selected, { id, lang }) => {
|
||||
if (id === props.selectedSecondarySubtitlesTrackId) {
|
||||
return lang;
|
||||
}
|
||||
return selected;
|
||||
}, null)
|
||||
:
|
||||
null;
|
||||
}, [allSubtitles, props.selectedSecondarySubtitlesTrackId]);
|
||||
const subtitlesTracksForLanguage = React.useMemo(() => {
|
||||
const tracks = allSubtitles.filter(({ lang }) => lang === selectedSubtitlesLanguage);
|
||||
return sortByValues(tracks, ORIGIN_PRIORITIES);
|
||||
|
|
@ -153,6 +165,41 @@ const SubtitlesMenu = React.memo(React.forwardRef((props, ref) => {
|
|||
}
|
||||
}
|
||||
}, [props.selectedSubtitlesTrackId, props.selectedExtraSubtitlesTrackId, props.subtitlesOffset, props.extraSubtitlesOffset, props.onSubtitlesOffsetChanged, props.onExtraSubtitlesOffsetChanged]);
|
||||
|
||||
// Secondary subtitle support (desktop-only via ShellVideo/mpv)
|
||||
const hasPrimary = typeof props.selectedSubtitlesTrackId === 'string' ||
|
||||
typeof props.selectedExtraSubtitlesTrackId === 'string';
|
||||
const showSecondarySection = hasPrimary && props.isShellActive &&
|
||||
typeof props.onSecondarySubtitlesTrackSelected === 'function';
|
||||
|
||||
const secondaryLanguages = React.useMemo(() => {
|
||||
return subtitlesLanguages.filter((lang) => lang !== selectedSubtitlesLanguage);
|
||||
}, [subtitlesLanguages, selectedSubtitlesLanguage]);
|
||||
|
||||
const secondaryLanguageOnClick = React.useCallback((event) => {
|
||||
const lang = event.currentTarget.dataset.lang;
|
||||
// OFF button has no data-lang
|
||||
if (!lang) {
|
||||
if (typeof props.onSecondarySubtitlesTrackSelected === 'function') {
|
||||
props.onSecondarySubtitlesTrackSelected(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (lang === selectedSecondarySubtitlesLanguage) {
|
||||
// Deselect secondary if clicking the already-selected one
|
||||
if (typeof props.onSecondarySubtitlesTrackSelected === 'function') {
|
||||
props.onSecondarySubtitlesTrackSelected(null);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const tracks = allSubtitles.filter(({ lang: tLang }) => tLang === lang);
|
||||
const track = sortByValues(tracks, ORIGIN_PRIORITIES).shift();
|
||||
if (track && typeof props.onSecondarySubtitlesTrackSelected === 'function') {
|
||||
props.onSecondarySubtitlesTrackSelected(track);
|
||||
}
|
||||
}, [allSubtitles, selectedSecondarySubtitlesLanguage, props.onSecondarySubtitlesTrackSelected]);
|
||||
return (
|
||||
<div ref={ref} className={classnames(props.className, styles['subtitles-menu-container'])} onMouseDown={onMouseDown}>
|
||||
<div className={styles['languages-container']}>
|
||||
|
|
@ -183,6 +230,40 @@ const SubtitlesMenu = React.memo(React.forwardRef((props, ref) => {
|
|||
</Button>
|
||||
))}
|
||||
</div>
|
||||
{
|
||||
showSecondarySection && secondaryLanguages.length > 0 ?
|
||||
<React.Fragment>
|
||||
<div className={styles['secondary-header']}>{ t('PLAYER_SUBTITLES_SECONDARY', { defaultValue: 'Secondary Subtitle' }) }</div>
|
||||
<div className={styles['languages-list']}>
|
||||
<Button title={t('OFF')} className={classnames(styles['language-option'], { 'secondary-selected': selectedSecondarySubtitlesLanguage === null })} onClick={secondaryLanguageOnClick}>
|
||||
<div className={styles['language-label']}>{ t('OFF') }</div>
|
||||
{
|
||||
selectedSecondarySubtitlesLanguage === null ?
|
||||
<div className={styles['secondary-icon']} />
|
||||
:
|
||||
null
|
||||
}
|
||||
</Button>
|
||||
{secondaryLanguages.map((lang, index) => (
|
||||
<Button key={index} title={languages.label(lang)} className={classnames(styles['language-option'], { 'secondary-selected': selectedSecondarySubtitlesLanguage === lang })} data-lang={lang} onClick={secondaryLanguageOnClick}>
|
||||
<div className={styles['language-label']}>
|
||||
{
|
||||
lang === 'local' ? t('LOCAL') : languages.label(lang)
|
||||
}
|
||||
</div>
|
||||
{
|
||||
selectedSecondarySubtitlesLanguage === lang ?
|
||||
<div className={styles['secondary-icon']} />
|
||||
:
|
||||
null
|
||||
}
|
||||
</Button>
|
||||
))}
|
||||
</div>
|
||||
</React.Fragment>
|
||||
:
|
||||
null
|
||||
}
|
||||
</div>
|
||||
<div className={styles['variants-container']}>
|
||||
<div className={styles['variants-header']}>{ t('PLAYER_SUBTITLES_VARIANTS') }</div>
|
||||
|
|
@ -250,6 +331,7 @@ SubtitlesMenu.displayName = 'MainNavBars';
|
|||
|
||||
SubtitlesMenu.propTypes = {
|
||||
className: PropTypes.string,
|
||||
isShellActive: PropTypes.bool,
|
||||
subtitlesLanguage: PropTypes.string,
|
||||
interfaceLanguage: PropTypes.string,
|
||||
subtitlesTracks: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
|
@ -258,6 +340,7 @@ SubtitlesMenu.propTypes = {
|
|||
origin: PropTypes.string.isRequired
|
||||
})),
|
||||
selectedSubtitlesTrackId: PropTypes.string,
|
||||
selectedSecondarySubtitlesTrackId: PropTypes.string,
|
||||
subtitlesOffset: PropTypes.number,
|
||||
subtitlesSize: PropTypes.number,
|
||||
extraSubtitlesTracks: PropTypes.arrayOf(PropTypes.shape({
|
||||
|
|
@ -276,6 +359,7 @@ SubtitlesMenu.propTypes = {
|
|||
extraSubtitlesSize: PropTypes.number,
|
||||
onSubtitlesTrackSelected: PropTypes.func,
|
||||
onExtraSubtitlesTrackSelected: PropTypes.func,
|
||||
onSecondarySubtitlesTrackSelected: PropTypes.func,
|
||||
onSubtitlesOffsetChanged: PropTypes.func,
|
||||
onSubtitlesSizeChanged: PropTypes.func,
|
||||
onExtraSubtitlesOffsetChanged: PropTypes.func,
|
||||
|
|
|
|||
|
|
@ -36,10 +36,15 @@
|
|||
margin-bottom: 0.5rem;
|
||||
border-radius: var(--border-radius);
|
||||
|
||||
&:global(.selected), &:hover {
|
||||
&:global(.selected), &:global(.secondary-selected), &:hover {
|
||||
background-color: var(--overlay-color);
|
||||
}
|
||||
|
||||
&:global(.secondary-selected) {
|
||||
background-color: var(--overlay-color);
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.language-label {
|
||||
flex: 1;
|
||||
font-size: 1.1rem;
|
||||
|
|
@ -56,12 +61,36 @@
|
|||
margin-left: 1rem;
|
||||
background-color: var(--secondary-accent-color);
|
||||
}
|
||||
|
||||
.secondary-icon {
|
||||
flex: none;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
border-radius: 100%;
|
||||
margin-left: 1rem;
|
||||
border: 2px solid var(--secondary-accent-color);
|
||||
background-color: transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.languages-container {
|
||||
width: 16rem;
|
||||
|
||||
.secondary-header {
|
||||
flex: none;
|
||||
align-self: stretch;
|
||||
padding: 0.75rem 2rem 0.25rem;
|
||||
font-size: 0.9rem;
|
||||
font-weight: 700;
|
||||
color: var(--color-placeholder-text);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
border-top: 1px solid var(--overlay-color);
|
||||
margin-top: 0.5rem;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
.variants-container {
|
||||
|
|
|
|||
5
src/routes/Player/useSubtitles.d.ts
vendored
5
src/routes/Player/useSubtitles.d.ts
vendored
|
|
@ -22,6 +22,7 @@ type VideoSubtitleState = {
|
|||
stream: unknown | null,
|
||||
subtitlesTracks: SubtitleTrack[],
|
||||
selectedSubtitlesTrackId: string | null,
|
||||
selectedSecondarySubtitlesTrackId: string | null,
|
||||
subtitlesOffset: number | null,
|
||||
subtitlesSize: number | null,
|
||||
extraSubtitlesTracks: SubtitleTrack[],
|
||||
|
|
@ -43,6 +44,7 @@ type VideoController = {
|
|||
addLocalSubtitles: (filename: string, buffer: ArrayBuffer) => void,
|
||||
setSubtitlesTrack: (id: string | null) => void,
|
||||
setExtraSubtitlesTrack: (id: string | null) => void,
|
||||
setSecondarySubtitlesTrack: (id: string | null) => void,
|
||||
setSubtitlesDelay: (delay: number) => void,
|
||||
setSubtitlesSize: (size: number) => void,
|
||||
setSubtitlesOffset: (offset: number) => void,
|
||||
|
|
@ -63,10 +65,12 @@ type UseSubtitlesArgs = {
|
|||
};
|
||||
|
||||
type SubtitlesMenuProps = {
|
||||
isShellActive: boolean,
|
||||
subtitlesLanguage: string | null,
|
||||
interfaceLanguage: string,
|
||||
subtitlesTracks: SubtitleTrack[],
|
||||
selectedSubtitlesTrackId: string | null,
|
||||
selectedSecondarySubtitlesTrackId: string | null,
|
||||
subtitlesOffset: number | null,
|
||||
subtitlesSize: number | null,
|
||||
extraSubtitlesTracks: SubtitleTrack[],
|
||||
|
|
@ -76,6 +80,7 @@ type SubtitlesMenuProps = {
|
|||
extraSubtitlesSize: number | null,
|
||||
onSubtitlesTrackSelected: (track: SubtitleTrack | null) => void,
|
||||
onExtraSubtitlesTrackSelected: (track: SubtitleTrack | null) => void,
|
||||
onSecondarySubtitlesTrackSelected: (track: SubtitleTrack | null) => void,
|
||||
onSubtitlesOffsetChanged: (offset: number) => void,
|
||||
onSubtitlesSizeChanged: (size: number) => void,
|
||||
onExtraSubtitlesOffsetChanged: (offset: number) => void,
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
import { useCallback, useEffect, useMemo, useRef } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CONSTANTS, languages, onFileDrop, onShortcut, useToast } from 'stremio/common';
|
||||
import { CONSTANTS, languages, onFileDrop, onShortcut, usePlatform, useToast } from 'stremio/common';
|
||||
|
||||
const withFallbackLabels = (tracks?: SubtitleTrack[] | null): SubtitleTrack[] => {
|
||||
if (!Array.isArray(tracks)) {
|
||||
|
|
@ -47,6 +47,7 @@ const useSubtitles = ({
|
|||
}: UseSubtitlesArgs): UseSubtitlesResult => {
|
||||
const { t } = useTranslation();
|
||||
const toast = useToast();
|
||||
const platform = usePlatform();
|
||||
const videoRef = useRef(video);
|
||||
const settingsRef = useRef(settings);
|
||||
const defaultTrackSelected = useRef(false);
|
||||
|
|
@ -95,6 +96,7 @@ const useSubtitles = ({
|
|||
defaultTrackSelected.current = true;
|
||||
video.setSubtitlesTrack(null);
|
||||
video.setExtraSubtitlesTrack(null);
|
||||
video.setSecondarySubtitlesTrack(null);
|
||||
streamStateChanged({ subtitleTrack: null });
|
||||
}, [streamStateChanged, video]);
|
||||
|
||||
|
|
@ -120,6 +122,16 @@ const useSubtitles = ({
|
|||
rememberTrack(track, false);
|
||||
}, [disableSubtitles, rememberTrack, video]);
|
||||
|
||||
const selectSecondaryTrack = useCallback((track: SubtitleTrack | null) => {
|
||||
if (!track) {
|
||||
video.setSecondarySubtitlesTrack(null);
|
||||
return;
|
||||
}
|
||||
|
||||
defaultTrackSelected.current = true;
|
||||
video.setSecondarySubtitlesTrack(track.id);
|
||||
}, [video]);
|
||||
|
||||
const changeDelay = useCallback((delay: number) => {
|
||||
video.setSubtitlesDelay(delay);
|
||||
streamStateChanged({ subtitleDelay: delay });
|
||||
|
|
@ -301,7 +313,8 @@ const useSubtitles = ({
|
|||
|
||||
onShortcut('toggleSubtitles', () => {
|
||||
const subtitlesEnabled = video.state.selectedSubtitlesTrackId !== null ||
|
||||
video.state.selectedExtraSubtitlesTrackId !== null;
|
||||
video.state.selectedExtraSubtitlesTrackId !== null ||
|
||||
video.state.selectedSecondarySubtitlesTrackId !== null;
|
||||
|
||||
if (subtitlesEnabled) {
|
||||
if (video.state.selectedSubtitlesTrackId) {
|
||||
|
|
@ -318,6 +331,7 @@ const useSubtitles = ({
|
|||
|
||||
video.setSubtitlesTrack(null);
|
||||
video.setExtraSubtitlesTrack(null);
|
||||
video.setSecondarySubtitlesTrack(null);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -332,6 +346,7 @@ const useSubtitles = ({
|
|||
player.streamState,
|
||||
video.state.selectedExtraSubtitlesTrackId,
|
||||
video.state.selectedSubtitlesTrackId,
|
||||
video.state.selectedSecondarySubtitlesTrackId,
|
||||
], !menusOpen);
|
||||
|
||||
onShortcut('subtitlesMenu', () => {
|
||||
|
|
@ -342,10 +357,12 @@ const useSubtitles = ({
|
|||
}, [closeMenus, hasTracks, toggleSubtitlesMenu]);
|
||||
|
||||
const menuProps = useMemo(() => ({
|
||||
isShellActive: platform.shell.active,
|
||||
subtitlesLanguage: settings.subtitlesLanguage,
|
||||
interfaceLanguage: settings.interfaceLanguage,
|
||||
subtitlesTracks: video.state.subtitlesTracks,
|
||||
selectedSubtitlesTrackId: video.state.selectedSubtitlesTrackId,
|
||||
selectedSecondarySubtitlesTrackId: video.state.selectedSecondarySubtitlesTrackId,
|
||||
subtitlesOffset: video.state.subtitlesOffset,
|
||||
subtitlesSize: video.state.subtitlesSize,
|
||||
extraSubtitlesTracks: video.state.extraSubtitlesTracks,
|
||||
|
|
@ -355,6 +372,7 @@ const useSubtitles = ({
|
|||
extraSubtitlesSize: video.state.extraSubtitlesSize,
|
||||
onSubtitlesTrackSelected: selectEmbeddedTrack,
|
||||
onExtraSubtitlesTrackSelected: selectExtraTrack,
|
||||
onSecondarySubtitlesTrackSelected: selectSecondaryTrack,
|
||||
onSubtitlesOffsetChanged: changeOffset,
|
||||
onSubtitlesSizeChanged: changeSize,
|
||||
onExtraSubtitlesOffsetChanged: changeOffset,
|
||||
|
|
@ -364,8 +382,10 @@ const useSubtitles = ({
|
|||
changeDelay,
|
||||
changeOffset,
|
||||
changeSize,
|
||||
platform.shell.active,
|
||||
selectEmbeddedTrack,
|
||||
selectExtraTrack,
|
||||
selectSecondaryTrack,
|
||||
settings.interfaceLanguage,
|
||||
settings.subtitlesLanguage,
|
||||
video.state.extraSubtitlesDelay,
|
||||
|
|
@ -374,6 +394,7 @@ const useSubtitles = ({
|
|||
video.state.extraSubtitlesTracks,
|
||||
video.state.selectedExtraSubtitlesTrackId,
|
||||
video.state.selectedSubtitlesTrackId,
|
||||
video.state.selectedSecondarySubtitlesTrackId,
|
||||
video.state.subtitlesOffset,
|
||||
video.state.subtitlesSize,
|
||||
video.state.subtitlesTracks,
|
||||
|
|
|
|||
|
|
@ -34,6 +34,7 @@ const useVideo = () => {
|
|||
subtitlesOutlineColor: null,
|
||||
extraSubtitlesTracks: [],
|
||||
selectedExtraSubtitlesTrackId: null,
|
||||
selectedSecondarySubtitlesTrackId: null,
|
||||
extraSubtitlesSize: null,
|
||||
extraSubtitlesDelay: null,
|
||||
extraSubtitlesOffset: null,
|
||||
|
|
@ -130,6 +131,10 @@ const useVideo = () => {
|
|||
setProp('selectedExtraSubtitlesTrackId', id);
|
||||
};
|
||||
|
||||
const setSecondarySubtitlesTrack = (id) => {
|
||||
setProp('selectedSecondarySubtitlesTrackId', id);
|
||||
};
|
||||
|
||||
const setSubtitlesDelay = (delay) => {
|
||||
setProp('extraSubtitlesDelay', delay);
|
||||
};
|
||||
|
|
@ -248,6 +253,7 @@ const useVideo = () => {
|
|||
setSubtitlesBackgroundColor,
|
||||
setSubtitlesOutlineColor,
|
||||
setExtraSubtitlesTrack,
|
||||
setSecondarySubtitlesTrack,
|
||||
setVideoScale,
|
||||
setFullscreen,
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue