Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
84 changes: 84 additions & 0 deletions src/routes/Player/SubtitlesMenu/SubtitlesMenu.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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']}>
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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({
Expand All @@ -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({
Expand All @@ -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,
Expand Down
31 changes: 30 additions & 1 deletion src/routes/Player/SubtitlesMenu/styles.less
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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 {
Expand Down
5 changes: 5 additions & 0 deletions src/routes/Player/useSubtitles.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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[],
Expand All @@ -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,
Expand All @@ -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[],
Expand All @@ -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,
Expand Down
25 changes: 23 additions & 2 deletions src/routes/Player/useSubtitles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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)) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -95,6 +96,7 @@ const useSubtitles = ({
defaultTrackSelected.current = true;
video.setSubtitlesTrack(null);
video.setExtraSubtitlesTrack(null);
video.setSecondarySubtitlesTrack(null);
streamStateChanged({ subtitleTrack: null });
}, [streamStateChanged, video]);

Expand All @@ -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 });
Expand Down Expand Up @@ -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) {
Expand All @@ -318,6 +331,7 @@ const useSubtitles = ({

video.setSubtitlesTrack(null);
video.setExtraSubtitlesTrack(null);
video.setSecondarySubtitlesTrack(null);
return;
}

Expand All @@ -332,6 +346,7 @@ const useSubtitles = ({
player.streamState,
video.state.selectedExtraSubtitlesTrackId,
video.state.selectedSubtitlesTrackId,
video.state.selectedSecondarySubtitlesTrackId,
], !menusOpen);

onShortcut('subtitlesMenu', () => {
Expand All @@ -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,
Expand All @@ -355,6 +372,7 @@ const useSubtitles = ({
extraSubtitlesSize: video.state.extraSubtitlesSize,
onSubtitlesTrackSelected: selectEmbeddedTrack,
onExtraSubtitlesTrackSelected: selectExtraTrack,
onSecondarySubtitlesTrackSelected: selectSecondaryTrack,
onSubtitlesOffsetChanged: changeOffset,
onSubtitlesSizeChanged: changeSize,
onExtraSubtitlesOffsetChanged: changeOffset,
Expand All @@ -364,8 +382,10 @@ const useSubtitles = ({
changeDelay,
changeOffset,
changeSize,
platform.shell.active,
selectEmbeddedTrack,
selectExtraTrack,
selectSecondaryTrack,
settings.interfaceLanguage,
settings.subtitlesLanguage,
video.state.extraSubtitlesDelay,
Expand All @@ -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,
Expand Down
6 changes: 6 additions & 0 deletions src/routes/Player/useVideo.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ const useVideo = () => {
subtitlesOutlineColor: null,
extraSubtitlesTracks: [],
selectedExtraSubtitlesTrackId: null,
selectedSecondarySubtitlesTrackId: null,
extraSubtitlesSize: null,
extraSubtitlesDelay: null,
extraSubtitlesOffset: null,
Expand Down Expand Up @@ -130,6 +131,10 @@ const useVideo = () => {
setProp('selectedExtraSubtitlesTrackId', id);
};

const setSecondarySubtitlesTrack = (id) => {
setProp('selectedSecondarySubtitlesTrackId', id);
};

const setSubtitlesDelay = (delay) => {
setProp('extraSubtitlesDelay', delay);
};
Expand Down Expand Up @@ -248,6 +253,7 @@ const useVideo = () => {
setSubtitlesBackgroundColor,
setSubtitlesOutlineColor,
setExtraSubtitlesTrack,
setSecondarySubtitlesTrack,
setVideoScale,
setFullscreen,
};
Expand Down