aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShadowghost <Shadowghost@users.noreply.github.com>2026-09-15 11:16:19 -0400
committerCody Robibero <cody@robibe.ro>2026-09-15 11:16:19 -0400
commitd8aa558655f3b0e8c3edd77528b6be65b73b39ed (patch)
tree9084241e0385d077e6f0bab8979b41b022809aaa
parent7d5d4487421f0351f95069f6cd5fc6bd986f780a (diff)
Backport pull request #17998 from jellyfin/release-12.z
Fix forced-only not overriding remembered subtitles Original-merge: 95d7028f3afdd4419c664e917f43bee685b0ab7e Merged-by: crobibero <cody@robibe.ro> Backported-by: Cody Robibero <cody@robibe.ro>
-rw-r--r--Emby.Server.Implementations/Library/MediaSourceManager.cs7
-rw-r--r--tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs69
2 files changed, 75 insertions, 1 deletions
diff --git a/Emby.Server.Implementations/Library/MediaSourceManager.cs b/Emby.Server.Implementations/Library/MediaSourceManager.cs
index 97e00177b6..8e428452f3 100644
--- a/Emby.Server.Implementations/Library/MediaSourceManager.cs
+++ b/Emby.Server.Implementations/Library/MediaSourceManager.cs
@@ -494,7 +494,12 @@ namespace Emby.Server.Implementations.Library
{
var index = userData.SubtitleStreamIndex.Value;
// Make sure the saved index is still valid
- if (index == -1 || source.MediaStreams.Any(i => i.Type == MediaStreamType.Subtitle && i.Index == index))
+ var savedStream = source.MediaStreams.FirstOrDefault(i => i.Type == MediaStreamType.Subtitle && i.Index == index);
+ // "Only forced" rules out full tracks entirely, so a remembered one must not resurrect them.
+ // The client reports whatever is playing, so an index remembered under another mode sticks forever otherwise.
+ if (index == -1
+ || (savedStream is not null
+ && (user.SubtitleMode != SubtitlePlaybackMode.OnlyForced || savedStream.IsForced)))
{
source.DefaultSubtitleStreamIndex = index;
return;
diff --git a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs
index c80f899498..685b769f34 100644
--- a/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs
+++ b/tests/Jellyfin.Server.Implementations.Tests/Library/MediaSourceManagerTests.cs
@@ -7,7 +7,9 @@ using Castle.Components.DictionaryAdapter;
using Emby.Server.Implementations.IO;
using Emby.Server.Implementations.Library;
using Jellyfin.Database.Implementations.Entities;
+using Jellyfin.Database.Implementations.Enums;
using MediaBrowser.Controller.Entities;
+using MediaBrowser.Controller.Entities.Audio;
using MediaBrowser.Controller.Library;
using MediaBrowser.Controller.LiveTv;
using MediaBrowser.Controller.MediaSegments;
@@ -149,6 +151,73 @@ namespace Jellyfin.Server.Implementations.Tests.Library
Assert.Equal(expectedIndex, mediaInfo.DefaultAudioStreamIndex);
}
+ [Theory]
+ // A remembered full track must not survive a switch to "only forced" (it falls through to
+ // the forced track here); a remembered forced track and "off" still must.
+ [InlineData(SubtitlePlaybackMode.OnlyForced, 2, 3)]
+ [InlineData(SubtitlePlaybackMode.OnlyForced, 3, 3)]
+ [InlineData(SubtitlePlaybackMode.OnlyForced, -1, -1)]
+ [InlineData(SubtitlePlaybackMode.Default, 2, 2)]
+ [InlineData(SubtitlePlaybackMode.Always, 2, 2)]
+ [InlineData(SubtitlePlaybackMode.Smart, 2, 2)]
+ [InlineData(SubtitlePlaybackMode.None, 2, null)]
+ public void SetDefaultSubtitleStreamIndex_RememberedSelection_RespectsSubtitleMode(
+ SubtitlePlaybackMode mode,
+ int rememberedIndex,
+ int? expectedIndex)
+ {
+ _mockUserDataManager
+ .Setup(m => m.GetUserData(It.IsAny<User>(), It.IsAny<BaseItem>()))
+ .Returns(new UserItemData { Key = "key", SubtitleStreamIndex = rememberedIndex });
+
+ var mediaInfo = new MediaSourceInfo
+ {
+ MediaStreams = new MediaStream[]
+ {
+ new() { Index = 0, Type = MediaStreamType.Video, IsDefault = true },
+ new() { Index = 1, Type = MediaStreamType.Audio, Language = "eng", IsDefault = true },
+ new() { Index = 2, Type = MediaStreamType.Subtitle, Language = "eng", IsDefault = true, IsForced = false },
+ new() { Index = 3, Type = MediaStreamType.Subtitle, Language = "eng", IsDefault = false, IsForced = true }
+ }
+ };
+
+ _user.SubtitleMode = mode;
+ _user.SubtitleLanguagePreference = string.Empty;
+ _user.RememberSubtitleSelections = true;
+ _user.AudioLanguagePreference = string.Empty;
+
+ _mediaSourceManager.SetDefaultAudioAndSubtitleStreamIndices(_item, mediaInfo, _user);
+
+ Assert.Equal(expectedIndex, mediaInfo.DefaultSubtitleStreamIndex);
+ }
+
+ [Fact]
+ public void SetDefaultSubtitleStreamIndex_OnlyForcedRemembersFullTrackWithNoForcedStream_SelectsNothing()
+ {
+ _mockUserDataManager
+ .Setup(m => m.GetUserData(It.IsAny<User>(), It.IsAny<BaseItem>()))
+ .Returns(new UserItemData { Key = "key", SubtitleStreamIndex = 2 });
+
+ var mediaInfo = new MediaSourceInfo
+ {
+ MediaStreams = new MediaStream[]
+ {
+ new() { Index = 0, Type = MediaStreamType.Video, IsDefault = true },
+ new() { Index = 1, Type = MediaStreamType.Audio, Language = "eng", IsDefault = true },
+ new() { Index = 2, Type = MediaStreamType.Subtitle, Language = "eng", IsDefault = true, IsForced = false }
+ }
+ };
+
+ _user.SubtitleMode = SubtitlePlaybackMode.OnlyForced;
+ _user.SubtitleLanguagePreference = string.Empty;
+ _user.RememberSubtitleSelections = true;
+ _user.AudioLanguagePreference = string.Empty;
+
+ _mediaSourceManager.SetDefaultAudioAndSubtitleStreamIndices(_item, mediaInfo, _user);
+
+ Assert.Null(mediaInfo.DefaultSubtitleStreamIndex);
+ }
+
[Fact]
public void GetStaticMediaSources_PrimaryQueried_DefaultsToMostRecentlyPlayedVersion()
{