Refactor code structure for improved readability and maintainability
@@ -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"/>
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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)
|

|
||||||
|
|
||||||
## 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
|
### Settings
|
||||||
|
|
||||||

|

|
||||||
|

|
||||||
|

|
||||||
|

|
||||||
|
|
||||||
### Light music mode
|
## Many thanks to
|
||||||
|
|
||||||
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)
|
- [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
|
||||||
|
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 |