fix playbackservice

This commit is contained in:
Zhe Fang
2025-07-17 13:04:50 -04:00
parent 5a4fe54ac2
commit 153679228d
13 changed files with 110 additions and 60 deletions

View File

@@ -51,6 +51,7 @@
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />

View File

@@ -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;
}
}

View File

@@ -167,9 +167,11 @@ namespace BetterLyrics.WinUI3.Helper
public static async Task<byte[]> 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)

View File

@@ -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<CancellationToken, Task> func)
public async Task RunAsync(Func<CancellationToken, Task> 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();
}
}
}

View File

@@ -19,6 +19,8 @@ namespace BetterLyrics.WinUI3.Services
Task PauseAsync();
Task PreviousAsync();
Task NextAsync();
bool IsPlaying { get; }
}
}

View File

@@ -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<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
private byte[]? _SMTCAlbumArtBytes = null;
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new();
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<PositionChangedEventArgs>? 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<ObservableCollection<AlbumArtSearchProviderInfo>> message)
public async void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> 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);
});

View File

@@ -772,4 +772,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>Frequently asked questions</value>
</data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>Unable to connect to LX Music server, please go to Settings - Advanced options to check if the link is entered correctly</value>
</data>
</root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>よくある質問</value>
</data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>LX Music Serverに接続できない場合は、設定に移動してください。リンクが正しく入力されているかどうかを確認するための高度なオプションに移動してください</value>
</data>
</root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>자주 묻는 질문</value>
</data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>LX Music Server에 연결할 수 없습니다. 설정으로 이동하십시오 - 고급 옵션이 링크가 올바르게 입력되었는지 확인하십시오.</value>
</data>
</root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>常见问题与解答</value>
</data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>无法连接到 LX 音乐服务器,请转到设置 - 高级选项以检查是否正确输入链接</value>
</data>
</root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>常見問題與解答</value>
</data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>無法連接到 LX 音樂服務器,請轉到設置 - 高級選項以檢查是否正確輸入鏈接</value>
</data>
</root>

View File

@@ -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)

View File

@@ -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);