aboutsummaryrefslogtreecommitdiff
path: root/MediaBrowser.Controller/Library/VersionPlaybackSelector.cs
blob: 1766c50141754a1c85ea519ccc5d86800469f1c2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
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;
        }
    }
}