Compare commits

..

8 Commits

Author SHA1 Message Date
Zhe Fang
a207224bbf chores: add zh edition 2025-11-03 12:36:16 -05:00
Zhe Fang
2c80e0815a Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-11-03 11:31:09 -05:00
Zhe Fang
7f65b4addb fix: handle device lost event 2025-11-03 11:31:07 -05:00
Zhe Fang
210df4dd61 Update README.md 2025-11-02 21:33:45 -05:00
Zhe Fang
3c82908890 Update README.md 2025-11-02 21:01:01 -05:00
Zhe Fang
597cc1ec9b Update README.md 2025-11-02 20:59:29 -05:00
Zhe Fang
e354e29ec1 chores: update version code 2025-11-02 19:10:46 -05:00
Zhe Fang
dd5fb91764 chores: add delay when automatically show and hide window 2025-11-02 17:16:40 -05:00
24 changed files with 400 additions and 175 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -117,6 +117,9 @@
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AlbumArtPlaceholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AMLLPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI;
using static Vanara.PInvoke.Ole32;
@@ -46,43 +47,14 @@ namespace BetterLyrics.WinUI3.Helper
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<IRandomAccessStream> CreateTextPlaceholderBytesAsync(int width, int height)
public static async Task<IRandomAccessStream> GetAlbumArtPlaceholderAsync()
{
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 随机生成渐变色
Windows.UI.Color RandomColor()
{
var rand = new Random(Guid.NewGuid().GetHashCode());
double h = rand.NextDouble() * 360;
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
}
Windows.UI.Color color1 = RandomColor();
Windows.UI.Color color2 = RandomColor();
using (var ds = renderTarget.CreateDrawingSession())
{
// 绘制线性渐变背景
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
};
ds.FillRectangle(0, 0, width, height, gradientBrush);
}
// 保存为 PNG 并转为 byte[]
var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
stream.Seek(0);
Uri uri = new Uri($"ms-appx:///Assets/AlbumArtPlaceholder.png");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
return stream;
}
public static Task<ThemeColorResult> GetAccentColorAsync(BitmapDecoder decoder, PaletteGeneratorType generatorType)
{
return generatorType switch

View File

@@ -6,6 +6,7 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
@@ -31,6 +32,8 @@ namespace BetterLyrics.WinUI3.Helper
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private static DispatcherQueueTimer? _setLyricsWindowVisibilityByPlayingStatusTimer;
public static void HideWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
@@ -346,27 +349,36 @@ namespace BetterLyrics.WinUI3.Helper
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
}
public static void SetLyricsWindowVisibilityByPlayingStatus()
/// <summary>
///
/// </summary>
/// <param name="dispatcherQueue">请确保此参数指向同一个对象,建议传值 BaseViewModel._dispatcherQueue</param>
public static void SetLyricsWindowVisibilityByPlayingStatus(DispatcherQueue dispatcherQueue)
{
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
_setLyricsWindowVisibilityByPlayingStatusTimer ??= dispatcherQueue.CreateTimer();
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(false);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(false);
}
HideWindow<LyricsWindow>();
}
HideWindow<LyricsWindow>();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(true);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(true);
}
OpenOrShowWindow<LyricsWindow>();
}
OpenOrShowWindow<LyricsWindow>();
}
}, Constants.Time.DebounceTimeout);
}
}

View File

@@ -91,11 +91,6 @@ namespace BetterLyrics.WinUI3.Models
this.OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
partial void OnAutoShowOrHideWindowChanged(bool value)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
}
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using Windows.Graphics.Imaging;
using Windows.UI;
@@ -31,4 +32,14 @@ namespace BetterLyrics.WinUI3.Models
public SongInfo() { }
}
public static class SongInfoExtensions
{
public static SongInfo Placeholder => new()
{
Title = "N/A",
Album = "N/A",
Artist = "N/A",
};
}
}

View File

@@ -14,6 +14,7 @@
<Grid>
<canvas:CanvasAnimatedControl
x:Name="LyricsCanvas"
CreateResources="LyricsCanvas_CreateResources"
Draw="LyricsCanvas_Draw"
Update="LyricsCanvas_Update" />
</Grid>

View File

@@ -35,5 +35,10 @@ namespace BetterLyrics.WinUI3.Renderer
LyricsCanvas.RemoveFromVisualTree();
LyricsCanvas = null;
}
private void LyricsCanvas_CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
ViewModel.CreateResources(sender, args);
}
}
}

View File

@@ -93,6 +93,9 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
break;
default:
break;
}
@@ -134,7 +137,7 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)

View File

@@ -46,7 +46,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (buffer == null)
{
using var placeHolderStream = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
var tempBuffer = new Windows.Storage.Streams.Buffer((uint)placeHolderStream.Size);
await placeHolderStream.ReadAsync(tempBuffer, (uint)placeHolderStream.Size, InputStreamOptions.None);
buffer = tempBuffer;

View File

@@ -69,7 +69,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == targetLangCode)
{
@@ -83,7 +83,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetLangCode);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
_logger.LogInformation("Found translated text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetTranslatedText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
@@ -119,7 +119,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (originalText == null) return;
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
_logger.LogInformation("Original phonetic code: {OriginalLangCode}", originalLangCode);
if (originalLangCode == "zh" && _settingsService.AppSettings.TranslationSettings.IsChineseRomanizationEnabled)
{
@@ -139,7 +139,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr, targetPhoneticCode);
if (found >= 0)
{
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
_logger.LogInformation("Found phonetic text in lyrics data at index {FoundIndex}", found);
_lyricsDataArr[0].SetPhoneticText(_lyricsDataArr[found], _liveStatesService.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsTranslationSeparator, 50);
}
@@ -174,7 +174,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (token.IsCancellationRequested) return;
LyricsSearchProvider = lyricsSearchResult?.Provider;
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsSearchResult?.IsFound, LyricsSearchProvider?.ToString() ?? "null");
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsSearchResult?.IsFound, LyricsSearchProvider);
var lyricsParser = new LyricsParser();
lyricsParser.Parse(

View File

@@ -213,14 +213,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
}
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
private async void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
{
if (!_mediaManager.IsStarted) return;
SendFocusedMessagesAsync();
await SendFocusedMessagesAsync();
}
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
@@ -241,16 +241,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
{
_cachedPosition = timelineProperties.Position;
_cachedPosition = timelineProperties?.Position ?? TimeSpan.Zero;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties?.EndTime ?? TimeSpan.Zero));
});
}
}
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
@@ -268,7 +268,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
else
{
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
_cachedIsPlaying = playbackInfo?.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
@@ -279,26 +279,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
});
}
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProperties)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
if (mediaSession == null)
{
_cachedSongInfo = SongInfoExtensions.Placeholder;
}
string sessionId = mediaSession.Id;
string? sessionId = mediaSession?.Id;
var desiredSession = GetCurrentSession();
//RecordMediaSourceProviderInfo(mediaSession);
if (mediaSession != desiredSession) return;
if (!IsMediaSourceEnabled(sessionId))
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -315,33 +317,33 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string fixedArtist = mediaProperties.Artist;
string fixedAlbum = mediaProperties.AlbumTitle;
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? songId = null;
if (sessionId == Constants.PlayerID.AppleMusic || sessionId == Constants.PlayerID.AppleMusicAlternative)
{
fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
}
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId))
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId ?? ""))
{
songId = mediaProperties.Genres.FirstOrDefault()?.Replace("NCM-", "");
songId = mediaProperties?.Genres.FirstOrDefault()?.Replace("NCM-", "");
}
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Title = mediaProperties?.Title ?? "N/A",
Artist = fixedArtist,
Album = fixedAlbum,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds,
PlayerId = sessionId,
SongId = songId
};
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_cachedSongInfo.Duration = (int)((_cachedSongInfo.DurationMs ?? 0) / 1000f);
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -356,7 +358,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
@@ -438,7 +440,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_cachedIsPlaying = false;
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
@@ -448,14 +450,21 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private async Task SendFocusedMessagesAsync()
{
var desiredSession = GetCurrentSession();
if (desiredSession == null || desiredSession.ControlSession == null) return;
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
var mediaProps = await desiredSession.ControlSession.TryGetMediaPropertiesAsync();
if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession.ControlSession.GetTimelineProperties());
var desiredSession = GetCurrentSession();
//if (desiredSession == null || desiredSession.ControlSession == null) return;
try
{
mediaProps = await desiredSession?.ControlSession?.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
//if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession?.ControlSession?.GetTimelineProperties());
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession.ControlSession.GetPlaybackInfo());
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession?.ControlSession?.GetPlaybackInfo());
}
private void StartSSE()
@@ -654,7 +663,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageCode))
{
_logger.LogInformation("Target language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
_logger.LogInformation("Target LibreTranslate language code changed: {code}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageCode);
UpdateTranslations();
}
}

View File

@@ -19,10 +19,14 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
// 新建一个 AppSettings 类
public partial class SettingsService : BaseViewModel, ISettingsService
{
private DispatcherQueueTimer _writeAppSettingsTimer;
public AppSettings AppSettings { get; set; }
public SettingsService()
{
_writeAppSettingsTimer = _dispatcherQueue.CreateTimer();
AppSettings = ReadAppSettings();
AppSettings.PropertyChanged += AppSettings_PropertyChanged;
@@ -94,12 +98,12 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
private void AppSettings_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -112,7 +116,7 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
default:
break;
}
WriteAppSettingsDebounce();
WriteAppSettings();
}
/// <summary>
@@ -161,9 +165,9 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
return data;
}
private void WriteAppSettingsDebounce()
private void WriteAppSettings()
{
_dispatcherQueueTimer.Debounce(() =>
_writeAppSettingsTimer.Debounce(() =>
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{

View File

@@ -11,14 +11,12 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class BaseViewModel : ObservableRecipient
{
private protected readonly DispatcherQueue _dispatcherQueue;
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
public BaseViewModel()
{
IsActive = true;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
}
}
}

View File

@@ -0,0 +1,30 @@
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public void CreateResources(Microsoft.Graphics.Canvas.UI.Xaml.CanvasAnimatedControl sender, Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesEventArgs args)
{
_logger.LogInformation("Creating resources... Reason: {Reason}", args.Reason);
switch (args.Reason)
{
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.FirstTime:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.NewDevice:
_isDeviceChanged = true;
break;
case Microsoft.Graphics.Canvas.UI.CanvasCreateResourcesReason.DpiChanged:
break;
default:
break;
}
}
}
}

View File

@@ -26,6 +26,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private bool _isLayoutChanged = true;
private bool _isCanvasWidthChanged = false;
private bool _isCanvasHeightChanged = false;
@@ -60,6 +62,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
private bool _isLyrics3DMatrixChanged = true;
private bool _isDeviceChanged = true;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
_elapsedTime = args.Timing.ElapsedTime;
@@ -77,7 +81,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
//_effect?.Properties["iTime"] = Convert.ToSingle(TotalTime.TotalSeconds);
if (_isFluidOverlayEnabledChanged)
if (_isDeviceChanged || _isFluidOverlayEnabledChanged)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsFluidOverlayEnabled)
{
@@ -142,8 +146,11 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_isDebugOverlayEnabledChanged = false;
}
_rotateAngle += _coverRotateBaseSpeed * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed / 100.0;
_rotateAngle %= Math.PI * 2;
if (_liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed > 0)
{
_rotateAngle += _coverRotateBaseSpeed * _liveStatesService.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.CoverOverlaySpeed / 100.0;
_rotateAngle %= Math.PI * 2;
}
if (_isSpectrumOverlayEnabledChanged)
{
@@ -176,7 +183,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// }
//}
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
if (_isDeviceChanged || _isCanvasWidthChanged || _isCanvasHeightChanged)
{
UpdateSongInfoFontSize();
@@ -200,7 +207,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
}
}
if (_isDisplayTypeChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
if (_isDeviceChanged || _isDisplayTypeChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
_isSongInfoFontSizeChanged || _isSongTitleVisibilityChanged || _isSongArtistsVisibilityChanged ||
_isCanvasWidthChanged || _isCanvasHeightChanged ||
_isAlbumArtSizeChanged)
@@ -320,20 +327,18 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
// 将当前背景图放到 _lastAlbumArtSwBitmap 中 并设置不透明度为 1
// 将新的背景图放到 _albumArtSwBitmap 中 并设置不透明度为 0
// 这样可以实现背景图的连贯渐变效果
if (_albumArtChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
if (_isDeviceChanged || _albumArtChanged || _isLyricsLayoutOrientationChanged || _isAlbumArtSizeChanged ||
_isCanvasHeightChanged || _isCanvasWidthChanged ||
_lyricsBgBrightnessTransition.IsTransitioning ||
_albumArtBgTransition.IsTransitioning)
{
// 必须先在此处重置动画
if (_albumArtChanged)
if (_isDeviceChanged || _albumArtChanged)
{
// 必须先在此处重置动画
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
}
// 更新 last 和 current
if (_albumArtChanged)
{
// 更新 last 和 current
if (_lastAlbumArtSwBitmap != null)
{
_lastAlbumArtCanvasBitmap?.Dispose();
@@ -360,50 +365,37 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_isLyricsLayoutOrientationChanged = false;
_isAlbumArtSizeChanged = false;
if (_isCoverAcrylicEffectAmountChanged)
if (_isDeviceChanged || _isAlbumArtBgOpacityChanged || _isAlbumArtBgBlurAmountChanged || _isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
if (_isDeviceChanged || _isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
}
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isCoverAcrylicEffectAmountChanged = false;
}
if (_isAlbumArtBgOpacityChanged)
{
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isAlbumArtBgOpacityChanged = false;
}
if (_isAlbumArtBgBlurAmountChanged)
{
DisposeAlbumArtBgRenderTarget();
UpdateAlbumArtBgEffect(control);
_isAlbumArtBgEffectChanged = true;
_isAlbumArtBgBlurAmountChanged = false;
_isCoverAcrylicEffectAmountChanged = false;
}
_albumArtChanged = false;
if (!_isAlbumArtEffectChanged && _albumArtEffect != null)
if (_isDeviceChanged || (!_isAlbumArtEffectChanged && _albumArtEffect != null))
{
UpdateAlbumArtRenderTarget(control);
DisposeAlbumArtEffect();
}
if (!_isAlbumArtBgEffectChanged && _albumArtBgEffect != null)
if (_isDeviceChanged || (!_isAlbumArtBgEffectChanged && _albumArtBgEffect != null))
{
UpdateAlbumArtBgRenderTarget(control);
DisposeAlbumArtBgEffect();
}
if (_isCanvasHeightChanged || _isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
if (_isDeviceChanged || _isCanvasHeightChanged || _isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
_maxLyricsWidth = _canvasWidth - _lyricsXTransition.Value - _rightMargin;
_maxLyricsWidth = Math.Max(_maxLyricsWidth, 0);
@@ -458,6 +450,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
_canvasYScrollTransition.Update(_elapsedTime);
_isDeviceChanged = false;
}
private string AutoSelectFontFamily(string text)

View File

@@ -130,8 +130,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
[ObservableProperty]
public partial bool IsPlaying { get; set; } = false;
private bool _isLayoutChanged = true;
private int _timelineSyncThreshold = 0;
private int _phoneticLyricsFontSize = 18;

View File

@@ -16,13 +16,13 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using Vanara.PInvoke;
using Windows.Foundation;
using Windows.System;
using Windows.UI;
using WinRT.Interop;
using WinUIEx;
@@ -39,6 +39,7 @@ namespace BetterLyrics.WinUI3
private readonly ILiveStatesService _liveStatesService;
private ForegroundWindowWatcher? _fgWindowWatcher = null;
private DispatcherQueueTimer? _fgWindowWatcherTimer = null;
public LyricsWindowViewModel(ISettingsService settingsService, IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
{
@@ -54,7 +55,7 @@ namespace BetterLyrics.WinUI3
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
}
[ObservableProperty] public partial AppSettings AppSettings { get; set; }
@@ -142,11 +143,13 @@ namespace BetterLyrics.WinUI3
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
_fgWindowWatcherTimer = _dispatcherQueue.CreateTimer();
_fgWindowWatcher = new ForegroundWindowWatcher(
hwnd,
fgHwnd =>
{
_dispatcherQueueTimer.Debounce(() =>
_fgWindowWatcherTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTop &&
_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling &&

View File

@@ -37,6 +37,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly MediaTimelineController _timelineController = new();
private readonly SystemMediaTransportControls _smtc;
private readonly DispatcherQueueTimer _refreshSongsTimer;
// All songs
private List<Track> _tracks = [];
// Songs in current playlist
@@ -95,6 +97,8 @@ namespace BetterLyrics.WinUI3.ViewModels
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService)
{
_refreshSongsTimer = _dispatcherQueue.CreateTimer();
_settingsService = settingsService;
AppSettings = _settingsService.AppSettings;
@@ -263,7 +267,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public void RefreshSongs()
{
_dispatcherQueueTimer.Debounce(() =>
_refreshSongsTimer.Debounce(() =>
{
IsDataLoading = true;
_tracks.Clear();

View File

@@ -29,35 +29,6 @@
<dev:SnowFlakeEffect FlakeCount="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.SnowFlakeOverlayAmount, Mode=OneWay}" Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<!-- No music playing placeholder -->
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
x:Uid="MainPageNoMusicPlaying"
HorizontalAlignment="Center"
FontFamily="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily, Mode=OneWay}"
FontSize="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.OriginalLyricsFontSize, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="Equal"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<!-- Bottom command area -->
<Grid
x:Name="BottomCommandGrid"

View File

@@ -1 +1,201 @@
[原页面已更改,点此返回项目主页](https://github.com/jayfunc/BetterLyrics)
![](Promotion/banner.png)
<div align=center>
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="96">
</div>
<h2 align=center>
BetterLyrics
</h2>
<h4 align="center">
🤩 一款优雅且高度自定义的歌词/播放器应用,基于 WinUI3/Win2D 构建
</h4>
<div align="center">
[**_📖 点按此处浏览软件操作说明_**](https://github.com/jayfunc/BetterLyrics/wiki)
</div>
<div align=center>
![Static Badge](https://img.shields.io/badge/Language-C%23-purple) ![Static Badge](https://img.shields.io/badge/License-MIT-red) ![Static Badge](https://img.shields.io/badge/IDE-Visual%20Studio-purple) ![Static Badge](https://img.shields.io/badge/Framework-WinUI%203-blue)
</div>
<div align=center>
[![GitHub Repo stars](https://img.shields.io/github/stars/jayfunc/BetterLyrics)](https://github.com/jayfunc/BetterLyrics/stargazers)
</div>
<div align="center">
<mark>**_💞 BetterLyrics 的发展离不开每一位贡献者、反馈者和用户的全力支持。_**</mark>
</div>
## 🎉 该项目入选少数派推荐文章!
文章链接:[BetterLyrics - 一款专为 Windows 打造的沉浸式流畅歌词显示软件](https://sspai.com/post/101028)
## 🔈 反馈交流群
[QQ 群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) | [Discord Server](https://discord.gg/5yAQPnyCKv) | [Telegram Group](https://t.me/+svhSLZ7awPsxNGY1)
## 🌟 特色功能
- 🌠 **精美的用户界面**
- 流畅、高度自定义的样式、动画、动效
- 沉浸式流体背景
- 透视/扇形歌词
- 雪花效果
- 多种歌词滚动函数
- ...
- ↔️ **强大的歌词翻译**
- 本地机器翻译 (支持 30 多种语言)
- 自动读取本地音乐文件内嵌歌词
- 🧩 **多种歌词源**
- 💾 本地源
- 音乐文件 (内嵌歌词)
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) 文件 (传统格式、增强格式)
- [.eslrc](https://github.com/ESLyric/release) 文件
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
- ☁️ 在线源
- QQ 音乐
- 网易云音乐
- 酷狗音乐
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
- [LRCLIB](https://lrclib.net/)
- <details><summary>⚠️ Apple Music (需要额外配置)</summary>
- 浏览器打开 Apple Music打开开发者工具。刷新网页回到开发者工具窗口筛选出 Fetch/XHR选择一个请求在请求标头中找到 media-user-token 并复制其值。
- 打开 BetterLyrics 转到播放源设置。在 Media-User-Token (for Apple Music) 中粘贴复制的值并点按右侧对勾。
- 🎶 **支持众多音乐播放器**
- 点击 [此处](https://github.com/jayfunc/BetterLyrics/wiki/Known-supported-music-players-(configuration-guidance)) 查看详细信息
- 🪟 **多种显示模式**
- **标准模式**
- 标准的歌词窗口样式,沉浸式的音乐歌词体验。
- **停靠模式**
- 停靠在屏幕上/下边缘的轻量歌词窗口,工作休闲互不打扰。
- **桌面模式**
- 悬浮在所有应用上层,不能被选中,但能直击你的使用需求。
- **更多模式...**
- 等你来发现...
- 🧠 **智能化行为**
- 根据歌曲播放状态自动显隐歌词窗口
## 屏幕截图
![](Screenshots/fs2.png)
![](Screenshots/std.png)
![](Screenshots/narrow.png)
![](Screenshots/Snipaste_2025-10-31_19-23-17.png)
![](Screenshots/Snipaste_2025-10-31_19-27-34.png)
![](Screenshots/dock.png)
![](Screenshots/desktop.png)
> ⚠️ 由于 GIF 格式帧率限制,效果仅作展示。请以实机效果为准。
![](Screenshots/PixPin_2025-10-24_18-13-44.gif)
![](Screenshots/PixPin_2025-10-24_18-17-17.gif)
## 演示
在 [哔哩哔哩](https://www.bilibili.com/video/BV1QRstz1EGt/) 上观看于 2025 年 10 月 21 日上传的演示视频
## 即刻体验
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" width="200"/>
</a>
**无限期**免费试用版和付费版**无任何区别**
☕ 如果喜欢该软件,请考虑 [捐赠](#捐赠) 或在 **Microsoft Store** 🧧 购买, 感谢您的支持! 🥰
无法从 Microsoft Store 下载?点按 [此处](https://github.com/jayfunc/BetterLyrics/wiki/Alternative-way-to-download-and-install) 查看其他下载安装方式
## 构建
在构建之前确保替换文件 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFMTemplate``BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFM.cs`
## 💖 感谢
| 项目/包 | 描述 |
| :--- | :--- |
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | 为 QQ、网易、酷狗在线歌词源提供歌词抓取、解密、解析等一系列方法 |
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB 歌词 API |
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | Apple Music 歌词抓取Python 实现) |
| [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) | 从音乐文件提取图片 |
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | 提供有关窗口的开箱即用的 Win32 API |
| [TagLib#](https://github.com/mono/taglib-sharp) | 读取音乐文件内嵌的原始歌词内容 |
| [Vanara](https://github.com/dahall/Vanara) | 提供开箱即用的 Win32 API |
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | 离线翻译核心 |
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | 动态流体背景 |
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | 频谱图 |
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | 为 WinUI3 提供众多开箱即用的功能 |
| ... | ... |
### 教程、博客等
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
- [Bilibili -【WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D 中的游戏循环CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
## 💡 灵感来源
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
## ✍️ 协助翻译
找不到你的语言?有更好的翻译?没关系!😆
现在访问 https://crowdin.com/project/betterlyrics/invite?h=c9bfb28fce061484883c0891e7a26f9b2592556 即刻为本应用提供翻译,成为贡献者!
## 星标记录
<div style="display: flex; justify-content: space-around; align-items: flex-start;">
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date" width="100%" >
</div>
## 欢迎反馈问题、提交代码
如果发现 Bug 请在 Issues 内提出,同时也欢迎任何想法、建议。
## 捐赠
如果你喜欢本应用,请考虑捐赠支持开发者。这将有助于本应用的长远发展。
通过以下途径捐赠:
- [PayPal](https://paypal.me/zhefangpay)
- [Buy Me a Coffee](https://buymeacoffee.com/founchoo)
- <details><summary>支付宝</summary>
![](Donate/Alipay.jpg)
</detais>
- <details><summary>微信</summary>
![](Donate/WeChatReward.png)
</details>
## ⚠️ 免责声明
本项目按“原样”提供,不提供任何形式的担保。
所有歌词、字体、图标及其他第三方资源均为其各自版权所有者的财产。
本项目作者不主张对这些资源的所有权。
本项目为非商业用途,不得用于侵犯任何权利。
用户有责任确保其使用符合适用的法律和许可协议。

View File

@@ -9,17 +9,17 @@ BetterLyrics
</h2>
<h4 align="center">
Your dynamic lyrics display tool, built with WinUI 3 and Win2D, works with in-app playback and other players
🤩 An elegant and deeply customizable lyrics & player app, built with WinUI3/Win2D
</h4>
<div align="center">
[**_Click here to view wiki_**](https://github.com/jayfunc/BetterLyrics/wiki)
[**_📖 Click here to view wiki_**](https://github.com/jayfunc/BetterLyrics/wiki)
</div>
<div align=center>
![Static Badge](https://img.shields.io/badge/Language-C%23-purple) ![Static Badge](https://img.shields.io/badge/License-MIT-red) ![Static Badge](https://img.shields.io/badge/IDE-Visual%20Studio-purple) ![Static Badge](https://img.shields.io/badge/Framework-WinUI%203-blue)
</div>
@@ -30,6 +30,18 @@ Your dynamic lyrics display tool, built with WinUI 3 and Win2D, works with in-ap
</div>
<div align="center">
<mark>**_💞 BetterLyrics is made possible by all its contributors, bug reporters and users._**</mark>
</div>
<div align="center">
**_[中文版 README 请点按此处](https://github.com/jayfunc/BetterLyrics/blob/dev/README.CN.md)_**
</div>
## 🎉 This project was featured by SSPAI!
Check out the article: [BetterLyrics An immersive and smooth lyrics display tool designed for Windows](https://sspai.com/post/101028)
@@ -144,7 +156,7 @@ Before you build, make sure that you have already replaced `BetterLyrics\BetterL
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
## Inspired by
## 💡 Inspired by
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)