From 5ae0be5ba08e12ef3fb99a8b5f2a9033baf983f4 Mon Sep 17 00:00:00 2001 From: TimGels <43609220+TimGels@users.noreply.github.com> Date: Sat, 20 Aug 2022 22:24:38 +0200 Subject: [PATCH] Added Subtitle Track selector Implemented the ability to select a subtitle track on the video player page. Added a SubtitleAudioTrackSelector UserControl. This UserControl hosts the subtitle tracks and will also host the audio tracks from which a user can select a track. Introduced a MediaTrackService which can be injected to provider some methods. Methods it provide are at this moment the retrieval of subtitle tracks and the preferred subtitle track. Due to LibVLC not being able to be injected by a dependency injector due to several reasons, this service needs to be initialized by calling the initialize method and providing it with a reference to a (initialized!) MediaPlayer instance. This service can be expanded to perform more general MediaPlayer tasks as well in the future if need be. Added a ValueConverterGroup enabling the chaining of value converters. Added a custom exception which gets thrown when one of the the MediaTrackService methods get called without the service being initialized. Added MediaTrackService to the dependency Injector container in App.xaml.cs. Replaced public mediaplayer property calls with private. --- OneDrive-Cloud-Player/App.xaml.cs | 2 + .../Models/Interfaces/IMediaTrackService.cs | 37 ++++ .../OneDrive-Cloud-Player.csproj | 16 ++ .../Services/Converters/MediaTimeConverter.cs | 2 +- .../Converters/ValueConverterGroup.cs | 20 ++ .../Services/MediaTrackService.cs | 90 +++++++++ .../ViewModels/VideoPlayerPageViewModel.cs | 172 ++++++++++++++---- .../Views/SubtitleAudioTrackSelector.xaml | 49 +++++ .../Views/SubtitleAudioTrackSelector.xaml.cs | 128 +++++++++++++ .../Views/VideoPlayerPage.xaml | 14 ++ .../Views/VideoPlayerPage.xaml.cs | 10 + 11 files changed, 500 insertions(+), 40 deletions(-) create mode 100644 OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs create mode 100644 OneDrive-Cloud-Player/Services/Converters/ValueConverterGroup.cs create mode 100644 OneDrive-Cloud-Player/Services/MediaTrackService.cs create mode 100644 OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml create mode 100644 OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs diff --git a/OneDrive-Cloud-Player/App.xaml.cs b/OneDrive-Cloud-Player/App.xaml.cs index 0324603..a10ce71 100644 --- a/OneDrive-Cloud-Player/App.xaml.cs +++ b/OneDrive-Cloud-Player/App.xaml.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Identity.Client; using OneDrive_Cloud_Player.Models.GraphData; +using OneDrive_Cloud_Player.Models.Interfaces; using OneDrive_Cloud_Player.Services; using OneDrive_Cloud_Player.Views; using System; @@ -83,6 +84,7 @@ IServiceProvider ConfigureDependencyInjection() var serviceCollection = new ServiceCollection(); // Add services for dependency injection here. + serviceCollection.AddTransient(); return serviceCollection.BuildServiceProvider(); } diff --git a/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs b/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs new file mode 100644 index 0000000..0c27a88 --- /dev/null +++ b/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs @@ -0,0 +1,37 @@ +using LibVLCSharp.Shared; +using LibVLCSharp.Shared.Structures; +using System.Threading.Tasks; + +namespace OneDrive_Cloud_Player.Models.Interfaces +{ + public interface IMediaTrackService + { + /// + /// Needs to be called before using the service and before subscribing to other libvlcsharp events.
+ /// Due to LibVLCSharp's inability for the MediaPlayer to be put into a service, we need to get a reference to the MediaPlayer ourselves instead.
+ /// Initialize this service by parsing the MediaPlayer object reference for its media tracks. + ///
+ /// + /// + IMediaTrackService Initialize(ref MediaPlayer mediaPlayer); + + /// + /// Get the preferred subtitle track. When no preferred subtitle track is found, a default will be returned. + ///
+ /// Note: This method should not be called on a LibVLC thread. + /// Here is more info on why. + ///
+ /// Preferred subtitle track or default + /// + TrackDescription GetPreferredSubtitleTrack(); + + /// + /// Get the embedded subtitle tracks in the media file. + ///
+ /// Note: This method should not be called on a LibVLC thread. + /// Here is more info on why. + ///
+ /// Array containing 's or empty when no tracks are available + TrackDescription[] GetEmbeddedSubtitleTracks(); + } +} diff --git a/OneDrive-Cloud-Player/OneDrive-Cloud-Player.csproj b/OneDrive-Cloud-Player/OneDrive-Cloud-Player.csproj index fcdee5c..d0d4b1d 100644 --- a/OneDrive-Cloud-Player/OneDrive-Cloud-Player.csproj +++ b/OneDrive-Cloud-Player/OneDrive-Cloud-Player.csproj @@ -130,15 +130,18 @@ + + + @@ -154,6 +157,9 @@ SettingsPage.xaml + + SubtitleAudioTrackSelector.xaml + VideoPlayerPage.xaml @@ -250,6 +256,10 @@ MSBuild:Compile Designer + + Designer + MSBuild:Compile + Designer MSBuild:Compile @@ -277,6 +287,12 @@ 7.1.2 + + 7.1.3 + + + 2.8.2 + 2.0.1 diff --git a/OneDrive-Cloud-Player/Services/Converters/MediaTimeConverter.cs b/OneDrive-Cloud-Player/Services/Converters/MediaTimeConverter.cs index faa8ebd..5b5b21b 100644 --- a/OneDrive-Cloud-Player/Services/Converters/MediaTimeConverter.cs +++ b/OneDrive-Cloud-Player/Services/Converters/MediaTimeConverter.cs @@ -6,7 +6,7 @@ namespace OneDrive_Cloud_Player.Services.Converters class MediaTimeConverter : IValueConverter { object IValueConverter.Convert(object timeMs, Type targetType, object parameter, string language) - { + { TimeSpan timeSpan = TimeSpan.FromMilliseconds(Convert.ToDouble(timeMs)); return timeSpan.ToString(@"hh\:mm\:ss"); } diff --git a/OneDrive-Cloud-Player/Services/Converters/ValueConverterGroup.cs b/OneDrive-Cloud-Player/Services/Converters/ValueConverterGroup.cs new file mode 100644 index 0000000..5c2223a --- /dev/null +++ b/OneDrive-Cloud-Player/Services/Converters/ValueConverterGroup.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Windows.UI.Xaml.Data; + +namespace OneDrive_Cloud_Player.Services.Converters +{ + public class ValueConverterGroup : List, IValueConverter + { + public object Convert(object value, Type targetType, object parameter, string language) + { + return this.Aggregate(value, (current, converter) => converter.Convert(current, targetType, parameter, language)); + } + + public object ConvertBack(object value, Type targetType, object parameter, string language) + { + throw new NotImplementedException(); + } + } +} diff --git a/OneDrive-Cloud-Player/Services/MediaTrackService.cs b/OneDrive-Cloud-Player/Services/MediaTrackService.cs new file mode 100644 index 0000000..bc51d86 --- /dev/null +++ b/OneDrive-Cloud-Player/Services/MediaTrackService.cs @@ -0,0 +1,90 @@ +using LibVLCSharp.Shared; +using LibVLCSharp.Shared.Structures; +using OneDrive_Cloud_Player.Models.Interfaces; +using System; +using System.Diagnostics; +using System.Linq; +using Windows.Storage; + +namespace OneDrive_Cloud_Player.Services +{ + /// + /// Manage the media tracks of the libvlcsharp media player instance. + /// + public class MediaTrackService : IMediaTrackService + { + private MediaPlayer _mediaPlayer; + private bool _isInitialized = false; + private readonly ApplicationDataContainer _userSettings; + + public MediaTrackService() + { + _userSettings = ApplicationData.Current.LocalSettings; + } + + /// + /// Due to LibVLCSharp's inability for the MediaPlayer to be put into a service, we need to get a reference to the MediaPlayer ourselves instead. + /// Initialize this service by parsing the MediaPlayer object reference for its media tracks. + /// + public IMediaTrackService Initialize(ref MediaPlayer mediaPlayer) + { + if (_isInitialized) + { + throw new InvalidOperationException("Service already initialized!"); + } + + Debug.WriteLine(String.Format("initializing {0}", GetType().Name)); + _mediaPlayer = mediaPlayer; + _isInitialized = true; + return this; + } + + /// + /// + /// + /// + private void CheckInitializationState() + { + if (!_isInitialized) + { + throw new InvalidOperationException("Service not initialized!"); + } + } + + /// + /// + /// + /// + public TrackDescription GetPreferredSubtitleTrack() + { + CheckInitializationState(); + TrackDescription[] subtitleTracks = _mediaPlayer.SpuDescription; + + if (_mediaPlayer.SpuCount <= 0) + { + return default; + } + + // Return default track subtitle (when possible, like in mkv) depending on the user setting. + if ((bool)_userSettings.Values["ShowDefaultSubtitles"]) + { + return subtitleTracks.First(subtitleTrack => subtitleTrack.Id == _mediaPlayer.Spu); + } + else + { + // Return the "disabled" indicator track positioned at index 0. + return _mediaPlayer.SpuDescription.ElementAt(0); + } + } + + /// + /// + /// + /// + public TrackDescription[] GetEmbeddedSubtitleTracks() + { + CheckInitializationState(); + return _mediaPlayer.SpuDescription; + } + } +} diff --git a/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs b/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs index 8a823e2..e9c059f 100644 --- a/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs +++ b/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs @@ -1,5 +1,6 @@ using LibVLCSharp.Platforms.UWP; using LibVLCSharp.Shared; +using LibVLCSharp.Shared.Structures; using Microsoft.Toolkit.Mvvm.ComponentModel; using Microsoft.Toolkit.Mvvm.Input; using OneDrive_Cloud_Player.Models; @@ -7,14 +8,17 @@ using OneDrive_Cloud_Player.Services; using OneDrive_Cloud_Player.Services.Helpers; using System; +using System.Collections.ObjectModel; using System.ComponentModel; using System.Diagnostics; +using System.Linq; using System.Threading.Tasks; using System.Timers; using System.Windows.Input; using Windows.System; using Windows.UI.Core; using Windows.UI.Xaml; +using Timer = System.Timers.Timer; namespace OneDrive_Cloud_Player.ViewModels { @@ -41,17 +45,19 @@ public class VideoPlayerPageViewModel : ObservableRecipient, IDisposable, INavig /// private readonly object volumeLocker = new object(); + private readonly IMediaTrackService _mediaTrackService; /// /// Used to make sure that the volume is initialized once after starting video /// playback. This needs to happen every time when creating a new LibVLC object, /// so upon every navigation action to this page. /// private bool volumeUpdated = false; - private MediaWrapper MediaWrapper = null; private bool InvalidOneDriveSession = false; - private MediaPlayer mediaPlayer; + private MediaPlayer _mediaPlayer; private int MediaListIndex; + private bool _isFirstPlaying = true; + private bool _isReloading = false; public bool IsSeeking { get; set; } private LibVLC LibVLC { get; set; } @@ -71,6 +77,7 @@ public class VideoPlayerPageViewModel : ObservableRecipient, IDisposable, INavig public ICommand SeekForwardCommand { get; } public ICommand PlayPreviousVideoCommand { get; } public ICommand PlayNextVideoCommand { get; } + public ICommand OkClickedCommand { get; } private long timeLineValue; @@ -205,20 +212,46 @@ public bool IsBackBtnEnabled } } + private TrackDescription _selectedSubtitleTrack; + + public TrackDescription SelectedSubtitleTrack + { + get { return _selectedSubtitleTrack; } + set + { + _selectedSubtitleTrack = value; + SetSubtitleTrackById(value.Id); + OnPropertyChanged(); + } + } + + private ObservableCollection _subtitleTracks = new ObservableCollection(); + + public ObservableCollection SubtitleTracks + { + get { return _subtitleTracks; } + set + { + _subtitleTracks = value; + OnPropertyChanged(); + } + } + /// /// Gets the media player /// public MediaPlayer MediaPlayer { - get => mediaPlayer; - set => SetProperty(ref mediaPlayer, value); + get => _mediaPlayer; + set => SetProperty(ref _mediaPlayer, value); } /// /// Initialized a new instance of class /// - public VideoPlayerPageViewModel() + public VideoPlayerPageViewModel(IMediaTrackService mediaTrackService) { + _mediaTrackService = mediaTrackService; InitializeLibVLCCommand = new RelayCommand(InitializeLibVLC); StartedDraggingThumbCommand = new RelayCommand(StartedDraggingThumb, CanExecuteCommand); StoppedDraggingThumbCommand = new RelayCommand(StoppedDraggingThumb, CanExecuteCommand); @@ -252,14 +285,15 @@ private async void InitializeLibVLC(InitializedEventArgs eventArgs) // Create LibVLC instance. LibVLC = new LibVLC(eventArgs.SwapChainOptions); - MediaPlayer = new MediaPlayer(LibVLC); - + _mediaPlayer = new MediaPlayer(LibVLC); + _mediaTrackService.Initialize(ref _mediaPlayer); // Subscribe to events only once. - MediaPlayer.Playing += MediaPlayer_Playing; - MediaPlayer.Paused += MediaPlayer_Paused; - MediaPlayer.TimeChanged += MediaPlayer_TimeChanged; + _mediaPlayer.Playing += MediaPlayer_Playing; + _mediaPlayer.Paused += MediaPlayer_Paused; + _mediaPlayer.TimeChanged += MediaPlayer_TimeChanged; + _mediaPlayer.MediaChanged += MediaPlayer_MediaChanged; // Listen to the first volumechanged event, after which we can initialise the volume level correctly. - MediaPlayer.VolumeChanged += UpdateInitialVolume; + _mediaPlayer.VolumeChanged += UpdateInitialVolume; reloadIntervalTimer.Elapsed += ReloadIntervalTimer_Elapsed; fileNameOverlayTimer.Elapsed += FileNameOverlayTimer_Elapsed; @@ -297,27 +331,69 @@ private async void UpdateInitialVolume(object sender, MediaPlayerVolumeChangedEv // Updating the GUI should happen on the dispatcher. await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.High, () => { - MediaPlayer.VolumeChanged -= UpdateInitialVolume; + _mediaPlayer.VolumeChanged -= UpdateInitialVolume; MediaVolumeLevel = (int)App.Current.UserSettings.Values["MediaVolume"]; IsBackBtnEnabled = true; }); } + /// + /// Media playing event handler. Gets called when the media player state is changed to playing. + /// + /// + /// private async void MediaPlayer_Playing(object sender, EventArgs e) { Debug.WriteLine(DateTime.Now.ToString("hh:mm:ss.fff") + ": Media is playing"); - await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.High, () => + + if (_isFirstPlaying || _isReloading) { - //Sets the max value of the seekbar. - VideoLength = MediaPlayer.Length; + TrackDescription[] subtitleTracks = null; + TrackDescription selectedSubtitleTrack = default; - PlayPauseButtonFontIcon = "\xE769"; + await Task.Run(() => + { + subtitleTracks = _mediaTrackService.GetEmbeddedSubtitleTracks(); + selectedSubtitleTrack = _mediaTrackService.GetPreferredSubtitleTrack(); + }); - //Enable or disable default subtitle based on user setting. - if (!(bool)App.Current.UserSettings.Values["ShowDefaultSubtitles"]) + if (_isFirstPlaying) { - MediaPlayer.SetSpu(-1); + _isFirstPlaying = false; } + + if (_isReloading) + { + _isReloading = false; + + // Check if there is a selected subtitle track, for re-selection, to begin with. + if (_selectedSubtitleTrack.Id != 0 && _selectedSubtitleTrack.Name != null) + { + // Retrieve the same selected subtitle track again as the one that was used with the former subtitle tracks collection. + selectedSubtitleTrack = subtitleTracks.FirstOrDefault(subtitleTrack => subtitleTrack.Id == _selectedSubtitleTrack.Id); + } + } + + await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.Normal, () => + { + // Clear collection as we want to refill it with updated tracks from the new media source. + SubtitleTracks.Clear(); + foreach (TrackDescription subtitleTrack in subtitleTracks) + { + SubtitleTracks.Add(subtitleTrack); + } + + // Select the correct subtitle track. + SelectedSubtitleTrack = selectedSubtitleTrack; + }); + } + + await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.High, () => + { + //Sets the max value of the seekbar. + VideoLength = _mediaPlayer.Length; + + PlayPauseButtonFontIcon = "\xE769"; }); } @@ -337,16 +413,24 @@ await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.Low, () => // when the user is not seeking. if (!IsSeeking) { - // Sometimes the MediaPlayer is already null upon + // Sometimes the _mediaPlayer is already null upon // navigating away and this still gets called. - if (MediaPlayer != null) + if (_mediaPlayer != null) { - TimeLineValue = MediaPlayer.Time; + TimeLineValue = _mediaPlayer.Time; } } }); } + private void MediaPlayer_MediaChanged(object sender, EventArgs e) + { + if (!_isReloading) + { + _isFirstPlaying = true; + } + } + private void ReloadIntervalTimer_Elapsed(object sender, ElapsedEventArgs e) { InvalidOneDriveSession = true; @@ -405,27 +489,36 @@ private async Task PlayMedia(long startTime = 0) // Play the OneDrive file. using (Media media = new Media(LibVLC, new Uri(mediaDownloadURL))) { - MediaPlayer.Play(media); + _mediaPlayer.Play(media); } - if (MediaPlayer is null) + if (_mediaPlayer is null) { Debug.WriteLine("PlayMedia: Error: Could not set start time."); return; } - MediaPlayer.Time = startTime; + _mediaPlayer.Time = startTime; + } + + /// + /// Set the subtitle track used in the by id. + /// + /// Subtitle track id + private void SetSubtitleTrackById(int subtitleTrackId) + { + _mediaPlayer.SetSpu(subtitleTrackId); } private void SetMediaVolume(int volumeLevel) { - if (MediaPlayer is null) + if (_mediaPlayer is null) { Debug.WriteLine("Error: SetMediaVolumeLevel: Could not set the volume."); - return; // Return when the MediaPlayer is null so it does not cause exception. + return; // Return when the _mediaPlayer is null so it does not cause exception. } App.Current.UserSettings.Values["MediaVolume"] = volumeLevel; // Set the new volume in the MediaVolume setting. - MediaPlayer.Volume = volumeLevel; + _mediaPlayer.Volume = volumeLevel; UpdateVolumeButtonFontIcon(volumeLevel); } @@ -483,7 +576,7 @@ private void Seeked() /// public void SeekBackward(double ms) { - SetVideoTime(MediaPlayer.Time - ms); + SetVideoTime(_mediaPlayer.Time - ms); } /// @@ -492,7 +585,7 @@ public void SeekBackward(double ms) /// public void SeekForward(double ms) { - SetVideoTime(MediaPlayer.Time + ms); + SetVideoTime(_mediaPlayer.Time + ms); } /// @@ -507,7 +600,7 @@ private void SetVideoTime(double time) Debug.WriteLine(" + Reloading media."); ReloadCurrentMedia(); } - MediaPlayer.Time = Convert.ToInt64(time); + _mediaPlayer.Time = Convert.ToInt64(time); } //TODO: Implement a Dialog system that shows a dialog when there is an error. @@ -516,6 +609,7 @@ private void SetVideoTime(double time) /// private async void ReloadCurrentMedia() { + _isReloading = true; await PlayMedia(TimeLineValue); InvalidOneDriveSession = false; } @@ -531,7 +625,7 @@ private void ChangePlayingState() public void StopMedia() { - MediaPlayer.Stop(); + _mediaPlayer.Stop(); TimeLineValue = 0; Dispose(); // Go back to the last page. @@ -667,20 +761,20 @@ public void Deactivate(object parameter) /// public void Dispose() { - // TODO: Reproduce MediaPlayer == null - Debug.Assert(MediaPlayer != null); + // TODO: Reproduce _mediaPlayer == null + Debug.Assert(_mediaPlayer != null); Debug.Assert(LibVLC != null); // Unsubscribe from event handlers. - MediaPlayer.Playing -= MediaPlayer_Playing; - MediaPlayer.Paused -= MediaPlayer_Paused; - MediaPlayer.TimeChanged -= MediaPlayer_TimeChanged; + _mediaPlayer.Playing -= MediaPlayer_Playing; + _mediaPlayer.Paused -= MediaPlayer_Paused; + _mediaPlayer.TimeChanged -= MediaPlayer_TimeChanged; reloadIntervalTimer.Elapsed -= ReloadIntervalTimer_Elapsed; fileNameOverlayTimer.Elapsed -= FileNameOverlayTimer_Elapsed; // Dispose of the LibVLC instance and the mediaplayer. - var mediaPlayer = MediaPlayer; - MediaPlayer = null; + var mediaPlayer = _mediaPlayer; + _mediaPlayer = null; mediaPlayer?.Dispose(); LibVLC?.Dispose(); LibVLC = null; diff --git a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml new file mode 100644 index 0000000..3cfbb05 --- /dev/null +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml @@ -0,0 +1,49 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs new file mode 100644 index 0000000..825f9f4 --- /dev/null +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs @@ -0,0 +1,128 @@ +using LibVLCSharp.Shared.Structures; +using System; +using System.Collections.ObjectModel; +using System.Collections.Specialized; +using System.ComponentModel; +using System.Diagnostics; +using Windows.UI.Xaml; +using Windows.UI.Xaml.Controls; + +namespace OneDrive_Cloud_Player.Views +{ + public sealed partial class SubtitleAudioTrackSelector : UserControl, INotifyPropertyChanged + { + public event EventHandler ItemClicked; + + private bool _isSubtitlesAvailable = false; + public bool IsSubtitlesAvailable + { + get { return _isSubtitlesAvailable; } + set + { + _isSubtitlesAvailable = value; + OnPropertyChanged(nameof(IsSubtitlesAvailable)); + } + } + + public ObservableCollection SubtitleTracks + { + get { return (ObservableCollection)GetValue(SubtitleTracksProperty); } + set { SetValue(SubtitleTracksProperty, value); } + } + + // Using a DependencyProperty as the backing store for SubtitleTracks. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SubtitleTracksProperty = + DependencyProperty.Register("SubtitleTracks", typeof(ObservableCollection), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null, SubtitleTracksPropertyChangedCallback)); + + public TrackDescription SelectedSubtitleTrack + { + get { return (TrackDescription)GetValue(SelectedSubtitleTrackProperty); } + set { SetValue(SelectedSubtitleTrackProperty, value); } + } + + // Using a DependencyProperty as the backing store for SelectedTrack. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SelectedSubtitleTrackProperty = + DependencyProperty.Register("SelectedSubtitleTrack", typeof(TrackDescription), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null)); + + public SubtitleAudioTrackSelector() + { + InitializeComponent(); + } + + /// + /// Callback for SubtitleTracksProperty property changes. + /// + /// + /// + private static void SubtitleTracksPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) + { + SubtitleAudioTrackSelector subtitleAudioTrackSelector = (SubtitleAudioTrackSelector)dependencyObject; + if (((ObservableCollection)eventArgs.NewValue).Count > 0) + { + subtitleAudioTrackSelector.IsSubtitlesAvailable = true; + } + + if (eventArgs.NewValue is INotifyCollectionChanged newCollection) + { + // Subscribe to changes inside the new collection. + newCollection.CollectionChanged += subtitleAudioTrackSelector.SubtitleTracks_CollectionChanged; + } + + if (eventArgs.OldValue is INotifyCollectionChanged oldCollection) + { + // Unsubscribe to changes inside the old collection. + oldCollection.CollectionChanged -= subtitleAudioTrackSelector.SubtitleTracks_CollectionChanged; + } + } + + /// + /// Hnadling changes that occur inside the subtitle tracks collection (i.e. added or removed). + /// + /// + /// + private void SubtitleTracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Check for Default value. + if (e.NewItems == null) + { + IsSubtitlesAvailable = false; + return; + } + + // Check if there are subtitles. + if (e.NewItems.Count <= 0) + { + IsSubtitlesAvailable = false; + return; + } + + IsSubtitlesAvailable = true; + } + + /// + /// Invoke the custom event when a subtitle item in the listview is clicked. + /// + /// + /// + private void SubtitleTrackListView_ItemClick(object sender, ItemClickEventArgs e) + { + Debug.WriteLine("Subtitle item clicked from listview."); + ItemClicked?.Invoke(this, EventArgs.Empty); + } + + /// + /// When called, notifies the UI that the property value has changed. + /// + /// Name of the property which value has changed + private void OnPropertyChanged(string propertyName) + { + PropertyChangedEventHandler handler = PropertyChanged; + + if (handler != null) + { + PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); + } + } + public event PropertyChangedEventHandler PropertyChanged; + } +} diff --git a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml index 19fd184..c41ab1b 100644 --- a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml +++ b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml @@ -453,6 +453,7 @@ + @@ -475,6 +476,19 @@ Background="Transparent"> + diff --git a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml.cs b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml.cs index ab98ec3..5dcd6c7 100644 --- a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml.cs +++ b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml.cs @@ -274,5 +274,15 @@ protected override void OnNavigatingFrom(NavigatingCancelEventArgs e) base.OnNavigatingFrom(e); } + + /// + /// Hide the subtitle audio track selector flyout when an item inside of it is clicked. + /// + /// + /// + private void SubtitleAudioTrackSelector_ItemClicked(object sender, EventArgs e) + { + AudioSubtitleSelectorFlyout.Hide(); + } } }