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..b4f2737 --- /dev/null +++ b/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs @@ -0,0 +1,57 @@ +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(); + + + /// + /// Get the preferred audio track. When no preferred audio 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 audio track or default + /// + TrackDescription GetPreferredAudioTrack(); + + /// + /// Get the embedded audio 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[] GetEmbeddedAudioTracks(); + } +} 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..5a1799f --- /dev/null +++ b/OneDrive-Cloud-Player/Services/MediaTrackService.cs @@ -0,0 +1,110 @@ +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 <= 1) + { + 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; + } + + public TrackDescription GetPreferredAudioTrack() + { + CheckInitializationState(); + TrackDescription[] audioTracks = _mediaPlayer.AudioTrackDescription; + + if (_mediaPlayer.AudioTrackCount <= 1) + { + return default; + } + + // Return the first actual audio track. + return _mediaPlayer.AudioTrackDescription.ElementAt(1); + } + + public TrackDescription[] GetEmbeddedAudioTracks() + { + CheckInitializationState(); + return _mediaPlayer.AudioTrackDescription; + } + } +} diff --git a/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs b/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs index 8a823e2..673117f 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,71 @@ 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(); + } + } + + private TrackDescription _selectedAudioTrack; + + public TrackDescription SelectedAudioTrack + { + get { return _selectedAudioTrack; } + set + { + _selectedAudioTrack = value; + SetAudioTrackTrackById(value.Id); + OnPropertyChanged(); + } + } + + private ObservableCollection _audioTracks = new ObservableCollection(); + + public ObservableCollection AudioTracks + { + get { return _audioTracks; } + set + { + _audioTracks = 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 +310,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 +356,88 @@ 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; + TrackDescription[] audioTracks = null; + TrackDescription selectedAudioTrack = default; - PlayPauseButtonFontIcon = "\xE769"; + await Task.Run(() => + { + subtitleTracks = _mediaTrackService.GetEmbeddedSubtitleTracks(); + selectedSubtitleTrack = _mediaTrackService.GetPreferredSubtitleTrack(); + audioTracks = _mediaTrackService.GetEmbeddedAudioTracks(); + selectedAudioTrack = _mediaTrackService.GetPreferredAudioTrack(); + + }); + + if (_isFirstPlaying) + { + _isFirstPlaying = false; + } - //Enable or disable default subtitle based on user setting. - if (!(bool)App.Current.UserSettings.Values["ShowDefaultSubtitles"]) + if (_isReloading) { - MediaPlayer.SetSpu(-1); + _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); + } + + // Check if there is a selected audio track, for re-selection, to begin with. + if (_selectedAudioTrack.Id != 0 && _selectedAudioTrack.Name != null) + { + // Retrieve the same selected audio track again as the one that was used with the former audio tracks collection. + selectedAudioTrack = audioTracks.FirstOrDefault(audioTrack => audioTrack.Id == _selectedAudioTrack.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(); + AudioTracks.Clear(); + foreach (TrackDescription subtitleTrack in subtitleTracks) + { + SubtitleTracks.Add(subtitleTrack); + } + + foreach (TrackDescription audioTrack in audioTracks) + { + AudioTracks.Add(audioTrack); + } + + // Select the correct track. + SelectedSubtitleTrack = selectedSubtitleTrack; + SelectedAudioTrack = selectedAudioTrack; + }); + } + + await App.Current.UIDispatcher.RunAsync(CoreDispatcherPriority.High, () => + { + //Sets the max value of the seekbar. + VideoLength = _mediaPlayer.Length; + + PlayPauseButtonFontIcon = "\xE769"; }); } @@ -337,16 +457,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 +533,45 @@ 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); + } + + /// + /// Set the subtitle track used in the by id. + /// + /// Subtitle track id + private void SetAudioTrackTrackById(int audioTrackId) + { + _mediaPlayer.SetAudioTrack(audioTrackId); } 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 +629,7 @@ private void Seeked() /// public void SeekBackward(double ms) { - SetVideoTime(MediaPlayer.Time - ms); + SetVideoTime(_mediaPlayer.Time - ms); } /// @@ -492,7 +638,7 @@ public void SeekBackward(double ms) /// public void SeekForward(double ms) { - SetVideoTime(MediaPlayer.Time + ms); + SetVideoTime(_mediaPlayer.Time + ms); } /// @@ -507,7 +653,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 +662,7 @@ private void SetVideoTime(double time) /// private async void ReloadCurrentMedia() { + _isReloading = true; await PlayMedia(TimeLineValue); InvalidOneDriveSession = false; } @@ -531,7 +678,7 @@ private void ChangePlayingState() public void StopMedia() { - MediaPlayer.Stop(); + _mediaPlayer.Stop(); TimeLineValue = 0; Dispose(); // Go back to the last page. @@ -667,20 +814,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..87d2d5a --- /dev/null +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml @@ -0,0 +1,62 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs new file mode 100644 index 0000000..6b238be --- /dev/null +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs @@ -0,0 +1,221 @@ +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)); + } + } + + private bool _isAudioAvailable; + + public bool IsAudioAvailable + { + get { return _isAudioAvailable; } + set + { + _isAudioAvailable = value; + OnPropertyChanged(nameof(IsAudioAvailable)); + } + } + + public ObservableCollection AudioTracks + { + get { return (ObservableCollection)GetValue(AudioTracksProperty); } + set { SetValue(AudioTracksProperty, value); } + } + + // Using a DependencyProperty as the backing store for AudioTracks. + public static readonly DependencyProperty AudioTracksProperty = + DependencyProperty.Register("AudioTracks", typeof(ObservableCollection), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null, AudioTracksPropertyChangedCallback)); + + public TrackDescription SelectedAudioTrack + { + get { return (TrackDescription)GetValue(SelectedAudioTrackProperty); } + set { SetValue(SelectedAudioTrackProperty, value); } + } + + // Using a DependencyProperty as the backing store for SelectedAudioTrack. + public static readonly DependencyProperty SelectedAudioTrackProperty = + DependencyProperty.Register("SelectedAudioTrack", typeof(TrackDescription), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null)); + + public ObservableCollection SubtitleTracks + { + get { return (ObservableCollection)GetValue(SubtitleTracksProperty); } + set { SetValue(SubtitleTracksProperty, value); } + } + + // Using a DependencyProperty as the backing store for SubtitleTracks. + 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. + public static readonly DependencyProperty SelectedSubtitleTrackProperty = + DependencyProperty.Register("SelectedSubtitleTrack", typeof(TrackDescription), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null)); + + public SubtitleAudioTrackSelector() + { + InitializeComponent(); + } + + /// + /// Callback for AudioTracksProperty property changes. + /// + /// + /// + private static void AudioTracksPropertyChangedCallback(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs eventArgs) + { + SubtitleAudioTrackSelector subtitleAudioTrackSelector = (SubtitleAudioTrackSelector)dependencyObject; + if (((ObservableCollection)eventArgs.NewValue).Count > 0) + { + subtitleAudioTrackSelector.IsAudioAvailable = true; + } + + if (eventArgs.NewValue is INotifyCollectionChanged newCollection) + { + // Subscribe to changes inside the new collection. + newCollection.CollectionChanged += subtitleAudioTrackSelector.AudioTracks_CollectionChanged; + } + + if (eventArgs.OldValue is INotifyCollectionChanged oldCollection) + { + // Unsubscribe to changes inside the old collection. + oldCollection.CollectionChanged -= subtitleAudioTrackSelector.AudioTracks_CollectionChanged; + } + } + + /// + /// Hnadling changes that occur inside the audio tracks collection (i.e. added or removed). + /// + /// + /// + private void AudioTracks_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Check for Default value. + if (e.NewItems == null) + { + IsAudioAvailable = false; + return; + } + + // Check if there are subtitles. + if (e.NewItems.Count <= 0) + { + IsAudioAvailable = false; + return; + } + + IsAudioAvailable = true; + } + + /// + /// 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); + } + + /// + /// Invoke the custom event when an audio item in the listview is clicked. + /// + /// + /// + private void AudioTrackListView_ItemClick(object sender, ItemClickEventArgs e) + { + Debug.WriteLine("Audio 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..3292884 100644 --- a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml +++ b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml @@ -453,6 +453,7 @@ + @@ -475,6 +476,21 @@ 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(); + } } }