Refactor code structure for improved readability and maintainability
@@ -10,7 +10,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
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"/>
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
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;
|
||||
Artist = mediaProps?.Artist;
|
||||
@@ -128,14 +131,19 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
CoverImageDominantColors[i] = Colors.Transparent;
|
||||
}
|
||||
} else {
|
||||
canvasBitmap = await CanvasBitmap.LoadAsync(control, stream);
|
||||
stream.Seek(0);
|
||||
|
||||
CoverImage = new BitmapImage();
|
||||
await CoverImage.SetSourceAsync(stream);
|
||||
stream.Seek(0);
|
||||
|
||||
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);
|
||||
for (int i = 0; i < 3; i++) {
|
||||
Helper.QuantizedColor quantizedColor = quantizedColors[i];
|
||||
@@ -146,7 +154,7 @@ namespace BetterLyrics.WinUI3.ViewModels {
|
||||
stream.Dispose();
|
||||
}
|
||||
|
||||
return (GetLyrics(track), canvasBitmap);
|
||||
return (GetLyrics(track), coverSoftwareBitmap, coverImagePixelWidth, coverImagePixelHeight);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -25,6 +25,7 @@ using Windows.Media.Control;
|
||||
using Color = Windows.UI.Color;
|
||||
using System.Linq;
|
||||
using Microsoft.UI.Windowing;
|
||||
using Windows.Graphics.Imaging;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// 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 CanvasBitmap? _canvasCoverBitmap;
|
||||
private SoftwareBitmap? _coverSoftwareBitmap = null;
|
||||
private uint _coverImagePixelWidth = 0;
|
||||
private uint _coverImagePixelHeight = 0;
|
||||
|
||||
private float _coverBitmapRotateAngle = 0f;
|
||||
private float _coverScaleFactor = 1;
|
||||
@@ -260,7 +263,7 @@ namespace BetterLyrics.WinUI3.Views {
|
||||
ViewModel.AboutToUpdateUI = true;
|
||||
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
|
||||
LyricsCanvas.Paused = false;
|
||||
@@ -311,7 +314,7 @@ namespace BetterLyrics.WinUI3.Views {
|
||||
var b = _lyricsColor.B;
|
||||
|
||||
// Draw (dynamic) cover image as the very first layer
|
||||
if (_settingsService.IsCoverOverlayEnabled && _canvasCoverBitmap != null) {
|
||||
if (_settingsService.IsCoverOverlayEnabled && _coverSoftwareBitmap != null) {
|
||||
DrawCoverImage(sender, ds, _settingsService.IsDynamicCoverOverlay);
|
||||
}
|
||||
|
||||
@@ -458,7 +461,6 @@ namespace BetterLyrics.WinUI3.Views {
|
||||
}
|
||||
|
||||
private void DrawCoverImage(ICanvasAnimatedControl control, CanvasDrawingSession ds, bool dynamic) {
|
||||
if (_settingsService.IsCoverOverlayEnabled && _canvasCoverBitmap != null) {
|
||||
|
||||
ds.Transform = Matrix3x2.CreateRotation(_coverBitmapRotateAngle, control.Size.ToVector2() * 0.5f);
|
||||
|
||||
@@ -470,17 +472,16 @@ namespace BetterLyrics.WinUI3.Views {
|
||||
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
|
||||
BorderMode = EffectBorderMode.Hard,
|
||||
Scale = new Vector2(_coverScaleFactor),
|
||||
Source = _canvasCoverBitmap,
|
||||
Source = CanvasBitmap.CreateFromSoftwareBitmap(control, _coverSoftwareBitmap),
|
||||
}
|
||||
}
|
||||
};
|
||||
ds.DrawImage(
|
||||
coverOverlayEffect,
|
||||
(float)control.Size.Width / 2 - _canvasCoverBitmap.SizeInPixels.Width * _coverScaleFactor / 2,
|
||||
(float)control.Size.Height / 2 - _canvasCoverBitmap.SizeInPixels.Height * _coverScaleFactor / 2);
|
||||
(float)control.Size.Width / 2 - _coverImagePixelWidth * _coverScaleFactor / 2,
|
||||
(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) {
|
||||
byte verticalEdgeAlpha = (byte)(255 * _settingsService.LyricsVerticalEdgeOpacity / 100f);
|
||||
@@ -509,13 +510,11 @@ namespace BetterLyrics.WinUI3.Views {
|
||||
_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));
|
||||
|
||||
_coverScaleFactor = (float)diagonal / Math.Min(
|
||||
_canvasCoverBitmap.SizeInPixels.Width,
|
||||
_canvasCoverBitmap.SizeInPixels.Height);
|
||||
_coverScaleFactor = (float)diagonal / Math.Min(_coverImagePixelWidth, _coverImagePixelHeight);
|
||||
|
||||
}
|
||||
|
||||
|
||||
85
README.md
@@ -3,6 +3,7 @@
|
||||
Local song lyrics presentation app built with WinUI3
|
||||
|
||||
## Highlighted features
|
||||
|
||||
- Dynamic blur album art as background
|
||||
- Smooth lyrics fade in/out, zoom in/out effects
|
||||
- Smooth user interface change from song to song
|
||||
@@ -17,11 +18,13 @@ Coding in progress...
|
||||
We provide more than one setting item to better align with your preference
|
||||
|
||||
- Theme
|
||||
|
||||
- Follow system
|
||||
- Light
|
||||
- Dark
|
||||
|
||||
- Backdrop
|
||||
|
||||
- None
|
||||
- Mica
|
||||
- Mica alt
|
||||
@@ -31,17 +34,19 @@ We provide more than one setting item to better align with your preference
|
||||
- Transparent
|
||||
|
||||
- Album art as background
|
||||
|
||||
- Dynamic
|
||||
- Opacity
|
||||
- Blur amount
|
||||
|
||||
- Lyrics
|
||||
|
||||
- Alignment
|
||||
- Font size
|
||||
- Line spacing
|
||||
- Opacity on the edge
|
||||
- Blur amount
|
||||
- Glow effect
|
||||
- Dynamic glow effect
|
||||
|
||||
- Language
|
||||
- English
|
||||
@@ -49,33 +54,42 @@ We provide more than one setting item to better align with your preference
|
||||
- Traditional Chinese
|
||||
|
||||
## Inspired by
|
||||
|
||||
- [BetterNCM](https://github.com/std-microblock/BetterNCM)
|
||||
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
|
||||
## Demonstration video
|
||||
|
||||
See our latest introduction video「BetterLyrics 阶段性开发成果展示」on [Bilibili](https://b23.tv/QjKkYmL)
|
||||

|
||||
|
||||
## Screenshots (outdated)
|
||||
Or watch our introduction video「BetterLyrics 阶段性开发成果展示」(uploaded on 31 May 2025) on Bilibili below (click the cover image to watch):
|
||||
|
||||
[](https://b23.tv/QjKkYmL)
|
||||
|
||||
## Screenshots
|
||||
|
||||
### Split view
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Fullscreen
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
### Settings
|
||||
|
||||

|
||||
|
||||
### Light music mode
|
||||
|
||||
Will be activated automatically when lyrics are not detected/found
|
||||
|
||||

|
||||
|
||||
### General music mode
|
||||

|
||||
|
||||
### Real-time gif
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
## Many thanks to
|
||||
|
||||
- [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet)
|
||||
- [DevWinUI](https://github.com/ghost1372/DevWinUI)
|
||||
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
|
||||
@@ -85,17 +99,28 @@ Will be activated automatically when lyrics are not detected/found
|
||||
- [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)
|
||||
|
||||
## Third-party libraries that this app uses
|
||||
- CommunityToolkit.Labs.WinUI.MarqueeText
|
||||
- CommunityToolkit.Labs.WinUI.OpacityMaskView
|
||||
- CommunityToolkit.Mvvm
|
||||
- CommunityToolkit.WinUI.Controls.Primitives
|
||||
- CommunityToolkit.WinUI.Extensions
|
||||
- DevWinUI
|
||||
- DevWinUI.Controls
|
||||
- Microsoft.Extensions.DependencyInjection
|
||||
- Microsoft.Graphics.Win2D
|
||||
- Microsoft.Windows.SDK.BuildTools
|
||||
- Microsoft.WindowsAppSDK
|
||||
- Microsoft.Xaml.Behaviors.WinUI.Managed
|
||||
- z440.atl.core
|
||||
## Third-party libraries that this project uses
|
||||
|
||||
```
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
|
||||
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250513-build.2126" />
|
||||
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.4.0" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.SettingsControls" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="DevWinUI" Version="8.2.0" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.5" />
|
||||
<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
|
||||
|
After Width: | Height: | Size: 6.1 MiB |
BIN
Screenshots/Snipaste_2025-06-03_16-46-55.png
Normal file
|
After Width: | Height: | Size: 1.7 MiB |
BIN
Screenshots/Snipaste_2025-06-03_16-47-43.png
Normal file
|
After Width: | Height: | Size: 1.8 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-51-22.png
Normal file
|
After Width: | Height: | Size: 1.6 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-51-52.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-52-00.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-52-05.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-52-11.png
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
BIN
Screenshots/Snipaste_2025-06-03_17-52-51.png
Normal file
|
After Width: | Height: | Size: 142 KiB |
BIN
Screenshots/Snipaste_2025-06-03_17-53-07.png
Normal file
|
After Width: | Height: | Size: 175 KiB |
BIN
Screenshots/Snipaste_2025-06-03_18-36-05.png
Normal file
|
After Width: | Height: | Size: 459 KiB |