mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-13 03:34:55 +08:00
Compare commits
9 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
52711cba1f | ||
|
|
7eda076920 | ||
|
|
282c2f5ac0 | ||
|
|
94e76a6ac9 | ||
|
|
516d388009 | ||
|
|
ad33eed57c | ||
|
|
64bf2dc3d9 | ||
|
|
b86e4a3d12 | ||
|
|
411506b9cd |
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.0.53.0" />
|
||||
Version="1.0.60.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -51,10 +51,13 @@
|
||||
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
|
||||
<converter:MediaSourceProviderToLogoUriConverter x:Key="MediaSourceProviderToLogoUriConverter" />
|
||||
<converter:MediaSourceProviderToDisplayedNameConverter x:Key="MediaSourceProviderToDisplayedNameConverter" />
|
||||
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
|
||||
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
|
||||
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
|
||||
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
|
||||
@@ -118,6 +118,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<PlaybackSettingsControlViewModel>()
|
||||
.AddSingleton<MediaSettingsControlViewModel>()
|
||||
.AddSingleton<AllLyricsSettingsControlViewModel>()
|
||||
.AddSingleton<LyricsSearchControlViewModel>()
|
||||
.AddSingleton<LyricsWindowViewModel>()
|
||||
.AddSingleton<SettingsWindowViewModel>()
|
||||
.AddSingleton<SystemTrayViewModel>()
|
||||
|
||||
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Empty.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Empty.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 24 KiB |
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Page.png
Normal file
BIN
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Page.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 20 KiB |
@@ -26,7 +26,7 @@
|
||||
<None Remove="Controls\AllLyricsSettingsControl.xaml" />
|
||||
<None Remove="Controls\AppSettingsControl.xaml" />
|
||||
<None Remove="Controls\ExtendedSlider.xaml" />
|
||||
<None Remove="Controls\LyricsBavkgroundSettingsControl.xaml" />
|
||||
<None Remove="Controls\LyricsSearchControl.xaml" />
|
||||
<None Remove="Controls\LyricsSettingsControl.xaml" />
|
||||
<None Remove="Controls\MediaSettingsControl.xaml" />
|
||||
<None Remove="Controls\PlaybackSettingsControl.xaml" />
|
||||
@@ -61,6 +61,7 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="Dubya.WindowsMediaController" Version="2.5.5" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.8" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.8" />
|
||||
@@ -87,11 +88,6 @@
|
||||
<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>
|
||||
@@ -122,6 +118,9 @@
|
||||
<Content Update="Assets\Edge.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Empty.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\EmptyBox.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -161,6 +160,9 @@
|
||||
<Content Update="Assets\NetEaseCloudMusic.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\Page.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\PotPlayer.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -186,6 +188,11 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\LyricsSearchControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\ShortcutTextBox.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -222,7 +229,7 @@
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\LyricsBavkgroundSettingsControl.xaml">
|
||||
<Page Update="Controls\LyricsBackgroundSettingsControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
|
||||
@@ -55,6 +55,7 @@
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartInAppLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartDockLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartDesktopLyrics" />
|
||||
<ComboBoxItem x:Uid="SettingsPageAutoStartPIPLyrics" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.LyricsBavkgroundSettingsControl"
|
||||
x:Class="BetterLyrics.WinUI3.Controls.LyricsBackgroundSettingsControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
@@ -20,11 +20,11 @@ using Windows.Foundation.Collections;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class LyricsBavkgroundSettingsControl : UserControl
|
||||
public sealed partial class LyricsBackgroundSettingsControl : UserControl
|
||||
{
|
||||
public LyricsBackgroundSettingsControlViewModel ViewModel => (LyricsBackgroundSettingsControlViewModel)DataContext;
|
||||
|
||||
public LyricsBavkgroundSettingsControl()
|
||||
public LyricsBackgroundSettingsControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsBackgroundSettingsControlViewModel>();
|
||||
@@ -0,0 +1,227 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.LyricsSearchControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Padding="16" RowSpacing="6">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<Grid Grid.Row="0" Margin="0,0,0,6">
|
||||
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
|
||||
<controls:SettingsExpander x:Uid="LyricsSearchControlSongInfoMapping" IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="LyricsSearchControlTitle" Description="{x:Bind ViewModel.MappedSongSearchQuery.OriginalTitle, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
|
||||
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedTitle, Mode=TwoWay}" TextWrapping="Wrap" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ResetMappedTitleCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="LyricsSearchControlArtist" Description="{x:Bind ViewModel.MappedSongSearchQuery.OriginalArtist, Mode=OneWay}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock x:Uid="LyricsSearchControlMappedAs" VerticalAlignment="Center" />
|
||||
<TextBox Text="{x:Bind ViewModel.MappedSongSearchQuery.MappedArtist, Mode=TwoWay}" TextWrapping="Wrap" />
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Command="{x:Bind ViewModel.ResetMappedArtistCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard>
|
||||
<CheckBox x:Uid="LyricsSearchControlMarkAsPureMusic" IsChecked="{x:Bind ViewModel.MappedSongSearchQuery.IsMarkedAsPureMusic, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="LyricsSearchControlTargetSearchProvider">
|
||||
<Button
|
||||
x:Uid="LyricsSearchControlSearch"
|
||||
Command="{x:Bind ViewModel.SearchCommand}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</controls:SettingsCard>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<!-- 结果列表及原始歌词文件展示区域 -->
|
||||
<Grid Grid.Row="1">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="2*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid Grid.Column="0" RowSpacing="12">
|
||||
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<ListViewItem IsEnabled="{Binding IsFound}">
|
||||
<Grid Opacity="{Binding IsFound, Converter={StaticResource BoolToOpacityConverter}}">
|
||||
<RelativePanel Padding="0,6">
|
||||
<TextBlock
|
||||
x:Name="SearchedTitle"
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
Text="{Binding Title}"
|
||||
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
x:Name="SearchedArtists"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
RelativePanel.AlignLeftWithPanel="True"
|
||||
RelativePanel.Below="SearchedTitle"
|
||||
Text="{Binding Artist}"
|
||||
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
<TextBlock
|
||||
x:Name="SearchedProvider"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Text="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
</RelativePanel>
|
||||
<TextBlock
|
||||
x:Uid="LyricsSearchControlNotFound"
|
||||
VerticalAlignment="Center"
|
||||
Text="Not found"
|
||||
Visibility="{Binding IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
|
||||
</Grid>
|
||||
</ListViewItem>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</ListView>
|
||||
<StackPanel
|
||||
Padding="0,36"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/Empty.png" />
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsSearchResults.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</StackPanel>
|
||||
<ProgressBar
|
||||
VerticalAlignment="Top"
|
||||
IsIndeterminate="True"
|
||||
ShowError="False"
|
||||
ShowPaused="False"
|
||||
Visibility="{x:Bind ViewModel.IsSearching, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<Grid Grid.Column="1">
|
||||
<ListView SelectedItem="{x:Bind ViewModel.SelectedLyricsLine, Mode=TwoWay}" ItemsSource="{x:Bind ViewModel.LyricsData.LyricsLines, Mode=OneWay}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding StartMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
|
||||
<TextBlock
|
||||
Margin="1,0"
|
||||
Foreground="{ThemeResource SystemFillColorNeutralBrush}"
|
||||
Text="-" />
|
||||
<TextBlock Foreground="{ThemeResource SystemFillColorNeutralBrush}" Text="{Binding EndMs, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
|
||||
<TextBlock Margin="6,0" Text="{Binding OriginalText}" />
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
<StackPanel
|
||||
Padding="0,36"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/Page.png" />
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.LyricsData, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="{x:Null}">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<Grid Grid.Row="2">
|
||||
<RelativePanel>
|
||||
<TextBlock
|
||||
x:Uid="LyricsSearchControlHelp"
|
||||
Margin="0,0,24,0"
|
||||
FontSize="12"
|
||||
Foreground="{StaticResource TextFillColorSecondaryBrush}"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
RelativePanel.LeftOf="Reset"
|
||||
TextWrapping="Wrap" />
|
||||
<Button
|
||||
x:Name="Reset"
|
||||
x:Uid="LyricsSearchControlReset"
|
||||
Margin="0,0,6,0"
|
||||
Command="{x:Bind ViewModel.ResetCommand}"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
RelativePanel.LeftOf="SaveChanges" />
|
||||
<Button
|
||||
x:Name="SaveChanges"
|
||||
x:Uid="LyricsSearchControlSaveChanges"
|
||||
Command="{x:Bind ViewModel.SaveCommand}"
|
||||
RelativePanel.AlignRightWithPanel="True"
|
||||
RelativePanel.AlignVerticalCenterWithPanel="True"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</RelativePanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,35 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Controls.Primitives;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using Microsoft.UI.Xaml.Input;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Navigation;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using Ude.Core;
|
||||
using Windows.Foundation;
|
||||
using Windows.Foundation.Collections;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class LyricsSearchControl : UserControl
|
||||
{
|
||||
public LyricsSearchControlViewModel ViewModel => (LyricsSearchControlViewModel)DataContext;
|
||||
|
||||
public LyricsSearchControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<LyricsSearchControlViewModel>();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -234,14 +234,46 @@
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsLyricsLineFadeEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 高亮 -->
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsHighlightScope" HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}">
|
||||
<!-- 译文高亮 -->
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsTranslationHighlight"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageAmount">
|
||||
<local:ExtendedSlider
|
||||
Default="60"
|
||||
Frequency="5"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsTranslationHighlightAmount, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- 原文高亮 -->
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsHighlightScope"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
IsExpanded="True">
|
||||
<ComboBox SelectedIndex="{x:Bind LyricsEffectSettings.LyricsHighlightScope, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeLineStartToCurrentChar" />
|
||||
<ComboBoxItem x:Uid="SettingsPageLyricsRendingScopeCurrentLine" />
|
||||
</ComboBox>
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageAmount">
|
||||
<local:ExtendedSlider
|
||||
Default="100"
|
||||
Frequency="5"
|
||||
Maximum="100"
|
||||
Minimum="0"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsHighlightAmount, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<!-- 阴影 -->
|
||||
<controls:SettingsExpander
|
||||
@@ -319,6 +351,7 @@
|
||||
<ToggleSwitch IsOn="{x:Bind LyricsEffectSettings.IsFanLyricsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 滚动动画 -->
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageScrollEasing"
|
||||
HeaderIcon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
@@ -341,7 +374,6 @@
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollTopDuration">
|
||||
<local:ExtendedSlider
|
||||
x:Uid="SettingsPageLyricsScrollTopDurationExtendedSlider"
|
||||
Default="500"
|
||||
Frequency="50"
|
||||
Maximum="1000"
|
||||
@@ -351,7 +383,6 @@
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollDuration">
|
||||
<local:ExtendedSlider
|
||||
x:Uid="SettingsPageLyricsScrollDurationExtendedSlider"
|
||||
Default="500"
|
||||
Frequency="50"
|
||||
Maximum="1000"
|
||||
@@ -361,7 +392,6 @@
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollBottomDuration">
|
||||
<local:ExtendedSlider
|
||||
x:Uid="SettingsPageLyricsScrollBottomDurationExtendedSlider"
|
||||
Default="500"
|
||||
Frequency="50"
|
||||
Maximum="1000"
|
||||
@@ -369,6 +399,24 @@
|
||||
Unit="ms"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsScrollBottomDuration, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollTopDelay">
|
||||
<local:ExtendedSlider
|
||||
Default="0"
|
||||
Frequency="50"
|
||||
Maximum="2000"
|
||||
Minimum="0"
|
||||
Unit="ms"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsScrollTopDelay, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsCard x:Uid="SettingsPageScrollBottomDelay">
|
||||
<local:ExtendedSlider
|
||||
Default="0"
|
||||
Frequency="50"
|
||||
Maximum="2000"
|
||||
Minimum="0"
|
||||
Unit="ms"
|
||||
Value="{x:Bind LyricsEffectSettings.LyricsScrollBottomDelay, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
|
||||
@@ -31,16 +31,20 @@
|
||||
</controls:SettingsCard>
|
||||
|
||||
<!-- 时间轴相关配置 -->
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold">
|
||||
<local:ExtendedSlider
|
||||
x:Uid="SettingsPageLyricsTimelineThresholdReset"
|
||||
Frequency="100"
|
||||
Maximum="1000"
|
||||
Minimum="0"
|
||||
ResetButtonVisibility="Collapsed"
|
||||
Unit="ms"
|
||||
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsExpander x:Uid="SettingsPageLyricsTimeline" IsExpanded="True">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.SelectedMediaSourceProvider.IsTimelineSyncEnabled, Mode=TwoWay}" />
|
||||
<controls:SettingsExpander.Items>
|
||||
<controls:SettingsCard x:Uid="SettingsPageLyricsTimelineThreshold" IsEnabled="{x:Bind ViewModel.SelectedMediaSourceProvider.IsTimelineSyncEnabled, Mode=OneWay}">
|
||||
<local:ExtendedSlider
|
||||
Frequency="100"
|
||||
Maximum="1000"
|
||||
Minimum="0"
|
||||
ResetButtonVisibility="Collapsed"
|
||||
Unit="ms"
|
||||
Value="{x:Bind ViewModel.SelectedMediaSourceProvider.TimelineSyncThreshold, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
</controls:SettingsExpander.Items>
|
||||
</controls:SettingsExpander>
|
||||
<controls:SettingsExpander x:Uid="MainPagePositionOffsetSlider" IsExpanded="True">
|
||||
<local:ExtendedSlider
|
||||
x:Uid="SettingsPagePositionOffsetReset"
|
||||
|
||||
@@ -24,6 +24,14 @@
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
<Button
|
||||
Margin="3,0,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Click="CheckButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.UI.Input;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
@@ -91,5 +92,22 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
Shortcut = [];
|
||||
UpdateTextBox();
|
||||
}
|
||||
|
||||
private void CheckButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool registered = GlobalHotKeyHelper.IsHotKeyRegistered(Shortcut);
|
||||
if (registered)
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("SettingsPageShortcutRegSuccessInfo"),
|
||||
InfoBarSeverity.Success);
|
||||
}
|
||||
else
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(
|
||||
App.ResourceLoader!.GetString("SettingsPageShortcutRegFailInfo"),
|
||||
InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class BoolNegationToVisibilityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue ? Visibility.Collapsed : Visibility.Visible;
|
||||
}
|
||||
return Visibility.Visible;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
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 BoolToOpacityConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is bool boolValue)
|
||||
{
|
||||
return boolValue ? 1.0 : 0.3;
|
||||
}
|
||||
return 1.0;
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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 MillisecondsToFormattedTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int milliseconds)
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(milliseconds).ToString(@"mm\:ss\.fff");
|
||||
}
|
||||
else if (value is double doubleMilliseconds)
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff");
|
||||
}
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,5 +67,16 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
_ => ".*",
|
||||
};
|
||||
}
|
||||
|
||||
public static LyricsSearchProvider? ToLyricsSearchProvider(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => LyricsSearchProvider.LocalLrcFile,
|
||||
LyricsFormat.Eslrc => LyricsSearchProvider.LocalEslrcFile,
|
||||
LyricsFormat.Ttml => LyricsSearchProvider.LocalTtmlFile,
|
||||
_ => null,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Events
|
||||
{
|
||||
public class LyricsChangedEventArgs(LyricsData? lyricsData) : EventArgs
|
||||
{
|
||||
public LyricsData? LyricsData { get; } = lyricsData;
|
||||
}
|
||||
}
|
||||
@@ -262,17 +262,50 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return mask;
|
||||
}
|
||||
|
||||
public static CanvasCommandList CreateTranslationHighlightMask(ICanvasAnimatedControl control, LyricsLine lyricsLine)
|
||||
{
|
||||
var mask = new CanvasCommandList(control);
|
||||
using var ds = mask.CreateDrawingSession();
|
||||
|
||||
if (lyricsLine.CanvasTextLayout == null)
|
||||
{
|
||||
return mask;
|
||||
}
|
||||
|
||||
var regions = lyricsLine.CanvasTextLayout.GetCharacterRegions(lyricsLine.OriginalText.Length, lyricsLine.DisplayedText.Length - lyricsLine.OriginalText.Length);
|
||||
if (regions.Length > 0)
|
||||
{
|
||||
for (int j = 0; j < regions.Length; j++)
|
||||
{
|
||||
var region = regions[j];
|
||||
var rect = new Rect(
|
||||
region.LayoutBounds.X,
|
||||
region.LayoutBounds.Y + lyricsLine.Position.Y,
|
||||
region.LayoutBounds.Width,
|
||||
region.LayoutBounds.Height
|
||||
);
|
||||
ds.FillRectangle(rect, Colors.White);
|
||||
}
|
||||
}
|
||||
|
||||
return mask;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建高亮效果层
|
||||
/// </summary>
|
||||
/// <param name="control"></param>
|
||||
/// <param name="lineRenderingType"></param>
|
||||
public static AlphaMaskEffect CreateForegroundHighlightEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask)
|
||||
public static OpacityEffect CreateForegroundHighlightEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, double opacity)
|
||||
{
|
||||
return new AlphaMaskEffect
|
||||
return new OpacityEffect
|
||||
{
|
||||
Source = foregroundFontEffect,
|
||||
AlphaMask = mask,
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = foregroundFontEffect,
|
||||
AlphaMask = mask,
|
||||
},
|
||||
Opacity = (float)opacity,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -291,6 +324,19 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
};
|
||||
}
|
||||
|
||||
public static OpacityEffect CreateForegroundTranslationEffect(CanvasCommandList foregroundFontEffect, IGraphicsEffectSource mask, double opacity)
|
||||
{
|
||||
return new OpacityEffect
|
||||
{
|
||||
Source = new AlphaMaskEffect
|
||||
{
|
||||
Source = foregroundFontEffect,
|
||||
AlphaMask = mask,
|
||||
},
|
||||
Opacity = (float)opacity,
|
||||
};
|
||||
}
|
||||
|
||||
public static IGraphicsEffectSource GetAlphaMask(ICanvasAnimatedControl control, IGraphicsEffectSource charMask, IGraphicsEffectSource lineStartToCharMask, IGraphicsEffectSource lineMask, LineRenderingType lineRenderingType)
|
||||
{
|
||||
var result = lineRenderingType switch
|
||||
|
||||
@@ -11,6 +11,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class FontHelper
|
||||
{
|
||||
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies();
|
||||
public static string[] SystemFontFamilies => CanvasTextFormat.GetSystemFontFamilies().Order().ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class GlobalHotKeyHelper
|
||||
{
|
||||
private static Dictionary<int, Action> _hotKeyActions = [];
|
||||
private static Dictionary<int, Action> _actions = [];
|
||||
private static Dictionary<int, List<string>> _keys = [];
|
||||
|
||||
/// <summary>
|
||||
/// Register a global hotkey for a specific window type
|
||||
@@ -22,7 +23,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
/// <param name="id"></param>
|
||||
/// <param name="keys"></param>
|
||||
/// <param name="action"></param>
|
||||
public static void RegisterHotKey<T>(ShortcutID id, List<string> keys, Action action)
|
||||
private static void RegisterHotKey<T>(ShortcutID id, List<string> keys, Action action)
|
||||
{
|
||||
if (keys.Count == 0) return;
|
||||
|
||||
@@ -55,18 +56,23 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
key = (VirtualKey)Enum.Parse(typeof(VirtualKey), item, true);
|
||||
}
|
||||
}
|
||||
User32.RegisterHotKey(hwnd, (int)id, modifiers, (uint)key);
|
||||
_hotKeyActions[(int)id] = action;
|
||||
bool success = User32.RegisterHotKey(hwnd, (int)id, modifiers, (uint)key);
|
||||
if (success)
|
||||
{
|
||||
_actions[(int)id] = action;
|
||||
_keys[(int)id] = keys;
|
||||
}
|
||||
}
|
||||
|
||||
public static void UnregisterHotKey<T>(ShortcutID id)
|
||||
private static void UnregisterHotKey<T>(ShortcutID id)
|
||||
{
|
||||
var window = WindowHelper.GetWindowByWindowType<T>();
|
||||
if (window == null) return;
|
||||
|
||||
HWND hwnd = WindowNative.GetWindowHandle(window);
|
||||
User32.UnregisterHotKey(hwnd, (int)id);
|
||||
_hotKeyActions.Remove((int)id);
|
||||
_actions.Remove((int)id);
|
||||
_keys.Remove((int)id);
|
||||
}
|
||||
|
||||
public static void UpdateHotKey<T>(ShortcutID id, List<string> keys, Action action)
|
||||
@@ -75,6 +81,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
RegisterHotKey<T>(id, keys, action);
|
||||
}
|
||||
|
||||
public static bool IsHotKeyRegistered(ShortcutID id)
|
||||
{
|
||||
return _actions.ContainsKey((int)id);
|
||||
}
|
||||
|
||||
public static bool IsHotKeyRegistered(List<string> keys)
|
||||
{
|
||||
return _keys.ContainsValue(keys);
|
||||
}
|
||||
|
||||
public static bool TryInvokeAction(ShortcutID id)
|
||||
{
|
||||
return TryInvokeAction((int)id);
|
||||
@@ -82,7 +98,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static bool TryInvokeAction(int id)
|
||||
{
|
||||
if (_hotKeyActions.TryGetValue(id, out var action))
|
||||
if (_actions.TryGetValue(id, out var action))
|
||||
{
|
||||
action?.Invoke();
|
||||
return true;
|
||||
|
||||
@@ -177,7 +177,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
square.Mutate(ctx => ctx.DrawImage(image, new Point(offsetX, offsetY), 1f));
|
||||
|
||||
using var ms = new MemoryStream();
|
||||
square.Save(ms, new JpegEncoder());
|
||||
square.Save(ms, new PngEncoder());
|
||||
return ms.ToArray();
|
||||
}
|
||||
|
||||
|
||||
@@ -58,6 +58,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_identifier = _factory.Load(PathHelper.LanguageProfilePath);
|
||||
}
|
||||
|
||||
private static string SimplifiedChineseOrTraditionalChinese(string text)
|
||||
{
|
||||
return text == ChineseConverter.ConvertToSimplifiedChinese(text) ? "zh-Hans" : "zh-Hant";
|
||||
}
|
||||
|
||||
public static string? DetectLanguageCode(string? text)
|
||||
{
|
||||
if (text == null) return null;
|
||||
@@ -67,9 +72,9 @@ namespace BetterLyrics.WinUI3.Services
|
||||
code = code switch
|
||||
{
|
||||
"simple" => "en",
|
||||
"zh_classical" => "zh-Hant",
|
||||
"zh_yue" => "zh-Hant",
|
||||
"zh" => text == ChineseConverter.ConvertToSimplifiedChinese(text) ? "zh-Hans" : "zh-Hant",
|
||||
"zh_classical" => SimplifiedChineseOrTraditionalChinese(text),
|
||||
"zh_yue" => SimplifiedChineseOrTraditionalChinese(text),
|
||||
"zh" => SimplifiedChineseOrTraditionalChinese(text),
|
||||
_ => code
|
||||
};
|
||||
return code;
|
||||
|
||||
@@ -7,11 +7,11 @@ using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class BackgroundTaskRunner
|
||||
public class LatestOnlyTaskRunner
|
||||
{
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
public void Run(Func<CancellationToken, Task> taskFactory)
|
||||
public async Task RunAsync(Func<CancellationToken, Task> taskFactory)
|
||||
{
|
||||
_cts?.Cancel();
|
||||
_cts?.Dispose();
|
||||
@@ -19,19 +19,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_cts = new CancellationTokenSource();
|
||||
var token = _cts.Token;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
try
|
||||
{
|
||||
try
|
||||
{
|
||||
await taskFactory(token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}, token);
|
||||
await taskFactory(token);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
using Windows.Graphics;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PointHelper
|
||||
{
|
||||
public static PointInt32 ToPointInt32(this Point point)
|
||||
{
|
||||
return new PointInt32((int)point.X, (int)point.Y);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
@@ -11,6 +10,8 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
private T _currentValue;
|
||||
private double _durationSeconds;
|
||||
private double _delaySeconds;
|
||||
private double _delayRemaining;
|
||||
private EasingType? _easingType;
|
||||
private Func<T, T, double, T> _interpolator;
|
||||
private bool _isTransitioning;
|
||||
@@ -19,18 +20,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
private T _targetValue;
|
||||
|
||||
public double DurationSeconds => _durationSeconds;
|
||||
public double DelaySeconds => _delaySeconds;
|
||||
|
||||
public bool IsTransitioning => _isTransitioning;
|
||||
public T Value => _currentValue;
|
||||
public T TargetValue => _targetValue;
|
||||
public EasingType? EasingType => _easingType;
|
||||
|
||||
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null)
|
||||
public ValueTransition(T initialValue, double durationSeconds, Func<T, T, double, T>? interpolator = null, EasingType? easingType = null, double delaySeconds = 0)
|
||||
{
|
||||
_currentValue = initialValue;
|
||||
_startValue = initialValue;
|
||||
_targetValue = initialValue;
|
||||
_durationSeconds = durationSeconds;
|
||||
_delaySeconds = delaySeconds;
|
||||
_delayRemaining = 0;
|
||||
_progress = 1f;
|
||||
_isTransitioning = false;
|
||||
|
||||
@@ -58,12 +62,18 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_durationSeconds = seconds;
|
||||
}
|
||||
|
||||
public void SetDelay(double seconds)
|
||||
{
|
||||
_delaySeconds = seconds;
|
||||
}
|
||||
|
||||
private void JumpTo(T value)
|
||||
{
|
||||
_currentValue = value;
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 1f;
|
||||
_delayRemaining = 0;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
@@ -73,6 +83,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_startValue = value;
|
||||
_targetValue = value;
|
||||
_progress = 0f;
|
||||
_delayRemaining = 0;
|
||||
_isTransitioning = false;
|
||||
}
|
||||
|
||||
@@ -89,6 +100,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_startValue = _currentValue;
|
||||
_targetValue = targetValue;
|
||||
_progress = 0f;
|
||||
_delayRemaining = _delaySeconds;
|
||||
_isTransitioning = true;
|
||||
}
|
||||
}
|
||||
@@ -103,7 +115,24 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
if (!_isTransitioning) return;
|
||||
|
||||
_progress += (double)(elapsedTime / TimeSpan.FromSeconds(_durationSeconds));
|
||||
if (_delayRemaining > 0)
|
||||
{
|
||||
double consume = Math.Min(_delayRemaining, elapsedTime.TotalSeconds);
|
||||
_delayRemaining -= consume;
|
||||
if (_delayRemaining > 0)
|
||||
return;
|
||||
elapsedTime = TimeSpan.FromSeconds(elapsedTime.TotalSeconds - consume);
|
||||
}
|
||||
|
||||
if (_durationSeconds <= 0)
|
||||
{
|
||||
_progress = 1f;
|
||||
}
|
||||
else
|
||||
{
|
||||
_progress += elapsedTime.TotalSeconds / _durationSeconds;
|
||||
}
|
||||
|
||||
if (_progress >= 1f)
|
||||
{
|
||||
_progress = 1f;
|
||||
@@ -178,4 +207,4 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
_interpolator = GetInterpolatorByEasingType(easingType);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LyricsSearchResult
|
||||
{
|
||||
public bool IsFound => !string.IsNullOrEmpty(Raw);
|
||||
public LyricsSearchProvider? Provider { get; set; }
|
||||
|
||||
public string? Raw { get; set; }
|
||||
|
||||
public string? Title { get; set; }
|
||||
public string? Artist { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MappedSongSearchQuery : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalTitle { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string OriginalArtist { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedTitle { get; set; } = string.Empty;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string MappedArtist { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMarkedAsPureMusic { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; }
|
||||
|
||||
public MappedSongSearchQuery Clone()
|
||||
{
|
||||
return new MappedSongSearchQuery
|
||||
{
|
||||
OriginalTitle = this.OriginalTitle,
|
||||
OriginalArtist = this.OriginalArtist,
|
||||
MappedTitle = this.MappedTitle,
|
||||
MappedArtist = this.MappedArtist,
|
||||
IsMarkedAsPureMusic = this.IsMarkedAsPureMusic,
|
||||
LyricsSearchProvider = this.LyricsSearchProvider
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,23 +12,29 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class MediaSourceProviderInfo : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsEnabled { get; set; } = true;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial string Provider { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLastFMTrackEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsTimelineSyncEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int TimelineSyncThreshold { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int PositionOffset { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool ResetPositionOffsetOnSongChanged { get; set; } = false;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; } = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<AlbumArtSearchProviderInfo> AlbumArtSearchProvidersInfo { get; set; } = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
|
||||
|
||||
public MediaSourceProviderInfo() { }
|
||||
public MediaSourceProviderInfo()
|
||||
{
|
||||
Provider = string.Empty;
|
||||
TimelineSyncThreshold = 0;
|
||||
PositionOffset = 0;
|
||||
}
|
||||
|
||||
public MediaSourceProviderInfo(string provider) : base()
|
||||
{
|
||||
@@ -40,18 +46,13 @@ namespace BetterLyrics.WinUI3.Models
|
||||
PositionOffset = 1000;
|
||||
break;
|
||||
default:
|
||||
// 设置 100 以防不必要的重复同步
|
||||
TimelineSyncThreshold = 100;
|
||||
// 设置 300 以防不必要的重复同步
|
||||
TimelineSyncThreshold = 300;
|
||||
PositionOffset = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
Provider = provider;
|
||||
IsEnabled = true;
|
||||
IsLastFMTrackEnabled = false;
|
||||
ResetPositionOffsetOnSongChanged = false;
|
||||
LyricsSearchProvidersInfo = [.. Enum.GetValues<LyricsSearchProvider>().Select(p => new LyricsSearchProviderInfo(p, true))];
|
||||
AlbumArtSearchProvidersInfo = [.. Enum.GetValues<AlbumArtSearchProvider>().Select(p => new AlbumArtSearchProviderInfo(p, true))];
|
||||
}
|
||||
|
||||
partial void OnAlbumArtSearchProvidersInfoChanged(FullyObservableCollection<AlbumArtSearchProviderInfo> oldValue, FullyObservableCollection<AlbumArtSearchProviderInfo> newValue)
|
||||
|
||||
@@ -35,6 +35,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<LocalMediaFolder> LocalMediaFolders { get; set; } = [];
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MediaSourceProviderInfo> MediaSourceProvidersInfo { get; set; } = [];
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial FullyObservableCollection<MappedSongSearchQuery> MappedSongSearchQueries { get; set; } = [];
|
||||
|
||||
public AppSettings() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,7 +23,10 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsShadowAmount { get; set; } = 8;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LineRenderingType LyricsHighlightScope { get; set; } = LineRenderingType.LineStartToCurrentChar;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsHighlightAmount { get; set; } = 100; // 100% 是上界
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsTranslationHighlightAmount { get; set; } = 60; // 100% 是上界
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsLyricsFloatAnimationEnabled { get; set; } = true;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsFloatAmount { get; set; } = 1;
|
||||
|
||||
@@ -31,6 +34,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollDuration { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollTopDuration { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollBottomDuration { get; set; }
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollTopDelay { get; set; } = 0;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsScrollBottomDelay { get; set; } = 0;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int LyricsVerticalEdgeOpacity { get; set; } = 0; // 0% opacity
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsFanLyricsEnabled { get; set; } = false;
|
||||
|
||||
@@ -4,11 +4,13 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Foundation;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
{
|
||||
public partial class PictureInPictureModeSettings : BaseModeSettings
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Point WindowPosition { get; set; } = new Point(100, 100);
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial List<string> ToggleShortcut { get; set; } = new List<string>() { "Ctrl", "Shift", "P" };
|
||||
|
||||
public PictureInPictureModeSettings()
|
||||
|
||||
@@ -13,7 +13,8 @@ namespace BetterLyrics.WinUI3.Models.Settings
|
||||
public partial class StandardModeSettings : BaseModeSettings
|
||||
{
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial Rect WindowBounds { get; set; } = new Rect(100, 100, 1000, 600);
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsMaximized { get; set; } = false;
|
||||
|
||||
public StandardModeSettings()
|
||||
{
|
||||
LyricsDisplayType = LyricsDisplayType.SplitView;
|
||||
|
||||
@@ -16,15 +16,5 @@
|
||||
x:Name="LyricsCanvas"
|
||||
Draw="LyricsCanvas_Draw"
|
||||
Update="LyricsCanvas_Update" />
|
||||
<Grid
|
||||
Margin="36"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Top"
|
||||
Visibility="{x:Bind ViewModel.IsTranslating, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<FontIcon
|
||||
x:Name="RotatingIcon"
|
||||
FontFamily="{StaticResource IconFontFamily}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -12,6 +12,7 @@ using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
@@ -30,7 +31,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
_iTunesHttpClinet = new();
|
||||
}
|
||||
|
||||
public async Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null)
|
||||
public async Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token)
|
||||
{
|
||||
byte[]? result = null;
|
||||
|
||||
@@ -55,6 +56,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
foreach (string countryCode in new List<string>() { "us", "cn", "jp", "kr" })
|
||||
{
|
||||
result = await SearchiTunesAsync(artist, album, title, countryCode);
|
||||
if (token.IsCancellationRequested) return result;
|
||||
if (result != null) break;
|
||||
}
|
||||
break;
|
||||
|
||||
@@ -2,12 +2,13 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
||||
{
|
||||
public interface IAlbumArtSearchService
|
||||
{
|
||||
Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC = null);
|
||||
Task<byte[]?> SearchAsync(string mediaSessionId, string title, string artist, string album, byte[]? bytesFromSMTC, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,18 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
public interface ILyricsSearchService
|
||||
{
|
||||
Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
|
||||
Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Helper.BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Lyricify.Lyrics.Providers.Web.Kugou;
|
||||
@@ -11,6 +12,7 @@ using Lyricify.Lyrics.Searchers;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using NTextCat.Commons;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -87,89 +89,158 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<(string?, LyricsSearchProvider?)> SearchAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
public async Task<LyricsSearchResult> SearchSmartlyAsync(string mediaSessionId, string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
string overridenTitle = title;
|
||||
string overridenArtist = artist;
|
||||
|
||||
_logger.LogInformation("Searching img for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
|
||||
var found = _settingsService.AppSettings.MappedSongSearchQueries
|
||||
.Where(x => x.OriginalTitle == title && x.OriginalArtist == artist)
|
||||
.FirstOrDefault();
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
overridenTitle = found.MappedTitle;
|
||||
overridenArtist = found.MappedArtist;
|
||||
|
||||
_logger.LogInformation("Found mapped song search query: {MappedSongSearchQuery}", found);
|
||||
|
||||
var pureMusic = found.IsMarkedAsPureMusic;
|
||||
if (pureMusic)
|
||||
{
|
||||
lyricsSearchResult.Title = overridenTitle;
|
||||
lyricsSearchResult.Artist = overridenArtist;
|
||||
lyricsSearchResult.Raw = "[99:00.000]🎶🎶🎶";
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
var targetProvider = found.LyricsSearchProvider;
|
||||
if (targetProvider != null)
|
||||
{
|
||||
return await SearchSingleAsync(targetProvider.Value, overridenTitle, overridenArtist, album, durationMs, token);
|
||||
}
|
||||
}
|
||||
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
lyricsSearchResult = await SearchSingleAsync(provider.Provider, overridenTitle, overridenArtist, album, durationMs, token);
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
public async Task<List<LyricsSearchResult>> SearchAllAsync(string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Searching all lyrics for: {Title} - {Artist} (Album: {Album}, Duration: {DurationMs}ms)", title, artist, album, durationMs);
|
||||
var results = new List<LyricsSearchResult>();
|
||||
foreach (var provider in Enum.GetValues<LyricsSearchProvider>())
|
||||
{
|
||||
var searchResult = await SearchSingleAsync(provider, title, artist, album, durationMs, token);
|
||||
results.Add(searchResult);
|
||||
}
|
||||
return results;
|
||||
}
|
||||
|
||||
private async Task<LyricsSearchResult> SearchSingleAsync(LyricsSearchProvider provider, string title, string artist, string album, double durationMs, CancellationToken token)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = provider,
|
||||
};
|
||||
|
||||
try
|
||||
{
|
||||
foreach (var provider in _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == mediaSessionId).FirstOrDefault()?.LyricsSearchProvidersInfo ?? [])
|
||||
LyricsFormat lyricsFormat = provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.IsRemote())
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
var cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
continue;
|
||||
lyricsSearchResult.Raw = cachedLyrics;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
|
||||
string? cachedLyrics;
|
||||
LyricsFormat lyricsFormat = provider.Provider.GetLyricsFormat();
|
||||
|
||||
// Check cache first
|
||||
if (provider.Provider.IsRemote())
|
||||
if (provider.IsLocal())
|
||||
{
|
||||
if (provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
cachedLyrics = FileHelper.ReadLyricsCache(title, artist, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, provider.Provider);
|
||||
}
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
if (provider.Provider.IsLocal())
|
||||
{
|
||||
if (provider.Provider == LyricsSearchProvider.LocalMusicFile)
|
||||
{
|
||||
searchedLyrics = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
searchedLyrics = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
lyricsSearchResult = SearchEmbedded(title, artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
searchedLyrics = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
searchedLyrics = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
lyricsSearchResult = await SearchFile(title, artist, lyricsFormat);
|
||||
}
|
||||
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (provider)
|
||||
{
|
||||
if (provider.Provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, searchedLyrics, lyricsFormat, provider.Provider.GetCacheDirectory());
|
||||
}
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
lyricsSearchResult = await SearchLrcLibAsync(title, artist, album, (int)(durationMs / 1000));
|
||||
break;
|
||||
case LyricsSearchProvider.QQ:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.QQMusic);
|
||||
break;
|
||||
case LyricsSearchProvider.Kugou:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Kugou);
|
||||
break;
|
||||
case LyricsSearchProvider.Netease:
|
||||
lyricsSearchResult = await SearchQQNeteaseKugouAsync(title, artist, album, (int)durationMs, Searchers.Netease);
|
||||
break;
|
||||
case LyricsSearchProvider.AmllTtmlDb:
|
||||
lyricsSearchResult = await SearchAmllTtmlDbAsync(title, artist);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (searchedLyrics, provider.Provider);
|
||||
if (token.IsCancellationRequested)
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
if (lyricsSearchResult.IsFound)
|
||||
{
|
||||
if (provider.IsRemote())
|
||||
{
|
||||
FileHelper.WriteLyricsCache(title, artist, lyricsSearchResult.Raw!, lyricsFormat, provider.GetCacheDirectory());
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception) { }
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchFile(string title, string artist, LyricsFormat format)
|
||||
private async Task<LyricsSearchResult> SearchFile(string title, string artist, LyricsFormat format)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = format.ToLyricsSearchProvider(),
|
||||
};
|
||||
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
@@ -183,7 +254,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
string? raw = await File.ReadAllTextAsync(file, FileHelper.GetEncoding(file));
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
lyricsSearchResult.Raw = raw;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -193,11 +266,16 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private string? SearchEmbedded(string title, string artist)
|
||||
private LyricsSearchResult SearchEmbedded(string title, string artist)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = LyricsSearchProvider.LocalMusicFile,
|
||||
};
|
||||
|
||||
foreach (var folder in _settingsService.AppSettings.LocalMediaFolders)
|
||||
{
|
||||
if (Directory.Exists(folder.Path) && folder.IsEnabled)
|
||||
@@ -212,23 +290,32 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
var plain = TagLib.File.Create(file).Tag.Lyrics;
|
||||
if (!plain.IsNullOrEmpty())
|
||||
{
|
||||
return plain;
|
||||
lyricsSearchResult.Raw = plain;
|
||||
lyricsSearchResult.Title = track.Title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
private async Task<LyricsSearchResult> SearchAmllTtmlDbAsync(string title, string artist)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = LyricsSearchProvider.AmllTtmlDb,
|
||||
};
|
||||
|
||||
if (IsAmllTtmlDbIndexInvalid())
|
||||
{
|
||||
var downloadOk = await DownloadAmllTtmlDbIndexAsync();
|
||||
if (!downloadOk)
|
||||
return null;
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
|
||||
string? rawLyricFile = null;
|
||||
@@ -271,7 +358,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(rawLyricFile))
|
||||
return null;
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
// 下载歌词内容
|
||||
var url = $"{Constants.AmllTTmlDB.QueryPrefix}{rawLyricFile}";
|
||||
@@ -279,17 +368,29 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
{
|
||||
using var response = await _amllTtmlDbHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
return await response.Content.ReadAsStringAsync();
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
string lyrics = await response.Content.ReadAsStringAsync();
|
||||
|
||||
lyricsSearchResult.Raw = lyrics;
|
||||
lyricsSearchResult.Title = title;
|
||||
lyricsSearchResult.Artist = artist;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchLrcLibAsync(string title, string artist, string album, int duration)
|
||||
private async Task<LyricsSearchResult> SearchLrcLibAsync(string title, string artist, string album, int duration)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult
|
||||
{
|
||||
Provider = LyricsSearchProvider.LrcLib,
|
||||
};
|
||||
|
||||
// Build API query URL
|
||||
var url =
|
||||
$"https://lrclib.net/api/search?" +
|
||||
@@ -300,7 +401,9 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
using var response = await _lrcLibHttpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
{
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
@@ -308,22 +411,47 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
json,
|
||||
Serialization.SourceGenerationContext.Default.JsonElement
|
||||
);
|
||||
|
||||
string? original = null;
|
||||
string? searchedTitle = null;
|
||||
string? searchedArtist = null;
|
||||
|
||||
if (jArr.ValueKind == JsonValueKind.Array && jArr.GetArrayLength() > 0)
|
||||
{
|
||||
var first = jArr[0];
|
||||
var syncedLyrics = first.GetProperty("syncedLyrics").GetString();
|
||||
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
original = first.GetProperty("syncedLyrics").GetString();
|
||||
searchedTitle = first.GetProperty("trackName").GetString();
|
||||
searchedArtist = first.GetProperty("artistName").GetString();
|
||||
}
|
||||
|
||||
return null;
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Title = searchedTitle;
|
||||
lyricsSearchResult.Artist = searchedArtist;
|
||||
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
|
||||
private static async Task<string?> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
|
||||
private static async Task<LyricsSearchResult> SearchQQNeteaseKugouAsync(string title, string artist, string album, int durationMs, Searchers searchers)
|
||||
{
|
||||
var lyricsSearchResult = new LyricsSearchResult();
|
||||
|
||||
switch (searchers)
|
||||
{
|
||||
case Searchers.QQMusic:
|
||||
lyricsSearchResult.Provider = LyricsSearchProvider.QQ;
|
||||
break;
|
||||
case Searchers.Netease:
|
||||
lyricsSearchResult.Provider = LyricsSearchProvider.Netease;
|
||||
break;
|
||||
case Searchers.Kugou:
|
||||
lyricsSearchResult.Provider = LyricsSearchProvider.Kugou;
|
||||
break;
|
||||
case Searchers.Musixmatch:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
var result = await SearchersHelper.GetSearcher(searchers).SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
@@ -349,11 +477,15 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
PathHelper.QQTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return original;
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Title = qqResult.Title;
|
||||
lyricsSearchResult.Artist = qqResult.Artists.Join(" | ");
|
||||
}
|
||||
else if (result is NeteaseSearchResult neteaseResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
|
||||
var original = response?.Lrc?.Lyric;
|
||||
var translated = response?.Tlyric?.Lyric;
|
||||
if (!string.IsNullOrEmpty(translated))
|
||||
{
|
||||
@@ -365,21 +497,26 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
PathHelper.NeteaseTranslationCacheDirectory
|
||||
);
|
||||
}
|
||||
return response?.Lrc.Lyric;
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Title = neteaseResult.Title;
|
||||
lyricsSearchResult.Artist = neteaseResult.Artists.Join(" | ");
|
||||
}
|
||||
else if (result is KugouSearchResult kugouResult)
|
||||
{
|
||||
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
|
||||
string? original = null;
|
||||
if (response?.Candidates.FirstOrDefault() is SearchLyricsResponse.Candidate candidate)
|
||||
{
|
||||
return Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(
|
||||
candidate.Id,
|
||||
candidate.AccessKey
|
||||
);
|
||||
original = Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyrics(candidate.Id, candidate.AccessKey);
|
||||
}
|
||||
|
||||
lyricsSearchResult.Raw = original;
|
||||
lyricsSearchResult.Title = kugouResult.Title;
|
||||
lyricsSearchResult.Artist = kugouResult.Artists.Join(" | ");
|
||||
}
|
||||
|
||||
return null;
|
||||
return lyricsSearchResult;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
// 2025/6/23 by Zhe Fang
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using TagLib.Riff;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
@@ -12,7 +15,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
|
||||
event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
|
||||
event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
Task PlayAsync();
|
||||
@@ -26,5 +30,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
bool IsPlaying { get; }
|
||||
SongInfo? SongInfo { get; }
|
||||
TimeSpan Position { get; }
|
||||
|
||||
LyricsSearchProvider? LyricsSearchProvider { get; }
|
||||
TranslationSearchProvider? TranslationSearchProvider { get; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.InteropServices.WindowsRuntime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
public partial class MediaSessionsService : IMediaSessionsService
|
||||
{
|
||||
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
|
||||
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChanged;
|
||||
|
||||
private void UpdateAlbumArt()
|
||||
{
|
||||
_albumArtRefreshRunner.RunAsync(RefreshArtAlbum);
|
||||
}
|
||||
|
||||
private async Task RefreshArtAlbum(CancellationToken token)
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
{
|
||||
_logger.LogWarning("Cached song info is null, cannot update album art.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? bytes = await Task.Run(async () => await _albumArtSearchService.SearchAsync(
|
||||
SongInfo?.SourceAppUserModelId ?? "",
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo?.Album ?? string.Empty,
|
||||
_SMTCAlbumArtBytes,
|
||||
token
|
||||
), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
albumArtSwBitmap = SoftwareBitmap.Copy(albumArtSwBitmap);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtLightAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, false).FirstOrDefault();
|
||||
var albumArtDarkAccentColor = ImageHelper.GetAccentColorsFromByte(bytes, 1, true).FirstOrDefault();
|
||||
|
||||
AlbumArtChanged?.Invoke(this, new AlbumArtChangedEventArgs(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Vanara.PInvoke;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
public partial class MediaSessionsService : IMediaSessionsService
|
||||
{
|
||||
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
|
||||
private LatestOnlyTaskRunner _refreshTranslationRunner = new();
|
||||
|
||||
private int _langIndex = 0;
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
private LyricsData? CurrentLyricsData => _lyricsDataArr.ElementAtOrDefault(_langIndex);
|
||||
|
||||
public event EventHandler<LyricsChangedEventArgs>? LyricsChanged;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchProvider? LyricsSearchProvider { get; set; }
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TranslationSearchProvider? TranslationSearchProvider { get; set; }
|
||||
|
||||
[ObservableProperty] public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
private async Task RefreshTranslationAsync(CancellationToken token)
|
||||
{
|
||||
TranslationSearchProvider = null;
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
IsTranslating = true;
|
||||
|
||||
if (_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled)
|
||||
{
|
||||
await SetDisplayedAlongWithTranslationsAsync(token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogInformation("Translation is disabled, showing original lyrics only.");
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_langIndex = 0;
|
||||
}
|
||||
IsTranslating = false;
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
}
|
||||
|
||||
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
|
||||
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
|
||||
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
|
||||
if (originalText == null) return;
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
|
||||
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
{
|
||||
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
|
||||
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try get translation from itself first
|
||||
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
|
||||
if (found >= 0)
|
||||
{
|
||||
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
|
||||
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
|
||||
_langIndex = found;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator, 50);
|
||||
_langIndex = 0;
|
||||
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
|
||||
}
|
||||
}
|
||||
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
|
||||
{
|
||||
_logger.LogInformation("LibreTranslate is enabled, trying to translate lyrics...");
|
||||
string translated = string.Empty;
|
||||
try
|
||||
{
|
||||
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
if (translated == string.Empty) return;
|
||||
|
||||
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
|
||||
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
|
||||
_langIndex = _lyricsDataArr.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator);
|
||||
_langIndex = 0;
|
||||
}
|
||||
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshLyricsAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Refreshing lyrics...");
|
||||
|
||||
LyricsSearchProvider = null;
|
||||
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
|
||||
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
|
||||
if (SongInfo != null)
|
||||
{
|
||||
_logger.LogInformation("Searching lyrics for: Title={Title}, Artist={Artist}, Album={Album}, DurationMs={DurationMs}",
|
||||
SongInfo.Title, SongInfo.Artist, SongInfo.Album, SongInfo.DurationMs);
|
||||
|
||||
var lyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(
|
||||
SongInfo.SourceAppUserModelId ?? "",
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
token
|
||||
), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
LyricsSearchProvider = lyricsSearchResult?.Provider;
|
||||
|
||||
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsSearchResult?.IsFound, LyricsSearchProvider?.ToString() ?? "null");
|
||||
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsSearchResult?.Raw, (int?)SongInfo?.DurationMs);
|
||||
FillTranslationFromCache(LyricsSearchProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
|
||||
|
||||
// This ensures that original lyrics are always shown while waiting for translations
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
LyricsChanged?.Invoke(this, new LyricsChangedEventArgs(CurrentLyricsData));
|
||||
|
||||
UpdateTranslations();
|
||||
}
|
||||
|
||||
private void FillTranslationFromCache(LyricsSearchProvider? provider)
|
||||
{
|
||||
string? translationRaw = null;
|
||||
switch (provider)
|
||||
{
|
||||
case Enums.LyricsSearchProvider.QQ:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Kugou:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Netease:
|
||||
translationRaw = FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, PathHelper.NeteaseTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LrcLib:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.AmllTtmlDb:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalMusicFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalLrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalEslrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalTtmlFile:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (translationRaw != null)
|
||||
{
|
||||
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
|
||||
if (provider == Enums.LyricsSearchProvider.QQ)
|
||||
{
|
||||
foreach (var data in translationData)
|
||||
{
|
||||
foreach (var item in data.LyricsLines)
|
||||
{
|
||||
if (item.OriginalText == "//")
|
||||
{
|
||||
item.OriginalText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateLyrics()
|
||||
{
|
||||
_refreshLyricsRunner.RunAsync(RefreshLyricsAsync);
|
||||
}
|
||||
|
||||
private void UpdateTranslations()
|
||||
{
|
||||
_refreshTranslationRunner.RunAsync(RefreshTranslationAsync);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -8,7 +8,11 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -36,13 +40,19 @@ using WindowsMediaController;
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<List<string>>>,
|
||||
IRecipient<PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>>>
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsWindowMode>>,
|
||||
IRecipient<PropertyChangedMessage<List<string>>>
|
||||
{
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILogger<MediaSessionsService> _logger;
|
||||
private readonly ILyricsSearchService _lyrcsSearchService;
|
||||
private readonly ITranslateService _translateService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly ILiveStatesService _liveStatesService;
|
||||
private readonly ILogger<MediaSessionsService> _logger;
|
||||
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
private double _lxMusicDurationSeconds = 0;
|
||||
@@ -54,32 +64,58 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
|
||||
private readonly BackgroundTaskRunner _albumArtRefreshRunner = new();
|
||||
private readonly BackgroundTaskRunner _onAnyMediaPropertyChangedRunner = new();
|
||||
|
||||
private SongInfo? _cachedSongInfo;
|
||||
private byte[]? _SMTCAlbumArtBytes = null;
|
||||
|
||||
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
public event EventHandler<TimelineChangedEventArgs>? TimelineChanged;
|
||||
public event EventHandler<SongInfoChangedEventArgs>? SongInfoChanged;
|
||||
public event EventHandler<AlbumArtChangedEventArgs>? AlbumArtChangedChanged;
|
||||
public event EventHandler<MediaSourceProvidersInfoEventArgs>? MediaSourceProvidersInfoChanged;
|
||||
|
||||
public MediaSessionsService(ISettingsService settingsService, IAlbumArtSearchService albumArtSearchService)
|
||||
public bool IsPlaying => _cachedIsPlaying;
|
||||
public SongInfo? SongInfo => _cachedSongInfo;
|
||||
public TimeSpan Position => _cachedPosition;
|
||||
|
||||
public MediaSessionsService(
|
||||
ISettingsService settingsService,
|
||||
IAlbumArtSearchService albumArtSearchService,
|
||||
ILyricsSearchService musicSearchService,
|
||||
ILibWatcherService libWatcherService,
|
||||
ILiveStatesService liveStatesService,
|
||||
ITranslateService libreTranslateService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
_lyrcsSearchService = musicSearchService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_translateService = libreTranslateService;
|
||||
_liveStatesService = liveStatesService;
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<MediaSessionsService>>();
|
||||
|
||||
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
|
||||
|
||||
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
||||
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
|
||||
|
||||
_settingsService.AppSettings.MappedSongSearchQueries.CollectionChanged += MappedSongSearchQueries_CollectionChanged;
|
||||
_settingsService.AppSettings.MappedSongSearchQueries.ItemPropertyChanged += MappedSongSearchQueries_ItemPropertyChanged;
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
InitMediaManager();
|
||||
InitPlaybackShortcuts();
|
||||
}
|
||||
|
||||
private void MappedSongSearchQueries_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateLyrics();
|
||||
}
|
||||
|
||||
private void MappedSongSearchQueries_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateLyrics();
|
||||
}
|
||||
|
||||
private void InitPlaybackShortcuts()
|
||||
{
|
||||
UpdatePlayOrPauseSongShortcut();
|
||||
@@ -120,12 +156,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateAlbumArtRelated();
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
UpdateAlbumArtRelated();
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
}
|
||||
|
||||
private void MediaSourceProvidersInfo_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
@@ -133,22 +171,38 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MediaSourceProviderInfo.AlbumArtSearchProvidersInfo):
|
||||
UpdateAlbumArtRelated();
|
||||
UpdateAlbumArt();
|
||||
break;
|
||||
case nameof(MediaSourceProviderInfo.LyricsSearchProvidersInfo):
|
||||
UpdateLyrics();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsPlaying => _cachedIsPlaying;
|
||||
public SongInfo? SongInfo => _cachedSongInfo;
|
||||
public TimeSpan Position => _cachedPosition;
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
|
||||
{
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
}
|
||||
|
||||
public MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == desiredSession?.Id);
|
||||
}
|
||||
|
||||
private bool IsMediaSourceEnabled(string id)
|
||||
{
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
|
||||
}
|
||||
|
||||
private bool IsMediaSourceTimelineSyncEnabled(string id)
|
||||
{
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsTimelineSyncEnabled ?? true;
|
||||
}
|
||||
|
||||
private void InitMediaManager()
|
||||
{
|
||||
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
|
||||
@@ -159,18 +213,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
|
||||
|
||||
_mediaManager.Start();
|
||||
Task.Run(() =>
|
||||
{
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
|
||||
});
|
||||
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
|
||||
}
|
||||
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
SendFocusedMessagesAsync();
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
|
||||
@@ -178,9 +230,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
if (mediaSession != focusedSession) return;
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
@@ -192,61 +244,64 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedPosition = timelineProperties.Position;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
|
||||
});
|
||||
_cachedPosition = timelineProperties.Position;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != focusedSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
_cachedIsPlaying = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
_cachedIsPlaying = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
}
|
||||
|
||||
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
string id = mediaSession.Id;
|
||||
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != focusedSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(id))
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
_onAnyMediaPropertyChangedRunner.Run(async token =>
|
||||
string id = mediaSession.Id;
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(id))
|
||||
{
|
||||
_cachedSongInfo = null;
|
||||
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
@@ -256,42 +311,26 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
|
||||
_SMTCAlbumArtBytes = null;
|
||||
|
||||
UpdateAlbumArtRelated();
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
|
||||
if (currentMediaSourceProviderInfo?.ResetPositionOffsetOnSongChanged == true)
|
||||
{
|
||||
currentMediaSourceProviderInfo?.PositionOffset = 0;
|
||||
}
|
||||
});
|
||||
|
||||
_cachedSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties.Title,
|
||||
Artist = mediaProperties.Artist,
|
||||
Album = mediaProperties.AlbumTitle,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = id,
|
||||
};
|
||||
_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);
|
||||
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
|
||||
|
||||
_onAnyMediaPropertyChangedRunner.Run(async token =>
|
||||
{
|
||||
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
|
||||
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
|
||||
|
||||
@@ -312,18 +351,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
_SMTCAlbumArtBytes = null;
|
||||
}
|
||||
}
|
||||
|
||||
UpdateAlbumArtRelated();
|
||||
|
||||
if (!token.IsCancellationRequested)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
@@ -346,6 +379,30 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private MediaManager.MediaSession? GetCurrentSession()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (IsMediaSourceEnabled(focusedSession.Id))
|
||||
{
|
||||
return focusedSession;
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var session in _mediaManager.CurrentMediaSessions.Values)
|
||||
{
|
||||
if (IsMediaSourceEnabled(session.Id))
|
||||
{
|
||||
return session;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
@@ -378,61 +435,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private async Task SendFocusedMessagesAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession == null || focusedSession.ControlSession == null) return;
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession == null || desiredSession.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());
|
||||
}
|
||||
|
||||
private void UpdateAlbumArtRelated()
|
||||
{
|
||||
_albumArtRefreshRunner.Run(async (token) =>
|
||||
{
|
||||
if (_cachedSongInfo == null)
|
||||
{
|
||||
_logger.LogWarning("Cached song info is null, cannot update album art.");
|
||||
return;
|
||||
}
|
||||
|
||||
byte[]? bytes = await _albumArtSearchService.SearchAsync(
|
||||
SongInfo?.SourceAppUserModelId ?? "",
|
||||
_cachedSongInfo.Title,
|
||||
_cachedSongInfo.Artist,
|
||||
_cachedSongInfo?.Album ?? string.Empty,
|
||||
_SMTCAlbumArtBytes
|
||||
);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
if (bytes == null)
|
||||
{
|
||||
bytes = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
|
||||
bytes = ImageHelper.MakeSquareWithThemeColor(bytes);
|
||||
|
||||
using var stream = new InMemoryRandomAccessStream();
|
||||
await stream.WriteAsync(bytes.AsBuffer());
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var decoder = await BitmapDecoder.CreateAsync(stream);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
var albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
|
||||
token.ThrowIfCancellationRequested();
|
||||
|
||||
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(null, albumArtSwBitmap, albumArtLightAccentColor, albumArtDarkAccentColor));
|
||||
});
|
||||
|
||||
});
|
||||
var mediaProps = await desiredSession.ControlSession.TryGetMediaPropertiesAsync();
|
||||
if (desiredSession == null || desiredSession.ControlSession == null) return;
|
||||
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession.ControlSession.GetTimelineProperties());
|
||||
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
|
||||
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession.ControlSession.GetPlaybackInfo());
|
||||
}
|
||||
|
||||
private void StartSSE()
|
||||
@@ -467,7 +477,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void Sse_Disconnected(object sender, DisconnectEventArgs e)
|
||||
{
|
||||
Task.Run(async () =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
await Task.Delay(e.ReconnectDelay);
|
||||
if (_sse != null && !_sse.IsDisposed) _sse.Start();
|
||||
@@ -476,77 +486,76 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void Sse_MessageReceived(object sender, EventSourceMessageEventArgs e)
|
||||
{
|
||||
if (_cachedSongInfo?.SourceAppUserModelId == Constants.PlayerID.LXMusic)
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
if (_cachedSongInfo?.SourceAppUserModelId == Constants.PlayerID.LXMusic)
|
||||
{
|
||||
if (e.Event == "progress")
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
{
|
||||
_lxMusicPositionSeconds = data.GetDouble();
|
||||
if (e.Event == "progress")
|
||||
{
|
||||
_lxMusicPositionSeconds = data.GetDouble();
|
||||
}
|
||||
else if (e.Event == "duration")
|
||||
{
|
||||
_lxMusicDurationSeconds = data.GetDouble();
|
||||
}
|
||||
|
||||
if (IsMediaSourceTimelineSyncEnabled(Constants.PlayerID.LXMusic))
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.FromSeconds(_lxMusicPositionSeconds), TimeSpan.FromSeconds(_lxMusicDurationSeconds)));
|
||||
}
|
||||
}
|
||||
else if (e.Event == "duration")
|
||||
{
|
||||
_lxMusicDurationSeconds = data.GetDouble();
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(TimeSpan.FromSeconds(_lxMusicPositionSeconds), TimeSpan.FromSeconds(_lxMusicDurationSeconds)));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public async Task PlayAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryPlayAsync();
|
||||
await desiredSession.ControlSession?.TryPlayAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PauseAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryPauseAsync();
|
||||
await desiredSession.ControlSession?.TryPauseAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TrySkipPreviousAsync();
|
||||
await desiredSession.ControlSession?.TrySkipPreviousAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task NextAsync()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TrySkipNextAsync();
|
||||
await desiredSession.ControlSession?.TrySkipNextAsync();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task ChangePosition(double seconds)
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession != null)
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await focusedSession.ControlSession?.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
await desiredSession.ControlSession?.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
}
|
||||
}
|
||||
|
||||
public MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo()
|
||||
{
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.Where(x => x.Provider == _cachedSongInfo?.SourceAppUserModelId)?.FirstOrDefault();
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is MediaSourceProviderInfo)
|
||||
@@ -556,19 +565,23 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
}
|
||||
}
|
||||
else if (message.Sender is AlbumArtSearchProviderInfo)
|
||||
else if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(AlbumArtSearchProviderInfo.IsEnabled))
|
||||
if (message.PropertyName == nameof(TranslationSettings.IsLibreTranslateEnabled))
|
||||
{
|
||||
UpdateAlbumArtRelated();
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.IsTranslationEnabled))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.ShowTranslationOnly))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<FullyObservableCollection<AlbumArtSearchProviderInfo>> message)
|
||||
{
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<List<string>> message)
|
||||
{
|
||||
if (message.Sender is GeneralSettings)
|
||||
@@ -587,5 +600,39 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageIndex))
|
||||
{
|
||||
_logger.LogInformation("Target language index changed: {Index}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex);
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<string> message)
|
||||
{
|
||||
if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsWindowMode> message)
|
||||
{
|
||||
if (message.Sender is LiveStates)
|
||||
{
|
||||
if (message.PropertyName == nameof(LiveStates.CurrentLyricsWindowMode))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,6 +57,9 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
|
||||
AppSettings.LocalMediaFolders.CollectionChanged += AppSettings_CollectionChanged;
|
||||
AppSettings.LocalMediaFolders.ItemPropertyChanged += AppSettings_ItemPropertyChanged;
|
||||
|
||||
AppSettings.MappedSongSearchQueries.CollectionChanged += AppSettings_CollectionChanged;
|
||||
AppSettings.MappedSongSearchQueries.ItemPropertyChanged += AppSettings_ItemPropertyChanged;
|
||||
|
||||
AppSettings.Version = MetadataHelper.AppVersion;
|
||||
|
||||
EnsureMediaSourceProvidersInfo();
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
{
|
||||
public interface ITranslateService
|
||||
{
|
||||
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token);
|
||||
Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token);
|
||||
|
||||
int SearchTranslatedLyricsItself(List<LyricsData> lyricsDataArr);
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
_httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken? token)
|
||||
public async Task<string> TranslateTextAsync(string text, string targetLangCode, CancellationToken token)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
@@ -49,12 +49,10 @@ namespace BetterLyrics.WinUI3.Services.TranslateService
|
||||
new("q", text),
|
||||
new("source", originalLangCode),
|
||||
new("target", targetLangCode),
|
||||
]));
|
||||
token?.ThrowIfCancellationRequested();
|
||||
]), token);
|
||||
|
||||
response.EnsureSuccessStatusCode();
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
token?.ThrowIfCancellationRequested();
|
||||
var json = await response.Content.ReadAsStringAsync(token);
|
||||
|
||||
var result = System.Text.Json.JsonSerializer.Deserialize(json, SourceGenerationContext.Default.TranslateResponse);
|
||||
return result?.TranslatedText ?? string.Empty;
|
||||
|
||||
@@ -150,9 +150,6 @@
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Exit picture-in-picture mode</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>Cancel</value>
|
||||
</data>
|
||||
@@ -222,6 +219,9 @@
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>Lyrics provider</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Manually search lyrics</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>Lyrics style and effect quick settings</value>
|
||||
</data>
|
||||
@@ -252,6 +252,39 @@
|
||||
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>Translation provider</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
|
||||
<value>Artist</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>* Save changes take effect immediately, after which the track lyrics will be retrieved with mapping information and target lyrics; marking as pure music will directly return to pure music placeholder lyrics. Reset to retrieve by original data.</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMappedAs.Text" xml:space="preserve">
|
||||
<value>mapped as</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>Mark as pure music</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>Not found</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlReset.Content" xml:space="preserve">
|
||||
<value>Reset</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSaveChanges.Content" xml:space="preserve">
|
||||
<value>Save changes</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSearch.Content" xml:space="preserve">
|
||||
<value>Search</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSongInfoMapping.Header" xml:space="preserve">
|
||||
<value>Song info mapping</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
|
||||
<value>Target lyrics search provider</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
|
||||
<value>Title</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>Local .ESLRC files</value>
|
||||
</data>
|
||||
@@ -478,6 +511,9 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>Activate standard mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartPIPLyrics.Content" xml:space="preserve">
|
||||
<value>Start Picture-in-Picture Mode</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>When starting the app</value>
|
||||
</data>
|
||||
@@ -485,7 +521,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<value>Lyrics backdrop</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>Album Background Acrylic Effect Strength!</value>
|
||||
<value>Album Background Acrylic Effect Strength</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>Lyrics background</value>
|
||||
@@ -695,13 +731,13 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<value>Lyrics background</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>Album background layer ambiguity!</value>
|
||||
<value>Album background layer ambiguity</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>Album background layer opacity!</value>
|
||||
<value>Album background layer opacity</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundSpeed.Header" xml:space="preserve">
|
||||
<value>Album Background Layer Motion Rate!</value>
|
||||
<value>Album Background Layer Motion Rate</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>Font color (Non-current playback area)</value>
|
||||
@@ -779,7 +815,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<value>Glow effect</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>Highlight scope</value>
|
||||
<value>Original highlight range</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>Left</value>
|
||||
@@ -838,12 +874,18 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>Thin</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimeline.Header" xml:space="preserve">
|
||||
<value>Lyrics timeline sync</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>If the lyrics progress is jittery, try increasing this threshold; changing this value can cause lyrics synchronization to deviate</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>Lyrics timeline sync threshold</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationHighlight.Header" xml:space="preserve">
|
||||
<value>Translation Highlight</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
|
||||
<value>Source and translation separator</value>
|
||||
</data>
|
||||
@@ -934,17 +976,23 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>Scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDelay.Header" xml:space="preserve">
|
||||
<value>Tail line delay</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDuration.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation duration (Last line)</value>
|
||||
<value>Final line duration</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation duration (Current line)</value>
|
||||
<value>Current line duration</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation type</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
|
||||
<value>First line delay</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
|
||||
<value>Lyrics scrolling animation duration (First line)</value>
|
||||
<value>The duration of the first line</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>Test server</value>
|
||||
@@ -958,6 +1006,12 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>Settings manager</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>This hotkey was not successfully registered</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegSuccessInfo" xml:space="preserve">
|
||||
<value>This hotkey has been successfully registered</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>Current value: </value>
|
||||
</data>
|
||||
|
||||
@@ -150,9 +150,6 @@
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモード</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモードを終了します</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>キャンセル</value>
|
||||
</data>
|
||||
@@ -199,7 +196,7 @@
|
||||
<value>ブラウザの承認を完了してください</value>
|
||||
</data>
|
||||
<data name="LastFMRequestAuthTitle" xml:space="preserve">
|
||||
<value>BetterLyricsにLast.fmアカウントへのアクセスを許可してください!</value>
|
||||
<value>BetterLyricsにLast.fmアカウントへのアクセスを許可してください</value>
|
||||
</data>
|
||||
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
|
||||
<value>認可をキャンセルしました</value>
|
||||
@@ -208,7 +205,7 @@
|
||||
<value>ブラウザでキャンセル操作を完了してください</value>
|
||||
</data>
|
||||
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
|
||||
<value>Last.fmアカウントへのBetterLyricsアクセスを取り消します!</value>
|
||||
<value>Last.fmアカウントへのBetterLyricsアクセスを取り消します</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>リブレットランスレートからの翻訳のリクエストが失敗しました。設定またはネイティブリブレットランレート構成を確認してください</value>
|
||||
@@ -222,6 +219,9 @@
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌詞プロバイダー</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>手動で歌詞を検索します</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌詞スタイルと効果クイック設定</value>
|
||||
</data>
|
||||
@@ -252,6 +252,39 @@
|
||||
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>翻訳プロバイダー</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
|
||||
<value>アーティスト</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>*保存変更はすぐに有効になります。その後、トラックの歌詞がマッピング情報とターゲット歌詞で取得されます。純粋な音楽としてのマーキングは、純粋な音楽プレースホルダーの歌詞に直接戻ります。元のデータで取得するためにリセット。</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMappedAs.Text" xml:space="preserve">
|
||||
<value>としてマッピングされました</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>純粋な音楽としてマークしてください</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>見つかりませんでした</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlReset.Content" xml:space="preserve">
|
||||
<value>リセット</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSaveChanges.Content" xml:space="preserve">
|
||||
<value>変更を保存</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSearch.Content" xml:space="preserve">
|
||||
<value>検索する</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSongInfoMapping.Header" xml:space="preserve">
|
||||
<value>曲情報マッピング</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
|
||||
<value>ターゲット歌詞検索プロバイダー</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>ローカル.ESLRCファイル</value>
|
||||
</data>
|
||||
@@ -478,6 +511,9 @@
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>標準モードをアクティブにします</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartPIPLyrics.Content" xml:space="preserve">
|
||||
<value>ピクチャーインピクチャーモードを開始します</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>アプリを起動するとき</value>
|
||||
</data>
|
||||
@@ -485,7 +521,7 @@
|
||||
<value>歌詞の背景素材</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>アルバムの背景アクリル効果の強さ!</value>
|
||||
<value>アルバムの背景アクリル効果の強さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌詞の背景</value>
|
||||
@@ -695,13 +731,13 @@
|
||||
<value>歌詞の背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>アルバムの背景レイヤーの曖昧さ!</value>
|
||||
<value>アルバムの背景レイヤーの曖昧さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>アルバムの背景レイヤーの不透明度!</value>
|
||||
<value>アルバムの背景レイヤーの不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundSpeed.Header" xml:space="preserve">
|
||||
<value>アルバムの背景レイヤーのモーションレート!</value>
|
||||
<value>アルバムの背景レイヤーのモーションレート</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>フォントカラー(非電流再生エリア)</value>
|
||||
@@ -779,7 +815,7 @@
|
||||
<value>グロー効果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>ハイライトスコープ</value>
|
||||
<value>オリジナルのハイライト範囲</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>左</value>
|
||||
@@ -838,12 +874,18 @@
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>薄い</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimeline.Header" xml:space="preserve">
|
||||
<value>歌詞のタイムラインを同期</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>歌詞の進行が不安定な場合は、このしきい値を増やしてみてください。この値を変更すると、歌詞の同期が逸脱する可能性があります</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞タイムライン同期しきい値</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationHighlight.Header" xml:space="preserve">
|
||||
<value>翻訳ハイライト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
|
||||
<value>ソースおよび翻訳セパレーター</value>
|
||||
</data>
|
||||
@@ -934,17 +976,23 @@
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範囲</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDelay.Header" xml:space="preserve">
|
||||
<value>テールラインの遅延</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDuration.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーション期間(最終行)</value>
|
||||
<value>最終ライン期間</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーション期間(現在の行)</value>
|
||||
<value>現在のライン期間</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーションタイプ</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
|
||||
<value>最初の行の遅延</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
|
||||
<value>歌詞スクロールアニメーション期間(最初の行)</value>
|
||||
<value>最初の行の期間</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>テストサーバー</value>
|
||||
@@ -958,6 +1006,12 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>設定マネージャー</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>このホットキーは正常に登録されていません</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegSuccessInfo" xml:space="preserve">
|
||||
<value>このホットキーは正常に登録されています</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>現在の値: </value>
|
||||
</data>
|
||||
@@ -1043,6 +1097,6 @@
|
||||
<value>翻訳サーバーは設定されていません。最初に設定で構成してください</value>
|
||||
</data>
|
||||
<data name="TryRunMultipleInstance" xml:space="preserve">
|
||||
<value>BetterLyrics はすでに実行されています!</value>
|
||||
<value>BetterLyrics はすでに実行されています</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -150,9 +150,6 @@
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>사진 인당 모드</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Picture-in-Picture 모드 종료</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>취소</value>
|
||||
</data>
|
||||
@@ -199,7 +196,7 @@
|
||||
<value>브라우저에서 승인을 완료하십시오</value>
|
||||
</data>
|
||||
<data name="LastFMRequestAuthTitle" xml:space="preserve">
|
||||
<value>Last.fm 계정에 BetterLyrics 액세스 권한을 부여하세요!</value>
|
||||
<value>Last.fm 계정에 BetterLyrics 액세스 권한을 부여하세요</value>
|
||||
</data>
|
||||
<data name="LastFMRequestUnAuthConfirm" xml:space="preserve">
|
||||
<value>내 승인을 취소했습니다</value>
|
||||
@@ -208,7 +205,7 @@
|
||||
<value>브라우저에서 취소 작업을 완료하십시오</value>
|
||||
</data>
|
||||
<data name="LastFMRequestUnAuthTitle" xml:space="preserve">
|
||||
<value>Last.fm 계정에 대한 BetterLyrics 액세스를 취소하십시오!</value>
|
||||
<value>Last.fm 계정에 대한 BetterLyrics 액세스를 취소하십시오</value>
|
||||
</data>
|
||||
<data name="LibreTranslateFailed" xml:space="preserve">
|
||||
<value>LibreTranslate에서 번역 요청 실패, 설정 또는 기본 LibreTranslate 구성을 확인하십시오.</value>
|
||||
@@ -222,6 +219,9 @@
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>가사 제공자</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>가사를 수동으로 검색합니다</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>가사 스타일과 효과 빠른 설정</value>
|
||||
</data>
|
||||
@@ -252,6 +252,39 @@
|
||||
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>번역 제공자</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
|
||||
<value>아티스트</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>* 변경 사항 저장 변경은 즉시 적용되며, 그 후 트랙 가사는 맵핑 정보와 대상 가사로 검색됩니다. 순수한 음악으로 표시하면 순수한 음악 자리 표시 자 가사로 직접 돌아갑니다. 원래 데이터로 검색하도록 재설정하십시오.</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMappedAs.Text" xml:space="preserve">
|
||||
<value>로 매핑됨</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>순수한 음악으로 표시하세요</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>찾을 수 없음</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlReset.Content" xml:space="preserve">
|
||||
<value>재설정</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSaveChanges.Content" xml:space="preserve">
|
||||
<value>변경 사항 저장</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSearch.Content" xml:space="preserve">
|
||||
<value>검색</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSongInfoMapping.Header" xml:space="preserve">
|
||||
<value>노래 정보 매핑</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
|
||||
<value>가사 검색 공급자를 타겟팅하십시오</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
|
||||
<value>제목</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>로컬 .ESLRC 파일</value>
|
||||
</data>
|
||||
@@ -478,6 +511,9 @@
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>표준 모드를 활성화합니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartPIPLyrics.Content" xml:space="preserve">
|
||||
<value>Picture-in-Picture 모드를 시작하십시오</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>앱을 시작할 때</value>
|
||||
</data>
|
||||
@@ -485,7 +521,7 @@
|
||||
<value>가사 배경 자료</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>앨범 배경 아크릴 효과 강도!</value>
|
||||
<value>앨범 배경 아크릴 효과 강도</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>가사 배경</value>
|
||||
@@ -695,13 +731,13 @@
|
||||
<value>가사 배경</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>앨범 배경 레이어 모호성!</value>
|
||||
<value>앨범 배경 레이어 모호성</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>앨범 배경 레이어 불투명도!</value>
|
||||
<value>앨범 배경 레이어 불투명도</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundSpeed.Header" xml:space="preserve">
|
||||
<value>앨범 배경 레이어 모션 속도!</value>
|
||||
<value>앨범 배경 레이어 모션 속도</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>글꼴 색상 (비 전류 재생 영역)</value>
|
||||
@@ -779,7 +815,7 @@
|
||||
<value>글로우 효과</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>하이라이트 범위</value>
|
||||
<value>원래 하이라이트 범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>왼쪽</value>
|
||||
@@ -838,12 +874,18 @@
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>얇은</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimeline.Header" xml:space="preserve">
|
||||
<value>가사 타임라인 동기화</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>가사 진행 상황이 불안하다면이 임계 값을 높이십시오. 이 값을 변경하면 가사가 동기화 될 수 있습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>가사 타임 라인 동기화 임계 값</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationHighlight.Header" xml:space="preserve">
|
||||
<value>번역 하이라이트</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
|
||||
<value>소스 및 번역 분리기</value>
|
||||
</data>
|
||||
@@ -934,17 +976,23 @@
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>범위</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDelay.Header" xml:space="preserve">
|
||||
<value>테일 라인 지연</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDuration.Header" xml:space="preserve">
|
||||
<value>가사 스크롤 애니메이션 지속 시간 (마지막 줄)</value>
|
||||
<value>최종 라인 기간</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>가사 스크롤 애니메이션 지속 시간 (현재 줄)</value>
|
||||
<value>현재 라인 기간</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>가사 스크롤링 애니메이션 유형</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
|
||||
<value>첫 번째 줄 지연</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
|
||||
<value>가사 스크롤 애니메이션 지속 시간 (첫 번째 줄)</value>
|
||||
<value>첫 번째 줄의 기간</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>테스트 서버</value>
|
||||
@@ -958,6 +1006,12 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>설정 관리자</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>이 핫키는 성공적으로 등록되지 않았습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegSuccessInfo" xml:space="preserve">
|
||||
<value>이 핫키는 성공적으로 등록되었습니다</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>현재 가치 : </value>
|
||||
</data>
|
||||
|
||||
@@ -150,9 +150,6 @@
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>画中画模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出画中画模式</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>取消</value>
|
||||
</data>
|
||||
@@ -222,6 +219,9 @@
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌词来源</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>手动检索歌词</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌词样式与效果快捷设置</value>
|
||||
</data>
|
||||
@@ -252,6 +252,39 @@
|
||||
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>翻译来源</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
|
||||
<value>艺术家</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>* 保存更改立即生效,此后将以映射信息和目标歌词源检索该曲目歌词;标记为纯音乐将直接返回纯音乐占位歌词。重置以按原始数据检索。</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMappedAs.Text" xml:space="preserve">
|
||||
<value>映射为</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>标记为纯音乐</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>未找到</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlReset.Content" xml:space="preserve">
|
||||
<value>重置</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSaveChanges.Content" xml:space="preserve">
|
||||
<value>保存更改</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSearch.Content" xml:space="preserve">
|
||||
<value>搜索</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSongInfoMapping.Header" xml:space="preserve">
|
||||
<value>歌曲信息映射</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
|
||||
<value>目标歌词搜索提供商</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
|
||||
<value>标题</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>本地 .ESLRC 文件</value>
|
||||
</data>
|
||||
@@ -478,6 +511,9 @@
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>启动标准模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartPIPLyrics.Content" xml:space="preserve">
|
||||
<value>启动画中画模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>启动应用时</value>
|
||||
</data>
|
||||
@@ -779,7 +815,7 @@
|
||||
<value>辉光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>高亮显示范围</value>
|
||||
<value>原文高亮显示范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>靠左</value>
|
||||
@@ -838,12 +874,18 @@
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>极细</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimeline.Header" xml:space="preserve">
|
||||
<value>歌词时间轴同步</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>当歌词进度抖动时,请尝试增加该阈值;更改此值会导致歌词同步有偏差</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌词时间轴同步阈值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationHighlight.Header" xml:space="preserve">
|
||||
<value>翻译高亮</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
|
||||
<value>原文译文分隔符</value>
|
||||
</data>
|
||||
@@ -934,17 +976,23 @@
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDelay.Header" xml:space="preserve">
|
||||
<value>尾行延时</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDuration.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画持续时间(最后一行)</value>
|
||||
<value>尾行持续时间</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画持续时间(当前行)</value>
|
||||
<value>当前行持续时间</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画类型</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
|
||||
<value>首行延时</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
|
||||
<value>歌词滚动动画持续时间(第一行)</value>
|
||||
<value>首行持续时间</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>测试服务器</value>
|
||||
@@ -958,6 +1006,12 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>设置管理器</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>该热键未成功注册</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegSuccessInfo" xml:space="preserve">
|
||||
<value>该热键已成功注册</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>当前值: </value>
|
||||
</data>
|
||||
|
||||
@@ -150,9 +150,6 @@
|
||||
<data name="BaseWindowMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>畫中畫模式</value>
|
||||
</data>
|
||||
<data name="BaseWindowUnMiniFlyoutItem.Text" xml:space="preserve">
|
||||
<value>退出畫中畫模式</value>
|
||||
</data>
|
||||
<data name="Cancel" xml:space="preserve">
|
||||
<value>取消</value>
|
||||
</data>
|
||||
@@ -222,6 +219,9 @@
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌詞來源</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearchButtonToolTip.Content" xml:space="preserve">
|
||||
<value>手動檢索歌詞</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettingsButtonToolTip.Content" xml:space="preserve">
|
||||
<value>歌詞樣式與效果快捷設置</value>
|
||||
</data>
|
||||
@@ -252,6 +252,39 @@
|
||||
<data name="LyricsPageTranslationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>翻譯來源</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlArtist.Header" xml:space="preserve">
|
||||
<value>藝術家</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>* 保存更改立即生效,此後將以映射信息和目標歌詞源檢索該曲目歌詞;標記為純音樂將直接返回純音樂佔位歌詞。重置以按原始數據檢索。</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMappedAs.Text" xml:space="preserve">
|
||||
<value>映射爲</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>標記為純音樂</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>找不到</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlReset.Content" xml:space="preserve">
|
||||
<value>重設</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSaveChanges.Content" xml:space="preserve">
|
||||
<value>保存更改</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSearch.Content" xml:space="preserve">
|
||||
<value>搜索</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlSongInfoMapping.Header" xml:space="preserve">
|
||||
<value>歌曲信息映射</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTargetSearchProvider.Header" xml:space="preserve">
|
||||
<value>目標歌詞搜尋提供者</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlTitle.Header" xml:space="preserve">
|
||||
<value>標題</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderEslrcFile" xml:space="preserve">
|
||||
<value>本地 .ESLRC 文件</value>
|
||||
</data>
|
||||
@@ -478,6 +511,9 @@
|
||||
<data name="SettingsPageAutoStartInAppLyrics.Content" xml:space="preserve">
|
||||
<value>啟動標準模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartPIPLyrics.Content" xml:space="preserve">
|
||||
<value>啟動畫中畫模式</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStartWindow.Header" xml:space="preserve">
|
||||
<value>啟動應用程式時</value>
|
||||
</data>
|
||||
@@ -485,7 +521,7 @@
|
||||
<value>歌詞背景材質</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundAcrylicEffectAmount.Header" xml:space="preserve">
|
||||
<value>專輯背景壓克力效果強度!</value>
|
||||
<value>專輯背景壓克力效果強度</value>
|
||||
</data>
|
||||
<data name="SettingsPageBackgroundOverlay.Content" xml:space="preserve">
|
||||
<value>歌詞背景</value>
|
||||
@@ -695,13 +731,13 @@
|
||||
<value>歌詞背景</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundBlurAmount.Header" xml:space="preserve">
|
||||
<value>專輯背景層模糊度!</value>
|
||||
<value>專輯背景層模糊度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundOpacity.Header" xml:space="preserve">
|
||||
<value>專輯背景層不透明度!</value>
|
||||
<value>專輯背景層不透明度</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBackgroundSpeed.Header" xml:space="preserve">
|
||||
<value>專輯背景層運動速率!</value>
|
||||
<value>專輯背景層運動速率</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsBgFontColor.Header" xml:space="preserve">
|
||||
<value>字體顏色(非當前播放區域)</value>
|
||||
@@ -779,7 +815,7 @@
|
||||
<value>輝光效果</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsHighlightScope.Header" xml:space="preserve">
|
||||
<value>高亮顯示範圍</value>
|
||||
<value>原文高亮顯示範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsLeft.Content" xml:space="preserve">
|
||||
<value>靠左</value>
|
||||
@@ -838,12 +874,18 @@
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>極細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimeline.Header" xml:space="preserve">
|
||||
<value>歌詞時間軌同步閾值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Description" xml:space="preserve">
|
||||
<value>當歌詞進度抖動時,請嘗試增加該閾值;更改此值會導致歌詞同步偏差</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTimelineThreshold.Header" xml:space="preserve">
|
||||
<value>歌詞時間軌同步閾值</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationHighlight.Header" xml:space="preserve">
|
||||
<value>翻譯高亮</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsTranslationSeparator.Header" xml:space="preserve">
|
||||
<value>原文譯文分隔符</value>
|
||||
</data>
|
||||
@@ -934,17 +976,23 @@
|
||||
<data name="SettingsPageScope.Header" xml:space="preserve">
|
||||
<value>範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDelay.Header" xml:space="preserve">
|
||||
<value>尾行延時</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollBottomDuration.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫持續時間(最後一行)</value>
|
||||
<value>尾行持續時間</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollDuration.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫持續時間(當前行)</value>
|
||||
<value>當前行持續時間</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollEasing.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫類型</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDelay.Header" xml:space="preserve">
|
||||
<value>首行延時</value>
|
||||
</data>
|
||||
<data name="SettingsPageScrollTopDuration.Header" xml:space="preserve">
|
||||
<value>歌詞滾動動畫持續時間(第一行)</value>
|
||||
<value>首行持續時間</value>
|
||||
</data>
|
||||
<data name="SettingsPageServerTestButton.Content" xml:space="preserve">
|
||||
<value>測試服務器</value>
|
||||
@@ -958,6 +1006,12 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>設置管理器</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>該熱鍵未成功註冊</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegSuccessInfo" xml:space="preserve">
|
||||
<value>該熱鍵已成功註冊</value>
|
||||
</data>
|
||||
<data name="SettingsPageSliderPrefix.Text" xml:space="preserve">
|
||||
<value>目前值: </value>
|
||||
</data>
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LibWatcherService;
|
||||
using BetterLyrics.WinUI3.Services.LiveStatesService;
|
||||
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.LyricsRendererViewModel
|
||||
{
|
||||
public partial class LyricsRendererViewModel
|
||||
{
|
||||
public LyricsRendererViewModel(
|
||||
ISettingsService settingsService,
|
||||
IMediaSessionsService mediaSessionsService,
|
||||
ILyricsSearchService musicSearchService,
|
||||
ILibWatcherService libWatcherService,
|
||||
ITranslateService libreTranslateService,
|
||||
ILastFMService lastFMService,
|
||||
ILiveStatesService liveStatesService
|
||||
)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_lyrcsSearchService = musicSearchService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_translateService = libreTranslateService;
|
||||
_liveStatesService = liveStatesService;
|
||||
|
||||
_lastFMService = lastFMService;
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
|
||||
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
|
||||
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
||||
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
|
||||
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
_timelineSyncThreshold = 0;
|
||||
|
||||
_libWatcherService.MusicLibraryFilesChanged += LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
_mediaSessionsService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_mediaSessionsService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_mediaSessionsService.AlbumArtChangedChanged += PlaybackService_AlbumArtChangedChanged;
|
||||
_mediaSessionsService.TimelineChanged += PlaybackService_TimelineChanged;
|
||||
|
||||
IsPlaying = _mediaSessionsService.IsPlaying;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, Extensions.ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
|
||||
_refreshLyricsRunner.Run(async tokne =>
|
||||
{
|
||||
await RefreshLyricsAsync(tokne);
|
||||
});
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
_logger.LogInformation("Local lyrics folders changed, refreshing lyrics.");
|
||||
_refreshLyricsRunner.Run(async tokne =>
|
||||
{
|
||||
await RefreshLyricsAsync(tokne);
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaSourceProvidersInfo_ItemPropertyChanged(object? sender, Extensions.ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
switch (e.PropertyName)
|
||||
{
|
||||
case nameof(MediaSourceProviderInfo.LyricsSearchProvidersInfo):
|
||||
_logger.LogInformation("MediaSourceProviderInfo.LyricsSearchProvidersInfo changed, refreshing lyrics.");
|
||||
_refreshLyricsRunner.Run(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -63,9 +63,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
_drawFrameCount++;
|
||||
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine != null)
|
||||
{
|
||||
@@ -87,7 +85,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
$"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),
|
||||
@@ -206,9 +203,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private void DrawBlurredLyrics(ICanvasAnimatedControl control, CanvasDrawingSession ds)
|
||||
{
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
@@ -217,7 +212,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
|
||||
var textLayout = line.CanvasTextLayout;
|
||||
@@ -239,10 +234,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
using var combined = new CanvasCommandList(control);
|
||||
using var combinedDs = combined.CreateDrawingSession();
|
||||
|
||||
// Mock gradient blurred lyrics layer
|
||||
// 先铺一层带默认透明度的已经加了模糊效果的歌词作为最底层(背景歌词层次)
|
||||
// Current line will not be blurred
|
||||
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
|
||||
using var backgroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
|
||||
_liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
|
||||
using var backgroundEffect = CanvasHelper.CreateBackgroundEffect(line, backgroundFontEffect, _lyricsOpacityTransition.Value);
|
||||
combinedDs.DrawImage(backgroundEffect);
|
||||
@@ -257,16 +250,23 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.IsLyricsLineFadeEnabled);
|
||||
using var lineMask = CanvasHelper.CreateLineMask(control, line);
|
||||
|
||||
using var foregroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
|
||||
_liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsFontStrokeWidth, _bgFontColor);
|
||||
using var foregroundFontEffect = CanvasHelper.CreateFontEffect(line, control, _strokeFontColor,
|
||||
_liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsFontStrokeWidth, _fgFontColor);
|
||||
|
||||
using var effectLayer = new CanvasCommandList(control);
|
||||
using var effectLayerDs = effectLayer.CreateDrawingSession();
|
||||
if (line.OriginalText != line.DisplayedText && _liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsTranslationHighlightAmount != 0)
|
||||
{
|
||||
using var translationHighlightMask = CanvasHelper.CreateTranslationHighlightMask(control, line);
|
||||
using var foregroundTranslationHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, translationHighlightMask,
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsTranslationHighlightAmount / 100.0);
|
||||
effectLayerDs.DrawImage(foregroundTranslationHighlightEffect);
|
||||
}
|
||||
if (_liveStatesService.LiveStates.CurrentLyricsEffectSettings.IsLyricsShadowEnabled)
|
||||
{
|
||||
var shadowEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
|
||||
var shadowEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsShadowScope);
|
||||
using var foregroundShadowEffect = CanvasHelper.CreateForegroundShadowEffect(foregroundFontEffect, shadowEffectMask,
|
||||
using var foregroundShadowEffect = CanvasHelper.CreateForegroundShadowEffect(foregroundFontEffect, shadowEffectMask,
|
||||
_albumArtAccentColorTransition.Value, _liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsShadowAmount);
|
||||
effectLayerDs.DrawImage(foregroundShadowEffect);
|
||||
}
|
||||
@@ -278,10 +278,14 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsGlowEffectAmount);
|
||||
effectLayerDs.DrawImage(foregroundBlurEffect);
|
||||
}
|
||||
var highlightEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsHighlightScope);
|
||||
using var foregroundHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, highlightEffectMask);
|
||||
effectLayerDs.DrawImage(foregroundHighlightEffect);
|
||||
if (_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsHighlightAmount != 0)
|
||||
{
|
||||
var highlightEffectMask = CanvasHelper.GetAlphaMask(control, charMask, lineStartToCharMask, lineMask,
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsHighlightScope);
|
||||
using var foregroundHighlightEffect = CanvasHelper.CreateForegroundHighlightEffect(foregroundFontEffect, highlightEffectMask,
|
||||
_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsHighlightAmount / 100.0);
|
||||
effectLayerDs.DrawImage(foregroundHighlightEffect);
|
||||
}
|
||||
|
||||
combinedDs.DrawImage(new OpacityEffect
|
||||
{
|
||||
|
||||
@@ -53,22 +53,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
}
|
||||
}
|
||||
else if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.IsLibreTranslateEnabled))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.IsTranslationEnabled))
|
||||
{
|
||||
_logger.LogInformation("Translation enabled state changed: {IsEnabled}", _settingsService.AppSettings.TranslationSettings.IsTranslationEnabled);
|
||||
UpdateTranslations();
|
||||
}
|
||||
else if (message.PropertyName == nameof(TranslationSettings.ShowTranslationOnly))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsWindowViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsWindowViewModel.IsLyricsWindowLocked))
|
||||
@@ -89,18 +73,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
UpdateIsLastFMTrackEnabled();
|
||||
}
|
||||
}
|
||||
if (message.Sender is LyricsSearchProviderInfo)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSearchProviderInfo.IsEnabled))
|
||||
{
|
||||
_logger.LogInformation("LyricsSearchProviderInfo.IsEnabled changed, refreshing lyrics.");
|
||||
_refreshLyricsRunner.Run(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<Color> message)
|
||||
@@ -195,6 +167,14 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollTopDelay))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsEffectSettings.LyricsScrollBottomDelay))
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is LyricsStyleSettings)
|
||||
{
|
||||
@@ -211,14 +191,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
else if (message.Sender is TranslationSettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(TranslationSettings.SelectedTargetLanguageIndex))
|
||||
{
|
||||
_logger.LogInformation("Target language index changed: {Index}", _settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex);
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is MediaSourceProviderInfo)
|
||||
{
|
||||
if (message.PropertyName == nameof(MediaSourceProviderInfo.TimelineSyncThreshold))
|
||||
@@ -337,10 +309,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
else if (message.PropertyName == nameof(LyricsStyleSettings.LyricsTranslationSeparator))
|
||||
{
|
||||
UpdateTranslations();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -93,7 +93,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
if (_isCanvasWidthChanged)
|
||||
{
|
||||
if (_canvasWidth < 450)
|
||||
if (_canvasWidth < 500)
|
||||
{
|
||||
_lyricsLayoutOrientation = LyricsLayoutOrientation.Vertical;
|
||||
}
|
||||
@@ -336,9 +336,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
double y = 0;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
for (int i = 0; i < _currentLyricsData?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsDataArr[_langIndex].LyricsLines.ElementAtOrDefault(i);
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null)
|
||||
{
|
||||
@@ -366,7 +366,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
// Set _scrollOffsetY
|
||||
|
||||
LyricsLine? currentPlayingLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
LyricsLine? currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
@@ -374,7 +374,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
if (playingTextLayout == null) return;
|
||||
|
||||
double? targetYScrollOffset = -currentPlayingLine!.Position.Y + _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2.0;
|
||||
double? targetYScrollOffset = -currentPlayingLine!.Position.Y + _currentLyricsData?.LyricsLines[0].Position.Y - playingTextLayout.LayoutBounds.Height / 2.0;
|
||||
|
||||
if (!targetYScrollOffset.HasValue) return;
|
||||
|
||||
@@ -385,7 +385,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
var (startLineIndex, endLineIndex) = GetMaxLyricsLineIndexBoundaries();
|
||||
|
||||
var lines = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines;
|
||||
var lines = _currentLyricsData?.LyricsLines;
|
||||
if (lines == null || lines.Count == 0) return;
|
||||
|
||||
double offset = _canvasYScrollTransition.Value + _canvasHeight / 2;
|
||||
@@ -555,15 +555,13 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private void UpdateVisibleLinesProps(ICanvasAnimatedControl control)
|
||||
{
|
||||
var currentPlayingLine = _lyricsDataArr
|
||||
.ElementAtOrDefault(_langIndex)
|
||||
?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
var currentPlayingLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(_playingLineIndex);
|
||||
|
||||
if (currentPlayingLine == null) return;
|
||||
|
||||
for (int i = _startVisibleLineIndex; i <= _endVisibleLineIndex + 1; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
|
||||
if (line == null) continue;
|
||||
|
||||
@@ -591,25 +589,31 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
line.HighlightOpacityTransition.StartTransition(i == _playingLineIndex ? 1f : 0f);
|
||||
|
||||
double yScrollDuration;
|
||||
double yScrollDelay;
|
||||
|
||||
if (lineCountDelta < 0)
|
||||
{
|
||||
yScrollDuration =
|
||||
_canvasYScrollTransition.DurationSeconds +
|
||||
distanceFactor * (_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsScrollTopDuration / 1000.0 - _canvasYScrollTransition.DurationSeconds);
|
||||
yScrollDelay = distanceFactor * _liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsScrollTopDelay / 1000.0;
|
||||
}
|
||||
else if (lineCountDelta == 0)
|
||||
{
|
||||
yScrollDuration = _canvasYScrollTransition.DurationSeconds;
|
||||
yScrollDelay = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
yScrollDuration =
|
||||
_canvasYScrollTransition.DurationSeconds +
|
||||
distanceFactor * (_liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsScrollBottomDuration / 1000.0 - _canvasYScrollTransition.DurationSeconds);
|
||||
yScrollDelay = distanceFactor * _liveStatesService.LiveStates.CurrentLyricsEffectSettings.LyricsScrollBottomDelay / 1000.0;
|
||||
}
|
||||
|
||||
line.YOffsetTransition.SetEasingType(_canvasYScrollTransition.EasingType ?? EasingType.Linear);
|
||||
line.YOffsetTransition.SetDuration(yScrollDuration);
|
||||
line.YOffsetTransition.SetDelay(yScrollDelay);
|
||||
line.YOffsetTransition.StartTransition(_canvasTargetYScrollOffset, _isLayoutChanged);
|
||||
}
|
||||
|
||||
@@ -669,7 +673,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private void UpdateTimelineSyncThreshold()
|
||||
{
|
||||
_timelineSyncThreshold = _mediaSessionsService.GetCurrentMediaSourceProviderInfo()?.TimelineSyncThreshold ?? 0;
|
||||
var current = _mediaSessionsService.GetCurrentMediaSourceProviderInfo();
|
||||
_timelineSyncThreshold = current?.TimelineSyncThreshold ?? 0;
|
||||
}
|
||||
|
||||
private void UpdatePositionOffset()
|
||||
|
||||
@@ -14,6 +14,7 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslateService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Graphics.Canvas;
|
||||
@@ -55,8 +56,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
private int _drawFrameCount = 0;
|
||||
private int _displayedDrawFrameCount = 0;
|
||||
|
||||
private Queue<SoftwareBitmap?> _cachedAlbumArtSwBitmaps = [];
|
||||
|
||||
private SoftwareBitmap? _lastAlbumArtSwBitmap = null;
|
||||
private SoftwareBitmap? _albumArtSwBitmap = null;
|
||||
|
||||
@@ -87,21 +86,10 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private double _canvasTargetYScrollOffset = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial LyricsSearchProvider? LyricsSearchProvider { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial TranslationSearchProvider? TranslationSearchProvider { get; set; } = null;
|
||||
|
||||
private double _maxLyricsWidth = 0f;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILyricsSearchService _lyrcsSearchService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
private readonly ITranslateService _translateService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ILiveStatesService _liveStatesService;
|
||||
private readonly ILogger _logger;
|
||||
@@ -132,6 +120,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
private int _startVisibleLineIndex = -1;
|
||||
private int _endVisibleLineIndex = -1;
|
||||
|
||||
private LyricsData? _currentLyricsData;
|
||||
|
||||
private bool _isDebugOverlayEnabled = false;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -142,10 +132,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
|
||||
private bool _isLayoutChanged = true;
|
||||
|
||||
private int _langIndex = 0;
|
||||
|
||||
private List<LyricsData> _lyricsDataArr = [];
|
||||
|
||||
private int _timelineSyncThreshold = 0;
|
||||
|
||||
private CanvasTextFormat _lyricsTextFormat = new()
|
||||
@@ -178,14 +164,8 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
FontWeight = FontWeights.ExtraBlack,
|
||||
};
|
||||
|
||||
private BackgroundTaskRunner _refreshLyricsRunner = new();
|
||||
private BackgroundTaskRunner _showTranslationsRunner = new();
|
||||
|
||||
private LyricsLayoutOrientation _lyricsLayoutOrientation;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsTranslating { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; }
|
||||
|
||||
@@ -193,16 +173,53 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme ThemeTypeSent { get; set; }
|
||||
|
||||
public LyricsRendererViewModel(
|
||||
ISettingsService settingsService,
|
||||
IMediaSessionsService mediaSessionsService,
|
||||
ILastFMService lastFMService,
|
||||
ILiveStatesService liveStatesService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
_liveStatesService = liveStatesService;
|
||||
|
||||
_lastFMService = lastFMService;
|
||||
|
||||
_logger = Ioc.Default.GetRequiredService<ILogger<LyricsRendererViewModel>>();
|
||||
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
_titleTextFormat.HorizontalAlignment = _artistTextFormat.HorizontalAlignment = _settingsService.AppSettings.AlbumArtLayoutSettings.SongInfoAlignmentType.ToCanvasHorizontalAlignment();
|
||||
|
||||
_timelineSyncThreshold = 0;
|
||||
|
||||
_mediaSessionsService.IsPlayingChanged += MediaSessionsService_IsPlayingChanged;
|
||||
_mediaSessionsService.SongInfoChanged += MediaSessionsService_SongInfoChanged;
|
||||
_mediaSessionsService.AlbumArtChanged += MediaSessionsService_AlbumArtChangedChanged;
|
||||
_mediaSessionsService.LyricsChanged += MediaSessionsService_LyricsChanged;
|
||||
_mediaSessionsService.TimelineChanged += MediaSessionsService_TimelineChanged;
|
||||
|
||||
IsPlaying = _mediaSessionsService.IsPlaying;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
|
||||
private void MediaSessionsService_LyricsChanged(object? sender, LyricsChangedEventArgs e)
|
||||
{
|
||||
_currentLyricsData = e.LyricsData;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
var totalMs = TotalTime.TotalMilliseconds + _positionOffset.TotalMilliseconds;
|
||||
if (totalMs < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
|
||||
if (totalMs < _currentLyricsData?.LyricsLines.FirstOrDefault()?.StartMs) return 0;
|
||||
|
||||
for (int i = 0; i < _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.Count; i++)
|
||||
for (int i = 0; i < _currentLyricsData?.LyricsLines.Count; i++)
|
||||
{
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i);
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i);
|
||||
if (line == null) continue;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(i + 1);
|
||||
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(i + 1);
|
||||
if (nextLine != null && line.StartMs <= totalMs && totalMs < nextLine.StartMs)
|
||||
{
|
||||
return i;
|
||||
@@ -222,9 +239,9 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
charLength = 0;
|
||||
charProgress = 0f;
|
||||
|
||||
var line = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
var line = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex);
|
||||
if (line == null) return;
|
||||
var nextLine = _lyricsDataArr.ElementAtOrDefault(_langIndex)?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
var nextLine = _currentLyricsData?.LyricsLines.ElementAtOrDefault(lineIndex + 1);
|
||||
|
||||
int lineEndMs;
|
||||
if (line.EndMs != null) lineEndMs = line.EndMs.Value;
|
||||
@@ -309,31 +326,22 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
{
|
||||
if (
|
||||
SongInfo == null
|
||||
|| _lyricsDataArr.ElementAtOrDefault(_langIndex) == null
|
||||
|| _lyricsDataArr[_langIndex].LyricsLines.Count == 0
|
||||
|| _currentLyricsData == null
|
||||
|| _currentLyricsData.LyricsLines.Count == 0
|
||||
)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, _lyricsDataArr[_langIndex].LyricsLines.Count - 1);
|
||||
return new Tuple<int, int>(0, _currentLyricsData.LyricsLines.Count - 1);
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(object? sender, LibChangedEventArgs e)
|
||||
{
|
||||
_logger.LogInformation("Music library files changed: {ChangeType} {FilePath}, refreshing lyrics...", e.ChangeType, e.FilePath);
|
||||
_refreshLyricsRunner.Run(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
|
||||
private void MediaSessionsService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
|
||||
{
|
||||
IsPlaying = e.IsPlaying;
|
||||
}
|
||||
|
||||
private void PlaybackService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
|
||||
private void MediaSessionsService_TimelineChanged(object? sender, TimelineChangedEventArgs e)
|
||||
{
|
||||
var diff = Math.Abs(TotalTime.TotalMilliseconds - e.Position.TotalMilliseconds);
|
||||
if (diff >= _timelineSyncThreshold)
|
||||
@@ -352,7 +360,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
private void MediaSessionsService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
{
|
||||
SongInfo = e.SongInfo;
|
||||
|
||||
@@ -373,11 +381,6 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
_songInfoOpacityTransition.Reset(0f);
|
||||
_songInfoOpacityTransition.StartTransition(1f);
|
||||
|
||||
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
|
||||
_refreshLyricsRunner.Run(async token =>
|
||||
{
|
||||
await RefreshLyricsAsync(token);
|
||||
});
|
||||
TotalTime = TimeSpan.Zero;
|
||||
|
||||
// 处理 Last.fm 追踪
|
||||
@@ -386,222 +389,17 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private void PlaybackService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
|
||||
private void MediaSessionsService_AlbumArtChangedChanged(object? sender, AlbumArtChangedEventArgs e)
|
||||
{
|
||||
if (e.AlbumArtSwBitmap != _albumArtSwBitmap)
|
||||
{
|
||||
_cachedAlbumArtSwBitmaps.Append(_albumArtSwBitmap);
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_albumArtSwBitmap = e.AlbumArtSwBitmap;
|
||||
|
||||
_lastAlbumArtSwBitmap = _albumArtSwBitmap;
|
||||
_albumArtChanged = true;
|
||||
|
||||
if (_cachedAlbumArtSwBitmaps.Count > 2)
|
||||
{
|
||||
_cachedAlbumArtSwBitmaps.Dequeue()?.Dispose();
|
||||
}
|
||||
_albumArtLightAccentColor = e.AlbumArtLightAccentColor ?? Colors.Transparent;
|
||||
_albumArtDarkAccentColor = e.AlbumArtDarkAccentColor ?? Colors.Transparent;
|
||||
|
||||
_cachedAlbumArtSwBitmaps.Append(e.AlbumArtSwBitmap);
|
||||
|
||||
_albumArtSwBitmap = e.AlbumArtSwBitmap;
|
||||
|
||||
if (_cachedAlbumArtSwBitmaps.Count > 2)
|
||||
{
|
||||
_cachedAlbumArtSwBitmaps.Dequeue()?.Dispose();
|
||||
}
|
||||
|
||||
_albumArtChanged = true;
|
||||
|
||||
_albumArtLightAccentColor = e.AlbumArtLightAccentColor ?? Colors.Transparent;
|
||||
_albumArtDarkAccentColor = e.AlbumArtDarkAccentColor ?? Colors.Transparent;
|
||||
|
||||
UpdateColorConfig();
|
||||
}
|
||||
else
|
||||
{
|
||||
e.AlbumArtSwBitmap?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTranslations()
|
||||
{
|
||||
TranslationSearchProvider = null;
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_isLayoutChanged = true;
|
||||
|
||||
IsTranslating = true;
|
||||
if (_settingsService.AppSettings.TranslationSettings.IsTranslationEnabled)
|
||||
{
|
||||
_refreshLyricsRunner.Run(async token =>
|
||||
{
|
||||
await SetDisplayedAlongWithTranslationsAsync(token);
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr.ElementAtOrDefault(0)?.SetDisplayedTextInOriginalText();
|
||||
_langIndex = 0;
|
||||
IsTranslating = false;
|
||||
_isLayoutChanged = true;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task SetDisplayedAlongWithTranslationsAsync(CancellationToken token)
|
||||
{
|
||||
_logger.LogInformation("Showing translation for lyrics...");
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[_settingsService.AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
|
||||
_logger.LogInformation("Target language code: {TargetLangCode}", targetLangCode);
|
||||
string? originalText = _lyricsDataArr.FirstOrDefault()?.WrappedOriginalText;
|
||||
if (originalText == null) return;
|
||||
|
||||
string? originalLangCode = LanguageHelper.DetectLanguageCode(originalText);
|
||||
_logger.LogInformation("Original language code: {OriginalLangCode}", originalLangCode ?? "null");
|
||||
|
||||
if (originalLangCode == targetLangCode)
|
||||
{
|
||||
_logger.LogInformation("Original lyrics already in target language: {TargetLangCode}", targetLangCode);
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Try get translation from itself first
|
||||
int found = _translateService.SearchTranslatedLyricsItself(_lyricsDataArr);
|
||||
if (found >= 0)
|
||||
{
|
||||
_logger.LogInformation("Found translation in lyrics data at index {FoundIndex}", found);
|
||||
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[found].SetDisplayedTextInOriginalText();
|
||||
_langIndex = found;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(_lyricsDataArr[found], _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator, 50);
|
||||
_langIndex = 0;
|
||||
}
|
||||
TranslationSearchProvider = LyricsSearchProvider.ToTranslationSearchProvider();
|
||||
}
|
||||
else if (_settingsService.AppSettings.TranslationSettings.IsLibreTranslateEnabled)
|
||||
{
|
||||
_logger.LogInformation("LibreTranslate is enabled, trying to translate lyrics...");
|
||||
string translated = string.Empty;
|
||||
try
|
||||
{
|
||||
translated = await _translateService.TranslateTextAsync(originalText, targetLangCode, token);
|
||||
if (translated == string.Empty) return;
|
||||
|
||||
if (_settingsService.AppSettings.TranslationSettings.ShowTranslationOnly)
|
||||
{
|
||||
_lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
|
||||
_lyricsDataArr[^1].SetDisplayedTextInOriginalText();
|
||||
_langIndex = _lyricsDataArr.Count - 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated, _liveStatesService.LiveStates.CurrentLyricsStyleSettings.LyricsTranslationSeparator);
|
||||
_langIndex = 0;
|
||||
}
|
||||
TranslationSearchProvider = Enums.TranslationSearchProvider.LibreTranslate;
|
||||
token.ThrowIfCancellationRequested();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("LibreTranslateFailed")!, Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshLyricsAsync(CancellationToken token)
|
||||
{
|
||||
LyricsSearchProvider = null;
|
||||
_logger.LogInformation("Refreshing lyrics...");
|
||||
|
||||
_lyricsDataArr = [LyricsData.GetLoadingPlaceholder()];
|
||||
_isLayoutChanged = true;
|
||||
|
||||
string? lyricsRaw = null;
|
||||
LyricsSearchProvider? lyricsSearchProvider = null;
|
||||
|
||||
if (SongInfo != null)
|
||||
{
|
||||
_logger.LogInformation("Searching lyrics for: Title={Title}, Artist={Artist}, Album={Album}, DurationMs={DurationMs}",
|
||||
SongInfo.Title, SongInfo.Artist, SongInfo.Album, SongInfo.DurationMs);
|
||||
(lyricsRaw, lyricsSearchProvider) = await _lyrcsSearchService.SearchAsync(
|
||||
SongInfo.SourceAppUserModelId ?? "",
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album ?? "",
|
||||
SongInfo.DurationMs ?? 0,
|
||||
token
|
||||
);
|
||||
_logger.LogInformation("Lyrics was found? {Found}, Provider: {LyricsSearchProvider}", lyricsRaw != null, lyricsSearchProvider?.ToString() ?? "null");
|
||||
LyricsSearchProvider = lyricsSearchProvider;
|
||||
token.ThrowIfCancellationRequested();
|
||||
_lyricsDataArr = new LyricsParser().Parse(lyricsRaw, (int?)SongInfo?.DurationMs);
|
||||
FillTranslationFromCache(LyricsSearchProvider);
|
||||
}
|
||||
else
|
||||
{
|
||||
_logger.LogWarning("SongInfo is null, cannot search lyrics.");
|
||||
}
|
||||
|
||||
_logger.LogInformation("Parsed lyrics: {MultiLangLyricsCount} languages", _lyricsDataArr.Count);
|
||||
|
||||
// This ensures that original lyrics are always shown while waiting for translations
|
||||
_lyricsDataArr[0].SetDisplayedTextInOriginalText();
|
||||
_isLayoutChanged = true;
|
||||
|
||||
UpdateTranslations();
|
||||
}
|
||||
|
||||
private void FillTranslationFromCache(LyricsSearchProvider? provider)
|
||||
{
|
||||
string? translationRaw = null;
|
||||
switch (provider)
|
||||
{
|
||||
case Enums.LyricsSearchProvider.QQ:
|
||||
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.QQTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Kugou:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.Netease:
|
||||
translationRaw = Helper.FileHelper.ReadLyricsCache(SongInfo!.Title, SongInfo.Artist, LyricsFormat.Lrc, Helper.PathHelper.NeteaseTranslationCacheDirectory);
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LrcLib:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.AmllTtmlDb:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalMusicFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalLrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalEslrcFile:
|
||||
break;
|
||||
case Enums.LyricsSearchProvider.LocalTtmlFile:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
if (translationRaw != null)
|
||||
{
|
||||
var translationData = new LyricsParser().Parse(translationRaw, (int?)SongInfo?.DurationMs);
|
||||
if (provider == Enums.LyricsSearchProvider.QQ)
|
||||
{
|
||||
foreach (var data in translationData)
|
||||
{
|
||||
foreach (var item in data.LyricsLines)
|
||||
{
|
||||
if (item.OriginalText == "//")
|
||||
{
|
||||
item.OriginalText = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_lyricsDataArr = _lyricsDataArr.Concat(translationData).ToList();
|
||||
}
|
||||
UpdateColorConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsSearchControlViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ILyricsSearchService _lyricsSearchService;
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private LatestOnlyTaskRunner _lyricsSearchRunner = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AppSettings AppSettings { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<LyricsSearchResult> LyricsSearchResults { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchResult? SelectedLyricsSearchResult { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsData? LyricsData { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsLine? SelectedLyricsLine { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial MappedSongSearchQuery? MappedSongSearchQuery { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsSearching { get; set; } = false;
|
||||
|
||||
public LyricsSearchControlViewModel(ILyricsSearchService lyricsSearchService, IMediaSessionsService mediaSessionsService, ISettingsService settingsService)
|
||||
{
|
||||
_lyricsSearchService = lyricsSearchService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
_settingsService = settingsService;
|
||||
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
_mediaSessionsService.SongInfoChanged += MediaSessionsService_SongInfoChanged;
|
||||
|
||||
InitMappedSongSearchQuery();
|
||||
}
|
||||
|
||||
private void MediaSessionsService_SongInfoChanged(object? sender, Events.SongInfoChangedEventArgs e)
|
||||
{
|
||||
InitMappedSongSearchQuery();
|
||||
}
|
||||
|
||||
private void InitMappedSongSearchQuery()
|
||||
{
|
||||
LyricsSearchResults.Clear();
|
||||
LyricsData = null;
|
||||
if (_mediaSessionsService.SongInfo != null)
|
||||
{
|
||||
var found = GetMappedSongSearchQueryFromSettings();
|
||||
if (found == null)
|
||||
{
|
||||
MappedSongSearchQuery = new MappedSongSearchQuery
|
||||
{
|
||||
OriginalTitle = _mediaSessionsService.SongInfo.Title,
|
||||
OriginalArtist = _mediaSessionsService.SongInfo.Artist,
|
||||
MappedTitle = _mediaSessionsService.SongInfo.Title,
|
||||
MappedArtist = _mediaSessionsService.SongInfo.Artist,
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
MappedSongSearchQuery = found.Clone();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private MappedSongSearchQuery? GetMappedSongSearchQueryFromSettings()
|
||||
{
|
||||
if (_mediaSessionsService.SongInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var found = AppSettings.MappedSongSearchQueries
|
||||
.Where(x => x.OriginalTitle == _mediaSessionsService.SongInfo.Title && x.OriginalArtist == _mediaSessionsService.SongInfo.Artist).FirstOrDefault();
|
||||
return found;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Search()
|
||||
{
|
||||
if (MappedSongSearchQuery == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
IsSearching = true;
|
||||
LyricsSearchResults.Clear();
|
||||
MappedSongSearchQuery.LyricsSearchProvider = null;
|
||||
_ = _lyricsSearchRunner.RunAsync(async (token) =>
|
||||
{
|
||||
LyricsSearchResults = [..await Task.Run(async () =>
|
||||
{
|
||||
return await _lyricsSearchService.SearchAllAsync(
|
||||
MappedSongSearchQuery.MappedTitle,
|
||||
MappedSongSearchQuery.MappedArtist,
|
||||
_mediaSessionsService.SongInfo?.Album ?? "",
|
||||
_mediaSessionsService.SongInfo?.DurationMs ?? 0, token);
|
||||
}, token)];
|
||||
IsSearching = false;
|
||||
});
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Save()
|
||||
{
|
||||
if (MappedSongSearchQuery == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var existing = GetMappedSongSearchQueryFromSettings();
|
||||
if (existing != null)
|
||||
{
|
||||
AppSettings.MappedSongSearchQueries.Remove(existing);
|
||||
}
|
||||
AppSettings.MappedSongSearchQueries.Add(MappedSongSearchQuery);
|
||||
MappedSongSearchQuery = MappedSongSearchQuery.Clone();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void Reset()
|
||||
{
|
||||
var existing = GetMappedSongSearchQueryFromSettings();
|
||||
if (existing != null)
|
||||
{
|
||||
AppSettings.MappedSongSearchQueries.Remove(existing);
|
||||
}
|
||||
InitMappedSongSearchQuery();
|
||||
SelectedLyricsSearchResult = null;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetMappedTitle()
|
||||
{
|
||||
MappedSongSearchQuery?.MappedTitle = MappedSongSearchQuery?.OriginalTitle ?? string.Empty;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ResetMappedArtist()
|
||||
{
|
||||
MappedSongSearchQuery?.MappedArtist = MappedSongSearchQuery?.OriginalArtist ?? string.Empty;
|
||||
}
|
||||
|
||||
partial void OnSelectedLyricsSearchResultChanged(LyricsSearchResult? value)
|
||||
{
|
||||
MappedSongSearchQuery?.LyricsSearchProvider = value?.Provider;
|
||||
if (value?.Raw != null)
|
||||
{
|
||||
var lyricsParser = new LyricsParser();
|
||||
var lyricsDataArr = lyricsParser.Parse(value?.Raw, (int?)_mediaSessionsService.SongInfo?.DurationMs);
|
||||
LyricsData = lyricsDataArr.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
LyricsData = null;
|
||||
}
|
||||
}
|
||||
|
||||
partial void OnSelectedLyricsLineChanged(LyricsLine? value)
|
||||
{
|
||||
if (value?.StartMs == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
_mediaSessionsService.ChangePosition(value.StartMs / 1000.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -120,6 +120,7 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void UpdateDockOrDesktopWindow()
|
||||
{
|
||||
|
||||
var window = WindowHelper.GetWindowByWindowType<LyricsWindow>();
|
||||
if (window == null) return;
|
||||
|
||||
@@ -137,7 +138,7 @@ namespace BetterLyrics.WinUI3
|
||||
}
|
||||
else
|
||||
{
|
||||
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode)
|
||||
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode)
|
||||
{
|
||||
DockModeHelper.UpdateAppBarHeight(hwnd, _dockMonitorDeviceName, _dockWindowHeight, _dockPlacement);
|
||||
}
|
||||
@@ -217,8 +218,7 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void UpdateDesktopLockShortcut()
|
||||
{
|
||||
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DesktopLock);
|
||||
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DesktopLock,
|
||||
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.DesktopLock,
|
||||
_settingsService.AppSettings.DesktopModeSettings.LockShortcut,
|
||||
() =>
|
||||
{
|
||||
@@ -232,8 +232,7 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void UpdateDesktopToggleShortcut()
|
||||
{
|
||||
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DesktopToggle);
|
||||
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DesktopToggle,
|
||||
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.DesktopToggle,
|
||||
_settingsService.AppSettings.DesktopModeSettings.ToggleShortcut,
|
||||
() =>
|
||||
{
|
||||
@@ -248,8 +247,7 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void UpdateDockToggleShortcut()
|
||||
{
|
||||
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.DockToggle);
|
||||
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.DockToggle,
|
||||
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.DockToggle,
|
||||
_settingsService.AppSettings.DockModeSettings.ToggleShortcut,
|
||||
() =>
|
||||
{
|
||||
@@ -264,8 +262,7 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
private void UpdatePictureInPictureToggleShortcut()
|
||||
{
|
||||
GlobalHotKeyHelper.UnregisterHotKey<LyricsWindow>(ShortcutID.PictureInPictureToggle);
|
||||
GlobalHotKeyHelper.RegisterHotKey<LyricsWindow>(ShortcutID.PictureInPictureToggle,
|
||||
GlobalHotKeyHelper.UpdateHotKey<LyricsWindow>(ShortcutID.PictureInPictureToggle,
|
||||
_settingsService.AppSettings.PictureInPictureModeSettings.ToggleShortcut,
|
||||
() =>
|
||||
{
|
||||
@@ -462,6 +459,7 @@ namespace BetterLyrics.WinUI3
|
||||
if (LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.PictureInPictureMode)
|
||||
{
|
||||
window.AppWindow.SetPresenter(AppWindowPresenterKind.CompactOverlay);
|
||||
window.AppWindow.Move(AppSettings.PictureInPictureModeSettings.WindowPosition.ToPointInt32());
|
||||
SetPIPModeTitleBarControlsStatus();
|
||||
}
|
||||
else
|
||||
|
||||
@@ -80,6 +80,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsLastFMAuthenticated = _lastFMService.IsAuthenticated;
|
||||
LastFMUser = _lastFMService.User;
|
||||
|
||||
LyricsSearchProvider = _mediaSessionsService.LyricsSearchProvider;
|
||||
TranslationSearchProvider = _mediaSessionsService.TranslationSearchProvider;
|
||||
|
||||
SelectedMediaSourceProvider = AppSettings.MediaSourceProvidersInfo.FirstOrDefault();
|
||||
}
|
||||
|
||||
@@ -121,7 +124,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
try
|
||||
{
|
||||
string targetLangCode = LanguageHelper.SupportedTargetLanguages[AppSettings.TranslationSettings.SelectedTargetLanguageIndex].Code;
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, null);
|
||||
string result = await _libreTranslateService.TranslateTextAsync("Hello, world!", targetLangCode, new System.Threading.CancellationToken());
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("SettingsPageServerTestSuccessInfo"), InfoBarSeverity.Success);
|
||||
@@ -183,9 +186,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsSearchProvider?> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
|
||||
if (message.Sender is MediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.LyricsSearchProvider))
|
||||
if (message.PropertyName == nameof(MediaSessionsService.LyricsSearchProvider))
|
||||
{
|
||||
LyricsSearchProvider = message.NewValue;
|
||||
}
|
||||
@@ -194,9 +197,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public void Receive(PropertyChangedMessage<TranslationSearchProvider?> message)
|
||||
{
|
||||
if (message.Sender is LyricsRendererViewModel.LyricsRendererViewModel)
|
||||
if (message.Sender is MediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsRendererViewModel.TranslationSearchProvider))
|
||||
if (message.PropertyName == nameof(MediaSessionsService.TranslationSearchProvider))
|
||||
{
|
||||
TranslationSearchProvider = message.NewValue;
|
||||
}
|
||||
|
||||
@@ -80,14 +80,17 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
var file = await picker.PickSingleFileAsync();
|
||||
|
||||
var succeed = _settingsService.ImportSettings(file.Path);
|
||||
if (succeed)
|
||||
if (file != null)
|
||||
{
|
||||
WindowHelper.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("ImportSettingsFailed") ?? "");
|
||||
var succeed = _settingsService.ImportSettings(file.Path);
|
||||
if (succeed)
|
||||
{
|
||||
WindowHelper.RestartApp();
|
||||
}
|
||||
else
|
||||
{
|
||||
App.Current.SettingsWindowNotificationPanel?.Notify(App.ResourceLoader?.GetString("ImportSettingsFailed") ?? "");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -237,7 +237,25 @@
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Lyrics & Translation -->
|
||||
<Button
|
||||
Click="LyricsSearchShortcutButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<ToolTip x:Uid="LyricsPageLyricsSearchButtonToolTip" />
|
||||
</ToolTipService.ToolTip>
|
||||
<Button.ContextFlyout>
|
||||
<Flyout
|
||||
x:Name="LyricsSearchFlyout"
|
||||
Closed="LyricsSearchFlyout_Closed"
|
||||
FlyoutPresenterStyle="{StaticResource FlyoutPageStyle}"
|
||||
Placement="Right"
|
||||
ShouldConstrainToRootBounds="False" />
|
||||
</Button.ContextFlyout>
|
||||
</Button>
|
||||
|
||||
<!-- Playback shortcut settings -->
|
||||
<Button
|
||||
Click="PlaybackSettingsShortcutButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
@@ -279,9 +297,14 @@
|
||||
Maximum="{x:Bind ViewModel.SongDurationSeconds, Mode=OneWay}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
Tapped="TimelineSliderOverlay_Tapped"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.TimelinePositionSeconds, Mode=OneWay}" />
|
||||
<Grid
|
||||
Height="32"
|
||||
Margin="0,-32,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
PointerPressed="TimelineSliderOverlay_PointerPressed" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
@@ -315,7 +338,7 @@
|
||||
<Flyout x:Name="BottomCommandFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style TargetType="FlyoutPresenter">
|
||||
<Setter Property="MinWidth" Value="450" />
|
||||
<Setter Property="MinWidth" Value="500" />
|
||||
<Setter Property="MinHeight" Value="100" />
|
||||
<Setter Property="CornerRadius" Value="12" />
|
||||
</Style>
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
if (e.NewSize.Width < 450 || e.NewSize.Height < 100)
|
||||
if (e.NewSize.Width < 500 || e.NewSize.Height < 100)
|
||||
{
|
||||
if (BottomCommandGrid.Children.Count != 0)
|
||||
{
|
||||
@@ -110,11 +110,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
_mediaSessionsService.ChangePosition(TimelineSlider.Value);
|
||||
}
|
||||
|
||||
private void PlaybackSettingsFlyout_Closed(object sender, object e)
|
||||
{
|
||||
PlaybackSettingsFlyout.Content = null;
|
||||
@@ -134,5 +129,28 @@ namespace BetterLyrics.WinUI3.Views
|
||||
};
|
||||
LyricsSettingsFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
}
|
||||
|
||||
private void LyricsSearchShortcutButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
LyricsSearchFlyout.Content = new LyricsSearchControl
|
||||
{
|
||||
MaxHeight = 500,
|
||||
MaxWidth = 850,
|
||||
};
|
||||
LyricsSearchFlyout.ShowAt(BottomRightCommandStackPanel);
|
||||
}
|
||||
|
||||
private void LyricsSearchFlyout_Closed(object sender, object e)
|
||||
{
|
||||
LyricsSearchFlyout.Content = null;
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_PointerPressed(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
var grid = (Grid)sender;
|
||||
var pos = e.GetCurrentPoint(grid).Position;
|
||||
var ratio = pos.X / grid.ActualWidth;
|
||||
_mediaSessionsService.ChangePosition(TimelineSlider.Maximum * ratio);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,6 +20,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly WindowMessageMonitor _wmm;
|
||||
private bool _autoSelectLyricsModeOnRunning = true;
|
||||
|
||||
public LyricsWindow()
|
||||
{
|
||||
@@ -75,8 +76,17 @@ namespace BetterLyrics.WinUI3.Views
|
||||
switch (type!)
|
||||
{
|
||||
case LyricsWindowMode.StandardMode:
|
||||
AppWindow.MoveAndResize(_settingsService.AppSettings.StandardModeSettings.WindowBounds.ToRectInt32());
|
||||
ViewModel.SetStandardModeTitleBarControlsStatus();
|
||||
if (_settingsService.AppSettings.StandardModeSettings.IsMaximized)
|
||||
{
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ô<EFBFBD><C3B4>ڴ<EFBFBD>С<EFBFBD>Ա<EFBFBD><D4B1>˳<EFBFBD><CBB3><EFBFBD><EFBFBD><EFBFBD>
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ܶ<EFBFBD><DCB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ļ<EFBFBD><C4BB>ԵӰ<D4B5><D3B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
|
||||
this.Maximize();
|
||||
}
|
||||
else
|
||||
{
|
||||
AppWindow.MoveAndResize(_settingsService.AppSettings.StandardModeSettings.WindowBounds.ToRectInt32());
|
||||
}
|
||||
break;
|
||||
case LyricsWindowMode.DockMode:
|
||||
ViewModel.ToggleDockMode();
|
||||
@@ -84,9 +94,13 @@ namespace BetterLyrics.WinUI3.Views
|
||||
case LyricsWindowMode.DesktopMode:
|
||||
ViewModel.ToggleDesktopMode();
|
||||
break;
|
||||
case LyricsWindowMode.PictureInPictureMode:
|
||||
ViewModel.TogglePictureInPictureMode();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
_autoSelectLyricsModeOnRunning = false;
|
||||
}
|
||||
|
||||
private void AOTFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
@@ -96,6 +110,8 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void AppWindow_Changed(AppWindow sender, AppWindowChangedEventArgs args)
|
||||
{
|
||||
if (_autoSelectLyricsModeOnRunning) return;
|
||||
|
||||
if (args.DidPositionChange || args.DidSizeChange)
|
||||
{
|
||||
var size = AppWindow.Size;
|
||||
@@ -107,14 +123,28 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ViewModel.LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DesktopMode)
|
||||
switch (ViewModel.LiveStates.CurrentLyricsWindowMode)
|
||||
{
|
||||
_settingsService.AppSettings.DesktopModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
|
||||
}
|
||||
else if (ViewModel.LiveStates.CurrentLyricsWindowMode == LyricsWindowMode.DockMode) { }
|
||||
else
|
||||
{
|
||||
_settingsService.AppSettings.StandardModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
|
||||
case LyricsWindowMode.StandardMode:
|
||||
if (AppWindow.Presenter is OverlappedPresenter overlappedPresenter)
|
||||
{
|
||||
_settingsService.AppSettings.StandardModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
|
||||
_settingsService.AppSettings.StandardModeSettings.IsMaximized = overlappedPresenter.State == OverlappedPresenterState.Maximized;
|
||||
}
|
||||
break;
|
||||
case LyricsWindowMode.DockMode:
|
||||
break;
|
||||
case LyricsWindowMode.DesktopMode:
|
||||
_settingsService.AppSettings.DesktopModeSettings.WindowBounds = new Windows.Foundation.Rect(rect.X, rect.Y, size.Width, size.Height);
|
||||
break;
|
||||
case LyricsWindowMode.PictureInPictureMode:
|
||||
if (AppWindow.Presenter is CompactOverlayPresenter compactOverlayPresenter)
|
||||
{
|
||||
_settingsService.AppSettings.PictureInPictureModeSettings.WindowPosition = new Windows.Foundation.Point(rect.X, rect.Y);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
|
||||
<!-- Lyrics background -->
|
||||
<controls:Case Value="Background">
|
||||
<uc:LyricsBavkgroundSettingsControl />
|
||||
<uc:LyricsBackgroundSettingsControl />
|
||||
</controls:Case>
|
||||
|
||||
<!-- Album art area style -->
|
||||
|
||||
@@ -163,7 +163,7 @@ WinUI 3とWin2Dで構築された動的歌詞表示ツール — ローカル再
|
||||
|
||||
## デモ
|
||||
|
||||
Bilibiliで紹介動画を見る(2025年7月7日アップロード):[こちら](https://www.bilibili.com/video/BV1zjGjzfEXh)
|
||||
Bilibiliで紹介動画を見る(2025年8月18日アップロード):[こちら](https://www.bilibili.com/video/BV1yLYtzQEME/)
|
||||
|
||||
## 今すぐ試す
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ WinUI 3와 Win2D로 제작된 동적 가사 디스플레이 도구 — 로컬
|
||||
|
||||
## 데모
|
||||
|
||||
Bilibili에서 소개 영상을 시청하세요(2025년 7월 7일 업로드): [여기서 보기](https://www.bilibili.com/video/BV1zjGjzfEXh)
|
||||
Bilibili에서 소개 영상을 시청하세요(2025년 8월 18일 업로드): [여기서 보기](https://www.bilibili.com/video/BV1yLYtzQEME/)
|
||||
|
||||
## 지금 사용해보세요
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ Check out the article: [BetterLyrics – An immersive and smooth lyrics display
|
||||
|
||||
## Demonstration
|
||||
|
||||
Watch our introduction video (uploaded on 7 July 2025) on Bilibili [here](https://www.bilibili.com/video/BV1zjGjzfEXh).
|
||||
Watch our introduction video (uploaded on 18 Aug 2025) on Bilibili [here](https://www.bilibili.com/video/BV1yLYtzQEME/).
|
||||
|
||||
## Try it now
|
||||
|
||||
|
||||
@@ -167,7 +167,7 @@ BetterLyrics
|
||||
|
||||
## 演示
|
||||
|
||||
在 B 站观看我们的介绍视频(2025 年 7 月 7 日上传):[点此观看](https://www.bilibili.com/video/BV1zjGjzfEXh)
|
||||
在 B 站观看我们的介绍视频(2025 年 8 月 18 日上传):[点此观看](https://www.bilibili.com/video/BV1yLYtzQEME/)
|
||||
|
||||
## 立即体验
|
||||
|
||||
|
||||
@@ -163,7 +163,7 @@ BetterLyrics
|
||||
|
||||
## 演示
|
||||
|
||||
在 B 站觀看我們的介紹影片(2025 年 7 月 7 日上傳):[點此觀看](https://www.bilibili.com/video/BV1zjGjzfEXh)
|
||||
在 B 站觀看我們的介紹影片(2025 年 8 月 18 日上傳):[點此觀看](https://www.bilibili.com/video/BV1yLYtzQEME/)
|
||||
|
||||
## 立即體驗
|
||||
|
||||
|
||||
Reference in New Issue
Block a user