Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -499,24 +499,29 @@
var inProgress = context.UserData
.Where(ud => ud.UserId == userId && ud.PlaybackPositionTicks > 0);

// Folders are resumable when a descendant is in progress, or when they hold both played and
// unplayed descendants (partially watched). Alternate versions keep their own progress, so
// they count towards the in-progress check but not towards the played/unplayed one.
// Series and Seasons are resumable when a descendant is in progress, or when they hold both
// played and unplayed descendants (partially watched). Alternate versions keep their own
// progress, so they count towards the in-progress check but not towards the played/unplayed one.
var leafItems = GetAccessFilteredLeafItemsQuery(context, filter.User!);
var inProgressLeafItems = GetAccessFilteredLeafItemsQuery(context, filter.User!, includeOwnedItems: true)
.Where(e => e.UserData!.Any(ud => ud.UserId == userId && ud.PlaybackPositionTicks > 0));

var folderResumableFilter = BuildHasDescendantFilter(context, inProgressLeafItems)
.Or(BuildHasDescendantFilter(context, leafItems.Where(e => e.UserData!.Any(ud => ud.UserId == userId && ud.Played)))
.And(BuildHasDescendantFilter(context, leafItems.Where(e => !e.UserData!.Any(ud => ud.UserId == userId && ud.Played)))));
// Every other folder kind is a container rather than one continuous piece of media
var resumableFolderTypes = _resumableFolderKinds
.Select(kind => _itemTypeLookup.BaseItemKindNames.GetValueOrDefault(kind))
.ToArray();
var folderIsResumableFilter = IsFolderFilter.And(e => resumableFolderTypes.Contains(e.Type))
.And(BuildHasDescendantFilter(context, inProgressLeafItems)
.Or(BuildHasDescendantFilter(context, leafItems.Where(e => e.UserData!.Any(ud => ud.UserId == userId && ud.Played)))
.And(BuildHasDescendantFilter(context, leafItems.Where(e => !e.UserData!.Any(ud => ud.UserId == userId && ud.Played))))));

Check warning on line 516 in Jellyfin.Server.Implementations/Item/BaseItemRepository.TranslateQuery.cs

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Remove this null-forgiving operator; nullable warnings are disabled here.

See more on https://sonarcloud.io/project/issues?id=jellyfin_jellyfin&issues=AZ_GcyxExR8TZER3giT8&open=AZ_GcyxExR8TZER3giT8&pullRequest=17523

if (isResumable)
{
// Resume queries surface the version that was actually played, which may be an alternate.
// Match each version on its own progress rather than coalescing onto the primary.
var inProgressIds = inProgress.Select(ud => ud.ItemId);

baseQuery = baseQuery.Where(IsFolderFilter.And(folderResumableFilter)
baseQuery = baseQuery.Where(folderIsResumableFilter
.Or(IsFolderFilter.Not().And(e => inProgressIds.Contains(e.Id))));

// When several versions of the same item are in progress, keep only the most recently played one, use id as tiebreaker.
Expand All @@ -543,7 +548,7 @@
var resumableMovieIds = inProgress
.Join(context.BaseItems, ud => ud.ItemId, bi => bi.Id, (ud, bi) => bi.PrimaryVersionId ?? bi.Id);

baseQuery = baseQuery.Where(IsFolderFilter.And(folderResumableFilter.Not())
baseQuery = baseQuery.Where(IsFolderFilter.And(folderIsResumableFilter.Not())
.Or(IsFolderFilter.Not().And(e => !resumableMovieIds.Contains(e.Id))));
}
}
Expand Down
8 changes: 8 additions & 0 deletions Jellyfin.Server.Implementations/Item/BaseItemRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,14 @@ public sealed partial class BaseItemRepository
private static readonly IReadOnlyList<ItemValueType> _getStudiosValueTypes = [ItemValueType.Studios];
private static readonly IReadOnlyList<ItemValueType> _getGenreValueTypes = [ItemValueType.Genre];

// The only folder kinds whose children form a single viewing sequence, so playback progress on a
// child rolls up to them. Every other folder kind is a container that cannot be resumed.
private static readonly BaseItemKind[] _resumableFolderKinds =
[
BaseItemKind.Series,
BaseItemKind.Season
];

/// <summary>
/// Initializes a new instance of the <see cref="BaseItemRepository"/> class.
/// </summary>
Expand Down
Loading