Compare commits

...

83 Commits

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

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

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:21:05 -04:00
Zhe Fang
7b2ff0cc8f 新增:
- 歌词原文译文分隔符现可自定义
- 音乐库新增“播放全部”按钮
- 接入 Last.fm 服务(分别为播放源配置是否开启 Last.fm 听歌记录服务)

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

修复:
- 在启用强制显示在全屏应用上时从停靠模式、桌面模式切回标准模式后窗口仍永远处于顶部的异常行为
- “歌词背景不透明度“选项对桌面模式、停靠模式无效的问题
2025-08-06 19:19:49 -04:00
Zhe Fang
7c9ab73a34 Fix #76 2025-08-04 14:38:02 -04:00
Zhe Fang
a08bf91784 fix 2025-08-04 13:36:56 -04:00
Zhe Fang
61906670fd fix 2025-08-04 11:51:09 -04:00
Zhe Fang
430b2f4d28 fix 2025-08-04 11:23:07 -04:00
Zhe Fang
eb05c1ea13 update readme 2025-08-04 10:01:37 -04:00
Zhe Fang
9bebf36e6a update readme 2025-08-04 09:53:02 -04:00
Zhe Fang
d2b0b6afb1 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-04 09:13:33 -04:00
Zhe Fang
9c2f4fbff9 update readme 2025-08-04 09:13:31 -04:00
Zhe Fang
1b69493afd Update release-to-telegram.yml 2025-08-03 20:38:37 -04:00
Zhe Fang
c524dc013c Update release-to-telegram.yml 2025-08-03 20:35:37 -04:00
Zhe Fang
9ca5939e57 fix 2025-08-03 20:29:58 -04:00
Zhe Fang
860abd4037 fix 2025-08-03 19:43:14 -04:00
Zhe Fang
618415016f fix 2025-08-03 17:05:40 -04:00
Zhe Fang
bfcba1425d 更新 FAQ.md 中的链接文本
在 FAQ.md 文件中,修改了 FAQ 部分的链接文本。将原有的“点此访问”替换为“Click here to visit”,并在中文、日文和韩文版本中相应更新为“点此前往”、“こちらをクリック”和“여기를 클릭하세요”。
2025-08-02 12:26:36 -04:00
Zhe Fang
0dc9ebf18e Revert "Add GitHub stars badge to README files and fix encoding issues in Chinese translations"
This reverts commit 35ca28ac7b.
2025-08-02 12:24:30 -04:00
Zhe Fang
366d396b93 revert 2025-08-02 12:22:09 -04:00
Zhe Fang
35ca28ac7b Add GitHub stars badge to README files and fix encoding issues in Chinese translations 2025-08-02 12:11:16 -04:00
Zhe Fang
1505933107 update FAQ links for consistency in language and clarity 2025-08-02 11:36:51 -04:00
Zhe Fang
958227d0f2 refactor font size settings and update lyrics renderer initialization; add FAQ documentation 2025-08-02 11:35:09 -04:00
Zhe Fang
b1978fec09 update version to 1.0.40.0 and refactor image drawing methods for better opacity handling 2025-08-02 09:15:28 -04:00
Zhe Fang
010040b376 fix incorrect lyrics/translation language detection 2025-08-01 15:50:19 -04:00
Zhe Fang
35272d8324 update readme 2025-08-01 12:57:43 -04:00
Zhe Fang
29a714fe87 update readme 2025-08-01 12:45:06 -04:00
Zhe Fang
78edc2b3ce update readme 2025-08-01 12:19:39 -04:00
Zhe Fang
932f9d3a4f 更新 README.ko.md 文件内容
删除旧说明并添加浮动窗口控制栏功能描述。
2025-08-01 12:18:29 -04:00
Zhe Fang
a56a7af08c 添加 issue 翻译工作流,删除 README 翻译工作流
在 `issues-translator.yml` 中添加了 "issue-translator" 工作流,支持在创建评论和打开问题时自动翻译非英语内容。使用 `usthe/issues-translate-action@v2.7` 动作,并提供自定义机器人的提示信息。

同时,删除了 `readme-translator.yml` 中的整个工作流,原本用于翻译 README 文件的多种语言(简体中文、繁体中文、日语和韩语)。
2025-08-01 12:14:29 -04:00
Zhe Fang
5427c1992f 更新多个 README 文件内容
- 在 README.ja.md 中添加了 NetEase Cloud Music 和 foobar2000 的详细说明,并更新了设置提示。
- 更新了 README.ja.md 中关于 Bilibili 的信息,增加了“现在就试试”的呼吁。
- 在 README.zh-TW.md 中更新了歌词获取工具的链接和描述,增加了对 QQ、NetEase 和 Kugou 的支持说明。
- 修改了 README.zh-TW.md 中 FAQ 部分的标题和内容,以更好地反映用户问题。
- 更新了 README.zh-TW.md 中关于 Apple Music 的说明,提供了更清晰的操作指导。
2025-08-01 12:13:34 -04:00
Zhe Fang
64d69f44c3 更新版本号并优化多语言文档
- 将 `Package.appxmanifest` 中的版本号更新为 `1.0.37.0`。
- 更新 `README.ja.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合日语表达。
- 更新 `README.ko.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合韩语表达。
- 更新 `README.zh-CN.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合中文表达。
- 更新 `README.zh-TW.md` 文件中的常见问题链接、徽章格式和项目描述,以更符合繁体中文表达。
2025-08-01 12:12:39 -04:00
Zhe Fang
d75fe4b27a Delete .github/workflows/readme-translator.yml 2025-08-01 11:58:06 -04:00
github-actions[bot]
eac9b3e28a docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:35 +00:00
github-actions[bot]
d6975ba2d4 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:22 +00:00
github-actions[bot]
aae8e1322a docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:29:09 +00:00
github-actions[bot]
88aac0eaf0 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 15:28:57 +00:00
Zhe Fang
c463d9bc5c Create issues-translator.yml 2025-08-01 11:28:33 -04:00
github-actions[bot]
f305b469f2 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:56 +00:00
github-actions[bot]
ea276f3e2e docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:42 +00:00
github-actions[bot]
15601e6ee4 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:28 +00:00
github-actions[bot]
44b14cab17 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 14:27:14 +00:00
Zhe Fang
0a97c56c77 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-08-01 10:26:58 -04:00
Zhe Fang
ac5dc5991f add using 2025-08-01 10:26:56 -04:00
github-actions[bot]
d22dc673e8 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:25 +00:00
github-actions[bot]
9eb2b83796 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:12 +00:00
github-actions[bot]
1cf6226a06 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:39:00 +00:00
github-actions[bot]
bce330a9e0 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:38:47 +00:00
Zhe Fang
687e286c2e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-31 20:38:34 -04:00
Zhe Fang
fc05035053 fix 2025-07-31 20:38:32 -04:00
github-actions[bot]
f45f5ead01 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:21:01 +00:00
github-actions[bot]
0eeea77896 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:49 +00:00
github-actions[bot]
78ca7d8435 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:35 +00:00
github-actions[bot]
98d2ac404d docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-08-01 00:20:23 +00:00
Zhe Fang
b4b1ffd58e Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-31 20:20:09 -04:00
Zhe Fang
2522bd00ab fix #32 and some improvemnets 2025-07-31 20:20:08 -04:00
github-actions[bot]
0601039fcf docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:32 +00:00
github-actions[bot]
857b95e525 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:20 +00:00
github-actions[bot]
4be855f11a docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:44:07 +00:00
github-actions[bot]
9e1018770d docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:43:55 +00:00
Zhe Fang
1235a09d19 docs: Add warning to foobar2000 timeline issue in README 2025-07-30 18:43:54 -04:00
github-actions[bot]
1647c3a2f1 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:42:15 +00:00
github-actions[bot]
588838acaa docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:59 +00:00
github-actions[bot]
25c772434c docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:44 +00:00
github-actions[bot]
2c597a3b37 docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:41:28 +00:00
Zhe Fang
80a44977a6 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 18:41:20 -04:00
Zhe Fang
0389fa6a56 docs: Update FAQ section link format and heading in README.md 2025-07-30 18:41:19 -04:00
github-actions[bot]
30ca476d8d docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:36:00 +00:00
github-actions[bot]
6439dee5ef docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:48 +00:00
github-actions[bot]
f6e5a24fe4 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:35 +00:00
Zhe Fang
77aad546bc Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 18:35:24 -04:00
github-actions[bot]
7fdbe664ba docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:35:23 +00:00
Zhe Fang
df76074ce9 docs: Remove outdated FAQ section and integrate relevant content into README.md 2025-07-30 18:35:23 -04:00
github-actions[bot]
31939630a3 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:23 +00:00
github-actions[bot]
50500626f8 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:11 +00:00
github-actions[bot]
fb1f0c8fc7 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:14:00 +00:00
Zhe Fang
9036b3be5f docs: Rearranged language badge section in README.md for better visibility 2025-07-30 18:13:49 -04:00
github-actions[bot]
51f840c0ef docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 22:13:49 +00:00
github-actions[bot]
b3a98cdaa2 docs: Added README."ko".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:24 +00:00
github-actions[bot]
7799a8dd94 docs: Added README."ja".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:12 +00:00
github-actions[bot]
e84d1faf71 docs: Added README."zh-TW".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:58:02 +00:00
github-actions[bot]
66f0fd0f8f docs: Added README."zh-CN".md translation via https://github.com/dephraiim/translate-readme 2025-07-30 21:57:51 +00:00
Zhe Fang
4f0dbe4836 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-07-30 17:57:53 -04:00
Zhe Fang
d7a53e360a Update README.md to enhance language support and clarify features 2025-07-30 17:57:51 -04:00
Zhe Fang
cb76341666 Create readme-translator.yml 2025-07-30 17:29:00 -04:00
127 changed files with 333215 additions and 58264 deletions

18
.github/workflows/issues-translator.yml vendored Normal file
View File

@@ -0,0 +1,18 @@
name: 'issue-translator'
on:
issue_comment:
types: [created]
issues:
types: [opened]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: usthe/issues-translate-action@v2.7
with:
IS_MODIFY_TITLE: false
# not require, default false, . Decide whether to modify the issue title
# if true, the robot account @Issues-translate-bot must have modification permissions, invite @Issues-translate-bot to your project or use your custom bot.
CUSTOM_BOT_NOTE: Bot detected the issue body's language is not English, translate it automatically. 👯👭🏻🧑‍🤝‍🧑👫🧑🏿‍🤝‍🧑🏻👩🏾‍🤝‍👨🏿👬🏿
# not require. Customize the translation robot prefix message.

View File

@@ -12,6 +12,7 @@ jobs:
env:
TELEGRAM_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
THREAD_ID: ${{ secrets.TELEGRAM_THREAD_ID }}
RELEASE_URL: ${{ github.event.release.html_url }}
RELEASE_TAG: ${{ github.event.release.tag_name }}
RELEASE_NAME: ${{ github.event.release.name }}
@@ -23,5 +24,5 @@ jobs:
curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_TOKEN}/sendMessage" \
-d chat_id="${CHAT_ID}" \
-d text="${TEXT}" \
-d parse_mode=Markdown
-d message_thread_id="${THREAD_ID}" \
-d text="${TEXT}"

1
.gitignore vendored
View File

@@ -406,3 +406,4 @@ FodyWeavers.xsd
# JetBrains Rider
*.sln.iml
/BetterLyrics.WinUI3/BetterLyrics.WinUI3 (Package)/BetterLyrics.WinUI3 (Package)_TemporaryKey.pfx
/BetterLyrics.WinUI3/BetterLyrics.WinUI3/Constants/LastFM.cs

View File

@@ -1,149 +1,150 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250513003" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
<PropertyGroup Condition="'$(VisualStudioVersion)' == '' or '$(VisualStudioVersion)' &lt; '15.0'">
<VisualStudioVersion>15.0</VisualStudioVersion>
</PropertyGroup>
<ItemGroup Label="ProjectConfigurations">
<ProjectConfiguration Include="Debug|x86">
<Configuration>Debug</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x86">
<Configuration>Release</Configuration>
<Platform>x86</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|x64">
<Configuration>Debug</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|x64">
<Configuration>Release</Configuration>
<Platform>x64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Debug|ARM64">
<Configuration>Debug</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
<ProjectConfiguration Include="Release|ARM64">
<Configuration>Release</Configuration>
<Platform>ARM64</Platform>
</ProjectConfiguration>
</ItemGroup>
<PropertyGroup>
<WapProjPath Condition="'$(WapProjPath)'==''">$(MSBuildExtensionsPath)\Microsoft\DesktopBridge\</WapProjPath>
<PathToXAMLWinRTImplementations>BetterLyrics.WinUI3\</PathToXAMLWinRTImplementations>
</PropertyGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.props" />
<PropertyGroup>
<ProjectGuid>6576cd19-ef92-4099-b37d-e2d8ebdb6bf5</ProjectGuid>
<TargetPlatformVersion>10.0.26100.0</TargetPlatformVersion>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<AssetTargetFallback>net8.0-windows$(TargetPlatformVersion);$(AssetTargetFallback)</AssetTargetFallback>
<DefaultLanguage>zh-CN</DefaultLanguage>
<AppxPackageSigningEnabled>True</AppxPackageSigningEnabled>
<EntryPointProjectUniqueName>..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj</EntryPointProjectUniqueName>
<GenerateAppInstallerFile>False</GenerateAppInstallerFile>
<AppxPackageSigningTimestampDigestAlgorithm>SHA256</AppxPackageSigningTimestampDigestAlgorithm>
<AppxAutoIncrementPackageRevision>False</AppxAutoIncrementPackageRevision>
<GenerateTestArtifacts>True</GenerateTestArtifacts>
<AppxBundlePlatforms>x86|x64</AppxBundlePlatforms>
<GenerateTemporaryStoreCertificate>True</GenerateTemporaryStoreCertificate>
<HoursBetweenUpdateChecks>0</HoursBetweenUpdateChecks>
<PackageCertificateKeyFile>BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx</PackageCertificateKeyFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x86'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|ARM64'">
<AppxBundle>Always</AppxBundle>
<DefaultLanguage>en-US</DefaultLanguage>
</PropertyGroup>
<ItemGroup>
<AppxManifest Include="Package.appxmanifest">
<SubType>Designer</SubType>
</AppxManifest>
</ItemGroup>
<ItemGroup>
<None Include="BetterLyrics.WinUI3 %28Package%29_TemporaryKey.pfx" />
<Content Include="Images\LargeTile.scale-100.png" />
<Content Include="Images\LargeTile.scale-125.png" />
<Content Include="Images\LargeTile.scale-150.png" />
<Content Include="Images\LargeTile.scale-200.png" />
<Content Include="Images\LargeTile.scale-400.png" />
<Content Include="Images\SmallTile.scale-100.png" />
<Content Include="Images\SmallTile.scale-125.png" />
<Content Include="Images\SmallTile.scale-150.png" />
<Content Include="Images\SmallTile.scale-200.png" />
<Content Include="Images\SmallTile.scale-400.png" />
<Content Include="Images\SplashScreen.scale-100.png" />
<Content Include="Images\SplashScreen.scale-125.png" />
<Content Include="Images\SplashScreen.scale-150.png" />
<Content Include="Images\SplashScreen.scale-200.png" />
<Content Include="Images\LockScreenLogo.scale-200.png" />
<Content Include="Images\SplashScreen.scale-400.png" />
<Content Include="Images\Square150x150Logo.scale-100.png" />
<Content Include="Images\Square150x150Logo.scale-125.png" />
<Content Include="Images\Square150x150Logo.scale-150.png" />
<Content Include="Images\Square150x150Logo.scale-200.png" />
<Content Include="Images\Square150x150Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-24.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-lightunplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-16.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-256.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-32.png" />
<Content Include="Images\Square44x44Logo.altform-unplated_targetsize-48.png" />
<Content Include="Images\Square44x44Logo.scale-100.png" />
<Content Include="Images\Square44x44Logo.scale-125.png" />
<Content Include="Images\Square44x44Logo.scale-150.png" />
<Content Include="Images\Square44x44Logo.scale-200.png" />
<Content Include="Images\Square44x44Logo.scale-400.png" />
<Content Include="Images\Square44x44Logo.targetsize-16.png" />
<Content Include="Images\Square44x44Logo.targetsize-24.png" />
<Content Include="Images\Square44x44Logo.targetsize-24_altform-unplated.png" />
<Content Include="Images\Square44x44Logo.targetsize-256.png" />
<Content Include="Images\Square44x44Logo.targetsize-32.png" />
<Content Include="Images\Square44x44Logo.targetsize-48.png" />
<Content Include="Images\StoreLogo.scale-100.png" />
<Content Include="Images\StoreLogo.scale-125.png" />
<Content Include="Images\StoreLogo.scale-150.png" />
<Content Include="Images\StoreLogo.scale-200.png" />
<Content Include="Images\StoreLogo.scale-400.png" />
<Content Include="Images\Wide310x150Logo.scale-100.png" />
<Content Include="Images\Wide310x150Logo.scale-125.png" />
<Content Include="Images\Wide310x150Logo.scale-150.png" />
<Content Include="Images\Wide310x150Logo.scale-200.png" />
<Content Include="Images\Wide310x150Logo.scale-400.png" />
<None Include="Package.StoreAssociation.xml" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\BetterLyrics.WinUI3\BetterLyrics.WinUI3.csproj">
<EnableMsixTooling>true</EnableMsixTooling>
<SkipGetTargetFrameworkProperties>True</SkipGetTargetFrameworkProperties>
<PublishProfile>Properties\PublishProfiles\win-$(Platform).pubxml</PublishProfile>
</ProjectReference>
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4654" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
</ItemGroup>
<Import Project="$(WapProjPath)\Microsoft.DesktopBridge.targets" />
</Project>

View File

@@ -12,7 +12,7 @@
<Identity
Name="37412.BetterLyrics"
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
Version="1.0.32.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" />
@@ -81,7 +83,10 @@
<Setter Property="BorderThickness" Value="0" />
<Setter Property="Background" Value="Transparent" />
</Style>
<Style x:Key="TitleBarToggleButtonStyle" TargetType="ToggleButton">
<Style
x:Key="TitleBarToggleButtonStyle"
BasedOn="{StaticResource ToggleButtonRevealStyle}"
TargetType="ToggleButton">
<Setter Property="CornerRadius" Value="4" />
<Setter Property="VerticalAlignment" Value="Stretch" />
<Setter Property="BorderThickness" Value="0" />
@@ -302,4 +307,5 @@
<FontFamily x:Key="IconFontFamily">ms-appx:///Assets/Segoe Fluent Icons.ttf#Segoe Fluent Icons</FontFamily>
</ResourceDictionary>
</Application.Resources>
</Application>

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

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

View File

@@ -20,8 +20,8 @@
<PRIResource Remove="ViewModels\Lyrics\**" />
</ItemGroup>
<ItemGroup>
<None Remove="Assets\Core14.profile.xml" />
<None Remove="Assets\Segoe Fluent Icons.ttf" />
<None Remove="Assets\Wiki82.profile.xml" />
<None Remove="Controls\SystemTray.xaml" />
<None Remove="Views\MusicGalleryPage.xaml" />
<None Remove="Views\MusicGalleryWindow.xaml" />
@@ -35,6 +35,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="3v.EvtSource" Version="2.0.0" />
<PackageReference Include="ColorThief.ImageSharp" Version="1.0.0" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.MarqueeText" Version="0.1.230830" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.OpacityMaskView" Version="0.1.250703-build.2173" />
<PackageReference Include="CommunityToolkit.Labs.WinUI.Shimmer" Version="0.1.250703-build.2173" />
@@ -52,20 +53,20 @@
<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" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.3-dev-02320" />
<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.10" />
<PackageReference Include="System.Drawing.Common" Version="9.0.7" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.7" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.11" />
<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" />
@@ -77,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>
@@ -92,9 +98,84 @@
<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>
<Content Update="Assets\Wiki82.profile.xml">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
</ItemGroup>
<ItemGroup>
<Page Update="Views\MusicGalleryWindow.xaml">

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

@@ -26,6 +26,8 @@
ShowMode="TransientWithDismissOnPointerMoveAway">
<MenuFlyoutItem x:Uid="SystemTrayMusicGallery" Command="{x:Bind ViewModel.OpenMusicGalleryCommand}" />
<MenuFlyoutItem x:Uid="SystemTraySettings" Command="{x:Bind ViewModel.OpenSettingsCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayResetWindowPosition" Command="{x:Bind ViewModel.ResetWindowPositionCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayRestart" Command="{x:Bind ViewModel.RestartAppCommand}" />
<MenuFlyoutItem x:Uid="SystemTrayExit" Command="{x:Bind ViewModel.ExitAppCommand}" />
<MenuFlyoutItem
x:Uid="SystemTrayUnlock"

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

@@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Enums
{
public enum LyricsLayoutOrientation
{
Horizontal,
Vertical,
}
}

View File

@@ -8,9 +8,11 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events
{
public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtAccentColor) : 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? AlbumArtAccentColor { get; set; } = albumArtAccentColor;
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

@@ -11,9 +11,9 @@ namespace BetterLyrics.WinUI3.Helper
{
public static void SetIcons(this AppWindow appWindow)
{
appWindow.SetIcon(@"Assets/Logo.ico");
appWindow.SetTaskbarIcon(@"Assets/Logo.ico");
appWindow.SetTitleBarIcon(@"Assets/Logo.ico");
appWindow.SetIcon(PathHelper.LogoPath);
appWindow.SetTaskbarIcon(PathHelper.LogoPath);
appWindow.SetTitleBarIcon(PathHelper.LogoPath);
}
}
}

View File

@@ -106,25 +106,25 @@ namespace BetterLyrics.WinUI3.Helper
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, brightness);
}
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, WindowPixelSampleMode mode)
public static System.Drawing.Color GetAccentColor(IntPtr myHwnd, string monitorDeviceName, WindowPixelSampleMode mode)
{
if (!User32.GetWindowRect(myHwnd, out RECT myRect)) return System.Drawing.Color.Transparent;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
switch (mode)
{
case WindowPixelSampleMode.BelowWindow:
{
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Bottom + 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.AboveWindow:
{
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int sampleHeight = 1;
int sampleY = myRect.Top - 1;
return GetAverageColorFromScreenRegion(0, sampleY, screenWidth, sampleHeight);
return GetAverageColorFromScreenRegion(myRect.Left, sampleY, screenWidth, sampleHeight);
}
case WindowPixelSampleMode.WindowArea:
{

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;
@@ -73,17 +73,14 @@ namespace BetterLyrics.WinUI3.Helper
int targetX = _settingsService.DesktopWindowLeft;
int targetY = _settingsService.DesktopWindowTop;
if (targetWidth <= 0 || targetHeight <= 0 || targetX < 0 || targetY < 0)
{
targetWidth = 1200;
targetHeight = 600;
targetX = 200;
targetY = 200;
}
// <20><><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD><D0A1>λ<EFBFBD><CEBB>
window.AppWindow.MoveAndResize(
new Windows.Graphics.RectInt32(targetX, targetY, targetWidth, targetHeight)
new Windows.Graphics.RectInt32(
targetX,
targetY,
targetWidth,
targetHeight
)
);
// <20><><EFBFBD><EFBFBD>ԭTopMost״̬

View File

@@ -1,4 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Xaml;
using System;
@@ -16,6 +18,8 @@ namespace BetterLyrics.WinUI3.Helper
{
public static class DockModeHelper
{
private static readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
private static readonly HashSet<IntPtr> _registered = [];
private static readonly Dictionary<IntPtr, RECT> _originalPositions = [];
private static readonly Dictionary<IntPtr, WindowStyle> _originalWindowStyle = [];
@@ -52,10 +56,9 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void Enable(Window window, int appBarHeight, DockPlacement dockPlacement)
public static void Enable(Window window, string monitorDeviceName, int appBarHeight, DockPlacement dockPlacement)
{
window.SetIsShownInSwitchers(false);
//window.ExtendsContentIntoTitleBar = false;
window.SetIsAlwaysOnTop(true);
IntPtr hwnd = WindowNative.GetWindowHandle(window);
@@ -73,16 +76,18 @@ namespace BetterLyrics.WinUI3.Helper
}
}
RegisterAppBar(hwnd, appBarHeight, dockPlacement);
RegisterAppBar(hwnd, monitorDeviceName, appBarHeight, dockPlacement);
int screenWidth = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN);
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int y = dockPlacement == DockPlacement.Top ? 0 : screenHeight - appBarHeight;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(_settingsService.DockMonitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int screenHeight = monitorInfo.rcMonitor.Bottom - monitorInfo.rcMonitor.Top;
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - appBarHeight;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
appBarHeight,
@@ -93,14 +98,16 @@ namespace BetterLyrics.WinUI3.Helper
window.Show();
}
private static void RegisterAppBar(IntPtr hwnd, int height, DockPlacement dockPlacement)
private static void RegisterAppBar(IntPtr hwnd, string monitorDeviceName, int height, DockPlacement dockPlacement)
{
if (_registered.Contains(hwnd)) return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - height;
int bottom = dockPlacement == DockPlacement.Top ? height : screenHeight;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - height;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + height : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
@@ -109,14 +116,13 @@ namespace BetterLyrics.WinUI3.Helper
uEdge = uEdge,
rc = new RECT
{
Left = 0,
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
// Ref: https://github.com/TwilightLemon/AppBarTest/blob/master/AppBarCreator.cs
Shell32.SHAppBarMessage(Shell32.ABM.ABM_NEW, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_QUERYPOS, ref abd);
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
@@ -140,12 +146,7 @@ namespace BetterLyrics.WinUI3.Helper
_registered.Remove(hwnd);
}
private static void RefreshWorkArea()
{
User32.SendMessage(HWND.HWND_BROADCAST, User32.WindowMessage.WM_SETTINGCHANGE, IntPtr.Zero, IntPtr.Zero);
}
public static void UpdateAppBarHeight(IntPtr hwnd, int newHeight, DockPlacement dockPlacement)
public static void UpdateAppBarHeight(IntPtr hwnd, string monitorDeviceName, int newHeight, DockPlacement dockPlacement)
{
App.DispatcherQueueTimer?.Debounce(() =>
{
@@ -153,9 +154,12 @@ namespace BetterLyrics.WinUI3.Helper
return;
var uEdge = dockPlacement == DockPlacement.Top ? Shell32.ABE.ABE_TOP : Shell32.ABE.ABE_BOTTOM;
int screenHeight = User32.GetSystemMetrics(User32.SystemMetric.SM_CYSCREEN);
int top = dockPlacement == DockPlacement.Top ? 0 : screenHeight - newHeight;
int bottom = dockPlacement == DockPlacement.Top ? newHeight : screenHeight;
var monitorInfo = MonitorHelper.GetMonitorInfoExFromDeviceName(monitorDeviceName);
int screenWidth = monitorInfo.rcMonitor.Width;
int top = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int bottom = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top + newHeight : monitorInfo.rcMonitor.Bottom;
Shell32.APPBARDATA abd = new()
{
@@ -164,9 +168,9 @@ namespace BetterLyrics.WinUI3.Helper
uEdge = uEdge,
rc = new RECT
{
Left = 0,
Left = monitorInfo.rcMonitor.Left,
Top = top,
Right = User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
Right = monitorInfo.rcMonitor.Right,
Bottom = bottom,
},
};
@@ -175,16 +179,21 @@ namespace BetterLyrics.WinUI3.Helper
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
// 同步窗口实际高度和位置
int y = dockPlacement == DockPlacement.Top ? 0 : screenHeight - newHeight;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
0,
y,
User32.GetSystemMetrics(User32.SystemMetric.SM_CXSCREEN),
newHeight,
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
int y = dockPlacement == DockPlacement.Top ? monitorInfo.rcMonitor.Top : monitorInfo.rcMonitor.Bottom - newHeight;
int repeatCount = 2;
while (repeatCount > 0)
{
repeatCount--;
User32.SetWindowPos(
hwnd,
IntPtr.Zero,
monitorInfo.rcMonitor.Left,
y,
screenWidth,
newHeight,
newHeight == 0 ? User32.SetWindowPosFlags.SWP_HIDEWINDOW : User32.SetWindowPosFlags.SWP_SHOWWINDOW
);
}
}, TimeSpan.FromMilliseconds(100));
}
}

View File

@@ -107,6 +107,12 @@ namespace BetterLyrics.WinUI3.Helper
return t * t * (3f - 2f * t);
}
public static float CubicBezier(float t, float p0, float p1, float p2, float p3)
{
float u = 1 - t;
return u * u * u * p0 + 3 * u * u * t * p1 + 3 * u * t * t * p2 + t * t * t * p3;
}
public static float Linear(float t) => t;
}
}

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;
@@ -24,11 +25,9 @@ namespace BetterLyrics.WinUI3.Helper
{
public class ImageHelper
{
private const int _accentColorCount = 1;
public static async Task<InMemoryRandomAccessStream> ByteArrayToStream(byte[] bytes)
{
var stream = new InMemoryRandomAccessStream();
using var stream = new InMemoryRandomAccessStream();
await stream.WriteAsync(bytes.AsBuffer());
stream.Seek(0);
@@ -37,8 +36,8 @@ namespace BetterLyrics.WinUI3.Helper
public static RandomAccessStreamReference ByteArrayToRandomAccessStreamReference(byte[] bytes)
{
var stream = new InMemoryRandomAccessStream();
var writer = new DataWriter(stream);
using var stream = new InMemoryRandomAccessStream();
using var writer = new DataWriter(stream);
writer.WriteBytes(bytes);
writer.StoreAsync().GetAwaiter().GetResult();
writer.FlushAsync().GetAwaiter().GetResult();
@@ -48,8 +47,8 @@ namespace BetterLyrics.WinUI3.Helper
public static async Task<byte[]> CreateTextPlaceholderBytesAsync(int width, int height)
{
var device = CanvasDevice.GetSharedDevice();
var renderTarget = new CanvasRenderTarget(device, width, height, 96);
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 随机生成渐变色
Windows.UI.Color RandomColor()
@@ -58,7 +57,7 @@ namespace BetterLyrics.WinUI3.Helper
double h = rand.NextDouble() * 360;
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
return HslToColor(h, s, l);
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
}
Windows.UI.Color color1 = RandomColor();
@@ -67,86 +66,40 @@ namespace BetterLyrics.WinUI3.Helper
using (var ds = renderTarget.CreateDrawingSession())
{
// 绘制线性渐变背景
var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
{
StartPoint = new System.Numerics.Vector2(0, 0),
EndPoint = new System.Numerics.Vector2(width, height)
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
};
ds.FillRectangle(0, 0, width, height, gradientBrush);
}
// 保存为 PNG 并转为 byte[]
using (var stream = new InMemoryRandomAccessStream())
using var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
var buffer = new byte[stream.Size];
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
{
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
var buffer = new byte[stream.Size];
using (var reader = new DataReader(stream.GetInputStreamAt(0)))
{
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(buffer);
}
return buffer;
}
// HSL转Color
static Windows.UI.Color HslToColor(double h, double s, double l)
{
h = h / 360.0;
double r = l, g = l, b = l;
if (s != 0)
{
double q = l < 0.5 ? l * (1 + s) : l + s - l * s;
double p = 2 * l - q;
r = HueToRgb(p, q, h + 1.0 / 3.0);
g = HueToRgb(p, q, h);
b = HueToRgb(p, q, h - 1.0 / 3.0);
}
return Windows.UI.Color.FromArgb(255, (byte)(r * 255), (byte)(g * 255), (byte)(b * 255));
}
static double HueToRgb(double p, double q, double t)
{
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1.0 / 6.0) return p + (q - p) * 6 * t;
if (t < 1.0 / 2.0) return q;
if (t < 2.0 / 3.0) return p + (q - p) * (2.0 / 3.0 - t) * 6;
return p;
await reader.LoadAsync((uint)stream.Size);
reader.ReadBytes(buffer);
}
return buffer;
}
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes)
public static List<Windows.UI.Color> GetAccentColorsFromByte(byte[] bytes, int count, bool? isDark = null)
{
// 使用 ImageSharp 读取图片
using var image = SixLabors.ImageSharp.Image.Load<SixLabors.ImageSharp.PixelFormats.Rgba32>(bytes);
// 简单聚类法:统计所有像素出现频率,取出现最多的前 AccentColorCount 个颜色
var colorCount = new Dictionary<SixLabors.ImageSharp.PixelFormats.Rgba32, int>();
for (int y = 0; y < image.Height; y++)
{
for (int x = 0; x < image.Width; x++)
{
var color = image[x, y];
// 可选:忽略透明像素
if (color.A < 32) continue;
if (colorCount.ContainsKey(color))
colorCount[color]++;
else
colorCount[color] = 1;
}
}
// 按出现次数排序,取前 AccentColorCount 个
var topColors = colorCount
.OrderByDescending(kv => kv.Value)
.Take(_accentColorCount)
.Select(kv => kv.Key)
using var image = Image.Load<Rgba32>(bytes);
var colorThief = new ColorThief.ImageSharp.ColorThief();
var mainColor = colorThief.GetColor(image, 10, false);
var palette = colorThief.GetPalette(image, 255, 10, false);
var topColors = palette
.OrderByDescending(x => x.Population)
.Where(x => x.IsDark == (isDark ?? mainColor.IsDark))
.Select(x => Windows.UI.Color.FromArgb(x.Color.A, x.Color.R, x.Color.G, x.Color.B))
.Take(count)
.ToList();
// 转换为 Windows.UI.Color
return topColors
.Select(c => Windows.UI.Color.FromArgb(c.A, c.R, c.G, c.B))
.ToList();
return topColors;
}
//public static async Task<BitmapImage> GetBitmapImageFromBytesAsync(byte[] imageBytes)
@@ -214,39 +167,57 @@ namespace BetterLyrics.WinUI3.Helper
int size = Math.Max(image.Width, image.Height);
var themeColor = Rgba32.ParseHex(GetAccentColorsFromByte(imageBytes).FirstOrDefault().ToHex());
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(size / image.Width, size / image.Height);
if (factor <= 1)
using (Image image = Image.Load(imageBytes))
{
return imageBytes;
}
int width = image.Width * factor;
int height = image.Height * factor;
image.Mutate(x => x.Resize(width, height, KnownResamplers.Welch));
var factor = Math.Max((float)size / image.Width, (float)size / image.Height);
using var ms = new MemoryStream();
image.Save(ms, new PngEncoder());
return ms.ToArray();
int width = (int)(image.Width * factor);
int height = (int)(image.Height * factor);
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;
@@ -57,36 +58,20 @@ namespace BetterLyrics.WinUI3.Services
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
}
private static string? ThreeLetterToTwoLetter(string? threeLetterCode)
{
if (threeLetterCode == null) return null;
foreach (var ci in CultureInfo.GetCultures(CultureTypes.AllCultures))
{
if (string.Equals(ci.ThreeLetterISOLanguageName, threeLetterCode, StringComparison.OrdinalIgnoreCase))
{
return ci.TwoLetterISOLanguageName;
}
}
return null;
}
public static string? DetectLanguageCode(string? text)
{
if (text == null) return null;
string? code = ThreeLetterToTwoLetter(_identifier.Identify(text).FirstOrDefault()?.Item1.Iso639_2T);
if (code != null && code == "zh")
var guessList = _identifier.Identify(text);
string? code = guessList?.FirstOrDefault()?.Item1.Iso639_2T;
code = code switch
{
if (ChineseConverter.ConvertToTraditionalChinese(text) == text)
{
return "zh-Hant";
}
else
{
return "zh-Hans";
}
}
"simple" => "en",
"zh_classical" => "zh-Hant",
"zh_yue" => "zh-Hant",
"zh" => "zh-Hans",
_ => code
};
return code;
}

View File

@@ -1,19 +0,0 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class ListViewHelper
{
public static int FindChildIndex(this ListView listView, object frameworkElement)
{
var children = listView.ItemsPanelRoot.Children.Select(x => ((ListViewItem)x).ContentTemplateRoot).ToList();
return children.IndexOf((UIElement)frameworkElement);
}
}
}

View File

@@ -121,39 +121,45 @@ namespace BetterLyrics.WinUI3.Helper
for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组
foreach (var group in grouped)
if (grouped != null)
{
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
foreach (var group in grouped)
{
// 如果该语言有翻译,取对应行,否则用原文(第一行)
var (start, text, syllables) =
langIdx < linesInGroup.Count ? linesInGroup[langIdx] : linesInGroup[0];
var line = new LyricsLine
var linesInGroup = group.ToList();
for (int langIdx = 0; langIdx < languageCount; langIdx++)
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
// 只添加有对应行的语言,否则跳过
if (langIdx < linesInGroup.Count)
{
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
var (start, text, syllables) = linesInGroup[langIdx];
var line = new LyricsLine
{
StartMs = start,
OriginalText = text,
LyricsChars = [],
};
if (syllables != null && syllables.Count > 0)
{
int currentIndex = 0;
for (int j = 0; j < syllables.Count; j++)
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
var (charStart, charText) = syllables[j];
int startIndex = currentIndex;
line.LyricsChars.Add(
new LyricsChar
{
StartMs = charStart,
Text = charText ?? "",
StartIndex = startIndex,
}
);
currentIndex += charText?.Length ?? 0;
}
);
currentIndex += charText?.Length ?? 0;
}
_lyricsDataArr[langIdx].LyricsLines.Add(line);
}
// 没有翻译行则不补原文,直接跳过
}
_lyricsDataArr[langIdx].LyricsLines.Add(line);
}
}
}

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

@@ -0,0 +1,64 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3.Helper
{
public static class MonitorHelper
{
public static IEnumerable<string> GetAllMonitorDeviceNames()
{
var deviceNames = new List<string>();
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
{
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
{
deviceNames.Add(monitorInfoEx.szDevice);
}
return true; // 继续枚举
}, IntPtr.Zero);
return deviceNames;
}
public static User32.MONITORINFOEX GetMonitorInfoExFromDeviceName(string deviceName)
{
User32.MONITORINFOEX? result = null;
User32.EnumDisplayMonitors(IntPtr.Zero, null, (hMonitor, hdcMonitor, lprcMonitor, dwData) =>
{
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
if (User32.GetMonitorInfo(hMonitor, ref monitorInfoEx))
{
if (string.Equals(monitorInfoEx.szDevice, deviceName, StringComparison.OrdinalIgnoreCase))
{
result = monitorInfoEx;
return false; // 找到后停止枚举
}
}
return true; // 继续枚举
}, IntPtr.Zero);
return result ?? GetPrimaryMonitorInfoEx();
}
public static User32.MONITORINFOEX GetPrimaryMonitorInfoEx()
{
// (0,0) 总是在主屏
var ptZero = new POINT(0, 0);
HMONITOR hMonitor = User32.MonitorFromPoint(ptZero, User32.MonitorFlags.MONITOR_DEFAULTTOPRIMARY);
User32.MONITORINFOEX monitorInfoEx = new() { cbSize = (uint)Marshal.SizeOf<User32.MONITORINFOEX>() };
User32.GetMonitorInfo(hMonitor, ref monitorInfoEx);
return monitorInfoEx;
}
public static string GetPrimaryMonitorDeviceName()
{
var primaryMonitorInfo = GetPrimaryMonitorInfoEx();
return primaryMonitorInfo.szDevice;
}
}
}

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

@@ -0,0 +1,54 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class ObjectHelper
{
// Credit/Copyright to https://gist.github.com/tcartwright/dab50ebaff7c59f05013de0fb349cabd
public static bool IsDisposed(this IDisposable obj)
{
/*
TIM C: This hacky code is because MSFT does not provide a standard way to interrogate if an object is disposed or not.
I wrote this based upon streams, but it should work for many other types of MSFT objects (maybe).
*/
if (obj == null) { return true; }
var objType = obj.GetType();
//var foo = new System.IO.BufferedStream();
// the _disposed pattern should catch a lot of msft objects.... hopefully
var isDisposedField = objType.GetField("_disposed", BindingFlags.NonPublic | BindingFlags.Instance) ??
objType.GetField("disposed", BindingFlags.NonPublic | BindingFlags.Instance);
if (isDisposedField != null) { return Convert.ToBoolean(isDisposedField.GetValue(obj)); }
isDisposedField = objType.GetField("_isOpen", BindingFlags.NonPublic | BindingFlags.Instance);
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)
{
var strategy = strategyField.GetValue(obj);
var isClosedField = strategy.GetType().GetProperty("IsClosed", BindingFlags.NonPublic | BindingFlags.Instance);
if (isClosedField != null) { return Convert.ToBoolean(isClosedField.GetValue(strategy)); }
}
// other streams that use this pattern to determine if they are disposed
if (obj is Stream stream) { return !stream.CanRead && !stream.CanWrite; }
return false;
}
}
}

View File

@@ -15,7 +15,24 @@ namespace BetterLyrics.WinUI3.Helper
public static string CacheFolder => ApplicationData.Current.LocalCacheFolder.Path;
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
public static string LanguageProfilePath => Path.Combine(AssetsFolder, "Core14.profile.xml");
//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

@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.Helper
{
if (!_isTransitioning) return;
_progress += (float)elapsedTime.TotalSeconds / _durationSeconds;
_progress += (float)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
if (_progress >= 1f)
{
_progress = 1f;

View File

@@ -86,6 +86,16 @@ namespace BetterLyrics.WinUI3.Helper
}
}
public static void ExitApp()
{
LyricsWindow? lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (lyricsWindow != null)
{
DockModeHelper.Disable(lyricsWindow);
}
Environment.Exit(0);
}
private static void TrackWindow(object window)
{
if (!_activeWindows.Contains(window))

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,22 +47,22 @@ 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
{
// 没有匹配的翻译,翻译部分留空
line.DisplayedText = $"{line.OriginalText}\n";
line.DisplayedText = $"{line.OriginalText}";
}
}
}
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

@@ -1,24 +1,49 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Geometry;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using System.Collections.Generic;
using System.Numerics;
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas.Text;
using Windows.UI;
namespace BetterLyrics.WinUI3.Models
{
public class LyricsLine
{
private const float _animationDuration = 0.3f;
public ValueTransition<float> AngleTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> OpacityTransition { get; set; } = new(initialValue: 0f, durationSeconds: _animationDuration);
public ValueTransition<float> ScaleTransition { get; set; } = new(initialValue: 0.95f, durationSeconds: _animationDuration);
public ValueTransition<float> AngleTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> BlurAmountTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> HighlightOpacityTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> OpacityTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public ValueTransition<float> ScaleTransition { get; set; } = new(
initialValue: 0f,
durationSeconds: _animationDuration,
easingType: EasingType.EaseInOutSine
);
public CanvasTextLayout? CanvasTextLayout { get; set; }
public CanvasTextLayout? CanvasTextLayout { get; private set; }
public Vector2 CenterPosition { get; set; }
public Vector2 CenterPosition { get; private set; }
public Vector2 Position { get; set; }
public List<LyricsChar> LyricsChars { get; set; } = [];
@@ -29,5 +54,78 @@ namespace BetterLyrics.WinUI3.Models
public string DisplayedText { get; set; } = "";
public string OriginalText { get; set; } = "";
public CanvasGeometry? TextGeometry { get; private set; }
public CanvasCommandList? BackgroundFontEffect { get; private set; }
public CanvasCommandList? ForegroundFontEffect { get; private set; }
public void UpdateCenterPosition(float maxWidth, TextAlignmentType type)
{
if (CanvasTextLayout == null)
{
return;
}
float centerY = Position.Y + (float)CanvasTextLayout.LayoutBounds.Height;
CenterPosition = type switch
{
TextAlignmentType.Left => new Vector2(Position.X, centerY),
TextAlignmentType.Center => new Vector2(Position.X + maxWidth / 2, centerY),
TextAlignmentType.Right => new Vector2(Position.X + maxWidth, centerY),
_ => throw new System.ArgumentOutOfRangeException(nameof(type), type, null),
};
}
public void UpdateTextLayout(ICanvasAnimatedControl control, CanvasTextFormat textFormat, float maxWidth, float maxHeight, TextAlignmentType type)
{
CanvasTextLayout?.Dispose();
CanvasTextLayout = null;
CanvasTextLayout = new CanvasTextLayout(control, DisplayedText, textFormat, maxWidth, maxHeight);
CanvasTextLayout.HorizontalAlignment = type.ToCanvasHorizontalAlignment();
}
public void DisposeTextGeometry()
{
TextGeometry?.Dispose();
TextGeometry = null;
}
public void UpdateTextGeometry()
{
DisposeTextGeometry();
if (CanvasTextLayout == null)
{
return;
}
TextGeometry = CanvasGeometry.CreateText(CanvasTextLayout);
}
public void DisposeFontEffects()
{
BackgroundFontEffect?.Dispose();
BackgroundFontEffect = null;
ForegroundFontEffect?.Dispose();
ForegroundFontEffect = null;
}
public void UpdateFontEffect(ICanvasAnimatedControl control, bool drawStroke, Color strokeColor, int strokeWidth, Color fontColor)
{
DisposeFontEffects();
if (TextGeometry == null)
{
return;
}
BackgroundFontEffect = new CanvasCommandList(control);
using var bgFontEffectDs = BackgroundFontEffect.CreateDrawingSession();
ForegroundFontEffect = new CanvasCommandList(control);
using var fgFontEffectDs = ForegroundFontEffect.CreateDrawingSession();
if (drawStroke)
{
bgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
fgFontEffectDs.DrawGeometry(TextGeometry, Position, strokeColor, strokeWidth); // 描边
}
bgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
fgFontEffectDs.FillGeometry(TextGeometry, Position, fontColor); // 填充
}
}
}

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,10 +103,10 @@ 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
HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
using HttpResponseMessage response = await _iTunesHttpClinet.GetAsync(url);
response.EnsureSuccessStatusCode();
string responseBody = await response.Content.ReadAsStringAsync();

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,10 +268,10 @@ 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
{
var response = await _amllTtmlDbHttpClient.GetAsync(url);
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;
return await response.Content.ReadAsStringAsync();
@@ -292,7 +292,7 @@ namespace BetterLyrics.WinUI3.Services
$"&album_name={Uri.EscapeDataString(album)}" +
$"&durationMs={Uri.EscapeDataString(duration.ToString())}";
var response = await _lrcLibHttpClient.GetAsync(url);
using var response = await _lrcLibHttpClient.GetAsync(url);
if (!response.IsSuccessStatusCode)
return null;

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,16 +1,21 @@
// 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.PixelFormats;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
@@ -23,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;
@@ -52,6 +55,7 @@ namespace BetterLyrics.WinUI3.Services
private SongInfo? _cachedSongInfo;
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
private byte[]? _SMTCAlbumArtBytes = null;
private int _targetAlbumArtSize = 400;
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
@@ -59,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();
@@ -70,6 +74,7 @@ namespace BetterLyrics.WinUI3.Services
public bool IsPlaying => _cachedIsPlaying;
public SongInfo? SongInfo => _cachedSongInfo;
public TimeSpan Position => _cachedPosition;
private bool IsMediaSourceEnabled(string id)
{
@@ -106,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)
@@ -122,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, () =>
{
@@ -146,56 +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);
_SMTCAlbumArtBytes = ImageHelper.Resize(_SMTCAlbumArtBytes, 800);
}
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)
@@ -229,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, () =>
{
@@ -256,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)
@@ -279,10 +337,11 @@ namespace BetterLyrics.WinUI3.Services
if (bytes == null)
{
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(400, 400);
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(_targetAlbumArtSize, _targetAlbumArtSize);
token.ThrowIfCancellationRequested();
}
bytes = ImageHelper.Resize(bytes, _targetAlbumArtSize);
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
using var stream = new InMemoryRandomAccessStream();
@@ -292,14 +351,15 @@ namespace BetterLyrics.WinUI3.Services
var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested();
var _albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested();
var _albumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(_albumArtSwBitmap, _albumArtAccentColor));
AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
});
}
@@ -307,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;
}
@@ -344,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)
@@ -417,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; }
@@ -106,5 +103,11 @@ namespace BetterLyrics.WinUI3.Services
bool IsDragEverywhereEnabled { get; set; }
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";
@@ -113,6 +110,13 @@ namespace BetterLyrics.WinUI3.Services
private const string LyricsFontFamilyKey = "LyricsFontFamily";
private const string IsDragEverywhereEnabledKey = "IsDragEverywhereEnabled";
private const string DockMonitorDeviceNameKey = "DockMonitorDeviceName";
// LastFM
private const string LastFMSessionKeyKey = "LastFMSessionKey";
private const string LyricsTranslationSeparatorKey = "LyricsTranslationSeparator";
private readonly ApplicationDataContainer _localSettings;
public SettingsService()
@@ -121,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(
@@ -167,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);
@@ -193,12 +192,12 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(CoverImageRadiusKey, 12); // 12 %
SetDefault(CoverAcrylicEffectAmountKey, 0);
// Lyrics
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Center);
SetDefault(LyricsAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(SongInfoAlignmentTypeKey, (int)TextAlignmentType.Left);
SetDefault(LyricsFontWeightKey, (int)LyricsFontWeight.Bold);
SetDefault(LyricsBlurAmountKey, 5);
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Default);
SetDefault(LyricsBackgroundThemeKey, (int)ElementTheme.Dark);
SetDefault(LyricsBgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
SetDefault(LyricsFgFontColorTypeKey, (int)LyricsFontColorType.AdaptiveGrayed);
@@ -208,8 +207,8 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(LyricsCustomFgFontColorKey, Colors.White.ToInt());
SetDefault(LyricsCustomStrokeFontColorKey, Colors.White.ToInt());
SetDefault(LyricsStandardFontSizeKey, 28);
SetDefault(LyricsDockFontSizeKey, 20);
SetDefault(LyricsStandardFontSizeKey, 32);
SetDefault(LyricsDockFontSizeKey, 16);
SetDefault(LyricsDesktopFontSizeKey, 28);
SetDefault(LyricsLineSpacingFactorKey, 0.5f);
@@ -231,13 +230,11 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(IgnoreFullscreenWindowKey, false);
SetDefault(PreferredDisplayTypeKey, (int)LyricsDisplayType.SplitView);
SetDefault(LyricsScrollEasingTypeKey, (int)EasingType.EaseInOutQuad);
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);
@@ -247,6 +244,11 @@ namespace BetterLyrics.WinUI3.Services
SetDefault(SelectedFontFamilyIndexKey, 0);
SetDefault(LyricsFontFamilyKey, FontHelper.SystemFontFamilies.ElementAtOrDefault(0));
SetDefault(IsDragEverywhereEnabledKey, false);
SetDefault(DockMonitorDeviceNameKey, MonitorHelper.GetPrimaryMonitorDeviceName());
SetDefault(LastFMSessionKeyKey, "");
SetDefault(LyricsTranslationSeparatorKey, StringHelper.NewLine);
}
public bool IsDragEverywhereEnabled
@@ -445,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
@@ -560,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 =>
@@ -653,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);
@@ -689,6 +662,28 @@ namespace BetterLyrics.WinUI3.Services
set => SetValue(IsImmersiveModeKey, value);
}
public string DockMonitorDeviceName
{
get => GetValue<string>(DockMonitorDeviceNameKey)!;
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">
@@ -907,4 +901,79 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<value>Translation provider</value>
</data>
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
<value>Target monitor</value>
</data>
<data name="SystemTrayRestart.Text" xml:space="preserve">
<value>Resart</value>
</data>
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>Reset window position</value>
</data>
<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">
@@ -907,4 +901,79 @@
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<value>翻訳プロバイダー</value>
</data>
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
<value>ターゲットモニター</value>
</data>
<data name="SystemTrayRestart.Text" xml:space="preserve">
<value>再アート</value>
</data>
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>ウィンドウの位置をリセットします</value>
</data>
<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">
@@ -907,4 +901,79 @@
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<value>번역 제공자</value>
</data>
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
<value>대상 모니터</value>
</data>
<data name="SystemTrayRestart.Text" xml:space="preserve">
<value>resart</value>
</data>
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>창 위치를 재설정합니다</value>
</data>
<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">
@@ -907,4 +901,79 @@
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<value>翻译来源</value>
</data>
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
<value>目标显示器</value>
</data>
<data name="SystemTrayRestart.Text" xml:space="preserve">
<value>重新启动</value>
</data>
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>重置窗口位置</value>
</data>
<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

@@ -118,7 +118,7 @@
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
<value>本地媒體庫</value>
<value>本地音樂資料庫</value>
</data>
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
<value>新增存放音樂或歌詞的資料夾</value>
@@ -127,7 +127,7 @@
<value>在檔案總管中開啟</value>
</data>
<data name="SettingsPageRemovePath.Content" xml:space="preserve">
<value>從應用程中移除</value>
<value>從應用程中移除</value>
</data>
<data name="SettingsPageRemoveInfo.Title" xml:space="preserve">
<value>您可以安全刪除以下項目</value>
@@ -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>
@@ -563,16 +560,13 @@
<value>歌曲標題和藝術家</value>
</data>
<data name="SettingsPageEasingFuncType.Header" xml:space="preserve">
<value>缓动动画类型</value>
<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>
@@ -581,7 +575,7 @@
<value>歌詞翻譯</value>
</data>
<data name="MainPagePositionOffsetSlider.Header" xml:space="preserve">
<value>歌詞時間偏移(毫秒)</value>
<value>歌詞時間偏移(毫秒)</value>
</data>
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
<value>LibreTranslate 翻譯服務</value>
@@ -596,7 +590,7 @@
<value>目標語言</value>
</data>
<data name="SettingsPageTranslationInfo.Header" xml:space="preserve">
<value>翻译服务由 LibreTranslate 驱动</value>
<value>翻譯服務由 LibreTranslate 驅動</value>
</data>
<data name="SettingsPageTranslationInfoLink.Text" xml:space="preserve">
<value>造訪 https://github.com/LibreTranslate/LibreTranslate 以取得安裝教學及更多資訊(本軟體與此翻譯服務無任何關聯)</value>
@@ -611,13 +605,13 @@
<value>歌詞描邊寬度(僅桌面模式)</value>
</data>
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
<value>跟随系统</value>
<value>跟隨系統</value>
</data>
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
<value>自動啟動</value>
</data>
<data name="SettingsPageLyricsStrokeFontColor.Header" xml:space="preserve">
<value>歌词描边颜色(桌面模式)</value>
<value>歌詞描邊顏色(桌面模式)</value>
</data>
<data name="SettingsPageIgnoreFullscreenWindow.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">
@@ -716,7 +710,7 @@
<value>高亮顯示範圍</value>
</data>
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
<value>歌詞時間同步閾值</value>
<value>歌詞時間同步閾值</value>
</data>
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
<value>當歌詞進度抖動時,請嘗試增加該閾值;更改此值會導致歌詞同步偏差</value>
@@ -907,4 +901,79 @@
<data name="LyricsPageTranslationProviderPrefix.Text" xml:space="preserve">
<value>翻譯來源</value>
</data>
<data name="SettingsPageDockMonitor.Header" xml:space="preserve">
<value>目標顯示器</value>
</data>
<data name="SystemTrayRestart.Text" xml:space="preserve">
<value>重新啟動</value>
</data>
<data name="SystemTrayResetWindowPosition.Text" xml:space="preserve">
<value>重置窗口位置</value>
</data>
<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,23 +1,23 @@
// 2025/6/23 by Zhe Fang
using System;
using BetterLyrics.WinUI3.Services;
using BetterLyrics.WinUI3.Services.SettingsService;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Dispatching;
using System;
using System.Runtime.InteropServices;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class BaseViewModel : ObservableRecipient
{
private protected readonly DispatcherQueue _dispatcherQueue;
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
private protected readonly ISettingsService _settingsService;
public BaseViewModel(ISettingsService settingsService)
{
IsActive = true;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
_settingsService = settingsService;

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,20 +28,26 @@ 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));
public LyricsPageViewModel(ISettingsService settingsService, IPlaybackService playbackService) : base(settingsService)
private bool _isDockMode = false;
private bool _isDesktopMode = false;
private int _lyricsStandardFontSize = 8;
private int _lyricsDockFontSize = 8;
private int _lyricsDesktopFontSize = 8;
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;
LyricsStandardFontSize = _settingsService.LyricsStandardFontSize;
UpdateHintMessageFontSize();
LyricsFontFamily = _settingsService.LyricsFontFamily;
@@ -49,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)
@@ -76,10 +83,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
SongInfo = e.SongInfo;
SongDurationSeconds = SongInfo?.Duration ?? 0;
if (ResetPositionOffsetOnSongChanged)
{
PositionOffset = 0;
}
}
[ObservableProperty]
@@ -95,7 +98,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial string LyricsFontFamily { get; set; }
[ObservableProperty]
public partial int LyricsStandardFontSize { get; set; }
public partial int HintMessageFontSize { get; set; }
[ObservableProperty]
public partial bool IsImmersiveMode { get; set; }
@@ -131,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; }
@@ -144,12 +143,29 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
private void UpdateHintMessageFontSize()
{
if (_isDockMode)
{
HintMessageFontSize = _settingsService.LyricsDockFontSize;
}
else if (_isDesktopMode)
{
HintMessageFontSize = _settingsService.LyricsDesktopFontSize;
}
else
{
HintMessageFontSize = _settingsService.LyricsStandardFontSize;
}
}
public void Receive(PropertyChangedMessage<bool> message)
{
if (message.Sender is LyricsWindowViewModel)
{
if (message.PropertyName == nameof(LyricsWindowViewModel.IsDockMode))
{
_isDockMode = message.NewValue;
if (message.NewValue)
{
DisplayType = LyricsDisplayType.LyricsOnly;
@@ -158,9 +174,11 @@ namespace BetterLyrics.WinUI3.ViewModels
{
DisplayType = _settingsService.DisplayType;
}
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsDesktopMode))
{
_isDesktopMode = message.NewValue;
if (message.NewValue)
{
DisplayType = LyricsDisplayType.LyricsOnly;
@@ -169,6 +187,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
DisplayType = _settingsService.DisplayType;
}
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(LyricsWindowViewModel.IsImmersiveMode))
{
@@ -186,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)
@@ -244,20 +263,28 @@ 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))
{
LyricsStandardFontSize = message.NewValue;
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDockFontSize))
{
UpdateHintMessageFontSize();
}
else if (message.PropertyName == nameof(SettingsPageViewModel.SettingsPageViewModel.LyricsDesktopFontSize))
{
UpdateHintMessageFontSize();
}
}
}
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;
}
@@ -271,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())
{
@@ -288,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;
}
@@ -299,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;
@@ -49,14 +66,23 @@ namespace BetterLyrics.WinUI3.ViewModels
_lyricsBgTheme = _settingsService.LyricsBackgroundTheme;
_isFanLyricsEnabled = _settingsService.IsFanLyricsEnabled;
// 歌词描边
_lyricsFontStrokeWidth = _settingsService.LyricsFontStrokeWidth;
_lyricsStrokeFontColorType = _settingsService.LyricsStrokeFontColorType;
_customStrokeFontColor = _settingsService.LyricsCustomStrokeFontColor;
_isTranslationEnabled = _settingsService.IsTranslationEnabled;
_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);
@@ -68,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
{
@@ -30,28 +30,20 @@ namespace BetterLyrics.WinUI3.ViewModels
DrawBlurredLyrics(control, blurredLyricsDs);
}
if (_lastAlbumArtSwBitmap != null && _lastAlbumArtCanvasBitmap == null)
{
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
}
if (_albumArtSwBitmap != null && _albumArtCanvasBitmap == null)
{
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
}
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (_isDockMode)
{
DrawImmersiveBackground(control, combinedDs, 0f);
FillBackground(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value * _albumArtBgOpacity / 100f);
}
else if (_isDesktopMode)
{
DrawImmersiveBackground(control, combinedDs, 0f);
FillBackground(control, combinedDs, _immersiveBgColorTransition.Value, 0f, _immersiveBgOpacityTransition.Value * _albumArtBgOpacity / 100f);
}
else
{
FillBackground(control, combinedDs, _albumArtAccentColorTransition.Value, 0f, _albumArtBgOpacity / 100f);
DrawAlbumArtBackground(control, combinedDs);
}
@@ -64,6 +56,8 @@ namespace BetterLyrics.WinUI3.ViewModels
if (_isDebugOverlayEnabled)
{
_drawFrameCount++;
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
@@ -79,165 +73,76 @@ namespace BetterLyrics.WinUI3.ViewModels
ds.DrawText(
$"[DEBUG]\n" +
$"Cur playing {_playingLineIndex}, char start idx {charStartIndex}, length {charLength}, prog {charProgress}\n" +
$"Visible lines [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"Cur time {TotalTime + _positionOffset}\n" +
$"Lang size {_lyricsDataArr.Count}\n" +
$"Song duration {TimeSpan.FromMilliseconds(SongInfo?.DurationMs ?? 0)}",
new Vector2(10, 10),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White
$"Canvas size: {_canvasWidth}x{_canvasHeight}\n" +
$"FPS (Draw): {_displayedDrawFrameCount}\n" +
$"Playing line: {_playingLineIndex}\n" +
$"Syllable start idx: {charStartIndex}\n" +
$"Syllable len: {charLength}\n" +
$"Syllable prog: {charProgress}\n" +
$"Visible lines: [{_startVisibleLineIndex}, {_endVisibleLineIndex}]\n" +
$"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" +
$"Y offset: {_canvasYScrollTransition.Value}",
new Vector2(10, 40),
ThemeTypeSent == Microsoft.UI.Xaml.ElementTheme.Light ? Colors.Black : Colors.White,
_debugTextFormat
);
}
//for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
//{
// LyricsLine? line = _multiLangLyrics.SafeGet(_langIndex)?.SafeGet(i);
// if (line != null)
// {
// ds.DrawText(
// $"[{i}] {line.OriginalText} {line.HighlightOpacityTransition.Value}",
// new Vector2(10, 30 + (i - _startVisibleLineIndex) * 20),
// ThemeTypeSent == ElementTheme.Light ? Colors.Black : Colors.White
// );
// }
//}
if (_drawFrameStopwatch?.Elapsed.TotalSeconds >= 1.0)
{
_displayedDrawFrameCount = _drawFrameCount;
_drawFrameStopwatch?.Restart();
_drawFrameCount = 0;
}
}
}
private void DrawBackgroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
private void DrawBackgroundImgae(OpacityEffect effect, CanvasDrawingSession ds, CanvasBitmap canvasBitmap)
{
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2)) * 1.4f;
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2));
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
float x = _canvasWidth / 2 - imageWidth * scaleFactor / 2;
float y = _canvasHeight / 2 - imageHeight * scaleFactor / 2;
// Original source: https://zhuanlan.zhihu.com/p/37178216
float gain = _lyricsBgBrightnessTransition.Value;
float whiteX = 1 - 0.5f * gain;
float whiteY = 0.5f + 0.5f * gain;
float blackX = 0.5f - 0.5f * gain;
float blackY = 0 + 0.5f * gain;
ds.DrawImage(new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
Opacity = opacity,
}, new Vector2(x, y));
ds.DrawImage(effect, new Vector2(x, y));
}
private void DrawForegroundImgae(ICanvasAnimatedControl control, CanvasDrawingSession ds, CanvasBitmap canvasBitmap, float opacity)
private void DrawForegroundImgae(OpacityEffect effect, CanvasDrawingSession ds)
{
if (opacity == 0) return;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
using var cornerRadiusMask = new CanvasCommandList(control.Device);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
cornerRadius, cornerRadius, Colors.White
);
ds.DrawImage(new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = opacity,
}, new Vector2(_albumArtXTransition.Value, _albumArtY)
);
ds.DrawImage(effect, new Vector2(_albumArtXTransition.Value, _albumArtYTransition.Value));
}
private void DrawAlbumArtBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
//ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
using var overlappedCovers = new CanvasCommandList(control.Device);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
overlappedCoversDs.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
if (_lastAlbumArtCanvasBitmap != null)
if (_albumArtBgEffect == null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
if (_albumArtCanvasBitmap != null)
{
DrawBackgroundImgae(control, overlappedCoversDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
return;
}
overlappedCoversDs.Transform = Matrix3x2.Identity;
IGraphicsEffectSource blurredCover = new GaussianBlurEffect
{
BlurAmount = _albumArtBgBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
};
// 应用亚克力噪点效果
// TODO: 没有写_coverAcrylicNoiseCanvasBitmap加载的代码
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
{
blurredCover = new BlendEffect
{
Mode = BlendEffectMode.SoftLight,
Background = blurredCover,
Foreground = new OpacityEffect
{
Source = _coverAcrylicNoiseCanvasBitmap,
Opacity = _coverAcrylicEffectAmount / 100f,
},
};
}
var coverOverlayEffect = new OpacityEffect
{
Opacity = _albumArtBgOpacity / 100f,
Source = blurredCover,
};
ds.DrawImage(coverOverlayEffect);
//ds.Transform = Matrix3x2.Identity;
ds.Transform = Matrix3x2.CreateRotation(_rotateAngle, control.Size.ToVector2() * 0.5f);
ds.DrawImage(_albumArtBgEffect);
ds.Transform = Matrix3x2.Identity;
}
private void DrawAlbumArt(ICanvasAnimatedControl control, CanvasDrawingSession ds)
{
using var albumArt = new CanvasCommandList(control.Device);
using var albumArtDs = albumArt.CreateDrawingSession();
if (_albumArtCanvasBitmap != null)
if (_lastFgImageEffect != null && !_lastFgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
DrawForegroundImgae(_lastFgImageEffect, albumArtDs);
}
if (_lastAlbumArtCanvasBitmap != null)
if (_fgImageEffect != null && !_fgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
{
DrawForegroundImgae(control, albumArtDs, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
DrawForegroundImgae(_fgImageEffect, albumArtDs);
}
using var opacity = new CanvasCommandList(control.Device);
@@ -246,7 +151,7 @@ namespace BetterLyrics.WinUI3.ViewModels
{
Source = albumArt,
BlurAmount = 12f,
Optimization = EffectOptimization.Quality,
Optimization = EffectOptimization.Speed,
});
opacityDs.DrawImage(albumArt);
@@ -271,21 +176,32 @@ namespace BetterLyrics.WinUI3.ViewModels
private void DrawSingleTitleAndArtist(ICanvasAnimatedControl control, CanvasDrawingSession ds, string? title, string? artist, float opacity)
{
CanvasTextLayout titleLayout = new(
var maxWidth = _lyricsLayoutOrientation switch
{
LyricsLayoutOrientation.Horizontal => _albumArtSize,
LyricsLayoutOrientation.Vertical => _canvasWidth - _leftMargin - _albumArtSize - _rightMargin,
_ => 0f
};
if (maxWidth <= 0)
{
return;
}
using CanvasTextLayout titleLayout = new(
control, title ?? string.Empty,
_titleTextFormat, _albumArtSize, _canvasHeight
_titleTextFormat, maxWidth, _canvasHeight
);
CanvasTextLayout artistLayout = new(
using CanvasTextLayout artistLayout = new(
control, artist ?? string.Empty,
_artistTextFormat, _albumArtSize, _canvasHeight
_artistTextFormat, maxWidth, _canvasHeight
);
ds.DrawTextLayout(
titleLayout,
new Vector2(_albumArtXTransition.Value, _titleY),
new Vector2(_titleXTransition.Value, _titleYTransition.Value),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 255 * opacity)));
ds.DrawTextLayout(
artistLayout,
new Vector2(_albumArtXTransition.Value, _titleY + (float)titleLayout.LayoutBounds.Height),
new Vector2(_titleXTransition.Value, _titleYTransition.Value + (float)titleLayout.LayoutBounds.Height),
_bgFontColor.WithAlpha((byte)(_albumArtOpacityTransition.Value * 128 * opacity)));
}
@@ -308,60 +224,20 @@ namespace BetterLyrics.WinUI3.ViewModels
var textLayout = line.CanvasTextLayout;
if (textLayout == null) continue;
var position = new Vector2(line.Position.X, line.Position.Y);
float layoutWidth = (float)textLayout.LayoutBounds.Width;
float layoutHeight = (float)textLayout.LayoutBounds.Height;
if (layoutWidth <= 0 || layoutHeight <= 0) continue;
float centerX = position.X;
float centerY = position.Y + layoutHeight / 2;
switch (_lyricsAlignmentType)
{
case TextAlignmentType.Left:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Left;
break;
case TextAlignmentType.Center:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Center;
centerX += _maxLyricsWidth / 2;
break;
case TextAlignmentType.Right:
textLayout.HorizontalAlignment = CanvasHorizontalAlignment.Right;
centerX += _maxLyricsWidth;
break;
default:
break;
}
float xOffset = _lyricsXTransition.Value;
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2;
float yOffset = _canvasYScrollTransition.Value + _canvasHeight / 2 + _lyricsYTransition.Value;
// 组合变换:缩放 -> 旋转 -> 平移
ds.Transform =
Matrix3x2.CreateScale(line.ScaleTransition.Value, new Vector2(centerX, centerY))
Matrix3x2.CreateScale(line.ScaleTransition.Value, line.CenterPosition)
* Matrix3x2.CreateRotation(line.AngleTransition.Value, currentPlayingLine.Position)
* Matrix3x2.CreateTranslation(xOffset, yOffset);
* Matrix3x2.CreateTranslation(_lyricsXTransition.Value, yOffset);
// Create the background lyrics line with stroke and fill
using var bgLyrics = new CanvasCommandList(control.Device);
using var bgLyricsDs = bgLyrics.CreateDrawingSession();
// Create the foreground lyrics line with stroke and fill
using var fgLyrics = new CanvasCommandList(control.Device);
using var fgLyricsDs = fgLyrics.CreateDrawingSession();
// 创建文字几何体
using var textGeometry = CanvasGeometry.CreateText(textLayout);
if (_isDesktopMode)
{
bgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 背景描边
fgLyricsDs.DrawGeometry(textGeometry, position, _strokeFontColor, _lyricsFontStrokeWidth); // 前景描边
}
bgLyricsDs.FillGeometry(textGeometry, position, _bgFontColor); // 背景填充
fgLyricsDs.FillGeometry(textGeometry, position, _fgFontColor); // 前景填充
if (line.BackgroundFontEffect == null || line.ForegroundFontEffect == null) continue;
using var combined = new CanvasCommandList(control.Device);
using var combinedDs = combined.CreateDrawingSession();
@@ -370,12 +246,16 @@ namespace BetterLyrics.WinUI3.ViewModels
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
// Current line will not be blurred
combinedDs.DrawImage(
new GaussianBlurEffect
new OpacityEffect
{
Source = new OpacityEffect { Source = bgLyrics, Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value },
BlurAmount = line.BlurAmountTransition.Value,
Optimization = EffectOptimization.Quality,
BorderMode = EffectBorderMode.Soft,
Source = new GaussianBlurEffect
{
Source = line.BackgroundFontEffect,
BlurAmount = line.BlurAmountTransition.Value,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
},
Opacity = line.OpacityTransition.Value * _lyricsOpacityTransition.Value,
}
);
@@ -408,7 +288,7 @@ namespace BetterLyrics.WinUI3.ViewModels
var region = regions[j];
var rect = new Rect(
region.LayoutBounds.X,
region.LayoutBounds.Y + position.Y,
region.LayoutBounds.Y + line.Position.Y,
region.LayoutBounds.Width,
region.LayoutBounds.Height
);
@@ -425,32 +305,32 @@ namespace BetterLyrics.WinUI3.ViewModels
// Rects
var highlightRect = new Rect(
highlightRegion.LayoutBounds.X,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
highlightWidth,
highlightRegion.LayoutBounds.Height
);
var fadeInRect = new Rect(
highlightRect.Right - fadingWidth,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
var fadeOutRect = new Rect(
highlightRect.Right,
highlightRegion.LayoutBounds.Y + position.Y,
highlightRegion.LayoutBounds.Y + line.Position.Y,
fadingWidth,
highlightRegion.LayoutBounds.Height
);
// 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,
@@ -465,23 +345,34 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
float height = 0f;
//var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
//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,
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
@@ -493,27 +384,27 @@ namespace BetterLyrics.WinUI3.ViewModels
{
Source = new AlphaMaskEffect
{
Source = fgLyrics,
Source = line.ForegroundFontEffect,
AlphaMask = _lyricsGlowEffectScope switch
{
LineRenderingType.CurrentChar => highlightMask,
LineRenderingType.LineStartToCurrentChar => mask,
LineRenderingType.CurrentLine => fgLyrics,
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
_ => mask,
},
},
BlurAmount = _lyricsGlowEffectAmount,
Optimization = EffectOptimization.Quality,
Optimization = EffectOptimization.Speed,
}
: new CanvasCommandList(control.Device),
Foreground = new AlphaMaskEffect
{
Source = fgLyrics,
Source = line.ForegroundFontEffect,
AlphaMask = _lyricsHighlightScope switch
{
LineRenderingType.CurrentChar => highlightMask,
LineRenderingType.LineStartToCurrentChar => mask,
LineRenderingType.CurrentLine => fgLyrics,
LineRenderingType.CurrentLine => line.ForegroundFontEffect,
_ => mask,
},
},
@@ -533,7 +424,7 @@ namespace BetterLyrics.WinUI3.ViewModels
Displacement = mask,
XChannelSelect = EffectChannelSelect.Red,
YChannelSelect = EffectChannelSelect.Alpha,
Amount = 1f
Amount = 1f,
});
}
else
@@ -553,47 +444,66 @@ namespace BetterLyrics.WinUI3.ViewModels
// Reset scale
ds.Transform = Matrix3x2.Identity;
line.DisposeFontEffects();
line.DisposeTextGeometry();
}
}
private void DrawImmersiveBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, float radius)
private void FillBackground(ICanvasAnimatedControl control, CanvasDrawingSession ds, Color color, float radius, float opacity)
{
CanvasCommandList list = new(control.Device);
using var listDs = list.CreateDrawingSession();
listDs.FillRoundedRectangle(
ds.FillRoundedRectangle(
new Rect(0, 0, _canvasWidth, _canvasHeight),
radius,
radius,
_immersiveBgTransition.Value
color.WithAlpha((byte)(opacity * 255))
);
ds.DrawImage(new OpacityEffect
{
Source = list,
Opacity = _immersiveBgOpacityTransition.Value
});
}
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,
float width
)
{
return new CanvasLinearGradientBrush(
control,
stops
.Select(stops => new CanvasGradientStop
{
Position = stops.position,
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
})
.ToArray()
)
return new CanvasLinearGradientBrush(control, stops.Select(stops => new CanvasGradientStop
{
Position = stops.position,
Color = Color.FromArgb((byte)(stops.opacity * 255), 128, 128, 128),
}).ToArray())
{
StartPoint = new Vector2(startX, 0),
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

@@ -0,0 +1,185 @@
using BetterLyrics.WinUI3.Helper;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Effects;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using System;
using System.Numerics;
using Windows.Foundation;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private OpacityEffect? _lastBgImageEffect;
private OpacityEffect? _bgImageEffect;
private OpacityEffect? _lastFgImageEffect;
private OpacityEffect? _fgImageEffect;
private CanvasCommandList? _albumArtBgEffect;
private OpacityEffect CreateBgImageEffect(CanvasBitmap canvasBitmap, float opacity)
{
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float targetSize = MathF.Sqrt(MathF.Pow(_canvasWidth, 2) + MathF.Pow(_canvasHeight, 2));
float scaleFactor = targetSize / MathF.Min(imageWidth, imageHeight);
// Original source: https://zhuanlan.zhihu.com/p/37178216
float gain = _lyricsBgBrightnessTransition.Value;
float whiteX = 1 - 0.5f * gain;
float whiteY = 0.5f + 0.5f * gain;
float blackX = 0.5f - 0.5f * gain;
float blackY = 0 + 0.5f * gain;
return new OpacityEffect
{
Source = new BrightnessEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
WhitePoint = new Vector2(whiteX, whiteY),
BlackPoint = new Vector2(blackX, blackY),
},
Opacity = opacity,
};
}
private OpacityEffect? CreateFgImageEffect(ICanvasAnimatedControl control, CanvasBitmap canvasBitmap, float opacity)
{
// TODO 最大化/还原时图片大小未跟随改变
if (opacity == 0) return null;
float imageWidth = (float)canvasBitmap.Size.Width;
float imageHeight = (float)canvasBitmap.Size.Height;
float scaleFactor = _albumArtSize / Math.Min(imageWidth, imageHeight);
if (scaleFactor < 0.01f) return null;
float cornerRadius = _albumArtCornerRadius / 100f * _albumArtSize / 2;
var cornerRadiusMask = new CanvasCommandList(control);
using var cornerRadiusMaskDs = cornerRadiusMask.CreateDrawingSession();
cornerRadiusMaskDs.FillRoundedRectangle(
new Rect(0, 0, imageWidth * scaleFactor, imageHeight * scaleFactor),
cornerRadius, cornerRadius, Colors.White
);
return new OpacityEffect
{
Source = new AlphaMaskEffect
{
Source = new ScaleEffect
{
Scale = new Vector2(scaleFactor),
Source = canvasBitmap,
},
AlphaMask = cornerRadiusMask,
},
Opacity = opacity,
};
}
private void UpdateAlbumArtBgEffect(ICanvasAnimatedControl control)
{
_albumArtBgEffect?.Dispose();
_albumArtBgEffect = null;
using var overlappedCovers = new CanvasCommandList(control);
using var overlappedCoversDs = overlappedCovers.CreateDrawingSession();
if (_lastBgImageEffect != null && !_lastBgImageEffect.IsDisposed() && _lastAlbumArtCanvasBitmap != null)
{
DrawBackgroundImgae(_lastBgImageEffect, overlappedCoversDs, _lastAlbumArtCanvasBitmap);
}
if (_bgImageEffect != null && !_bgImageEffect.IsDisposed() && _albumArtCanvasBitmap != null)
{
DrawBackgroundImgae(_bgImageEffect, overlappedCoversDs, _albumArtCanvasBitmap);
}
using var blurredCover = new GaussianBlurEffect
{
BlurAmount = _albumArtBgBlurAmount,
Source = overlappedCovers,
BorderMode = EffectBorderMode.Soft,
Optimization = EffectOptimization.Speed,
};
using var combined = new CanvasCommandList(control);
using var combinedDs = combined.CreateDrawingSession();
if (_coverAcrylicEffectAmount > 0 && _coverAcrylicNoiseCanvasBitmap != null)
{
// 应用亚克力噪点效果
combinedDs.DrawImage(new BlendEffect
{
Mode = BlendEffectMode.SoftLight,
Background = blurredCover,
Foreground = new OpacityEffect
{
Source = _coverAcrylicNoiseCanvasBitmap,
Opacity = _coverAcrylicEffectAmount / 100f,
},
});
}
else
{
combinedDs.DrawImage(blurredCover);
}
_albumArtBgEffect = new CanvasCommandList(control);
using var albumArtBgDs = _albumArtBgEffect.CreateDrawingSession();
albumArtBgDs.DrawImage(new OpacityEffect
{
Opacity = _albumArtBgOpacity / 100f,
Source = combined,
});
}
private void UpdateLastBgImageEffect()
{
_lastBgImageEffect?.Dispose();
_lastBgImageEffect = null;
if (_lastAlbumArtCanvasBitmap != null)
{
_lastBgImageEffect = CreateBgImageEffect(_lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
}
private void UpdateBgImageEffect()
{
_bgImageEffect?.Dispose();
_bgImageEffect = null;
if (_albumArtCanvasBitmap != null)
{
_bgImageEffect = CreateBgImageEffect(_albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
}
private void UpdateLastFgImageEffect(ICanvasAnimatedControl control)
{
_lastFgImageEffect?.Dispose();
_lastFgImageEffect = null;
if (_lastAlbumArtCanvasBitmap != null)
{
_lastFgImageEffect = CreateFgImageEffect(control, _lastAlbumArtCanvasBitmap, 1 - _albumArtBgTransition.Value);
}
}
private void UpdateFgImageEffect(ICanvasAnimatedControl control)
{
_fgImageEffect?.Dispose();
_fgImageEffect = null;
if (_albumArtCanvasBitmap != null)
{
_fgImageEffect = CreateFgImageEffect(control, _albumArtCanvasBitmap, _albumArtBgTransition.Value);
}
}
}
}

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,30 +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();
@@ -139,24 +148,24 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (message.PropertyName == nameof(LyricsWindowViewModel.ActivatedWindowAccentColor))
{
_immersiveBgTransition.StartTransition(message.NewValue);
_immersiveBgColorTransition.StartTransition(message.NewValue);
_environmentalColor = message.NewValue;
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();
@@ -166,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;
@@ -178,92 +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;
}
@@ -272,13 +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();
@@ -293,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();
@@ -315,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;
@@ -327,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();
@@ -339,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);
}
@@ -350,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,17 +8,23 @@ using System.Text;
using System.Threading.Tasks;
using Windows.UI;
namespace BetterLyrics.WinUI3.ViewModels
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
public partial class LyricsRendererViewModel
{
private readonly ValueTransition<float> _canvasYScrollTransition = new(
initialValue: 0f,
durationSeconds: 0.5f,
easingType: EasingType.EaseInOutCubic
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutSine
);
private readonly ValueTransition<Color> _immersiveBgTransition = new(
private readonly ValueTransition<Color> _immersiveBgColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
);
private readonly ValueTransition<Color> _albumArtAccentColorTransition = new(
initialValue: Colors.Transparent,
durationSeconds: 0.3f,
interpolator: (from, to, progress) => Helper.ColorHelper.GetInterpolatedColor(progress, from, to)
@@ -26,12 +32,31 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<float> _immersiveBgOpacityTransition = new(
initialValue: 1f,
durationSeconds: 0.2f
durationSeconds: 0.3f
);
private readonly ValueTransition<float> _titleXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _titleYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _lyricsOpacityTransition = new(
@@ -51,7 +76,14 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly ValueTransition<float> _albumArtXTransition = new(
initialValue: 0f,
durationSeconds: 0.3f
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _albumArtYTransition = new(
initialValue: 0f,
durationSeconds: 0.3f,
easingType: EasingType.EaseInOutBack
);
private readonly ValueTransition<float> _songInfoOpacityTransition = new(

View File

@@ -6,6 +6,8 @@ using Microsoft.Graphics.Canvas.Text;
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;
@@ -15,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
{
@@ -23,10 +25,18 @@ namespace BetterLyrics.WinUI3.ViewModels
private bool _isCanvasHeightChanged = false;
private bool _isDisplayTypeChanged = false;
private bool _isLyricsLayoutOrientationChanged = false;
private bool _isPlayingLineChanged = false;
private bool _isVisibleLinesBoundaryChanged = false;
private bool _isDebugOverlayEnabledChanged = false;
private bool _albumArtChanged = false;
private bool _isCoverAcrylicEffectAmountChanged = false;
private bool _isAlbumArtCornerRadiusChanged = true;
public void Update(ICanvasAnimatedControl control, CanvasAnimatedUpdateEventArgs args)
{
_elapsedTime = args.Timing.ElapsedTime;
@@ -34,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();
@@ -48,11 +64,19 @@ namespace BetterLyrics.WinUI3.ViewModels
_displayType = _displayTypeReceived;
_playingLineIndex = playingLineIndex;
_immersiveBgOpacityTransition.Update(_elapsedTime);
_immersiveBgTransition.Update(_elapsedTime);
_albumArtBgTransition.Update(_elapsedTime);
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
if (_isDebugOverlayEnabledChanged)
{
if (_isDebugOverlayEnabled)
{
_drawFrameStopwatch = Stopwatch.StartNew();
}
else
{
_drawFrameStopwatch?.Stop();
_drawFrameStopwatch = null;
}
_isDebugOverlayEnabledChanged = false;
}
if (_isDynamicCoverOverlayEnabled)
{
@@ -60,58 +84,150 @@ namespace BetterLyrics.WinUI3.ViewModels
_rotateAngle %= MathF.PI * 2;
}
if (_isCanvasHeightChanged)
if (_isCanvasWidthChanged)
{
_albumArtY = 36 + (_canvasHeight - 36 * 2) * 3 / 16f;
if (_canvasWidth < 450)
{
_lyricsLayoutOrientation = LyricsLayoutOrientation.Vertical;
}
else
{
_lyricsLayoutOrientation = LyricsLayoutOrientation.Horizontal;
}
}
if (_isCanvasWidthChanged || _isCanvasHeightChanged)
{
_albumArtSize = MathF.Min(
(_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16,
(_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
_albumArtSize = MathF.Max(0, _albumArtSize);
_titleY = _albumArtY + _albumArtSize * 1.05f;
_isCoverAcrylicEffectAmountChanged = true;
}
if (_isCoverAcrylicEffectAmountChanged)
if (_isDisplayTypeChanged || _isCanvasWidthChanged || _isCanvasHeightChanged)
{
UpdateCoverAcrylicOverlay(control);
}
if (_isDisplayTypeChanged || _isCanvasWidthChanged)
{
bool jumpTo = !_isDisplayTypeChanged && _isCanvasWidthChanged;
switch (_displayType)
bool jumpTo = !_isDisplayTypeChanged && (_isCanvasWidthChanged || _isCanvasHeightChanged);
switch (_lyricsLayoutOrientation)
{
case LyricsDisplayType.AlbumArtOnly:
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_albumArtXTransition.StartTransition(_canvasWidth / 2 - _albumArtSize / 2, jumpTo);
case LyricsLayoutOrientation.Horizontal:
_albumArtSize = MathF.Min((_canvasHeight - _topMargin - _bottomMargin) * 8.5f / 16, (_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2);
_albumArtSize = MathF.Max(0, _albumArtSize);
_albumArtYTransition.StartTransition((_canvasHeight - _albumArtSize * 1.05f - _titleTextFormat.FontSize - _artistTextFormat.FontSize) / 2, jumpTo);
_titleYTransition.StartTransition(_albumArtYTransition.TargetValue + _albumArtSize * 1.05f, jumpTo);
_lyricsYTransition.StartTransition(0, jumpTo);
switch (_displayType)
{
case LyricsDisplayType.AlbumArtOnly:
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_albumArtXTransition.StartTransition(_canvasWidth / 2 - _albumArtSize / 2, jumpTo);
_titleXTransition.StartTransition(_albumArtXTransition.TargetValue, jumpTo);
break;
case LyricsDisplayType.LyricsOnly:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
break;
case LyricsDisplayType.SplitView:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
_titleXTransition.StartTransition(_albumArtXTransition.TargetValue, jumpTo);
break;
default:
break;
}
break;
case LyricsDisplayType.LyricsOnly:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
case LyricsLayoutOrientation.Vertical:
_albumArtSize = 64;
_lyricsXTransition.StartTransition(_leftMargin, jumpTo);
break;
case LyricsDisplayType.SplitView:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_lyricsXTransition.StartTransition((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 + _leftMargin + _middleMargin, jumpTo);
_albumArtXTransition.StartTransition(_leftMargin + ((_canvasWidth - _leftMargin - _middleMargin - _rightMargin) / 2 - _albumArtSize) / 2, jumpTo);
_albumArtXTransition.StartTransition(_leftMargin, jumpTo);
_titleXTransition.StartTransition(_leftMargin + _albumArtSize * 1.2f, jumpTo);
switch (_displayType)
{
case LyricsDisplayType.AlbumArtOnly:
_lyricsOpacityTransition.StartTransition(0f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_albumArtYTransition.StartTransition((_canvasHeight - _albumArtSize) / 2, jumpTo);
_titleYTransition.StartTransition(_albumArtYTransition.TargetValue, jumpTo);
break;
case LyricsDisplayType.LyricsOnly:
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(0f, jumpTo);
_lyricsYTransition.StartTransition(0, jumpTo);
break;
case LyricsDisplayType.SplitView:
_albumArtYTransition.StartTransition(_topMargin, jumpTo);
_titleYTransition.StartTransition(_topMargin, jumpTo);
_lyricsOpacityTransition.StartTransition(1f, jumpTo);
_albumArtOpacityTransition.StartTransition(1f, jumpTo);
_lyricsYTransition.StartTransition(_albumArtSize, jumpTo);
break;
default:
break;
}
break;
default:
break;
}
}
_lyricsXTransition.Update(_elapsedTime);
_albumArtXTransition.Update(_elapsedTime);
_lyricsOpacityTransition.Update(_elapsedTime);
_albumArtOpacityTransition.Update(_elapsedTime);
if (_isAlbumArtCornerRadiusChanged)
{
UpdateLastFgImageEffect(control);
UpdateFgImageEffect(control);
_isAlbumArtCornerRadiusChanged = false;
}
// 背景图切换计算
// 将当前背景图放到 _lastAlbumArtSwBitmap 中 并设置不透明度为 1
// 将新的背景图放到 _albumArtSwBitmap 中 并设置不透明度为 0
// 这样可以实现背景图的连贯渐变效果
if (_albumArtChanged || _isCanvasHeightChanged || _isCanvasWidthChanged ||
_lyricsBgBrightnessTransition.IsTransitioning ||
_albumArtBgTransition.IsTransitioning)
{
// 必须先在此处重置动画
if (_albumArtChanged)
{
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
}
// 更新 last
if (_albumArtChanged)
{
if (_lastAlbumArtSwBitmap != null)
{
_lastAlbumArtCanvasBitmap?.Dispose();
_lastAlbumArtCanvasBitmap = null;
_lastAlbumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _lastAlbumArtSwBitmap);
}
}
UpdateLastFgImageEffect(control);
UpdateLastBgImageEffect();
// 更新 current
if (_albumArtChanged)
{
if (_albumArtSwBitmap != null)
{
_albumArtCanvasBitmap?.Dispose();
_albumArtCanvasBitmap = null;
_albumArtCanvasBitmap = CanvasBitmap.CreateFromSoftwareBitmap(control, _albumArtSwBitmap);
}
}
UpdateFgImageEffect(control);
UpdateBgImageEffect();
// 更新叠加的背景效果
UpdateAlbumArtBgEffect(control);
}
if (_isCoverAcrylicEffectAmountChanged)
{
UpdateCoverAcrylicOverlay(control);
UpdateAlbumArtBgEffect(control);
_isCoverAcrylicEffectAmountChanged = false;
}
_albumArtChanged = false;
if (_isCanvasWidthChanged || _lyricsXTransition.IsTransitioning)
{
@@ -130,9 +246,24 @@ namespace BetterLyrics.WinUI3.ViewModels
UpdateCanvasYScrollOffset(control, false, true);
}
UpdateLinesProps();
UpdateLinesProps(control);
_isLayoutChanged = false;
_titleXTransition.Update(_elapsedTime);
_titleYTransition.Update(_elapsedTime);
_lyricsXTransition.Update(_elapsedTime);
_lyricsYTransition.Update(_elapsedTime);
_albumArtXTransition.Update(_elapsedTime);
_albumArtYTransition.Update(_elapsedTime);
_lyricsOpacityTransition.Update(_elapsedTime);
_albumArtOpacityTransition.Update(_elapsedTime);
_immersiveBgOpacityTransition.Update(_elapsedTime);
_immersiveBgColorTransition.Update(_elapsedTime);
_albumArtAccentColorTransition.Update(_elapsedTime);
_albumArtBgTransition.Update(_elapsedTime);
_lyricsBgBrightnessTransition.Update(_elapsedTime);
_songInfoOpacityTransition.Update(_elapsedTime);
}
private void ReLayout(ICanvasAnimatedControl control)
@@ -165,22 +296,17 @@ namespace BetterLyrics.WinUI3.ViewModels
continue;
}
if (line.CanvasTextLayout != null)
{
line.CanvasTextLayout.Dispose();
line.CanvasTextLayout = null;
}
// Calculate layout bounds
line.CanvasTextLayout = new CanvasTextLayout(
control,
line.DisplayedText,
_lyricsTextFormat,
_maxLyricsWidth,
_canvasHeight
);
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);
if (line.CanvasTextLayout == null)
{
continue;
}
y +=
(float)line.CanvasTextLayout.LayoutBounds.Height
@@ -292,46 +418,30 @@ namespace BetterLyrics.WinUI3.ViewModels
ThemeTypeSent = _lyricsBgTheme;
}
float brightness = 0f;
float brightness;
Color grayedEnvironmentalColor = Colors.Transparent;
switch (ThemeTypeSent)
bool isLight = ThemeTypeSent switch
{
case ElementTheme.Default:
switch (Application.Current.RequestedTheme)
{
case ApplicationTheme.Light:
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ApplicationTheme.Dark:
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
break;
case ElementTheme.Light:
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
break;
case ElementTheme.Dark:
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
break;
default:
break;
}
ElementTheme.Default => Application.Current.RequestedTheme == ApplicationTheme.Light,
ElementTheme.Light => true,
ElementTheme.Dark => false,
_ => false
};
if (_adaptiveGrayedFontColor == _lightColor)
{
grayedEnvironmentalColor = _darkColor;
}
else if (_adaptiveGrayedFontColor == _darkColor)
if (isLight)
{
_adaptiveGrayedFontColor = _darkColor;
brightness = 0.7f;
grayedEnvironmentalColor = _lightColor;
_albumArtAccentColorTransition.StartTransition(_albumArtLightAccentColor);
}
else
{
_adaptiveGrayedFontColor = _lightColor;
brightness = 0.3f;
grayedEnvironmentalColor = _darkColor;
_albumArtAccentColorTransition.StartTransition(_albumArtDarkAccentColor);
}
_lyricsBgBrightnessTransition.StartTransition(brightness);
@@ -342,7 +452,14 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
_adaptiveColoredFontColor = Helper.ColorHelper.GetForegroundColor(_albumArtAccentColor?.WithBrightness(brightness) ?? Colors.Transparent);
if (isLight)
{
_adaptiveColoredFontColor = _albumArtDarkAccentColor;
}
else
{
_adaptiveColoredFontColor = _albumArtLightAccentColor;
}
}
switch (_lyricsBgFontColorType)
@@ -389,9 +506,11 @@ namespace BetterLyrics.WinUI3.ViewModels
default:
break;
}
_isLayoutChanged = true;
}
private void UpdateLinesProps()
private void UpdateLinesProps(ICanvasAnimatedControl control)
{
var currentPlayingLine = _lyricsDataArr
.ElementAtOrDefault(_langIndex)
@@ -405,6 +524,9 @@ namespace BetterLyrics.WinUI3.ViewModels
if (line == null) continue;
line.UpdateTextGeometry();
line.UpdateFontEffect(control, _isDesktopMode, _strokeFontColor, _lyricsFontStrokeWidth, _bgFontColor);
if (_isLayoutChanged || _isVisibleLinesBoundaryChanged || _isPlayingLineChanged)
{
float distanceFromPlayingLine = Math.Abs(line.Position.Y - currentPlayingLine.Position.Y);
@@ -464,7 +586,9 @@ 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(
control,
ret,
@@ -473,7 +597,28 @@ namespace BetterLyrics.WinUI3.ViewModels
Windows.Graphics.DirectX.DirectXPixelFormat.B8G8R8A8UIntNormalized
);
}
_isCoverAcrylicEffectAmountChanged = false;
}
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

@@ -1,19 +1,20 @@
// 2025/6/23 by Zhe Fang
using ABI.Microsoft.UI.Xaml;
using BetterLyrics.WinUI3.Enums;
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.Mvvm.DependencyInjection;
using Lyricify.Lyrics.Helpers.General;
using Lyricify.Lyrics.Providers;
using CommunityToolkit.WinUI;
using Microsoft.Extensions.Logging;
using Microsoft.Graphics.Canvas;
using Microsoft.Graphics.Canvas.Text;
using Microsoft.Graphics.Canvas.UI.Xaml;
using Microsoft.UI;
using Microsoft.UI.Text;
using Microsoft.UI.Xaml;
@@ -26,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]
@@ -40,6 +45,10 @@ namespace BetterLyrics.WinUI3.ViewModels
private int _songDurationMs = (int)TimeSpan.FromMinutes(99).TotalMilliseconds;
private Stopwatch? _drawFrameStopwatch;
private int _drawFrameCount = 0;
private int _displayedDrawFrameCount = 0;
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
private SoftwareBitmap? _albumArtSwBitmap = null;
@@ -47,18 +56,13 @@ namespace BetterLyrics.WinUI3.ViewModels
private CanvasBitmap? _albumArtCanvasBitmap = null;
private CanvasBitmap? _coverAcrylicNoiseCanvasBitmap = null;
private bool _isCoverAcrylicEffectAmountChanged = false;
private float _albumArtSize = 0f;
private int _albumArtCornerRadius = 0;
private float _albumArtY = 0f;
private string? _lastSongTitle;
private string? _songTitle;
private float _titleY = 0f;
private string? _lastSongArtist;
private string? _songArtist;
@@ -106,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;
@@ -116,10 +121,13 @@ 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;
private Color? _albumArtAccentColor = null;
private Color _albumArtLightAccentColor = Colors.Transparent;
private Color _albumArtDarkAccentColor = Colors.Transparent;
private Color _environmentalColor = Colors.Transparent;
private Color _lightColor = Colors.White;
@@ -158,13 +166,14 @@ namespace BetterLyrics.WinUI3.ViewModels
private int _langIndex = 0;
private List<LyricsData> _lyricsDataArr = [];
private List<string> _translationList = [];
private bool _isTranslationEnabled;
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()
{
@@ -189,6 +198,11 @@ namespace BetterLyrics.WinUI3.ViewModels
TrimmingSign = CanvasTrimmingSign.Ellipsis,
TrimmingGranularity = CanvasTextTrimmingGranularity.Character,
};
private CanvasTextFormat _debugTextFormat = new()
{
FontSize = 12,
FontWeight = FontWeights.ExtraBlack,
};
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
private LatestOnlyTaskRunner _showTranslationsRunner = new();
@@ -196,6 +210,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private LyricsDisplayType _displayTypeReceived;
private LyricsDisplayType _displayType;
private LyricsLayoutOrientation _lyricsLayoutOrientation;
private int _albumArtBgBlurAmount;
private int _albumArtBgOpacity;
@@ -356,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;
}
}
}
@@ -363,6 +384,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
SongInfo = e.SongInfo;
UpdateTimelineSyncThreshold();
UpdatePositionOffset();
UpdateIsLastFMTrackEnabled();
if (SongInfo?.Title != _songTitle || SongInfo?.Artist != _songArtist)
{
_lastSongTitle = _songTitle;
@@ -382,6 +407,10 @@ namespace BetterLyrics.WinUI3.ViewModels
await RefreshLyricsAsync(token);
});
TotalTime = TimeSpan.Zero;
// 处理 Last.fm 追踪
_totalPlayingTime = TimeSpan.Zero;
_isLastFMTracked = false;
}
}
@@ -389,16 +418,18 @@ namespace BetterLyrics.WinUI3.ViewModels
{
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
{
//_lastAlbumArtSwBitmap?.Dispose();
_lastAlbumArtSwBitmap = null;
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
_lastAlbumArtCanvasBitmap = null;
//_albumArtSwBitmap?.Dispose();
_albumArtSwBitmap = null;
_albumArtSwBitmap = e.AlbumArtSwBitmap;
_albumArtCanvasBitmap = null;
_albumArtAccentColor = e.AlbumArtAccentColor;
_albumArtChanged = true;
_albumArtBgTransition.Reset(0f);
_albumArtBgTransition.StartTransition(1f);
_albumArtLightAccentColor = e.AlbumArtLightAccentColor ?? Colors.Transparent;
_albumArtDarkAccentColor = e.AlbumArtDarkAccentColor ?? Colors.Transparent;
UpdateColorConfig();
}
@@ -456,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();
@@ -477,7 +508,7 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated);
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _lyricsTranslationSeparator);
_langIndex = 0;
}
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
@@ -505,6 +536,7 @@ namespace BetterLyrics.WinUI3.ViewModels
if (SongInfo != null)
{
(lyricsRaw, lyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
SongInfo.SourceAppUserModelId ?? "",
SongInfo.Title,
SongInfo.Artist,
SongInfo.Album ?? "",
@@ -537,12 +569,12 @@ namespace BetterLyrics.WinUI3.ViewModels
switch (provider)
{
case Enums.LyricsSearchProvider.QQ:
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.Kugou:
break;
case Enums.LyricsSearchProvider.Netease:
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
break;
case Enums.LyricsSearchProvider.LrcLib:
break;

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;
@@ -30,19 +34,22 @@ namespace BetterLyrics.WinUI3
: BaseWindowViewModel,
IRecipient<PropertyChangedMessage<int>>,
IRecipient<PropertyChangedMessage<bool>>,
IRecipient<PropertyChangedMessage<string>>,
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;
private DockPlacement _dockPlacement;
private int _dockWindowHeight;
private string _dockMonitorDeviceName;
public LyricsWindowViewModel(ISettingsService settingsService) : base(settingsService)
{
_dockMonitorDeviceName = _settingsService.DockMonitorDeviceName;
_ignoreFullscreenWindow = _settingsService.IgnoreFullscreenWindow;
_hideWindowWhenNotPlaying = _settingsService.HideWindowWhenNotPlaying;
IsImmersiveMode = _settingsService.IsImmersiveMode;
@@ -50,12 +57,12 @@ 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)
{
AutoHideOrShowWindow();
UpdateDockWindow();
}
[ObservableProperty]
@@ -94,7 +101,7 @@ namespace BetterLyrics.WinUI3
[ObservableProperty]
public partial string LockHotKey { get; set; } = "";
private void AutoHideOrShowWindow()
private void UpdateDockWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
@@ -103,11 +110,11 @@ namespace BetterLyrics.WinUI3
if (IsDockMode || IsDesktopMode)
{
if (_hideWindowWhenNotPlaying && !_playbackService.IsPlaying)
if (_hideWindowWhenNotPlaying && !_mediaSessionsService.IsPlaying)
{
if (IsDockMode)
{
DockModeHelper.UpdateAppBarHeight(hwnd, 0, _dockPlacement);
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, 0, _dockPlacement);
}
window.Hide();
}
@@ -115,24 +122,13 @@ namespace BetterLyrics.WinUI3
{
if (IsDockMode)
{
DockModeHelper.UpdateAppBarHeight(hwnd, _dockWindowHeight, _dockPlacement);
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
}
window.Show();
}
}
}
private void UpdateDockWindow()
{
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (!_hideWindowWhenNotPlaying || _playbackService.SongInfo != null)
{
DockModeHelper.UpdateAppBarHeight(WindowNative.GetWindowHandle(window), _dockWindowHeight, _dockPlacement);
}
}
partial void OnIsImmersiveModeChanged(bool value)
{
if (value)
@@ -166,7 +162,7 @@ namespace BetterLyrics.WinUI3
else if (message.PropertyName == nameof(SettingsPageViewModel.HideWindowWhenNotPlaying))
{
_hideWindowWhenNotPlaying = message.NewValue;
AutoHideOrShowWindow();
UpdateDockWindow();
}
}
}
@@ -230,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();
@@ -255,7 +251,7 @@ namespace BetterLyrics.WinUI3
public void UpdateAccentColor(nint hwnd)
{
WindowPixelSampleMode mode = IsDesktopMode ? WindowPixelSampleMode.WindowEdge : _dockPlacement.ToWindowPixelSampleMode();
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, mode).ToColor();
ActivatedWindowAccentColor = Helper.ColorHelper.GetAccentColor(hwnd, _settingsService.DockMonitorDeviceName, mode).ToColor();
}
public void InitLockHotKey()
@@ -282,7 +278,7 @@ namespace BetterLyrics.WinUI3
IsImmersiveMode = true;
}
AutoHideOrShowWindow();
UpdateDockWindow();
}
[RelayCommand]
@@ -317,7 +313,7 @@ namespace BetterLyrics.WinUI3
if (IsDockMode)
{
window.Restore();
DockModeHelper.Enable(window, _dockWindowHeight, _dockPlacement);
DockModeHelper.Enable(window, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
StartWatchWindowColorChange();
}
else
@@ -325,7 +321,7 @@ namespace BetterLyrics.WinUI3
DockModeHelper.Disable(window);
}
AutoHideOrShowWindow();
UpdateDockWindow();
}
[RelayCommand]
@@ -345,5 +341,17 @@ namespace BetterLyrics.WinUI3
}
}
}
public void Receive(PropertyChangedMessage<string> message)
{
if (message.Sender is SettingsPageViewModel)
{
if (message.PropertyName == nameof(SettingsPageViewModel.SelectedDockMonitorDeviceName))
{
_dockMonitorDeviceName = message.NewValue;
UpdateDockWindow();
}
}
}
}
}

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;
@@ -85,7 +101,32 @@ namespace BetterLyrics.WinUI3.ViewModels
LyricsFontFamily = _settingsService.LyricsFontFamily;
IsDragEverywhereEnabled = _settingsService.IsDragEverywhereEnabled;
_playbackService.MediaSourceProvidersInfoChanged += PlaybackService_SessionIdsChanged;
MonitorDeviceNames = [.. MonitorHelper.GetAllMonitorDeviceNames()];
SelectedDockMonitorDeviceName = _settingsService.DockMonitorDeviceName;
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;
@@ -223,5 +215,13 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_settingsService.IsLibreTranslateEnabled = value;
}
partial void OnSelectedDockMonitorDeviceNameChanged(string value)
{
_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;
@@ -233,5 +225,24 @@ namespace BetterLyrics.WinUI3.ViewModels
[ObservableProperty]
[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)
@@ -105,18 +99,18 @@ namespace BetterLyrics.WinUI3.ViewModels
if (LocalMediaFolders.Any(x => Path.GetFullPath(x.Path).TrimEnd(Path.DirectorySeparatorChar).Equals(normalizedPath.TrimEnd(Path.DirectorySeparatorChar), StringComparison.OrdinalIgnoreCase)))
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathExistedInfo"), InfoBarSeverity.Warning);
}
else if (LocalMediaFolders.Any(item => normalizedPath.StartsWith(Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar, StringComparison.OrdinalIgnoreCase)))
{
// 添加的文件夹是现有文件夹的子文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathBeIncludedInfo"), InfoBarSeverity.Warning);
}
else if (LocalMediaFolders.Any(item => Path.GetFullPath(item.Path).TrimEnd(Path.DirectorySeparatorChar).StartsWith(normalizedPath, StringComparison.OrdinalIgnoreCase))
)
{
// 添加的文件夹是现有文件夹的父文件夹
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"));
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPagePathIncludingOthersInfo"), InfoBarSeverity.Warning);
}
else
{
@@ -130,25 +124,25 @@ 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]
private static async Task OpenCacheFolderAsync()
{
await Windows.System.Launcher.LaunchFolderPathAsync(PathHelper.CacheFolder);
await Windows.System.Launcher.LaunchFolderPathAsync(Helper.PathHelper.CacheFolder);
}
[RelayCommand]
private static void RestartApp()
{
WindowHelper.RestartApp();
Helper.WindowHelper.RestartApp();
}
[RelayCommand]
private async Task SelectAndAddFolderAsync(UIElement sender)
{
var window = WindowHelper.GetWindowByWindowType<SettingsWindow>();
var window = Helper.WindowHelper.GetWindowByWindowType<SettingsWindow>();
if (window == null) return;
var picker = new Windows.Storage.Pickers.FolderPicker();
@@ -177,18 +171,20 @@ namespace BetterLyrics.WinUI3.ViewModels
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Success);
IsLibreTranslateServerTesting = false;
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
});
}
catch (Exception)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
IsLibreTranslateServerTesting = false;
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
});
}
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
IsLibreTranslateServerTesting = false;
});
});
}
@@ -201,17 +197,47 @@ namespace BetterLyrics.WinUI3.ViewModels
bool testResult = await NetHelper.CheckConnectivity($"{LXMusicServer}/status");
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
App.Current.SettingsWindowNotificationPanel?.Notify(
App.ResourceLoader!.GetString($"SettingsPageServerTest{(testResult ? "Success" : "Failed")}Info"),
testResult ? InfoBarSeverity.Success : InfoBarSeverity.Error);
if (testResult)
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
}
else
{
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestFailedInfo"), InfoBarSeverity.Error);
}
IsLXMusicServerTesting = false;
});
});
}
[RelayCommand]
private void RefreshMonitorDeviceNames()
{
MonitorDeviceNames = [.. MonitorHelper.GetAllMonitorDeviceNames()];
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();
@@ -226,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:

Some files were not shown because too many files have changed in this diff Show More