Compare commits

...

3 Commits

Author SHA1 Message Date
Zhe Fang
64bf2dc3d9 fix pause music hide window issue 2025-08-17 12:15:05 -04:00
Zhe Fang
b86e4a3d12 split lyrics updater from render to media sessions service 2025-08-17 10:50:48 -04:00
Zhe Fang
411506b9cd fix lyrics display issue 2025-08-16 22:27:38 -04:00
19 changed files with 490 additions and 504 deletions

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.53.0" />
Version="1.0.55.0" />
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>

View File

@@ -177,7 +177,7 @@ namespace BetterLyrics.WinUI3.Helper
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
using var ms = new MemoryStream();
square.Save(ms, new JpegEncoder());
square.Save(ms, new PngEncoder());
return ms.ToArray();
}

View File

@@ -7,11 +7,11 @@ using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public class BackgroundTaskRunner
public class LatestOnlyTaskRunner
{
private CancellationTokenSource? _cts;
public void Run(Func<CancellationToken, Task> taskFactory)
public async Task RunAsync(Func<CancellationToken, Task> taskFactory)
{
_cts?.Cancel();
_cts?.Dispose();
@@ -19,19 +19,16 @@ namespace BetterLyrics.WinUI3.Helper
_cts = new CancellationTokenSource();
var token = _cts.Token;
_ = Task.Run(async () =>
try
{
try
{
await taskFactory(token);
}
catch (OperationCanceledException)
{
}
catch (Exception)
{
}
}, token);
await taskFactory(token);
}
catch (OperationCanceledException)
{
}
catch (Exception)
{
}
}
}
}

View File

@@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsChangedEventArgs : EventArgs
{
}
}

View File

@@ -40,8 +40,8 @@ namespace BetterLyrics.WinUI3.Models
PositionOffset = 1000;
break;
default:
// 设置 100 以防不必要的重复同步
TimelineSyncThreshold = 100;
// 设置 300 以防不必要的重复同步
TimelineSyncThreshold = 300;
PositionOffset = 0;
break;
}

View File

@@ -16,15 +16,5 @@
x:Name="LyricsCanvas"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
<Grid
Margin="36"
HorizontalAlignment="Right"
VerticalAlignment="Top"
Visibility="{x:Bind ViewModel.IsTranslating, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<FontIcon
x:Name="RotatingIcon"
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xE8C1;" />
</Grid>
</Grid>
</UserControl>

View File

@@ -1,9 +1,11 @@
// 2025/6/23 by Zhe Fang
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using TagLib.Riff;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
@@ -12,7 +14,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
Task PlayAsync();
@@ -26,5 +29,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
bool IsPlaying { get; }
SongInfo? SongInfo { get; }
TimeSpan Position { get; }
LyricsData? CurrentLyricsData { get; }
}
}

View File

@@ -0,0 +1,67 @@
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Text;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public partial class MediaSessionsService : IMediaSessionsService
{
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
private void UpdateAlbumArt()
{
_albumArtRefreshRunner.RunAsync(async (token) =>
{
if (_cachedSongInfo == null)
{
_logger.LogWarning("Cached song info is null, cannot update album art.");
return;
}
byte[]? bytes = await _albumArtSearchService.SearchAsync(
SongInfo?.SourceAppUserModelId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
_SMTCAlbumArtBytes
);
token.ThrowIfCancellationRequested();
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba16, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
});
}
}
}

View File

@@ -0,0 +1,233 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public partial class MediaSessionsService : IMediaSessionsService
{
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
private LatestOnlyTaskRunner _showTranslationsRunner = new();
private int _langIndex = 0;
private List<LyricsData> _lyricsDataArr = [];
public LyricsData? CurrentLyricsData => _lyricsDataArr.ElementAtOrDefault(_langIndex);
public event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TranslationSearchProvider? TranslationSearchProvider { get; set; }
[ObservableProperty] public partial bool IsTranslating { get; set; } = false;
private void UpdateTranslations()
{
TranslationSearchProvider = null;
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs());
IsTranslating = true;
if (_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled)
{
_showTranslationsRunner.RunAsync(async token =>
{
await SetDisplayedAlongWithTranslationsAsync(token);
_dispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
IsTranslating = false;
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs());
});
});
}
else
{
_dispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
{
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_langIndex = 0;
IsTranslating = false;
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs());
});
}
}
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
{
_logger.LogInformation("Showing translation for lyrics...");
string targetLangCode = LanguageHelper.SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
if (originalLangCode == targetLangCode)
{
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
}
else
{
// Try get translation from itself first
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
{
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
_langIndex = found;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator, 50);
_langIndex = 0;
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
}
}
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
{
_logger.LogInformation("LibreTranslate is enabled, trying to translate lyrics...");
string translated = string.Empty;
try
{
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
if (token.IsCancellationRequested) return;
if (translated == string.Empty) return;
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
{
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
_langIndex = _lyricsDataArr.Count - 1;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator);
_langIndex = 0;
}
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
}
catch (Exception)
{
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
}
}
}
}
private async Task RefreshLyricsAsync(CancellationToken token)
{
_logger.LogInformation("Refreshing lyrics...");
LyricsSearchProvider = null;
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs());
string? lyricsRaw = null;
LyricsSearchProvider = null;
if (SongInfo != null)
{
_logger.LogInformation("Searching lyrics for: Title={Title}, Artist={Artist}, Album={Album}, DurationMs={DurationMs}",
SongInfo.Title, SongInfo.Artist, SongInfo.Album, SongInfo.DurationMs);
(lyricsRaw, LyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
SongInfo.SourceAppUserModelId ?? "",
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
SongInfo.DurationMs ?? 0,
token
);
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsRaw != null, LyricsSearchProvider?.ToString() ?? "null");
token.ThrowIfCancellationRequested();
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
FillTranslationFromCache(LyricsSearchProvider);
}
else
{
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
}
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
// This ensures that original lyrics are always shown while waiting for translations
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs());
UpdateTranslations();
}
private void FillTranslationFromCache(LyricsSearchProvider? provider)
{
string? translationRaw = null;
switch (provider)
{
case Enums.LyricsSearchProvider.QQ:
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.Kugou:
break;
case Enums.LyricsSearchProvider.Netease:
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.LrcLib:
break;
case Enums.LyricsSearchProvider.AmllTtmlDb:
break;
case Enums.LyricsSearchProvider.LocalMusicFile:
break;
case Enums.LyricsSearchProvider.LocalLrcFile:
break;
case Enums.LyricsSearchProvider.LocalEslrcFile:
break;
case Enums.LyricsSearchProvider.LocalTtmlFile:
break;
default:
break;
}
if (translationRaw != null)
{
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
if (provider == Enums.LyricsSearchProvider.QQ)
{
foreach (var data in translationData)
{
foreach (var item in data.LyricsLines)
{
if (item.OriginalText == "//")
{
item.OriginalText = "";
}
}
}
}
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
}
}
private void UpdateLyrics()
{
_refreshLyricsRunner.RunAsync(async token =>
{
await RefreshLyricsAsync(token);
});
}
}
}

View File

@@ -8,7 +8,11 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -36,13 +40,18 @@ using WindowsMediaController;
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<List<string>>>,
IRecipient<PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>>>
IRecipient<PropertyChangedMessage<string>>,
IRecipient<PropertyChangedMessage<List<string>>>
{
private readonly IAlbumArtSearchService _albumArtSearchService;
private readonly ILogger<MediaSessionsService> _logger;
private readonly ILyricsSearchService _lyrcsSearchService;
private readonly ITranslateService _translateService;
private readonly ISettingsService _settingsService;
private readonly ILibWatcherService _libWatcherService;
private readonly ILiveStatesService _liveStatesService;
private readonly ILogger<MediaSessionsService> _logger;
private double _lxMusicPositionSeconds = 0;
private double _lxMusicDurationSeconds = 0;
@@ -54,8 +63,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private readonly MediaManager _mediaManager = new();
private readonly BackgroundTaskRunner _albumArtRefreshRunner = new();
private readonly BackgroundTaskRunner _onAnyMediaPropertyChangedRunner = new();
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
private SongInfo? _cachedSongInfo;
private byte[]? _SMTCAlbumArtBytes = null;
@@ -63,19 +72,34 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
public MediaSessionsService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService)
public bool IsPlaying => _cachedIsPlaying;
public SongInfo? SongInfo => _cachedSongInfo;
public TimeSpan Position => _cachedPosition;
public MediaSessionsService(
ISettingsService settingsService,
IAlbumArtSearchService albumArtSearchService,
ILyricsSearchService musicSearchService,
ILibWatcherService libWatcherService,
ILiveStatesService liveStatesService,
ITranslateService libreTranslateService)
{
_settingsService = settingsService;
_albumArtSearchService = albumArtSearchService;
_lyrcsSearchService = musicSearchService;
_libWatcherService = libWatcherService;
_translateService = libreTranslateService;
_liveStatesService = liveStatesService;
_logger = Ioc.Default.GetRequiredService<ILogger<MediaSessionsService>>();
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
InitMediaManager();
InitPlaybackShortcuts();
}
@@ -120,12 +144,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
UpdateAlbumArtRelated();
UpdateAlbumArt();
UpdateLyrics();
}
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
UpdateAlbumArtRelated();
UpdateAlbumArt();
UpdateLyrics();
}
private void MediaSourceProvidersInfo_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
@@ -133,16 +159,20 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
switch (e.PropertyName)
{
case nameof(MediaSourceProviderInfo.AlbumArtSearchProvidersInfo):
UpdateAlbumArtRelated();
UpdateAlbumArt();
break;
case nameof(MediaSourceProviderInfo.LyricsSearchProvidersInfo):
UpdateLyrics();
break;
default:
break;
}
}
public bool IsPlaying => _cachedIsPlaying;
public SongInfo? SongInfo => _cachedSongInfo;
public TimeSpan Position => _cachedPosition;
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
UpdateLyrics();
}
private bool IsMediaSourceEnabled(string id)
{
@@ -245,7 +275,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_cachedSongInfo = null;
_onAnyMediaPropertyChangedRunner.Run(async token =>
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
@@ -257,7 +287,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_SMTCAlbumArtBytes = null;
UpdateAlbumArtRelated();
UpdateAlbumArt();
if (!token.IsCancellationRequested)
{
@@ -290,7 +320,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_onAnyMediaPropertyChangedRunner.Run(async token =>
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
@@ -313,7 +343,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_SMTCAlbumArtBytes = null;
}
UpdateAlbumArtRelated();
UpdateAlbumArt();
UpdateLyrics();
if (!token.IsCancellationRequested)
{
@@ -387,54 +418,6 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
MediaManager_OnAnyPlaybackStateChanged(focusedSession, focusedSession.ControlSession.GetPlaybackInfo());
}
private void UpdateAlbumArtRelated()
{
_albumArtRefreshRunner.Run(async (token) =>
{
if (_cachedSongInfo == null)
{
_logger.LogWarning("Cached song info is null, cannot update album art.");
return;
}
byte[]? bytes = await _albumArtSearchService.SearchAsync(
SongInfo?.SourceAppUserModelId ?? "",
_cachedSongInfo.Title,
_cachedSongInfo.Artist,
_cachedSongInfo?.Album ?? string.Empty,
_SMTCAlbumArtBytes
);
token.ThrowIfCancellationRequested();
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
token.ThrowIfCancellationRequested();
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
});
}
private void StartSSE()
{
try
@@ -556,19 +539,24 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
MediaManager_OnFocusedSessionChanged(null);
}
}
else if (message.Sender is AlbumArtSearchProviderInfo)
else if (message.Sender is TranslationSettings)
{
if (message.PropertyName == nameof(AlbumArtSearchProviderInfo.IsEnabled))
if (message.PropertyName == nameof(TranslationSettings.IsLibreTranslateEnabled))
{
UpdateAlbumArtRelated();
UpdateTranslations();
}
else if (message.PropertyName == nameof(TranslationSettings.IsTranslationEnabled))
{
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _settingsService.AppSettings.TranslationSettings.IsTranslationEnabled);
UpdateTranslations();
}
else if (message.PropertyName == nameof(TranslationSettings.ShowTranslationOnly))
{
UpdateTranslations();
}
}
}
public void Receive(PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>> message)
{
}
public void Receive(PropertyChangedMessage<List<string>> message)
{
if (message.Sender is GeneralSettings)
@@ -587,5 +575,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
}
}
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is TranslationSettings)
{
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageIndex))
{
_logger.LogInformation("Target language index changed: {Index}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex);
UpdateTranslations();
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is LyricsStyleSettings)
{
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
{
UpdateTranslations();
}
}
}
}
}

View File

@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
{
public interface ITranslateService
{
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token);
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token);
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr);
}

View File

@@ -25,7 +25,7 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
_httpClient = new HttpClient();
}
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token)
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token)
{
if (string.IsNullOrWhiteSpace(text))
{
@@ -49,12 +49,10 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
new("q", text),
new("source", originalLangCode),
new("target", targetLangCode),
]));
token?.ThrowIfCancellationRequested();
]), token);
response.EnsureSuccessStatusCode();
var json = await response.Content.ReadAsStringAsync();
token?.ThrowIfCancellationRequested();
var json = await response.Content.ReadAsStringAsync(token);
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
return result?.TranslatedText ?? string.Empty;

View File

@@ -1,99 +0,0 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LiveStatesService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
using System.Diagnostics;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public LyricsRendererViewModel(
ISettingsService settingsService,
IMediaSessionsService mediaSessionsService,
ILyricsSearchService musicSearchService,
ILibWatcherService libWatcherService,
ITranslateService libreTranslateService,
ILastFMService lastFMService,
ILiveStatesService liveStatesService
)
{
_settingsService = settingsService;
_lyrcsSearchService = musicSearchService;
_mediaSessionsService = mediaSessionsService;
_libWatcherService = libWatcherService;
_translateService = libreTranslateService;
_liveStatesService = liveStatesService;
_lastFMService = lastFMService;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
AppSettings = _settingsService.AppSettings;
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
_timelineSyncThreshold = 0;
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
_mediaSessionsService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_mediaSessionsService.SongInfoChanged += PlaybackService_SongInfoChanged;
_mediaSessionsService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
_mediaSessionsService.TimelineChanged += PlaybackService_TimelineChanged;
IsPlaying = _mediaSessionsService.IsPlaying;
UpdateColorConfig();
}
private void LocalMediaFolders_ItemPropertyChanged(object? sender, Extensions.ItemPropertyChangedEventArgs e)
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
_refreshLyricsRunner.Run(async tokne =>
{
await RefreshLyricsAsync(tokne);
});
}
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
_refreshLyricsRunner.Run(async tokne =>
{
await RefreshLyricsAsync(tokne);
});
}
private void MediaSourceProvidersInfo_ItemPropertyChanged(object? sender, Extensions.ItemPropertyChangedEventArgs e)
{
switch (e.PropertyName)
{
case nameof(MediaSourceProviderInfo.LyricsSearchProvidersInfo):
_logger.LogInformation("MediaSourceProviderInfo.LyricsSearchProvidersInfo changed, refreshing lyrics.");
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
break;
default:
break;
}
}
}
}

View File

@@ -63,9 +63,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
_drawFrameCount++;
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
var currentPlayingLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine != null)
{
@@ -87,7 +85,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Total line count: {GetMaxLyricsLineIndexBoundaries().Item2 + 1}\n" +
$"Cur time: {TotalTime + _positionOffset}\n" +
$"Lang size: {_lyricsDataArr.Count}\n" +
$"Song duration: {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}",
new Vector2(10, 40),
@@ -206,9 +203,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
var currentPlayingLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine == null)
{
@@ -217,7 +212,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
{
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
var line = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
var textLayout = line.CanvasTextLayout;
@@ -242,7 +237,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// Mock gradient blurred lyrics layer
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
// Current line will not be blurred
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
_liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
using var backgroundEffect = CanvasHelper.CreateBackgroundEffect(line, backgroundFontEffect, _lyricsOpacityTransition.Value);
combinedDs.DrawImage(backgroundEffect);
@@ -257,16 +252,16 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.IsLyricsLineFadeEnabled);
using var lineMask = CanvasHelper.CreateLineMask(control, line);
using var foregroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
using var foregroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
_liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
using var effectLayer = new CanvasCommandList(control);
using var effectLayerDs = effectLayer.CreateDrawingSession();
if (_liveStatesService.LiveStates.CurrentLyricsEffectSettings.IsLyricsShadowEnabled)
{
var shadowEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
var shadowEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsShadowScope);
using var foregroundShadowEffect = CanvasHelper.CreateForegroundShadowEffect(foregroundFontEffect, shadowEffectMask,
using var foregroundShadowEffect = CanvasHelper.CreateForegroundShadowEffect(foregroundFontEffect, shadowEffectMask,
_albumArtAccentColorTransition.Value, _liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsShadowAmount);
effectLayerDs.DrawImage(foregroundShadowEffect);
}

View File

@@ -53,22 +53,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
}
}
else if (message.Sender is TranslationSettings)
{
if (message.PropertyName == nameof(TranslationSettings.IsLibreTranslateEnabled))
{
UpdateTranslations();
}
else if (message.PropertyName == nameof(TranslationSettings.IsTranslationEnabled))
{
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _settingsService.AppSettings.TranslationSettings.IsTranslationEnabled);
UpdateTranslations();
}
else if (message.PropertyName == nameof(TranslationSettings.ShowTranslationOnly))
{
UpdateTranslations();
}
}
else if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
@@ -89,18 +73,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
UpdateIsLastFMTrackEnabled();
}
}
if (message.Sender is LyricsSearchProviderInfo)
{
if (message.PropertyName == nameof(LyricsSearchProviderInfo.IsEnabled))
{
_logger.LogInformation("LyricsSearchProviderInfo.IsEnabled changed, refreshing lyrics.");
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
}
}
}
public void Receive(PropertyChangedMessage<Color> message)
@@ -211,14 +183,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_isLayoutChanged = true;
}
}
else if (message.Sender is TranslationSettings)
{
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageIndex))
{
_logger.LogInformation("Target language index changed: {Index}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex);
UpdateTranslations();
}
}
else if (message.Sender is MediaSourceProviderInfo)
{
if (message.PropertyName == nameof(MediaSourceProviderInfo.TimelineSyncThreshold))
@@ -337,10 +301,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
{
UpdateTranslations();
}
}
}

View File

@@ -336,9 +336,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
double y = 0;
// Init Positions
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
for (int i = 0; i < _mediaSessionsService.CurrentLyricsData?.LyricsLines.Count; i++)
{
var line = _lyricsDataArr[_langIndex].LyricsLines.ElementAtOrDefault(i);
var line = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null)
{
@@ -366,7 +366,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// Set _scrollOffsetY
LyricsLine? currentPlayingLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
LyricsLine? currentPlayingLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine == null) return;
@@ -374,7 +374,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
if (playingTextLayout == null) return;
double? targetYScrollOffset = -currentPlayingLine!.Position.Y + _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2.0;
double? targetYScrollOffset = -currentPlayingLine!.Position.Y + _mediaSessionsService.CurrentLyricsData?.LyricsLines[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2.0;
if (!targetYScrollOffset.HasValue) return;
@@ -385,7 +385,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
var lines = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines;
var lines = _mediaSessionsService.CurrentLyricsData?.LyricsLines;
if (lines == null || lines.Count == 0) return;
double offset = _canvasYScrollTransition.Value + _canvasHeight / 2;
@@ -555,15 +555,13 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private void UpdateVisibleLinesProps(ICanvasAnimatedControl control)
{
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
var currentPlayingLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
if (currentPlayingLine == null) return;
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex + 1; i++)
{
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
var line = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;

View File

@@ -14,6 +14,7 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
@@ -87,21 +88,10 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private double _canvasTargetYScrollOffset = 0;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial LyricsSearchProvider? LyricsSearchProvider { get; set; } = null;
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
private double _maxLyricsWidth = 0f;
private readonly ISettingsService _settingsService;
private readonly ILyricsSearchService _lyrcsSearchService;
private readonly ILibWatcherService _libWatcherService;
private readonly IMediaSessionsService _mediaSessionsService;
private readonly ITranslateService _translateService;
private readonly ILastFMService _lastFMService;
private readonly ILiveStatesService _liveStatesService;
private readonly ILogger _logger;
@@ -142,10 +132,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private bool _isLayoutChanged = true;
private int _langIndex = 0;
private List<LyricsData> _lyricsDataArr = [];
private int _timelineSyncThreshold = 0;
private CanvasTextFormat _lyricsTextFormat = new()
@@ -178,14 +164,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
FontWeight = FontWeights.ExtraBlack,
};
private BackgroundTaskRunner _refreshLyricsRunner = new();
private BackgroundTaskRunner _showTranslationsRunner = new();
private LyricsLayoutOrientation _lyricsLayoutOrientation;
[ObservableProperty]
public partial bool IsTranslating { get; set; } = false;
[ObservableProperty]
public partial SongInfo? SongInfo { get; set; }
@@ -193,16 +173,52 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
[NotifyPropertyChangedRecipients]
public partial ElementTheme ThemeTypeSent { get; set; }
public LyricsRendererViewModel(
ISettingsService settingsService,
IMediaSessionsService mediaSessionsService,
ILastFMService lastFMService,
ILiveStatesService liveStatesService)
{
_settingsService = settingsService;
_mediaSessionsService = mediaSessionsService;
_liveStatesService = liveStatesService;
_lastFMService = lastFMService;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
AppSettings = _settingsService.AppSettings;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
_timelineSyncThreshold = 0;
_mediaSessionsService.IsPlayingChanged += MediaSessionsService_IsPlayingChanged;
_mediaSessionsService.SongInfoChanged += MediaSessionsService_SongInfoChanged;
_mediaSessionsService.AlbumArtChanged += MediaSessionsService_AlbumArtChangedChanged;
_mediaSessionsService.LyricsChanged += MediaSessionsService_LyricsChanged;
_mediaSessionsService.TimelineChanged += MediaSessionsService_TimelineChanged;
IsPlaying = _mediaSessionsService.IsPlaying;
UpdateColorConfig();
}
private void MediaSessionsService_LyricsChanged(object? sender, LyricsChangedEventArgs e)
{
_isLayoutChanged = true;
}
private int GetCurrentPlayingLineIndex()
{
var totalMs = TotalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
if (totalMs < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
if (totalMs < _mediaSessionsService.CurrentLyricsData?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
for (int i = 0; i < _mediaSessionsService.CurrentLyricsData?.LyricsLines.Count; i++)
{
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
var line = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(i);
if (line == null) continue;
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i + 1);
var nextLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(i + 1);
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
{
return i;
@@ -222,9 +238,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
charLength = 0;
charProgress = 0f;
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex);
var line = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex);
if (line == null) return;
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
var nextLine = _mediaSessionsService.CurrentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
int lineEndMs;
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
@@ -309,31 +325,22 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
if (
SongInfo == null
|| _lyricsDataArr.ElementAtOrDefault(_langIndex) == null
|| _lyricsDataArr[_langIndex].LyricsLines.Count == 0
|| _mediaSessionsService.CurrentLyricsData == null
|| _mediaSessionsService.CurrentLyricsData.LyricsLines.Count == 0
)
{
return new Tuple<int, int>(-1, -1);
}
return new Tuple<int, int>(0, _lyricsDataArr[_langIndex].LyricsLines.Count - 1);
return new Tuple<int, int>(0, _mediaSessionsService.CurrentLyricsData.LyricsLines.Count - 1);
}
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
{
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
}
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
private void MediaSessionsService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
{
IsPlaying = e.IsPlaying;
}
private void PlaybackService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
private void MediaSessionsService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
{
var diff = Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds);
if (diff >= _timelineSyncThreshold)
@@ -352,7 +359,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
private void MediaSessionsService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
{
SongInfo = e.SongInfo;
@@ -373,11 +380,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_songInfoOpacityTransition.Reset(0f);
_songInfoOpacityTransition.StartTransition(1f);
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
_refreshLyricsRunner.Run(async token =>
{
await RefreshLyricsAsync(token);
});
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
@@ -386,7 +388,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
private void PlaybackService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
private void MediaSessionsService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
{
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
{
@@ -420,188 +422,5 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
e.AlbumArtSwBitmap?.Dispose();
}
}
private void UpdateTranslations()
{
TranslationSearchProvider = null;
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_isLayoutChanged = true;
IsTranslating = true;
if (_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled)
{
_refreshLyricsRunner.Run(async token =>
{
await SetDisplayedAlongWithTranslationsAsync(token);
IsTranslating = false;
_isLayoutChanged = true;
});
}
else
{
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
_langIndex = 0;
IsTranslating = false;
_isLayoutChanged = true;
}
}
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
{
_logger.LogInformation("Showing translation for lyrics...");
string targetLangCode = LanguageHelper.SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
if (originalLangCode == targetLangCode)
{
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
}
else
{
// Try get translation from itself first
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
{
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
_langIndex = found;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator, 50);
_langIndex = 0;
}
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
}
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
{
_logger.LogInformation("LibreTranslate is enabled, trying to translate lyrics...");
string translated = string.Empty;
try
{
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
if (translated == string.Empty) return;
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
{
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
_langIndex = _lyricsDataArr.Count - 1;
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator);
_langIndex = 0;
}
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
token.ThrowIfCancellationRequested();
}
catch (Exception)
{
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
}
}
}
}
private async Task RefreshLyricsAsync(CancellationToken token)
{
LyricsSearchProvider = null;
_logger.LogInformation("Refreshing lyrics...");
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
_isLayoutChanged = true;
string? lyricsRaw = null;
LyricsSearchProvider? lyricsSearchProvider = null;
if (SongInfo != null)
{
_logger.LogInformation("Searching lyrics for: Title={Title}, Artist={Artist}, Album={Album}, DurationMs={DurationMs}",
SongInfo.Title, SongInfo.Artist, SongInfo.Album, SongInfo.DurationMs);
(lyricsRaw, lyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
SongInfo.SourceAppUserModelId ?? "",
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
SongInfo.DurationMs ?? 0,
token
);
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsRaw != null, lyricsSearchProvider?.ToString() ?? "null");
LyricsSearchProvider = lyricsSearchProvider;
token.ThrowIfCancellationRequested();
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
FillTranslationFromCache(LyricsSearchProvider);
}
else
{
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
}
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
// This ensures that original lyrics are always shown while waiting for translations
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
_isLayoutChanged = true;
UpdateTranslations();
}
private void FillTranslationFromCache(LyricsSearchProvider? provider)
{
string? translationRaw = null;
switch (provider)
{
case Enums.LyricsSearchProvider.QQ:
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.Kugou:
break;
case Enums.LyricsSearchProvider.Netease:
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.LrcLib:
break;
case Enums.LyricsSearchProvider.AmllTtmlDb:
break;
case Enums.LyricsSearchProvider.LocalMusicFile:
break;
case Enums.LyricsSearchProvider.LocalLrcFile:
break;
case Enums.LyricsSearchProvider.LocalEslrcFile:
break;
case Enums.LyricsSearchProvider.LocalTtmlFile:
break;
default:
break;
}
if (translationRaw != null)
{
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
if (provider == Enums.LyricsSearchProvider.QQ)
{
foreach (var data in translationData)
{
foreach (var item in data.LyricsLines)
{
if (item.OriginalText == "//")
{
item.OriginalText = "";
}
}
}
}
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
}
}
}
}

View File

@@ -120,6 +120,7 @@ namespace BetterLyrics.WinUI3
private void UpdateDockOrDesktopWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
@@ -137,7 +138,7 @@ namespace BetterLyrics.WinUI3
}
else
{
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode)
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode)
{
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
}

View File

@@ -121,7 +121,7 @@ namespace BetterLyrics.WinUI3.ViewModels
try
{
string targetLangCode = LanguageHelper.SupportedTargetLanguages[AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, new System.Threading.CancellationToken());
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
@@ -183,9 +183,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<LyricsSearchProvider?> message)
{
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
if (message.Sender is MediaSessionsService)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.LyricsSearchProvider))
if (message.PropertyName == nameof(MediaSessionsService.LyricsSearchProvider))
{
LyricsSearchProvider = message.NewValue;
}
@@ -194,9 +194,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<TranslationSearchProvider?> message)
{
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
if (message.Sender is MediaSessionsService)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.TranslationSearchProvider))
if (message.PropertyName == nameof(MediaSessionsService.TranslationSearchProvider))
{
TranslationSearchProvider = message.NewValue;
}