diff options
Diffstat (limited to 'MediaBrowser.Controller/Library')
5 files changed, 164 insertions, 3 deletions
diff --git a/MediaBrowser.Controller/Library/ILibraryManager.cs b/MediaBrowser.Controller/Library/ILibraryManager.cs index 0b64da291c..2a6ea214b8 100644 --- a/MediaBrowser.Controller/Library/ILibraryManager.cs +++ b/MediaBrowser.Controller/Library/ILibraryManager.cs @@ -256,6 +256,14 @@ namespace MediaBrowser.Controller.Library IEnumerable<Video> GetLinkedAlternateVersions(Video video); /// <summary> + /// Gets, in a single query, the subset of the supplied items that own at least one alternate + /// version (local or linked). Items absent from the result have no alternate versions. + /// </summary> + /// <param name="itemIds">The item IDs to check.</param> + /// <returns>The set of item IDs that have alternate versions.</returns> + IReadOnlySet<Guid> GetItemIdsWithAlternateVersions(IReadOnlyList<Guid> itemIds); + + /// <summary> /// Creates or updates a LinkedChild entry linking a parent to a child item. /// </summary> /// <param name="parentId">The parent item ID.</param> @@ -598,6 +606,12 @@ namespace MediaBrowser.Controller.Library IReadOnlyList<string> GetPeopleNames(InternalPeopleQuery query); /// <summary> + /// Deletes every credit that no item maps to any more. + /// </summary> + /// <returns>The number of credits that were deleted.</returns> + int DeleteOrphanedCredits(); + + /// <summary> /// Gets the distinct people names per item for multiple items. /// </summary> /// <param name="itemIds">The item IDs.</param> @@ -606,6 +620,13 @@ namespace MediaBrowser.Controller.Library IReadOnlyDictionary<Guid, IReadOnlyList<string>> GetPeopleNamesByItems(IReadOnlyList<Guid> itemIds, IReadOnlyList<string> personTypes); /// <summary> + /// Gets the people for multiple items in a single query, keyed by item id. + /// </summary> + /// <param name="itemIds">The item IDs.</param> + /// <returns>A dictionary mapping each item ID to its people. Items with no people are omitted.</returns> + IReadOnlyDictionary<Guid, IReadOnlyList<PersonInfo>> GetPeopleByItems(IReadOnlyList<Guid> itemIds); + + /// <summary> /// Queries the items. /// </summary> /// <param name="query">The query.</param> @@ -799,5 +820,13 @@ namespace MediaBrowser.Controller.Library /// <param name="mediaStreamType">The stream type.</param> /// <returns>List of language codes.</returns> IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType); + + /// <summary> + /// Gets a list of all language codes for the matching items and the the provided stream type. + /// </summary> + /// <param name="mediaStreamType">The stream type.</param> + /// <param name="query">The query filter.</param> + /// <returns>List of language codes.</returns> + IReadOnlyList<string> GetMediaStreamLanguages(MediaStreamType mediaStreamType, InternalItemsQuery query); } } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 798812bf1f..2ee8845346 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -63,6 +63,24 @@ namespace MediaBrowser.Controller.Library Dictionary<Guid, UserItemData> GetUserDataBatch(IReadOnlyList<BaseItem> items, User user); /// <summary> + /// Gets the user data that should drive resume for a multi-version item: the data of the most + /// recently played alternate version (including the item itself) that has a resume point. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + /// <returns>The resume version's data, or <c>null</c> when the item has no versions or none has a resume point.</returns> + VersionResumeData? GetResumeUserData(User user, BaseItem item); + + /// <summary> + /// Gets the resume-driving user data for multiple items in a single batch operation. + /// See <see cref="GetResumeUserData(User, BaseItem)"/>. + /// </summary> + /// <param name="items">The items to get resume data for.</param> + /// <param name="user">The user.</param> + /// <returns>A dictionary mapping item ids to their resume version's data; items without one are omitted.</returns> + IReadOnlyDictionary<Guid, VersionResumeData> GetResumeUserDataBatch(IReadOnlyList<BaseItem> items, User user); + + /// <summary> /// Gets the user data dto. /// </summary> /// <param name="item">Item to use.</param> @@ -80,5 +98,13 @@ namespace MediaBrowser.Controller.Library /// <param name="reportedPositionTicks">New playstate.</param> /// <returns>True if playstate was updated.</returns> bool UpdatePlayState(BaseItem item, UserItemData data, long? reportedPositionTicks); + + /// <summary> + /// Clears any stored audio and subtitle stream selections for the given user/item pair. + /// Used when the user has opted out of remembering selections. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="item">The item.</param> + void ResetPlaybackStreamSelections(User user, BaseItem item); } } diff --git a/MediaBrowser.Controller/Library/SearchProviderQuery.cs b/MediaBrowser.Controller/Library/SearchProviderQuery.cs index 845588c872..b1ff800fa0 100644 --- a/MediaBrowser.Controller/Library/SearchProviderQuery.cs +++ b/MediaBrowser.Controller/Library/SearchProviderQuery.cs @@ -19,7 +19,9 @@ public class SearchProviderQuery public Guid? UserId { get; init; } /// <summary> - /// Gets the item types to include in the search. + /// Gets the item types to include in the search. An empty array means every type is eligible. + /// When this is non-empty it is the authoritative type filter and <see cref="ExcludeItemTypes"/> + /// does not apply; excludes only take effect when no include types were requested. /// </summary> public BaseItemKind[] IncludeItemTypes { get; init; } = []; @@ -29,7 +31,9 @@ public class SearchProviderQuery public BaseItemKind[] ExcludeItemTypes { get; init; } = []; /// <summary> - /// Gets the media types to include in the search. + /// Gets the media types to include in the search. This is an additional constraint rather than + /// an alternative one: a provider must return only items that match both the requested media + /// types and the requested item types, not the union of the two. /// </summary> public MediaType[] MediaTypes { get; init; } = []; @@ -39,7 +43,9 @@ public class SearchProviderQuery public int? Limit { get; init; } /// <summary> - /// Gets the parent ID to scope the search. + /// Gets the parent ID to scope the search. This scopes to the whole subtree, not just direct + /// children - callers routinely pass a library folder id and expect items nested arbitrarily + /// deep beneath it (an episode under a season under a series) to match. /// </summary> public Guid? ParentId { get; init; } } diff --git a/MediaBrowser.Controller/Library/VersionPlaybackSelector.cs b/MediaBrowser.Controller/Library/VersionPlaybackSelector.cs new file mode 100644 index 0000000000..1766c50141 --- /dev/null +++ b/MediaBrowser.Controller/Library/VersionPlaybackSelector.cs @@ -0,0 +1,59 @@ +using System; +using System.Collections.Generic; +using MediaBrowser.Controller.Entities; + +namespace MediaBrowser.Controller.Library +{ + /// <summary> + /// Single definition of "which alternate version was most recently played" shared by the resume tile + /// (<see cref="IUserDataManager.GetResumeUserData"/>), the media-source default ordering and Next Up. + /// Each call site declares its own eligibility rule so the intentional differences (resumable-only vs. + /// resumable-or-completed) are visible in one place instead of being re-implemented divergently. + /// The SQL resume query keeps its own translation of the same rule. + /// </summary> + public static class VersionPlaybackSelector + { + /// <summary> + /// Selects the entry whose user data has the greatest <see cref="UserItemData.LastPlayedDate"/>, + /// considering only entries that satisfy <paramref name="isEligible"/>. On an exact tie the first + /// encountered entry wins. + /// </summary> + /// <typeparam name="T">The candidate type (e.g. a version item or a media source).</typeparam> + /// <param name="items">The candidates to choose from.</param> + /// <param name="dataSelector">Resolves the user data for a candidate, or <c>null</c> when it has none.</param> + /// <param name="isEligible">Whether a candidate's user data makes it a valid winner.</param> + /// <returns>The most recently played eligible candidate, or <c>default</c> when none qualify.</returns> + public static T? SelectMostRecentlyPlayed<T>( + IEnumerable<T> items, + Func<T, UserItemData?> dataSelector, + Func<UserItemData, bool> isEligible) + { + ArgumentNullException.ThrowIfNull(items); + ArgumentNullException.ThrowIfNull(dataSelector); + ArgumentNullException.ThrowIfNull(isEligible); + + T? winner = default; + var winnerDate = DateTime.MinValue; + var hasWinner = false; + + foreach (var item in items) + { + var data = dataSelector(item); + if (data is null || !isEligible(data)) + { + continue; + } + + var date = data.LastPlayedDate ?? DateTime.MinValue; + if (!hasWinner || date > winnerDate) + { + winner = item; + winnerDate = date; + hasWinner = true; + } + } + + return winner; + } + } +} diff --git a/MediaBrowser.Controller/Library/VersionResumeData.cs b/MediaBrowser.Controller/Library/VersionResumeData.cs new file mode 100644 index 0000000000..772e2bf3a7 --- /dev/null +++ b/MediaBrowser.Controller/Library/VersionResumeData.cs @@ -0,0 +1,41 @@ +using System; +using MediaBrowser.Controller.Entities; +using MediaBrowser.Model.Dto; + +namespace MediaBrowser.Controller.Library +{ + /// <summary> + /// The user data of the most recently played alternate version that should drive the completion state of a multi-version item. + /// </summary> + /// <param name="VersionId">The id of the version that owns <paramref name="UserData"/>.</param> + /// <param name="UserData">The resume version's user data.</param> + public record VersionResumeData(Guid VersionId, UserItemData UserData) + { + /// <summary> + /// Merges the most recently played version's completion state into the supplied user data dto. + /// Completion (played) propagates to the primary. An in-progress resume position stays on the version + /// that owns it, which is surfaced directly (e.g. in resume queries) so that playback always targets + /// the correct version rather than resuming the primary at another version's offset. When the movie was + /// finished on a different version, the primary's own stale resume position is cleared so it does not + /// render as "watched and resumable" at the same time. + /// </summary> + /// <param name="dto">The user data dto to update.</param> + public void ApplyTo(UserItemDataDto dto) + { + dto.Played = dto.Played || UserData.Played; + + if ((UserData.LastPlayedDate ?? DateTime.MinValue) > (dto.LastPlayedDate ?? DateTime.MinValue)) + { + dto.LastPlayedDate = UserData.LastPlayedDate; + } + + // A different version was finished (played, no resume position of its own) and is the most + // recently played: the whole movie is watched. + if (!VersionId.Equals(dto.ItemId) && UserData.Played && UserData.PlaybackPositionTicks <= 0) + { + dto.PlaybackPositionTicks = 0; + dto.PlayedPercentage = null; + } + } + } +} |
