This commit is contained in:
lakshayrastogi 2026-05-21 21:51:00 +00:00 committed by GitHub
commit a1859f4525
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 158 additions and 3 deletions

View file

@ -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 {

View file

@ -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,

View file

@ -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 {

View file

@ -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,

View file

@ -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,

View file

@ -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,
};