From 153679228da7fe0a782aae38e765f5b54536edf2 Mon Sep 17 00:00:00 2001 From: Zhe Fang Date: Thu, 17 Jul 2025 13:04:50 -0400 Subject: [PATCH] fix playbackservice --- .../BetterLyrics.WinUI3.csproj | 1 + .../Events/AlbumArtChangedEventArgs.cs | 6 +- .../BetterLyrics.WinUI3/Helper/ImageHelper.cs | 8 +- .../Helper/LatestOnlyTaskRunner.cs | 38 +++++--- .../Services/IPlaybackService.cs | 4 +- .../Services/PlaybackService.cs | 95 +++++++++++-------- .../Strings/en-US/Resources.resw | 3 + .../Strings/ja-JP/Resources.resw | 3 + .../Strings/ko-KR/Resources.resw | 3 + .../Strings/zh-CN/Resources.resw | 3 + .../Strings/zh-TW/Resources.resw | 3 + .../ViewModels/LyricsPageViewModel.cs | 2 + .../ViewModels/LyricsRendererViewModel.cs | 1 + 13 files changed, 110 insertions(+), 60 deletions(-) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj index f3c1d0a..19882fa 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/BetterLyrics.WinUI3.csproj @@ -51,6 +51,7 @@ + diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/AlbumArtChangedEventArgs.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/AlbumArtChangedEventArgs.cs index f9533a4..c1e8497 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/AlbumArtChangedEventArgs.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Events/AlbumArtChangedEventArgs.cs @@ -8,9 +8,9 @@ using Windows.UI; namespace BetterLyrics.WinUI3.Events { - public class AlbumArtChangedEventArgs : EventArgs + public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtAccentColor) : EventArgs { - public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null; - public Color? AlbumArtAccentColor { get; set; } = null; + public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap; + public Color? AlbumArtAccentColor { get; set; } = albumArtAccentColor; } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs index f7994ea..e13955a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/ImageHelper.cs @@ -167,9 +167,11 @@ namespace BetterLyrics.WinUI3.Helper public static async Task ToByteArrayAsync(IRandomAccessStreamReference streamRef) { using IRandomAccessStream stream = await streamRef.OpenReadAsync(); - using var memoryStream = new MemoryStream(); - await stream.AsStreamForRead().CopyToAsync(memoryStream); - return memoryStream.ToArray(); + using var reader = new DataReader(stream); + await reader.LoadAsync((uint)stream.Size); + byte[] buffer = new byte[stream.Size]; + reader.ReadBytes(buffer); + return buffer; } public static float GetAverageLuminance(CanvasBitmap bitmap) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LatestOnlyTaskRunner.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LatestOnlyTaskRunner.cs index 51ef900..eff577e 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LatestOnlyTaskRunner.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LatestOnlyTaskRunner.cs @@ -1,4 +1,5 @@ -using System; +using Nito.AsyncEx; +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -9,23 +10,34 @@ namespace BetterLyrics.WinUI3.Helper { public class LatestOnlyTaskRunner { - private CancellationTokenSource? _cts; + private readonly AsyncLock _mutex = new(); + private CancellationTokenSource _cts; - public async Task RunAsync(Func func) + public async Task RunAsync(Func action) { - _cts?.Cancel(); - _cts = new CancellationTokenSource(); - var token = _cts.Token; + CancellationTokenSource oldCts; + + // 使用 AsyncLock 保证线程安全 + using (await _mutex.LockAsync()) + { + // 取消旧的 + oldCts = _cts; + _cts = new CancellationTokenSource(); + } + + oldCts?.Cancel(); + oldCts?.Dispose(); + + CancellationToken token = _cts.Token; + try { - await func(token); + await action(token); + } + catch (OperationCanceledException) + { + // 可以选择忽略取消异常 } - catch (OperationCanceledException) { } - } - - public void Cancel() - { - _cts?.Cancel(); } } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs index ab84b7b..8742cc5 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/IPlaybackService.cs @@ -19,6 +19,8 @@ namespace BetterLyrics.WinUI3.Services Task PauseAsync(); Task PreviousAsync(); Task NextAsync(); - + + bool IsPlaying { get; } + } } diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs index 35e3ca6..add458a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Services/PlaybackService.cs @@ -13,6 +13,7 @@ using Microsoft.UI.Dispatching; using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics; using System.Linq; using System.Runtime.InteropServices.WindowsRuntime; using System.Text.Json; @@ -34,17 +35,18 @@ namespace BetterLyrics.WinUI3.Services private readonly string _lxMusicId = "cn.toside.music.desktop"; + private bool _cachedIsPlaying = false; + private EventSourceReader? _sse = null; private readonly MediaManager _mediaManager = new(); - private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new(); - private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new(); + private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new(); + private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new(); private SongInfo? _cachedSongInfo; private List _mediaSourceProvidersInfo; private byte[]? _SMTCAlbumArtBytes = null; - private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new(); public event EventHandler? IsPlayingChanged; public event EventHandler? PositionChanged; @@ -61,6 +63,8 @@ namespace BetterLyrics.WinUI3.Services InitMediaManager(); } + public bool IsPlaying => _cachedIsPlaying; + private bool IsMediaSourceEnabled(string id) { return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true; @@ -88,7 +92,7 @@ namespace BetterLyrics.WinUI3.Services } else { - _dispatcherQueue.TryEnqueue(async () => + Task.Run(async () => { try { @@ -96,11 +100,7 @@ namespace BetterLyrics.WinUI3.Services MediaManager_OnAnyMediaPropertyChanged(mediaSession, props); MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo()); } - catch (Exception ex) - { - _logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed"); - SendNullMessages(); - } + catch (Exception) { } }); } } @@ -123,7 +123,7 @@ namespace BetterLyrics.WinUI3.Services RecordMediaSourceProviderInfo(mediaSession); if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return; - bool isPlaying = playbackInfo.PlaybackStatus switch + _cachedIsPlaying = playbackInfo.PlaybackStatus switch { GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true, _ => false, @@ -132,24 +132,32 @@ namespace BetterLyrics.WinUI3.Services _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, () => { - IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(isPlaying)); + IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying)); } ); } - private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties) + private async void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties) { - _ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token => + string id = mediaSession.ControlSession.SourceAppUserModelId; + + RecordMediaSourceProviderInfo(mediaSession); + if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return; + + _cachedSongInfo = new SongInfo + { + Title = mediaProperties.Title, + Artist = mediaProperties.Artist, + Album = mediaProperties.AlbumTitle, + DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds, + SourceAppUserModelId = id, + }; + + await _onAnyMediaPropertyChangedRunner.RunAsync(async token => { _logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}", mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle); - RecordMediaSourceProviderInfo(mediaSession); - string id = mediaSession.ControlSession.SourceAppUserModelId; - if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return; - - token.ThrowIfCancellationRequested(); - if (id == _lxMusicId) { StartSSE(); @@ -159,30 +167,24 @@ namespace BetterLyrics.WinUI3.Services StopSSE(); } - _cachedSongInfo = new SongInfo - { - Title = mediaProperties.Title, - Artist = mediaProperties.Artist, - Album = mediaProperties.AlbumTitle, - DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds, - SourceAppUserModelId = id, - }; - if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference) { _SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference); token.ThrowIfCancellationRequested(); } + else + { + _SMTCAlbumArtBytes = null; + } - _ = _AlbumArtRefreshRunner.RunAsync(async tokne => + await _albumArtRefreshRunner.RunAsync(async tokne => { await UpdateAlbumArtRelated(tokne); }); if (!token.IsCancellationRequested) { - _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, - () => + _dispatcherQueue.TryEnqueue(() => { SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo)); }); @@ -227,8 +229,9 @@ namespace BetterLyrics.WinUI3.Services () => { _cachedSongInfo = null; + _cachedIsPlaying = false; SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo)); - IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false)); + IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying)); PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero)); }); } @@ -264,22 +267,34 @@ namespace BetterLyrics.WinUI3.Services var decoder = await BitmapDecoder.CreateAsync(stream); token.ThrowIfCancellationRequested(); - _albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied); + var _albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied); token.ThrowIfCancellationRequested(); - _albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault(); + var _albumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault(); _dispatcherQueue.TryEnqueue(() => { - AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs); + AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(_albumArtSwBitmap, _albumArtAccentColor)); }); } private void StartSSE() { - _sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress")).Start(); - _sse.MessageReceived += Sse_MessageReceived; - _sse.Disconnected += Sse_Disconnected; + try + { + _sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress")).Start(); + _sse.MessageReceived += Sse_MessageReceived; + _sse.Disconnected += Sse_Disconnected; + } + catch (Exception) + { + _logger.LogError("Failed to start SSE connection for LX Music."); + _dispatcherQueue.TryEnqueue(() => + { + App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("FailToStartLXMusicServer"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error); + }); + StopSSE(); + } } private void StopSSE() @@ -364,7 +379,7 @@ namespace BetterLyrics.WinUI3.Services } } - public void Receive(PropertyChangedMessage> message) + public async void Receive(PropertyChangedMessage> message) { if (message.Sender is SettingsPageViewModel) { @@ -372,7 +387,7 @@ namespace BetterLyrics.WinUI3.Services { // Album art search providers info changed, re-fetch album art _logger.LogInformation("Album art search providers info changed, refreshing album art."); - _ = _AlbumArtRefreshRunner.RunAsync(async tokne => + await _albumArtRefreshRunner.RunAsync(async tokne => { await UpdateAlbumArtRelated(tokne); }); diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw index 243388a..8057a49 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/en-US/Resources.resw @@ -772,4 +772,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi Frequently asked questions + + Unable to connect to LX Music server, please go to Settings - Advanced options to check if the link is entered correctly + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw index 3205943..ddadf19 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ja-JP/Resources.resw @@ -772,4 +772,7 @@ よくある質問 + + LX Music Serverに接続できない場合は、設定に移動してください。リンクが正しく入力されているかどうかを確認するための高度なオプションに移動してください + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw index e8f369d..6a3f351 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/ko-KR/Resources.resw @@ -772,4 +772,7 @@ 자주 묻는 질문 + + LX Music Server에 연결할 수 없습니다. 설정으로 이동하십시오 - 고급 옵션이 링크가 올바르게 입력되었는지 확인하십시오. + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw index 771b7ea..a20994a 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-CN/Resources.resw @@ -772,4 +772,7 @@ 常见问题与解答 + + 无法连接到 LX 音乐服务器,请转到设置 - 高级选项以检查是否正确输入链接 + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw index 87f5ff5..b022049 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Strings/zh-TW/Resources.resw @@ -772,4 +772,7 @@ 常見問題與解答 + + 無法連接到 LX 音樂服務器,請轉到設置 - 高級選項以檢查是否正確輸入鏈接 + \ No newline at end of file diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs index 4187237..b2f62af 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsPageViewModel.cs @@ -36,6 +36,8 @@ namespace BetterLyrics.WinUI3.ViewModels _playbackService = playbackService; _playbackService.SongInfoChanged += PlaybackService_SongInfoChanged; _playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged; + + IsSongPlaying = _playbackService.IsPlaying; } //private void SystemVolumeHelper_VolumeChanged(int volume) diff --git a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs index cfe91d8..fcdcd70 100644 --- a/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs +++ b/BetterLyrics.WinUI3/BetterLyrics.WinUI3/ViewModels/LyricsRendererViewModel.cs @@ -345,6 +345,7 @@ namespace BetterLyrics.WinUI3.ViewModels _songInfoOpacityTransition.StartTransition(1f); _logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist); + Debug.WriteLine($"Song info changed: Title={_songTitle}, Artist={_songArtist}"); _ = _refreshLyricsRunner.RunAsync(async token => { await RefreshLyricsAsync(token);