From d8e7e14da0f742a54e402642723707f0e91165d3 Mon Sep 17 00:00:00 2001 From: Ian Walton Date: Sat, 24 Apr 2021 19:09:07 -0400 Subject: [PATCH 1/4] Add subtitle/audio auto-set feature. --- src/components/playback/playbackmanager.js | 98 +++++++++++++++++-- .../playbackSettings/playbackSettings.js | 2 + .../playbackSettings.template.html | 8 ++ src/scripts/settings/userSettings.js | 14 +++ src/strings/en-us.json | 2 + 5 files changed, 118 insertions(+), 6 deletions(-) diff --git a/src/components/playback/playbackmanager.js b/src/components/playback/playbackmanager.js index 6defed364a..90d56e3d0b 100644 --- a/src/components/playback/playbackmanager.js +++ b/src/components/playback/playbackmanager.js @@ -2106,7 +2106,7 @@ class PlaybackManager { } } - function playInternal(item, playOptions, onPlaybackStartedFn) { + function playInternal(item, playOptions, onPlaybackStartedFn, prevSource) { if (item.IsPlaceHolder) { loading.hide(); showPlaybackInfoErrorMessage(self, 'PlaybackErrorPlaceHolder'); @@ -2131,7 +2131,7 @@ class PlaybackManager { const mediaType = item.MediaType; const onBitrateDetectionFailure = function () { - return playAfterBitrateDetect(getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn); + return playAfterBitrateDetect(getSavedMaxStreamingBitrate(ServerConnections.getApiClient(item.ServerId), mediaType), item, playOptions, onPlaybackStartedFn, prevSource); }; if (!isServerItem(item) || itemHelper.isLocalItem(item)) { @@ -2144,7 +2144,7 @@ class PlaybackManager { return apiClient.detectBitrate().then(function (bitrate) { appSettings.maxStreamingBitrate(endpointInfo.IsInNetwork, mediaType, bitrate); - return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn); + return playAfterBitrateDetect(bitrate, item, playOptions, onPlaybackStartedFn, prevSource); }, onBitrateDetectionFailure); } else { onBitrateDetectionFailure(); @@ -2222,7 +2222,80 @@ class PlaybackManager { }); } - function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn) { + function rankStreamType(prevIndex, prevSource, mediaSource, streamType) { + if (prevIndex == -1) { + console.debug(`AutoSet ${streamType} - No Stream Set`); + if (streamType == 'Subtitle') + mediaSource.DefaultSubtitleStreamIndex = -1; + return; + } + + let bestStreamIndex = null; + let bestStreamScore = 0; + const prevStream = prevSource.MediaStreams[prevIndex]; + + console.debug(`AutoSet ${streamType} - Previous was ${prevStream.Index} - ${prevStream.DisplayTitle}`); + + let prevRelIndex = 0; + for (const stream of prevSource.MediaStreams) { + if (stream.Type != streamType) + continue; + + if (stream.Index == prevIndex) + break; + + prevRelIndex += 1; + } + + let newRelIndex = 0; + for (const stream of mediaSource.MediaStreams) { + if (stream.Type != streamType) + continue; + + let score = 0; + + if (prevStream.Codec == stream.Codec) + score += 1; + if (prevRelIndex == newRelIndex) + score += 1; + if (prevStream.Title && prevStream.Title == stream.Title) + score += 2; + if (prevStream.Language && prevStream.Language != 'und' && prevStream.Language == stream.Language) + score += 2; + + console.debug(`AutoSet ${streamType} - Score ${score} for ${stream.Index} - ${stream.DisplayTitle}`); + if (score > bestStreamScore && score >= 3) { + bestStreamScore = score; + bestStreamIndex = stream.Index; + } + + newRelIndex += 1; + } + + if (bestStreamIndex != null) { + console.debug(`AutoSet ${streamType} - Using ${bestStreamIndex} score ${bestStreamScore}.`); + if (streamType == 'Subtitle') + mediaSource.DefaultSubtitleStreamIndex = bestStreamIndex; + if (streamType == 'Audio') + mediaSource.DefaultAudioStreamIndex = bestStreamIndex; + } else { + console.debug(`AutoSet ${streamType} - Threshold not met. Using default.`); + } + } + + function autoSetNextTracks(prevSource, mediaSource) { + if (!prevSource + || typeof prevSource.DefaultAudioStreamIndex != 'number' + || typeof mediaSource.DefaultAudioStreamIndex != 'number' + || typeof prevSource.DefaultSubtitleStreamIndex != 'number' + || typeof mediaSource.DefaultSubtitleStreamIndex != 'number') + return; + + rankStreamType(prevSource.DefaultAudioStreamIndex, prevSource, mediaSource, 'Audio'); + rankStreamType(prevSource.DefaultSubtitleStreamIndex, prevSource, mediaSource, 'Subtitle'); + } + + function playAfterBitrateDetect(maxBitrate, item, playOptions, onPlaybackStartedFn, prevSource) { const startPosition = playOptions.startPositionTicks; const player = getPlayer(item, playOptions); @@ -2272,6 +2345,9 @@ class PlaybackManager { playOptions.items = null; return getPlaybackMediaSource(player, apiClient, deviceProfile, maxBitrate, item, startPosition, mediaSourceId, audioStreamIndex, subtitleStreamIndex).then(function (mediaSource) { + if (userSettings.enableSetUsingLastTracks()) + autoSetNextTracks(prevSource, mediaSource); + const streamInfo = createStreamInfo(apiClient, item.MediaType, item, mediaSource, startPosition); streamInfo.fullscreen = playOptions.fullscreen; @@ -2622,6 +2698,16 @@ class PlaybackManager { return self.previousTrack(player); }; + function getPreviousSource(player) { + const prevSource = self.currentMediaSource(player); + const prevPlayerData = getPlayerData(player); + return { + ...prevSource, + DefaultAudioStreamIndex: prevPlayerData.audioStreamIndex, + DefaultSubtitleStreamIndex: prevPlayerData.subtitleStreamIndex + }; + } + self.nextTrack = function (player) { player = player || self._currentPlayer; if (player && !enableLocalPlaylistManagement(player)) { @@ -2637,7 +2723,7 @@ class PlaybackManager { playInternal(newItemInfo.item, newItemPlayOptions, function () { setPlaylistState(newItemInfo.item.PlaylistItemId, newItemInfo.index); - }); + }, getPreviousSource(player)); } }; @@ -2658,7 +2744,7 @@ class PlaybackManager { playInternal(newItem, newItemPlayOptions, function () { setPlaylistState(newItem.PlaylistItemId, newIndex); - }); + }, getPreviousSource(player)); } } }; diff --git a/src/components/playbackSettings/playbackSettings.js b/src/components/playbackSettings/playbackSettings.js index 0e52ae5a35..e0571b63d6 100644 --- a/src/components/playbackSettings/playbackSettings.js +++ b/src/components/playbackSettings/playbackSettings.js @@ -195,6 +195,7 @@ import template from './playbackSettings.template.html'; context.querySelector('.chkPreferFmp4HlsContainer').checked = userSettings.preferFmp4HlsContainer(); context.querySelector('.chkEnableCinemaMode').checked = userSettings.enableCinemaMode(); context.querySelector('.chkEnableNextVideoOverlay').checked = userSettings.enableNextVideoInfoOverlay(); + context.querySelector('.chkSetUsingLastTracks').checked = userSettings.enableSetUsingLastTracks(); context.querySelector('.chkExternalVideoPlayer').checked = appSettings.enableSystemExternalPlayers(); setMaxBitrateIntoField(context.querySelector('.selectVideoInNetworkQuality'), true, 'Video'); @@ -236,6 +237,7 @@ import template from './playbackSettings.template.html'; userSettingsInstance.enableCinemaMode(context.querySelector('.chkEnableCinemaMode').checked); userSettingsInstance.enableNextVideoInfoOverlay(context.querySelector('.chkEnableNextVideoOverlay').checked); + userSettingsInstance.enableSetUsingLastTracks(context.querySelector('.chkSetUsingLastTracks').checked); userSettingsInstance.chromecastVersion(context.querySelector('.selectChromecastVersion').value); userSettingsInstance.skipForwardLength(context.querySelector('.selectSkipForwardLength').value); userSettingsInstance.skipBackLength(context.querySelector('.selectSkipBackLength').value); diff --git a/src/components/playbackSettings/playbackSettings.template.html b/src/components/playbackSettings/playbackSettings.template.html index ae6429ed95..8792cd77b9 100644 --- a/src/components/playbackSettings/playbackSettings.template.html +++ b/src/components/playbackSettings/playbackSettings.template.html @@ -82,6 +82,14 @@ +
+ +
${SetUsingLastTracksHelp}
+
+