Refactor code structure for improved readability and maintainability

This commit is contained in:
Zhe Fang
2025-06-03 19:13:19 -04:00
parent f24e8b5fcd
commit 4304912395
15 changed files with 118 additions and 86 deletions

View File

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

View File

@@ -8,6 +8,7 @@ using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging; using Microsoft.UI.Xaml.Media.Imaging;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks; using System.Threading.Tasks;
using Windows.Graphics.Imaging; using Windows.Graphics.Imaging;
using Windows.Media.Control; using Windows.Media.Control;
@@ -99,9 +100,11 @@ namespace BetterLyrics.WinUI3.ViewModels {
} }
public async Task<(List<LyricsLine>, CanvasBitmap?)> SetSongInfoAsync(GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps, ICanvasAnimatedControl control) { public async Task<(List<LyricsLine>, SoftwareBitmap?, uint, uint)> SetSongInfoAsync(GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps, ICanvasAnimatedControl control) {
CanvasBitmap? canvasBitmap = null; SoftwareBitmap? coverSoftwareBitmap = null;
uint coverImagePixelWidth = 0;
uint coverImagePixelHeight = 0;
Title = mediaProps?.Title; Title = mediaProps?.Title;
Artist = mediaProps?.Artist; Artist = mediaProps?.Artist;
@@ -128,14 +131,19 @@ namespace BetterLyrics.WinUI3.ViewModels {
CoverImageDominantColors[i] = Colors.Transparent; CoverImageDominantColors[i] = Colors.Transparent;
} }
} else { } else {
canvasBitmap = await CanvasBitmap.LoadAsync(control, stream);
stream.Seek(0);
CoverImage = new BitmapImage(); CoverImage = new BitmapImage();
await CoverImage.SetSourceAsync(stream); await CoverImage.SetSourceAsync(stream);
stream.Seek(0); stream.Seek(0);
var decoder = await BitmapDecoder.CreateAsync(stream); var decoder = await BitmapDecoder.CreateAsync(stream);
coverImagePixelHeight = decoder.PixelHeight;
coverImagePixelWidth = decoder.PixelWidth;
coverSoftwareBitmap = await decoder.GetSoftwareBitmapAsync(
BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Premultiplied
);
var quantizedColors = await _colorThief.GetPalette(decoder, 3); var quantizedColors = await _colorThief.GetPalette(decoder, 3);
for (int i = 0; i < 3; i++) { for (int i = 0; i < 3; i++) {
Helper.QuantizedColor quantizedColor = quantizedColors[i]; Helper.QuantizedColor quantizedColor = quantizedColors[i];
@@ -146,7 +154,7 @@ namespace BetterLyrics.WinUI3.ViewModels {
stream.Dispose(); stream.Dispose();
} }
return (GetLyrics(track), canvasBitmap); return (GetLyrics(track), coverSoftwareBitmap, coverImagePixelWidth, coverImagePixelHeight);
} }

View File

@@ -25,6 +25,7 @@ using Windows.Media.Control;
using Color = Windows.UI.Color; using Color = Windows.UI.Color;
using System.Linq; using System.Linq;
using Microsoft.UI.Windowing; using Microsoft.UI.Windowing;
using Windows.Graphics.Imaging;
// To learn more about WinUI, the WinUI project structure, // To learn more about WinUI, the WinUI project structure,
// and more about our project templates, see: http://aka.ms/winui-project-info. // and more about our project templates, see: http://aka.ms/winui-project-info.
@@ -40,7 +41,9 @@ namespace BetterLyrics.WinUI3.Views {
private List<LyricsLine> _lyricsLines = []; private List<LyricsLine> _lyricsLines = [];
private CanvasBitmap? _canvasCoverBitmap; private SoftwareBitmap? _coverSoftwareBitmap = null;
private uint _coverImagePixelWidth = 0;
private uint _coverImagePixelHeight = 0;
private float _coverBitmapRotateAngle = 0f; private float _coverBitmapRotateAngle = 0f;
private float _coverScaleFactor = 1; private float _coverScaleFactor = 1;
@@ -260,7 +263,7 @@ namespace BetterLyrics.WinUI3.Views {
ViewModel.AboutToUpdateUI = true; ViewModel.AboutToUpdateUI = true;
await Task.Delay(_animationDurationMs); await Task.Delay(_animationDurationMs);
(_lyricsLines, _canvasCoverBitmap) = await ViewModel.SetSongInfoAsync(mediaProps, LyricsCanvas); (_lyricsLines, _coverSoftwareBitmap, _coverImagePixelWidth, _coverImagePixelHeight) = await ViewModel.SetSongInfoAsync(mediaProps, LyricsCanvas);
// Force to show lyrics and scroll to current line even if the music is not playing // Force to show lyrics and scroll to current line even if the music is not playing
LyricsCanvas.Paused = false; LyricsCanvas.Paused = false;
@@ -311,7 +314,7 @@ namespace BetterLyrics.WinUI3.Views {
var b = _lyricsColor.B; var b = _lyricsColor.B;
// Draw (dynamic) cover image as the very first layer // Draw (dynamic) cover image as the very first layer
if (_settingsService.IsCoverOverlayEnabled && _canvasCoverBitmap != null) { if (_settingsService.IsCoverOverlayEnabled && _coverSoftwareBitmap != null) {
DrawCoverImage(sender, ds, _settingsService.IsDynamicCoverOverlay); DrawCoverImage(sender, ds, _settingsService.IsDynamicCoverOverlay);
} }
@@ -458,28 +461,26 @@ namespace BetterLyrics.WinUI3.Views {
} }
private void DrawCoverImage(ICanvasAnimatedControl control, CanvasDrawingSession ds, bool dynamic) { private void DrawCoverImage(ICanvasAnimatedControl control, CanvasDrawingSession ds, bool dynamic) {
if (_settingsService.IsCoverOverlayEnabled && _canvasCoverBitmap != null) {
ds.Transform = Matrix3x2.CreateRotation(_coverBitmapRotateAngle, control.Size.ToVector2() * 0.5f); ds.Transform = Matrix3x2.CreateRotation(_coverBitmapRotateAngle, control.Size.ToVector2() * 0.5f);
using var coverOverlayEffect = new OpacityEffect { using var coverOverlayEffect = new OpacityEffect {
Opacity = _settingsService.CoverOverlayOpacity / 100f, Opacity = _settingsService.CoverOverlayOpacity / 100f,
Source = new GaussianBlurEffect { Source = new GaussianBlurEffect {
BlurAmount = _settingsService.CoverOverlayBlurAmount, BlurAmount = _settingsService.CoverOverlayBlurAmount,
Source = new ScaleEffect { Source = new ScaleEffect {
InterpolationMode = CanvasImageInterpolation.HighQualityCubic, InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
BorderMode = EffectBorderMode.Hard, BorderMode = EffectBorderMode.Hard,
Scale = new Vector2(_coverScaleFactor), Scale = new Vector2(_coverScaleFactor),
Source = _canvasCoverBitmap, Source = CanvasBitmap.CreateFromSoftwareBitmap(control, _coverSoftwareBitmap),
}
} }
}; }
ds.DrawImage( };
coverOverlayEffect, ds.DrawImage(
(float)control.Size.Width / 2 - _canvasCoverBitmap.SizeInPixels.Width * _coverScaleFactor / 2, coverOverlayEffect,
(float)control.Size.Height / 2 - _canvasCoverBitmap.SizeInPixels.Height * _coverScaleFactor / 2); (float)control.Size.Width / 2 - _coverImagePixelWidth * _coverScaleFactor / 2,
ds.Transform = Matrix3x2.Identity; (float)control.Size.Height / 2 - _coverImagePixelHeight * _coverScaleFactor / 2);
} ds.Transform = Matrix3x2.Identity;
} }
private void DrawGradientOpacityMask(ICanvasAnimatedControl control, CanvasDrawingSession ds, byte r, byte g, byte b) { private void DrawGradientOpacityMask(ICanvasAnimatedControl control, CanvasDrawingSession ds, byte r, byte g, byte b) {
@@ -509,13 +510,11 @@ namespace BetterLyrics.WinUI3.Views {
_lyricsGlowEffectAngle %= MathF.PI * 2; _lyricsGlowEffectAngle %= MathF.PI * 2;
} }
if (_settingsService.IsCoverOverlayEnabled && _canvasCoverBitmap != null) { if (_settingsService.IsCoverOverlayEnabled && _coverSoftwareBitmap != null) {
var diagonal = Math.Sqrt(Math.Pow(_lyricsAreaWidth, 2) + Math.Pow(_lyricsAreaHeight, 2)); var diagonal = Math.Sqrt(Math.Pow(_lyricsAreaWidth, 2) + Math.Pow(_lyricsAreaHeight, 2));
_coverScaleFactor = (float)diagonal / Math.Min( _coverScaleFactor = (float)diagonal / Math.Min(_coverImagePixelWidth, _coverImagePixelHeight);
_canvasCoverBitmap.SizeInPixels.Width,
_canvasCoverBitmap.SizeInPixels.Height);
} }

131
README.md
View File

@@ -3,6 +3,7 @@
Local song lyrics presentation app built with WinUI3 Local song lyrics presentation app built with WinUI3
## Highlighted features ## Highlighted features
- Dynamic blur album art as background - Dynamic blur album art as background
- Smooth lyrics fade in/out, zoom in/out effects - Smooth lyrics fade in/out, zoom in/out effects
- Smooth user interface change from song to song - Smooth user interface change from song to song
@@ -17,85 +18,109 @@ Coding in progress...
We provide more than one setting item to better align with your preference We provide more than one setting item to better align with your preference
- Theme - Theme
- Follow system
- Light - Follow system
- Dark - Light
- Dark
- Backdrop - Backdrop
- None
- Mica - None
- Mica alt - Mica
- Acrylic desktop - Mica alt
- Acrylic thin - Acrylic desktop
- Acrylic base - Acrylic thin
- Transparent - Acrylic base
- Transparent
- Album art as background - Album art as background
- Dynamic
- Opacity - Dynamic
- Blur amount - Opacity
- Blur amount
- Lyrics - Lyrics
- Alignment
- Font size - Alignment
- Line spacing - Font size
- Opacity on the edge - Line spacing
- Blur amount - Opacity on the edge
- Glow effect - Blur amount
- Dynamic glow effect
- Language - Language
- English - English
- Simplified Chinese - Simplified Chinese
- Traditional Chinese - Traditional Chinese
## Inspired by
## Inspired by
- [BetterNCM](https://github.com/std-microblock/BetterNCM) - [BetterNCM](https://github.com/std-microblock/BetterNCM)
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App) - [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player) - [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
## Demonstration video ## Demonstration video
See our latest introduction video「BetterLyrics 阶段性开发成果展示」on [Bilibili](https://b23.tv/QjKkYmL) ![alt text](Screenshots/Beelink-SER-8-Moonlight2025-06-0318-35-35-ezgif.com-video-to-gif-converter.gif)
## Screenshots (outdated) Or watch our introduction video「BetterLyrics 阶段性开发成果展示」(uploaded on 31 May 2025) on Bilibili below (click the cover image to watch):
[![Bilibili](https://i1.hdslb.com/bfs/archive/75a7a1a3803b617574090a91c59785b6c40d0fe5.jpg@672w_378h_1c.avif)](https://b23.tv/QjKkYmL)
## Screenshots
### Split view
![alt text](Screenshots/Snipaste_2025-06-03_16-46-55.png)
![alt text](Screenshots/Snipaste_2025-06-03_16-47-43.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-51-22.png)
### Fullscreen
![alt text](Screenshots/Snipaste_2025-06-03_17-52-51.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-53-07.png)
![alt text](Screenshots/Snipaste_2025-06-03_18-36-05.png)
### Settings ### Settings
![Settings](/Screenshots/settings.png) ![alt text](Screenshots/Snipaste_2025-06-03_17-51-52.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-52-00.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-52-05.png)
![alt text](Screenshots/Snipaste_2025-06-03_17-52-11.png)
### Light music mode ## Many thanks to
Will be activated automatically when lyrics are not detected/found
![Light music mode](/Screenshots/light-music.png)
### General music mode
![General music mode](/Screenshots/general-music.png)
### Real-time gif
![Real-time gif](/Screenshots/lyrics-animation.gif)
## Many thanks to
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) - [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
- [DevWinUI](https://github.com/ghost1372/DevWinUI) - [DevWinUI](https://github.com/ghost1372/DevWinUI)
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731) - [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
- [TagLib#](https://github.com/mono/taglib-sharp) - [TagLib#](https://github.com/mono/taglib-sharp)
- [Bilibili -【WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS) - [Bilibili -【WinUI3】SystemBackdropController定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
- [cnblogs - .NET App 与Windows系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496) - [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
- [Win2D 中的游戏循环CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html) - [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) - [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
## Third-party libraries that this app uses ## Third-party libraries that this project uses
- CommunityToolkit.Labs.WinUI.MarqueeText
- CommunityToolkit.Labs.WinUI.OpacityMaskView ```
- CommunityToolkit.Mvvm <PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
- CommunityToolkit.WinUI.Controls.Primitives <PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
- CommunityToolkit.WinUI.Extensions <PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
- DevWinUI <PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
- DevWinUI.Controls <PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
- Microsoft.Extensions.DependencyInjection <PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
- Microsoft.Graphics.Win2D <PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
- Microsoft.Windows.SDK.BuildTools <PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
- Microsoft.WindowsAppSDK <PackageReference Include="DevWinUI" Version="8.2.0" />
- Microsoft.Xaml.Behaviors.WinUI.Managed <PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
- z440.atl.core <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.250513003" />
<PackageReference Include="Microsoft.Xaml.Behaviors.WinUI.Managed" Version="3.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.5" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
<PackageReference Include="z440.atl.core" Version="6.24.0" />
```
## Any issues and PRs are welcomed

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 175 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 459 KiB