diff --git a/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs b/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs index 0c27a88..b4f2737 100644 --- a/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs +++ b/OneDrive-Cloud-Player/Models/Interfaces/IMediaTrackService.cs @@ -33,5 +33,25 @@ public interface IMediaTrackService /// /// 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/Services/MediaTrackService.cs b/OneDrive-Cloud-Player/Services/MediaTrackService.cs index bc51d86..5a1799f 100644 --- a/OneDrive-Cloud-Player/Services/MediaTrackService.cs +++ b/OneDrive-Cloud-Player/Services/MediaTrackService.cs @@ -60,7 +60,7 @@ public TrackDescription GetPreferredSubtitleTrack() CheckInitializationState(); TrackDescription[] subtitleTracks = _mediaPlayer.SpuDescription; - if (_mediaPlayer.SpuCount <= 0) + if (_mediaPlayer.SpuCount <= 1) { return default; } @@ -86,5 +86,25 @@ 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 3ce1eb1..2f17c59 100644 --- a/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs +++ b/OneDrive-Cloud-Player/ViewModels/VideoPlayerPageViewModel.cs @@ -237,6 +237,31 @@ public ObservableCollection SubtitleTracks } } + 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 /// @@ -350,11 +375,16 @@ private async void MediaPlayer_Playing(object sender, EventArgs e) { TrackDescription[] subtitleTracks = null; TrackDescription selectedSubtitleTrack = default; + TrackDescription[] audioTracks = null; + TrackDescription selectedAudioTrack = default; await Task.Run(() => { subtitleTracks = _mediaTrackService.GetEmbeddedSubtitleTracks(); selectedSubtitleTrack = _mediaTrackService.GetPreferredSubtitleTrack(); + audioTracks = _mediaTrackService.GetEmbeddedAudioTracks(); + selectedAudioTrack = _mediaTrackService.GetPreferredAudioTrack(); + }); if (_isFirstPlaying) @@ -372,19 +402,33 @@ await Task.Run(() => // 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); } - // Select the correct subtitle track. + foreach (TrackDescription audioTrack in audioTracks) + { + AudioTracks.Add(audioTrack); + } + + // Select the correct track. SelectedSubtitleTrack = selectedSubtitleTrack; + SelectedAudioTrack = selectedAudioTrack; }); } @@ -507,6 +551,15 @@ 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) diff --git a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml index 3cfbb05..87d2d5a 100644 --- a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml @@ -42,8 +42,21 @@ - + Visibility="{x:Bind Path=IsAudioAvailable, Converter={StaticResource InvertBoolToVisibilityConverter}, Mode=OneWay}"> + + + + + + + + + diff --git a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs index 825f9f4..fcb23d4 100644 --- a/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs +++ b/OneDrive-Cloud-Player/Views/SubtitleAudioTrackSelector.xaml.cs @@ -24,6 +24,38 @@ public bool 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. This enables animation, styling, binding, etc... + 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. This enables animation, styling, binding, etc... + public static readonly DependencyProperty SelectedAudioTrackProperty = + DependencyProperty.Register("SelectedAudioTrack", typeof(TrackDescription), typeof(SubtitleAudioTrackSelector), new PropertyMetadata(null)); + public ObservableCollection SubtitleTracks { get { return (ObservableCollection)GetValue(SubtitleTracksProperty); } @@ -49,6 +81,56 @@ 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. /// @@ -110,6 +192,17 @@ private void SubtitleTrackListView_ItemClick(object sender, ItemClickEventArgs e 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. /// diff --git a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml index c41ab1b..3292884 100644 --- a/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml +++ b/OneDrive-Cloud-Player/Views/VideoPlayerPage.xaml @@ -484,7 +484,9 @@ + ItemClicked="SubtitleAudioTrackSelector_ItemClicked" + SelectedAudioTrack="{Binding SelectedAudioTrack, Mode=TwoWay}" + AudioTracks="{Binding AudioTracks, Mode=OneWay}">