From 5508da39ccb557c4a47f1d951f93eb4289ad5cdd Mon Sep 17 00:00:00 2001 From: Connor Smith Date: Thu, 20 Jun 2024 13:46:02 -0400 Subject: [PATCH] Add ability to remember audio and subtitle selection settings --- .../androidtv/preference/UserPreferences.kt | 10 +++ .../ui/playback/PlaybackController.java | 90 ++++++++++++++++--- .../PlaybackAdvancedPreferencesScreen.kt | 12 +++ app/src/main/res/values/strings.xml | 4 + 4 files changed, 103 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt index 7adc367b1b..8e326b2ba5 100644 --- a/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/preference/UserPreferences.kt @@ -60,6 +60,16 @@ class UserPreferences(context: Context) : SharedPreferenceStore( */ var mediaQueuingEnabled = booleanPreference("pref_enable_tv_queuing", true) + /** + * Set audio track to match previous video + */ + var rememberAudio = booleanPreference("pref_remember_audio", true) + + /** + * Set subtitle track to match previous video + */ + var rememberSubtitle = booleanPreference("pref_remember_subtitle", true) + /** * Enable the next up screen or not */ diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java index 67f7ff05c6..7f49acdc80 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java +++ b/app/src/main/java/org/jellyfin/androidtv/ui/playback/PlaybackController.java @@ -49,6 +49,7 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.util.List; +import java.util.Objects; import kotlin.Lazy; import timber.log.Timber; @@ -81,6 +82,7 @@ public class PlaybackController implements PlaybackControllerNotifiable { private Boolean spinnerOff = false; private VideoOptions mCurrentOptions; + private VideoOptions mPrevOptions; private int mDefaultSubIndex = -1; private int mDefaultAudioIndex = -1; private boolean burningSubs = false; @@ -107,6 +109,8 @@ public class PlaybackController implements PlaybackControllerNotifiable { private Display.Mode[] mDisplayModes; private RefreshRateSwitchingBehavior refreshRateSwitchingBehavior = RefreshRateSwitchingBehavior.DISABLED; + private Boolean rememberPreviousAudioTrack = false; + private Boolean rememberPreviousSubtitleTrack = false; public PlaybackController(List items, CustomPlaybackOverlayFragment fragment) { this(items, fragment, 0); @@ -115,13 +119,15 @@ public PlaybackController(List items, CustomPlaybackOverlayFragment public PlaybackController(List items, CustomPlaybackOverlayFragment fragment, int startIndex) { mItems = items; mCurrentIndex = 0; + mPrevOptions = new VideoOptions(); if (items != null && startIndex > 0 && startIndex < items.size()) { mCurrentIndex = startIndex; } mFragment = fragment; mHandler = new Handler(); - + rememberPreviousAudioTrack = userPreferences.getValue().get(UserPreferences.Companion.getRememberAudio()); + rememberPreviousSubtitleTrack = userPreferences.getValue().get(UserPreferences.Companion.getRememberSubtitle()); refreshRateSwitchingBehavior = userPreferences.getValue().get(UserPreferences.Companion.getRefreshRateSwitchingBehavior()); if (refreshRateSwitchingBehavior != RefreshRateSwitchingBehavior.DISABLED) getDisplayModes(); @@ -700,6 +706,49 @@ private Integer bestGuessAudioTrack(MediaSourceInfo info) { return null; } + private Integer bestGuessTrackFromPrevious(String trackType, StreamInfo info) { + if (info == null) + return null; + + Integer prevTrackIndex = Objects.equals(trackType, "audio") ? mPrevOptions.getAudioStreamIndex() : mPrevOptions.getSubtitleStreamIndex(); + List prevMediaSources = mPrevOptions.getMediaSources(); + if (prevTrackIndex != null && prevMediaSources != null) { + List prevMediaStreams = prevMediaSources.get(0).getMediaStreams(); + if (prevMediaStreams != null) { + MediaStream prevTrackStream = prevMediaStreams.get(prevTrackIndex); + if (prevTrackStream != null) { + List infoMediaStreams = info.getMediaSource().getMediaStreams(); + if (infoMediaStreams != null) { + MediaStream infoAudioStream = infoMediaStreams.get(prevTrackIndex); + if (infoAudioStream != null && Objects.equals(prevTrackStream.getDisplayTitle(), infoAudioStream.getDisplayTitle())) + return prevTrackIndex; + + Integer indexOfPrevTrackStreamInInfo = findIndexByDisplayNameAndLang(infoMediaStreams, prevTrackStream.getDisplayTitle(), prevTrackStream.getLanguage()); + if (indexOfPrevTrackStreamInInfo != null) + return indexOfPrevTrackStreamInInfo; + } + } + } + } + return info.getMediaSource().getDefaultAudioStreamIndex(); + } + + // Method to find index of object in array by display name + private Integer findIndexByDisplayNameAndLang(List mediaStreams, String displayTitle, String lang) { + int fallBackIndex = -1; + for (int i = 0; i < mediaStreams.size(); i++) { + String currentDisplayTitle = mediaStreams.get(i).getDisplayTitle(); + String currentLang = mediaStreams.get(i).getLanguage(); + if (currentDisplayTitle != null && currentLang != null && currentDisplayTitle.equals(displayTitle) && currentLang.equals(lang)) { + return i; // Return index if display name matches + } else if (currentLang != null && currentLang.equals(lang)) { + fallBackIndex = i; // Fallback will be track that matches language + } + } + + return fallBackIndex; // Return index if only language matches + } + private void setDefaultAudioIndex(StreamInfo info) { if (mDefaultAudioIndex != -1) return; @@ -719,12 +768,15 @@ public void switchAudioStream(int index) { return; int currAudioIndex = getAudioStreamIndex(); - Timber.d("trying to switch audio stream from %s to %s", currAudioIndex, index); - if (currAudioIndex == index) { - Timber.d("skipping setting audio stream, already set to requested index %s", index); - if (mCurrentOptions.getAudioStreamIndex() == null || mCurrentOptions.getAudioStreamIndex() != index) { - Timber.d("setting mCurrentOptions audio stream index from %s to %s", mCurrentOptions.getAudioStreamIndex(), index); - mCurrentOptions.setAudioStreamIndex(index); + Integer currOptionsAudioIndex = mCurrentOptions.getAudioStreamIndex(); + Integer prevOptionsAudioIndex = mPrevOptions.getAudioStreamIndex(); + int audioIndexToUse = rememberPreviousAudioTrack && prevOptionsAudioIndex != null ? prevOptionsAudioIndex : index; + Timber.d("trying to switch audio stream from %s to %s", currAudioIndex, audioIndexToUse); + if (currAudioIndex == audioIndexToUse || (prevOptionsAudioIndex != null && prevOptionsAudioIndex == audioIndexToUse)) { + Timber.d("skipping setting audio stream, already set to requested index %s", audioIndexToUse); + if ((currOptionsAudioIndex == null || currOptionsAudioIndex != audioIndexToUse) || (prevOptionsAudioIndex == null || prevOptionsAudioIndex != audioIndexToUse)) { + Timber.d("setting mCurrentOptions audio stream index from %s to %s", currOptionsAudioIndex, audioIndexToUse); + mCurrentOptions.setAudioStreamIndex(audioIndexToUse); } return; } @@ -734,11 +786,11 @@ public void switchAudioStream(int index) { if (!isTranscoding() && mVideoManager.setExoPlayerTrack(index, MediaStreamType.AUDIO, getCurrentlyPlayingItem().getMediaStreams())) { mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId()); - mCurrentOptions.setAudioStreamIndex(index); + mCurrentOptions.setAudioStreamIndex(audioIndexToUse); } else { startSpinner(); mCurrentOptions.setMediaSourceId(getCurrentMediaSource().getId()); - mCurrentOptions.setAudioStreamIndex(index); + mCurrentOptions.setAudioStreamIndex(audioIndexToUse); stop(); playInternal(getCurrentlyPlayingItem(), mCurrentPosition, mCurrentOptions); mPlaybackState = PlaybackState.BUFFERING; @@ -944,6 +996,9 @@ public void next() { if (mCurrentIndex < mItems.size() - 1) { stop(); resetPlayerErrors(); + mPrevOptions.setSubtitleStreamIndex(mCurrentOptions.getSubtitleStreamIndex()); + mPrevOptions.setAudioStreamIndex(mCurrentOptions.getAudioStreamIndex()); + mPrevOptions.setMediaSources(mCurrentOptions.getMediaSources()); mCurrentIndex++; videoQueueManager.getValue().setCurrentMediaPosition(mCurrentIndex); Timber.d("Moving to index: %d out of %d total items.", mCurrentIndex, mItems.size()); @@ -1219,8 +1274,13 @@ public void onPrepared() { } else { // select or disable subtitles Integer currentSubtitleIndex = mCurrentOptions.getSubtitleStreamIndex(); - if (mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex) { - Timber.i("subtitle stream %s is already selected", mDefaultSubIndex); + Integer previousSubtitleIndex = mPrevOptions.getSubtitleStreamIndex(); + if (rememberPreviousSubtitleTrack && previousSubtitleIndex != null) { + Timber.i("Enabling sub stream from previous: %d", previousSubtitleIndex); + switchSubtitleStream(previousSubtitleIndex); + } + else if ((mDefaultSubIndex >= 0 && currentSubtitleIndex != null && currentSubtitleIndex == mDefaultSubIndex)) { + Timber.i("subtitle stream %s is already selected", currentSubtitleIndex); } else { if (mDefaultSubIndex < 0) Timber.i("Turning off subs"); @@ -1230,12 +1290,16 @@ public void onPrepared() { } // select an audio track + Integer currentAudioIndex = mCurrentOptions.getAudioStreamIndex(); + Integer previousAudioIndex = mPrevOptions.getAudioStreamIndex(); int eligibleAudioTrack = mDefaultAudioIndex; // if track switching is done without rebuilding the stream, mCurrentOptions is updated // otherwise, use the server default - if (mCurrentOptions.getAudioStreamIndex() != null) { - eligibleAudioTrack = mCurrentOptions.getAudioStreamIndex(); + if (rememberPreviousAudioTrack && previousAudioIndex != null) + eligibleAudioTrack = previousAudioIndex; + else if (currentAudioIndex != null) { + eligibleAudioTrack = currentAudioIndex; } else if (getCurrentMediaSource().getDefaultAudioStreamIndex() != null) { eligibleAudioTrack = getCurrentMediaSource().getDefaultAudioStreamIndex(); } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt index be72b4b0df..5d691182c1 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/PlaybackAdvancedPreferencesScreen.kt @@ -43,6 +43,18 @@ class PlaybackAdvancedPreferencesScreen : OptionsFragment() { setTitle(R.string.lbl_tv_queuing) bind(userPreferences, UserPreferences.mediaQueuingEnabled) } + + checkbox { + setTitle(R.string.lbl_remember_audio) + setContent(R.string.desc_remember_audio) + bind(userPreferences, UserPreferences.rememberAudio) + } + + checkbox { + setTitle(R.string.lbl_remember_subtitle) + setContent(R.string.desc_remember_subtitle) + bind(userPreferences, UserPreferences.rememberSubtitle) + } } category { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 903e53fcca..e8a3415c93 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -94,6 +94,10 @@ No items Empty Play next episode automatically + Set audio tracked based on previous item + Try to set the audio track to the closest match to the last video + Set subtitle tracked based on previous item + Try to set the subtitle track to the closest match to the last video Search text (select for keyboard) Play first unwatched Mark unplayed