From 0cff65a4f1dd81b0800e7274046706f70747bfd5 Mon Sep 17 00:00:00 2001 From: CQ Chen Date: Tue, 21 Jul 2020 00:10:18 +0800 Subject: [PATCH] Make playback more stable --- AilianBT/Services/MusicManager.cs | 63 +++----- AilianBT/Services/MusicService.cs | 49 ++++-- AilianBT/ViewModels/MusicVM.cs | 13 +- AilianBT/ViewModels/PlayerViewModel.cs | 180 ++++++++------------- AilianBT/Views/Controls/PlayerView.xaml.cs | 11 +- AilianBT/Views/MusicView.xaml | 8 +- 6 files changed, 144 insertions(+), 180 deletions(-) diff --git a/AilianBT/Services/MusicManager.cs b/AilianBT/Services/MusicManager.cs index 4b0dea5..159702a 100644 --- a/AilianBT/Services/MusicManager.cs +++ b/AilianBT/Services/MusicManager.cs @@ -26,16 +26,11 @@ public class MusicManager public MusicManager() { _mediaPlaybackList = new MediaPlaybackList(); - _mediaPlaybackList.CurrentItemChanged += _mediaPlaybackListCurrentItemChanged; _mediaPlaybackList.ItemOpened += _mediaPlaybackListItemOpened; _mediaPlaybackList.ItemFailed += _mediaPlaybackListItemFailed; _mediaPlaybackList.AutoRepeatEnabled = false; _mediaPlaybackList.MaxPlayedItemsToKeepOpen = 3; - _mediaPlaybackList.Items.Add(_createPlaceholderItem()); - _mediaPlaybackList.Items.Add(_createPlaceholderItem()); - _mediaPlaybackList.Items.Add(_createPlaceholderItem()); - _mediaPlayer = new MediaPlayer(); _mediaPlayer.AutoPlay = false; _mediaPlayer.Source = _mediaPlaybackList; @@ -43,50 +38,19 @@ public MusicManager() _mediaPlayer.MediaEnded += _mediaPlayerMediaEnded; } - private void _mediaPlayerMediaEnded(MediaPlayer sender, object args) - { - MediaEnd?.Invoke(); - } - private void PlaybackSession_PositionChanged(MediaPlaybackSession sender, object args) { PositionChanged?.Invoke(_mediaPlayer.PlaybackSession.Position, _mediaPlayer.PlaybackSession.NaturalDuration); } public event Action MediaLoaded; + public event Action MediaEnd; public event Action MediaFailed; - public event Action MediaChanged; - public event Action MediaLoading; public event Action MediaCached; public event Action PositionChanged; - public event Action MediaEnd; #region MediaPlaybackList callbacks - private void _mediaPlaybackListCurrentItemChanged(MediaPlaybackList sender, CurrentMediaPlaybackItemChangedEventArgs args) - { - if(args.Reason== MediaPlaybackItemChangedReason.EndOfStream) - { - _mediaPlayer.Pause(); - MediaEnd?.Invoke(); - return; - } - - MusicModel newModel = null; - MusicModel oldModel= null; - if(args.NewItem!=null) - { - var newModelJson = args.NewItem.Source.CustomProperties["model"] as string; - newModel = JsonSerializer.Deserialize(newModelJson); - } - if (args.OldItem != null) - { - var oldModelJson = args.OldItem.Source.CustomProperties["model"] as string; - oldModel = JsonSerializer.Deserialize(oldModelJson); - } - MediaChanged?.Invoke(newModel, oldModel); - } - private void _mediaPlaybackListItemOpened(MediaPlaybackList sender, MediaPlaybackItemOpenedEventArgs args) { var modelJson = args.Item.Source.CustomProperties["model"] as string; @@ -95,15 +59,21 @@ private void _mediaPlaybackListItemOpened(MediaPlaybackList sender, MediaPlaybac MediaLoaded?.Invoke(model); } + private void _mediaPlayerMediaEnded(MediaPlayer sender, object args) + { + MediaEnd?.Invoke(null); + } + private void _mediaPlaybackListItemFailed(MediaPlaybackList sender, MediaPlaybackItemFailedEventArgs args) { + MusicModel model = null; if(args.Item.Source.CustomProperties.TryGetValue("model", out object value)) { var modelJson = value as string; - var model = JsonSerializer.Deserialize(modelJson); + model = JsonSerializer.Deserialize(modelJson); _logger.Error($"Play music failed: \n\t{modelJson}\n\t{args.Error.ErrorCode}", args.Error.ExtendedError); - MediaFailed?.Invoke(model); } + MediaFailed?.Invoke(model); } #endregion @@ -188,10 +158,17 @@ public async Task Seek(TimeSpan position) } private async Task _play(MusicModel model) { - var playbackItem = await _cacheMusic(model); - var playbackItemIndex = _mediaPlaybackList.Items.IndexOf(playbackItem); - _mediaPlaybackList.MoveTo((uint)playbackItemIndex); - _mediaPlayer.Play(); + try + { + var playbackItem = await _cacheMusic(model); + var playbackItemIndex = _mediaPlaybackList.Items.IndexOf(playbackItem); + _mediaPlaybackList.MoveTo((uint)playbackItemIndex); + _mediaPlayer.Play(); + } + catch + { + MediaFailed?.Invoke(model); + } } private int? _getIndex(MusicModel model) diff --git a/AilianBT/Services/MusicService.cs b/AilianBT/Services/MusicService.cs index ea71af8..1809012 100644 --- a/AilianBT/Services/MusicService.cs +++ b/AilianBT/Services/MusicService.cs @@ -14,6 +14,7 @@ using Windows.Media.Core; using Windows.Networking.BackgroundTransfer; using Windows.Storage; +using Windows.Storage.Streams; using Windows.UI.WindowManagement; namespace AilianBT.Services @@ -30,6 +31,7 @@ public class MusicService private StorageService _storageService; private BackgroundDownloader _backgroundDownloader; + private BackgroundTransferGroup _backgroundDownloaderGroup = BackgroundTransferGroup.CreateGroup(DOWNLOAD_GROUP_NAME); public MusicService(UtilityHelper utilityHelper,LogService logger, StorageService storageService) { @@ -127,10 +129,11 @@ public async Task CachePlayListAsync(IList playlist) } #endregion - + private const string DOWNLOAD_GROUP_NAME = "MusicCaching"; private void _initDownloader() { _backgroundDownloader = new BackgroundDownloader(); + _backgroundDownloader.TransferGroup = _backgroundDownloaderGroup; _backgroundDownloader.SetRequestHeader("Referer", KISSSUB_HOST); } @@ -149,21 +152,45 @@ public async Task RequestMusic(MusicModel model) } }; + IRandomAccessStreamReference ras = null; + DownloadOperation downloadOperation = null; + var downloadProgress = new Progress(); + downloadProgress.ProgressChanged += downloadProgressHandler; + var cacheFile = await _createMusicFile(model); if (cacheFile == null) { - _logger.Error($"Create local file for storing music failed"); - return null; + var operations = await BackgroundDownloader.GetCurrentDownloadsForTransferGroupAsync(_backgroundDownloaderGroup); + foreach (var item in operations) + { + if (item.ResultFile is StorageFile cachedFile) + { + if (cachedFile.Name == _utilityHelper.CreateMd5HashString(model.Title)) + { + downloadOperation = item; + break; + } + } + } + if (downloadOperation != null) + { + ras = downloadOperation.GetResultRandomAccessStreamReference(); + downloadOperation.AttachAsync().AsTask(downloadProgress); + } + else + { + _logger.Warning($"Don't find the downloading task for {model.Title}"); + return null; + } + } + else + { + downloadOperation = _backgroundDownloader.CreateDownload(model.Uri, cacheFile); + downloadOperation.IsRandomAccessRequired = true; + ras = downloadOperation.GetResultRandomAccessStreamReference(); + downloadOperation.StartAsync().AsTask(downloadProgress); } - var downloadProgress = new Progress(); - downloadProgress.ProgressChanged += downloadProgressHandler; - - var downloadOperation = _backgroundDownloader.CreateDownload(model.Uri, cacheFile); - downloadOperation.IsRandomAccessRequired = true; - var ras = downloadOperation.GetResultRandomAccessStreamReference(); - - downloadOperation.StartAsync().AsTask(downloadProgress); var source = MediaSource.CreateFromStreamReference(ras, "audio/mpeg"); return source; } diff --git a/AilianBT/ViewModels/MusicVM.cs b/AilianBT/ViewModels/MusicVM.cs index 529675a..f981605 100644 --- a/AilianBT/ViewModels/MusicVM.cs +++ b/AilianBT/ViewModels/MusicVM.cs @@ -31,8 +31,8 @@ public ObservableCollection MusicList public async void Load() { - PlayerVM.SetMusicList(MusicList); await _preparePlaylist(); + PlayerVM.SetMusicList(MusicList); await _musicService.CheckCachedMusicAsync(MusicList, SynchronizationContext); } @@ -82,7 +82,6 @@ private async Task _preparePlaylist() { MusicList.Add(item); } - PlayerVM.CurrentIndex = 0; } catch (Exception e) { @@ -90,18 +89,12 @@ private async Task _preparePlaylist() _logger.Error($"Initilize the playlist failed", e); return; } - PlayerVM.CanPreviou = false; - PlayerVM.CanNext = true; } - public async void ItemClicked(MusicModel model) + public void ItemClicked(MusicModel model) { - //IsLoading = true; - - //_musicManager.Pause(true); - //await _musicManager.Play(model); - //Title = model.Title; + PlayerVM.PlayClicked(); } } } diff --git a/AilianBT/ViewModels/PlayerViewModel.cs b/AilianBT/ViewModels/PlayerViewModel.cs index 8c2109a..0bd2b44 100644 --- a/AilianBT/ViewModels/PlayerViewModel.cs +++ b/AilianBT/ViewModels/PlayerViewModel.cs @@ -6,6 +6,7 @@ using System.Threading; using System.Linq; using System; +using System.Threading.Tasks; namespace AilianBT.ViewModels { @@ -18,45 +19,31 @@ public class PlayerViewModel : ViewModelBase public PlayerViewModel() { _musicManager.PositionChanged += _playbackPositionChanged; - + _musicManager.MediaLoaded += _musicManagerMediaLoaded; _musicManager.MediaEnd += _musicManagerMediaEnd; + _musicManager.MediaFailed += _musicManagerMediaFailed; } - private void _musicManagerMediaEnd() + public void SetMusicList(Collection musicList) { - SynchronizationContext.Post((o) => - { - NextClicked(); - }, null); - } + _musicList = musicList; - private void _playbackPositionChanged(TimeSpan position, TimeSpan length) - { - if (!Seeking) + if (CurrentIndex == -1 && _musicList.Count > 0) { - SynchronizationContext.Post((o) => - { - CurrentMusic.Position = position; - CurrentMusic.Length = length; - - if (length != TimeSpan.Zero) - { - PlaybackProgress = (100d) * position / length; - } - }, null); + CurrentIndex = 0; } - } - public void SetMusicList(Collection musicList) - { - _musicList = musicList; + if (CurrentIndex != -1) + { + CurrentMusic = _musicList[CurrentIndex]; + } } - private bool _isLoading = false; - public bool IsLoading + private bool _seeking = false; + public bool Seeking { - get { return _isLoading; } - set { Set(ref _isLoading, value); } + get { return _seeking; } + set { Set(ref _seeking, value); } } private int _currentIndex = -1; @@ -86,6 +73,14 @@ public int CurrentIndex } } + #region Player UI related props + private bool _isLoading = false; + public bool IsLoading + { + get { return _isLoading; } + set { Set(ref _isLoading, value); } + } + private bool _canPreviou; public bool CanPreviou { @@ -93,6 +88,13 @@ public bool CanPreviou set { Set(ref _canPreviou, value); } } + private PlayerStatus _status = PlayerStatus.Stopped; + public PlayerStatus Status + { + get { return _status; } + set { Set(ref _status, value); } + } + private bool _canNext; public bool CanNext { @@ -100,13 +102,6 @@ public bool CanNext set { Set(ref _canNext, value); } } - private PlayerStatus _status = PlayerStatus.Stopped; - public PlayerStatus Status - { - get { return _status; } - set { Set(ref _status, value); } - } - private MusicModel _currentMusic; public MusicModel CurrentMusic { @@ -120,16 +115,12 @@ public double PlaybackProgress get { return _playbackProgress; } set { Set(ref _playbackProgress, value); } } - - private bool _seeking = false; - public bool Seeking - { - get { return _seeking; } - set { Set(ref _seeking, value); } - } + #endregion public async void PlayClicked() { + if (_musicList.Count <= 0 || CurrentIndex == -1) + return; if (Status == PlayerStatus.Paused) { Status = PlayerStatus.Playing; @@ -137,11 +128,7 @@ public async void PlayClicked() } else { - IsLoading = true; - await _musicManager.Play(_musicList[CurrentIndex]); - CurrentMusic = _musicList[CurrentIndex]; - IsLoading = false; - Status = PlayerStatus.Playing; + await _play(); } } @@ -153,6 +140,8 @@ public void PauseClicked() public async void PreviousClicked() { + if (_musicList.Count <= 0) + return; if (CurrentIndex <= 0) { return; @@ -160,17 +149,14 @@ public async void PreviousClicked() if (CurrentIndex - 1 >= 0) { CurrentIndex--; - Status = PlayerStatus.Stopped; - IsLoading = true; - await _musicManager.Next(_musicList[CurrentIndex]); - CurrentMusic = _musicList[CurrentIndex]; - IsLoading = false; - Status = PlayerStatus.Playing; + await _play(); } } public async void NextClicked() { + if (_musicList.Count <= 0) + return; if (CurrentIndex >= _musicList.Count - 1) { return; @@ -178,93 +164,69 @@ public async void NextClicked() if (CurrentIndex + 1 <= _musicList.Count - 1) { CurrentIndex++; - Status = PlayerStatus.Stopped; - IsLoading = true; - await _musicManager.Next(_musicList[CurrentIndex]); - CurrentMusic = _musicList[CurrentIndex]; - IsLoading = false; - Status = PlayerStatus.Playing; + await _play(); } } + private async Task _play() + { + PlaybackProgress = 0; + Status = PlayerStatus.Stopped; + IsLoading = true; + await _musicManager.Next(_musicList[CurrentIndex]); + CurrentMusic = _musicList[CurrentIndex]; + Status = PlayerStatus.Playing; + } + public async void Seek(double targetProgress) { var targetPosition = targetProgress * CurrentMusic.Length; await _musicManager.Seek(targetPosition); } + #region Manager events - private void MusicManager_MediaEnd() + + private void _musicManagerMediaLoaded(MusicModel model) { SynchronizationContext.Post((o) => { - NextClicked(); + IsLoading = false; }, null); } - private void MusicManager_MediaCached(MusicModel model) + private void _musicManagerMediaFailed(MusicModel model) { SynchronizationContext.Post((o) => { - var matchedModel = _musicList.Where(m => m.Equals(model)).FirstOrDefault(); - matchedModel.HasCached = true; - //if (model.Equals(_musicList[CurrentIndex])) - //{ - // //IsLoading = false; - //} + IsLoading = false; + NextClicked(); }, null); } - - private void MusicManager_MediaLoading(MusicModel model) + + private void _musicManagerMediaEnd(MusicModel model) { SynchronizationContext.Post((o) => { - if (model.Equals(_musicList[CurrentIndex])) - { - IsLoading = true; - } + NextClicked(); }, null); - } - private void MusicManager_MediaChanged(MusicModel newModel, MusicModel oldModel) + private void _playbackPositionChanged(TimeSpan position, TimeSpan length) { - SynchronizationContext.Post((o) => + if (!Seeking) { - - if (newModel != null) + SynchronizationContext.Post((o) => { - if (newModel.Equals(_musicList[CurrentIndex])) + CurrentMusic.Position = position; + CurrentMusic.Length = length; + + if (length != TimeSpan.Zero) { - IsLoading = false; + PlaybackProgress = (100d) * position / length; } - } - else - { - } - }, null); - } - - private void MusicManager_MediaLoaded(MusicModel model) - { - SynchronizationContext.Post((o) => - { - var matchedModel = _musicList.Where(m => m.Equals(model)).FirstOrDefault(); - matchedModel.HasCached = true; - //if (model.Equals(_musicList[CurrentIndex])) - //{ - // //IsLoading = false; - //} - }, null); - } - - private void MusicManager_MediaFailed(MusicModel model) - { - SynchronizationContext.Post((o) => - { - IsLoading = false; - }, null); + }, null); + } } #endregion } -} - +} \ No newline at end of file diff --git a/AilianBT/Views/Controls/PlayerView.xaml.cs b/AilianBT/Views/Controls/PlayerView.xaml.cs index a2ab60a..251e5e8 100644 --- a/AilianBT/Views/Controls/PlayerView.xaml.cs +++ b/AilianBT/Views/Controls/PlayerView.xaml.cs @@ -112,22 +112,23 @@ private void _gridPlayerTitleSizeChanged(object sender, SizeChangedEventArgs e) _panelSizeChangedTimer.Change(TimeSpan.FromMilliseconds(400), TimeSpan.Zero); } + #region Interact with Player Progress private void _initPlaybackProgress() { - sliderProgress.ManipulationMode = ManipulationModes.TranslateX; - sliderProgress.ManipulationCompleted += SliderProgress_ManipulationCompleted; - sliderProgress.ManipulationStarted += SliderProgress_ManipulationStarted; + sliderProgress.AddHandler(PointerPressedEvent, new PointerEventHandler(_sliderProgressPointerPressed), true); + sliderProgress.AddHandler(PointerReleasedEvent, new PointerEventHandler(_sliderProgressPointerReleased), true); } - private void SliderProgress_ManipulationStarted(object sender, ManipulationStartedRoutedEventArgs e) + private void _sliderProgressPointerPressed(object sender, PointerRoutedEventArgs e) { _playerVM.Seeking = true; } - private void SliderProgress_ManipulationCompleted(object sender, ManipulationCompletedRoutedEventArgs e) + private void _sliderProgressPointerReleased(object sender, PointerRoutedEventArgs e) { _playerVM.Seek(sliderProgress.Value / (sliderProgress.Maximum - sliderProgress.Minimum)); _playerVM.Seeking = false; } + #endregion } } diff --git a/AilianBT/Views/MusicView.xaml b/AilianBT/Views/MusicView.xaml index 53429b4..8f01f93 100644 --- a/AilianBT/Views/MusicView.xaml +++ b/AilianBT/Views/MusicView.xaml @@ -56,7 +56,9 @@ Margin="4" VerticalAlignment="Center" FontFamily="{StaticResource FontAwesome}" - FontSize="20" + FontSize="16" + FontStyle="Normal" + FontWeight="ExtraLight" Foreground="{ThemeResource AilianBtTitleUnselectForeground}" Text="" Visibility="{Binding Path=Visibility, ElementName=tbIconCached, Converter={StaticResource VisibilityInverseConverter}}" /> @@ -66,7 +68,9 @@ Margin="4" VerticalAlignment="Center" FontFamily="{StaticResource FontAwesome}" - FontSize="20" + FontSize="16" + FontStyle="Normal" + FontWeight="ExtraLight" Foreground="{ThemeResource AilianBtMainColor}" Text="" Visibility="{x:Bind HasCached, Mode=OneWay}" />