diff --git a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs index 2c0ec69941..c0476b00e7 100644 --- a/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs +++ b/Jellyfin.Server.Implementations/Item/BaseItemRepository.cs @@ -3323,6 +3323,33 @@ public sealed class BaseItemRepository baseQuery = baseQuery.Where(s => !playedSeriesIdList.Contains(s.Id)); } } + else if (filter.IncludeItemTypes.Length == 1 && filter.IncludeItemTypes[0] == BaseItemKind.BoxSet) + { + var boxSetIds = baseQuery.Select(e => e.Id).ToList(); + var userId = filter.User!.Id; + var playedBoxSetIds = new List(boxSetIds.Count); + foreach (var boxSetId in boxSetIds) + { + var descendantIds = DescendantQueryHelper.GetAllDescendantIds(context, boxSetId); + var leafItems = context.BaseItems + .Where(e => descendantIds.Contains(e.Id) && !e.IsFolder && !e.IsVirtualItem); + + if (leafItems.Any() + && leafItems.All(f => f.UserData!.Any(ud => ud.UserId == userId && ud.Played))) + { + playedBoxSetIds.Add(boxSetId); + } + } + + if (filter.IsPlayed.Value) + { + baseQuery = baseQuery.Where(s => playedBoxSetIds.Contains(s.Id)); + } + else + { + baseQuery = baseQuery.Where(s => !playedBoxSetIds.Contains(s.Id)); + } + } else { var playedItemIds = context.UserData diff --git a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs index 2166a58024..8216937cad 100644 --- a/MediaBrowser.Controller/Entities/Movies/BoxSet.cs +++ b/MediaBrowser.Controller/Entities/Movies/BoxSet.cs @@ -194,6 +194,34 @@ namespace MediaBrowser.Controller.Entities.Movies return true; } + public override void MarkPlayed(User user, DateTime? datePlayed, bool resetPosition) + { + if (IsLegacyBoxSet) + { + base.MarkPlayed(user, datePlayed, resetPosition); + return; + } + + foreach (var item in GetLinkedChildren(user)) + { + item.MarkPlayed(user, datePlayed, resetPosition); + } + } + + public override void MarkUnplayed(User user) + { + if (IsLegacyBoxSet) + { + base.MarkUnplayed(user); + return; + } + + foreach (var item in GetLinkedChildren(user)) + { + item.MarkUnplayed(user); + } + } + public override bool IsVisibleStandalone(User user) { if (IsLegacyBoxSet) diff --git a/MediaBrowser.Controller/Entities/UserViewBuilder.cs b/MediaBrowser.Controller/Entities/UserViewBuilder.cs index 47d732c745..81b0fe1c8c 100644 --- a/MediaBrowser.Controller/Entities/UserViewBuilder.cs +++ b/MediaBrowser.Controller/Entities/UserViewBuilder.cs @@ -539,6 +539,15 @@ namespace MediaBrowser.Controller.Entities } } + if (query.IsPlayed.HasValue) + { + userData ??= userDataManager.GetUserData(user, item); + if (item.IsPlayed(user, userData) != query.IsPlayed.Value) + { + return false; + } + } + if (query.IsLocked.HasValue) { var val = query.IsLocked.Value;