From efbf3dbda0b08387c071d920b63328bd34a380b3 Mon Sep 17 00:00:00 2001 From: LanZhan-Harmony Date: Fri, 19 Sep 2025 19:41:20 +0800 Subject: [PATCH] =?UTF-8?q?=E9=87=8D=E6=9E=84SMTC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- The Untamed Music Player/App.xaml.cs | 13 +- .../Services/IMaterialSelectorService.cs | 9 +- .../Models/MusicPlayer.cs | 1220 +++++++---------- .../Models/OnlineMusicLibrary.cs | 4 +- .../Services/MaterialSelectorService.cs | 197 ++- .../SystemMediaTransportControlsManager.cs | 230 ++++ .../ViewModels/HomeViewModel.cs | 6 +- .../ViewModels/LocalAlbumDetailViewModel.cs | 6 +- .../ViewModels/LocalAlbumsViewModel.cs | 8 +- .../ViewModels/LocalArtistDetailViewModel.cs | 4 +- .../ViewModels/LocalArtistsViewModel.cs | 4 +- .../ViewModels/LocalSongsViewModel.cs | 14 +- .../ViewModels/LyricViewModel.cs | 12 +- .../ViewModels/MusicLibraryViewModel.cs | 2 +- .../ViewModels/OnlineAlbumDetailViewModel.cs | 6 +- .../ViewModels/OnlineArtistDetailViewModel.cs | 4 +- .../OnlinePlayListDetailViewModel.cs | 4 +- .../ViewModels/OnlineSongsViewModel.cs | 2 +- .../ViewModels/PlayListDetailViewModel.cs | 10 +- .../ViewModels/PlayListsViewModel.cs | 4 +- .../ViewModels/PlayQueueViewModel.cs | 10 +- .../ViewModels/RootPlayBarViewModel.cs | 4 +- .../ViewModels/SettingsViewModel.cs | 57 +- .../ViewModels/ShellViewModel.cs | 6 +- .../Views/OnlineAlbumDetailPage.xaml.cs | 4 +- .../Views/SettingsPage.xaml | 4 +- 26 files changed, 983 insertions(+), 861 deletions(-) create mode 100644 The Untamed Music Player/Services/SystemMediaTransportControlsManager.cs diff --git a/The Untamed Music Player/App.xaml.cs b/The Untamed Music Player/App.xaml.cs index 73befb2..ef8c9a9 100644 --- a/The Untamed Music Player/App.xaml.cs +++ b/The Untamed Music Player/App.xaml.cs @@ -9,6 +9,7 @@ using The_Untamed_Music_Player.Models; using The_Untamed_Music_Player.Services; using The_Untamed_Music_Player.ViewModels; using WinUIEx; +using ZLogger; namespace The_Untamed_Music_Player; @@ -135,19 +136,15 @@ public partial class App : Application _logger.UnexpectedException(errorMessage, exception); // 记录堆栈跟踪和内部异常 - _logger.LogError( + _logger.ZLogInformation( exception, - "异常详细信息: {ExceptionType}, 堆栈跟踪: {StackTrace}", - exception.GetType().Name, - exception.StackTrace + $"异常详细信息: {exception.GetType().Name}, 堆栈跟踪: {exception.StackTrace}" ); - if (exception.InnerException is not null) { - _logger.LogError( + _logger.ZLogInformation( exception.InnerException, - "内部异常: {InnerExceptionMessage}", - exception.InnerException.Message + $"内部异常: {exception.InnerException.Message}" ); } e.Handled = true; diff --git a/The Untamed Music Player/Contracts/Services/IMaterialSelectorService.cs b/The Untamed Music Player/Contracts/Services/IMaterialSelectorService.cs index 031582f..31c204f 100644 --- a/The Untamed Music Player/Contracts/Services/IMaterialSelectorService.cs +++ b/The Untamed Music Player/Contracts/Services/IMaterialSelectorService.cs @@ -4,10 +4,10 @@ namespace The_Untamed_Music_Player.Contracts.Services; public interface IMaterialSelectorService : IDisposable { - MaterialType Material { get; } - bool IsFallBack { get; } - byte LuminosityOpacity { get; } - Color TintColor { get; } + MaterialType Material { get; set; } + bool IsFallBack { get; set; } + byte LuminosityOpacity { get; set; } + Color TintColor { get; set; } void InitializeSettings(); Task InitializeMaterialAsync(); Task<(byte, Color)> SetMaterial( @@ -15,7 +15,6 @@ public interface IMaterialSelectorService : IDisposable bool firstStart = false, bool forced = false ); - void SetIsFallBack(bool isFallBack); void SetLuminosityOpacity(byte opacity, bool firstStart = false); void SetTintColor(Color color, bool firstStart = false); } diff --git a/The Untamed Music Player/Models/MusicPlayer.cs b/The Untamed Music Player/Models/MusicPlayer.cs index 8f89d69..072d6b5 100644 --- a/The Untamed Music Player/Models/MusicPlayer.cs +++ b/The Untamed Music Player/Models/MusicPlayer.cs @@ -1,6 +1,5 @@ using System.Collections.ObjectModel; -using System.Diagnostics; -using System.Runtime.InteropServices.WindowsRuntime; +using System.Threading.Tasks; using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; using ManagedBass; @@ -18,7 +17,6 @@ using The_Untamed_Music_Player.Messages; using The_Untamed_Music_Player.OnlineAPIs.CloudMusicAPI; using The_Untamed_Music_Player.Services; using Windows.Media; -using Windows.Storage.Streams; using Windows.System; using Windows.System.Threading; using ZLinq; @@ -37,29 +35,9 @@ public partial class MusicPlayer private static readonly ILogger _logger = LoggingService.CreateLogger(); /// - /// 用于获取SMTC的临时播放器 + /// SMTC管理器 /// - private readonly Windows.Media.Playback.MediaPlayer _tempPlayer = new(); - - /// - /// 用于SMTC显示封面图片的流 - /// - private static InMemoryRandomAccessStream? _currentCoverStream = null!; - - /// - /// SMTC控件 - /// - private SystemMediaTransportControls _systemControls = null!; - - /// - /// SMTC显示内容更新器 - /// - private SystemMediaTransportControlsDisplayUpdater _displayUpdater = null!; - - /// - /// SMTC时间线属性 - /// - private readonly SystemMediaTransportControlsTimelineProperties _timelineProperties = new(); + private readonly SystemMediaTransportControlsManager _smtcManager = new(); /// /// 线程锁开启状态, true为开启, false为关闭 @@ -141,59 +119,53 @@ public partial class MusicPlayer public partial ObservableCollection PlayQueue { get; set; } = []; /// - /// 隨機播放隊列集合 + /// 随机播放队列集合 /// [ObservableProperty] public partial ObservableCollection ShuffledPlayQueue { get; set; } = []; /// - /// 隨機播放模式, true為開啟, false為關閉. + /// 随机播放模式, true为开启, false为关闭. /// [ObservableProperty] public partial bool ShuffleMode { get; set; } = false; /// - /// 播放隊列名 + /// 播放队列名 /// [ObservableProperty] public partial string PlayQueueName { get; set; } = ""; /// - /// 當前歌曲在播放隊列中的索引 + /// 当前歌曲在播放队列中的索引 /// [ObservableProperty] public partial int PlayQueueIndex { get; set; } partial void OnPlayQueueIndexChanged(int value) { - var repeatOffOrSingle = RepeatMode == 0 || RepeatMode == 2; - _systemControls.IsPreviousEnabled = !(value == 0 && repeatOffOrSingle); - _systemControls.IsNextEnabled = !(value == _playQueueLength - 1 && repeatOffOrSingle); + _smtcManager.UpdatePlayQueueInfo(value, _playQueueLength, RepeatMode); } /// - /// 循環播放模式, 0為不循環, 1為列表循環, 2為單曲循環 + /// 循环播放模式, 0为不循环, 1为列表循环, 2为单曲循环 /// [ObservableProperty] public partial byte RepeatMode { get; set; } = 0; partial void OnRepeatModeChanged(byte value) { - var isFirstSong = PlayQueueIndex == 0; - var isLastSong = PlayQueueIndex == _playQueueLength - 1; - var isRepeatOffOrSingle = value == 0 || value == 2; - _systemControls.IsPreviousEnabled = !(isFirstSong && isRepeatOffOrSingle); - _systemControls.IsNextEnabled = !(isLastSong && isRepeatOffOrSingle); + _smtcManager.UpdatePlayQueueInfo(PlayQueueIndex, _playQueueLength, value); } /// - /// 播放狀態, 0為暫停, 1為播放, 2為加載中 + /// 播放状态, 0为暂停, 1为播放, 2为加载中 /// [ObservableProperty] public partial byte PlayState { get; set; } = 0; /// - /// 當前播放的歌曲簡要版 + /// 当前播放的歌曲简要版 /// [ObservableProperty] public partial IBriefSongInfoBase? CurrentBriefSong { get; set; } @@ -204,31 +176,31 @@ public partial class MusicPlayer } /// - /// 當前播放歌曲 + /// 当前播放歌曲 /// [ObservableProperty] public partial IDetailedSongInfoBase? CurrentSong { get; set; } /// - /// 當前播放時間 + /// 当前播放时间 /// [ObservableProperty] public partial TimeSpan CurrentPlayingTime { get; set; } = TimeSpan.Zero; /// - /// 當前歌曲總時長 + /// 当前歌曲总时长 /// [ObservableProperty] public partial TimeSpan TotalPlayingTime { get; set; } = TimeSpan.Zero; /// - /// 當前播放進度(百分比) + /// 当前播放进度(百分比) /// [ObservableProperty] public partial double CurrentPosition { get; set; } = 0; /// - /// 當前音量 + /// 当前音量 /// [ObservableProperty] public partial double CurrentVolume { get; set; } = 100; @@ -257,11 +229,6 @@ public partial class MusicPlayer /// public bool IsExclusiveMode { get; set; } = false; - /// - /// 当前音频源文件路径 - /// - private string? _currentSourcePath = null; - /// /// 当前歌词内容 /// @@ -279,10 +246,18 @@ public partial class MusicPlayer { Messenger.Register(this); InitializeBass(); - InitializeSystemMediaTransportControls(); + InitializeSmtc(); LoadCurrentStateAsync(); } + public void Receive(FontSizeChangeMessage message) + { + foreach (var slice in CurrentLyric) + { + slice.UpdateStyle(); + } + } + /// /// 初始化Bass音频库 /// @@ -293,7 +268,6 @@ public partial class MusicPlayer _logger.ZLogInformation($"Bass初始化失败: {Bass.LastError}"); return; } - LoadBassPlugins(); // 加载Bass插件 // 设置同步回调 @@ -305,226 +279,41 @@ public partial class MusicPlayer } /// - /// WASAPI回调处理程序 + /// 初始化SMTC /// - private int WasapiProc(nint buffer, int length, nint user) + private void InitializeSmtc() { - if (_tempoStream == 0) // 确保流句柄有效 - { - return 0; - } - var bytesRead = Bass.ChannelGetData(_tempoStream, buffer, length); // 从解码流获取数据 - if (bytesRead == -1 || bytesRead == 0) // 如果读取失败或到达流末尾,返回0 - { - return 0; - } - return bytesRead; + _smtcManager.ButtonPressed += OnSmtcButtonPressed; } /// - /// 切换独占模式 + /// 处理SMTC按钮按下事件 /// - public bool SetExclusiveMode(bool isExclusiveMode) + private void OnSmtcButtonPressed(SystemMediaTransportControlsButton button) { - if (IsExclusiveMode == isExclusiveMode) // 如果模式没有改变,直接返回成功 + switch (button) { - return true; - } - var wasPlaying = PlayState == 1; - var currentTime = CurrentPlayingTime.TotalSeconds; - var prevMode = IsExclusiveMode; - try - { - if (wasPlaying) // 停止当前播放 - { - InternalStop(); - } - CleanupWasapi(); // 清理WASAPI状态 - IsExclusiveMode = isExclusiveMode; // 更新模式 - if (!string.IsNullOrEmpty(_currentSourcePath)) // 如果有当前音频源,重新设置 - { - CreateStreamFromPath(_currentSourcePath); - SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); - if (currentTime > 0) // 恢复播放位置 - { - SetPlaybackPositionInternal(currentTime); - } - if (wasPlaying) // 如果之前在播放,继续播放 - { - InternalPlay(); - } - } - return true; - } - catch - { - // 回退到之前的模式 - IsExclusiveMode = prevMode; - CleanupWasapi(); - - // 尝试恢复播放 - if (!string.IsNullOrEmpty(_currentSourcePath)) - { - try - { - CreateStreamFromPath(_currentSourcePath); - SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); - if (currentTime > 0) - { - SetPlaybackPositionInternal(currentTime); - } - if (wasPlaying) - { - InternalPlay(); - } - } - catch (Exception restoreEx) - { - _logger.ZLogInformation(restoreEx, $"恢复播放失败"); - } - } - return false; - } - } - - /// - /// 清理WASAPI资源 - /// - private static void CleanupWasapi() - { - try - { - if (BassWasapi.IsStarted) - { - BassWasapi.Stop(true); - } - BassWasapi.Free(); - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"清理WASAPI资源时出错"); - } - } - - /// - /// 初始化WASAPI独占模式 - /// - private bool InitializeWasapiExclusive() - { - try - { - if (_tempoStream == 0) // 确保有有效的流 - { - _logger.ZLogInformation($"没有有效的音频流,无法初始化WASAPI独占模式"); - return false; - } - - // 初始化WASAPI独占模式 - var result = BassWasapi.Init( - -1, - 0, - 0, - WasapiInitFlags.Exclusive | WasapiInitFlags.EventDriven, - 0.05f, - 0, - _wasapiProc, - IntPtr.Zero - ); - - if (!result) - { - var error = Bass.LastError; - _logger.ZLogInformation($"WASAPI独占模式初始化失败: {error}"); - - // 处理ALREADY错误,释放后重试 - if (error == Errors.Already) - { - CleanupWasapi(); - result = BassWasapi.Init( - -1, - 0, - 0, - WasapiInitFlags.Exclusive | WasapiInitFlags.EventDriven, - 0.05f, - 0, - _wasapiProc, - IntPtr.Zero - ); - - if (!result) - { - _logger.ZLogInformation($"WASAPI独占模式重试初始化失败: {Bass.LastError}"); - return false; - } - } - else - { - return false; - } - } - - _logger.ZLogInformation($"WASAPI独占模式初始化成功"); - return true; - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"初始化WASAPI独占模式时出现异常"); - return false; - } - } - - /// - /// 启动WASAPI独占模式播放 - /// - private bool StartWasapiExclusivePlayback() - { - try - { - if (!InitializeWasapiExclusive()) - { - return false; - } - - if (!BassWasapi.Start()) // 启动WASAPI - { - var error = Bass.LastError; - _logger.ZLogInformation($"WASAPI启动失败: {error}"); - CleanupWasapi(); - return false; - } - - _logger.ZLogInformation($"WASAPI独占模式播放启动成功"); - return true; - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"启动WASAPI独占模式播放时出现异常"); - CleanupWasapi(); - return false; - } - } - - /// - /// 停止WASAPI播放 - /// - private void StopWasapiPlayback() - { - try - { - if (BassWasapi.IsStarted) - { - BassWasapi.Stop(true); - } - - if (_wasapiDevice != -1) - { - BassWasapi.Free(); - _wasapiDevice = -1; - } - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"停止WASAPI播放时出错"); + case SystemMediaTransportControlsButton.Play: + case SystemMediaTransportControlsButton.Pause: + Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( + DispatcherQueuePriority.Low, + PlayPauseUpdate + ); + break; + case SystemMediaTransportControlsButton.Previous: + Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( + DispatcherQueuePriority.Low, + PlayPreviousSong + ); + break; + case SystemMediaTransportControlsButton.Next: + Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( + DispatcherQueuePriority.Low, + PlayNextSong + ); + break; + default: + break; } } @@ -554,29 +343,6 @@ public partial class MusicPlayer } } - /// - /// 初始化系统媒体传输控件 - /// - private void InitializeSystemMediaTransportControls() - { - _systemControls = _tempPlayer.SystemMediaTransportControls; - _displayUpdater = _systemControls.DisplayUpdater; - _displayUpdater.Type = MediaPlaybackType.Music; - _displayUpdater.AppMediaId = "AppDisplayName".GetLocalized(); - _systemControls.IsEnabled = true; - _systemControls.ButtonPressed += SystemControls_ButtonPressed; - _timelineProperties.StartTime = TimeSpan.Zero; - _timelineProperties.MinSeekTime = TimeSpan.Zero; - } - - public void Receive(FontSizeChangeMessage message) - { - foreach (var slice in CurrentLyric) - { - slice.UpdateStyle(); - } - } - /// /// Bass同步回调 - 播放结束 /// @@ -627,6 +393,9 @@ public partial class MusicPlayer }); } + /// + /// 处理歌曲不可用的情况 + /// private void HandleSongNotAvailable() { _logger.SongPlaybackError(CurrentSong!.Title); @@ -656,6 +425,415 @@ public partial class MusicPlayer }); } + /// + /// WASAPI回调处理程序 + /// + private int WasapiProc(nint buffer, int length, nint user) + { + if (_tempoStream == 0) + { + return 0; + } + var bytesRead = Bass.ChannelGetData(_tempoStream, buffer, length); + if (bytesRead == -1 || bytesRead == 0) + { + return 0; + } + return bytesRead; + } + + /// + /// 清理WASAPI资源 + /// + private static void CleanupWasapi() + { + try + { + if (BassWasapi.IsStarted) + { + BassWasapi.Stop(true); + } + BassWasapi.Free(); + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"清理WASAPI资源时出错"); + } + } + + /// + /// 初始化WASAPI独占模式 + /// + private bool InitializeWasapiExclusive() + { + try + { + if (_tempoStream == 0) + { + _logger.ZLogInformation($"没有有效的音频流,无法初始化WASAPI独占模式"); + return false; + } + + var result = BassWasapi.Init( + -1, + 0, + 0, + WasapiInitFlags.Exclusive | WasapiInitFlags.EventDriven, + 0.05f, + 0, + _wasapiProc, + nint.Zero + ); + + if (!result) + { + var error = Bass.LastError; + _logger.ZLogInformation($"WASAPI独占模式初始化失败: {error}"); + + if (error == Errors.Already) + { + CleanupWasapi(); + result = BassWasapi.Init( + -1, + 0, + 0, + WasapiInitFlags.Exclusive | WasapiInitFlags.EventDriven, + 0.05f, + 0, + _wasapiProc, + nint.Zero + ); + + if (!result) + { + _logger.ZLogInformation($"WASAPI独占模式重试初始化失败: {Bass.LastError}"); + return false; + } + } + else + { + return false; + } + } + + _logger.ZLogInformation($"WASAPI独占模式初始化成功"); + return true; + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"初始化WASAPI独占模式时出现异常"); + return false; + } + } + + /// + /// 启动WASAPI独占模式播放 + /// + private bool StartWasapiExclusivePlayback() + { + try + { + if (!InitializeWasapiExclusive()) + { + return false; + } + + if (!BassWasapi.Start()) + { + var error = Bass.LastError; + _logger.ZLogInformation($"WASAPI启动失败: {error}"); + CleanupWasapi(); + return false; + } + + _logger.ZLogInformation($"WASAPI独占模式播放启动成功"); + return true; + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"启动WASAPI独占模式播放时出现异常"); + CleanupWasapi(); + return false; + } + } + + /// + /// 停止WASAPI播放 + /// + private void StopWasapiPlayback() + { + try + { + if (BassWasapi.IsStarted) + { + BassWasapi.Stop(true); + } + if (_wasapiDevice != -1) + { + BassWasapi.Free(); + _wasapiDevice = -1; + } + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"停止WASAPI播放时出错"); + } + } + + /// + /// 切换独占模式 + /// + public bool SetExclusiveMode(bool isExclusiveMode) + { + if (IsExclusiveMode == isExclusiveMode) + { + return true; + } + var wasPlaying = PlayState == 1; + var currentTime = CurrentPlayingTime.TotalSeconds; + var prevMode = IsExclusiveMode; + try + { + if (wasPlaying) + { + InternalStop(); + } + CleanupWasapi(); + IsExclusiveMode = isExclusiveMode; + if (CurrentSong is not null) + { + CreateStreamFromPath(CurrentSong.Path); + SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); + if (wasPlaying) + { + InternalPlay(); + } + // 在播放启动后设置播放位置,确保在独占模式下位置设置生效 + if (currentTime > 0) + { + SetPlaybackPositionInternal(currentTime); + } + } + return true; + } + catch + { + IsExclusiveMode = prevMode; + CleanupWasapi(); + + if (CurrentSong is not null) + { + try + { + CreateStreamFromPath(CurrentSong.Path); + SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); + if (currentTime > 0) + { + SetPlaybackPositionInternal(currentTime); + } + if (wasPlaying) + { + InternalPlay(); + } + } + catch (Exception restoreEx) + { + _logger.ZLogInformation(restoreEx, $"恢复播放失败"); + } + } + return false; + } + } + + /// + /// 计时器更新事件 + /// + /// + private void UpdateTimerHandler250ms(ThreadPoolTimer timer) + { + try + { + if (_tempoStream == 0 || _lockable || PlayState != 1) + { + return; + } + + var positionBytes = Bass.ChannelGetPosition(_tempoStream); + var positionSeconds = Bass.ChannelBytes2Seconds(_tempoStream, positionBytes); + var playingTime = TimeSpan.FromSeconds(positionSeconds); + + App.MainWindow?.DispatcherQueue.TryEnqueue(() => + { + CurrentPlayingTime = playingTime; + if (TotalPlayingTime.TotalMilliseconds > 0) + { + CurrentPosition = + 100 * (playingTime.TotalMilliseconds / TotalPlayingTime.TotalMilliseconds); + } + }); + + var dispatcherQueue = + Data.LyricPage?.DispatcherQueue ?? Data.DesktopLyricWindow?.DispatcherQueue; + if (CurrentLyric.Count > 0) + { + dispatcherQueue?.TryEnqueue(() => UpdateCurrentLyricIndex(positionSeconds * 1000)); + } + + _smtcManager.UpdateTimelinePosition(CurrentPlayingTime); + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"计时器更新失败"); + } + } + + /// + /// 播放 + /// + public void Play() + { + PositionUpdateTimer250ms = ThreadPoolTimer.CreatePeriodicTimer( + UpdateTimerHandler250ms, + TimeSpan.FromMilliseconds(250) + ); + InternalPlay(); + PlayState = 1; + _smtcManager.UpdatePlaybackStatus(MediaPlaybackStatus.Playing); + } + + /// + /// 暂停 + /// + public void Pause() + { + InternalPause(); + PlayState = 0; + _smtcManager.UpdatePlaybackStatus(MediaPlaybackStatus.Paused); + PositionUpdateTimer250ms?.Cancel(); + PositionUpdateTimer250ms = null; + } + + /// + /// 停止 + /// + public void Stop() + { + InternalStop(); + PlayState = 0; + CurrentPlayingTime = TimeSpan.Zero; + CurrentPosition = 0; + _currentLyricIndex = 0; + CurrentLyricContent = ""; + PositionUpdateTimer250ms?.Cancel(); + PositionUpdateTimer250ms = null; + } + + /// + /// 内部播放实现 + /// + private void InternalPlay() + { + if (_tempoStream == 0) + { + return; + } + if (IsExclusiveMode) + { + if (!StartWasapiExclusivePlayback()) + { + _logger.ZLogInformation($"WASAPI独占模式失败,回退到共享模式"); + IsExclusiveMode = false; + + if (CurrentSong is not null) + { + CreateStreamFromPath(CurrentSong.Path); + SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); + } + Bass.ChannelPlay(_tempoStream, false); + } + } + else + { + if (!Bass.ChannelPlay(_tempoStream, false)) + { + var error = Bass.LastError; + _logger.ZLogInformation($"共享模式播放失败: {error}"); + if (error == Errors.Start) + { + if (Bass.Start()) + { + Bass.ChannelPlay(_tempoStream, false); + } + } + } + } + } + + /// + /// 内部暂停实现 + /// + private void InternalPause() + { + if (_tempoStream == 0) + { + return; + } + if (IsExclusiveMode) + { + if (BassWasapi.IsStarted) + { + BassWasapi.Stop(false); + } + } + else + { + Bass.ChannelPause(_tempoStream); + } + } + + /// + /// 内部停止实现 + /// + private void InternalStop() + { + if (_tempoStream != 0) + { + if (IsExclusiveMode) + { + CleanupWasapi(); + } + Bass.ChannelStop(_tempoStream); + } + } + + /// + /// 为播放器设置音乐源 + /// + /// + private async Task SetSource(string path) + { + try + { + Data.RootPlayBarViewModel?.ButtonVisibility = Visibility.Visible; + Data.RootPlayBarViewModel?.Availability = true; + CleanupWasapi(); + CreateStreamFromPath(path); + SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); + _smtcManager.UpdateMediaInfo( + CurrentSong!.Title, + CurrentSong.ArtistsStr, + TotalPlayingTime + ); + } + catch (Exception ex) + { + _logger.ZLogInformation(ex, $"SetSource失败"); + } + + await _smtcManager.SetCoverImageAsync(CurrentSong!); + _smtcManager.Update(); + } + /// /// 按路径播放歌曲 /// @@ -673,8 +851,7 @@ public partial class MusicPlayer { await SetSource(CurrentSong!.Path); _ = UpdateLyric(CurrentSong!.Lyric); - _systemControls.IsPlayEnabled = true; - _systemControls.IsPauseEnabled = true; + _smtcManager.SetButtonsEnabled(true, true, true, true); Play(); } else @@ -705,8 +882,7 @@ public partial class MusicPlayer { await SetSource(CurrentSong!.Path); _ = UpdateLyric(CurrentSong!.Lyric); - _systemControls.IsPlayEnabled = true; - _systemControls.IsPauseEnabled = true; + _smtcManager.SetButtonsEnabled(true, true, true, true); if (isLast) { PlayState = 0; @@ -732,7 +908,6 @@ public partial class MusicPlayer var insertIndex = PlayQueueIndex + 1; queue.Insert(insertIndex, new IndexedPlayQueueSong(insertIndex, info)); _playQueueLength++; - // 仅更新从插入位置之后的索引 for (var i = insertIndex + 1; i < queue.Count; i++) { queue[i].Index = i; @@ -757,7 +932,6 @@ public partial class MusicPlayer insertIndex++; } _playQueueLength += songs.AsValueEnumerable().Count(); - // 仅更新从插入位置之后的索引 for (var i = insertIndex; i < queue.Count; i++) { queue[i].Index = i; @@ -816,7 +990,7 @@ public partial class MusicPlayer var queue = ShuffleMode ? ShuffledPlayQueue : PlayQueue; var index = info.Index; int newIndex; - if (index == PlayQueueIndex) // 如果删除的歌曲正好是当前播放歌曲 + if (index == PlayQueueIndex) { var playState = PlayState; Stop(); @@ -831,8 +1005,7 @@ public partial class MusicPlayer { await SetSource(CurrentSong!.Path); _ = UpdateLyric(CurrentSong!.Lyric); - _systemControls.IsPauseEnabled = true; - _systemControls.IsPlayEnabled = true; + _smtcManager.SetButtonsEnabled(true, true, true, true); Play(); } else @@ -841,12 +1014,11 @@ public partial class MusicPlayer } } } - else if (index < PlayQueueIndex) // 删除歌曲在当前播放歌曲之前时,当前索引前移1位 + else if (index < PlayQueueIndex) { PlayQueueIndex--; } - // 从队列中移除歌曲并更新后续歌曲的索引 queue.RemoveAt(index); _playQueueLength--; for (var i = index; i < queue.Count; i++) @@ -911,7 +1083,6 @@ public partial class MusicPlayer actualIndex++; } _playQueueLength += songs.Count; - // 仅更新从插入位置之后的索引 for (var i = actualIndex; i < queue.Count; i++) { queue[i].Index = i; @@ -935,53 +1106,38 @@ public partial class MusicPlayer /// 文件路径 private void CreateStreamFromPath(string path) { - FreeCurrentStreams(); // 释放之前的流 - var flags = BassFlags.Unicode | BassFlags.Float | BassFlags.AsyncFile | BassFlags.Decode; - if (CurrentSong!.IsOnline) // 在线流 - { - _currentStream = Bass.CreateStream(path, 0, flags, null); - } - else // 本地文件 - { - _currentStream = Bass.CreateStream(path, 0, 0, flags); - } + FreeCurrentStreams(); + const BassFlags flags = + BassFlags.Unicode | BassFlags.Float | BassFlags.AsyncFile | BassFlags.Decode; + _currentStream = CurrentSong!.IsOnline + ? Bass.CreateStream(path, 0, flags, null) + : Bass.CreateStream(path, 0, 0, flags); if (_currentStream == 0) { var error = Bass.LastError; _logger.ZLogInformation($"创建Bass流失败: {error}, 文件: {path}"); } - if (IsExclusiveMode) - { - _tempoStream = BassFx.TempoCreate(_currentStream, BassFlags.Decode); // 使用BassFx创建Tempo流用于变速不变调 - } - else - { - _tempoStream = BassFx.TempoCreate(_currentStream, BassFlags.FxFreeSource); - } + _tempoStream = IsExclusiveMode + ? BassFx.TempoCreate(_currentStream, BassFlags.Decode) + : BassFx.TempoCreate(_currentStream, BassFlags.FxFreeSource); if (_tempoStream == 0) { var error = Bass.LastError; _logger.ZLogInformation($"创建Tempo流失败: {error}"); Bass.StreamFree(_currentStream); _currentStream = 0; - throw new InvalidOperationException($"创建Tempo流失败: {error}"); } - // 设置播放速度 SetPlaybackSpeed(PlaySpeed); - // 设置同步回调 Bass.ChannelSetSync(_tempoStream, SyncFlags.End, 0, _syncEndCallback); Bass.ChannelSetSync(_tempoStream, SyncFlags.Stalled, 0, _syncFailCallback); - // 获取歌曲时长 var lengthBytes = Bass.ChannelGetLength(_tempoStream); var lengthSeconds = Bass.ChannelBytes2Seconds(_tempoStream, lengthBytes); TotalPlayingTime = TimeSpan.FromSeconds(lengthSeconds); - - _logger.ZLogInformation($"成功创建音频流,时长: {TotalPlayingTime}"); } /// @@ -1002,137 +1158,25 @@ public partial class MusicPlayer } /// - /// 为播放器设置音乐源 + /// 更新循环模式 /// - /// - private async Task SetSource(string path) + public void RepeatModeUpdate(object _1, RoutedEventArgs _2) { - try - { - Data.RootPlayBarViewModel!.ButtonVisibility = Visibility.Visible; - Data.RootPlayBarViewModel!.Availability = true; - - // 停止WASAPI播放 - CleanupWasapi(); - - // 保存当前源路径 - _currentSourcePath = path; - - // 创建音频流 - CreateStreamFromPath(path); - - // 设置音量 - SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); - - _displayUpdater.MusicProperties.Title = CurrentSong!.Title; - _displayUpdater.MusicProperties.Artist = - CurrentSong.ArtistsStr == "SongInfo_UnknownArtist".GetLocalized() - ? "" - : CurrentSong.ArtistsStr; - _timelineProperties.MaxSeekTime = TotalPlayingTime; - _timelineProperties.EndTime = TotalPlayingTime; - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"SetSource失败"); - throw; - } - - // 设置封面图片 - await SetCoverImage(); - _displayUpdater.Update(); + RepeatMode = (byte)((RepeatMode + 1) % 3); } /// - /// 设置封面图片 + /// 静音按钮点击事件 /// - private async Task SetCoverImage() + public void MuteButton_Click(object _1, RoutedEventArgs _2) { - if (CurrentSong!.Cover is not null) - { - if (CurrentSong.IsOnline) - { - try - { - var info = (IDetailedOnlineSongInfo)CurrentSong; - _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( - new Uri(info.CoverPath!) - ); - } - catch { } - } - else - { - try - { - var info = (DetailedLocalSongInfo)CurrentSong; - _currentCoverStream?.Dispose(); - _currentCoverStream = new InMemoryRandomAccessStream(); - await _currentCoverStream.WriteAsync(info.CoverBuffer.AsBuffer()); - _currentCoverStream.Seek(0); - _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromStream( - _currentCoverStream - ); - } - catch - { - _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( - new Uri("ms-appx:///Assets/NoCover.png") - ); - } - } - } - else - { - _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( - new Uri("ms-appx:///Assets/NoCover.png") - ); - } + IsMute = !IsMute; } /// - /// 内部播放实现 + /// 设置音量 /// - private void InternalPlay() - { - if (_tempoStream == 0) - { - return; - } - if (IsExclusiveMode) // 独占模式:启动WASAPI独占播放 - { - if (!StartWasapiExclusivePlayback()) - { - // 如果独占模式失败,回退到共享模式 - _logger.ZLogInformation($"WASAPI独占模式失败,回退到共享模式"); - IsExclusiveMode = false; - - // 重新创建流(去掉DECODE标志) - if (!string.IsNullOrEmpty(_currentSourcePath)) - { - CreateStreamFromPath(_currentSourcePath); - SetVolumeValue(IsMute ? 0.0 : CurrentVolume / 100.0); - } - Bass.ChannelPlay(_tempoStream, false); - } - } - else // 共享模式:直接播放 - { - if (!Bass.ChannelPlay(_tempoStream, false)) - { - var error = Bass.LastError; - _logger.ZLogInformation($"共享模式播放失败: {error}"); - if (error == Errors.Start) // 处理播放错误 - { - if (Bass.Start()) // 设备需要启动 - { - Bass.ChannelPlay(_tempoStream, false); - } - } - } - } - } - + /// private void SetVolumeValue(double volume) { if (_tempoStream != 0) @@ -1162,7 +1206,6 @@ public partial class MusicPlayer if (ShuffleMode) { UpdateShufflePlayQueue(); - // 更新随机播放队列中每首歌曲的 Index 为实际位置 for (var i = 0; i < ShuffledPlayQueue.Count; i++) { ShuffledPlayQueue[i].Index = i; @@ -1198,81 +1241,6 @@ public partial class MusicPlayer _ = FileManager.SavePlayQueueDataAsync(PlayQueue, ShuffledPlayQueue); } - /// - /// 计时器更新事件 - /// - /// - private void UpdateTimerHandler250ms(ThreadPoolTimer timer) - { - try - { - if (_tempoStream == 0 || _lockable || PlayState != 1) - { - return; - } - - // 获取播放位置 - var positionBytes = Bass.ChannelGetPosition(_tempoStream); - if (positionBytes == -1) - { - // 如果获取位置失败,尝试重新启动播放 - var error = Bass.LastError; - _logger.ZLogInformation($"获取播放位置失败: {error}"); - - if (IsExclusiveMode) - { - // 在独占模式下,检查WASAPI状态 - if (!BassWasapi.IsStarted) - { - _logger.ZLogInformation($"WASAPI已停止,尝试重新启动"); - if (StartWasapiExclusivePlayback()) - { - BassWasapi.Start(); - } - else - { - // 如果重启失败,回退到共享模式 - _logger.ZLogInformation($"WASAPI重启失败,回退到共享模式"); - Data.RootPlayBarView?.DispatcherQueue.TryEnqueue(() => - { - IsExclusiveMode = false; - Bass.ChannelPlay(_tempoStream, false); - }); - } - } - } - return; - } - - var positionSeconds = Bass.ChannelBytes2Seconds(_tempoStream, positionBytes); - var playingTime = TimeSpan.FromSeconds(positionSeconds); - - App.MainWindow?.DispatcherQueue.TryEnqueue(() => - { - CurrentPlayingTime = playingTime; - if (TotalPlayingTime.TotalMilliseconds > 0) - { - CurrentPosition = - 100 * (playingTime.TotalMilliseconds / TotalPlayingTime.TotalMilliseconds); - } - }); - - var dispatcherQueue = - Data.LyricPage?.DispatcherQueue ?? Data.DesktopLyricWindow?.DispatcherQueue; - if (CurrentLyric.Count > 0) - { - dispatcherQueue?.TryEnqueue(() => UpdateCurrentLyricIndex(positionSeconds * 1000)); - } - - _timelineProperties.Position = CurrentPlayingTime; - _systemControls.UpdateTimelineProperties(_timelineProperties); - } - catch (Exception ex) - { - _logger.ZLogInformation(ex, $"计时器更新失败"); - } - } - /// /// 获取当前歌词切片索引 /// @@ -1303,14 +1271,12 @@ public partial class MusicPlayer var newIndex = GetCurrentLyricIndex(currentTime); if (newIndex != _currentLyricIndex) { - // 更新旧歌词状态 if (_currentLyricIndex >= 0 && _currentLyricIndex < CurrentLyric.Count) { CurrentLyric[_currentLyricIndex].IsCurrent = false; } _currentLyricIndex = newIndex; - // 更新新歌词状态 if (_currentLyricIndex >= 0 && _currentLyricIndex < CurrentLyric.Count) { CurrentLyric[_currentLyricIndex].IsCurrent = true; @@ -1319,136 +1285,11 @@ public partial class MusicPlayer } } - private void SystemControls_ButtonPressed( - SystemMediaTransportControls sender, - SystemMediaTransportControlsButtonPressedEventArgs args - ) - { - switch (args.Button) - { - case SystemMediaTransportControlsButton.Play: - Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( - DispatcherQueuePriority.Low, - PlayPauseUpdate - ); - break; - case SystemMediaTransportControlsButton.Pause: - Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( - DispatcherQueuePriority.Low, - PlayPauseUpdate - ); - break; - case SystemMediaTransportControlsButton.Previous: // 注意: 必须在UI线程中调用 - Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( - DispatcherQueuePriority.Low, - PlayPreviousSong - ); - break; - case SystemMediaTransportControlsButton.Next: - Data.RootPlayBarView?.DispatcherQueue.TryEnqueue( - DispatcherQueuePriority.Low, - PlayNextSong - ); - break; - default: - break; - } - } - - /// - /// 静音按钮点击事件 - /// - /// - /// - public void MuteButton_Click(object sender, RoutedEventArgs e) - { - IsMute = !IsMute; - } - - /// - /// 播放 - /// - public void Play() - { - PositionUpdateTimer250ms = ThreadPoolTimer.CreatePeriodicTimer( - UpdateTimerHandler250ms, - TimeSpan.FromMilliseconds(250) - ); - InternalPlay(); - PlayState = 1; - _systemControls.PlaybackStatus = MediaPlaybackStatus.Playing; - } - - /// - /// 内部暂停实现 - /// - private void InternalPause() - { - if (_tempoStream == 0) - { - return; - } - - if (IsExclusiveMode) // 独占模式:停止WASAPI但保持数据 - { - if (BassWasapi.IsStarted) - { - BassWasapi.Stop(false); - } - } - else // 共享模式:暂停通道 - { - Bass.ChannelPause(_tempoStream); - } - } - - /// - /// 暂停 - /// - public void Pause() - { - InternalPause(); - PlayState = 0; - _systemControls.PlaybackStatus = MediaPlaybackStatus.Paused; - PositionUpdateTimer250ms?.Cancel(); - PositionUpdateTimer250ms = null; - } - - /// - /// 内部停止实现 - /// - private void InternalStop() - { - if (_tempoStream != 0) - { - if (IsExclusiveMode) - { - CleanupWasapi(); - } - Bass.ChannelStop(_tempoStream); - } - } - - /// - /// 停止 - /// - public void Stop() - { - InternalStop(); - PlayState = 0; - CurrentPlayingTime = TimeSpan.Zero; - CurrentPosition = 0; - _currentLyricIndex = 0; - CurrentLyricContent = ""; - PositionUpdateTimer250ms?.Cancel(); - PositionUpdateTimer250ms = null; - } - /// /// 设置播放位置的内部实现 /// /// 目标时间(秒) - private void SetPlaybackPositionInternal(double targetTimeSeconds) + private async void SetPlaybackPositionInternal(double targetTimeSeconds) { if (_tempoStream != 0) { @@ -1456,8 +1297,19 @@ public partial class MusicPlayer var result = Bass.ChannelSetPosition(_tempoStream, targetBytes); if (!result) { - _logger.ZLogInformation($"设置播放位置失败: {Bass.LastError}"); + var error = Bass.LastError; + if (error == Errors.Position) + { + var retryCount = 0; + while (!result && retryCount < 10) // 最多重试10次 + { + await Task.Delay(100); + result = Bass.ChannelSetPosition(_tempoStream, targetBytes); + retryCount++; + } + } } + CurrentPlayingTime = TimeSpan.FromSeconds(targetTimeSeconds); if (TotalPlayingTime.TotalMilliseconds > 0) { @@ -1478,7 +1330,7 @@ public partial class MusicPlayer { var newIndex = PlayQueueIndex > 0 ? PlayQueueIndex - 1 : PlayQueueIndex; - if (RepeatMode == 1) // 列表循环 + if (RepeatMode == 1) { newIndex = (PlayQueueIndex + _playQueueLength - 1) % _playQueueLength; } @@ -1501,7 +1353,7 @@ public partial class MusicPlayer var newIndex = PlayQueueIndex < _playQueueLength - 1 ? PlayQueueIndex + 1 : 0; var isLast = PlayQueueIndex >= _playQueueLength - 1; - if (RepeatMode == 1) // 列表循环 + if (RepeatMode == 1) { newIndex = (PlayQueueIndex + 1) % _playQueueLength; isLast = false; @@ -1574,14 +1426,6 @@ public partial class MusicPlayer } } - /// - /// 循環播放模式更新 - /// - public void RepeatModeUpdate() - { - RepeatMode = (byte)((RepeatMode + 1) % 3); - } - /// /// 设置播放速度 /// @@ -1590,7 +1434,6 @@ public partial class MusicPlayer { if (_tempoStream != 0) { - // 使用Tempo属性来改变播放速度而不改变音调, Tempo属性的值以百分比表示: 0=正常速度, 100=2倍速度, -50=0.5倍速度 var tempoPercent = (speed - 1.0) * 100.0; Bass.ChannelSetAttribute(_tempoStream, ChannelAttribute.Tempo, (float)tempoPercent); } @@ -1599,7 +1442,6 @@ public partial class MusicPlayer /// /// 随机播放队列更新 /// - /// public void UpdateShufflePlayQueue() { ShuffledPlayQueue = [.. PlayQueue.AsValueEnumerable().OrderBy(x => Guid.NewGuid())]; @@ -1617,26 +1459,21 @@ public partial class MusicPlayer TotalPlayingTime = TimeSpan.Zero; PlayQueue.Clear(); ShuffledPlayQueue.Clear(); - OnPropertyChanged(nameof(PlayQueue)); // 不能删,用于通知按钮是否禁用 + OnPropertyChanged(nameof(PlayQueue)); CurrentLyric.Clear(); PlayQueueName = ""; PlayQueueIndex = 0; _playQueueLength = 0; Data.RootPlayBarViewModel!.ButtonVisibility = Visibility.Collapsed; Data.RootPlayBarViewModel!.Availability = false; - _systemControls.IsPlayEnabled = false; - _systemControls.IsPauseEnabled = false; - _systemControls.IsPreviousEnabled = false; - _systemControls.IsNextEnabled = false; + _smtcManager.SetButtonsEnabled(false, false, false, false); _ = FileManager.SavePlayQueueDataAsync(PlayQueue, ShuffledPlayQueue); } /// /// 按下滑动条事件 /// - /// - /// - public void ProgressLock(object sender, PointerRoutedEventArgs e) + public void ProgressLock(object sender, PointerRoutedEventArgs _) { _lockable = true; CurrentPlayingTime = TimeSpan.FromMilliseconds( @@ -1647,8 +1484,6 @@ public partial class MusicPlayer /// /// 键盘按下移动滑动条事件 /// - /// - /// public void ProgressLock(object sender, KeyRoutedEventArgs e) { if (e.Key != VirtualKey.Left && e.Key != VirtualKey.Right) @@ -1665,9 +1500,7 @@ public partial class MusicPlayer /// /// 滑动滑动条事件 /// - /// - /// - public void SliderUpdate(object sender, PointerRoutedEventArgs e) + public void SliderUpdate(object sender, PointerRoutedEventArgs _) { CurrentPlayingTime = TimeSpan.FromMilliseconds( ((Slider)sender).Value * TotalPlayingTime.TotalMilliseconds / 100 @@ -1678,9 +1511,7 @@ public partial class MusicPlayer /// /// 松开滑动条更新播放进度 /// - /// - /// - public void ProgressUpdate(object sender, PointerRoutedEventArgs e) + public void ProgressUpdate(object sender, PointerRoutedEventArgs _) { var targetTimeSeconds = ((Slider)sender).Value * TotalPlayingTime.TotalSeconds / 100; SetPlaybackPosition(targetTimeSeconds, false); @@ -1689,8 +1520,6 @@ public partial class MusicPlayer /// /// 键盘松开移动滑动条事件 /// - /// - /// public void ProgressUpdate(object sender, KeyRoutedEventArgs e) { if (e.Key != VirtualKey.Left && e.Key != VirtualKey.Right) @@ -1707,7 +1536,7 @@ public partial class MusicPlayer SetPlaybackPosition(targetTimeSeconds); } - public void SkipBack10sButton_Click(object sender, RoutedEventArgs e) + public void SkipBack10sButton_Click(object _1, RoutedEventArgs _2) { if (_tempoStream == 0) { @@ -1719,7 +1548,7 @@ public partial class MusicPlayer SetPlaybackPosition(newPositionSeconds); } - public void SkipForw30sButton_Click(object sender, RoutedEventArgs e) + public void SkipForw30sButton_Click(object _1, RoutedEventArgs _2) { if (_tempoStream == 0) { @@ -1749,7 +1578,7 @@ public partial class MusicPlayer _lockable = false; } - public void SpeedListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public void SpeedListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { if (sender is ListView listview && listview.SelectedIndex is int selectedIndex) { @@ -1765,7 +1594,7 @@ public partial class MusicPlayer } } - public void SpeedListView_Loaded(object sender, RoutedEventArgs e) + public void SpeedListView_Loaded(object sender, RoutedEventArgs _) { if (sender is ListView listview) { @@ -1808,7 +1637,7 @@ public partial class MusicPlayer } /// - /// 保存當前播放狀態至設置存儲 + /// 保存当前播放状态至设置存储 /// public async Task SaveCurrentStateAsync() { @@ -1824,7 +1653,7 @@ public partial class MusicPlayer } /// - /// 從設置存儲中讀取當前播放狀態 + /// 从设置存储中读取当前播放状态 /// public async void LoadCurrentStateAsync() { @@ -1842,11 +1671,10 @@ public partial class MusicPlayer PlaySpeed = await _localSettingsService.ReadSettingAsync("PlaySpeed"); } - // 如果是文件激活启动,跳过播放队列和当前歌曲的加载 if (Data.IsFileActivationLaunch) { - Data.RootPlayBarViewModel?.ButtonVisibility = Visibility.Visible; - Data.RootPlayBarViewModel?.Availability = true; + Data.RootPlayBarViewModel!.ButtonVisibility = Visibility.Visible; + Data.RootPlayBarViewModel!.Availability = true; HasLoaded = true; return; } @@ -1879,15 +1707,14 @@ public partial class MusicPlayer ); await SetSource(CurrentSong!.Path); _ = UpdateLyric(CurrentSong!.Lyric); - _systemControls.IsPlayEnabled = true; - _systemControls.IsPauseEnabled = true; + _smtcManager.SetButtonsEnabled(true, true, true, true); } } - Data.RootPlayBarViewModel?.ButtonVisibility = + Data.RootPlayBarViewModel!.ButtonVisibility = CurrentSong is not null && _playQueueLength > 0 ? Visibility.Visible : Visibility.Collapsed; - Data.RootPlayBarViewModel?.Availability = + Data.RootPlayBarViewModel!.Availability = CurrentSong is not null && _playQueueLength > 0; HasLoaded = true; } @@ -1897,8 +1724,8 @@ public partial class MusicPlayer CurrentSong = null; PlayQueue = []; ShuffledPlayQueue = []; - Data.RootPlayBarViewModel?.ButtonVisibility = Visibility.Collapsed; - Data.RootPlayBarViewModel?.Availability = false; + Data.RootPlayBarViewModel!.ButtonVisibility = Visibility.Collapsed; + Data.RootPlayBarViewModel!.Availability = false; HasLoaded = true; _logger.ZLogInformation(ex, $"初始化播放状态失败"); } @@ -1908,10 +1735,7 @@ public partial class MusicPlayer { Stop(); Messenger.Unregister(this); - - // 释放WASAPI资源 StopWasapiPlayback(); - if (_tempoStream != 0) { Bass.StreamFree(_tempoStream); @@ -1924,8 +1748,8 @@ public partial class MusicPlayer } Bass.Free(); BassWasapi.Free(); - _currentCoverStream?.Dispose(); - _tempPlayer?.Dispose(); + _smtcManager?.Dispose(); + GC.SuppressFinalize(this); } } diff --git a/The Untamed Music Player/Models/OnlineMusicLibrary.cs b/The Untamed Music Player/Models/OnlineMusicLibrary.cs index c174f00..e6992db 100644 --- a/The Untamed Music Player/Models/OnlineMusicLibrary.cs +++ b/The Untamed Music Player/Models/OnlineMusicLibrary.cs @@ -391,7 +391,7 @@ public partial class OnlineMusicLibrary : ObservableObject SuggestResultList = []; } - public void AutoSuggestBox_Loaded(object sender, RoutedEventArgs e) + public void AutoSuggestBox_Loaded(object sender, RoutedEventArgs _) { if (sender is AutoSuggestBox autoSuggestBox) { @@ -399,7 +399,7 @@ public partial class OnlineMusicLibrary : ObservableObject } } - public async void RetryButton_Click(object sender, RoutedEventArgs e) + public async void RetryButton_Click(object _1, RoutedEventArgs _2) { await ForceSearch(); } diff --git a/The Untamed Music Player/Services/MaterialSelectorService.cs b/The Untamed Music Player/Services/MaterialSelectorService.cs index 8158aa3..6daf6c8 100644 --- a/The Untamed Music Player/Services/MaterialSelectorService.cs +++ b/The Untamed Music Player/Services/MaterialSelectorService.cs @@ -19,6 +19,12 @@ public partial class MaterialSelectorService : IMaterialSelectorService IsInputActive = true, }; private ISystemBackdropControllerWithTargets? _currentBackdropController; + + // 防抖相关字段 + private Timer? _debounceTimer; + private readonly Lock _debounceLock = new(); + private const int DEBOUNCE_DELAY_MS = 100; // 100毫秒防抖延迟 + public MaterialType Material { get; @@ -79,78 +85,108 @@ public partial class MaterialSelectorService : IMaterialSelectorService bool forced = false ) { - if ((Material == material && !forced) || _mainWindow is null) + try { - return (LuminosityOpacity, TintColor); - } - _mainWindow.SystemBackdrop = null; - _currentBackdropController?.RemoveAllSystemBackdropTargets(); - _currentBackdropController?.Dispose(); - _currentBackdropController = material switch - { - MaterialType.Mica => new MicaController { Kind = MicaKind.Base }, - MaterialType.MicaAlt => new MicaController { Kind = MicaKind.BaseAlt }, - MaterialType.DesktopAcrylic => new DesktopAcrylicController + if ((Material == material && !forced) || _mainWindow is null) { - Kind = DesktopAcrylicKind.Default, - }, - MaterialType.AcrylicBase => new DesktopAcrylicController + return (LuminosityOpacity, TintColor); + } + _mainWindow.SystemBackdrop = null; + _currentBackdropController?.RemoveAllSystemBackdropTargets(); + _currentBackdropController?.Dispose(); + _currentBackdropController = material switch { - Kind = DesktopAcrylicKind.Base, - }, - MaterialType.AcrylicThin => new DesktopAcrylicController - { - Kind = DesktopAcrylicKind.Thin, - }, - _ => null, - }; - if (_currentBackdropController is not null) - { - SetConfigurationSourceTheme(); - _currentBackdropController?.AddSystemBackdropTarget(_backdropTarget); - _currentBackdropController?.SetSystemBackdropConfiguration(_configurationSource); - await Task.Delay(100); - } - else - { - _mainWindow.SystemBackdrop = material switch - { - MaterialType.Blur => new BlurredBackdrop(), - MaterialType.Transparent => new TransparentTintBackdrop(), - MaterialType.Animated => new ColorAnimatedBackdrop(), + MaterialType.Mica => new MicaController { Kind = MicaKind.Base }, + MaterialType.MicaAlt => new MicaController { Kind = MicaKind.BaseAlt }, + MaterialType.DesktopAcrylic => new DesktopAcrylicController + { + Kind = DesktopAcrylicKind.Default, + }, + MaterialType.AcrylicBase => new DesktopAcrylicController + { + Kind = DesktopAcrylicKind.Base, + }, + MaterialType.AcrylicThin => new DesktopAcrylicController + { + Kind = DesktopAcrylicKind.Thin, + }, _ => null, }; - } + if (_currentBackdropController is not null) + { + SetConfigurationSourceTheme(); + _currentBackdropController?.AddSystemBackdropTarget(_backdropTarget); + _currentBackdropController?.SetSystemBackdropConfiguration(_configurationSource); + await Task.Delay(100); + } + else + { + _mainWindow.SystemBackdrop = material switch + { + MaterialType.Blur => new BlurredBackdrop(), + MaterialType.Transparent => new TransparentTintBackdrop(), + MaterialType.Animated => new ColorAnimatedBackdrop(), + _ => null, + }; + } - if (firstStart && ThemeSelectorService.IsDarkTheme == Settings.PreviousIsDarkTheme) - { - SetLuminosityOpacity(LuminosityOpacity, true); - SetTintColor(TintColor, true); - } - else - { - LuminosityOpacity = GetLuminosityOpacity(); - TintColor = GetTintColor(); + if (firstStart && ThemeSelectorService.IsDarkTheme == Settings.PreviousIsDarkTheme) + { + SetLuminosityOpacity(LuminosityOpacity, true); + SetTintColor(TintColor, true); + } + else + { + LuminosityOpacity = GetLuminosityOpacity(); + TintColor = GetTintColor(); + } } + catch { } Material = material; return (LuminosityOpacity, TintColor); } - public void SetIsFallBack(bool isFallBack) => IsFallBack = isFallBack; - public void SetLuminosityOpacity(byte opacity, bool forced = false) { if (LuminosityOpacity == opacity && !forced) { return; } - if (_currentBackdropController is MicaController micaController) + + lock (_debounceLock) { - micaController.LuminosityOpacity = opacity / 100f; - } - else if (_currentBackdropController is DesktopAcrylicController desktopAcrylicController) - { - desktopAcrylicController.LuminosityOpacity = opacity / 100f; + _debounceTimer?.Dispose(); // 取消之前的定时器 + _debounceTimer = new Timer( // 创建新的定时器,延迟执行 + _ => + { + try + { + if (_currentBackdropController is MicaController micaController) + { + micaController.LuminosityOpacity = opacity / 100f; + } + else if ( + _currentBackdropController + is DesktopAcrylicController desktopAcrylicController + ) + { + desktopAcrylicController.LuminosityOpacity = opacity / 100f; + } + } + catch { } + finally + { + lock (_debounceLock) + { + _debounceTimer?.Dispose(); + _debounceTimer = null; + } + } + }, + null, + DEBOUNCE_DELAY_MS, + Timeout.Infinite + ); } LuminosityOpacity = opacity; } @@ -161,13 +197,41 @@ public partial class MaterialSelectorService : IMaterialSelectorService { return; } - if (_currentBackdropController is MicaController micaController) + + lock (_debounceLock) { - micaController.TintColor = color; - } - else if (_currentBackdropController is DesktopAcrylicController desktopAcrylicController) - { - desktopAcrylicController.TintColor = color; + _debounceTimer?.Dispose(); + _debounceTimer = new Timer( + _ => + { + try + { + if (_currentBackdropController is MicaController micaController) + { + micaController.TintColor = color; + } + else if ( + _currentBackdropController + is DesktopAcrylicController desktopAcrylicController + ) + { + desktopAcrylicController.TintColor = color; + } + } + catch { } + finally + { + lock (_debounceLock) + { + _debounceTimer?.Dispose(); + _debounceTimer = null; + } + } + }, + null, + DEBOUNCE_DELAY_MS, + Timeout.Infinite + ); } TintColor = color; } @@ -283,6 +347,19 @@ public partial class MaterialSelectorService : IMaterialSelectorService public void Dispose() { + // 清理防抖定时器 + lock (_debounceLock) + { + _debounceTimer?.Dispose(); + _debounceTimer = null; + } + + lock (_debounceLock) + { + _debounceTimer?.Dispose(); + _debounceTimer = null; + } + _currentBackdropController?.RemoveAllSystemBackdropTargets(); _currentBackdropController?.Dispose(); _currentBackdropController = null; diff --git a/The Untamed Music Player/Services/SystemMediaTransportControlsManager.cs b/The Untamed Music Player/Services/SystemMediaTransportControlsManager.cs new file mode 100644 index 0000000..6d87261 --- /dev/null +++ b/The Untamed Music Player/Services/SystemMediaTransportControlsManager.cs @@ -0,0 +1,230 @@ +using System.Runtime.InteropServices.WindowsRuntime; +using The_Untamed_Music_Player.Contracts.Models; +using The_Untamed_Music_Player.Helpers; +using The_Untamed_Music_Player.Models; +using Windows.Media; +using Windows.Media.Playback; +using Windows.Storage.Streams; + +namespace The_Untamed_Music_Player.Services; + +/// +/// 系统媒体传输控件管理器 +/// +public partial class SystemMediaTransportControlsManager : IDisposable +{ + /// + /// 用于获取SMTC的临时播放器 + /// + private readonly MediaPlayer _tempPlayer = new(); + + /// + /// 用于SMTC显示封面图片的流 + /// + private static InMemoryRandomAccessStream? _currentCoverStream = null!; + + /// + /// SMTC控件 + /// + private readonly SystemMediaTransportControls _systemControls; + + /// + /// SMTC显示内容更新器 + /// + private readonly SystemMediaTransportControlsDisplayUpdater _displayUpdater; + + /// + /// SMTC时间线属性 + /// + private readonly SystemMediaTransportControlsTimelineProperties _timelineProperties = new(); + + /// + /// 播放队列歌曲数量 + /// + private int _playQueueLength = 0; + + /// + /// 当前歌曲在播放队列中的索引 + /// + private int _playQueueIndex = 0; + + /// + /// 循环播放模式 + /// + private byte _repeatMode = 0; + + /// + /// 播放状态变化事件 + /// + public event Action? ButtonPressed; + + public SystemMediaTransportControls Controls => _systemControls; + + public SystemMediaTransportControlsManager() + { + _systemControls = _tempPlayer.SystemMediaTransportControls; + _displayUpdater = _systemControls.DisplayUpdater; + _displayUpdater.Type = MediaPlaybackType.Music; + _displayUpdater.AppMediaId = "AppDisplayName".GetLocalized(); + _systemControls.IsEnabled = true; + _systemControls.ButtonPressed += OnSystemControlsButtonPressed; + _timelineProperties.StartTime = TimeSpan.Zero; + _timelineProperties.MinSeekTime = TimeSpan.Zero; + } + + /// + /// 系统媒体控制按钮按下事件处理 + /// + private void OnSystemControlsButtonPressed( + SystemMediaTransportControls sender, + SystemMediaTransportControlsButtonPressedEventArgs args + ) + { + ButtonPressed?.Invoke(args.Button); + } + + /// + /// 更新播放状态 + /// + /// 播放状态 + public void UpdatePlaybackStatus(MediaPlaybackStatus playbackStatus) + { + _systemControls.PlaybackStatus = playbackStatus; + } + + /// + /// 设置按钮是否可用 + /// + /// 播放按钮是否可用 + /// 暂停按钮是否可用 + /// 上一首按钮是否可用 + /// 下一首按钮是否可用 + public void SetButtonsEnabled( + bool isPlayEnabled, + bool isPauseEnabled, + bool isPreviousEnabled, + bool isNextEnabled + ) + { + _systemControls.IsPlayEnabled = isPlayEnabled; + _systemControls.IsPauseEnabled = isPauseEnabled; + _systemControls.IsPreviousEnabled = isPreviousEnabled; + _systemControls.IsNextEnabled = isNextEnabled; + } + + /// + /// 更新播放队列信息以计算按钮状态 + /// + /// 当前播放索引 + /// 播放队列长度 + /// 循环模式 + public void UpdatePlayQueueInfo(int playQueueIndex, int playQueueLength, byte repeatMode) + { + _playQueueIndex = playQueueIndex; + _playQueueLength = playQueueLength; + _repeatMode = repeatMode; + + UpdateNavigationButtonsState(); + } + + /// + /// 更新导航按钮状态 + /// + private void UpdateNavigationButtonsState() + { + var isFirstSong = _playQueueIndex == 0; + var isLastSong = _playQueueIndex == _playQueueLength - 1; + var isRepeatOffOrSingle = _repeatMode == 0 || _repeatMode == 2; + + _systemControls.IsPreviousEnabled = !(isFirstSong && isRepeatOffOrSingle); + _systemControls.IsNextEnabled = !(isLastSong && isRepeatOffOrSingle); + } + + /// + /// 更新媒体信息 + /// + /// 歌曲标题 + /// 艺术家 + /// 总时长 + public void UpdateMediaInfo(string title, string artist, TimeSpan totalDuration) + { + _displayUpdater.MusicProperties.Title = title; + _displayUpdater.MusicProperties.Artist = + artist == "SongInfo_UnknownArtist".GetLocalized() ? "" : artist; + _timelineProperties.MaxSeekTime = totalDuration; + _timelineProperties.EndTime = totalDuration; + } + + /// + /// 设置封面图片 + /// + /// 当前歌曲 + public async Task SetCoverImageAsync(IDetailedSongInfoBase song) + { + if (song.Cover is null) + { + _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( + new Uri("ms-appx:///Assets/NoCover.png") + ); + return; + } + + if (song.IsOnline) + { + try + { + var info = (IDetailedOnlineSongInfo)song; + _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( + new Uri(info.CoverPath!) + ); + } + catch { } + } + else + { + try + { + var info = (DetailedLocalSongInfo)song; + _currentCoverStream?.Dispose(); + _currentCoverStream = new InMemoryRandomAccessStream(); + await _currentCoverStream.WriteAsync(info.CoverBuffer.AsBuffer()); + _currentCoverStream.Seek(0); + _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromStream( + _currentCoverStream + ); + } + catch + { + _displayUpdater.Thumbnail = RandomAccessStreamReference.CreateFromUri( + new Uri("ms-appx:///Assets/NoCover.png") + ); + } + } + } + + /// + /// 更新时间轴属性 + /// + /// 当前播放时间 + public void UpdateTimelinePosition(TimeSpan currentTime) + { + _timelineProperties.Position = currentTime; + _systemControls.UpdateTimelineProperties(_timelineProperties); + } + + /// + /// 应用所有更改 + /// + public void Update() + { + _displayUpdater.Update(); + } + + public void Dispose() + { + _systemControls.ButtonPressed -= OnSystemControlsButtonPressed; + _currentCoverStream?.Dispose(); + _tempPlayer?.Dispose(); + GC.SuppressFinalize(this); + } +} diff --git a/The Untamed Music Player/ViewModels/HomeViewModel.cs b/The Untamed Music Player/ViewModels/HomeViewModel.cs index 5a2de12..91a73f9 100644 --- a/The Untamed Music Player/ViewModels/HomeViewModel.cs +++ b/The Untamed Music Player/ViewModels/HomeViewModel.cs @@ -67,7 +67,7 @@ public partial class HomeViewModel : ObservableObject } public async void SuggestBox_QuerySubmitted( - AutoSuggestBox sender, + AutoSuggestBox _, AutoSuggestBoxQuerySubmittedEventArgs args ) { @@ -97,7 +97,7 @@ public partial class HomeViewModel : ObservableObject } } - public void SelectorBar_Loaded(object sender, RoutedEventArgs e) + public void SelectorBar_Loaded(object sender, RoutedEventArgs _) { if (sender is SelectorBar selectorBar) { @@ -109,7 +109,7 @@ public partial class HomeViewModel : ObservableObject public void SelectorBar_SelectionChanged( SelectorBar sender, - SelectorBarSelectionChangedEventArgs args + SelectorBarSelectionChangedEventArgs _ ) { var selectedItem = sender.SelectedItem; diff --git a/The Untamed Music Player/ViewModels/LocalAlbumDetailViewModel.cs b/The Untamed Music Player/ViewModels/LocalAlbumDetailViewModel.cs index cc5a0e2..70071e0 100644 --- a/The Untamed Music Player/ViewModels/LocalAlbumDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/LocalAlbumDetailViewModel.cs @@ -18,13 +18,13 @@ public class LocalAlbumDetailViewModel SongList = [.. Data.MusicLibrary.GetSongsByAlbum(Album)]; } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.SetPlayQueue($"LocalSongs:Album:{Album.Name}", SongList); Data.MusicPlayer.PlaySongByInfo(SongList[0]); } - public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e) + public void ShuffledPlayAllButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.SetShuffledPlayQueue($"ShuffledLocalSongs:Album:{Album.Name}", SongList); Data.MusicPlayer.PlaySongByIndexedInfo(Data.MusicPlayer.ShuffledPlayQueue[0]); @@ -48,7 +48,7 @@ public class LocalAlbumDetailViewModel } } - public void SongListView_ItemClick(object sender, ItemClickEventArgs e) + public void SongListView_ItemClick(object _, ItemClickEventArgs e) { Data.MusicPlayer.SetPlayQueue($"LocalSongs:Album:{Album.Name}", SongList); if (e.ClickedItem is BriefLocalSongInfo info) diff --git a/The Untamed Music Player/ViewModels/LocalAlbumsViewModel.cs b/The Untamed Music Player/ViewModels/LocalAlbumsViewModel.cs index 98b7aa0..f39ed2c 100644 --- a/The Untamed Music Player/ViewModels/LocalAlbumsViewModel.cs +++ b/The Untamed Music Player/ViewModels/LocalAlbumsViewModel.cs @@ -309,7 +309,7 @@ public partial class LocalAlbumsViewModel }); } - public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentsortmode = SortMode; SortMode = (byte)(sender as ListView)!.SelectedIndex; @@ -323,12 +323,12 @@ public partial class LocalAlbumsViewModel } } - public void SortByListView_Loaded(object sender, RoutedEventArgs e) + public void SortByListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = SortMode; } - public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentGenreMode = GenreMode; GenreMode = (byte)(sender as ListView)!.SelectedIndex; @@ -342,7 +342,7 @@ public partial class LocalAlbumsViewModel } } - public void GenreListView_Loaded(object sender, RoutedEventArgs e) + public void GenreListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = GenreMode; } diff --git a/The Untamed Music Player/ViewModels/LocalArtistDetailViewModel.cs b/The Untamed Music Player/ViewModels/LocalArtistDetailViewModel.cs index 89e29a6..1fc08b1 100644 --- a/The Untamed Music Player/ViewModels/LocalArtistDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/LocalArtistDetailViewModel.cs @@ -21,7 +21,7 @@ public class LocalArtistDetailViewModel AlbumList = Data.MusicLibrary.GetAlbumsByArtist(Artist); } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.SetPlayQueue( $"LocalSongs:Artist:{Artist.Name}", @@ -30,7 +30,7 @@ public class LocalArtistDetailViewModel Data.MusicPlayer.PlaySongByInfo(AlbumList[0].SongList[0]); } - public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e) + public void ShuffledPlayAllButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.SetShuffledPlayQueue( $"ShuffledLocalSongs:Artist:{Artist.Name}", diff --git a/The Untamed Music Player/ViewModels/LocalArtistsViewModel.cs b/The Untamed Music Player/ViewModels/LocalArtistsViewModel.cs index 769943e..54fb516 100644 --- a/The Untamed Music Player/ViewModels/LocalArtistsViewModel.cs +++ b/The Untamed Music Player/ViewModels/LocalArtistsViewModel.cs @@ -64,7 +64,7 @@ public partial class LocalArtistsViewModel IsProgressRingActive = false; } - public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentsortmode = SortMode; SortMode = (byte)(sender as ListView)!.SelectedIndex; @@ -77,7 +77,7 @@ public partial class LocalArtistsViewModel } } - public void SortByListView_Loaded(object sender, RoutedEventArgs e) + public void SortByListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = SortMode; } diff --git a/The Untamed Music Player/ViewModels/LocalSongsViewModel.cs b/The Untamed Music Player/ViewModels/LocalSongsViewModel.cs index 0789983..7ce8259 100644 --- a/The Untamed Music Player/ViewModels/LocalSongsViewModel.cs +++ b/The Untamed Music Player/ViewModels/LocalSongsViewModel.cs @@ -260,7 +260,7 @@ public partial class LocalSongsViewModel public object GetSongListViewSource( ICollectionView grouped, - List notgrouped + List _ ) { return _isGrouped ? grouped : NotGroupedSongList; @@ -492,7 +492,7 @@ public partial class LocalSongsViewModel }); } - public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentsortmode = SortMode; SortMode = (byte)(sender as ListView)!.SelectedIndex; @@ -507,12 +507,12 @@ public partial class LocalSongsViewModel } } - public void SortByListView_Loaded(object sender, RoutedEventArgs e) + public void SortByListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = SortMode; } - public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void GenreListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentGenreMode = GenreMode; GenreMode = (byte)(sender as ListView)!.SelectedIndex; @@ -527,18 +527,18 @@ public partial class LocalSongsViewModel } } - public void GenreListView_Loaded(object sender, RoutedEventArgs e) + public void GenreListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = GenreMode; } - public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e) + public void ShuffledPlayAllButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.SetShuffledPlayQueue("ShuffledLocalSongs:All", ConvertGroupedToFlatList()); Data.MusicPlayer.PlaySongByIndexedInfo(Data.MusicPlayer.ShuffledPlayQueue[0]); } - public void SongListView_ItemClick(object sender, ItemClickEventArgs e) + public void SongListView_ItemClick(object _, ItemClickEventArgs e) { if (e.ClickedItem is BriefLocalSongInfo info) { diff --git a/The Untamed Music Player/ViewModels/LyricViewModel.cs b/The Untamed Music Player/ViewModels/LyricViewModel.cs index b944eab..fc39eb8 100644 --- a/The Untamed Music Player/ViewModels/LyricViewModel.cs +++ b/The Untamed Music Player/ViewModels/LyricViewModel.cs @@ -11,7 +11,7 @@ public class LyricViewModel { public LyricViewModel() { } - public void ListView_ItemClick(object sender, ItemClickEventArgs e) + public void ListView_ItemClick(object _, ItemClickEventArgs e) { if (e.ClickedItem is LyricSlice lyricSlice) { @@ -20,19 +20,19 @@ public class LyricViewModel } } - public void PlayButton_Click(object sender, RoutedEventArgs e) + public void PlayButton_Click(object _1, RoutedEventArgs _2) { var currentSong = Data.MusicPlayer.CurrentBriefSong; Data.MusicPlayer.PlaySongByInfo(currentSong!); } - public void PlayNextButton_Click(object sender, RoutedEventArgs e) + public void PlayNextButton_Click(object _1, RoutedEventArgs _2) { var currentSong = Data.MusicPlayer.CurrentBriefSong; Data.MusicPlayer.AddSongToNextPlay(currentSong!); } - public void AddToPlayQueueButton_Click(object sender, RoutedEventArgs e) + public void AddToPlayQueueButton_Click(object _1, RoutedEventArgs _2) { var currentSong = Data.MusicPlayer.CurrentBriefSong; Data.MusicPlayer.AddSongToPlayQueue(currentSong!); @@ -44,7 +44,7 @@ public class LyricViewModel await Data.PlaylistLibrary.AddToPlaylist(playlist, currentSong!); } - public async void ShowAlbumButton_Click(object sender, RoutedEventArgs e) + public async void ShowAlbumButton_Click(object _1, RoutedEventArgs _2) { Data.RootPlayBarViewModel!.DetailModeUpdate(); var info = Data.MusicPlayer.CurrentBriefSong; @@ -76,7 +76,7 @@ public class LyricViewModel } } - public async void ShowArtistButton_Click(object sender, RoutedEventArgs e) + public async void ShowArtistButton_Click(object _1, RoutedEventArgs _2) { Data.RootPlayBarViewModel!.DetailModeUpdate(); var info = Data.MusicPlayer.CurrentBriefSong; diff --git a/The Untamed Music Player/ViewModels/MusicLibraryViewModel.cs b/The Untamed Music Player/ViewModels/MusicLibraryViewModel.cs index a93a779..4cb5cd9 100644 --- a/The Untamed Music Player/ViewModels/MusicLibraryViewModel.cs +++ b/The Untamed Music Player/ViewModels/MusicLibraryViewModel.cs @@ -58,7 +58,7 @@ public partial class MusicLibraryViewModel : Visibility.Visible; } - public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs e) + public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; var openPicker = new FolderPicker(App.MainWindow!.AppWindow.Id) diff --git a/The Untamed Music Player/ViewModels/OnlineAlbumDetailViewModel.cs b/The Untamed Music Player/ViewModels/OnlineAlbumDetailViewModel.cs index 97e10ec..16f362b 100644 --- a/The Untamed Music Player/ViewModels/OnlineAlbumDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/OnlineAlbumDetailViewModel.cs @@ -74,7 +74,7 @@ public partial class OnlineAlbumDetailViewModel : ObservableObject IsSearchProgressRingActive = false; } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { if (Album.SongList.Count == 0) { @@ -84,7 +84,7 @@ public partial class OnlineAlbumDetailViewModel : ObservableObject Data.MusicPlayer.PlaySongByInfo(Album.SongList[0]); } - public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e) + public void ShuffledPlayAllButton_Click(object _1, RoutedEventArgs _2) { if (Album.SongList.Count == 0) { @@ -119,7 +119,7 @@ public partial class OnlineAlbumDetailViewModel : ObservableObject } } - public void SongListView_ItemClick(object sender, ItemClickEventArgs e) + public void SongListView_ItemClick(object _, ItemClickEventArgs e) { Data.MusicPlayer.SetPlayQueue($"OnlineSongs:Album:{Album.Name}", Album.SongList); if (e.ClickedItem is IBriefOnlineSongInfo info) diff --git a/The Untamed Music Player/ViewModels/OnlineArtistDetailViewModel.cs b/The Untamed Music Player/ViewModels/OnlineArtistDetailViewModel.cs index 3f1aecc..7032153 100644 --- a/The Untamed Music Player/ViewModels/OnlineArtistDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/OnlineArtistDetailViewModel.cs @@ -136,7 +136,7 @@ public partial class OnlineArtistDetailViewModel : ObservableObject } } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { if (Artist.AlbumList.Count == 0) { @@ -147,7 +147,7 @@ public partial class OnlineArtistDetailViewModel : ObservableObject Data.MusicPlayer.PlaySongByInfo(allSongs[0]); } - public void ShuffledPlayAllButton_Click(object sender, RoutedEventArgs e) + public void ShuffledPlayAllButton_Click(object _1, RoutedEventArgs _2) { if (Artist.AlbumList.Count == 0) { diff --git a/The Untamed Music Player/ViewModels/OnlinePlayListDetailViewModel.cs b/The Untamed Music Player/ViewModels/OnlinePlayListDetailViewModel.cs index 401f90e..f9c9351 100644 --- a/The Untamed Music Player/ViewModels/OnlinePlayListDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/OnlinePlayListDetailViewModel.cs @@ -76,7 +76,7 @@ public partial class OnlinePlayListDetailViewModel : ObservableObject IsSearchProgressRingActive = false; } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { if (Playlist.SongList.Count == 0) { @@ -111,7 +111,7 @@ public partial class OnlinePlayListDetailViewModel : ObservableObject } } - public void SongListView_ItemClick(object sender, ItemClickEventArgs e) + public void SongListView_ItemClick(object _, ItemClickEventArgs e) { Data.MusicPlayer.SetPlayQueue($"OnlineSongs:Playlist:{Playlist.Name}", Playlist.SongList); if (e.ClickedItem is IBriefOnlineSongInfo info) diff --git a/The Untamed Music Player/ViewModels/OnlineSongsViewModel.cs b/The Untamed Music Player/ViewModels/OnlineSongsViewModel.cs index beefe87..e80f02d 100644 --- a/The Untamed Music Player/ViewModels/OnlineSongsViewModel.cs +++ b/The Untamed Music Player/ViewModels/OnlineSongsViewModel.cs @@ -10,7 +10,7 @@ public class OnlineSongsViewModel { public OnlineSongsViewModel() { } - public void OnlineSongsSongListView_ItemClick(object sender, ItemClickEventArgs e) + public void OnlineSongsSongListView_ItemClick(object _, ItemClickEventArgs e) { Data.MusicPlayer.SetPlayQueue( $"OnlineSongs:{Data.OnlineMusicLibrary.SearchKeyWords}", diff --git a/The Untamed Music Player/ViewModels/PlayListDetailViewModel.cs b/The Untamed Music Player/ViewModels/PlayListDetailViewModel.cs index 63f23e2..44bdfbd 100644 --- a/The Untamed Music Player/ViewModels/PlayListDetailViewModel.cs +++ b/The Untamed Music Player/ViewModels/PlayListDetailViewModel.cs @@ -67,7 +67,7 @@ public partial class PlayListDetailViewModel } } - public void PlayAllButton_Click(object sender, RoutedEventArgs e) + public void PlayAllButton_Click(object _1, RoutedEventArgs _2) { if (SongList.Count == 0) { @@ -102,14 +102,14 @@ public partial class PlayListDetailViewModel } } - public void DeleteButton_Click(object sender, RoutedEventArgs e) + public void DeleteButton_Click(object _1, RoutedEventArgs _2) { Data.SelectedPlaylist = null; Data.ShellPage!.GoBack(); Data.PlaylistLibrary.DeletePlaylist(Playlist); } - public void SongListView_ItemClick(object sender, ItemClickEventArgs e) + public void SongListView_ItemClick(object _, ItemClickEventArgs e) { var songList = SongList.AsValueEnumerable().Select(s => s.Song).ToArray(); Data.MusicPlayer.SetPlayQueue($"Songs:Playlist:{Playlist.Name}", songList); @@ -240,7 +240,7 @@ public partial class PlayListDetailViewModel } } - public void SongListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + public void SongListView_DragItemsStarting(object _, DragItemsStartingEventArgs e) { if (e.Items.Count > 0) { @@ -249,7 +249,7 @@ public partial class PlayListDetailViewModel } public void SongListView_DragItemsCompleted( - ListViewBase sender, + ListViewBase _1, DragItemsCompletedEventArgs args ) { diff --git a/The Untamed Music Player/ViewModels/PlayListsViewModel.cs b/The Untamed Music Player/ViewModels/PlayListsViewModel.cs index 4418cee..4f1e848 100644 --- a/The Untamed Music Player/ViewModels/PlayListsViewModel.cs +++ b/The Untamed Music Player/ViewModels/PlayListsViewModel.cs @@ -145,12 +145,12 @@ public partial class PlayListsViewModel }); } - public void SortByListView_Loaded(object sender, RoutedEventArgs e) + public void SortByListView_Loaded(object sender, RoutedEventArgs _) { (sender as ListView)!.SelectedIndex = SortMode; } - public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void SortByListView_SelectionChanged(object sender, SelectionChangedEventArgs _) { var currentsortmode = SortMode; SortMode = (byte)(sender as ListView)!.SelectedIndex; diff --git a/The Untamed Music Player/ViewModels/PlayQueueViewModel.cs b/The Untamed Music Player/ViewModels/PlayQueueViewModel.cs index 7d14318..55570f9 100644 --- a/The Untamed Music Player/ViewModels/PlayQueueViewModel.cs +++ b/The Untamed Music Player/ViewModels/PlayQueueViewModel.cs @@ -61,7 +61,7 @@ public partial class PlayQueueViewModel : ObservableObject Data.MusicPlayer.AddSongsToPlayQueue(songList); } - public void PlayQueueListView_ItemClick(object sender, ItemClickEventArgs e) + public void PlayQueueListView_ItemClick(object _, ItemClickEventArgs e) { if (e.ClickedItem is IndexedPlayQueueSong info) { @@ -164,7 +164,7 @@ public partial class PlayQueueViewModel : ObservableObject } } - public void ClearButton_Click(object sender, RoutedEventArgs e) + public void ClearButton_Click(object _1, RoutedEventArgs _2) { Data.MusicPlayer.ClearPlayQueue(); } @@ -230,7 +230,7 @@ public partial class PlayQueueViewModel : ObservableObject IsButtonEnabled = PlayQueue.Count > 0; } - public void PlayQueueListView_DragItemsStarting(object sender, DragItemsStartingEventArgs e) + public void PlayQueueListView_DragItemsStarting(object _, DragItemsStartingEventArgs e) { _currentSong = PlayQueue[Data.MusicPlayer.PlayQueueIndex]; if (e.Items.Count > 0) @@ -239,7 +239,7 @@ public partial class PlayQueueViewModel : ObservableObject } } - public void PlayQueueListView_DragOver(object sender, DragEventArgs e) + public void PlayQueueListView_DragOver(object _, DragEventArgs e) { if (e.DataView.Contains(StandardDataFormats.StorageItems)) { @@ -252,7 +252,7 @@ public partial class PlayQueueViewModel : ObservableObject } public void PlayQueueListView_DragItemsCompleted( - object sender, + object _, DragItemsCompletedEventArgs args ) { diff --git a/The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs b/The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs index 5817be5..fbad275 100644 --- a/The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs +++ b/The Untamed Music Player/ViewModels/RootPlayBarViewModel.cs @@ -122,7 +122,7 @@ public partial class RootPlayBarViewModel : ObservableObject } } - public void FullScreenButton_Click(object sender, RoutedEventArgs e) + public void FullScreenButton_Click(object _1, RoutedEventArgs _2) { var appWindow = App.MainWindow!.AppWindow; if (appWindow.Presenter.Kind == AppWindowPresenterKind.FullScreen) @@ -137,7 +137,7 @@ public partial class RootPlayBarViewModel : ObservableObject } } - public void DesktopLyricButton_Click(object sender, RoutedEventArgs e) + public void DesktopLyricButton_Click(object _1, RoutedEventArgs _2) { if (!IsDesktopLyricWindowStarted) { diff --git a/The Untamed Music Player/ViewModels/SettingsViewModel.cs b/The Untamed Music Player/ViewModels/SettingsViewModel.cs index dd9eb83..374cf23 100644 --- a/The Untamed Music Player/ViewModels/SettingsViewModel.cs +++ b/The Untamed Music Player/ViewModels/SettingsViewModel.cs @@ -158,7 +158,7 @@ public partial class SettingsViewModel partial void OnIsFallBackChanged(bool value) { - _materialSelectorService.SetIsFallBack(value); + _materialSelectorService.IsFallBack = value; } /// @@ -167,12 +167,22 @@ public partial class SettingsViewModel [ObservableProperty] public partial byte LuminosityOpacity { get; set; } + partial void OnLuminosityOpacityChanged(byte value) + { + _materialSelectorService.SetLuminosityOpacity(value, false); + } + /// /// 背景颜色 /// [ObservableProperty] public partial Color TintColor { get; set; } + partial void OnTintColorChanged(Color value) + { + _materialSelectorService.SetTintColor(value, false); + } + /// /// 是否显示歌词背景 /// @@ -215,7 +225,7 @@ public partial class SettingsViewModel IsExportPlaylistsButtonEnabled = message.HasPlaylist; } - public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs e) + public async void PickMusicFolderButton_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; var openPicker = new FolderPicker(App.MainWindow!.AppWindow.Id) @@ -247,7 +257,7 @@ public partial class SettingsViewModel await Data.MusicLibrary.LoadLibraryAgainAsync(); } - public async void RefreshButton_Click(object sender, RoutedEventArgs e) + public async void RefreshButton_Click(object sender, RoutedEventArgs _) { var senderButton = sender as Button; senderButton!.IsEnabled = false; @@ -255,12 +265,12 @@ public partial class SettingsViewModel senderButton!.IsEnabled = true; } - public void SongDownloadLocationButton_Click(object sender, RoutedEventArgs e) + public void SongDownloadLocationButton_Click(object _1, RoutedEventArgs _2) { Process.Start("explorer.exe", SongDownloadLocation); } - public async void ChangeSongDownloadLocationButton_Click(object sender, RoutedEventArgs e) + public async void ChangeSongDownloadLocationButton_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; try @@ -282,7 +292,7 @@ public partial class SettingsViewModel } } - public async void ImportFromM3u8Button_Click(object sender, RoutedEventArgs e) + public async void ImportFromM3u8Button_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; try @@ -328,7 +338,7 @@ public partial class SettingsViewModel } } - public async void ImportFromBinButton_Click(object sender, RoutedEventArgs e) + public async void ImportFromBinButton_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; try @@ -371,7 +381,7 @@ public partial class SettingsViewModel } } - public async void ExportToM3u8Button_Click(object sender, RoutedEventArgs e) + public async void ExportToM3u8Button_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; try @@ -408,7 +418,7 @@ public partial class SettingsViewModel } } - public async void ExportToBinButton_Click(object sender, RoutedEventArgs e) + public async void ExportToBinButton_Click(object sender, RoutedEventArgs _) { (sender as Button)!.IsEnabled = false; try @@ -456,7 +466,7 @@ public partial class SettingsViewModel } } - public async void MaterialComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + public async void MaterialComboBox_SelectionChanged(object _1, SelectionChangedEventArgs _2) { var (opacity, color) = await _materialSelectorService.SetMaterial( (MaterialType)SelectedMaterial, @@ -467,7 +477,7 @@ public partial class SettingsViewModel TintColor = color; } - public async void ResetMaterialButton_Click(object sender, RoutedEventArgs e) + public async void ResetMaterialButton_Click(object _1, RoutedEventArgs _2) { IsFallBack = true; SelectedMaterial = (byte)MaterialType.DesktopAcrylic; @@ -481,20 +491,7 @@ public partial class SettingsViewModel OnPropertyChanged(nameof(SelectedMaterial)); } - public void LuminosityOpacitySlider_ValueChanged( - object sender, - RangeBaseValueChangedEventArgs e - ) - { - _materialSelectorService.SetLuminosityOpacity(LuminosityOpacity, false); - } - - public void TintColorPicker_ColorChanged(ColorPicker sender, ColorChangedEventArgs args) - { - _materialSelectorService.SetTintColor(args.NewColor, false); - } - - public void FontFamilyComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + public void FontFamilyComboBox_SelectionChanged(object _, SelectionChangedEventArgs e) { if (e.AddedItems.Count > 0 && e.AddedItems[0] is FontInfo selectedFont) { @@ -502,7 +499,7 @@ public partial class SettingsViewModel } } - public void FontSizeComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e) + public void FontSizeComboBox_SelectionChanged(object _, SelectionChangedEventArgs e) { if (e.AddedItems.Count > 0 && e.AddedItems[0] is double fontSize) { @@ -522,7 +519,7 @@ public partial class SettingsViewModel } } - public void MaterialComboBox_Loaded(object sender, RoutedEventArgs e) + public void MaterialComboBox_Loaded(object sender, RoutedEventArgs _) { (sender as ComboBox)!.SelectedIndex = SelectedMaterial; } @@ -547,7 +544,7 @@ public partial class SettingsViewModel FontFamilies = [.. list.AsValueEnumerable().OrderBy(f => f.Name)]; } - public void FontFamilyComboBox_Loaded(object sender, RoutedEventArgs e) + public void FontFamilyComboBox_Loaded(object sender, RoutedEventArgs _) { var selectedFontName = SelectedFontFamily.Source; var index = FontFamilies.FindIndex(f => f.Name == selectedFontName); @@ -557,7 +554,7 @@ public partial class SettingsViewModel } } - public void FontSizeComboBox_Loaded(object sender, RoutedEventArgs e) + public void FontSizeComboBox_Loaded(object sender, RoutedEventArgs _) { var selectedItem = FontSizes.FirstOrDefault(f => f == SelectedCurrentFontSize); if (selectedItem != 0.0) @@ -570,7 +567,7 @@ public partial class SettingsViewModel } } - public void OpenLoggingFolderButton_Click(object sender, RoutedEventArgs e) + public void OpenLoggingFolderButton_Click(object _1, RoutedEventArgs _2) { var logFolder = LoggingService.GetLogFolderPath(); Directory.CreateDirectory(logFolder); diff --git a/The Untamed Music Player/ViewModels/ShellViewModel.cs b/The Untamed Music Player/ViewModels/ShellViewModel.cs index 1a40091..822e404 100644 --- a/The Untamed Music Player/ViewModels/ShellViewModel.cs +++ b/The Untamed Music Player/ViewModels/ShellViewModel.cs @@ -35,7 +35,7 @@ public partial class ShellViewModel : ObservableObject LoadAsync(); } - public void NavigationFrame_Navigating(object sender, NavigatingCancelEventArgs e) + public void NavigationFrame_Navigating(object _, NavigatingCancelEventArgs e) { if (e.NavigationMode == NavigationMode.Back) { @@ -95,7 +95,7 @@ public partial class ShellViewModel : ObservableObject SaveCurrentPageAsync(); } - public void NavigationFrame_DragOver(object sender, DragEventArgs e) + public void NavigationFrame_DragOver(object _, DragEventArgs e) { if (CurrentPage == nameof(PlayQueuePage)) { @@ -111,7 +111,7 @@ public partial class ShellViewModel : ObservableObject } } - public async void NavigationFrame_Drop(object sender, DragEventArgs e) + public async void NavigationFrame_Drop(object _, DragEventArgs e) { if (CurrentPage == nameof(PlayQueuePage)) { diff --git a/The Untamed Music Player/Views/OnlineAlbumDetailPage.xaml.cs b/The Untamed Music Player/Views/OnlineAlbumDetailPage.xaml.cs index 541fe83..5c669d3 100644 --- a/The Untamed Music Player/Views/OnlineAlbumDetailPage.xaml.cs +++ b/The Untamed Music Player/Views/OnlineAlbumDetailPage.xaml.cs @@ -411,7 +411,7 @@ public sealed partial class OnlineAlbumDetailPage : Page } } - private void AddToPlayQueueButton_Click(object sender, RoutedEventArgs e) + private void AddToPlayQueueButton_Click(object sender, RoutedEventArgs _) { if (sender is FrameworkElement { DataContext: IBriefOnlineSongInfo info }) { @@ -419,7 +419,7 @@ public sealed partial class OnlineAlbumDetailPage : Page } } - private async void AddToNewPlaylistButton_Click(object sender, RoutedEventArgs e) + private async void AddToNewPlaylistButton_Click(object sender, RoutedEventArgs _) { if (sender is FrameworkElement { DataContext: IBriefOnlineSongInfo info }) { diff --git a/The Untamed Music Player/Views/SettingsPage.xaml b/The Untamed Music Player/Views/SettingsPage.xaml index 0637279..7ce5db2 100644 --- a/The Untamed Music Player/Views/SettingsPage.xaml +++ b/The Untamed Music Player/Views/SettingsPage.xaml @@ -272,12 +272,10 @@ -