新增:

- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

改动:
- 分别为播放源配置歌词源、设置歌词同步阈值、歌词偏移值

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
This commit is contained in:
Zhe Fang
2025-08-06 19:21:05 -04:00
parent 7b2ff0cc8f
commit b043f9acd0
93 changed files with 1841 additions and 960 deletions

View File

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

View File

@@ -51,6 +51,8 @@
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />

View File

@@ -3,7 +3,16 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.DependencyInjection;
@@ -64,7 +73,7 @@ namespace BetterLyrics.WinUI3
private void EnsureSingleInstance()
{
bool createdNew;
_instanceMutex = new Mutex(true, MetadataHelper.AppName, out createdNew);
_instanceMutex = new Mutex(true, Constants.App.AppName, out createdNew);
if (!createdNew)
{
@@ -102,11 +111,12 @@ namespace BetterLyrics.WinUI3
})
// Services
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IPlaybackService, PlaybackService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ILibWatcherService, LibWatcherService>()
.AddSingleton<ITranslateService, TranslateService>()
.AddSingleton<ILastFMService, LastFMService>()
// ViewModels
.AddSingleton<LyricsWindowViewModel>()
.AddSingleton<SettingsWindowViewModel>()

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 162 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 304 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 260 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -53,8 +53,8 @@
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.7" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
@@ -65,8 +65,8 @@
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="ShadowViewer.Controls.Notification" Version="1.2.1" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageReference Include="System.Drawing.Common" Version="9.0.8" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.8" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
<PackageReference Include="TinyPinyin.Net" Version="1.0.2" />
<PackageReference Include="Ude.NetStandard" Version="1.2.0" />
@@ -78,6 +78,11 @@
<PackageReference Include="WinUIEx" Version="2.6.0" />
<PackageReference Include="z440.atl.core" Version="7.2.0" />
</ItemGroup>
<ItemGroup>
<Reference Include="Hqub.Lastfm">
<HintPath>..\..\..\Last.fm\src\Hqub.Lastfm\bin\Release\netstandard2.0\Hqub.Lastfm.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Page Update="Rendering\InAppLyricsRenderer.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -93,27 +98,78 @@
<TrimmerRootAssembly Include="TagLibSharp" />
</ItemGroup>
<ItemGroup>
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AppleMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Chrome.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Discord.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Edge.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyBox.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\EmptyState.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\foobar2000.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\iTunes.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\KugouMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LastFM.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Leaf.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.ico">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Logo.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\LXMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MediaPlayerWindows11.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\MusicBee.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\PotPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQ.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\QQMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Question.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Segoe Fluent Icons.ttf">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Spotify.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Telegram.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@@ -0,0 +1,15 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class AmllTTmlDB
{
private const string BaseUrl = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/";
public const string QueryPrefix = $"{BaseUrl}raw-lyrics/";
public const string Index = $"{BaseUrl}metadata/raw-lyrics-index.jsonl";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class App
{
public const string AppAuthor = "Zhe Fang";
public const string AppName = "BetterLyrics";
public const string AutoStartupTaskId = "AutoStartup";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class LXMusic
{
public const string QuerySuffix = "/subscribe-player-status?filter=progress,duration";
}
}

View File

@@ -0,0 +1,9 @@
namespace BetterLyrics.WinUI3.Constants
{
public static class LastFM
{
public const string ApiKey = "Your api key here";
public const string SharedSecret = "Your shared secret here";
public const string UnAuthUrl = "https://www.last.fm/settings/applications";
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Link
{
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string QQGroupUrl = "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";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
}
}

View File

@@ -0,0 +1,29 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.ApplicationModel;
namespace BetterLyrics.WinUI3.Constants
{
public static class PlayerID
{
public const string LXMusic = "cn.toside.music.desktop";
public const string MediaPlayerWindows11 = "Microsoft.ZuneMusic_8wekyb3d8bbwe!Microsoft.ZuneMusic";
public const string AIMP = "AIMP.exe";
public const string Foobar2000 = "foobar2000.exe";
public const string MusicBee = "MusicBee.exe";
public const string PotPlayer = "PotPlayerMini64.exe";
public const string Spotify = "Spotify.exe";
public const string AppleMusic = "AppleInc.AppleMusicWin_nzyj5cx40ttqa!App";
public const string NetEaseCloudMusic = "cloudmusic.exe";
public const string KugouMusic = "kugou";
public const string QQMusic = "QQMusic.exe";
public const string iTunes = "49586DaveAntoine.MediaControllerforiTunes_9bzempp7dntjg!App";
public const string Chrome = "Chrome";
public const string Edge = "MSEdge";
public const string BetterLyrics = "37412.BetterLyrics_rd1g0rsrrtxw8!App";
public const string BetterLyricsDebug = "37412.BetterLyrics_c8mj3v9sysxb4!App";
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public class PlayerName
{
public const string LXMusic = "LX Music";
public const string MediaPlayerWindows11 = "Media Player";
public const string AIMP = "AIMP";
public const string Foobar2000 = "foobar2000";
public const string MusicBee = "MusicBee";
public const string PotPlayer = "PotPlayer";
public const string Spotify = "Spotify";
public const string AppleMusic = "Apple Music";
public const string NetEaseCloudMusic = "网易云音乐";
public const string KugouMusic = "酷狗音乐";
public const string QQMusic = "QQ 音乐";
public const string iTunes = "iTunes";
public const string Chrome = "Google Chrome";
public const string Edge = "Microsoft Edge";
public const string BetterLyrics = "BetterLyrics";
public const string BetterLyricsDebug = "BetterLyrics (Debug)";
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class Time
{
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(300);
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Constants
{
public static class iTunes
{
public const string QueryPrefix = "https://itunes.apple.com/search?";
}
}

View File

@@ -15,9 +15,9 @@ namespace BetterLyrics.WinUI3.Converter
return provider switch
{
LyricsSearchProvider.LrcLib => "LrcLib",
LyricsSearchProvider.QQ => "QQ",
LyricsSearchProvider.Netease => "Netease",
LyricsSearchProvider.Kugou => "Kugou",
LyricsSearchProvider.QQ => "QQ 音乐",
LyricsSearchProvider.Netease => "网易云音乐",
LyricsSearchProvider.Kugou => "酷狗音乐",
LyricsSearchProvider.AmllTtmlDb => "amll-ttml-db",
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalLrcFile"),
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString("LyricsSearchProviderLocalMusicFile"),

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToDisplayedNameConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PlayerName.Spotify,
PlayerID.AppleMusic => PlayerName.AppleMusic,
PlayerID.iTunes => PlayerName.iTunes,
PlayerID.KugouMusic => PlayerName.KugouMusic,
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
PlayerID.QQMusic => PlayerName.QQMusic,
PlayerID.LXMusic => PlayerName.LXMusic,
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
PlayerID.AIMP => PlayerName.AIMP,
PlayerID.Foobar2000 => PlayerName.Foobar2000,
PlayerID.MusicBee => PlayerName.MusicBee,
PlayerID.PotPlayer => PlayerName.PotPlayer,
PlayerID.Chrome => PlayerName.Chrome,
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
_ => provider,
};
}
return value?.ToString() ?? "";
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,47 @@
using BetterLyrics.WinUI3.Constants;
using BetterLyrics.WinUI3.Helper;
using Microsoft.UI.Xaml.Data;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Converter
{
public class MediaSourceProviderToLogoUriConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is string provider)
{
return provider switch
{
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerID.iTunes => PathHelper.iTunesLogoPath,
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
PlayerID.AIMP => PathHelper.AIMPLogoPath,
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
PlayerID.Chrome => PathHelper.ChromeLogoPath,
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
_ => PathHelper.UnknownPlayerLogoPath,
};
}
return PathHelper.UnknownPlayerLogoPath;
}
public object ConvertBack(object value, Type targetType, object parameter, string language)
{
throw new NotImplementedException();
}
}
}

View File

@@ -8,8 +8,9 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
public class AlbumArtChangedEventArgs(byte[]? bytes, SoftwareBitmap? albumArtSwBitmap, Color? albumArtLightAccentColor, Color? albumArtDarkAccentColor) : EventArgs
{
public byte[]? Bytes { get; set; } = bytes;
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public Color? AlbumArtLightAccentColor { get; set; } = albumArtLightAccentColor;
public Color? AlbumArtDarkAccentColor { get; set; } = albumArtDarkAccentColor;

View File

@@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMIsAuthenticatedChangedEventArgs : EventArgs
{
public bool IsAuthenticated { get; set; }
public LastFMIsAuthenticatedChangedEventArgs(bool isAuthenticated)
{
IsAuthenticated = isAuthenticated;
}
}
}

View File

@@ -0,0 +1,18 @@
using Hqub.Lastfm.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Events
{
public class LastFMUserChangedEventArgs : EventArgs
{
public User? User { get; set; }
public LastFMUserChangedEventArgs(User? user)
{
User = user;
}
}
}

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;

View File

@@ -1,4 +1,4 @@
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Graphics.Canvas.Text;
using System;

View File

@@ -6,6 +6,7 @@ using Microsoft.Graphics.Canvas.Text;
using Microsoft.UI;
using Microsoft.UI.Xaml.Media.Imaging;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.Formats.Jpeg;
using SixLabors.ImageSharp.Formats.Png;
using SixLabors.ImageSharp.PixelFormats;
using SixLabors.ImageSharp.Processing;
@@ -168,34 +169,55 @@ namespace BetterLyrics.WinUI3.Helper
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes, 1).FirstOrDefault().ToHex());
// 新建正方形画布
using var square = new Image<Rgba32>(size, size, themeColor);
// 计算居中位置
int offsetX = (size - image.Width) / 2;
int offsetY = (size - image.Height) / 2;
// 绘制原图到正方形画布
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
// 保存为 PNG 字节流
using var ms = new MemoryStream();
square.Save(ms, new PngEncoder());
square.Save(ms, new JpegEncoder());
return ms.ToArray();
}
public static byte[] Resize(byte[] imageBytes, int size)
{
using Image image = Image.Load(imageBytes);
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
using (Image image = Image.Load(imageBytes))
{
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
int width = (int)(image.Width * factor);
int height = (int)(image.Height * factor);
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
int width = (int)(image.Width * factor);
int height = (int)(image.Height * factor);
using var ms = new MemoryStream();
image.Save(ms, new PngEncoder());
return ms.ToArray();
if (factor > 1)
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
}
else
{
image.Mutate(x => x.Resize(width, height, KnownResamplers.NearestNeighbor));
}
using var ms = new MemoryStream();
image.Save(ms, new JpegEncoder());
return ms.ToArray();
}
}
public static byte[] GenerateNoiseBGRA(int width, int height)
{
var random = new Random();
var pixelData = new byte[width * height * 4];
for (int i = 0; i < width * height; i++)
{
byte gray = (byte)random.Next(0, 256);
pixelData[i * 4 + 0] = gray; // B
pixelData[i * 4 + 1] = gray; // G
pixelData[i * 4 + 2] = gray; // R
pixelData[i * 4 + 3] = 255; // A
}
return pixelData;
}
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using NTextCat;

View File

@@ -14,9 +14,6 @@ namespace BetterLyrics.WinUI3.Helper
public static class MetadataHelper
{
public const string AppAuthor = "Zhe Fang";
public const string AppDisplayName = "Better Lyrics";
public const string AppName = "BetterLyrics";
public static string AppVersion
{
get
@@ -25,24 +22,5 @@ namespace BetterLyrics.WinUI3.Helper
return $"{version.Major}.{version.Minor}.{version.Build}.{version.Revision}";
}
}
public const string GithubUrl = "https://github.com/jayfunc/BetterLyrics";
public const string QQGroupUrl = "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";
public const string DiscordUrl = "https://discord.gg/5yAQPnyCKv";
public const string TelegramUrl = "https://t.me/+svhSLZ7awPsxNGY1";
public static async Task<DateTime> GetBuildDate()
{
var assembly = Assembly.GetExecutingAssembly();
var filePath = assembly.Location;
if (!File.Exists(filePath))
return DateTime.MinValue;
StorageFile file = await StorageFile.GetFileFromPathAsync(filePath);
// 获取文件基本属性
BasicProperties props = await file.GetBasicPropertiesAsync();
// 返回修改日期
return props.DateModified.DateTime;
}
}
}

View File

@@ -1,331 +0,0 @@
using System;
using System.Buffers;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Windows.Storage;
using static BetterLyrics.WinUI3.Helper.NoiseOverlayHelper.BitmapFileCreator;
namespace BetterLyrics.WinUI3.Helper
{
internal static class NoiseOverlayHelper
{
const string NoiseOverlayFileName = "noise_overlay.bmp";
static readonly string NoiseOverlayFilePath = Path.Combine(ApplicationData.Current.LocalFolder.Path, "Assets", NoiseOverlayFileName);
/// <summary>
/// 生成 BGRA 格式的灰阶噪声像素数据
/// </summary>
public static byte[] GenerateNoiseBitmapBGRA(int width, int height)
{
var random = new Random();
var pixelData = new byte[width * height * 4];
for (int i = 0; i < width * height; i++)
{
byte gray = (byte)random.Next(0, 256);
pixelData[i * 4 + 0] = gray; // B
pixelData[i * 4 + 1] = gray; // G
pixelData[i * 4 + 2] = gray; // R
pixelData[i * 4 + 3] = 255; // A
}
return pixelData;
}
/// <summary>
/// 生成单色灰阶随机噪声
/// </summary>
/// <param name="outputPath">输出文件路径</param>
/// <param name="width">图片宽度</param>
/// <param name="height">图片高度</param>
public static BitmapFile GenerateNoiseBitmap(int width, int height)
{
const uint NumOfGrayscale = 16;
uint bitCount = NextPowerOfTwo((uint)Math.Round(Math.Sqrt(NumOfGrayscale)));
var palette = BitmapFileCreator.CreateGrayscalePalette(16);
var pixelData = GenerateRandomNoise(width, height, bitCount);
var fileHeader = BitmapFileCreator.CreateFileHeader(palette, pixelData);
var infoHeader = BitmapFileCreator.CreateInfoHeader(width, height, bitCount);
return new BitmapFile(fileHeader, infoHeader, palette, pixelData);
}
/// <summary>
/// 读取噪声图片的位头信息
/// </summary>
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(string? FilePath)
{
var _filePath = FilePath ?? NoiseOverlayFilePath;
using var fs = new FileStream(_filePath, FileMode.Open, FileAccess.Read);
using var br = new BinaryReader(fs);
// 跳过文件头
fs.Seek(Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>(), SeekOrigin.Begin);
// 读取信息头
byte[] infoHeaderBytes = br.ReadBytes(Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>());
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
}
public static BitmapFileCreator.WINBMPINFOHEADER ReadBitmapInfoHeaders(byte[] FileBytes)
{
// 跳过文件头
var offset = Marshal.SizeOf<BitmapFileCreator.BITMAPFILEHEADER>();
// 读取信息头
var infoHeaderLength = Marshal.SizeOf<BitmapFileCreator.WINBMPINFOHEADER>();
Span<byte> infoHeaderBytes = new(FileBytes, offset, infoHeaderLength);
return MemoryMarshal.Read<BitmapFileCreator.WINBMPINFOHEADER>(infoHeaderBytes);
}
/// <summary>
/// safe 的写入 struct 到流
/// </summary>
/// <typeparam name="T">值 strcut</typeparam>
/// <param name="stream">要写入的字节流</param>
/// <param name="structure">要被写入的结构</param>
public static void WriteStruct<T>(Stream stream, in T structure) where T : struct
{
int size = Unsafe.SizeOf<T>();
byte[] buffer = ArrayPool<byte>.Shared.Rent(size);
try
{
MemoryMarshal.Write(buffer, in structure);
stream.Write(buffer, 0, size);
}
finally
{
ArrayPool<byte>.Shared.Return(buffer);
}
}
/// <summary>
/// 随机填充位图内容
/// </summary>
/// <param name="width">填充宽度</param>
/// <param name="height">填充高度</param>
/// <param name="bitCount">单个调色盘索引所占比特位数</param>
/// <returns>字节数据</returns>
private static byte[] GenerateRandomNoise(int width, int height, uint bitCount)
{
// 创建位图行字节数4K 对齐
int rowSize = ((width * (int)bitCount + 31) >> 5) << 2;
// 创建随机位图数据
Random rnd = new();
return Enumerable.Range(0, rowSize * height)
.Select(i => (byte)rnd.Next(0x00, 0xFF))
.ToArray();
}
private static uint NextPowerOfTwo(uint value)
{
value--;
value |= value >> 1;
value |= value >> 2;
value |= value >> 4;
value |= value >> 8;
value |= value >> 16;
return value + 1;
}
public static class BitmapFileCreator
{
/// <summary>
/// 创建BMP文件头
/// </summary>
/// <param name="palette">调色盘数据</param>
/// <param name="pixelData">像素数据</param>
/// <returns>文件头结构</returns>
public static BITMAPFILEHEADER CreateFileHeader(byte[] palette, byte[] pixelData)
{
return new BITMAPFILEHEADER
{
bfType = 0x4D42,
bfSize = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
Marshal.SizeOf<WINBMPINFOHEADER>() +
palette.Length +
pixelData.Length),
bfReserved1 = 0,
bfReserved2 = 0,
bfOffBits = (uint)(Marshal.SizeOf<BITMAPFILEHEADER>() +
Marshal.SizeOf<WINBMPINFOHEADER>() +
palette.Length)
};
}
/// <summary>
/// 将指定值填充到为最接近的2的幂数
/// </summary>
/// <summary>
/// 生成灰阶调色盘
/// </summary>
/// <param name="colors">灰阶数量</param>
/// <returns>调色盘byte数组</returns>
public static byte[] CreateGrayscalePalette(int colors)
{
return Enumerable.Range(0, colors)
.SelectMany(i => Enumerable.Repeat((byte)(i * 0x10), 4))
.ToArray();
}
/// <summary>
/// 创建BMP信息头
/// </summary>
/// <param name="width">宽度</param>
/// <param name="height">高度</param>
/// <param name="bitCount">单个像素(调色盘索引)位数</param>
/// <returns>BMP信息头结构</returns>
public static WINBMPINFOHEADER CreateInfoHeader(int width, int height, uint bitCount)
{
return new WINBMPINFOHEADER
{
biSize = (uint)Marshal.SizeOf<WINBMPINFOHEADER>(),
biWidth = (uint)width,
biHeight = (uint)height,
biPlanes = 1,
biBitCount = (ushort)bitCount,
biCompression = 0,
biSizeImage = 0,
biXPelsPerMeter = 0,
biYPelsPerMeter = 0,
biClrUsed = 0,
biClrImportant = 0
};
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
/// <summary>
/// BMP 位图文件头
/// </summary>
public struct BITMAPFILEHEADER
{
/// <summary>
/// 文件类型标识,用于指定文件格式
/// </summary>
public ushort bfType;
/// <summary>
/// 文件大小,以字节为单位
/// </summary>
public uint bfSize;
/// <summary>
/// 保留字段,未使用
/// </summary>
public ushort bfReserved1;
/// <summary>
/// 保留字段,未使用
/// </summary>
public ushort bfReserved2;
/// <summary>
/// 像素数据的起始位置,以字节为单位,从文件头开始计算
/// </summary>
public uint bfOffBits;
}
[StructLayout(LayoutKind.Sequential, Pack = 1)]
/// <summary>
/// BMP 位图信息头
/// </summary>
public struct WINBMPINFOHEADER
{
/// <summary>
/// 指定此结构体的字节大小。
/// </summary>
public uint biSize;
/// <summary>
/// 以像素为单位的位图宽度。
/// </summary>
public uint biWidth;
/// <summary>
/// 以像素为单位的位图高度。
/// </summary>
public uint biHeight;
/// <summary>
/// 目标设备的平面数通常为1。
/// </summary>
public ushort biPlanes;
/// <summary>
/// 每个像素的位数表示颜色深度如1、4、8、16、24、32等。
/// </summary>
public ushort biBitCount;
/// <summary>
/// 压缩类型0表示不压缩。
/// </summary>
public uint biCompression;
/// <summary>
/// 位图图像数据的大小以字节为单位若图像未压缩该值可设为0。
/// </summary>
public uint biSizeImage;
/// <summary>
/// 每米X轴方向的像素数水平分辨率通常设为0。
/// </summary>
public uint biXPelsPerMeter;
/// <summary>
/// 每米Y轴方向的像素数垂直分辨率通常设为0。
/// </summary>
public uint biYPelsPerMeter;
/// <summary>
/// 实际使用的颜色索引数若为0则使用位图中实际出现的颜色数。
/// </summary>
public uint biClrUsed;
/// <summary>
/// 重要颜色索引数0表示所有颜色都重要。
/// </summary>
public uint biClrImportant;
}
}
public class BitmapFile(BitmapFileCreator.BITMAPFILEHEADER fileHeader, BitmapFileCreator.WINBMPINFOHEADER infoHeader, byte[] palette, byte[] pixelData)
{
public BITMAPFILEHEADER FileHeader = fileHeader;
public WINBMPINFOHEADER InfoHeader = infoHeader;
public byte[] Palette = palette;
public byte[] PixelData = pixelData;
/// <summary>
/// 转换为byte[]
/// </summary>
/// <param name="bf"></param>
public static explicit operator byte[](BitmapFile bf)
{
var result = new byte[bf.FileHeader.bfSize];
var ms = new MemoryStream(result);
bf.WriteToStream(ms);
return result;
}
public byte[] ToByteArray() => (byte[])this;
public Task<byte[]> ToByteArrayAsync() { return Task.FromResult(ToByteArray());}
/// <summary>
/// 写入此结构到流中
/// </summary>
public void WriteToStream(Stream stream)
{
NoiseOverlayHelper.WriteStruct(stream, FileHeader);
NoiseOverlayHelper.WriteStruct(stream, InfoHeader);
stream.Write(Palette);
stream.Write(PixelData);
stream.Flush();
}
}
}
}

View File

@@ -32,6 +32,10 @@ namespace BetterLyrics.WinUI3.Helper
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// Windows.Graphics.Imaging.SoftwareBitmap
isDisposedField = objType.GetField("_objRef_global__System_IDisposable", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return !Convert.ToBoolean(isDisposedField.GetValue(obj)); }
// System.IO.FileStream
var strategyField = objType.GetField("_strategy", BindingFlags.NonPublic | BindingFlags.Instance);
if (strategyField != null)

View File

@@ -18,6 +18,21 @@ namespace BetterLyrics.WinUI3.Helper
//public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Wiki82.profile.xml");
public static string LogoPath => Path.Combine(AssetsFolder, "Logo.ico");
public static string AIMPLogoPath => Path.Combine(AssetsFolder, "AIMP.png");
public static string Foobar2000LogoPath => Path.Combine(AssetsFolder, "foobar2000.png");
public static string MusicBeeLogoPath => Path.Combine(AssetsFolder, "MusicBee.png");
public static string SpotifyLogoPath => Path.Combine(AssetsFolder, "Spotify.png");
public static string AppleMusicLogoPath => Path.Combine(AssetsFolder, "AppleMusic.png");
public static string iTunesLogoPath => Path.Combine(AssetsFolder, "iTunes.png");
public static string KugouMusicLogoPath => Path.Combine(AssetsFolder, "KugouMusic.png");
public static string NetEaseCloudMusicLogoPath => Path.Combine(AssetsFolder, "NetEaseCloudMusic.png");
public static string QQMusicLogoPath => Path.Combine(AssetsFolder, "QQMusic.png");
public static string LXMusicLogoPath => Path.Combine(AssetsFolder, "LXMusic.png");
public static string MediaPlayerWindows11LogoPath => Path.Combine(AssetsFolder, "MediaPlayerWindows11.png");
public static string PotPlayerLogoPath => Path.Combine(AssetsFolder, "PotPlayer.png");
public static string ChromeLogoPath => Path.Combine(AssetsFolder, "Chrome.png");
public static string EdgeLogoPath => Path.Combine(AssetsFolder, "Edge.png");
public static string UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");

View File

@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Models
LyricsLines = lyricsLines;
}
public void SetDisplayedTextAlongWith(LyricsData translationData, int toleranceMs = 0)
public void SetDisplayedTextAlongWith(LyricsData translationData, string separator, int toleranceMs = 0)
{
foreach (var line in LyricsLines)
{
@@ -47,11 +47,11 @@ namespace BetterLyrics.WinUI3.Models
{
tmp = ChineseConverter.ConvertToSimplifiedChinese(transLine.OriginalText);
}
line.DisplayedText = $"{line.OriginalText}\n{tmp}";
line.DisplayedText = $"{line.OriginalText}{separator}{tmp}";
}
else
{
line.DisplayedText = $"{line.OriginalText}\n{transLine.OriginalText}";
line.DisplayedText = $"{line.OriginalText}{separator}{transLine.OriginalText}";
}
}
else
@@ -62,7 +62,7 @@ namespace BetterLyrics.WinUI3.Models
}
}
public void SetDisplayedTextAlongWith(string translation)
public void SetDisplayedTextAlongWith(string translation, string separator)
{
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
@@ -74,7 +74,7 @@ namespace BetterLyrics.WinUI3.Models
}
else
{
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}{translationArr[i]}";
line.DisplayedText = $"{line.OriginalText}{separator}{translationArr[i]}";
}
i++;
}

View File

@@ -2,6 +2,10 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
namespace BetterLyrics.WinUI3.Models
{
@@ -13,12 +17,39 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty]
public partial string Provider { get; set; }
[ObservableProperty]
public partial bool IsLastFMTrackEnabled { get; set; }
[ObservableProperty]
public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty]
public partial int PositionOffset { get; set; }
[ObservableProperty]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
public MediaSourceProviderInfo() { }
public MediaSourceProviderInfo(string provider, bool isEnabled)
public MediaSourceProviderInfo(string provider)
{
Provider = provider;
IsEnabled = isEnabled;
IsEnabled = true;
IsLastFMTrackEnabled = false;
if (provider == Constants.PlayerID.AppleMusic)
{
TimelineSyncThreshold = PositionOffset = 1000;
}
else
{
TimelineSyncThreshold = 0;
PositionOffset = 0;
}
ResetPositionOffsetOnSongChanged = false;
LyricsSearchProvidersInfo = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
}
}

View File

@@ -1,9 +1,8 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Animation;
namespace BetterLyrics.WinUI3.Renderer
{

View File

@@ -1,6 +1,7 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Extensions.Logging;
using System;
@@ -14,7 +15,7 @@ using System.Text.Encodings.Web;
using System.Text.Json;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public class AlbumArtSearchService : IAlbumArtSearchService
{
@@ -102,7 +103,7 @@ namespace BetterLyrics.WinUI3.Services
}
// Build the iTunes API URL
string url = $"https://itunes.apple.com/search?term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
string url = $"{Constants.iTunes.QueryPrefix}term=" + WebUtility.UrlEncode($"{artist} {album}").Replace("%20", "+") + "&country=" + countryCode + "&entity=album&media=music&limit=1";
// Make a request to the API
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);

View File

@@ -4,7 +4,7 @@ using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
{
public interface IAlbumArtSearchService
{

View File

@@ -0,0 +1,27 @@
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using Hqub.Lastfm.Entities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.LastFMService
{
public interface ILastFMService
{
User User { get; }
bool IsAuthenticated { get; }
event EventHandler<LastFMUserChangedEventArgs>? UserChanged;
event EventHandler<LastFMIsAuthenticatedChangedEventArgs>? IsAuthenticatedChanged;
Task AuthAsync();
Task ConfirmAuth();
Task UnAuthAsync();
Task ConfirmUnAuthAsync();
Task TrackAsync(SongInfo songInfo);
Task RefreshAsync();
}
}

View File

@@ -0,0 +1,154 @@
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Hqub.Lastfm;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Windows.System;
namespace BetterLyrics.WinUI3.Services.LastFMService
{
public partial class LastFMService : ILastFMService
{
private readonly ISettingsService _settingsService;
private readonly LastfmClient _client;
public event EventHandler<LastFMUserChangedEventArgs>? UserChanged;
public event EventHandler<LastFMIsAuthenticatedChangedEventArgs>? IsAuthenticatedChanged;
public Hqub.Lastfm.Entities.User? User { get; private set; }
public bool IsAuthenticated { get; private set; }
public LastFMService(ISettingsService settingsService)
{
_settingsService = settingsService;
_client = new LastfmClient(Constants.LastFM.ApiKey, Constants.LastFM.SharedSecret);
_client.Session.SessionKey = _settingsService.LastFMSessionKey;
UpdateAuthStatusAsync();
}
public async Task ConfirmAuth()
{
try
{
await _client.AuthenticateViaWebAsync();
_settingsService.LastFMSessionKey = _client.Session.SessionKey;
await UpdateAuthStatusAsync();
}
catch (Exception)
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LastFMAuthFailed") ?? "", InfoBarSeverity.Error);
}
}
public async Task ConfirmUnAuthAsync()
{
_client.Session.SessionKey = "";
_settingsService.LastFMSessionKey = "";
await UpdateAuthStatusAsync();
}
public async Task AuthAsync()
{
var dialogXamlRoot = WindowHelper.GetWindowByWindowType<SettingsWindow>()?.Content.XamlRoot;
if (dialogXamlRoot == null)
{
return;
}
var dialog = new ContentDialog
{
Title = App.ResourceLoader?.GetString("LastFMRequestAuthTitle") ?? "",
Content = App.ResourceLoader?.GetString("LastFMRequestAuthDesc") ?? "",
PrimaryButtonText = App.ResourceLoader?.GetString("LastFMRequestAuthConfirm") ?? "",
CloseButtonText = App.ResourceLoader?.GetString("Cancel") ?? "",
DefaultButton = ContentDialogButton.Close,
XamlRoot = dialogXamlRoot,
};
dialog.PrimaryButtonClick += async (s, args) =>
{
await ConfirmAuth();
};
string url = await _client.GetWebAuthenticationUrlAsync();
await Launcher.LaunchUriAsync(new Uri(url));
await dialog.ShowAsync();
}
public async Task UnAuthAsync()
{
var dialogXamlRoot = WindowHelper.GetWindowByWindowType<SettingsWindow>()?.Content.XamlRoot;
if (dialogXamlRoot == null)
{
return;
}
var dialog = new ContentDialog
{
Title = App.ResourceLoader?.GetString("LastFMRequestUnAuthTitle") ?? "",
Content = App.ResourceLoader?.GetString("LastFMRequestUnAuthDesc") ?? "",
PrimaryButtonText = App.ResourceLoader?.GetString("LastFMRequestUnAuthConfirm") ?? "",
CloseButtonText = App.ResourceLoader?.GetString("Cancel") ?? "",
DefaultButton = ContentDialogButton.Close,
XamlRoot = dialogXamlRoot,
};
dialog.PrimaryButtonClick += async (s, args) =>
{
await ConfirmUnAuthAsync();
};
await Launcher.LaunchUriAsync(new Uri(Constants.LastFM.UnAuthUrl));
await dialog.ShowAsync();
}
private async Task UpdateAuthStatusAsync()
{
IsAuthenticated = _client.Session.Authenticated;
IsAuthenticatedChanged?.Invoke(this, new LastFMIsAuthenticatedChangedEventArgs(IsAuthenticated));
if (IsAuthenticated)
{
User = await _client.User.GetInfoAsync();
}
else
{
User = null;
}
UserChanged?.Invoke(this, new LastFMUserChangedEventArgs(User));
}
public async Task TrackAsync(SongInfo songInfo)
{
if (IsAuthenticated)
{
await _client.Track.ScrobbleAsync(new Hqub.Lastfm.Entities.Scrobble
{
Track = songInfo.Title,
Artist = songInfo.Artist,
Date = DateTime.Now,
});
}
}
public async Task RefreshAsync()
{
await UpdateAuthStatusAsync();
}
}
}

View File

@@ -8,7 +8,7 @@ using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.LibWatcherService
{
public interface ILibWatcherService
{

View File

@@ -6,10 +6,11 @@ using System.IO;
using System.Linq;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using Microsoft.UI.Dispatching;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.LibWatcherService
{
public class LibWatcherService : BaseViewModel, IDisposable, ILibWatcherService
{

View File

@@ -5,10 +5,10 @@ using System.Threading;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Enums;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
public interface ILyricsSearchService
{
Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token);
Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token);
}
}

View File

@@ -3,6 +3,7 @@
using ATL;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Providers.Web.Kugou;
using Lyricify.Lyrics.Searchers;
@@ -18,7 +19,7 @@ using System.Threading;
using System.Threading.Tasks;
using Windows.Storage;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
public class LyricsSearchService : ILyricsSearchService
{
@@ -36,7 +37,7 @@ namespace BetterLyrics.WinUI3.Services
_lrcLibHttpClient = new();
_lrcLibHttpClient.DefaultRequestHeaders.Add(
"User-Agent",
$"{MetadataHelper.AppName} {MetadataHelper.AppVersion} ({MetadataHelper.GithubUrl})"
$"{Constants.App.AppName} {MetadataHelper.AppVersion} ({Constants.Link.GithubUrl})"
);
_amllTtmlDbHttpClient = new();
}
@@ -60,10 +61,9 @@ namespace BetterLyrics.WinUI3.Services
public async Task<bool> DownloadAmllTtmlDbIndexAsync()
{
const string url = "https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/metadata/raw-lyrics-index.jsonl";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead);
using var response = await _amllTtmlDbHttpClient.GetAsync(Constants.AmllTTmlDB.Index, HttpCompletionOption.ResponseHeadersRead);
if (!response.IsSuccessStatusCode) return false;
await using var stream = await response.Content.ReadAsStreamAsync();
@@ -86,13 +86,13 @@ namespace BetterLyrics.WinUI3.Services
}
}
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string title, string artist, string album, double durationMs, CancellationToken token)
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
{
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
try
{
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
foreach (var provider in _settingsService.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
{
if (!provider.IsEnabled)
{
@@ -268,7 +268,7 @@ namespace BetterLyrics.WinUI3.Services
return null;
// 下载歌词内容
var url = $"https://raw.githubusercontent.com/Steve-xmh/amll-ttml-db/refs/heads/main/raw-lyrics/{rawLyricFile}";
var url = $"{Constants.AmllTTmlDB.QueryPrefix}{rawLyricFile}";
try
{
using var response = await _amllTtmlDbHttpClient.GetAsync(url);

View File

@@ -5,9 +5,9 @@ using System.Threading.Tasks;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Models;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public interface IPlaybackService
public interface IMediaSessionsService
{
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
@@ -23,5 +23,6 @@ namespace BetterLyrics.WinUI3.Services
bool IsPlaying { get; }
SongInfo? SongInfo { get; }
TimeSpan Position { get; }
}
}

View File

@@ -1,17 +1,20 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using EvtSource;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using SixLabors.ImageSharp;
using SixLabors.ImageSharp.PixelFormats;
using System;
using System.Collections.Generic;
@@ -25,24 +28,22 @@ using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Media.Control;
using Windows.Storage.Streams;
using Windows.UI.Shell;
using WindowsMediaController;
using static WindowsMediaController.MediaManager;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
public partial class PlaybackService : BaseViewModel, IPlaybackService,
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>>>
{
private readonly IAlbumArtSearchService _albumArtSearchService;
private readonly ILogger<PlaybackService> _logger;
private readonly ILogger<MediaSessionsService> _logger;
private readonly string _lxMusicId = "cn.toside.music.desktop";
private double _lxMusicPositionSeconds = 0;
private double _lxMusicDurationSeconds = 0;
private bool _cachedIsPlaying = false;
private TimeSpan _cachedPosition = TimeSpan.Zero;
private EventSourceReader? _sse = null;
@@ -62,10 +63,10 @@ namespace BetterLyrics.WinUI3.Services
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
public PlaybackService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService) : base(settingsService)
public MediaSessionsService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService) : base(settingsService)
{
_albumArtSearchService = albumArtSearchService;
_logger = Ioc.Default.GetRequiredService<ILogger<PlaybackService>>();
_logger = Ioc.Default.GetRequiredService<ILogger<MediaSessionsService>>();
_mediaSourceProvidersInfo = _settingsService.MediaSourceProvidersInfo;
InitMediaManager();
@@ -73,6 +74,7 @@ namespace BetterLyrics.WinUI3.Services
public bool IsPlaying => _cachedIsPlaying;
public SongInfo? SongInfo => _cachedSongInfo;
public TimeSpan Position => _cachedPosition;
private bool IsMediaSourceEnabled(string id)
{
@@ -109,12 +111,24 @@ namespace BetterLyrics.WinUI3.Services
var focusedSession = _mediaManager.GetFocusedSession();
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
if (mediaSession != focusedSession) return;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
if (!IsMediaSourceEnabled(mediaSession.Id))
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(timelineProperties.Position, timelineProperties.EndTime));
});
_cachedPosition = TimeSpan.Zero;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, TimeSpan.Zero));
});
}
else
{
_cachedPosition = timelineProperties.Position;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
});
}
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
@@ -125,13 +139,20 @@ namespace BetterLyrics.WinUI3.Services
var focusedSession = _mediaManager.GetFocusedSession();
RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(mediaSession.Id) || mediaSession != focusedSession) return;
if (mediaSession != focusedSession) return;
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
if (!IsMediaSourceEnabled(mediaSession.Id))
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
};
_cachedIsPlaying = false;
}
else
{
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
};
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
@@ -149,55 +170,88 @@ namespace BetterLyrics.WinUI3.Services
var focusedSession = _mediaManager.GetFocusedSession();
RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(id) || mediaSession != focusedSession) return;
if (mediaSession != focusedSession) return;
_cachedSongInfo = new SongInfo
if (!IsMediaSourceEnabled(id))
{
Title = mediaProperties.Title,
Artist = mediaProperties.Artist,
Album = mediaProperties.AlbumTitle,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
SourceAppUserModelId = id,
};
_cachedSongInfo = null;
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
if (id == _lxMusicId)
{
StartSSE();
}
else
{
StopSSE();
}
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
}
else
{
_SMTCAlbumArtBytes = null;
}
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
if (!token.IsCancellationRequested)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
if (id == Constants.PlayerID.LXMusic)
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
StopSSE();
}
_SMTCAlbumArtBytes = null;
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
}
}).ConfigureAwait(false);
if (!token.IsCancellationRequested)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
});
}
}).ConfigureAwait(false);
}
else
{
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Artist = mediaProperties.Artist,
Album = mediaProperties.AlbumTitle,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
SourceAppUserModelId = id,
};
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
if (id == Constants.PlayerID.LXMusic)
{
StartSSE();
}
else
{
StopSSE();
}
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
}
else
{
_SMTCAlbumArtBytes = null;
}
await _albumArtRefreshRunner.RunAsync(async tokne =>
{
await UpdateAlbumArtRelated(tokne);
});
if (!token.IsCancellationRequested)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
});
}
}).ConfigureAwait(false);
}
}
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
@@ -231,7 +285,9 @@ namespace BetterLyrics.WinUI3.Services
var found = _mediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
if (found == null)
{
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, true));
_mediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id));
// 在这里就写进设置
// 因为 SettingsPageViewModel 可能还没有初始化
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
@@ -258,9 +314,9 @@ namespace BetterLyrics.WinUI3.Services
if (focusedSession == null || focusedSession.ControlSession == null) return;
var mediaProps = await focusedSession.ControlSession.TryGetMediaPropertiesAsync();
MediaManager_OnAnyTimelinePropertyChanged(focusedSession, focusedSession.ControlSession.GetTimelineProperties());
MediaManager_OnAnyMediaPropertyChanged(focusedSession, mediaProps);
MediaManager_OnAnyPlaybackStateChanged(focusedSession, focusedSession.ControlSession.GetPlaybackInfo());
MediaManager_OnAnyTimelinePropertyChanged(focusedSession, focusedSession.ControlSession.GetTimelineProperties());
}
private async Task UpdateAlbumArtRelated(CancellationToken token)
@@ -285,8 +341,8 @@ namespace BetterLyrics.WinUI3.Services
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
bytes = ImageHelper.Resize(bytes, _targetAlbumArtSize);
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
@@ -303,7 +359,7 @@ namespace BetterLyrics.WinUI3.Services
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
}
@@ -311,7 +367,7 @@ namespace BetterLyrics.WinUI3.Services
{
try
{
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress,duration")).Start();
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}{Constants.LXMusic.QuerySuffix}")).Start();
_sse.MessageReceived += Sse_MessageReceived;
_sse.Disconnected += Sse_Disconnected;
}
@@ -348,7 +404,7 @@ namespace BetterLyrics.WinUI3.Services
private void Sse_MessageReceived(object sender, EventSourceMessageEventArgs e)
{
if (_cachedSongInfo?.SourceAppUserModelId == _lxMusicId)
if (_cachedSongInfo?.SourceAppUserModelId == Constants.PlayerID.LXMusic)
{
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
if (data.ValueKind == JsonValueKind.Number)
@@ -421,7 +477,6 @@ namespace BetterLyrics.WinUI3.Services
if (message.PropertyName == nameof(SettingsPageViewModel.MediaSourceProvidersInfo))
{
_mediaSourceProvidersInfo = [.. message.NewValue];
_settingsService.MediaSourceProvidersInfo = _mediaSourceProvidersInfo;
MediaManager_OnFocusedSessionChanged(null);
}
}

View File

@@ -6,7 +6,7 @@ using BetterLyrics.WinUI3.Models;
using Microsoft.UI.Xaml;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.SettingsService
{
public interface ISettingsService
{
@@ -38,7 +38,6 @@ namespace BetterLyrics.WinUI3.Services
string LibreTranslateServer { get; set; }
int SelectedTargetLanguageIndex { get; set; }
bool ResetPositionOffsetOnSongChanged { get; set; }
int PositionOffset { get; set; }
// Lyrics lib
@@ -78,7 +77,6 @@ namespace BetterLyrics.WinUI3.Services
float LyricsLineSpacingFactor { get; set; }
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
List<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
@@ -94,7 +92,6 @@ namespace BetterLyrics.WinUI3.Services
LyricsDisplayType DisplayType { get; set; }
int TimelineSyncThreshold { get; set; }
int LockHotKeyIndex { get; set; }
bool IsImmersiveMode { get; set; }
string LXMusicServer { get; set; }
@@ -107,5 +104,10 @@ namespace BetterLyrics.WinUI3.Services
PlaybackOrder PlaybackOrder { get; set; }
bool IsLibreTranslateEnabled { get; set; }
string DockMonitorDeviceName { get; set; }
// LastFM
string LastFMSessionKey { get; set; }
string LyricsTranslationSeparator { get; set; }
}
}

View File

@@ -10,10 +10,11 @@ using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
using System.Linq;
using Windows.Media.Core;
using Windows.Storage;
using Windows.UI;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.SettingsService
{
public class SettingsService : ISettingsService
{
@@ -51,7 +52,7 @@ namespace BetterLyrics.WinUI3.Services
private const string IsLyricsGlowEffectEnabledKey = "IsLyricsGlowEffectEnabled";
private const string LanguageKey = "Language";
private const string LocalLyricsFoldersKey = "LocalLyricsFolders";
private const string LocalMediaFoldersKey = "LocalLyricsFolders";
private const string LyricsAlignmentTypeKey = "TextAlignmentType";
private const string SongInfoAlignmentTypeKey = "SongInfoAlignmentType";
private const string LyricsBlurAmountKey = "LyricsBlurAmount";
@@ -71,7 +72,6 @@ namespace BetterLyrics.WinUI3.Services
private const string LyricsGlowEffectScopeKey = "LyricsGlowEffectScope";
private const string LyricsHighlightSopeKey = "LyricsHighlightSope";
private const string LyricsLineSpacingFactorKey = "LyricsLineSpacingFactor";
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
private const string AlbumArtSearchProvidersInfoKey = "AlbumArtSearchProvidersInfo";
private const string LyricsVerticalEdgeOpacityKey = "LyricsVerticalEdgeOpacity";
@@ -94,11 +94,8 @@ namespace BetterLyrics.WinUI3.Services
private const string LyricsScrollEasingTypeKey = "LyricsScrollEasingType";
private const string LyricsScrollDurationKey = "LyricsScrollDuration";
public const string TimelineSyncThresholdKey = "TimelineSyncThreshold";
private const string IsLyricsFloatAnimationEnabledKey = "IsLyricsFloatAnimationEnabled";
private const string ResetPositionOffsetOnSongChangedKey = "ResetPositionOffsetOnSongChanged";
private const string PlaybackOrderKey = "PlaybackOrder";
private const string PositionOffsetKey = "PositionOffset";
@@ -115,6 +112,11 @@ namespace BetterLyrics.WinUI3.Services
private const string DockMonitorDeviceNameKey = "DockMonitorDeviceName";
// LastFM
private const string LastFMSessionKeyKey = "LastFMSessionKey";
private const string LyricsTranslationSeparatorKey = "LyricsTranslationSeparator";
private readonly ApplicationDataContainer _localSettings;
public SettingsService()
@@ -123,29 +125,7 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IsFirstRunKey, true);
// Lyrics lib
SetDefault(LocalLyricsFoldersKey, "[]");
SetDefault(
LyricsSearchProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
Enum.GetValues<LyricsSearchProvider>()
.Select(p => new LyricsSearchProviderInfo(p, true))
.ToList(),
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)
);
if (LyricsSearchProvidersInfo.Count != Enum.GetValues<LyricsSearchProvider>().Length)
{
LyricsSearchProvidersInfo = Enum.GetValues<LyricsSearchProvider>()
.Select(p => new LyricsSearchProviderInfo(
p,
LyricsSearchProvidersInfo
.Where(x => x.Provider == p)
.FirstOrDefault()
?.IsEnabled ?? true
))
.ToList();
}
SetDefault(LocalMediaFoldersKey, "[]");
SetDefault(
AlbumArtSearchProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
@@ -169,6 +149,23 @@ namespace BetterLyrics.WinUI3.Services
}
SetDefault(MediaSourceProvidersInfoKey, "[]");
var tmp = MediaSourceProvidersInfo;
for (int i = 0; i < tmp.Count; i++)
{
var mediaSource = tmp[i];
if (mediaSource.LyricsSearchProvidersInfo == null || mediaSource.LyricsSearchProvidersInfo.Count != Enum.GetValues<LyricsSearchProvider>().Length)
{
mediaSource.LyricsSearchProvidersInfo = [..Enum.GetValues<LyricsSearchProvider>()
.Select(p => new LyricsSearchProviderInfo(
p,
mediaSource.LyricsSearchProvidersInfo?
.Where(x => x.Provider == p)
.FirstOrDefault()
?.IsEnabled ?? true
))];
}
}
MediaSourceProvidersInfo = tmp;
// App appearance
SetDefault(LanguageKey, (int)Language.FollowSystem);
@@ -235,11 +232,9 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutSine);
SetDefault(LyricsScrollDurationKey, 500); // 500ms
SetDefault(TimelineSyncThresholdKey, 0); // 0ms
SetDefault(IsLyricsFloatAnimationEnabledKey, true);
SetDefault(ResetPositionOffsetOnSongChangedKey, false);
SetDefault(PositionOffsetKey, 0);
SetDefault(LockHotKeyIndexKey, 'U' - 'A');
SetDefault(DockPlacementKey, (int)DockPlacement.Top);
@@ -250,6 +245,10 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(LyricsFontFamilyKey, FontHelper.SystemFontFamilies.ElementAtOrDefault(0));
SetDefault(IsDragEverywhereEnabledKey, false);
SetDefault(DockMonitorDeviceNameKey, MonitorHelper.GetPrimaryMonitorDeviceName());
SetDefault(LastFMSessionKeyKey, "");
SetDefault(LyricsTranslationSeparatorKey, StringHelper.NewLine);
}
public bool IsDragEverywhereEnabled
@@ -448,12 +447,12 @@ namespace BetterLyrics.WinUI3.Services
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LocalLyricsFoldersKey) ?? "[]",
GetValue<string>(LocalMediaFoldersKey) ?? "[]",
SourceGenerationContext.Default.ListLocalMediaFolder
)!;
set =>
SetValue(
LocalLyricsFoldersKey,
LocalMediaFoldersKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLocalMediaFolder
@@ -563,23 +562,6 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(LyricsLineSpacingFactorKey, value);
}
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
{
get =>
System.Text.Json.JsonSerializer.Deserialize(
GetValue<string>(LyricsSearchProvidersInfoKey) ?? "[]",
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)!;
set =>
SetValue(
LyricsSearchProvidersInfoKey,
System.Text.Json.JsonSerializer.Serialize(
value,
SourceGenerationContext.Default.ListLyricsSearchProviderInfo
)
);
}
public List<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo
{
get =>
@@ -656,24 +638,12 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(IgnoreFullscreenWindowKey, value);
}
public int TimelineSyncThreshold
{
get => GetValue<int>(TimelineSyncThresholdKey);
set => SetValue(TimelineSyncThresholdKey, value);
}
public bool IsLyricsFloatAnimationEnabled
{
get => GetValue<bool>(IsLyricsFloatAnimationEnabledKey);
set => SetValue(IsLyricsFloatAnimationEnabledKey, value);
}
public bool ResetPositionOffsetOnSongChanged
{
get => GetValue<bool>(ResetPositionOffsetOnSongChangedKey);
set => SetValue(ResetPositionOffsetOnSongChangedKey, value);
}
public PlaybackOrder PlaybackOrder
{
get => (PlaybackOrder)GetValue<int>(PlaybackOrderKey);
@@ -698,6 +668,22 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(DockMonitorDeviceNameKey, value);
}
// LastFM
public string LastFMSessionKey
{
get => GetValue<string>(LastFMSessionKeyKey)!;
set => SetValue(LastFMSessionKeyKey, value);
}
public string LyricsTranslationSeparator
{
get => GetValue<string>(LyricsTranslationSeparatorKey)!;
set => SetValue(LyricsTranslationSeparatorKey, value);
}
// Common methods
private T? GetValue<T>(string key)
{
if (_localSettings.Values.TryGetValue(key, out object? value))

View File

@@ -6,7 +6,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.TranslateService
{
public interface ITranslateService
{

View File

@@ -1,6 +1,7 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Serialization;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using Lyricify.Lyrics.Helpers.General;
using Microsoft.UI.Dispatching;
@@ -13,7 +14,7 @@ using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services
namespace BetterLyrics.WinUI3.Services.TranslateService
{
public class TranslateService : BaseViewModel, ITranslateService
{

View File

@@ -285,9 +285,6 @@
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>About</value>
</data>
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>Lyrics library</value>
</data>
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>App appearance</value>
</data>
@@ -566,13 +563,10 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<value>Easing animation type</value>
</data>
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
<value>Playback sources</value>
<value>Playback and lyrics sources</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>Playback sources</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>Enable or disable lyrics display for a specified media source</value>
<value>Monitor this playback source</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>Log record</value>
@@ -643,7 +637,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="TranslateServerNotSet" xml:space="preserve">
<value>Translate server is not set, please configure it in settings first</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>Reset to 0 when switching songs</value>
</data>
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
@@ -919,4 +913,67 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>This setting will not affect the dock mode and the dock mode will always remain centered.</value>
</data>
<data name="SettingsPageLastFM.Content" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMManager.Header" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMAuth.Content" xml:space="preserve">
<value>Authorize</value>
</data>
<data name="SettingsPageLastFMUnAuth.Content" xml:space="preserve">
<value>Revoke authorization</value>
</data>
<data name="SettingsPageLastFMTrack.Header" xml:space="preserve">
<value>Track listening history via Last.fm</value>
</data>
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>Username</value>
</data>
<data name="SettingsPageLastFMPlaycount.Header" xml:space="preserve">
<value>Total playing count</value>
</data>
<data name="SettingsPageLastFMRegistered.Header" xml:space="preserve">
<value>Registration date</value>
</data>
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
<value>Source and translation separator</value>
</data>
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>Immersive mode</value>
</data>
<data name="MusicGalleryPagePlayAll.Content" xml:space="preserve">
<value>Play all</value>
</data>
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>No playback source captured</value>
</data>
<data name="SettingsPageLastFMRefresh.Content" xml:space="preserve">
<value>Refresh</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>Authorization failed, please try again</value>
</data>
<data name="LastFMRequestAuthTitle" xml:space="preserve">
<value>Grant BetterLyrics permission to access your Last.fm account</value>
</data>
<data name="LastFMRequestAuthDesc" xml:space="preserve">
<value>Please complete the authorization in your browser</value>
</data>
<data name="LastFMRequestAuthConfirm" xml:space="preserve">
<value>I have completed the authorization</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>Cancel</value>
</data>
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
<value>Revoke BetterLyrics' permission to access your Last.fm account</value>
</data>
<data name="LastFMRequestUnAuthDesc" xml:space="preserve">
<value>Please complete the cancellation operation in your browser</value>
</data>
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
<value>I have canceled my authorization</value>
</data>
</root>

View File

@@ -285,9 +285,6 @@
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>について</value>
</data>
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞</value>
</data>
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>アプリの外観</value>
</data>
@@ -566,13 +563,10 @@
<value>アニメーションタイプを緩和します</value>
</data>
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
<value>再生ソース</value>
<value>プレイと歌詞</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>再生ソース</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>指定されたメディアソースの歌詞ディスプレイを有効または無効にする</value>
<value>この再生ソースを監視します</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>ログレコード</value>
@@ -643,7 +637,7 @@
<data name="TranslateServerNotSet" xml:space="preserve">
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>曲を切り替えるときに0にリセットします</value>
</data>
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
@@ -919,4 +913,67 @@
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>この設定はドックモードには影響しません。ドックモードは常に中心のままです。</value>
</data>
<data name="SettingsPageLastFM.Content" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMManager.Header" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMAuth.Content" xml:space="preserve">
<value>許可</value>
</data>
<data name="SettingsPageLastFMUnAuth.Content" xml:space="preserve">
<value>承認を取り消します</value>
</data>
<data name="SettingsPageLastFMTrack.Header" xml:space="preserve">
<value>last.fm 経由でリスニング履歴を追跡します</value>
</data>
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>ユーザー名</value>
</data>
<data name="SettingsPageLastFMPlaycount.Header" xml:space="preserve">
<value>合計プレイカウント</value>
</data>
<data name="SettingsPageLastFMRegistered.Header" xml:space="preserve">
<value>登録日</value>
</data>
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
<value>ソースおよび翻訳セパレーター</value>
</data>
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>没入モード</value>
</data>
<data name="MusicGalleryPagePlayAll.Content" xml:space="preserve">
<value>すべてを再生します</value>
</data>
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>キャプチャされた再生ソースはありません</value>
</data>
<data name="SettingsPageLastFMRefresh.Content" xml:space="preserve">
<value>リフレッシュします</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>承認が失敗しました、もう一度やり直してください</value>
</data>
<data name="LastFMRequestAuthTitle" xml:space="preserve">
<value>BetterLyricsにLast.fmアカウントへのアクセスを許可してください</value>
</data>
<data name="LastFMRequestAuthDesc" xml:space="preserve">
<value>ブラウザの承認を完了してください</value>
</data>
<data name="LastFMRequestAuthConfirm" xml:space="preserve">
<value>私は承認を完了しました</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>キャンセル</value>
</data>
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
<value>Last.fmアカウントへのBetterLyricsアクセスを取り消します</value>
</data>
<data name="LastFMRequestUnAuthDesc" xml:space="preserve">
<value>ブラウザでキャンセル操作を完了してください</value>
</data>
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
<value>認可をキャンセルしました</value>
</data>
</root>

View File

@@ -285,9 +285,6 @@
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>에 대한</value>
</data>
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>가사</value>
</data>
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>앱 모양</value>
</data>
@@ -566,13 +563,10 @@
<value>애니메이션 유형 완화</value>
</data>
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
<value>재생 소스</value>
<value>연극과 가사</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>재생 소스</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>지정된 미디어 소스의 가사 디스플레이 활성화 또는 비활성화</value>
<value>재생 소스를 모니터링하십시오</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>로그 레코드</value>
@@ -643,7 +637,7 @@
<data name="TranslateServerNotSet" xml:space="preserve">
<value>번역 서버가 설정되지 않았습니다. 먼저 설정으로 구성하십시오.</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>노래를 전환 할 때 0 으로 재설정하십시오</value>
</data>
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
@@ -919,4 +913,67 @@
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>이 설정은 도크 모드에 영향을 미치지 않으며 도크 모드는 항상 중앙에 유지됩니다.</value>
</data>
<data name="SettingsPageLastFM.Content" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMManager.Header" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMAuth.Content" xml:space="preserve">
<value>승인</value>
</data>
<data name="SettingsPageLastFMUnAuth.Content" xml:space="preserve">
<value>취소 승인</value>
</data>
<data name="SettingsPageLastFMTrack.Header" xml:space="preserve">
<value>Last.fm 을 통해 청취 기록을 추적합니다</value>
</data>
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>사용자 이름</value>
</data>
<data name="SettingsPageLastFMPlaycount.Header" xml:space="preserve">
<value>총 플레이 카운트</value>
</data>
<data name="SettingsPageLastFMRegistered.Header" xml:space="preserve">
<value>등록일</value>
</data>
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
<value>소스 및 번역 분리기</value>
</data>
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>몰입 형 모드</value>
</data>
<data name="MusicGalleryPagePlayAll.Content" xml:space="preserve">
<value>모두 재생하십시오</value>
</data>
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>재생 소스가 캡처되지 않았습니다</value>
</data>
<data name="SettingsPageLastFMRefresh.Content" xml:space="preserve">
<value>새로 고치다</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>승인이 실패했습니다. 다시 시도하십시오</value>
</data>
<data name="LastFMRequestAuthTitle" xml:space="preserve">
<value>Last.fm 계정에 BetterLyrics 액세스 권한을 부여하세요!</value>
</data>
<data name="LastFMRequestAuthDesc" xml:space="preserve">
<value>브라우저에서 승인을 완료하십시오</value>
</data>
<data name="LastFMRequestAuthConfirm" xml:space="preserve">
<value>나는 승인을 완료했다</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>취소</value>
</data>
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
<value>Last.fm 계정에 대한 BetterLyrics 액세스를 취소하십시오!</value>
</data>
<data name="LastFMRequestUnAuthDesc" xml:space="preserve">
<value>브라우저에서 취소 작업을 완료하십시오</value>
</data>
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
<value>내 승인을 취소했습니다</value>
</data>
</root>

View File

@@ -285,9 +285,6 @@
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>关于</value>
</data>
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌词源</value>
</data>
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>应用外观</value>
</data>
@@ -566,13 +563,10 @@
<value>缓动动画类型</value>
</data>
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
<value>播放源</value>
<value>播放与歌词源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>播放源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>为指定媒体源启用或禁用歌词显示</value>
<value>监听此播放源</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日志记录</value>
@@ -643,7 +637,7 @@
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未设置Translate服务器请先在设置中进行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>切换歌曲时重置为 0</value>
</data>
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
@@ -919,4 +913,67 @@
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>此设置不会影响停靠模式,停靠模式将始终保持居中。</value>
</data>
<data name="SettingsPageLastFM.Content" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMManager.Header" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMAuth.Content" xml:space="preserve">
<value>授权</value>
</data>
<data name="SettingsPageLastFMUnAuth.Content" xml:space="preserve">
<value>撤销授权</value>
</data>
<data name="SettingsPageLastFMTrack.Header" xml:space="preserve">
<value>通过 Last.fm 跟踪听歌历史记录</value>
</data>
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>用户名</value>
</data>
<data name="SettingsPageLastFMPlaycount.Header" xml:space="preserve">
<value>听歌总数量</value>
</data>
<data name="SettingsPageLastFMRegistered.Header" xml:space="preserve">
<value>注册日期</value>
</data>
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
<value>原文译文分隔符</value>
</data>
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="MusicGalleryPagePlayAll.Content" xml:space="preserve">
<value>播放全部</value>
</data>
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>没有捕获的播放源</value>
</data>
<data name="SettingsPageLastFMRefresh.Content" xml:space="preserve">
<value>刷新</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>授权失败,请重试</value>
</data>
<data name="LastFMRequestAuthTitle" xml:space="preserve">
<value>授予 BetterLyrics 访问您 Last.fm 账户的权限</value>
</data>
<data name="LastFMRequestAuthDesc" xml:space="preserve">
<value>请在浏览器中完成授权</value>
</data>
<data name="LastFMRequestAuthConfirm" xml:space="preserve">
<value>我已经完成了授权</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
<value>撤销 BetterLyrics 访问您 Last.fm 账户的权限</value>
</data>
<data name="LastFMRequestUnAuthDesc" xml:space="preserve">
<value>请在浏览器中完成取消操作</value>
</data>
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
<value>我已经取消了我的授权</value>
</data>
</root>

View File

@@ -285,9 +285,6 @@
<data name="SettingsPageAbout.Content" xml:space="preserve">
<value>關於</value>
</data>
<data name="SettingsPageLyricsLib.Content" xml:space="preserve">
<value>歌詞源</value>
</data>
<data name="SettingsPageAppAppearance.Text" xml:space="preserve">
<value>應用外觀</value>
</data>
@@ -566,13 +563,10 @@
<value>緩動動畫類型</value>
</data>
<data name="SettingsPagePlaybackLib.Content" xml:space="preserve">
<value>播放源</value>
<value>播放與歌詞源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Header" xml:space="preserve">
<value>播放來源</value>
</data>
<data name="SettingsPageMediaSourceProvidersConfig.Description" xml:space="preserve">
<value>為指定媒體源啟用或禁用歌詞顯示</value>
<value>監聽此播放來源</value>
</data>
<data name="SettingsPageLog.Header" xml:space="preserve">
<value>日誌記錄</value>
@@ -643,7 +637,7 @@
<data name="TranslateServerNotSet" xml:space="preserve">
<value>未設定翻譯伺服器,請先在設定中進行配置</value>
</data>
<data name="LyricsPagePositionOffsetHint.Text" xml:space="preserve">
<data name="LyricsPagePositionOffsetHint.Header" xml:space="preserve">
<value>切換歌曲時重置為 0</value>
</data>
<data name="SettingsPageTargetLanguage.Description" xml:space="preserve">
@@ -919,4 +913,67 @@
<data name="SettingsPageLyricsAlignment.Description" xml:space="preserve">
<value>此設定不會影響停靠模式,停靠模式將始終保持居中。</value>
</data>
<data name="SettingsPageLastFM.Content" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMManager.Header" xml:space="preserve">
<value>Last.fm</value>
</data>
<data name="SettingsPageLastFMAuth.Content" xml:space="preserve">
<value>授權</value>
</data>
<data name="SettingsPageLastFMUnAuth.Content" xml:space="preserve">
<value>撤銷授權</value>
</data>
<data name="SettingsPageLastFMTrack.Header" xml:space="preserve">
<value>透過 Last.fm 追蹤聽歌歷史記錄</value>
</data>
<data name="SettingsPageLastFMUsername.Header" xml:space="preserve">
<value>使用者名稱</value>
</data>
<data name="SettingsPageLastFMPlaycount.Header" xml:space="preserve">
<value>聽歌總數量</value>
</data>
<data name="SettingsPageLastFMRegistered.Header" xml:space="preserve">
<value>註冊日期</value>
</data>
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
<value>原文譯文分隔符</value>
</data>
<data name="LyricsWindowImmersiveButtonToolTip.Content" xml:space="preserve">
<value>沉浸模式</value>
</data>
<data name="MusicGalleryPagePlayAll.Content" xml:space="preserve">
<value>播放全部</value>
</data>
<data name="SettingsPagePlaybackNotFound.Text" xml:space="preserve">
<value>沒有捕獲的播放源</value>
</data>
<data name="SettingsPageLastFMRefresh.Content" xml:space="preserve">
<value>重新整理</value>
</data>
<data name="LastFMAuthFailed" xml:space="preserve">
<value>授權失敗,請重試</value>
</data>
<data name="LastFMRequestAuthTitle" xml:space="preserve">
<value>授予 BetterLyrics 訪問您 Last.fm 賬戶的權限</value>
</data>
<data name="LastFMRequestAuthDesc" xml:space="preserve">
<value>請在瀏覽器中完成授權</value>
</data>
<data name="LastFMRequestAuthConfirm" xml:space="preserve">
<value>我已經完成了授權</value>
</data>
<data name="Cancel" xml:space="preserve">
<value>取消</value>
</data>
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
<value>撤銷 BetterLyrics 訪問您 Last.fm 賬戶的權限</value>
</data>
<data name="LastFMRequestUnAuthDesc" xml:space="preserve">
<value>請在瀏覽器中完成取消操作</value>
</data>
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
<value>我已經取消了我的授權</value>
</data>
</root>

View File

@@ -1,6 +1,6 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using System;

View File

@@ -3,7 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;

View File

@@ -3,7 +3,8 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -27,7 +28,7 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient<PropertyChangedMessage<LyricsSearchProvider?>>,
IRecipient<PropertyChangedMessage<TranslationSearchProvider?>>
{
private readonly IPlaybackService _playbackService;
private readonly IMediaSessionsService _mediaSessionsService;
private readonly ThrottleHelper _timelineThrottle = new(TimeSpan.FromSeconds(1));
private bool _isDockMode = false;
@@ -37,12 +38,11 @@ namespace BetterLyrics.WinUI3.ViewModels
private int _lyricsDockFontSize = 8;
private int _lyricsDesktopFontSize = 8;
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
public LyricsPageViewModel(ISettingsService settingsService, IMediaSessionsService mediaSessionsService) : base(settingsService)
{
IsFirstRun = _settingsService.IsFirstRun;
IsTranslationEnabled = _settingsService.IsTranslationEnabled;
DisplayType = _settingsService.DisplayType;
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
PositionOffset = _settingsService.PositionOffset;
IsImmersiveMode = _settingsService.IsImmersiveMode;
ShowTranslationOnly = _settingsService.ShowTranslationOnly;
@@ -56,12 +56,12 @@ namespace BetterLyrics.WinUI3.ViewModels
//Volume = SystemVolumeHelper.GetMasterVolume();
//SystemVolumeHelper.VolumeChanged += SystemVolumeHelper_VolumeChanged;
_playbackService = playbackService;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
_mediaSessionsService = mediaSessionsService;
_mediaSessionsService.SongInfoChanged += PlaybackService_SongInfoChanged;
_mediaSessionsService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_mediaSessionsService.TimelineChanged += PlaybackService_TimelineChanged;
IsSongPlaying = _playbackService.IsPlaying;
IsSongPlaying = _mediaSessionsService.IsPlaying;
}
private void PlaybackService_TimelineChanged(object? sender, Events.TimelineChangedEventArgs e)
@@ -83,10 +83,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
SongInfo = e.SongInfo;
SongDurationSeconds = SongInfo?.Duration ?? 0;
if (ResetPositionOffsetOnSongChanged)
{
PositionOffset = 0;
}
}
[ObservableProperty]
@@ -138,10 +134,6 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial bool ShowTranslationOnly { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
public partial bool IsSongPlaying { get; set; }
@@ -213,25 +205,25 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private async Task PlaySongAsync()
{
await _playbackService.PlayAsync();
await _mediaSessionsService.PlayAsync();
}
[RelayCommand]
private async Task PauseSongAsync()
{
await _playbackService.PauseAsync();
await _mediaSessionsService.PauseAsync();
}
[RelayCommand]
private async Task PreviousSongAsync()
{
await _playbackService.PreviousAsync();
await _mediaSessionsService.PreviousAsync();
}
[RelayCommand]
private async Task NextSongAsync()
{
await _playbackService.NextAsync();
await _mediaSessionsService.NextAsync();
}
partial void OnIsFirstRunChanged(bool value)
@@ -271,17 +263,17 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStandardFontSize))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsStandardFontSize))
{
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDockFontSize))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDockFontSize))
{
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDesktopFontSize))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDesktopFontSize))
{
UpdateHintMessageFontSize();
}
@@ -290,9 +282,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsFontFamily))
{
LyricsFontFamily = message.NewValue;
}
@@ -306,9 +298,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<TimeSpan> message)
{
if (message.Sender is LyricsRendererViewModel)
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.TotalTime))
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.TotalTime))
{
if (_timelineThrottle.CanTrigger())
{
@@ -323,9 +315,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<LyricsSearchProvider?> message)
{
if (message.Sender is LyricsRendererViewModel)
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsSearchProvider))
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.LyricsSearchProvider))
{
LyricsSearchProvider = message.NewValue;
}
@@ -334,9 +326,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<TranslationSearchProvider?> message)
{
if (message.Sender is LyricsRendererViewModel)
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
{
if (message.PropertyName == nameof(LyricsRendererViewModel.TranslationSearchProvider))
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.TranslationSearchProvider))
{
TranslationSearchProvider = message.NewValue;
}

View File

@@ -1,20 +1,37 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
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
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
public LyricsRendererViewModel(ISettingsService settingsService, IPlaybackService playbackService, ILyricsSearchService musicSearchService, ILibWatcherService libWatcherService, ITranslateService libreTranslateService) : base(settingsService)
public LyricsRendererViewModel(
ISettingsService settingsService,
IMediaSessionsService mediaSessionsService,
ILyricsSearchService musicSearchService,
ILibWatcherService libWatcherService,
ITranslateService libreTranslateService,
ILastFMService lastFMService
) : base(settingsService)
{
_lyrcsSearchService = musicSearchService;
_playbackService = playbackService;
_mediaSessionsService = mediaSessionsService;
_libWatcherService = libWatcherService;
_translateService = libreTranslateService;
_lastFMService = lastFMService;
_mediaSourceProvidersInfo = _settingsService.MediaSourceProvidersInfo;
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
_albumArtCornerRadius = _settingsService.CoverImageRadius;
@@ -59,9 +76,13 @@ namespace BetterLyrics.WinUI3.ViewModels
_showTranslationOnly = _settingsService.ShowTranslationOnly;
_isLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
_targetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
_lyricsTranslationSeparator = _settingsService.LyricsTranslationSeparator;
_dockPlacement = _settingsService.DockPlacement;
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
_timelineSyncThreshold = _settingsService.TimelineSyncThreshold;
_timelineSyncThreshold = 0;
_canvasYScrollTransition.SetDuration(_settingsService.LyricsScrollDuration / 1000f);
_canvasYScrollTransition.SetEasingType(_settingsService.LyricsScrollEasingType);
@@ -73,12 +94,12 @@ namespace BetterLyrics.WinUI3.ViewModels
_libWatcherService.MusicLibraryFilesChanged +=
LibWatcherService_MusicLibraryFilesChanged;
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
_playbackService.TimelineChanged += PlaybackService_TimelineChanged;
_mediaSessionsService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_mediaSessionsService.SongInfoChanged += PlaybackService_SongInfoChanged;
_mediaSessionsService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
_mediaSessionsService.TimelineChanged += PlaybackService_TimelineChanged;
_isPlaying = _playbackService.IsPlaying;
_isPlaying = _mediaSessionsService.IsPlaying;
UpdateColorConfig();
}

View File

@@ -17,7 +17,7 @@ using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
@@ -35,15 +35,15 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isDockMode)
{
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
FillBackground(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value * _albumArtBgOpacity / 100f);
}
else if (_isDesktopMode)
{
FillBackgroundColor(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value);
FillBackground(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value * _albumArtBgOpacity / 100f);
}
else
{
FillBackgroundColor(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
FillBackground(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
DrawAlbumArtBackground(control, combinedDs);
}
@@ -83,7 +83,7 @@ namespace BetterLyrics.WinUI3.ViewModels
$"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" +
$"Song duration: {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}\n" +
$"Y offset: {_canvasYScrollTransition.Value}",
new Vector2(10, 40),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
@@ -259,7 +259,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
);
if (line.HighlightOpacityTransition.Value !=0)
if (line.HighlightOpacityTransition.Value != 0)
{
// 再叠加高亮行歌词层(前景歌词层)
using var mask = new CanvasCommandList(control.Device);
@@ -324,13 +324,13 @@ namespace BetterLyrics.WinUI3.ViewModels
);
// Brushes
using var fadeInBrush = GetHorizontalFillBrush(
using var fadeInBrush = CreateHorizontalFillBrush(
control,
[(0f, 0f), (1f, 1f)],
(float)highlightRect.Right - fadingWidth,
fadingWidth
);
using var fadeOutBrush = GetHorizontalFillBrush(
using var fadeOutBrush = CreateHorizontalFillBrush(
control,
[(0f, 1f), (1f, 0f)],
(float)highlightRect.Right,
@@ -345,22 +345,34 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
float height = 0f;
//float height = 0f;
var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
if (regions.Length > 0)
{
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
//height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;
for (int j = 0; j < regions.Length; j++)
{
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + line.Position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
maskDs.FillRectangle(rect, Colors.White);
}
}
maskDs.FillRectangle(
new Rect(
textLayout.LayoutBounds.X,
line.Position.Y,
textLayout.LayoutBounds.Width,
height
),
Colors.White
);
//maskDs.FillRectangle(
// new Rect(
// textLayout.LayoutBounds.X,
// line.Position.Y,
// textLayout.LayoutBounds.Width,
// height
// ),
// Colors.White
//);
}
using var opacityEffect = new OpacityEffect
@@ -438,7 +450,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
private void FillBackgroundColor(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
private void FillBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
{
ds.FillRoundedRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
@@ -448,7 +460,17 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
private CanvasLinearGradientBrush GetHorizontalFillBrush(
private void FillBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasLinearGradientBrush brush, float radius, float opacity)
{
ds.FillRoundedRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
radius,
radius,
brush
);
}
private CanvasLinearGradientBrush CreateHorizontalFillBrush(
ICanvasAnimatedControl control,
List<(float position, float opacity)> stops,
float startX,
@@ -465,5 +487,23 @@ namespace BetterLyrics.WinUI3.ViewModels
EndPoint = new Vector2(startX + width, 0),
};
}
private CanvasLinearGradientBrush CreateVerticalFillBrush(
ICanvasAnimatedControl control,
List<(float position, Color color)> stops,
float startY,
float height
)
{
return new CanvasLinearGradientBrush(control, stops.Select(x => new CanvasGradientStop
{
Position = x.position,
Color = x.color,
}).ToArray())
{
StartPoint = new Vector2(0, startY),
EndPoint = new Vector2(0, startY + height),
};
}
}
}

View File

@@ -7,7 +7,7 @@ using System;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{

View File

@@ -6,10 +6,11 @@ using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using System;
using System.Collections.ObjectModel;
using System.Linq;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
: IRecipient<PropertyChangedMessage<int>>,
@@ -24,14 +25,15 @@ namespace BetterLyrics.WinUI3.ViewModels
IRecipient<PropertyChangedMessage<LineRenderingType>>,
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<EasingType>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
IRecipient<PropertyChangedMessage<DockPlacement>>,
IRecipient<PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>>>,
IRecipient<PropertyChangedMessage<ObservableCollection<LocalMediaFolder>>>
{
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LocalMediaFolders))
{
// Music lib changed, re-fetch lyrics
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
@@ -43,13 +45,19 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
public void Receive(PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message)
public void Receive(PropertyChangedMessage<ObservableCollection<MediaSourceProviderInfo>> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsSearchProvidersInfo))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.MediaSourceProvidersInfo))
{
// Lyrics search providers info changed, re-fetch lyrics
_mediaSourceProvidersInfo = message.NewValue.ToList();
UpdateTimelineSyncThreshold();
UpdatePositionOffset();
UpdateIsLastFMTrackEnabled();
// Media source providers info changed (maybe include lyrics search providers info changed), re-fetch lyrics
_logger.LogInformation("Lyrics search providers info changed, refreshing lyrics.");
_ = _refreshLyricsRunner.RunAsync(async token =>
{
@@ -61,31 +69,31 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.IsDynamicCoverOverlayEnabled))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsDynamicCoverOverlayEnabled))
{
_isDynamicCoverOverlayEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsDebugOverlayEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsDebugOverlayEnabled))
{
_isDebugOverlayEnabled = message.NewValue;
_isDebugOverlayEnabledChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsGlowEffectEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsLyricsGlowEffectEnabled))
{
_isLyricsGlowEffectEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsFanLyricsEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsFanLyricsEnabled))
{
_isFanLyricsEnabled = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLyricsFloatAnimationEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsLyricsFloatAnimationEnabled))
{
_isLyricsFloatAnimationEnabled = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.IsLibreTranslateEnabled))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.IsLibreTranslateEnabled))
{
_isLibreTranslateEnabled = message.NewValue;
UpdateTranslations();
@@ -145,19 +153,19 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateColorConfig();
}
}
else if (message.Sender is SettingsPageViewModel)
else if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomBgFontColor))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsCustomBgFontColor))
{
_customBgFontColor = message.NewValue;
UpdateColorConfig();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomFgFontColor))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsCustomFgFontColor))
{
_customFgFontColor = message.NewValue;
UpdateColorConfig();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsCustomStrokeFontColor))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsCustomStrokeFontColor))
{
_customStrokeFontColor = message.NewValue;
UpdateColorConfig();
@@ -167,9 +175,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<float> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsLineSpacingFactor))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsLineSpacingFactor))
{
_lyricsLineSpacingFactor = message.NewValue;
_isLayoutChanged = true;
@@ -179,94 +187,83 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<int> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.CoverImageRadius))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.CoverImageRadius))
{
_albumArtCornerRadius = message.NewValue;
_isAlbumArtCornerRadiusChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayOpacity))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.CoverOverlayOpacity))
{
_albumArtBgOpacity = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverOverlayBlurAmount))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.CoverOverlayBlurAmount))
{
_albumArtBgBlurAmount = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.CoverAcrylicEffectAmount))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.CoverAcrylicEffectAmount))
{
_coverAcrylicEffectAmount = message.NewValue;
_isCoverAcrylicEffectAmountChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsVerticalEdgeOpacity))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsVerticalEdgeOpacity))
{
_lyricsVerticalEdgeOpacity = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBlurAmount))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsBlurAmount))
{
_lyricsBlurAmount = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStandardFontSize))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsStandardFontSize))
{
_lyricsStandardFontSize = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDockFontSize))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDockFontSize))
{
_lyricsDockFontSize = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsDesktopFontSize))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDesktopFontSize))
{
_lyricsDesktopFontSize = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SelectedTargetLanguageIndex))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.SelectedTargetLanguageIndex))
{
_targetLanguageIndex = message.NewValue;
_logger.LogInformation("Target language index changed: {Index}", _targetLanguageIndex);
UpdateTranslations();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontStrokeWidth))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsFontStrokeWidth))
{
_lyricsFontStrokeWidth = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollDuration))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsScrollDuration))
{
_canvasYScrollTransition.SetDuration(message.NewValue / 1000f);
}
else if (message.PropertyName == nameof(SettingsPageViewModel.TimelineSyncThreshold))
{
_timelineSyncThreshold = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontOpacity))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsBgFontOpacity))
{
_defaultOpacity = message.NewValue / 100f;
_isLayoutChanged = true;
}
}
else if (message.Sender is LyricsPageViewModel)
{
if (message.PropertyName == nameof(LyricsPageViewModel.PositionOffset))
{
_positionOffset = TimeSpan.FromMilliseconds(message.NewValue);
}
}
}
public void Receive(PropertyChangedMessage<LineRenderingType> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsGlowEffectScope))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsGlowEffectScope))
{
_lyricsGlowEffectScope = message.NewValue;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsHighlightScope))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsHighlightScope))
{
_lyricsHighlightScope = message.NewValue;
}
@@ -275,14 +272,14 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<TextAlignmentType> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsAlignmentType))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsAlignmentType))
{
_lyricsAlignmentType = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SongInfoAlignmentType))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.SongInfoAlignmentType))
{
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment =
message.NewValue.ToCanvasHorizontalAlignment();
@@ -297,19 +294,19 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<LyricsFontColorType> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBgFontColorType))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsBgFontColorType))
{
_lyricsBgFontColorType = message.NewValue;
UpdateColorConfig();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFgFontColorType))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsFgFontColorType))
{
_lyricsFgFontColorType = message.NewValue;
UpdateColorConfig();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.LyricsStrokeFontColorType))
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsStrokeFontColorType))
{
_lyricsStrokeFontColorType = message.NewValue;
UpdateColorConfig();
@@ -319,9 +316,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<LyricsFontWeight> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontWeight))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsFontWeight))
{
_lyricsTextFormat.FontWeight = message.NewValue.ToFontWeight();
_isLayoutChanged = true;
@@ -331,9 +328,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<ElementTheme> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsBackgroundTheme))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsBackgroundTheme))
{
_lyricsBgTheme = message.NewValue;
UpdateColorConfig();
@@ -343,9 +340,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<EasingType> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsScrollEasingType))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsScrollEasingType))
{
_canvasYScrollTransition.SetEasingType(message.NewValue);
}
@@ -354,13 +351,29 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LyricsFontFamily))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsFontFamily))
{
_lyricsTextFormat.FontFamily = _artistTextFormat.FontFamily = _titleTextFormat.FontFamily = message.NewValue;
_isLayoutChanged = true;
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsTranslationSeparator))
{
_lyricsTranslationSeparator = message.NewValue;
UpdateTranslations();
}
}
}
public void Receive(PropertyChangedMessage<DockPlacement> message)
{
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.DockPlacement))
{
_dockPlacement = message.NewValue;
}
}
}
}

View File

@@ -8,7 +8,7 @@ using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{

View File

@@ -7,6 +7,7 @@ using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
@@ -16,7 +17,7 @@ using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
@@ -43,6 +44,12 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isPlaying)
{
TotalTime += _elapsedTime;
_totalPlayingTime += _elapsedTime;
if (_isLastFMTrackEnabled && !_isLastFMTracked && SongInfo?.Duration != null && SongInfo.Duration > 0 && _totalPlayingTime.TotalSeconds >= SongInfo.Duration * 0.5)
{
_isLastFMTracked = true;
_lastFMService.TrackAsync(SongInfo);
}
}
var playingLineIndex = GetCurrentPlayingLineIndex();
@@ -292,7 +299,7 @@ namespace BetterLyrics.WinUI3.ViewModels
line.Position = new Vector2(0, y);
line.UpdateTextLayout(control, _lyricsTextFormat, _maxLyricsWidth, _canvasHeight, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
line.UpdateCenterPosition(_maxLyricsWidth, _isDockMode ? TextAlignmentType.Center : _lyricsAlignmentType);
//line.UpdateTextGeometry();
//line.UpdateFontEffect(control, _isDesktopMode, _strokeFontColor, _lyricsFontStrokeWidth, _bgFontColor);
@@ -579,7 +586,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (_coverAcrylicEffectAmount > 0)
{
var ret = NoiseOverlayHelper.GenerateNoiseBitmapBGRA((int)_canvasWidth, (int)_canvasHeight);
var ret = ImageHelper.GenerateNoiseBGRA((int)_canvasWidth, (int)_canvasHeight);
_coverAcrylicNoiseCanvasBitmap?.Dispose();
_coverAcrylicNoiseCanvasBitmap = null;
_coverAcrylicNoiseCanvasBitmap = CanvasBitmap.CreateFromBytes(
@@ -591,5 +598,27 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
}
private MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo()
{
return _mediaSourceProvidersInfo.Where(x => x.Provider == SongInfo?.SourceAppUserModelId)?.FirstOrDefault();
}
private void UpdateTimelineSyncThreshold()
{
_timelineSyncThreshold = GetCurrentMediaSourceProviderInfo()?.TimelineSyncThreshold ?? 0;
}
private void UpdatePositionOffset()
{
var current = GetCurrentMediaSourceProviderInfo();
_positionOffset = TimeSpan.FromMilliseconds(current?.PositionOffset ?? 0);
}
private void UpdateIsLastFMTrackEnabled()
{
var current = GetCurrentMediaSourceProviderInfo();
_isLastFMTrackEnabled = current?.IsLastFMTrackEnabled ?? false;
}
}
}

View File

@@ -5,7 +5,13 @@ using BetterLyrics.WinUI3.Events;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
@@ -21,10 +27,14 @@ using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel : BaseViewModel
{
private bool _isLastFMTrackEnabled = false;
private bool _isLastFMTracked = false;
private TimeSpan _totalPlayingTime = TimeSpan.Zero;
private TimeSpan _elapsedTime = TimeSpan.Zero;
[ObservableProperty]
@@ -100,8 +110,9 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ILyricsSearchService _lyrcsSearchService;
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly IMediaSessionsService _mediaSessionsService;
private readonly ITranslateService _translateService;
private readonly ILastFMService _lastFMService;
private readonly ILogger _logger;
private readonly float _leftMargin = 36f;
@@ -110,6 +121,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly float _topMargin = 36f;
private readonly float _bottomMargin = 36f;
private DockPlacement _dockPlacement;
private Color _adaptiveGrayedFontColor = Colors.Transparent;
private Color? _adaptiveColoredFontColor = null;
@@ -157,8 +170,10 @@ namespace BetterLyrics.WinUI3.ViewModels
private bool _showTranslationOnly;
private int _targetLanguageIndex;
private bool _isLibreTranslateEnabled;
private string _lyricsTranslationSeparator;
private int _timelineSyncThreshold;
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
private int _timelineSyncThreshold = 0;
private CanvasTextFormat _lyricsTextFormat = new()
{
@@ -357,6 +372,11 @@ namespace BetterLyrics.WinUI3.ViewModels
if (Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds) >= _timelineSyncThreshold)
{
TotalTime = e.Position;
if (TotalTime.TotalSeconds <= 1)
{
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
}
}
@@ -364,6 +384,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
SongInfo = e.SongInfo;
UpdateTimelineSyncThreshold();
UpdatePositionOffset();
UpdateIsLastFMTrackEnabled();
if (SongInfo?.Title != _songTitle || SongInfo?.Artist != _songArtist)
{
_lastSongTitle = _songTitle;
@@ -383,6 +407,10 @@ namespace BetterLyrics.WinUI3.ViewModels
await RefreshLyricsAsync(token);
});
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
}
@@ -459,7 +487,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], 50);
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], _lyricsTranslationSeparator, 50);
_langIndex = 0;
}
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
@@ -480,7 +508,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _lyricsTranslationSeparator);
_langIndex = 0;
}
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
@@ -508,6 +536,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (SongInfo != null)
{
(lyricsRaw, lyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
SongInfo.SourceAppUserModelId ?? "",
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",

View File

@@ -3,8 +3,11 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -17,6 +20,7 @@ using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using Vanara.PInvoke;
using Windows.System;
@@ -34,7 +38,7 @@ namespace BetterLyrics.WinUI3
IRecipient<PropertyChangedMessage<ElementTheme>>,
IRecipient<PropertyChangedMessage<DockPlacement>>
{
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private ForegroundWindowWatcher? _windowWatcher = null;
private bool _ignoreFullscreenWindow;
private bool _hideWindowWhenNotPlaying;
@@ -53,7 +57,7 @@ namespace BetterLyrics.WinUI3
_dockWindowHeight = _settingsService.DockWindowHeight;
OnIsImmersiveModeChanged(_settingsService.IsImmersiveMode);
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
_mediaSessionsService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
}
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
@@ -106,7 +110,7 @@ namespace BetterLyrics.WinUI3
if (IsDockMode || IsDesktopMode)
{
if (_hideWindowWhenNotPlaying && !_playbackService.IsPlaying)
if (_hideWindowWhenNotPlaying && !_mediaSessionsService.IsPlaying)
{
if (IsDockMode)
{
@@ -222,16 +226,16 @@ namespace BetterLyrics.WinUI3
var hwnd = WindowNative.GetWindowHandle(window);
_windowWatcher = new ForegroundWindowWatcher(
hwnd,
onWindowChanged =>
fgHwnd =>
{
_dispatcherQueueTimer.Debounce(() =>
{
if (_ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
if ((IsDockMode || IsDesktopMode) && _ignoreFullscreenWindow && window.AppWindow.Presenter is OverlappedPresenter presenter)
{
presenter.IsAlwaysOnTop = true;
}
UpdateAccentColor(hwnd);
}, TimeSpan.FromMilliseconds(300));
}, Constants.Time.DebounceTimeout);
}
);
_windowWatcher.Start();

View File

@@ -3,6 +3,8 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
@@ -408,9 +410,9 @@ namespace BetterLyrics.WinUI3.ViewModels
public void Receive(PropertyChangedMessage<ObservableCollection<LocalMediaFolder>> message)
{
if (message.Sender is SettingsPageViewModel)
if (message.Sender is SettingsPageViewModel.SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.LocalMediaFolders))
if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LocalMediaFolders))
{
RefreshSongs();
}

View File

@@ -1,27 +1,43 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel
{
public partial class SettingsPageViewModel
{
public SettingsPageViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService, IPlaybackService playbackService, ITranslateService libreTranslateService) : base(settingsService)
public SettingsPageViewModel(
ISettingsService settingsService,
ILibWatcherService libWatcherService,
IMediaSessionsService mediaSessionsService,
ITranslateService libreTranslateService,
ILastFMService lastFMService) : base(settingsService)
{
_libWatcherService = libWatcherService;
_playbackService = playbackService;
_mediaSessionsService = mediaSessionsService;
_libreTranslateService = libreTranslateService;
// LastFM
_lastFMService = lastFMService;
_lastFMService.UserChanged += LastFMService_UserChanged;
_lastFMService.IsAuthenticatedChanged += LastFMService_IsAuthenticatedChanged;
IsLastFMAuthenticated = _lastFMService.IsAuthenticated;
LastFMUser = _lastFMService.User;
IsLibreTranslateEnabled = _settingsService.IsLibreTranslateEnabled;
LibreTranslateServer = _settingsService.LibreTranslateServer;
SelectedTargetLanguageIndex = _settingsService.SelectedTargetLanguageIndex;
LocalMediaFolders = [.. _settingsService.LocalMediaFolders];
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
AlbumArtSearchProvidersInfo = [.. _settingsService.AlbumArtSearchProvidersInfo];
Language = _settingsService.Language;
@@ -64,14 +80,14 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
LyricsBackgroundTheme = _settingsService.LyricsBackgroundTheme;
MediaSourceProvidersInfo = [.. _settingsService.MediaSourceProvidersInfo];
SelectedMediaSourceProvider = MediaSourceProvidersInfo.FirstOrDefault();
IgnoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
LyricsScrollEasingType = _settingsService.LyricsScrollEasingType;
LyricsScrollDuration = _settingsService.LyricsScrollDuration;
TimelineSyncThreshold = _settingsService.TimelineSyncThreshold;
IsLyricsFloatAnimationEnabled = _settingsService.IsLyricsFloatAnimationEnabled;
ResetPositionOffsetOnSongChanged = _settingsService.ResetPositionOffsetOnSongChanged;
LockHotKeyIndex = _settingsService.LockHotKeyIndex;
LXMusicServer = _settingsService.LXMusicServer;
@@ -88,7 +104,29 @@ namespace BetterLyrics.WinUI3.ViewModels
MonitorDeviceNames = [.. MonitorHelper.GetAllMonitorDeviceNames()];
SelectedDockMonitorDeviceName = _settingsService.DockMonitorDeviceName;
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
LyricsTranslationSeparator = _settingsService.LyricsTranslationSeparator;
_mediaSessionsService.MediaSourceProvidersInfoChanged += MediaSessionsService_SessionIdsChanged;
_mediaSessionsService.SongInfoChanged += MediaSessionsService_SongInfoChanged;
}
private void MediaSessionsService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
{
var current = MediaSourceProvidersInfo.Where(x => x.Provider == e.SongInfo?.SourceAppUserModelId)?.FirstOrDefault();
if (_mediaSessionsService.Position.TotalSeconds <= 1 && current?.ResetPositionOffsetOnSongChanged == true)
{
current.PositionOffset = 0;
}
}
private void LastFMService_IsAuthenticatedChanged(object? sender, Events.LastFMIsAuthenticatedChangedEventArgs e)
{
IsLastFMAuthenticated = e.IsAuthenticated;
}
private void LastFMService_UserChanged(object? sender, Events.LastFMUserChangedEventArgs e)
{
LastFMUser = e.User;
}
}
}

View File

@@ -5,7 +5,7 @@ using Microsoft.UI.Xaml;
using Windows.Globalization;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel
{
public partial class SettingsPageViewModel
{
@@ -176,18 +176,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_settingsService.LyricsVerticalEdgeOpacity = value;
}
partial void OnTimelineSyncThresholdChanged(int value)
{
_settingsService.TimelineSyncThreshold = value;
}
partial void OnIsLyricsFloatAnimationEnabledChanged(bool value)
{
_settingsService.IsLyricsFloatAnimationEnabled = value;
}
partial void OnResetPositionOffsetOnSongChangedChanged(bool value)
{
_settingsService.ResetPositionOffsetOnSongChanged = value;
}
partial void OnLyricsBgFontOpacityChanged(int value)
{
_settingsService.LyricsBgFontOpacity = value;
@@ -227,5 +219,9 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_settingsService.DockMonitorDeviceName = value;
}
partial void OnLyricsTranslationSeparatorChanged(string value)
{
_settingsService.LyricsTranslationSeparator = value;
}
}
}

View File

@@ -2,6 +2,7 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using CommunityToolkit.Mvvm.ComponentModel;
using Hqub.Lastfm.Entities;
using Microsoft.UI.Xaml;
using System;
using System.Collections.Generic;
@@ -11,7 +12,7 @@ using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel
{
public partial class SettingsPageViewModel
{
@@ -87,10 +88,6 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial ObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial ObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
@@ -98,6 +95,9 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial ObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; }
[ObservableProperty]
public partial MediaSourceProviderInfo? SelectedMediaSourceProvider { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsFanLyricsEnabled { get; set; }
@@ -181,10 +181,6 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial object NavViewSelectedItemTag { get; set; } = "App";
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool ResetPositionOffsetOnSongChanged { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial bool IsLyricsFloatAnimationEnabled { get; set; }
@@ -215,10 +211,6 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial int LyricsScrollDuration { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial int TimelineSyncThreshold { get; set; }
[ObservableProperty]
public partial bool IsLXMusicServerTesting { get; set; } = false;
@@ -234,11 +226,23 @@ namespace BetterLyrics.WinUI3.ViewModels
[NotifyPropertyChangedRecipients]
public partial int DockWindowHeight { get; set; }
// Dock Monitor
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial string SelectedDockMonitorDeviceName { get; set; }
[ObservableProperty]
public partial ObservableCollection<string> MonitorDeviceNames { get; set; }
// LastFM
[ObservableProperty]
public partial bool IsLastFMAuthenticated { get; set; }
[ObservableProperty]
public partial User? LastFMUser { get; set; }
[ObservableProperty]
[NotifyPropertyChangedRecipients]
public partial string LyricsTranslationSeparator { get; set; }
}
}

View File

@@ -4,45 +4,45 @@ using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LibWatcherService;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.TranslateService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.ObjectModel;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Windows.ApplicationModel;
using Windows.Globalization;
using Windows.UI;
using WinRT.Interop;
using MetadataHelper = BetterLyrics.WinUI3.Helper.MetadataHelper;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel
{
public partial class SettingsPageViewModel : BaseViewModel
{
private readonly ILibWatcherService _libWatcherService;
private readonly IPlaybackService _playbackService;
private readonly IMediaSessionsService _mediaSessionsService;
private readonly ITranslateService _libreTranslateService;
private readonly ILastFMService _lastFMService;
private readonly string _autoStartupTaskId = "AutoStartup";
private void PlaybackService_SessionIdsChanged(object? sender, Events.MediaSourceProvidersInfoEventArgs e)
private void MediaSessionsService_SessionIdsChanged(object? sender, Events.MediaSourceProvidersInfoEventArgs e)
{
MediaSourceProvidersInfo = [.. e.MediaSourceProviersInfo];
}
public void OnLyricsSearchProvidersReordered()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
_settingsService.MediaSourceProvidersInfo = [.. MediaSourceProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
MediaSourceProvidersInfo,
MediaSourceProvidersInfo,
nameof(MediaSourceProvidersInfo)
);
}
@@ -70,16 +70,6 @@ namespace BetterLyrics.WinUI3.ViewModels
Broadcast(LocalMediaFolders, LocalMediaFolders, nameof(LocalMediaFolders));
}
public void ToggleLyricsSearchProvider()
{
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
Broadcast(
LyricsSearchProvidersInfo,
LyricsSearchProvidersInfo,
nameof(LyricsSearchProvidersInfo)
);
}
public void ToggleAlbumArtSearchProvider(AlbumArtSearchProviderInfo providerInfo)
{
_settingsService.AlbumArtSearchProvidersInfo = [.. AlbumArtSearchProvidersInfo];
@@ -90,13 +80,17 @@ namespace BetterLyrics.WinUI3.ViewModels
);
}
public void ToggleMediaSourceProvider(MediaSourceProviderInfo providerInfo)
public void BroadcastMediaSourceProvidersInfoChanged()
{
Broadcast(
MediaSourceProvidersInfo,
MediaSourceProvidersInfo,
nameof(MediaSourceProvidersInfo)
);
_dispatcherQueueTimer.Debounce(() =>
{
_settingsService.MediaSourceProvidersInfo = [.. MediaSourceProvidersInfo];
Broadcast(
MediaSourceProvidersInfo,
MediaSourceProvidersInfo,
nameof(MediaSourceProvidersInfo)
);
}, TimeSpan.FromMilliseconds(100));
}
private void AddFolderAsync(string path)
@@ -130,7 +124,7 @@ namespace BetterLyrics.WinUI3.ViewModels
[RelayCommand]
private async Task LaunchProjectGitHubPageAsync()
{
await Windows.System.Launcher.LaunchUriAsync(new Uri(MetadataHelper.GithubUrl));
await Windows.System.Launcher.LaunchUriAsync(new Uri(Constants.Link.GithubUrl));
}
[RelayCommand]
@@ -223,9 +217,27 @@ namespace BetterLyrics.WinUI3.ViewModels
SelectedDockMonitorDeviceName = MonitorHelper.GetPrimaryMonitorDeviceName();
}
[RelayCommand]
private async Task LastFMAuthAsync()
{
await _lastFMService.AuthAsync();
}
[RelayCommand]
private async Task LastFMUnAuthAsync()
{
await _lastFMService.UnAuthAsync();
}
[RelayCommand]
private async Task LastFMRefreshAsync()
{
await _lastFMService.RefreshAsync();
}
public async Task<bool> ToggleAutoStartupAsync(bool target)
{
StartupTask startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
StartupTask startupTask = await StartupTask.GetAsync(Constants.App.AutoStartupTaskId);
if (target)
{
await startupTask.RequestEnableAsync();
@@ -240,7 +252,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public async Task<bool> DetectIsAutoStartupEnabledAsync()
{
bool result = false;
var startupTask = await StartupTask.GetAsync(_autoStartupTaskId);
var startupTask = await StartupTask.GetAsync(Constants.App.AutoStartupTaskId);
switch (startupTask.State)
{
case StartupTaskState.Disabled:

View File

@@ -1,4 +1,4 @@
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
namespace BetterLyrics.WinUI3.ViewModels
{

View File

@@ -1,5 +1,5 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
@@ -18,7 +18,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial bool IsLyricsWindowLocked { get; set; } = false;
[ObservableProperty]
public partial string ToolTipText { get; set; } = MetadataHelper.AppName;
public partial string ToolTipText { get; set; } = Constants.App.AppName;
public void Receive(PropertyChangedMessage<bool> message)
{

View File

@@ -79,52 +79,6 @@
<TextBlock Text="{Binding ElementName=TimelineSlider, Path=Maximum, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
</StackPanel>
<!-- Position offset -->
<Button Click="TimelineOffsetButton_Click" Style="{StaticResource GhostButtonStyle}">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
Glyph="&#xECE7;"
RenderTransformOrigin="0.5,0.5">
<FontIcon.RenderTransform>
<RotateTransform Angle="90" CenterX="0.5" CenterY="0.5" />
</FontIcon.RenderTransform>
</FontIcon>
<ToolTipService.ToolTip>
<ToolTip x:Name="TimelineOffsetToolTip" x:Uid="LyricsPageTimelineOffsetButtonToolTip" />
</ToolTipService.ToolTip>
<Button.ContextFlyout>
<Flyout x:Name="TimelineOffsetFlyout" ShouldConstrainToRootBounds="False">
<StackPanel>
<Slider
x:Uid="MainPagePositionOffsetSlider"
Maximum="5000"
Minimum="-5000"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
Value="{x:Bind ViewModel.PositionOffset, Mode=TwoWay}" />
<RelativePanel>
<TextBlock
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{x:Bind ViewModel.PositionOffset, Mode=OneWay}" />
<Button
Click="PositionOffsetResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE777;}"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Style="{StaticResource GhostButtonStyle}" />
</RelativePanel>
<CheckBox IsChecked="{x:Bind ViewModel.ResetPositionOffsetOnSongChanged, Mode=TwoWay}">
<TextBlock x:Uid="LyricsPagePositionOffsetHint" />
</CheckBox>
</StackPanel>
</Flyout>
</Button.ContextFlyout>
</Button>
</StackPanel>
</Grid>

View File

@@ -1,7 +1,8 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
@@ -16,7 +17,7 @@ namespace BetterLyrics.WinUI3.Views
public sealed partial class LyricsPage : Page
{
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private readonly IPlaybackService _playbackService = Ioc.Default.GetRequiredService<IPlaybackService>();
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
public LyricsPageViewModel ViewModel => (LyricsPageViewModel)DataContext;
@@ -50,11 +51,6 @@ namespace BetterLyrics.WinUI3.Views
_settingsService.DisplayType = ViewModel.DisplayType;
}
private void PositionOffsetResetButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.PositionOffset = 0;
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (ViewModel.IsImmersiveMode && BottomCommandGrid.Children.Count != 0)
@@ -78,11 +74,6 @@ namespace BetterLyrics.WinUI3.Views
DisplayTypeSwitchFlyout.ShowAt(BottomRightCommandStackPanel);
}
private void TimelineOffsetButton_Click(object sender, RoutedEventArgs e)
{
TimelineOffsetFlyout.ShowAt(BottomLeftCommandStackPanel);
}
private void TranslationButton_Click(object sender, RoutedEventArgs e)
{
TranslationFlyout.ShowAt(BottomRightCommandStackPanel);
@@ -142,7 +133,7 @@ namespace BetterLyrics.WinUI3.Views
private void TimelineSliderOverlay_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
{
_playbackService.ChangePosition(TimelineSlider.Value);
_mediaSessionsService.ChangePosition(TimelineSlider.Value);
}
}
}

View File

@@ -123,6 +123,9 @@
Command="{x:Bind ViewModel.ImmersiveToggleButtonEnabledChangedCommand}"
IsChecked="{x:Bind ViewModel.IsImmersiveMode, Mode=TwoWay}"
Style="{StaticResource TitleBarToggleButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip x:Uid="LyricsWindowImmersiveButtonToolTip" />
</ToolTipService.ToolTip>
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="{x:Bind ViewModel.TitleBarFontSize}"

View File

@@ -2,7 +2,7 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;

View File

@@ -160,6 +160,7 @@
x:Name="SelectAllToggleButton"
x:Uid="MusicGalleryPageSelectAll"
Click="SelectAllToggleButton_Click" />
<Button x:Uid="MusicGalleryPagePlayAll" Click="PlayAllButton_Click" />
<Button x:Uid="MusicGalleryPageAddToPlayingQueue">
<Button.Flyout>
<MenuFlyout>

View File

@@ -167,5 +167,15 @@ namespace BetterLyrics.WinUI3.Views
ViewModel.SelectedSongsTabInfoIndex = 0;
ViewModel.ApplyPlaylist();
}
private void PlayAllButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.TrackPlayingQueue.Clear();
ViewModel.PlayingSongIndex = -1;
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.PlayingSongIndex + 1, SongListView.Items.Cast<Track>().Select(x => new PlayQueueItem(x)));
ViewModel.PlayingSongIndex = ViewModel.PlayingSongIndex + 1;
ViewModel.PlayTrackAt(ViewModel.PlayingSongIndex);
}
}
}

View File

@@ -23,62 +23,73 @@
IsSettingsVisible="False"
SelectionChanged="NavView_SelectionChanged">
<NavigationView.MenuItems>
<NavigationViewItem
x:Uid="SettingsPageApp"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xECAA;}"
IsSelected="True"
Tag="App" />
<NavigationViewItem
x:Uid="SettingsPageAlbumStyle"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE93C;}"
Tag="AlbumArtStyle" />
<NavigationViewItem
x:Uid="SettingsPageMediaLib"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8B7;}"
Tag="MediaLib" />
<NavigationViewItem
x:Uid="SettingsPageAlbumLib"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7FA;}"
Tag="AlbumArtLib" />
<NavigationViewItem
x:Uid="SettingsPagePlaybackLib"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEA69;}"
Tag="PlaybackLib" />
<NavigationViewItem
x:Uid="SettingsPageLyricsLib"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8F1;}"
Tag="LyricsLib" />
<NavigationViewItem
x:Uid="SettingsPageBackgroundOverlay"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xF5EF;}"
Tag="Background" />
<NavigationViewItem
x:Uid="SettingsPageLyrics"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEDC6;}"
Tag="Lyrics" />
<NavigationViewItem
x:Uid="SettingsPageTranslation"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7F0;}"
Tag="Translation" />
<NavigationViewItem
x:Uid="SettingsPageLastFM"
Icon="{ui:BitmapIcon Source=ms-appx:///Assets/LastFM.png}"
Tag="LastFM" />
<NavigationViewItem
x:Uid="SettingsPageAbout"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE946;}"
Tag="About" />
<NavigationViewItem
x:Uid="SettingsPageDev"
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xEC7A;}"
Tag="Dev" />
</NavigationView.MenuItems>
<ScrollViewer Padding="36,0">
@@ -200,13 +211,13 @@
<controls:SettingsCard x:Uid="SettingsPageDockMonitor" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE7F4;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedDockMonitorDeviceName, Mode=TwoWay}" />
<Button
Command="{x:Bind ViewModel.RefreshMonitorDeviceNamesCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE72C;}"
Style="{StaticResource GhostButtonStyle}" />
<ComboBox ItemsSource="{x:Bind ViewModel.MonitorDeviceNames, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedDockMonitorDeviceName, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
@@ -383,31 +394,168 @@
</StackPanel>
</controls:Case>
<!-- Playback source -->
<!-- Playback and lyrics source -->
<controls:Case Value="PlaybackLib">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageMediaSourceProvidersConfig" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEA69;}" />
<ListView
x:Name="MediaSourceProvidersListView"
Margin="0,-4,0,0"
ItemsSource="{x:Bind ViewModel.MediaSourceProvidersInfo, Mode=OneWay}"
SelectionMode="None">
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<Grid>
<NavigationView
IsBackButtonVisible="Collapsed"
IsBackEnabled="False"
IsSettingsVisible="False"
MenuItemsSource="{x:Bind ViewModel.MediaSourceProvidersInfo, Mode=OneWay}"
PaneDisplayMode="Top"
SelectedItem="{x:Bind ViewModel.SelectedMediaSourceProvider, Mode=TwoWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<NavigationView.MenuItemTemplate>
<DataTemplate x:DataType="models:MediaSourceProviderInfo">
<controls:SettingsCard Padding="60,0,48,0" Header="{Binding Provider, Mode=OneWay}">
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" Toggled="MediaSourceProviderToggleSwitch_Toggled" />
</controls:SettingsCard>
<NavigationViewItem>
<NavigationViewItem.Icon>
<ImageIcon Source="{Binding Provider, Converter={StaticResource MediaSourceProviderToLogoUriConverter}, Mode=OneWay}" />
</NavigationViewItem.Icon>
<NavigationViewItem.Content>
<TextBlock
MaxWidth="200"
Text="{Binding Provider, Converter={StaticResource MediaSourceProviderToDisplayedNameConverter}, Mode=OneWay}"
TextWrapping="Wrap" />
</NavigationViewItem.Content>
</NavigationViewItem>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</NavigationView.MenuItemTemplate>
<NavigationView.Content>
<Grid Background="{ThemeResource CardStrokeColorDefaultBrush}">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard x:Uid="SettingsPageMediaSourceProvidersConfig">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsEnabled, Mode=OneWay}" Toggled="MediaSourceProviderToggleSwitch_Toggled" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMTrack" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsLastFMTrackEnabled, Mode=OneWay}" Toggled="MediaSourceProviderLastFMTrackToggleSwitch_Toggled" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" ms" />
<Slider
Maximum="1000"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
ValueChanged="MediaSourceProviderTimelineSyncThresholdSlider_ValueChanged"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsExpander x:Uid="MainPagePositionOffsetSlider" IsExpanded="True">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.SelectedMediaSourceProvider.PositionOffset, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" ms" />
<Slider
Maximum="5000"
Minimum="-5000"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
ValueChanged="MediaSourceProviderPositionOffsetSlider_ValueChanged"
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.PositionOffset, Mode=TwoWay}" />
<Button
Margin="6,0,0,0"
Click="MediaSourceProviderPositionOffsetResetButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=12,
Glyph=&#xE777;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="LyricsPagePositionOffsetHint">
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.ResetPositionOffsetOnSongChanged, Mode=TwoWay}" Toggled="ResetPositionOffsetOnSongChangedToggleSwitch_Toggled" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
<controls:SettingsCard x:Uid="SettingsPageLyricsSearchProvidersConfig" />
<ListView
x:Name="LyricsSearchProvidersListView"
Margin="0,-4,0,0"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="LyricsSearchProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.SelectedMediaSourceProvider.LyricsSearchProvidersInfo, Mode=OneWay}"
SelectionMode="None">
<ListView.OpacityTransition>
<ScalarTransition />
</ListView.OpacityTransition>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
<controls:SettingsCard Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" Toggled="LyricsSearchProviderToggleSwitch_Toggled" />
</controls:SettingsCard>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</Grid>
</NavigationView.Content>
</NavigationView>
<StackPanel
HorizontalAlignment="Center"
VerticalAlignment="Center"
Spacing="12">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="Equal"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.MediaSourceProvidersInfo.Count, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="0">
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
<Image MaxWidth="200" Source="/Assets/Leaf.png" />
<TextBlock
x:Uid="SettingsPagePlaybackNotFound"
HorizontalAlignment="Center"
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
</StackPanel>
</Grid>
</controls:Case>
<!-- Media lib -->
@@ -481,46 +629,6 @@
</StackPanel>
</controls:Case>
<!-- Lyrics source -->
<controls:Case Value="LyricsLib">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsCard
x:Name="LyricsSearchProvidersSettingsExpander"
x:Uid="SettingsPageLyricsSearchProvidersConfig"
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8F1;}" />
<ListView
x:Name="LyricsSearchProvidersListView"
Margin="0,-4,0,0"
AllowDrop="True"
CanDragItems="True"
CanReorderItems="True"
DragItemsCompleted="LyricsSearchProvidersListView_DragItemsCompleted"
ItemsSource="{x:Bind ViewModel.LyricsSearchProvidersInfo, Mode=OneWay}"
SelectionMode="None">
<ListView.OpacityTransition>
<ScalarTransition />
</ListView.OpacityTransition>
<ListView.ItemContainerStyle>
<Style TargetType="ListViewItem">
<Setter Property="HorizontalContentAlignment" Value="Stretch" />
<Setter Property="Margin" Value="0" />
<Setter Property="Padding" Value="0" />
</Style>
</ListView.ItemContainerStyle>
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
<controls:SettingsCard Padding="60,0,48,0" Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" Toggled="LyricsSearchProviderToggleSwitch_Toggled" />
</controls:SettingsCard>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</StackPanel>
</controls:Case>
<!-- Lyrics style and effect -->
<controls:Case Value="Lyrics">
@@ -780,6 +888,13 @@
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsTranslationSeparator" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xF57B;}">
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBox AcceptsReturn="True" Text="{x:Bind ViewModel.LyricsTranslationSeparator, Mode=TwoWay}" />
<Button Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, FontSize=12, Glyph=&#xE8FB;}" Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
</controls:SettingsCard>
<!-- Effect -->
<TextBlock
@@ -978,6 +1093,41 @@
</StackPanel>
</controls:Case>
<!-- Last.FM -->
<controls:Case Value="LastFM">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<controls:SettingsExpander
x:Uid="SettingsPageLastFMManager"
HeaderIcon="{ui:BitmapIcon Source=ms-appx:///Assets/LastFM.png}"
IsExpanded="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<StackPanel Orientation="Horizontal" Spacing="6">
<Button
x:Uid="SettingsPageLastFMAuth"
Command="{x:Bind ViewModel.LastFMAuthCommand}"
IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}" />
<Button
x:Uid="SettingsPageLastFMUnAuth"
Command="{x:Bind ViewModel.LastFMUnAuthCommand}"
IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}" />
</StackPanel>
<controls:SettingsExpander.Items>
<controls:SettingsCard x:Uid="SettingsPageLastFMUsername" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<HyperlinkButton Content="{x:Bind ViewModel.LastFMUser.Name, Mode=OneWay}" NavigateUri="{x:Bind ViewModel.LastFMUser.Url, Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMPlaycount" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<TextBlock Text="{x:Bind ViewModel.LastFMUser.Playcount, Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLastFMRegistered" IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<TextBlock Text="{x:Bind ViewModel.LastFMUser.Registered.ToLongDateString(), Mode=OneWay}" />
</controls:SettingsCard>
<controls:SettingsCard IsEnabled="{x:Bind ViewModel.IsLastFMAuthenticated, Mode=OneWay}">
<Button x:Uid="SettingsPageLastFMRefresh" Command="{x:Bind ViewModel.LastFMRefreshCommand}" />
</controls:SettingsCard>
</controls:SettingsExpander.Items>
</controls:SettingsExpander>
</StackPanel>
</controls:Case>
<!-- About -->
<controls:Case Value="About">
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
@@ -1037,25 +1187,6 @@
<ToggleSwitch IsOn="{x:Bind ViewModel.IsDebugOverlayEnabled, Mode=TwoWay}" />
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
<StackPanel Orientation="Horizontal">
<TextBlock x:Uid="SettingsPageSliderPrefix" VerticalAlignment="Center" />
<TextBlock VerticalAlignment="Center" Text="{x:Bind ViewModel.TimelineSyncThreshold, Mode=OneWay}" />
<TextBlock
Margin="0,0,14,0"
VerticalAlignment="Center"
Text=" ms" />
<Slider
Maximum="1000"
Minimum="0"
SnapsTo="Ticks"
StepFrequency="100"
TickFrequency="100"
TickPlacement="Outside"
Value="{x:Bind ViewModel.TimelineSyncThreshold, Mode=TwoWay}" />
</StackPanel>
</controls:SettingsCard>
<controls:SettingsCard x:Uid="SettingsPageLXMusicServer">
<StackPanel Orientation="Horizontal" Spacing="12">
<TextBox

View File

@@ -3,10 +3,12 @@
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.ViewModels.SettingsPageViewModel;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Threading.Tasks;
using Windows.System;
namespace BetterLyrics.WinUI3.Views
@@ -42,13 +44,7 @@ namespace BetterLyrics.WinUI3.Views
private void LyricsSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
{
if (toggleSwitch.DataContext is LyricsSearchProviderInfo providerInfo)
{
ViewModel.ToggleLyricsSearchProvider();
}
}
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
private void NavView_SelectionChanged(
@@ -71,9 +67,10 @@ namespace BetterLyrics.WinUI3.Views
{
if (sender is ToggleSwitch toggleSwitch)
{
if (toggleSwitch.DataContext is MediaSourceProviderInfo providerInfo)
if (ViewModel.SelectedMediaSourceProvider != null)
{
ViewModel.ToggleMediaSourceProvider(providerInfo);
ViewModel.SelectedMediaSourceProvider.IsEnabled = toggleSwitch.IsOn;
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
}
}
@@ -118,22 +115,54 @@ namespace BetterLyrics.WinUI3.Views
private void QQGroupButton_Click(object sender, RoutedEventArgs e)
{
Launcher.LaunchUriAsync(new Uri(MetadataHelper.QQGroupUrl));
Launcher.LaunchUriAsync(new Uri(Constants.Link.QQGroupUrl));
}
private void DiscodGroupButton_Click(object sender, RoutedEventArgs e)
{
Launcher.LaunchUriAsync(new Uri(MetadataHelper.DiscordUrl));
Launcher.LaunchUriAsync(new Uri(Constants.Link.DiscordUrl));
}
private void TelegramGroupButton_Click(object sender, RoutedEventArgs e)
{
Launcher.LaunchUriAsync(new Uri(MetadataHelper.TelegramUrl));
Launcher.LaunchUriAsync(new Uri(Constants.Link.TelegramUrl));
}
private void AutoStartupToggleSwitch_Unloaded(object sender, RoutedEventArgs e)
{
AutoStartupToggleSwitch.Toggled -= AutoStartupToggleSwitch_Toggled;
}
private void MediaSourceProviderLastFMTrackToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
if (sender is ToggleSwitch toggleSwitch)
{
if (ViewModel.SelectedMediaSourceProvider != null)
{
ViewModel.SelectedMediaSourceProvider.IsLastFMTrackEnabled = toggleSwitch.IsOn;
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
}
}
private void MediaSourceProviderTimelineSyncThresholdSlider_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
private void MediaSourceProviderPositionOffsetResetButton_Click(object sender, RoutedEventArgs e)
{
ViewModel.SelectedMediaSourceProvider?.PositionOffset = 0;
}
private void MediaSourceProviderPositionOffsetSlider_ValueChanged(object sender, Microsoft.UI.Xaml.Controls.Primitives.RangeBaseValueChangedEventArgs e)
{
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
private void ResetPositionOffsetOnSongChangedToggleSwitch_Toggled(object sender, RoutedEventArgs e)
{
ViewModel.BroadcastMediaSourceProvidersInfoChanged();
}
}
}