diff --git a/RaceControl/RaceControl.Common/Interfaces/IPlayableContent.cs b/RaceControl/RaceControl.Common/Interfaces/IPlayableContent.cs index e592addd..23babc12 100644 --- a/RaceControl/RaceControl.Common/Interfaces/IPlayableContent.cs +++ b/RaceControl/RaceControl.Common/Interfaces/IPlayableContent.cs @@ -13,4 +13,5 @@ public interface IPlayableContent bool IsLive { get; } string SyncUID { get; } string SeriesUID { get; } + string RequiredSubscriptionLevel { get; } } \ No newline at end of file diff --git a/RaceControl/RaceControl/PlayableChannel.cs b/RaceControl/RaceControl/PlayableChannel.cs index 341ba313..c5fb94b0 100644 --- a/RaceControl/RaceControl/PlayableChannel.cs +++ b/RaceControl/RaceControl/PlayableChannel.cs @@ -14,6 +14,7 @@ public PlayableChannel(Session session, Channel channel) IsLive = session.IsLive; SyncUID = session.UID; SeriesUID = session.SeriesUID; + RequiredSubscriptionLevel = channel.RequiredSubcriptionLevel ?? "Pro"; } private static string GetDisplayName(Channel channel) diff --git a/RaceControl/RaceControl/PlayableContent.cs b/RaceControl/RaceControl/PlayableContent.cs index ec2f457a..5ad361c0 100644 --- a/RaceControl/RaceControl/PlayableContent.cs +++ b/RaceControl/RaceControl/PlayableContent.cs @@ -11,6 +11,7 @@ public abstract class PlayableContent : IPlayableContent public bool IsLive { get; protected init; } public string SyncUID { get; protected init; } public string SeriesUID { get; protected init; } + public string RequiredSubscriptionLevel { get; protected init; } public override string ToString() { diff --git a/RaceControl/RaceControl/PlayableEpisode.cs b/RaceControl/RaceControl/PlayableEpisode.cs index 76693551..d6bf965c 100644 --- a/RaceControl/RaceControl/PlayableEpisode.cs +++ b/RaceControl/RaceControl/PlayableEpisode.cs @@ -12,5 +12,6 @@ public PlayableEpisode(Episode episode) ThumbnailUrl = episode.ThumbnailUrl; SyncUID = episode.UID; SeriesUID = episode.SeriesUID; + RequiredSubscriptionLevel = episode.RequiredSubscriptionLevel ?? "Pro"; } } \ No newline at end of file diff --git a/RaceControl/RaceControl/ViewModels/MainWindowViewModel.cs b/RaceControl/RaceControl/ViewModels/MainWindowViewModel.cs index d53dfbfc..d9f39657 100644 --- a/RaceControl/RaceControl/ViewModels/MainWindowViewModel.cs +++ b/RaceControl/RaceControl/ViewModels/MainWindowViewModel.cs @@ -877,6 +877,7 @@ private async Task LoadEpisodesForGenreAsync(string genre) private void WatchContent(IPlayableContent playableContent, VideoDialogSettings settings = null) { + ValidateActiveSubscription(playableContent); var identifier = _numberGenerator.GetNextNumber(); var parameters = new DialogParameters { @@ -886,10 +887,11 @@ private void WatchContent(IPlayableContent playableContent, VideoDialogSettings }; _dialogService.Show(nameof(VideoDialog), parameters, _ => _numberGenerator.RemoveNumber(identifier), nameof(VideoDialogWindow)); - } + } private async Task WatchInVlcAsync(IPlayableContent playableContent) { + ValidateActiveSubscription(playableContent); var streamUrl = await _apiService.GetTokenisedUrlAsync(Settings.SubscriptionToken, playableContent); ValidateStreamUrl(streamUrl); using var process = ProcessUtils.CreateProcess(VlcExeLocation, $"\"{streamUrl}\" --meta-title=\"{playableContent.Title}\""); @@ -898,6 +900,7 @@ private async Task WatchInVlcAsync(IPlayableContent playableContent) private async Task WatchInMpvAsync(IPlayableContent playableContent, VideoDialogSettings settings = null) { + ValidateActiveSubscription(playableContent); // Use different stream type because this one doesn't require a playToken cookie (not supported by MPV) var streamUrl = await _apiService.GetTokenisedUrlAsync(Settings.SubscriptionToken, playableContent, StreamTypeKeys.BigScreenHls); ValidateStreamUrl(streamUrl); @@ -1003,6 +1006,7 @@ void AddVideoQualityArgument(VideoQuality videoQuality) private async Task WatchInMpcAsync(IPlayableContent playableContent) { + ValidateActiveSubscription(playableContent); var streamUrl = await _apiService.GetTokenisedUrlAsync(Settings.SubscriptionToken, playableContent); ValidateStreamUrl(streamUrl); using var process = ProcessUtils.CreateProcess(MpcExeLocation, $"\"{streamUrl}\""); @@ -1011,6 +1015,7 @@ private async Task WatchInMpcAsync(IPlayableContent playableContent) private async Task CastContentAsync(IReceiver receiver, IPlayableContent playableContent) { + ValidateActiveSubscription(playableContent); var streamUrl = await _apiService.GetTokenisedUrlAsync(Settings.SubscriptionToken, playableContent); ValidateStreamUrl(streamUrl); AudioTracks.Clear(); @@ -1069,6 +1074,7 @@ IDictionary GetCustomData() private async Task CopyUrlAsync(IPlayableContent playableContent) { + ValidateActiveSubscription(playableContent); var streamUrl = await _apiService.GetTokenisedUrlAsync(Settings.SubscriptionToken, playableContent); ValidateStreamUrl(streamUrl); Clipboard.SetText(streamUrl); @@ -1076,6 +1082,7 @@ private async Task CopyUrlAsync(IPlayableContent playableContent) private void StartDownload(IPlayableContent playableContent) { + ValidateActiveSubscription(playableContent); var defaultFilename = $"{playableContent.Title}.mp4".RemoveInvalidFileNameChars(); var initialDirectory = !string.IsNullOrWhiteSpace(Settings.RecordingLocation) ? Settings.RecordingLocation : FolderUtils.GetSpecialFolderPath(Environment.SpecialFolder.Desktop); var filename = Path.Join(initialDirectory, defaultFilename); @@ -1202,11 +1209,19 @@ private void ClearSessions() SelectedVodGenre = null; } + private void ValidateActiveSubscription(IPlayableContent playableContent) + { + if (Settings.SubscriptionStatus.ToLower() != "active") + { + throw new InvalidOperationException($"Failed to execute action: \nAn active F1TV subscription is required of type '{playableContent.RequiredSubscriptionLevel}' or higher"); + } + } + private static void ValidateStreamUrl(string streamUrl) { if (string.IsNullOrWhiteSpace(streamUrl)) { - throw new Exception("An error occurred while retrieving the stream URL."); + throw new ArgumentException("An error occurred while retrieving the stream URL."); } } diff --git a/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Channel.cs b/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Channel.cs index 6a297f0d..f1066b9c 100644 --- a/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Channel.cs +++ b/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Channel.cs @@ -5,4 +5,5 @@ public class Channel public string Name { get; init; } public string ChannelType { get; init; } public string PlaybackUrl { get; init; } + public string RequiredSubcriptionLevel { get; init; } } \ No newline at end of file diff --git a/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Episode.cs b/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Episode.cs index 479d63dc..fdc4a22b 100644 --- a/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Episode.cs +++ b/RaceControl/Services/RaceControl.Services.Interfaces/F1TV/Entities/Episode.cs @@ -16,6 +16,7 @@ public class Episode public DateTime? ContractStartDate { get; init; } public DateTime? ContractEndDate { get; init; } public long SessionIndex { get; init; } + public string RequiredSubscriptionLevel { get; init; } public override string ToString() { diff --git a/RaceControl/Services/RaceControl.Services/F1TV/ApiService.cs b/RaceControl/Services/RaceControl.Services/F1TV/ApiService.cs index 56a32461..12ab5c69 100644 --- a/RaceControl/Services/RaceControl.Services/F1TV/ApiService.cs +++ b/RaceControl/Services/RaceControl.Services/F1TV/ApiService.cs @@ -151,7 +151,8 @@ public async Task> GetChannelsForSessionAsync(Session session) { Name = s.Type == ChannelTypes.Onboard ? $"{s.DriverFirstName} {s.DriverLastName}" : s.Title, ChannelType = s.Type, - PlaybackUrl = s.PlaybackUrl + PlaybackUrl = s.PlaybackUrl, + RequiredSubcriptionLevel = metadata.Entitlement }) .ToList(); } @@ -162,7 +163,8 @@ public async Task> GetChannelsForSessionAsync(Session session) { Name = ChannelNames.Wif, ChannelType = ChannelTypes.Wif, - PlaybackUrl = GetPlaybackUrl(metadata.ContentId) + PlaybackUrl = GetPlaybackUrl(metadata.ContentId), + RequiredSubcriptionLevel = metadata.Entitlement } }; } @@ -361,7 +363,8 @@ private static Episode CreateEpisode(Container container) EndDate = container.Metadata.EmfAttributes.SessionEndDate.GetDateTimeFromEpoch(), ContractStartDate = container.Metadata.ContractStartDate.GetDateTimeFromEpoch(), ContractEndDate = container.Metadata.ContractEndDate.GetDateTimeFromEpoch(), - SessionIndex = container.Metadata.EmfAttributes.SessionIndex + SessionIndex = container.Metadata.EmfAttributes.SessionIndex, + RequiredSubscriptionLevel = container.Metadata.Entitlement }; }