Merge pull request #1115 from Stremio/feat/player-remember-selected-tracks
Some checks are pending
Build / build (push) Waiting to run

Player: Remember selected tracks
This commit is contained in:
Tim 2026-01-21 10:07:33 +01:00 committed by GitHub
commit cd111942a5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 79 additions and 19 deletions

View file

@ -29,6 +29,9 @@ const styles = require('./styles');
const Video = require('./Video'); const Video = require('./Video');
const { default: Indicator } = require('./Indicator/Indicator'); const { default: Indicator } = require('./Indicator/Indicator');
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
const findTrackById = (tracks, id) => tracks.find((track) => track.id === id);
const Player = ({ urlParams, queryParams }) => { const Player = ({ urlParams, queryParams }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const services = useServices(); const services = useServices();
@ -37,7 +40,7 @@ const Player = ({ urlParams, queryParams }) => {
return queryParams.has('forceTranscoding'); return queryParams.has('forceTranscoding');
}, [queryParams]); }, [queryParams]);
const profile = useProfile(); const profile = useProfile();
const [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams); const [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo] = usePlayer(urlParams);
const [settings, updateSettings] = useSettings(); const [settings, updateSettings] = useSettings();
const streamingServer = useStreamingServer(); const streamingServer = useStreamingServer();
const statistics = useStatistics(player, streamingServer); const statistics = useStatistics(player, streamingServer);
@ -224,15 +227,32 @@ const Player = ({ urlParams, queryParams }) => {
const onSubtitlesTrackSelected = React.useCallback((id) => { const onSubtitlesTrackSelected = React.useCallback((id) => {
video.setSubtitlesTrack(id); video.setSubtitlesTrack(id);
}, []); streamStateChanged({
subtitleTrack: {
id,
embedded: true,
},
});
}, [streamStateChanged]);
const onExtraSubtitlesTrackSelected = React.useCallback((id) => { const onExtraSubtitlesTrackSelected = React.useCallback((id) => {
video.setExtraSubtitlesTrack(id); video.setExtraSubtitlesTrack(id);
}, []); streamStateChanged({
subtitleTrack: {
id,
embedded: false,
},
});
}, [streamStateChanged]);
const onAudioTrackSelected = React.useCallback((id) => { const onAudioTrackSelected = React.useCallback((id) => {
video.setProp('selectedAudioTrackId', id); video.setProp('selectedAudioTrackId', id);
}, []); streamStateChanged({
audioTrack: {
id,
},
});
}, [streamStateChanged]);
const onExtraSubtitlesDelayChanged = React.useCallback((delay) => { const onExtraSubtitlesDelayChanged = React.useCallback((delay) => {
video.setProp('extraSubtitlesDelay', delay); video.setProp('extraSubtitlesDelay', delay);
@ -444,41 +464,49 @@ const Player = ({ urlParams, queryParams }) => {
} }
}, [player.nextVideo, video.state.time, video.state.duration]); }, [player.nextVideo, video.state.time, video.state.duration]);
// Auto subtitles track selection
React.useEffect(() => { React.useEffect(() => {
if (!defaultSubtitlesSelected.current) { if (!defaultSubtitlesSelected.current) {
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang);
if (settings.subtitlesLanguage === null) { if (settings.subtitlesLanguage === null) {
onSubtitlesTrackSelected(null); video.setSubtitlesTrack(null);
onExtraSubtitlesTrackSelected(null); video.setExtraSubtitlesTrack(null);
defaultSubtitlesSelected.current = true; defaultSubtitlesSelected.current = true;
return; return;
} }
const subtitlesTrack = findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage); const savedTrackId = player.streamState?.subtitleTrack?.id;
const extraSubtitlesTrack = findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage); const subtitlesTrack = savedTrackId ?
findTrackById(video.state.subtitlesTracks, savedTrackId) :
findTrackByLang(video.state.subtitlesTracks, settings.subtitlesLanguage);
const extraSubtitlesTrack = savedTrackId ?
findTrackById(video.state.extraSubtitlesTracks, savedTrackId) :
findTrackByLang(video.state.extraSubtitlesTracks, settings.subtitlesLanguage);
if (subtitlesTrack && subtitlesTrack.id) { if (subtitlesTrack && subtitlesTrack.id) {
onSubtitlesTrackSelected(subtitlesTrack.id); video.setSubtitlesTrack(subtitlesTrack.id);
defaultSubtitlesSelected.current = true; defaultSubtitlesSelected.current = true;
} else if (extraSubtitlesTrack && extraSubtitlesTrack.id) { } else if (extraSubtitlesTrack && extraSubtitlesTrack.id) {
onExtraSubtitlesTrackSelected(extraSubtitlesTrack.id); video.setExtraSubtitlesTrack(extraSubtitlesTrack.id);
defaultSubtitlesSelected.current = true; defaultSubtitlesSelected.current = true;
} }
} }
}, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks]); }, [video.state.subtitlesTracks, video.state.extraSubtitlesTracks, player.streamState]);
// Auto audio track selection
React.useEffect(() => { React.useEffect(() => {
if (!defaultAudioTrackSelected.current) { if (!defaultAudioTrackSelected.current) {
const findTrackByLang = (tracks, lang) => tracks.find((track) => track.lang === lang || langs.where('1', track.lang)?.[2] === lang); const savedTrackId = player.streamState?.audioTrack?.id;
const audioTrack = findTrackByLang(video.state.audioTracks, settings.audioLanguage); const audioTrack = savedTrackId ?
findTrackById(video.state.audioTracks, savedTrackId) :
findTrackByLang(video.state.audioTracks, settings.audioLanguage);
if (audioTrack && audioTrack.id) { if (audioTrack && audioTrack.id) {
onAudioTrackSelected(audioTrack.id); video.setProp('selectedAudioTrackId', audioTrack.id);
defaultAudioTrackSelected.current = true; defaultAudioTrackSelected.current = true;
} }
} }
}, [video.state.audioTracks]); }, [video.state.audioTracks, player.streamState]);
React.useEffect(() => { React.useEffect(() => {
defaultSubtitlesSelected.current = false; defaultSubtitlesSelected.current = false;

View file

@ -86,6 +86,9 @@ const usePlayer = (urlParams) => {
}; };
} }
}, [urlParams]); }, [urlParams]);
const player = useModelState({ model: 'player', action, map });
const videoParamsChanged = React.useCallback((videoParams) => { const videoParamsChanged = React.useCallback((videoParams) => {
core.transport.dispatch({ core.transport.dispatch({
action: 'Player', action: 'Player',
@ -153,8 +156,22 @@ const usePlayer = (urlParams) => {
}, 'player'); }, 'player');
}, []); }, []);
const player = useModelState({ model: 'player', action, map }); const streamStateChanged = React.useCallback((partialStreamState) => {
return [player, videoParamsChanged, timeChanged, seek, pausedChanged, ended, nextVideo]; return core.transport.dispatch({
action: 'Player',
args: {
action: 'StreamStateChanged',
args: {
state: {
...player.streamState,
...partialStreamState,
},
},
},
}, 'player');
}, [player.streamState]);
return [player, videoParamsChanged, streamStateChanged, timeChanged, seek, pausedChanged, ended, nextVideo];
}; };
module.exports = usePlayer; module.exports = usePlayer;

View file

@ -30,6 +30,20 @@ type SeriesInfo = {
season: number, season: number,
}; };
type SubtitlesTrackState = {
id: string,
embedded: boolean,
};
type AudioTrackState = {
id: string,
};
type StreamState = {
subtitleTrack?: SubtitlesTrackState,
audioTrack?: AudioTrackState,
};
type Player = { type Player = {
addon: Addon | null, addon: Addon | null,
libraryItem: LibraryItemPlayer | null, libraryItem: LibraryItemPlayer | null,
@ -42,6 +56,7 @@ type Player = {
subtitlesPath: ResourceRequestPath, subtitlesPath: ResourceRequestPath,
} | null, } | null,
seriesInfo: SeriesInfo | null, seriesInfo: SeriesInfo | null,
streamState: StreamState | null,
subtitles: Subtitle[], subtitles: Subtitle[],
title: string | null, title: string | null,
}; };