diff options
Diffstat (limited to 'MediaBrowser.Controller')
| -rw-r--r-- | MediaBrowser.Controller/Entities/BaseItem.cs | 33 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Entities/Video.cs | 145 | ||||
| -rw-r--r-- | MediaBrowser.Controller/Library/IUserDataManager.cs | 8 |
3 files changed, 142 insertions, 44 deletions
diff --git a/MediaBrowser.Controller/Entities/BaseItem.cs b/MediaBrowser.Controller/Entities/BaseItem.cs index c69e24f876..84ba560900 100644 --- a/MediaBrowser.Controller/Entities/BaseItem.cs +++ b/MediaBrowser.Controller/Entities/BaseItem.cs @@ -1115,17 +1115,15 @@ namespace MediaBrowser.Controller.Entities } } - return result.OrderBy(i => - { - if (i.VideoType == VideoType.VideoFile) - { - return 0; - } + // The source belonging to the item being queried sorts first so it is the default the client plays. + var selfId = Id.ToString("N", CultureInfo.InvariantCulture); - return 1; - }).ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) - .ThenByDescending(i => i, new MediaSourceWidthComparator()) - .ToArray(); + return result + .OrderByDescending(i => string.Equals(i.Id, selfId, StringComparison.OrdinalIgnoreCase)) + .ThenBy(i => i.VideoType == VideoType.VideoFile ? 0 : 1) + .ThenBy(i => i.Video3DFormat.HasValue ? 1 : 0) + .ThenByDescending(i => i, new MediaSourceWidthComparator()) + .ToArray(); } protected virtual IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() @@ -2114,12 +2112,23 @@ namespace MediaBrowser.Controller.Entities // I think it is okay to do this here. // if this is only called when a user is manually forcing something to un-played // then it probably is what we want to do... + ResetPlayedState(data); + + UserDataManager.SaveUserData(user, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None); + } + + /// <summary> + /// Clears the played state on the supplied user data. + /// </summary> + /// <param name="data">The user data to reset.</param> + protected static void ResetPlayedState(UserItemData data) + { + ArgumentNullException.ThrowIfNull(data); + data.PlayCount = 0; data.PlaybackPositionTicks = 0; data.LastPlayedDate = null; data.Played = false; - - UserDataManager.SaveUserData(user, this, data, UserDataSaveReason.TogglePlayed, CancellationToken.None); } /// <summary> diff --git a/MediaBrowser.Controller/Entities/Video.cs b/MediaBrowser.Controller/Entities/Video.cs index e7a5672ebd..168ef7d817 100644 --- a/MediaBrowser.Controller/Entities/Video.cs +++ b/MediaBrowser.Controller/Entities/Video.cs @@ -34,11 +34,11 @@ namespace MediaBrowser.Controller.Entities { public Video() { - AdditionalParts = Array.Empty<string>(); - LocalAlternateVersions = Array.Empty<string>(); - SubtitleFiles = Array.Empty<string>(); - AudioFiles = Array.Empty<string>(); - LinkedAlternateVersions = Array.Empty<LinkedChild>(); + AdditionalParts = []; + LocalAlternateVersions = []; + SubtitleFiles = []; + AudioFiles = []; + LinkedAlternateVersions = []; } [JsonIgnore] @@ -335,6 +335,92 @@ namespace MediaBrowser.Controller.Entities PresentationUniqueKey = CreatePresentationUniqueKey(); } + /// <summary> + /// Marks the played status of this video and propagates it to its alternate versions. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="datePlayed">The date played.</param> + /// <param name="resetPosition">if set to <c>true</c> [reset position].</param> + public override void MarkPlayed(User user, DateTime? datePlayed, bool resetPosition) + { + base.MarkPlayed(user, datePlayed, resetPosition); + PropagatePlayedState(user, true, resetPosition); + } + + /// <summary> + /// Marks this video unplayed and propagates the change to its alternate versions. + /// </summary> + /// <param name="user">The user.</param> + public override void MarkUnplayed(User user) + { + base.MarkUnplayed(user); + + // MarkUnplayed always clears the position on this video, so reset the versions too. + PropagatePlayedState(user, false, true); + } + + /// <summary> + /// Propagates the played status to every alternate version of this video. + /// </summary> + /// <param name="user">The user.</param> + /// <param name="played">The played status to apply to the alternate versions.</param> + /// <param name="resetPosition">When marking played, controls whether each version's resume point + /// is also reset (<c>true</c>) or left untouched (<c>false</c>). Ignored when marking unplayed, + /// which always fully resets every version.</param> + public void PropagatePlayedState(User user, bool played, bool resetPosition = true) + { + ArgumentNullException.ThrowIfNull(user); + + if (!PrimaryVersionId.HasValue && LinkedAlternateVersions.Length == 0 && !HasLocalAlternateVersions) + { + return; + } + + foreach (var (item, _) in GetAllItemsForMediaSources()) + { + if (item.Id.Equals(Id) || item is not Video) + { + continue; + } + + if (played) + { + var dto = new UpdateUserItemDataDto { Played = true }; + if (resetPosition) + { + dto.PlaybackPositionTicks = 0; + } + + // SaveUserData only writes the fields set on the DTO, so play count and other state are preserved. + UserDataManager.SaveUserData(user, item, dto, UserDataSaveReason.TogglePlayed); + } + else + { + var data = UserDataManager.GetUserData(user, item); + if (data is null) + { + continue; + } + + ResetPlayedState(data); + UserDataManager.SaveUserData(user, item, data, UserDataSaveReason.TogglePlayed, CancellationToken.None); + } + } + } + + /// <summary> + /// Gets the alternate version of this video that matches the supplied item id. + /// </summary> + /// <param name="itemId">The version item id (the playback media source id).</param> + /// <returns>The matching version, or <c>null</c> when the id is not a version of this video.</returns> + public Video GetAlternateVersion(Guid itemId) + { + return GetAllItemsForMediaSources() + .Select(i => i.Item) + .OfType<Video>() + .FirstOrDefault(i => i.Id.Equals(itemId)); + } + public override string CreatePresentationUniqueKey() { if (PrimaryVersionId.HasValue) @@ -644,37 +730,32 @@ namespace MediaBrowser.Controller.Entities protected override IEnumerable<(BaseItem Item, MediaSourceType MediaSourceType)> GetAllItemsForMediaSources() { - var list = new List<(BaseItem, MediaSourceType)> - { - (this, MediaSourceType.Default) - }; - - list.AddRange( - LibraryManager.GetLinkedAlternateVersions(this) - .Select(i => ((BaseItem)i, MediaSourceType.Grouping))); - - if (PrimaryVersionId.HasValue) - { - if (LibraryManager.GetItemById(PrimaryVersionId.Value) is Video primary) - { - var existingIds = list.Select(i => i.Item1.Id).ToList(); - list.Add((primary, MediaSourceType.Grouping)); - list.AddRange(LibraryManager.GetLinkedAlternateVersions(primary).Where(i => !existingIds.Contains(i.Id)).Select(i => ((BaseItem)i, MediaSourceType.Grouping))); - } - } + var primary = PrimaryVersionId.HasValue + ? LibraryManager.GetItemById(PrimaryVersionId.Value) as Video + : null; + + // This video and its linked alternates, when this is itself an alternate, the primary and the primary's linked alternates. + var grouped = new[] { ((BaseItem)this, MediaSourceType.Default) } + .Concat(LibraryManager.GetLinkedAlternateVersions(this).Select(i => ((BaseItem)i, MediaSourceType.Grouping))) + .Concat(primary is null + ? [] + : LibraryManager.GetLinkedAlternateVersions(primary).Prepend(primary).Select(i => ((BaseItem)i, MediaSourceType.Grouping))) + .ToList(); - var localAlternates = list - .SelectMany(i => - { - return i.Item1 is Video video ? LibraryManager.GetLocalAlternateVersionIds(video) : Enumerable.Empty<Guid>(); - }) + // The local (file-based) alternate versions of every grouped item. + var localAlternates = grouped + .Select(i => i.Item1) + .OfType<Video>() + .SelectMany(LibraryManager.GetLocalAlternateVersionIds) .Select(LibraryManager.GetItemById) .Where(i => i is not null) - .ToList(); + .Select(i => (i, MediaSourceType.Default)); - list.AddRange(localAlternates.Select(i => (i, MediaSourceType.Default))); - - return list; + // Deduplicate + return grouped + .Concat(localAlternates) + .DistinctBy(i => i.Item1.Id) + .ToList(); } } } diff --git a/MediaBrowser.Controller/Library/IUserDataManager.cs b/MediaBrowser.Controller/Library/IUserDataManager.cs index 798812bf1f..0b4229c784 100644 --- a/MediaBrowser.Controller/Library/IUserDataManager.cs +++ b/MediaBrowser.Controller/Library/IUserDataManager.cs @@ -80,5 +80,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); } } |
