Compare commits
57 Commits
db8627c684
...
v1.2.243.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a13bb6e8e4 | ||
|
|
0b436c1ea9 | ||
|
|
5d332fdfc6 | ||
|
|
572d2cd8ba | ||
|
|
1e5a95c55e | ||
|
|
18ce6d3a57 | ||
|
|
427aed6857 | ||
|
|
ebfa484a2e | ||
|
|
3ef9d81bea | ||
|
|
e999d07834 | ||
|
|
838b8de94f | ||
|
|
b3059dbeb1 | ||
|
|
6fea88a6a1 | ||
|
|
abca9ae5fb | ||
|
|
a062897e1a | ||
|
|
8b4748df1b | ||
|
|
1df5ea6bab | ||
|
|
c576635af2 | ||
|
|
c8590202ec | ||
|
|
2dc8b1283f | ||
|
|
c482edea0f | ||
|
|
315722252c | ||
|
|
32ba453264 | ||
|
|
d4902329bb | ||
|
|
83aee8948b | ||
|
|
1f9fab3228 | ||
|
|
7a3a659dfc | ||
|
|
a14afd3eb5 | ||
|
|
c2af7f3186 | ||
|
|
cd026dd2bf | ||
|
|
4bc1a9975d | ||
|
|
07eecf0930 | ||
|
|
35fba5abb0 | ||
|
|
03ef231a3f | ||
|
|
f41879f4e5 | ||
|
|
bda7510ed6 | ||
|
|
5ec8c7c61f | ||
|
|
7e6bd9dade | ||
|
|
56244cb793 | ||
|
|
cb5f70ab55 | ||
|
|
3cc018bb1f | ||
|
|
c517d2b008 | ||
|
|
e79f2a0223 | ||
|
|
39122b9147 | ||
|
|
accbdc1806 | ||
|
|
de014d1ad7 | ||
|
|
cc2ce5f8cf | ||
|
|
2a2d80436e | ||
|
|
ce3f79f35c | ||
|
|
12e6000cb3 | ||
|
|
c1dc684411 | ||
|
|
69ea2cb495 | ||
|
|
e2ee03c4be | ||
|
|
c6fe33d6ae | ||
|
|
7744e145fa | ||
|
|
0284b1de81 | ||
|
|
108c2cd34b |
@@ -12,7 +12,7 @@
|
||||
<Identity
|
||||
Name="37412.BetterLyrics"
|
||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||
Version="1.2.232.0" />
|
||||
Version="1.2.243.0" />
|
||||
|
||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
x:Class="BetterLyrics.WinUI3.App"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:converter="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:converters="using:CommunityToolkit.WinUI.Converters"
|
||||
xmlns:globalization="using:Windows.Globalization"
|
||||
xmlns:local="using:BetterLyrics.WinUI3"
|
||||
xmlns:media="using:CommunityToolkit.WinUI.Media">
|
||||
@@ -13,8 +11,14 @@
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<!-- Merged dictionaries here -->
|
||||
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" />
|
||||
|
||||
<ResourceDictionary Source="ms-appx:///CommunityToolkit.WinUI.Controls.Segmented/Segmented/Segmented.xaml" />
|
||||
<ResourceDictionary Source="ms-appx:///DevWinUI.Controls/Themes/Generic.xaml" />
|
||||
|
||||
<ResourceDictionary Source="/Styles/Converters.xaml" />
|
||||
<ResourceDictionary Source="/Styles/InteractiveListViewHeaderStyle.xaml" />
|
||||
<ResourceDictionary Source="/Styles/GhostSliderStyle.xaml" />
|
||||
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
|
||||
<!-- Theme -->
|
||||
@@ -42,46 +46,6 @@
|
||||
<ExponentialEase x:Key="EaseOut" EasingMode="EaseOut" />
|
||||
<ExponentialEase x:Key="EaseIn" EasingMode="EaseIn" />
|
||||
|
||||
<!-- Converter -->
|
||||
<converter:EnumToIntConverter x:Key="EnumToIntConverter" />
|
||||
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converter:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<converter:TransliterationSearchProviderToDisplayNameConverter x:Key="TransliterationSearchProviderToDisplayNameConverter" />
|
||||
<converter:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<converter:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<converter:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
|
||||
<converter:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
|
||||
<converter:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
|
||||
<converter:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
|
||||
<converter:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
|
||||
<converter:BoolToPartialOpacityConverter x:Key="BoolToPartialOpacityConverter" />
|
||||
<converter:BoolNegationToOpacityConverter x:Key="BoolNegationToOpacityConverter" />
|
||||
<converter:RectToMarginConverter x:Key="RectToMarginConverter" />
|
||||
<converter:LanguageCodeToDisplayedNameConverter x:Key="LanguageCodeToDisplayedNameConverter" />
|
||||
<converter:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
|
||||
<converter:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
|
||||
<converter:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
|
||||
<converter:IntToBoolConverter x:Key="IntToBoolConverter" />
|
||||
<converter:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
|
||||
<converter:IntToDoubleConverter x:Key="IntToDoubleConverter" />
|
||||
<converter:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
|
||||
<converter:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
|
||||
<converter:LyricsFontWeightToFontWeightConverter x:Key="LyricsFontWeightToFontWeightConverter" />
|
||||
<converter:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
|
||||
<converter:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
|
||||
<converter:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
|
||||
<converter:FileSourceTypeToIconConverter x:Key="FileSourceTypeToIconConverter" />
|
||||
<converter:PathToImageConverter x:Key="PathToImageConverter" />
|
||||
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
<converters:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
||||
|
||||
<x:Double x:Key="SettingsCardSpacing">4</x:Double>
|
||||
|
||||
<!-- Style -->
|
||||
@@ -109,7 +73,7 @@
|
||||
</Style>
|
||||
<Style
|
||||
x:Key="TitleBarToggleButtonStyle"
|
||||
BasedOn="{StaticResource ToggleButtonRevealStyle}"
|
||||
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="VerticalAlignment" Value="Top" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
@@ -117,7 +81,10 @@
|
||||
<Setter Property="Padding" Value="14,6,14,9" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
</Style>
|
||||
<Style x:Key="GhostToggleButtonStyle" TargetType="ToggleButton">
|
||||
<Style
|
||||
x:Key="GhostToggleButtonStyle"
|
||||
BasedOn="{StaticResource DefaultToggleButtonStyle}"
|
||||
TargetType="ToggleButton">
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" />
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
@@ -131,190 +98,6 @@
|
||||
<Setter Property="CornerRadius" Value="6" />
|
||||
</Style>
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Height="2"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="24"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
<Style
|
||||
x:Key="ListViewStretchedItemContainerStyle"
|
||||
BasedOn="{StaticResource DefaultListViewItemStyle}"
|
||||
@@ -358,10 +141,6 @@
|
||||
<Setter Property="ScrollViewer.HorizontalScrollBarVisibility" Value="Hidden" />
|
||||
</Style>
|
||||
|
||||
<StaticResource x:Key="ToggleButtonBackgroundChecked" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPointerOver" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
<StaticResource x:Key="ToggleButtonBackgroundCheckedPressed" ResourceKey="TextFillColorPrimaryBrush" />
|
||||
|
||||
<!-- Dimensions -->
|
||||
|
||||
<!-- Fonts -->
|
||||
|
||||
@@ -4,12 +4,13 @@ using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
@@ -19,7 +20,6 @@ using Microsoft.Data.Sqlite;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching; // 关键:用于线程调度
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.Windows.AppLifecycle; // 关键:App生命周期管理
|
||||
using Serilog;
|
||||
@@ -260,6 +260,7 @@ namespace BetterLyrics.WinUI3
|
||||
{
|
||||
Log.Logger = new LoggerConfiguration()
|
||||
.MinimumLevel.Is(Serilog.Events.LogEventLevel.Verbose)
|
||||
.MinimumLevel.Override("Microsoft.EntityFrameworkCore", Serilog.Events.LogEventLevel.Error)
|
||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||
.CreateLogger();
|
||||
|
||||
@@ -278,7 +279,8 @@ namespace BetterLyrics.WinUI3
|
||||
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
||||
.AddSingleton<ISMTCService, SMTCService>()
|
||||
.AddSingleton<IGSMTCService, GSMTCService>()
|
||||
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
|
||||
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
|
||||
.AddSingleton<ITranslationService, TranslationService>()
|
||||
@@ -304,6 +306,7 @@ namespace BetterLyrics.WinUI3
|
||||
.AddSingleton<AboutControlViewModel>()
|
||||
.AddSingleton<MusicGalleryWindowViewModel>()
|
||||
.AddSingleton<StatsDashboardControlViewModel>()
|
||||
.AddSingleton<PlayQueueViewModel>()
|
||||
|
||||
.AddTransient<NowPlayingWindowViewModel>()
|
||||
.AddTransient<NowPlayingPageViewModel>()
|
||||
|
||||
|
After Width: | Height: | Size: 593 KiB |
@@ -42,13 +42,17 @@
|
||||
<None Remove="Controls\LyricsWindowSwitchControl.xaml" />
|
||||
<None Remove="Controls\MediaSettingsControl.xaml" />
|
||||
<None Remove="Controls\NowPlayingBar.xaml" />
|
||||
<None Remove="Controls\PatronControl.xaml" />
|
||||
<None Remove="Controls\PlaybackSettingsControl.xaml" />
|
||||
<None Remove="Controls\PlayQueue.xaml" />
|
||||
<None Remove="Controls\PropertyRow.xaml" />
|
||||
<None Remove="Controls\RemoteServerConfigControl.xaml" />
|
||||
<None Remove="Controls\ShortcutTextBox.xaml" />
|
||||
<None Remove="Controls\StatsDashboardControl.xaml" />
|
||||
<None Remove="Controls\SystemTray.xaml" />
|
||||
<None Remove="Controls\WindowSettingsControl.xaml" />
|
||||
<None Remove="Styles\GhostSliderStyle.xaml" />
|
||||
<None Remove="Styles\InteractiveListViewHeaderStyle.xaml" />
|
||||
<None Remove="Views\LyricsSearchWindow.xaml" />
|
||||
<None Remove="Views\LyricsWindowSwitchWindow.xaml" />
|
||||
<None Remove="Views\MusicGalleryPage.xaml" />
|
||||
@@ -86,8 +90,8 @@
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.4.1" />
|
||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
||||
<PackageReference Include="LiveChartsCore.SkiaSharpView.WinUI" Version="2.0.0-rc6.1" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
|
||||
@@ -243,6 +247,9 @@
|
||||
<Content Update="Assets\Question.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\RevolvingHearts.gif">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
<Content Update="Assets\SaltPlayerForWindows.png">
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
@@ -259,6 +266,26 @@
|
||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||
</Content>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\PatronControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\GhostSliderStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\Converters.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\PlayQueue.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Controls\StatsDashboardControl.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
@@ -409,6 +436,11 @@
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="Styles\InteractiveListViewHeaderStyle.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<!-- Publish Properties -->
|
||||
<PropertyGroup>
|
||||
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun>
|
||||
|
||||
@@ -6,5 +6,6 @@ namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static readonly TimeSpan DebounceTimeout = TimeSpan.FromMilliseconds(250);
|
||||
public static readonly TimeSpan AnimationDuration = TimeSpan.FromMilliseconds(350);
|
||||
public static readonly TimeSpan WaitingDuration = TimeSpan.FromMilliseconds(300);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,12 +57,12 @@
|
||||
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="GitHub" NavigateUri="{x:Bind const:Link.BetterLyricsGitHub}" />
|
||||
<HyperlinkButton x:Uid="UserGuide" NavigateUri="{x:Bind const:Link.UserGuide}" />
|
||||
<HyperlinkButton x:Uid="PrivacyPolicy" NavigateUri="{x:Bind const:Link.PrivacyPolicy}" />
|
||||
<HyperlinkButton x:Uid="TermsOfService" NavigateUri="{x:Bind const:Link.TermsOfService}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
|
||||
</dev:SettingsCard>
|
||||
@@ -70,18 +70,18 @@
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageFeedback" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="QQ 反馈交流群" NavigateUri="{x:Bind const:Link.QQGroup}" />
|
||||
<HyperlinkButton Content="Discord" NavigateUri="{x:Bind const:Link.Discord}" />
|
||||
<HyperlinkButton Content="Telegram" NavigateUri="{x:Bind const:Link.Telegram}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageDonation" />
|
||||
<StackPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="Buy Me a Coffee" NavigateUri="{x:Bind const:Link.BuyMeACoffee}" />
|
||||
<HyperlinkButton Content="PayPal" NavigateUri="{x:Bind const:Link.PayPal}" />
|
||||
<HyperlinkButton
|
||||
@@ -117,7 +117,7 @@
|
||||
</HyperlinkButton.ContextFlyout>
|
||||
</HyperlinkButton>
|
||||
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
|
||||
</StackPanel>
|
||||
</dev:WrapPanel>
|
||||
<Grid ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -136,15 +136,6 @@
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageThanksList">
|
||||
<Button
|
||||
Click="Patron_Click"
|
||||
Content="{ui:FontIcon FontSize=16,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource AccentButtonStyle}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
</dev:SettingsExpander.Items>
|
||||
<dev:SettingsExpander.ItemsFooter>
|
||||
<InfoBar
|
||||
@@ -157,6 +148,96 @@
|
||||
</dev:SettingsExpander.ItemsFooter>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsExpander x:Uid="SettingsPageThanksList">
|
||||
<dev:SettingsExpander.HeaderIcon>
|
||||
<ImageIcon Source="ms-appx:///Assets/RevolvingHearts.gif" />
|
||||
</dev:SettingsExpander.HeaderIcon>
|
||||
<dev:SettingsExpander.Items>
|
||||
|
||||
<!-- 贡献者 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<RichTextBlock>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
<Run Text="(Code)" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="jayfunc" NavigateUri="https://github.com/jayfunc" />
|
||||
<HyperlinkButton Content="Raspberry-Monster" NavigateUri="https://github.com/Raspberry-Monster" />
|
||||
<HyperlinkButton Content="ZHider" NavigateUri="https://github.com/ZHider" />
|
||||
<HyperlinkButton Content="kusutori" NavigateUri="https://github.com/kusutori" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 贡献者 (Translator) -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<RichTextBlock>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
<Run Text="(Translator)" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="borcolasky" NavigateUri="https://crowdin.com/profile/borcolasky" />
|
||||
<HyperlinkButton Content="SuHeAndZl" NavigateUri="https://crowdin.com/profile/SuHeAndZl" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 赞助 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SettingsPagePatrons" />
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<uc:PatronControl Date="Dec 3, 2025" PatronName="YE" />
|
||||
<uc:PatronControl Date="Nov 23, 2025" PatronName="**玄" />
|
||||
<uc:PatronControl Date="Nov 21, 2025" PatronName="**智" />
|
||||
<uc:PatronControl Date="Nov 17, 2025" PatronName="*鹤" />
|
||||
<uc:PatronControl Date="Nov 2, 2025" PatronName="借过" />
|
||||
<uc:PatronControl Date="Aug 28, 2025" PatronName="**华" />
|
||||
<TextBlock x:Uid="SettingsPageUserWhoPurchased" Margin="12,8" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 特别鸣谢 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageSpecialThanks" />
|
||||
<TextBlock x:Uid="SettingsPageYouNowUsing" Margin="0,8" />
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- 代码参考 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageDeps" />
|
||||
<HyperlinkButton Margin="-12,0,0,0" NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
|
||||
<TextBlock x:Uid="SetingsPageDeps" />
|
||||
</HyperlinkButton>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
<!-- UI/UX 参考 -->
|
||||
<dev:SettingsCard HorizontalContentAlignment="Left" ContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<TextBlock x:Uid="SetingsPageUIUXRef" />
|
||||
<dev:WrapPanel Margin="-12,0,0,0" Orientation="Horizontal">
|
||||
<HyperlinkButton Content="refined-now-playing-netease" NavigateUri="https://github.com/solstice23/refined-now-playing-netease" />
|
||||
<HyperlinkButton Content="Lyricify" NavigateUri="https://github.com/WXRIW/Lyricify-App" />
|
||||
<HyperlinkButton Content="椒盐音乐 Salt Player" NavigateUri="https://moriafly.com/program/salt-player" />
|
||||
<HyperlinkButton Content="MyToolBar" NavigateUri="https://github.com/TwilightLemon/MyToolBar" />
|
||||
</dev:WrapPanel>
|
||||
</StackPanel>
|
||||
</dev:SettingsCard>
|
||||
|
||||
</dev:SettingsExpander.Items>
|
||||
</dev:SettingsExpander>
|
||||
|
||||
<dev:SettingsCard x:Uid="SettingsPageMockMusicPlaying">
|
||||
<HyperlinkButton x:Uid="SettingsPagePlayingMockMusicButton" NavigateUri="https://soundcloud.com/carlyraejepsen/cut-to-the-feeling" />
|
||||
</dev:SettingsCard>
|
||||
@@ -209,194 +290,28 @@
|
||||
Value="{x:Bind ViewModel.AppSettings.AdvancedSettings.FPS, Mode=TwoWay}" />
|
||||
</dev:SettingsCard>
|
||||
|
||||
<RichTextBlock
|
||||
Margin="0,16,0,0"
|
||||
HorizontalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineHeight="28">
|
||||
<Paragraph FontWeight="Bold">
|
||||
<Run Text="{x:Bind const:App.AppName}" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="An elegant and deeply customizable lyrics visualizer & versatile music player" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="Proudly built by" />
|
||||
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
|
||||
<Run Text="{x:Bind const:App.AppAuthor}" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Grid
|
||||
x:Name="CreditsReel"
|
||||
Background="{ThemeResource AcrylicInAppFillColorDefaultBrush}"
|
||||
Opacity="0"
|
||||
SizeChanged="CreditsReel_SizeChanged"
|
||||
Tapped="CreditsReel_Tapped"
|
||||
Visibility="Collapsed">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<ScrollViewer
|
||||
x:Name="CreditsReelScrollViewer"
|
||||
ScrollViewer.VerticalScrollBarVisibility="Hidden"
|
||||
ScrollViewer.VerticalScrollMode="Disabled">
|
||||
<RichTextBlock
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
HorizontalTextAlignment="Center"
|
||||
LineHeight="28"
|
||||
PointerEntered="RichTextBlock_PointerEntered"
|
||||
PointerExited="RichTextBlock_PointerExited">
|
||||
|
||||
<Paragraph x:Name="CreditsReelHeader" />
|
||||
|
||||
<!-- 贡献者 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageContributors" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/jayfunc">
|
||||
<Run Text="jayfunc" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Raspberry-Monster">
|
||||
<Run Text="Raspberry-Monster" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/ZHider">
|
||||
<Run Text="ZHider" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/kusutori">
|
||||
<Run Text="kusutori" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<!-- 赞助 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SettingsPagePatrons" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="YE" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Dec 3, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**玄" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 23, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**智" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 21, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="*鹤" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 17, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="借过" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Nov 2, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="**华" />
|
||||
<Run Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="Aug 28, 2025" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageUserWhoPurchased" />
|
||||
</Paragraph>
|
||||
|
||||
<!-- 特别鸣谢 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageSpecialThanks" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run x:Uid="SettingsPageYouNowUsing" />
|
||||
</Paragraph>
|
||||
|
||||
<!-- 代码参考 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageDeps" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://gist.github.com/mcworkaholic/82fbf203e3f1043bbe534b5b2974c0ce">
|
||||
<Run Text="Get album artwork from ITunes (with Python3 or C#)" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://stackoverflow.com/a/32013610/11048731">
|
||||
<Run Text="FullyObservableCollection" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/Impressionist">
|
||||
<Run Text="Impressionist" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Storyteller-Studios/ColorThief.WinUI3">
|
||||
<Run Text="ColorThief.WinUI3" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/Johnwikix/SpectrumVisualization">
|
||||
<Run Text="SpectrumVisualization" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://www.shadertoy.com/view/Mdt3Df">
|
||||
<Run Text="Snow (as shown in sweden)" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://www.shadertoy.com/view/lllSR2">
|
||||
<Run Text="w10" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/mo-jinran/Taskbar-Lyrics">
|
||||
<Run Text="Taskbar-Lyrics" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/jayfunc/BetterLyrics/network/dependencies">
|
||||
<Run Text="..." />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<!-- UI/UX 设计参考 -->
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run x:Uid="SetingsPageUIUXRef" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/solstice23/refined-now-playing-netease">
|
||||
<Run Text="refined-now-playing-netease" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/WXRIW/Lyricify-App">
|
||||
<Run Text="Lyricify" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://moriafly.com/program/salt-player">
|
||||
<Run Text="椒盐音乐 Salt Player" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="https://github.com/TwilightLemon/MyToolBar">
|
||||
<Run Text="MyToolBar" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Hyperlink NavigateUri="">
|
||||
<Run Text="" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph Margin="0,20,0,0" FontWeight="Bold">
|
||||
<Run Text="{x:Bind const:App.AppName}" />
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
<Run Text="Proudly built by" />
|
||||
<Hyperlink NavigateUri="{x:Bind const:Link.AuthorGitHub}">
|
||||
<Run Text="{x:Bind const:App.AppAuthor}" />
|
||||
</Hyperlink>
|
||||
</Paragraph>
|
||||
|
||||
<Paragraph x:Name="CreditsReelFooter" />
|
||||
|
||||
</RichTextBlock>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</UserControl>
|
||||
|
||||
@@ -11,7 +11,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
public sealed partial class AboutControl : UserControl
|
||||
{
|
||||
private bool _isCreditsScrolling = false;
|
||||
public AboutControlViewModel ViewModel => (AboutControlViewModel)DataContext;
|
||||
|
||||
public AboutControl()
|
||||
@@ -20,47 +19,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
DataContext = Ioc.Default.GetRequiredService<AboutControlViewModel>();
|
||||
}
|
||||
|
||||
private async void Patron_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
CompositionTarget.Rendering += CompositionTarget_Rendering;
|
||||
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Visible;
|
||||
CreditsReel.Opacity = 1;
|
||||
_isCreditsScrolling = true;
|
||||
}
|
||||
|
||||
private void CompositionTarget_Rendering(object? sender, object e)
|
||||
{
|
||||
if (_isCreditsScrolling)
|
||||
{
|
||||
CreditsReelScrollViewer.ChangeView(null, CreditsReelScrollViewer.VerticalOffset + 0.5, null);
|
||||
}
|
||||
}
|
||||
|
||||
private async void CreditsReel_Tapped(object sender, Microsoft.UI.Xaml.Input.TappedRoutedEventArgs e)
|
||||
{
|
||||
CreditsReel.Opacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
CreditsReel.Visibility = Microsoft.UI.Xaml.Visibility.Collapsed;
|
||||
CompositionTarget.Rendering -= CompositionTarget_Rendering;
|
||||
CreditsReelScrollViewer.ChangeView(null, 0, null);
|
||||
}
|
||||
|
||||
private void CreditsReel_SizeChanged(object sender, Microsoft.UI.Xaml.SizeChangedEventArgs e)
|
||||
{
|
||||
CreditsReelHeader.LineHeight = e.NewSize.Height;
|
||||
CreditsReelFooter.LineHeight = e.NewSize.Height / 2;
|
||||
}
|
||||
|
||||
private void RichTextBlock_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
_isCreditsScrolling = false;
|
||||
}
|
||||
|
||||
private void RichTextBlock_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
_isCreditsScrolling = true;
|
||||
}
|
||||
|
||||
private void WeChat_Click(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
WeChatFlyout.ShowAt(WeChatButton);
|
||||
|
||||
@@ -8,7 +8,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Renderer;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -31,7 +31,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
public sealed partial class LyricsCanvas : UserControl,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsData?>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<double>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
@@ -41,7 +41,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
IRecipient<PropertyChangedMessage<IRandomAccessStream?>>
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
|
||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||
@@ -98,8 +98,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private TimeSpan _songPositionWithOffset;
|
||||
private TimeSpan _songPosition; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1>
|
||||
private TimeSpan _totalPlayedTime; // <20><>ǰ<EFBFBD><C7B0><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ʱ<EFBFBD><CAB1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ػ<EFBFBD><D8BB>ظ<EFBFBD><D8B8><EFBFBD><EFBFBD>ŵ<EFBFBD>ʱ<EFBFBD>䣩
|
||||
private bool _isLastFMTracked = false;
|
||||
|
||||
private double _renderLyricsStartX = 0;
|
||||
private double _renderLyricsStartY = 0;
|
||||
@@ -345,7 +343,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
|
||||
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
||||
|
||||
double songDuration = _mediaSessionsService.CurrentSongInfo?.DurationMs ?? 0;
|
||||
double songDuration = _gsmtcService.CurrentSongInfo.DurationMs;
|
||||
bool isForceWordByWord = _settingsService.AppSettings.GeneralSettings.IsForceWordByWordEffect;
|
||||
|
||||
Color overlayColor;
|
||||
@@ -459,7 +457,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
var lyricsBg = _lyricsWindowStatus.LyricsBackgroundSettings;
|
||||
var lyricsStyle = _lyricsWindowStatus.LyricsStyleSettings;
|
||||
var lyricsEffect = _lyricsWindowStatus.LyricsEffectSettings;
|
||||
var lyricsData = _mediaSessionsService.CurrentLyricsData;
|
||||
var lyricsData = _gsmtcService.CurrentLyricsData;
|
||||
|
||||
TimeSpan elapsedTime = args.Timing.ElapsedTime;
|
||||
|
||||
@@ -654,25 +652,22 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private void UpdatePlaybackState(TimeSpan elapsedTime)
|
||||
{
|
||||
if (_mediaSessionsService.CurrentIsPlaying)
|
||||
if (_gsmtcService.CurrentIsPlaying)
|
||||
{
|
||||
_songPosition += elapsedTime;
|
||||
_totalPlayedTime += elapsedTime;
|
||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_gsmtcService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPlaybackState()
|
||||
{
|
||||
_songPosition = TimeSpan.Zero;
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
|
||||
private void UpdateRenderLyricsLines()
|
||||
{
|
||||
_renderLyricsLines = null;
|
||||
_renderLyricsLines = _mediaSessionsService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
|
||||
_renderLyricsLines = _gsmtcService.CurrentLyricsData?.LyricsLines.Select(x => new RenderLyricsLine()
|
||||
{
|
||||
LyricsSyllables = x.LyricsSyllables,
|
||||
StartMs = x.StartMs,
|
||||
@@ -685,7 +680,7 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
private async Task ReloadCoverBackgroundResourcesAsync()
|
||||
{
|
||||
if (_mediaSessionsService.AlbumArtBitmapStream is IRandomAccessStream stream)
|
||||
if (_gsmtcService.AlbumArtBitmapStream is IRandomAccessStream stream)
|
||||
{
|
||||
stream.Seek(0);
|
||||
CanvasBitmap bitmap = await CanvasBitmap.LoadAsync(Canvas, stream);
|
||||
@@ -695,26 +690,19 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentPosition))
|
||||
{
|
||||
var realPosition = message.NewValue;
|
||||
|
||||
var diff = Math.Abs(_songPosition.TotalMilliseconds - realPosition.TotalMilliseconds);
|
||||
var timelineSyncThreshold = _mediaSessionsService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
var timelineSyncThreshold = _gsmtcService.CurrentMediaSourceProviderInfo?.TimelineSyncThreshold ?? 0;
|
||||
|
||||
// ƫ<><C6AB> or seek
|
||||
if (diff >= timelineSyncThreshold)
|
||||
{
|
||||
_songPosition = realPosition;
|
||||
|
||||
// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>˿<EFBFBD>ͷ<EFBFBD><CDB7><EFBFBD><EFBFBD><EFBFBD><EFBFBD> LastFM ͳ<><CDB3>״̬
|
||||
if (_songPosition.TotalSeconds <= 1)
|
||||
{
|
||||
_totalPlayedTime = TimeSpan.Zero;
|
||||
_isLastFMTracked = false;
|
||||
}
|
||||
}
|
||||
|
||||
// <20>϶<EFBFBD><CFB6><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>ȴ<EFBFBD><C8B4><EFBFBD><EFBFBD><EFBFBD>
|
||||
@@ -728,9 +716,9 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<LyricsData?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentLyricsData))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentLyricsData))
|
||||
{
|
||||
UpdateRenderLyricsLines();
|
||||
_isLayoutChanged = true;
|
||||
@@ -738,11 +726,11 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
public void Receive(PropertyChangedMessage<SongInfo> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo))
|
||||
{
|
||||
ResetPlaybackState();
|
||||
}
|
||||
@@ -895,13 +883,14 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
|
||||
public void Receive(PropertyChangedMessage<IRandomAccessStream?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapStream))
|
||||
if (message.PropertyName == nameof(IGSMTCService.AlbumArtBitmapStream))
|
||||
{
|
||||
_ = ReloadCoverBackgroundResourcesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -124,7 +124,14 @@
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<dev:SettingsCard Style="{StaticResource DefaultSettingsExpanderItemStyle}">
|
||||
<StackPanel
|
||||
Margin="0,6,0,0"
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<Button Command="{x:Bind ViewModel.OpenMusicGalleryWindowCommand}">
|
||||
<TextBlock x:Uid="SystemTrayMusicGallery" />
|
||||
</Button>
|
||||
<DropDownButton x:Uid="SettingsPageAddFolderButton">
|
||||
<DropDownButton.Flyout>
|
||||
<MenuFlyout>
|
||||
@@ -169,7 +176,7 @@
|
||||
</MenuFlyout>
|
||||
</DropDownButton.Flyout>
|
||||
</DropDownButton>
|
||||
</dev:SettingsCard>
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
@@ -59,16 +60,16 @@
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<Grid VerticalAlignment="Center" CornerRadius="4">
|
||||
<local:ImageSwitcher
|
||||
x:Name="AlbumArtImageSwitcher"
|
||||
Width="36"
|
||||
Height="36" />
|
||||
Height="36"
|
||||
Source="{x:Bind ViewModel.GSMTCService.AlbumArtBitmapImage, Mode=OneWay}" />
|
||||
</Grid>
|
||||
<StackPanel VerticalAlignment="Center">
|
||||
<TextBlock x:Name="TitleTextBlock" />
|
||||
<TextBlock Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
|
||||
<TextBlock
|
||||
x:Name="ArtistsTextBlock"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DisplayArtists, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@@ -176,13 +177,13 @@
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
@@ -196,13 +197,13 @@
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="True">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.MediaSessionsService.CurrentIsPlaying, Mode=OneWay}"
|
||||
Binding="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="False">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
@@ -215,12 +216,16 @@
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
<!-- 播放队列按钮 -->
|
||||
<ToggleButton
|
||||
<Button
|
||||
Click="PlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}"
|
||||
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
Style="{StaticResource GhostButtonStyle}"
|
||||
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPagePlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -231,6 +236,19 @@
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
|
||||
<!-- Stop media session -->
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.StopTrackCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}"
|
||||
Visibility="{x:Bind ShowStopButton, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
<!-- Volume -->
|
||||
<Button Click="VolumeButton_Click" Style="{StaticResource GhostButtonStyle}">
|
||||
<Grid>
|
||||
@@ -402,10 +420,11 @@
|
||||
Margin="0,-14,0,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Top"
|
||||
Maximum="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
Maximum="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, Mode=OneWay, Converter={StaticResource MillisecondsToSecondsConverter}}"
|
||||
Minimum="0"
|
||||
Style="{StaticResource GhostSliderStyle}"
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}" />
|
||||
ThumbToolTipValueConverter="{StaticResource SecondsToFormattedTimeConverter}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentPosition.TotalSeconds, Mode=OneWay}" />
|
||||
|
||||
<Grid
|
||||
x:Name="TimelineSliderLyricsLineInfo"
|
||||
|
||||
@@ -2,7 +2,7 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
@@ -21,15 +21,13 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Controls;
|
||||
|
||||
public sealed partial class NowPlayingBar : UserControl,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<BitmapImage?>>,
|
||||
IRecipient<PropertyChangedMessage<TimeSpan>>
|
||||
public sealed partial class NowPlayingBar : UserControl
|
||||
{
|
||||
public NowPlayingBarViewModel ViewModel => (NowPlayingBarViewModel)DataContext;
|
||||
|
||||
public event EventHandler? SongInfoTapped;
|
||||
public event EventHandler? TimeTapped;
|
||||
public event EventHandler? PlayQueueButtonClick;
|
||||
|
||||
public bool ShowTime
|
||||
{
|
||||
@@ -64,6 +62,15 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
set { SetValue(ShowPlaybackOrderButtonProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowStopButtonProperty =
|
||||
DependencyProperty.Register(nameof(ShowStopButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
public bool ShowStopButton
|
||||
{
|
||||
get { return (bool)GetValue(ShowStopButtonProperty); }
|
||||
set { SetValue(ShowStopButtonProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty ShowPlaybackOrderButtonProperty =
|
||||
DependencyProperty.Register(nameof(ShowPlaybackOrderButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
@@ -76,15 +83,6 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
public static readonly DependencyProperty PlaybackOrderProperty =
|
||||
DependencyProperty.Register(nameof(PlaybackOrder), typeof(PlaybackOrder), typeof(NowPlayingBar), new PropertyMetadata(PlaybackOrder.RepeatAll));
|
||||
|
||||
public bool IsPlayingQueueOpened
|
||||
{
|
||||
get { return (bool)GetValue(IsPlayingQueueOpenedProperty); }
|
||||
set { SetValue(IsPlayingQueueOpenedProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPlayingQueueOpenedProperty =
|
||||
DependencyProperty.Register(nameof(IsPlayingQueueOpened), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||
|
||||
public bool IsCompactMode
|
||||
{
|
||||
get { return (bool)GetValue(IsCompactModeProperty); }
|
||||
@@ -109,8 +107,6 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<NowPlayingBarViewModel>();
|
||||
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
@@ -206,7 +202,7 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
var grid = (Grid)sender;
|
||||
var pos = e.GetCurrentPoint(grid).Position;
|
||||
var ratio = pos.X / grid.ActualWidth;
|
||||
ViewModel.MediaSessionsService.ChangePosition(TimelineSlider.Maximum * ratio);
|
||||
ViewModel.GSMTCService.ChangePosition(TimelineSlider.Maximum * ratio);
|
||||
}
|
||||
|
||||
private void TimelineSliderOverlay_PointerMoved(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
@@ -303,45 +299,11 @@ public sealed partial class NowPlayingBar : UserControl,
|
||||
|
||||
private void PlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
IsPlayingQueueOpened = !IsPlayingQueueOpened;
|
||||
PlayQueueButtonClick?.Invoke(sender, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void PlaybackOrderButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
PlaybackOrder = PlaybackOrder.GetNext();
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
{
|
||||
TitleTextBlock.Text = message.NewValue?.Title;
|
||||
ArtistsTextBlock.Text = message.NewValue?.DisplayArtists;
|
||||
}
|
||||
}
|
||||
}
|
||||
public void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
|
||||
{
|
||||
AlbumArtImageSwitcher.Source = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<TimeSpan> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
|
||||
{
|
||||
TimelineSlider.Value = message.NewValue.TotalSeconds;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.PatronControl"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid Margin="12,8">
|
||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
||||
<TextBlock Text="{x:Bind PatronName, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind Date, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@@ -0,0 +1,46 @@
|
||||
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 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 PatronControl : UserControl
|
||||
{
|
||||
public string PatronName
|
||||
{
|
||||
get { return (string)GetValue(PatronNameProperty); }
|
||||
set { SetValue(PatronNameProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty PatronNameProperty =
|
||||
DependencyProperty.Register(nameof(PatronName), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
|
||||
|
||||
public string Date
|
||||
{
|
||||
get { return (string)GetValue(DateProperty); }
|
||||
set { SetValue(DateProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty DateProperty =
|
||||
DependencyProperty.Register(nameof(Date), typeof(string), typeof(PatronControl), new PropertyMetadata(""));
|
||||
|
||||
public PatronControl()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
}
|
||||
142
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Controls/PlayQueue.xaml
Normal file
@@ -0,0 +1,142 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<UserControl
|
||||
x:Class="BetterLyrics.WinUI3.Controls.PlayQueue"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
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>
|
||||
<Grid.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</Grid.TranslationTransition>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="12,12,12,0">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueue"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1" Margin="12,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Scroll to playing item -->
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Click="ScrollToPlayingItemButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Empty play queue -->
|
||||
<Button
|
||||
Grid.Column="2"
|
||||
HorizontalAlignment="Right"
|
||||
Click="EmptyPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
|
||||
</Grid>
|
||||
|
||||
<NavigationViewItemSeparator Grid.Row="2" />
|
||||
|
||||
<ListView
|
||||
x:Name="PlayingQueueListView"
|
||||
Grid.Row="3"
|
||||
ItemsSource="{x:Bind ViewModel.SMTCService.TrackPlayingQueue, Mode=OneWay}"
|
||||
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="0,6">
|
||||
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
||||
<StackPanel Margin="0,0,36,0">
|
||||
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Track.Artist}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right">
|
||||
<Button
|
||||
VerticalAlignment="Center"
|
||||
Click="RemoveFromPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Grid Grid.Row="3">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.SMTCService.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueueEmpty"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</UserControl>
|
||||
@@ -0,0 +1,103 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
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 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 PlayQueue : UserControl, IRecipient<PropertyChangedMessage<int>>
|
||||
{
|
||||
public PlayQueueViewModel ViewModel => (PlayQueueViewModel)DataContext;
|
||||
public PlayQueue()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<PlayQueueViewModel>();
|
||||
WeakReferenceMessenger.Default.RegisterAll(this);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItem()
|
||||
{
|
||||
if (PlayingQueueListView == null) return;
|
||||
|
||||
var targetItem = ViewModel.SMTCService.TrackPlayingQueue
|
||||
.ElementAtOrDefault(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
if (targetItem == null) return;
|
||||
|
||||
PlayingQueueListView.ScrollIntoView(targetItem);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItemButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
|
||||
private async void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
await ViewModel.SMTCService.PlayTrackAsync(item);
|
||||
}
|
||||
|
||||
private async void RemoveFromPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool playNext = false;
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
int index = ViewModel.SMTCService.TrackPlayingQueue.IndexOf(item);
|
||||
if (item == PlayingQueueListView.SelectedItem)
|
||||
{
|
||||
playNext = true;
|
||||
}
|
||||
ViewModel.SMTCService.TrackPlayingQueue.Remove(item);
|
||||
if (playNext)
|
||||
{
|
||||
if (ViewModel.SMTCService.TrackPlayingQueue.Count == 0)
|
||||
{
|
||||
index = -1;
|
||||
}
|
||||
else if (index >= ViewModel.SMTCService.TrackPlayingQueue.Count)
|
||||
{
|
||||
index = ViewModel.SMTCService.TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = index;
|
||||
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void EmptyPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.SMTCService.TrackPlayingQueue.Clear();
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = -1;
|
||||
await ViewModel.SMTCService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender is MusicGallerySettings)
|
||||
{
|
||||
if (message.PropertyName == nameof(MusicGallerySettings.PlayQueueIndex))
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -314,8 +314,8 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.MediaSessionsService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSource" Value="{x:Bind ViewModel.GSMTCService.CurrentMediaSourceProviderInfo.DisplayName, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPagePlaybackSourceID" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.PlayerId, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
@@ -325,10 +325,10 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.MediaSessionsService.CurrentSongInfo.DurationMs, TargetNullValue=N/A, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.DurationMs, TargetNullValue=N/A, Converter={StaticResource MillisecondsToFormattedTimeConverter}, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
@@ -338,26 +338,26 @@
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Left">
|
||||
<StackPanel Spacing="6">
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageSongTitle" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Title, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageArtist" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.DisplayArtists, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="SettingsPageAlbum" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Album, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsSearchControlDurauion" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Duration, Converter={StaticResource SecondsToFormattedTimeConverter}, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageLanguageCode" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsData.LanguageCode, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource LanguageCodeToDisplayedNameConverter}}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageLyricsProviderPrefix"
|
||||
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay}"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
Link="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.Reference, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.ProviderIfFound, Mode=OneWay, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTransliterationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TransliterationProvider, Mode=OneWay, Converter={StaticResource TransliterationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow x:Uid="LyricsPageTranslationProviderPrefix" Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.TranslationProvider, Mode=OneWay, Converter={StaticResource TranslationSearchProviderToDisplayNameConverter}}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageMatchPercentage"
|
||||
Unit="%"
|
||||
Value="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
|
||||
Value="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.MatchPercentage, Mode=OneWay}" />
|
||||
<local:PropertyRow
|
||||
x:Uid="LyricsPageCachePath"
|
||||
Link="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.MediaSessionsService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay}" />
|
||||
Link="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
ToolTipService.ToolTip="{x:Bind ViewModel.GSMTCService.CurrentLyricsSearchResult.SelfPath, TargetNullValue=N/A, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}" />
|
||||
</StackPanel>
|
||||
</Expander>
|
||||
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
x:Class="BetterLyrics.WinUI3.Controls.StatsDashboardControl"
|
||||
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:converters="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:dev="using:DevWinUI"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||
xmlns:lvc="using:LiveChartsCore.SkiaSharpView.WinUI"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:statsmodels="using:BetterLyrics.WinUI3.Models.Stats"
|
||||
@@ -14,7 +17,7 @@
|
||||
|
||||
<UserControl.Resources>
|
||||
<Style x:Key="StatsCardStyle" TargetType="Border">
|
||||
<Setter Property="Background" Value="{ThemeResource LayerFillColorDefaultBrush}" />
|
||||
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
|
||||
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="1" />
|
||||
<Setter Property="CornerRadius" Value="8" />
|
||||
@@ -23,38 +26,98 @@
|
||||
</Style>
|
||||
</UserControl.Resources>
|
||||
|
||||
<Grid Margin="0,24,0,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="20,0">
|
||||
<Pivot x:Name="TimeRangePivot" SelectionChanged="Pivot_SelectionChanged">
|
||||
<PivotItem x:Uid="StatsDashboardControlToday" Tag="Day" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisWeek" Tag="Week" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisMonth" Tag="Month" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisQuarter" Tag="Quarter" />
|
||||
<PivotItem x:Uid="StatsDashboardControlThisYear" Tag="Year" />
|
||||
</Pivot>
|
||||
<ProgressBar
|
||||
Grid.Row="0"
|
||||
Background="Transparent"
|
||||
IsIndeterminate="{x:Bind ViewModel.IsLoading, Mode=OneWay}"
|
||||
Visibility="{x:Bind ViewModel.IsLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
|
||||
|
||||
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.GSMTCService.IsScrobbled, Mode=OneWay, Converter={StaticResource BoolNegationToVisibilityConverter}}">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
<InfoBar
|
||||
x:Uid="StatsDashboardControlRecording"
|
||||
Grid.Row="0"
|
||||
IsClosable="False"
|
||||
IsOpen="True"
|
||||
Message="{x:Bind ViewModel.GSMTCService.CurrentSongInfo.Title, Mode=OneWay}" />
|
||||
<ProgressBar
|
||||
Grid.Row="1"
|
||||
Background="Transparent"
|
||||
Maximum="{x:Bind ViewModel.GSMTCService.TargetScrobbledDuration.TotalSeconds, Mode=OneWay}"
|
||||
ShowPaused="{x:Bind ViewModel.GSMTCService.CurrentIsPlaying, Mode=OneWay, Converter={StaticResource BoolNegationConverter}}"
|
||||
Value="{x:Bind ViewModel.GSMTCService.ScrobbledDuration.TotalSeconds, Mode=OneWay}" />
|
||||
</Grid>
|
||||
|
||||
<ScrollViewer Grid.Row="1" Padding="20,0">
|
||||
<controls:WrapPanel
|
||||
Grid.Row="2"
|
||||
Margin="36,36,36,12"
|
||||
HorizontalSpacing="12"
|
||||
Orientation="Horizontal"
|
||||
VerticalSpacing="12">
|
||||
<ComboBox
|
||||
x:Uid="StatsDashboardControlTimeRange"
|
||||
Header="Time Range"
|
||||
SelectedIndex="{x:Bind ViewModel.SelectedTimeRange, Mode=TwoWay, Converter={StaticResource EnumToIntConverter}}">
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlToday" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisWeek" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisMonth" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisQuarter" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlThisYear" />
|
||||
<ComboBoxItem x:Uid="StatsDashboardControlCustom" />
|
||||
</ComboBox>
|
||||
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlStart"
|
||||
Date="{x:Bind ViewModel.CustomStartDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomStartTime, Mode=TwoWay}" />
|
||||
<CalendarDatePicker
|
||||
x:Uid="StatsDashboardControlEnd"
|
||||
Date="{x:Bind ViewModel.CustomEndDate, Mode=TwoWay}"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}" />
|
||||
<TimePicker
|
||||
VerticalAlignment="Bottom"
|
||||
IsEnabled="{x:Bind ViewModel.IsCustomRangeSelected, Mode=OneWay}"
|
||||
Time="{x:Bind ViewModel.CustomEndTime, Mode=TwoWay}" />
|
||||
<Button
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.RefreshDataCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}" />
|
||||
</controls:WrapPanel>
|
||||
|
||||
<ScrollViewer Grid.Row="3" Padding="36,0">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
<RowDefinition />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0" Margin="0,20,0,0">
|
||||
<Grid Grid.Row="0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 总播放时长 -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel
|
||||
@@ -71,7 +134,7 @@
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay}" />
|
||||
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay, Converter={StaticResource DoubleToDecimalConverter}}" />
|
||||
<TextBlock
|
||||
Margin="0,0,0,2"
|
||||
VerticalAlignment="Bottom"
|
||||
@@ -83,7 +146,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- 总播放歌曲次数 -->
|
||||
<Border Grid.Column="1" Style="{StaticResource StatsCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel
|
||||
@@ -100,7 +162,6 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Top source -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Margin="0,0,0,12"
|
||||
@@ -121,25 +182,99 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="1">
|
||||
<!-- Activity by hour -->
|
||||
<Border
|
||||
Grid.Row="1"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Margin="0,0,0,12"
|
||||
Orientation="Horizontal"
|
||||
Spacing="8">
|
||||
<TextBlock x:Uid="StatsDashboardControlActivityByHour" Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<Grid Grid.Row="1" Margin="0,0,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlMostActive"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.PeakHourText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="1">
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlLeastActive"
|
||||
FontSize="12"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
<TextBlock FontWeight="SemiBold" Text="{x:Bind ViewModel.QuietHourText, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<lvc:CartesianChart
|
||||
Grid.Row="2"
|
||||
Height="180"
|
||||
Margin="0,8,0,0"
|
||||
Background="Transparent"
|
||||
TooltipPosition="Top">
|
||||
|
||||
<lvc:CartesianChart.XAxes>
|
||||
<lvc:AxesCollection>
|
||||
<lvc:XamlAxis Labels="{x:Bind ViewModel.HourlyXAxisLabels, Mode=OneWay}" TextSize="{StaticResource BodyTextBlockFontSize}" />
|
||||
</lvc:AxesCollection>
|
||||
</lvc:CartesianChart.XAxes>
|
||||
|
||||
<lvc:CartesianChart.YAxes>
|
||||
<lvc:AxesCollection>
|
||||
<lvc:XamlAxis
|
||||
x:Uid="StatsDashboardControlTrackCountAxis"
|
||||
NameTextSize="{StaticResource BodyTextBlockFontSize}"
|
||||
ShowSeparatorLines="False"
|
||||
TextSize="{StaticResource BodyTextBlockFontSize}" />
|
||||
</lvc:AxesCollection>
|
||||
</lvc:CartesianChart.YAxes>
|
||||
|
||||
<lvc:CartesianChart.Series>
|
||||
<lvc:SeriesCollection>
|
||||
<lvc:XamlColumnSeries
|
||||
x:Name="HourlySeries"
|
||||
Rx="4"
|
||||
Ry="4"
|
||||
Values="{x:Bind ViewModel.HourlySeriesValues, Mode=OneWay}" />
|
||||
</lvc:SeriesCollection>
|
||||
</lvc:CartesianChart.Series>
|
||||
|
||||
</lvc:CartesianChart>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Top artists and sources -->
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="1.5*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Top artists -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlTopArtists"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="statsmodels:ArtistPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
@@ -163,67 +298,81 @@
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
x:Uid="StatsDashboardControlTrackCountText"
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Top sources -->
|
||||
<!-- Top tracks -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,12"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlSources"
|
||||
x:Uid="StatsDashboardControlTopSongs"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.PlayerStats, Mode=OneWay}">
|
||||
<ItemsControl ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:PlayerStatDisplayItem">
|
||||
<DataTemplate x:DataType="statsmodels:SongPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="0,0,12,0"
|
||||
Background="{ThemeResource LayerFillColorAltBrush}"
|
||||
CornerRadius="4">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Artist}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
FontSize="13"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind PlayerName}" />
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
x:Uid="StatsDashboardControlTrackCountText"
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Top song -->
|
||||
<!-- 播放源分布 -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Grid.Row="3"
|
||||
Margin="0,0,0,20"
|
||||
Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
@@ -232,71 +381,30 @@
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="StatsDashboardControlTopSongs"
|
||||
x:Uid="StatsDashboardControlSources"
|
||||
Margin="0,0,0,12"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||
|
||||
<ListView
|
||||
<lvc:PieChart
|
||||
Grid.Row="1"
|
||||
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||
ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}"
|
||||
SelectionMode="None">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate x:DataType="statsmodels:SongPlayCount">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid
|
||||
Width="40"
|
||||
Height="40"
|
||||
Margin="0,0,12,0"
|
||||
Background="{ThemeResource LayerFillColorAltBrush}"
|
||||
CornerRadius="4">
|
||||
<FontIcon
|
||||
FontSize="16"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Glyph="" />
|
||||
</Grid>
|
||||
MinHeight="250"
|
||||
Background="Transparent"
|
||||
LegendPosition="Bottom"
|
||||
LegendTextSize="{StaticResource BodyTextBlockFontSize}"
|
||||
Series="{x:Bind ViewModel.SourceSeries, Mode=OneWay}"
|
||||
TooltipPosition="Center" />
|
||||
|
||||
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||
Text="{x:Bind Artist}" />
|
||||
</StackPanel>
|
||||
|
||||
<TextBlock
|
||||
Grid.Column="2"
|
||||
VerticalAlignment="Center"
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
</Grid>
|
||||
</ScrollViewer>
|
||||
|
||||
<Button
|
||||
Grid.Row="1"
|
||||
<!--<Button
|
||||
Grid.Row="2"
|
||||
HorizontalAlignment="Right"
|
||||
VerticalAlignment="Bottom"
|
||||
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
|
||||
Content="Generate test data"
|
||||
Visibility="Collapsed" />
|
||||
|
||||
Content="Generate test data" />-->
|
||||
</Grid>
|
||||
</UserControl>
|
||||
</UserControl>
|
||||
@@ -29,30 +29,6 @@ public sealed partial class StatsDashboardControl : UserControl
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<StatsDashboardControlViewModel>();
|
||||
this.Loaded += StatsDashboardControl_Loaded;
|
||||
}
|
||||
|
||||
private async void StatsDashboardControl_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||
{
|
||||
await ViewModel.LoadDataAsync(StatsRange.Day);
|
||||
}
|
||||
|
||||
private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||
{
|
||||
if (ViewModel == null) return;
|
||||
|
||||
if (TimeRangePivot.SelectedItem is PivotItem item && item.Tag is string tag)
|
||||
{
|
||||
var range = tag switch
|
||||
{
|
||||
"Day" => StatsRange.Day,
|
||||
"Week" => StatsRange.Week,
|
||||
"Month" => StatsRange.Month,
|
||||
"Quarter" => StatsRange.Quarter,
|
||||
"Year" => StatsRange.Year,
|
||||
_ => StatsRange.Day
|
||||
};
|
||||
await ViewModel.LoadDataAsync(range);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class DoubleToDecimalConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value == null) return string.Empty;
|
||||
|
||||
if (double.TryParse(value.ToString(), out double number))
|
||||
{
|
||||
int decimalPlaces = 2;
|
||||
if (parameter != null && int.TryParse(parameter.ToString(), out int parsedParams))
|
||||
{
|
||||
decimalPlaces = parsedParams;
|
||||
}
|
||||
|
||||
return number.ToString($"F{decimalPlaces}");
|
||||
}
|
||||
|
||||
return value.ToString() ?? "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public partial class UriStringToDecodedAbsoluteUri : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is string uriString)
|
||||
{
|
||||
return uriString.ToDecodedAbsoluteUri();
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,10 +6,11 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum StatsRange
|
||||
{
|
||||
Day,
|
||||
Week,
|
||||
Month,
|
||||
Quarter,
|
||||
Year
|
||||
Today,
|
||||
ThisWeek,
|
||||
ThisMonth,
|
||||
ThisQuarter,
|
||||
ThisYear,
|
||||
Custom
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
{
|
||||
public static class SongInfoExtensions
|
||||
{
|
||||
public static SongInfo Placeholder => new SongInfo
|
||||
public static SongInfo Placeholder => new()
|
||||
{
|
||||
Title = "N/A",
|
||||
Album = "N/A",
|
||||
@@ -44,7 +44,7 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
PlayerId = songInfo.PlayerId ?? "N/A",
|
||||
TotalDurationMs = songInfo.DurationMs,
|
||||
DurationPlayedMs = actualPlayedMs,
|
||||
StartedAt = DateTime.Now.AddMilliseconds(-actualPlayedMs)
|
||||
StartedAt = DateTime.FromBinary(songInfo.StartedAt)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Extensions
|
||||
@@ -75,6 +76,18 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public string ToDecodedAbsoluteUri()
|
||||
{
|
||||
if (string.IsNullOrEmpty(str)) return "";
|
||||
try
|
||||
{
|
||||
var u = new Uri(str);
|
||||
return u.IsFile ? u.LocalPath : System.Net.WebUtility.UrlDecode(u.AbsoluteUri);
|
||||
}
|
||||
catch { return str; }
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using Microsoft.UI;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SkiaSharp;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Drawing.Imaging;
|
||||
@@ -15,6 +16,16 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class ColorHelper
|
||||
{
|
||||
public static Color GetSystemAccentColor()
|
||||
{
|
||||
if (Application.Current.Resources.TryGetValue("SystemAccentColor", out var resource) &&
|
||||
resource is Color uiColor)
|
||||
{
|
||||
return uiColor;
|
||||
}
|
||||
return Color.FromArgb(255, 0, 120, 215);
|
||||
}
|
||||
|
||||
public static ElementTheme GetElementThemeFromBackgroundColor(Color backgroundColor)
|
||||
{
|
||||
// 计算亮度(YIQ公式)
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using ColorThiefDotNet;
|
||||
using CommunityToolkit.WinUI.Helpers;
|
||||
using Impressionist.Abstractions;
|
||||
using Impressionist.Implementations;
|
||||
using System;
|
||||
@@ -50,7 +51,29 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return paletteResult;
|
||||
}
|
||||
|
||||
public static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
public static List<Windows.UI.Color> GenerateChartColors(Windows.UI.Color baseColor, int count)
|
||||
{
|
||||
List<Windows.UI.Color> results = [];
|
||||
|
||||
var baseHsl = baseColor.ToHsl();
|
||||
double baseHue = baseHsl.H;
|
||||
double baseSaturation = baseHsl.S;
|
||||
double baseBrightness = baseHsl.L;
|
||||
|
||||
double step = 360.0 / count;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
double newHue = (baseHue + (step * i)) % 360;
|
||||
|
||||
Windows.UI.Color newColor = CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(newHue, baseSaturation, baseBrightness);
|
||||
results.Add(newColor);
|
||||
}
|
||||
|
||||
return results;
|
||||
}
|
||||
|
||||
private static async Task<Dictionary<Vector3, int>> GetPixelColor(BitmapDecoder bitmapDecoder)
|
||||
{
|
||||
var pixelDataProvider = await bitmapDecoder.GetPixelDataAsync();
|
||||
var pixels = pixelDataProvider.DetachPixelData();
|
||||
|
||||
@@ -81,5 +81,6 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
|
||||
Directory.CreateDirectory(LocalAlbumArtCacheDirectory);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,20 +10,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public string Uri { get; private set; } = "";
|
||||
|
||||
public string DecodedAbsoluteUri
|
||||
{
|
||||
get
|
||||
{
|
||||
if (string.IsNullOrEmpty(Uri)) return "";
|
||||
try
|
||||
{
|
||||
var u = new Uri(Uri);
|
||||
return u.IsFile ? u.LocalPath : System.Net.WebUtility.UrlDecode(u.AbsoluteUri);
|
||||
}
|
||||
catch { return Uri; }
|
||||
}
|
||||
}
|
||||
|
||||
public string? RawLyrics { get; set; }
|
||||
public string? LocalAlbumArtPath { get; set; }
|
||||
public byte[]? AlbumArtByteArray { get; set; }
|
||||
@@ -119,14 +105,13 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public ExtendedTrack() : base() { }
|
||||
|
||||
public ExtendedTrack(string uriString) : base()
|
||||
public ExtendedTrack(string decodedUriString) : base()
|
||||
{
|
||||
Uri = uriString;
|
||||
|
||||
string atlPath = uriString;
|
||||
string atlPath = decodedUriString;
|
||||
try
|
||||
{
|
||||
var u = new Uri(uriString);
|
||||
var u = new Uri(decodedUriString);
|
||||
Uri = u.AbsoluteUri;
|
||||
if (u.IsFile) atlPath = u.LocalPath;
|
||||
}
|
||||
catch { }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Settings
|
||||
|
||||
@@ -6,7 +6,7 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class SongInfo : ObservableObject, ICloneable
|
||||
public partial class SongInfo : ObservableRecipient, ICloneable
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial string Album { get; set; }
|
||||
@@ -26,6 +26,8 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string? SongId { get; set; } = null;
|
||||
|
||||
[ObservableProperty] public partial long StartedAt { get; set; } = DateTime.Now.ToBinary();
|
||||
|
||||
public string? LinkedFileName { get; set; } = null;
|
||||
|
||||
public double Duration => DurationMs / 1000;
|
||||
@@ -45,6 +47,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
PlayerId = this.PlayerId,
|
||||
SongId = this.SongId,
|
||||
LinkedFileName = this.LinkedFileName,
|
||||
StartedAt = this.StartedAt,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models.Stats
|
||||
{
|
||||
public class HourlyStatBar
|
||||
{
|
||||
public int Hour { get; set; }
|
||||
public double NormalizedHeight { get; set; } // 0 - 100,用于UI高度
|
||||
public int RawCount { get; set; } // 实际播放数
|
||||
public string Label { get; set; } // Tooltip: "09:00 - 15 plays"
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,8 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
var blur = line.BlurAmountTransition.Value;
|
||||
var bounds = line.PhoneticCanvasTextLayout.LayoutBounds;
|
||||
|
||||
if (double.IsNaN(opacity)) return;
|
||||
|
||||
var destRect = new Rect(
|
||||
bounds.X + line.PhoneticPosition.X,
|
||||
bounds.Y + line.PhoneticPosition.Y,
|
||||
@@ -71,6 +73,8 @@ namespace BetterLyrics.WinUI3.Renderer
|
||||
var blur = line.BlurAmountTransition.Value;
|
||||
var bounds = line.TranslatedCanvasTextLayout.LayoutBounds;
|
||||
|
||||
if (double.IsNaN(opacity)) return;
|
||||
|
||||
var destRect = new Rect(
|
||||
bounds.X + line.TranslatedPosition.X,
|
||||
bounds.Y + line.TranslatedPosition.Y,
|
||||
|
||||
@@ -31,8 +31,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
|
||||
private readonly IDbContextFactory<FilesIndexDbContext> _contextFactory;
|
||||
|
||||
private bool _isInitialized = false;
|
||||
|
||||
// 定时器字典
|
||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _folderTimerTokens = new();
|
||||
// 当前正在执行的扫描任务字典
|
||||
@@ -460,6 +458,17 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<FilesIndexItem>> GetParsedFilesAsync()
|
||||
{
|
||||
using var context = await _contextFactory.CreateDbContextAsync();
|
||||
|
||||
// SQL: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
|
||||
return await context.FilesIndex
|
||||
.AsNoTracking()
|
||||
.Where(x => x.IsMetadataParsed)
|
||||
.ToListAsync();
|
||||
}
|
||||
|
||||
public async Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
|
||||
{
|
||||
if (enabledConfigIds == null || !enabledConfigIds.Any())
|
||||
|
||||
@@ -50,7 +50,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
Task ScanMediaFolderAsync(MediaFolder folder, CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库拉取
|
||||
/// 从数据库拉取全部已解析的数据
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
Task<List<FilesIndexItem>> GetParsedFilesAsync();
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库拉取全部已解析的且其所属的 MediaFolder 在应用内处于开启状态的数据
|
||||
/// </summary>
|
||||
/// <param name="enabledConfigIds"></param>
|
||||
/// <returns></returns>
|
||||
|
||||
@@ -17,9 +17,9 @@ using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
{
|
||||
public partial class MediaSessionsService : IMediaSessionsService
|
||||
public partial class GSMTCService : IGSMTCService
|
||||
{
|
||||
private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
|
||||
|
||||
@@ -42,15 +42,13 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
_logger.LogInformation("RefreshArtAlbum");
|
||||
|
||||
if (CurrentSongInfo == null)
|
||||
IBuffer? buffer = null;
|
||||
if (CurrentSongInfo != SongInfoExtensions.Placeholder)
|
||||
{
|
||||
_logger.LogWarning("CurrentSongInfo == null");
|
||||
return;
|
||||
buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(CurrentSongInfo, _SMTCAlbumArtBuffer, token), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
}
|
||||
|
||||
IBuffer? buffer = await Task.Run(async () => await _albumArtSearchService.SearchAsync(CurrentSongInfo, _SMTCAlbumArtBuffer, token), token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
|
||||
if (buffer == null)
|
||||
{
|
||||
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
|
||||
@@ -3,13 +3,15 @@ using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Parsers.LyricsParser;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
{
|
||||
public partial class MediaSessionsService : IMediaSessionsService
|
||||
public partial class GSMTCService : IGSMTCService
|
||||
{
|
||||
private LatestOnlyTaskRunner _refreshLyricsRunner = new();
|
||||
|
||||
@@ -24,15 +26,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
CurrentLyricsSearchResult = null;
|
||||
CurrentLyricsData = LyricsData.GetLoadingPlaceholder();
|
||||
|
||||
if (CurrentSongInfo != null)
|
||||
if (CurrentSongInfo != SongInfoExtensions.Placeholder)
|
||||
{
|
||||
CurrentLyricsSearchResult = await Task.Run(async () => await _lyrcsSearchService.SearchSmartlyAsync(
|
||||
CurrentSongInfo,
|
||||
true,
|
||||
CurrentMediaSourceProviderInfo?.LyricsSearchType,
|
||||
token),
|
||||
token);
|
||||
if (token.IsCancellationRequested) return;
|
||||
CurrentSongInfo, true, CurrentMediaSourceProviderInfo?.LyricsSearchType, token), token);
|
||||
|
||||
if (CurrentLyricsSearchResult != null)
|
||||
{
|
||||
@@ -40,14 +37,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
(CurrentLyricsData, CurrentLyricsSearchResult.TransliterationProvider, CurrentLyricsSearchResult.TranslationProvider) =
|
||||
await Task.Run(async () => await lyricsParser.Parse(
|
||||
_translationService,
|
||||
_transliterationService,
|
||||
_settingsService.AppSettings.TranslationSettings,
|
||||
CurrentLyricsSearchResult,
|
||||
token),
|
||||
token);
|
||||
_translationService, _transliterationService, _settingsService.AppSettings.TranslationSettings, CurrentLyricsSearchResult, token), token);
|
||||
}
|
||||
}
|
||||
|
||||
if (CurrentLyricsSearchResult == null)
|
||||
{
|
||||
CurrentLyricsData = LyricsData.GetNotfoundPlaceholder();
|
||||
}
|
||||
}
|
||||
|
||||
public async void UpdateLyrics()
|
||||
@@ -26,6 +26,7 @@ using CommunityToolkit.WinUI;
|
||||
using EvtSource;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
@@ -38,9 +39,9 @@ using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
using WindowsMediaController;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
{
|
||||
public partial class MediaSessionsService : BaseViewModel, IMediaSessionsService,
|
||||
public partial class GSMTCService : BaseViewModel, IGSMTCService,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>,
|
||||
IRecipient<PropertyChangedMessage<ChineseRomanization>>,
|
||||
@@ -50,6 +51,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private readonly MediaManager _mediaManager = new();
|
||||
private IBuffer? _SMTCAlbumArtBuffer = null;
|
||||
|
||||
private MediaManager.MediaSession? _currentDesiredSession = null;
|
||||
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
private readonly ILyricsSearchService _lyrcsSearchService;
|
||||
private readonly ITranslationService _translationService;
|
||||
@@ -58,22 +61,25 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private readonly IDiscordService _discordService;
|
||||
private readonly IPlayHistoryService _playHistoryService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ILogger<MediaSessionsService> _logger;
|
||||
private readonly ILogger<GSMTCService> _logger;
|
||||
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
private byte[]? _lxMusicAlbumArtBytes = null;
|
||||
|
||||
private readonly DispatcherQueueTimer? _onMediaPropsChangedTimer;
|
||||
private readonly DispatcherTimer _scrobbleTimer;
|
||||
|
||||
private readonly Stopwatch _scrobbleStopwatch = new();
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsScrobbled { get; set; } = false;
|
||||
[ObservableProperty] public partial TimeSpan ScrobbledDuration { get; set; } = TimeSpan.Zero;
|
||||
[ObservableProperty] public partial TimeSpan TargetScrobbledDuration { get; set; } = TimeSpan.Zero;
|
||||
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool CurrentIsPlaying { get; private set; } = false;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial TimeSpan CurrentPosition { get; private set; } = TimeSpan.Zero;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SongInfo? CurrentSongInfo { get; private set; } = SongInfoExtensions.Placeholder;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial SongInfo CurrentSongInfo { get; private set; } = SongInfoExtensions.Placeholder;
|
||||
|
||||
[ObservableProperty] public partial MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; set; }
|
||||
|
||||
public MediaSessionsService(
|
||||
public GSMTCService(
|
||||
ISettingsService settingsService,
|
||||
IAlbumArtSearchService albumArtSearchService,
|
||||
ILyricsSearchService lyricsSearchService,
|
||||
@@ -82,7 +88,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
ITransliterationService transliterationService,
|
||||
IPlayHistoryService playHistoryService,
|
||||
ILastFMService lastFMService,
|
||||
ILogger<MediaSessionsService> logger)
|
||||
ILogger<GSMTCService> logger)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
@@ -94,6 +100,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_lastFMService = lastFMService;
|
||||
_logger = logger;
|
||||
|
||||
_scrobbleTimer = new();
|
||||
_scrobbleTimer.Interval = TimeSpan.FromSeconds(1);
|
||||
_scrobbleTimer.Tick += ScrobbleTimer_Tick;
|
||||
|
||||
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
|
||||
@@ -106,6 +116,41 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
InitMediaManager();
|
||||
}
|
||||
|
||||
private void ScrobbleTimer_Tick(object? sender, object e)
|
||||
{
|
||||
if (!IsScrobbled)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(CurrentSongInfo.Title) && CurrentSongInfo.Title != "N/A")
|
||||
{
|
||||
ScrobbledDuration += _scrobbleTimer.Interval;
|
||||
if (ScrobbledDuration >= TargetScrobbledDuration)
|
||||
{
|
||||
// 写入本地播放记录
|
||||
var playHistoryItem = CurrentSongInfo.ToPlayHistoryItem(ScrobbledDuration.TotalMilliseconds);
|
||||
if (playHistoryItem != null)
|
||||
{
|
||||
// 后台
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
await _playHistoryService.AddLogAsync(playHistoryItem);
|
||||
});
|
||||
_logger.LogInformation("ScrobbleTimer_Tick: {} scrobbled", CurrentSongInfo.Title);
|
||||
}
|
||||
// 写入 Last.fm 播放记录
|
||||
var isLastFMEnabled = CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
|
||||
if (isLastFMEnabled)
|
||||
{
|
||||
// 后台
|
||||
_ = Task.Run(() => _lastFMService.TrackAsync(CurrentSongInfo));
|
||||
}
|
||||
|
||||
IsScrobbled = true;
|
||||
ScrobbledDuration = TimeSpan.Zero;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MappedSongSearchQueries_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
{
|
||||
UpdateLyrics();
|
||||
@@ -143,10 +188,9 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
|
||||
private MediaSourceProviderInfo? GetCurrentMediaSourceProviderInfo()
|
||||
private MediaSourceProviderInfo? GetCurrentDesiredMediaSourceProviderInfo()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == desiredSession?.Id);
|
||||
return _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == _currentDesiredSession?.Id);
|
||||
}
|
||||
|
||||
private bool IsMediaSourceEnabled(string id)
|
||||
@@ -176,6 +220,10 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void InitMediaManager()
|
||||
{
|
||||
_mediaManager.Start();
|
||||
|
||||
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSession(x.Value.Id));
|
||||
|
||||
_mediaManager.OnAnySessionOpened += MediaManager_OnAnySessionOpened;
|
||||
_mediaManager.OnAnySessionClosed += MediaManager_OnAnySessionClosed;
|
||||
_mediaManager.OnFocusedSessionChanged += MediaManager_OnFocusedSessionChanged;
|
||||
@@ -183,211 +231,157 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_mediaManager.OnAnyPlaybackStateChanged += MediaManager_OnAnyPlaybackStateChanged;
|
||||
_mediaManager.OnAnyTimelinePropertyChanged += MediaManager_OnAnyTimelinePropertyChanged;
|
||||
|
||||
_mediaManager.Start();
|
||||
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
|
||||
OnDesiredSessionChanged(true);
|
||||
}
|
||||
|
||||
private async void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
|
||||
private void OnDesiredSessionChanged(bool firstTime = false)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
|
||||
await SendFocusedMessagesAsync();
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
var desiredSession = GetCurrentDesiredSession();
|
||||
if (firstTime || desiredSession != _currentDesiredSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null)
|
||||
_currentDesiredSession = desiredSession;
|
||||
if (_currentDesiredSession == null)
|
||||
{
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
return;
|
||||
}
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
{
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
SendNullMessages();
|
||||
}
|
||||
else
|
||||
{
|
||||
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
|
||||
{
|
||||
CurrentPosition = timelineProperties?.Position ?? TimeSpan.Zero;
|
||||
CurrentSongInfo?.DurationMs = timelineProperties?.EndTime.TotalMilliseconds ?? 0;
|
||||
}
|
||||
_ = SendFocusedMessagesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
|
||||
{
|
||||
OnDesiredSessionChanged();
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (mediaSession != _currentDesiredSession) return;
|
||||
|
||||
CurrentPosition = timelineProperties.Position;
|
||||
CurrentSongInfo.DurationMs = timelineProperties.EndTime.TotalMilliseconds;
|
||||
UpdateTargetScrobbledDuration();
|
||||
if (CurrentPosition.TotalSeconds == 0)
|
||||
{
|
||||
IsScrobbled = false;
|
||||
ScrobbledDuration = TimeSpan.Zero;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
|
||||
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, (() =>
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null)
|
||||
if (mediaSession != _currentDesiredSession) return;
|
||||
|
||||
CurrentIsPlaying = playbackInfo.PlaybackStatus switch
|
||||
{
|
||||
CurrentIsPlaying = false;
|
||||
return;
|
||||
}
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
//RecordMediaSourceProviderInfo(mediaSession);
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (!IsMediaSourceEnabled(mediaSession.Id))
|
||||
if (CurrentIsPlaying)
|
||||
{
|
||||
CurrentIsPlaying = false;
|
||||
_scrobbleTimer.Start();
|
||||
}
|
||||
else
|
||||
{
|
||||
CurrentIsPlaying = playbackInfo?.PlaybackStatus switch
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
|
||||
_ => false,
|
||||
};
|
||||
_scrobbleTimer.Stop();
|
||||
}
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProperties)
|
||||
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
|
||||
{
|
||||
_onMediaPropsChangedTimer?.Debounce(() =>
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
_dispatcherQueue.TryEnqueue(async () =>
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null)
|
||||
if (mediaSession != _currentDesiredSession) return;
|
||||
|
||||
string sessionId = mediaSession.Id;
|
||||
|
||||
var currentMediaSourceProviderInfo = GetCurrentDesiredMediaSourceProviderInfo();
|
||||
if (currentMediaSourceProviderInfo?.ResetPositionOffsetOnSongChanged == true)
|
||||
{
|
||||
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
||||
currentMediaSourceProviderInfo?.PositionOffset = 0;
|
||||
}
|
||||
|
||||
string? sessionId = mediaSession?.Id;
|
||||
string fixedTitle = mediaProperties.Title;
|
||||
string fixedArtist = mediaProperties.Artist;
|
||||
string fixedAlbum = mediaProperties.AlbumTitle;
|
||||
string? songId = null;
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
|
||||
if (mediaSession != desiredSession) return;
|
||||
|
||||
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
|
||||
if (PlayerIdHelper.IsAppleMusic(sessionId))
|
||||
{
|
||||
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
||||
fixedArtist = mediaProperties.Artist.Split(" — ").First();
|
||||
fixedAlbum = mediaProperties.Artist.Split(" — ").Last();
|
||||
fixedAlbum = fixedAlbum.Replace(" - Single", "");
|
||||
fixedAlbum = fixedAlbum.Replace(" - EP", "");
|
||||
}
|
||||
else if (PlayerIdHelper.IsNeteaseFamily(sessionId))
|
||||
{
|
||||
songId = mediaProperties.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
|
||||
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
|
||||
}
|
||||
else if (sessionId == PlayerId.QQMusic)
|
||||
{
|
||||
songId = mediaProperties.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
|
||||
.Replace(ExtendedGenreFiled.QQMusicTrackID, "");
|
||||
}
|
||||
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
var linkedFileName = mediaProperties.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
|
||||
.Replace(ExtendedGenreFiled.FileName, "");
|
||||
|
||||
_SMTCAlbumArtBuffer = null;
|
||||
CurrentSongInfo = new()
|
||||
{
|
||||
Title = fixedTitle,
|
||||
Artists = fixedArtist.SplitByCommonSplitter(),
|
||||
Album = fixedAlbum,
|
||||
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
PlayerId = sessionId,
|
||||
SongId = songId,
|
||||
LinkedFileName = linkedFileName,
|
||||
StartedAt = DateTime.Now.ToBinary(),
|
||||
};
|
||||
|
||||
UpdateTargetScrobbledDuration();
|
||||
IsScrobbled = false;
|
||||
ScrobbledDuration = TimeSpan.Zero;
|
||||
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||
{
|
||||
StartSSE();
|
||||
}
|
||||
else
|
||||
{
|
||||
var currentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
|
||||
if (currentMediaSourceProviderInfo?.ResetPositionOffsetOnSongChanged == true)
|
||||
{
|
||||
currentMediaSourceProviderInfo?.PositionOffset = 0;
|
||||
}
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
string? fixedArtist = mediaProperties?.Artist;
|
||||
string? fixedAlbum = mediaProperties?.AlbumTitle;
|
||||
string? songId = null;
|
||||
|
||||
if (PlayerIdHelper.IsAppleMusic(sessionId))
|
||||
{
|
||||
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
|
||||
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
|
||||
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
|
||||
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
|
||||
}
|
||||
else if (PlayerIdHelper.IsNeteaseFamily(sessionId))
|
||||
{
|
||||
songId = mediaProperties?.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
|
||||
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
|
||||
}
|
||||
else if (sessionId == PlayerId.QQMusic)
|
||||
{
|
||||
songId = mediaProperties?.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
|
||||
.Replace(ExtendedGenreFiled.QQMusicTrackID, "");
|
||||
}
|
||||
|
||||
var linkedFileName = mediaProperties?.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
|
||||
.Replace(ExtendedGenreFiled.FileName, "");
|
||||
|
||||
// 写入播放记录
|
||||
if (CurrentSongInfo != null && CurrentSongInfo.Title != "N/A")
|
||||
{
|
||||
// 必须捕获一个副本给异步任务,因为 CurrentSongInfo 马上就要变了
|
||||
var lastSong = CurrentSongInfo;
|
||||
|
||||
// 当前秒表时间 >= 上一首总时长 / 2
|
||||
if (lastSong.DurationMs > 0 &&
|
||||
_scrobbleStopwatch.Elapsed.TotalMilliseconds >= (lastSong.DurationMs / 2))
|
||||
{
|
||||
// 写入本地播放记录
|
||||
var playHistoryItem = lastSong.ToPlayHistoryItem(_scrobbleStopwatch.Elapsed.TotalMilliseconds);
|
||||
if (playHistoryItem != null)
|
||||
{
|
||||
// 后台
|
||||
_ = Task.Run(() => _playHistoryService.AddLogAsync(playHistoryItem));
|
||||
_logger.LogInformation($"[Scrobble] 结算成功: {lastSong.Title}");
|
||||
}
|
||||
// 写入 Last.fm 播放记录
|
||||
var isLastFMEnabled = CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
|
||||
if (isLastFMEnabled)
|
||||
{
|
||||
// 后台
|
||||
_ = Task.Run(() => _lastFMService.TrackAsync(lastSong));
|
||||
}
|
||||
}
|
||||
}
|
||||
_scrobbleStopwatch.Restart();
|
||||
|
||||
CurrentSongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProperties?.Title ?? "N/A",
|
||||
Artists = fixedArtist?.SplitByCommonSplitter() ?? ["N/A"],
|
||||
Album = fixedAlbum ?? "N/A",
|
||||
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds ?? 0,
|
||||
PlayerId = sessionId,
|
||||
SongId = songId,
|
||||
LinkedFileName = linkedFileName
|
||||
};
|
||||
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||
{
|
||||
StartSSE();
|
||||
}
|
||||
else
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
||||
{
|
||||
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
|
||||
}
|
||||
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SMTCAlbumArtBuffer = null;
|
||||
}
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
||||
{
|
||||
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
|
||||
}
|
||||
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
|
||||
}
|
||||
else
|
||||
{
|
||||
_SMTCAlbumArtBuffer = null;
|
||||
}
|
||||
|
||||
_logger.LogInformation("MediaManager_OnAnyMediaPropertyChanged {SongInfo}", CurrentSongInfo);
|
||||
|
||||
CurrentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
|
||||
CurrentMediaSourceProviderInfo = GetCurrentDesiredMediaSourceProviderInfo();
|
||||
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
@@ -400,80 +394,65 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
private void MediaManager_OnAnySessionClosed(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
if (_mediaManager.CurrentMediaSessions.Count == 0)
|
||||
{
|
||||
SendNullMessages();
|
||||
}
|
||||
OnDesiredSessionChanged();
|
||||
}
|
||||
|
||||
private void MediaManager_OnAnySessionOpened(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
RecordMediaSourceProviderInfo(mediaSession);
|
||||
SendFocusedMessagesAsync().ConfigureAwait(false);
|
||||
var id = mediaSession.Id;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
RecordMediaSession(id);
|
||||
OnDesiredSessionChanged();
|
||||
});
|
||||
}
|
||||
|
||||
private MediaManager.MediaSession? GetCurrentSession()
|
||||
private void RecordMediaSession(string id)
|
||||
{
|
||||
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
|
||||
if (found == null)
|
||||
{
|
||||
_settingsService.AppSettings.MediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, _settingsService.AppSettings.GeneralSettings.ListenOnNewPlaybackSource));
|
||||
}
|
||||
}
|
||||
|
||||
private MediaManager.MediaSession? GetCurrentDesiredSession()
|
||||
{
|
||||
var focusedSession = _mediaManager.GetFocusedSession();
|
||||
if (focusedSession == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
if (IsMediaSourceEnabled(focusedSession.Id))
|
||||
if (focusedSession != null && IsMediaSourceEnabled(focusedSession.Id))
|
||||
{
|
||||
return focusedSession;
|
||||
}
|
||||
else
|
||||
|
||||
foreach (var session in _mediaManager.CurrentMediaSessions.Values)
|
||||
{
|
||||
foreach (var session in _mediaManager.CurrentMediaSessions.Values)
|
||||
if (IsMediaSourceEnabled(session.Id))
|
||||
{
|
||||
if (IsMediaSourceEnabled(session.Id))
|
||||
{
|
||||
return session;
|
||||
}
|
||||
return session;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void RecordMediaSourceProviderInfo(MediaManager.MediaSession mediaSession)
|
||||
{
|
||||
if (!_mediaManager.IsStarted) return;
|
||||
if (mediaSession == null) return;
|
||||
|
||||
var id = mediaSession?.Id;
|
||||
if (string.IsNullOrEmpty(id)) return;
|
||||
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(x => x.Provider == id);
|
||||
if (found == null)
|
||||
{
|
||||
_settingsService.AppSettings.MediaSourceProvidersInfo.Add(new MediaSourceProviderInfo(id, _settingsService.AppSettings.GeneralSettings.ListenOnNewPlaybackSource));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void SendNullMessages()
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, (() =>
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
|
||||
{
|
||||
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
||||
CurrentIsPlaying = false;
|
||||
|
||||
CurrentMediaSourceProviderInfo = GetCurrentMediaSourceProviderInfo();
|
||||
|
||||
CurrentPosition = TimeSpan.Zero;
|
||||
|
||||
UpdateAlbumArt();
|
||||
UpdateLyrics();
|
||||
|
||||
_scrobbleTimer.Stop();
|
||||
_discordService.Disable();
|
||||
UpdateCurrentMediaSourceProviderInfoPositionOffset();
|
||||
}));
|
||||
});
|
||||
}
|
||||
|
||||
private void UpdateCurrentMediaSourceProviderInfoPositionOffset()
|
||||
@@ -497,21 +476,39 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateTargetScrobbledDuration()
|
||||
{
|
||||
TargetScrobbledDuration = TimeSpan.FromSeconds(CurrentSongInfo.Duration == 0 ? 30 : CurrentSongInfo.Duration / 2);
|
||||
}
|
||||
|
||||
private async Task SendFocusedMessagesAsync()
|
||||
{
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (_currentDesiredSession == null)
|
||||
{
|
||||
SendNullMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
mediaProps = await desiredSession?.ControlSession?.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
catch (Exception) { }
|
||||
var mediaProps = await _currentDesiredSession.ControlSession?.TryGetMediaPropertiesAsync();
|
||||
var timelineProps = _currentDesiredSession.ControlSession?.GetTimelineProperties();
|
||||
var playbackInfo = _currentDesiredSession.ControlSession?.GetPlaybackInfo();
|
||||
|
||||
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession?.ControlSession?.GetTimelineProperties());
|
||||
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
|
||||
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession?.ControlSession?.GetPlaybackInfo());
|
||||
if (mediaProps == null || timelineProps == null || playbackInfo == null)
|
||||
{
|
||||
SendNullMessages();
|
||||
return;
|
||||
}
|
||||
|
||||
MediaManager_OnAnyTimelinePropertyChanged(_currentDesiredSession, timelineProps);
|
||||
MediaManager_OnAnyMediaPropertyChanged(_currentDesiredSession, mediaProps);
|
||||
MediaManager_OnAnyPlaybackStateChanged(_currentDesiredSession, playbackInfo);
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
SendNullMessages();
|
||||
}
|
||||
}
|
||||
|
||||
private void StartSSE()
|
||||
@@ -562,7 +559,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (PlayerIdHelper.IsLXMusic(CurrentSongInfo?.PlayerId))
|
||||
if (PlayerIdHelper.IsLXMusic(CurrentSongInfo.PlayerId))
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
@@ -573,11 +570,11 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
}
|
||||
else if (e.Event == "duration")
|
||||
{
|
||||
CurrentSongInfo?.DurationMs = data.GetDouble() * 1000;
|
||||
CurrentSongInfo.DurationMs = data.GetDouble() * 1000;
|
||||
UpdateDiscordPresence();
|
||||
}
|
||||
|
||||
if (IsMediaSourceTimelineSyncEnabled(CurrentSongInfo?.PlayerId))
|
||||
if (IsMediaSourceTimelineSyncEnabled(CurrentSongInfo.PlayerId))
|
||||
{
|
||||
CurrentPosition = TimeSpan.FromSeconds(_lxMusicPositionSeconds);
|
||||
}
|
||||
@@ -609,47 +606,27 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
public async Task PlayAsync()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await desiredSession.ControlSession?.TryPlayAsync();
|
||||
}
|
||||
await _currentDesiredSession?.ControlSession?.TryPlayAsync();
|
||||
}
|
||||
|
||||
public async Task PauseAsync()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await desiredSession.ControlSession?.TryPauseAsync();
|
||||
}
|
||||
await _currentDesiredSession?.ControlSession?.TryPauseAsync();
|
||||
}
|
||||
|
||||
public async Task PreviousAsync()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await desiredSession.ControlSession?.TrySkipPreviousAsync();
|
||||
}
|
||||
await _currentDesiredSession?.ControlSession?.TrySkipPreviousAsync();
|
||||
}
|
||||
|
||||
public async Task NextAsync()
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await desiredSession.ControlSession?.TrySkipNextAsync();
|
||||
}
|
||||
await _currentDesiredSession?.ControlSession?.TrySkipNextAsync();
|
||||
}
|
||||
|
||||
public async Task ChangePosition(double seconds)
|
||||
{
|
||||
var desiredSession = GetCurrentSession();
|
||||
if (desiredSession != null)
|
||||
{
|
||||
await desiredSession.ControlSession?.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
}
|
||||
await _currentDesiredSession?.ControlSession?.TryChangePlaybackPositionAsync(TimeSpan.FromSeconds(seconds).Ticks);
|
||||
}
|
||||
|
||||
public async Task ChangeLyricsLine(int index)
|
||||
@@ -672,7 +649,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
if (WindowHook.GetWindowHandle<NowPlayingWindow>() is IntPtr hwnd)
|
||||
{
|
||||
TaskbarList.SetProgressValue(hwnd, (ulong)value.TotalSeconds, (ulong)(CurrentSongInfo?.Duration ?? value.TotalSeconds));
|
||||
TaskbarList.SetProgressValue(hwnd, (ulong)value.TotalSeconds, (ulong)(CurrentSongInfo.Duration));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -682,7 +659,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
if (message.PropertyName == nameof(MediaSourceProviderInfo.IsEnabled))
|
||||
{
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
OnDesiredSessionChanged();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is TranslationSettings)
|
||||
@@ -712,7 +689,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
if (message.PropertyName == nameof(MusicGallerySettings.LyricsWindowStatus.IsOpened))
|
||||
{
|
||||
MediaManager_OnFocusedSessionChanged(null);
|
||||
OnDesiredSessionChanged();
|
||||
}
|
||||
}
|
||||
else if (message.Sender is MediaFolder)
|
||||
@@ -8,9 +8,12 @@ using System.Threading.Tasks;
|
||||
using Windows.Storage.Streams;
|
||||
using Windows.UI;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
namespace BetterLyrics.WinUI3.Services.GSMTCService
|
||||
{
|
||||
public interface IMediaSessionsService : INotifyPropertyChanged
|
||||
/// <summary>
|
||||
/// Interface for GlobalSystemMediaTransportControlsSession Service
|
||||
/// </summary>
|
||||
public interface IGSMTCService : INotifyPropertyChanged
|
||||
{
|
||||
Task PlayAsync();
|
||||
Task PauseAsync();
|
||||
@@ -23,8 +26,12 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
|
||||
MediaSourceProviderInfo? CurrentMediaSourceProviderInfo { get; }
|
||||
|
||||
bool IsScrobbled { get; }
|
||||
TimeSpan ScrobbledDuration { get; }
|
||||
TimeSpan TargetScrobbledDuration { get; }
|
||||
|
||||
bool CurrentIsPlaying { get; }
|
||||
SongInfo? CurrentSongInfo { get; }
|
||||
SongInfo CurrentSongInfo { get; }
|
||||
TimeSpan CurrentPosition { get; }
|
||||
LyricsData? CurrentLyricsData { get; }
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Db;
|
||||
using BetterLyrics.WinUI3.Models.Stats;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
@@ -99,7 +101,7 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
|
||||
var totalMs = await context.PlayHistory
|
||||
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
|
||||
.SumAsync(x => x.DurationPlayedMs); // 直接在数据库层面求和
|
||||
.SumAsync(x => Math.Min(x.DurationPlayedMs, x.TotalDurationMs)); // 防止超过歌曲本身时长
|
||||
|
||||
return TimeSpan.FromMilliseconds(totalMs);
|
||||
}
|
||||
@@ -197,7 +199,14 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
("Summer", "Calvin Harris", "Motion"),
|
||||
};
|
||||
|
||||
var playerIds = new[] { "Spotify", "Spotify", "Spotify", "MusicBee", "MusicBee", "QQMusic", "NeteaseCloudMusic", "AppleMusic" };
|
||||
var playerIds = new[]
|
||||
{
|
||||
PlayerId.Spotify, PlayerId.Spotify, PlayerId.Spotify,
|
||||
PlayerId.MusicBee, PlayerId.MusicBee,
|
||||
PlayerId.QQMusic,
|
||||
PlayerId.NetEaseCloudMusic,
|
||||
PlayerId.AppleMusic,
|
||||
};
|
||||
|
||||
var batchList = new List<PlayHistoryItem>();
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.SMTCService
|
||||
{
|
||||
/// <summary>
|
||||
/// Interface for SystemMediaTransportControlsSession Service
|
||||
/// </summary>
|
||||
public interface ISMTCService
|
||||
{
|
||||
public ObservableCollection<PlayQueueItem> TrackPlayingQueue { get; set; }
|
||||
public ExtendedTrack? PlayingTrack { get; set; }
|
||||
|
||||
Task PlayTrackAsync(PlayQueueItem? playQueueItem);
|
||||
Task PlayTrackAtAsync(int index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Windows.ApplicationModel;
|
||||
using Windows.Media;
|
||||
using Windows.Media.Core;
|
||||
using Windows.Media.Playback;
|
||||
using Windows.Storage;
|
||||
using Windows.Storage.Streams;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services.SMTCService
|
||||
{
|
||||
public partial class SMTCService : BaseViewModel, ISMTCService
|
||||
{
|
||||
private readonly MediaPlayer _mediaPlayer;
|
||||
private readonly MediaTimelineController _timelineController;
|
||||
private readonly SystemMediaTransportControls _smtc;
|
||||
|
||||
private IRandomAccessStream? _currentStream;
|
||||
private Stream? _currentNetStream;
|
||||
private IUnifiedFileSystem? _currentProvider;
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
|
||||
[ObservableProperty] public partial ObservableCollection<PlayQueueItem> TrackPlayingQueue { get; set; } = [];
|
||||
[ObservableProperty] public partial ExtendedTrack? PlayingTrack { get; set; }
|
||||
|
||||
public SMTCService(ISettingsService settingsService, IFileSystemService fileSystemService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_fileSystemService = fileSystemService;
|
||||
|
||||
_mediaPlayer = new MediaPlayer();
|
||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||
_mediaPlayer.CommandManager.IsEnabled = false;
|
||||
|
||||
_timelineController = _mediaPlayer.TimelineController = new();
|
||||
_timelineController.PositionChanged += TimelineController_PositionChanged;
|
||||
|
||||
_smtc = _mediaPlayer.SystemMediaTransportControls;
|
||||
_smtc.IsPlayEnabled = true;
|
||||
_smtc.IsPauseEnabled = true;
|
||||
_smtc.IsNextEnabled = true;
|
||||
_smtc.IsPreviousEnabled = true;
|
||||
_smtc.ButtonPressed += Smtc_ButtonPressed;
|
||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||
|
||||
_ = Task.Run(async () =>
|
||||
{
|
||||
var parsedFiles = await _fileSystemService.GetParsedFilesAsync();
|
||||
var playQueue = _settingsService.AppSettings.MusicGallerySettings.PlayQueuePaths
|
||||
.Where(x => !string.IsNullOrWhiteSpace(x))
|
||||
.Select(x =>
|
||||
{
|
||||
var encodedUri = new Uri(x).AbsoluteUri;
|
||||
return new PlayQueueItem(new ExtendedTrack(parsedFiles.FirstOrDefault(y => y.Uri == encodedUri)));
|
||||
});
|
||||
_dispatcherQueue.TryEnqueue(() =>
|
||||
{
|
||||
TrackPlayingQueue = [.. playQueue];
|
||||
TrackPlayingQueue.CollectionChanged += TrackPlayingQueue_CollectionChanged;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
private void Smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
|
||||
{
|
||||
switch (args.Button)
|
||||
{
|
||||
case SystemMediaTransportControlsButton.Play:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
_timelineController.Resume();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Pause:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
|
||||
_timelineController.Pause();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Next:
|
||||
PlayNextTrack();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Previous:
|
||||
PlayPreviousTrack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
|
||||
{
|
||||
_timelineController.Position = args.RequestedPlaybackPosition;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
|
||||
{
|
||||
_timelineController.Start();
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaEnded(MediaPlayer sender, object args)
|
||||
{
|
||||
PlayNextTrack();
|
||||
}
|
||||
|
||||
private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
|
||||
{
|
||||
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
|
||||
{
|
||||
Position = sender.Position,
|
||||
EndTime = _mediaPlayer.PlaybackSession.NaturalDuration
|
||||
});
|
||||
}
|
||||
|
||||
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
_settingsService.AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.Uri.ToDecodedAbsoluteUri())];
|
||||
}
|
||||
|
||||
private string GetMimeType(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path).ToLower();
|
||||
return ext switch
|
||||
{
|
||||
".mp3" => "audio/mpeg",
|
||||
".flac" => "audio/flac",
|
||||
".wav" => "audio/wav",
|
||||
".m4a" => "audio/mp4",
|
||||
".aac" => "audio/aac",
|
||||
".ogg" => "audio/ogg",
|
||||
".wma" => "audio/x-ms-wma",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
||||
private void PlayNextTrack()
|
||||
{
|
||||
var musicGallerySettings = _settingsService.AppSettings.MusicGallerySettings;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
switch (musicGallerySettings.PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
if (musicGallerySettings.PlayQueueIndex < TrackPlayingQueue.Count - 1)
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex = 0;
|
||||
}
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
//_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
await PlayTrackAtAsync(musicGallerySettings.PlayQueueIndex);
|
||||
});
|
||||
}
|
||||
|
||||
private void PlayPreviousTrack()
|
||||
{
|
||||
var musicGallerySettings = _settingsService.AppSettings.MusicGallerySettings;
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
switch (musicGallerySettings.PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
if (musicGallerySettings.PlayQueueIndex > 0)
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex = TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
//_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
musicGallerySettings.PlayQueueIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
await PlayTrackAtAsync(musicGallerySettings.PlayQueueIndex);
|
||||
});
|
||||
}
|
||||
|
||||
public async Task PlayTrackAsync(PlayQueueItem? playQueueItem)
|
||||
{
|
||||
_timelineController.Pause();
|
||||
_mediaPlayer.Source = null;
|
||||
|
||||
// 清理旧资源
|
||||
_currentStream?.Dispose();
|
||||
_currentNetStream?.Dispose();
|
||||
_currentStream = null;
|
||||
_currentNetStream = null;
|
||||
|
||||
if (playQueueItem == null)
|
||||
{
|
||||
_smtc.IsEnabled = false;
|
||||
_smtc.DisplayUpdater.ClearAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingTrack = playQueueItem.Track;
|
||||
_smtc.IsEnabled = true;
|
||||
|
||||
try
|
||||
{
|
||||
var targetFolder = _settingsService.AppSettings.LocalMediaFolders.FirstOrDefault(f =>
|
||||
{
|
||||
var fUri = f.GetStandardUri().AbsoluteUri;
|
||||
return PlayingTrack.Uri.StartsWith(fUri, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (targetFolder == null)
|
||||
{
|
||||
throw new FileNotFoundException(null, PlayingTrack.Uri.ToDecodedAbsoluteUri());
|
||||
}
|
||||
|
||||
_currentProvider = targetFolder.CreateFileSystem();
|
||||
if (_currentProvider == null) return;
|
||||
|
||||
await _currentProvider.ConnectAsync();
|
||||
|
||||
var fileCacheStub = new FilesIndexItem
|
||||
{
|
||||
Uri = PlayingTrack.Uri
|
||||
};
|
||||
|
||||
var sourceStream = await _fileSystemService.OpenFileAsync(_currentProvider, fileCacheStub);
|
||||
|
||||
if (sourceStream == null)
|
||||
{
|
||||
throw new FileNotFoundException(null, fileCacheStub.Uri);
|
||||
}
|
||||
|
||||
if (sourceStream.CanSeek)
|
||||
{
|
||||
_currentNetStream = sourceStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
var memStream = new MemoryStream();
|
||||
|
||||
await sourceStream.CopyToAsync(memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
sourceStream.Dispose();
|
||||
|
||||
_currentNetStream = memStream;
|
||||
}
|
||||
|
||||
_currentStream = _currentNetStream.AsRandomAccessStream();
|
||||
|
||||
string contentType = GetMimeType(PlayingTrack.FileName);
|
||||
var mediaSource = MediaSource.CreateFromStream(_currentStream, contentType);
|
||||
|
||||
_mediaPlayer.Source = mediaSource;
|
||||
|
||||
var updater = _smtc.DisplayUpdater;
|
||||
updater.Type = MediaPlaybackType.Music;
|
||||
|
||||
updater.MusicProperties.Title = PlayingTrack.Title ?? PlayingTrack.FileName;
|
||||
updater.MusicProperties.Artist = PlayingTrack.Artist ?? "";
|
||||
updater.MusicProperties.AlbumTitle = PlayingTrack.Album ?? "";
|
||||
|
||||
updater.MusicProperties.Genres.Clear();
|
||||
updater.MusicProperties.Genres.Add($"{ExtendedGenreFiled.FileName}{Path.GetFileNameWithoutExtension(PlayingTrack.FileName)}");
|
||||
|
||||
updater.AppMediaId = Package.Current.Id.FullName;
|
||||
|
||||
if (!string.IsNullOrEmpty(PlayingTrack.LocalAlbumArtPath) && File.Exists(PlayingTrack.LocalAlbumArtPath))
|
||||
{
|
||||
var storageFile = await StorageFile.GetFileFromPathAsync(PlayingTrack.LocalAlbumArtPath);
|
||||
updater.Thumbnail = RandomAccessStreamReference.CreateFromFile(storageFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.Thumbnail = null;
|
||||
}
|
||||
|
||||
updater.Update();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastHelper.ShowToast("Error", ex.Message, InfoBarSeverity.Error);
|
||||
_timelineController.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public async Task PlayTrackAtAsync(int index)
|
||||
{
|
||||
await PlayTrackAsync(TrackPlayingQueue.ElementAtOrDefault(index));
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>الوضع القياسي</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>النشاط بالساعة</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>مخصص</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>النهاية</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>الأقل نشاطاً</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>الأكثر نشاطاً</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>سكروبلينج...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>المصادر</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>ابدأ</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>هذا الشهر</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>هذا الربع</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>هذا الأسبوع</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>هذا العام</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>النطاق الزمني</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>التايمز</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>اليوم</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>المدة الإجمالية</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>التايمز</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>التايمز</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>المسارات التي تم تشغيلها</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Standard-Modus</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Aktivität nach Stunden</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Benutzerdefiniert</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Ende</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Am wenigsten aktiv</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Aktivste</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Scrobbling...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Quellen</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Dieser Monat</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Dieses Quartal</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Diese Woche</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Dieses Jahr</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Zeitspanne</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Zeiten</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Heute</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Gesamtdauer</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Zeiten</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Zeiten</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Gespielte Tracks</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Standard Mode</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Activity by Hour</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Custom</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>End</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Least Active</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Most Active</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Scrobbling...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Sources</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Start</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>This Month</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>This Quarter</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>This Week</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>This Year</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Time Range</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Today</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Total Duration</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Tracks Played</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Modo Estándar</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Actividad por horas</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>A medida</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Fin</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Menos activo</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Más activos</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Scrobbling...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Fuentes</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Inicio</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Este mes</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Este trimestre</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Esta semana</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Este año</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Intervalo de tiempo</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Hoy</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Duración total</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Pistas reproducidas</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Mode Standard</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Activité par heure</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Sur mesure</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Fin</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Le moins actif</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Les plus actifs</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Scrobbling...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Sources d'information</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Démarrage</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Ce mois-ci</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Ce trimestre</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Cette semaine</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Cette année</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Plage de temps</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Temps</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Aujourd'hui</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Durée totale</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Temps</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Temps</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Pistes jouées</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>मानक मोड</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>घंटे के हिसाब से गतिविधि</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>कस्टम</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>समाप्त</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>सबसे कम सक्रिय</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>सर्वाधिक सक्रिय</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>रिकॉर्डिंग...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>स्रोत</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>शुरू करें</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>इस महीने</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>इस तिमाही</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>इस सप्ताह</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>इस वर्ष</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>समय सीमा</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>दूसरे दर्जे का</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>आज</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>कुल अवधि</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>दूसरे दर्जे का</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>दूसरे दर्जे का</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>बजाए गए ट्रैक</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Mode Standar</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Aktivitas per Jam</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Kustom</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Akhir</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Paling Tidak Aktif</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Paling Aktif</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Menggelinding...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Sumber</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Mulai</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Bulan ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Kuartal ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Minggu Ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Tahun ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Rentang Waktu</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Waktu</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Hari ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Total Durasi</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Waktu</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Waktu</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Lagu yang Dimainkan</value>
|
||||
</data>
|
||||
|
||||
@@ -169,7 +169,7 @@
|
||||
<value>LX Music サーバーに接続できません。「設定」-「再生ソース」-「LX Music」-「LX Music サーバー」に移動し、リンクが正しく入力されているか確認してください</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
||||
<value>キャッシュをクリーニング中...</value>
|
||||
<value>キャッシュクリア中...</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
||||
<value>接続に失敗しました</value>
|
||||
@@ -181,19 +181,19 @@
|
||||
<value>ファイルリストを取得しています...</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceParsing" xml:space="preserve">
|
||||
<value>Parsing...</value>
|
||||
<value>解析中...</value>
|
||||
</data>
|
||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||
<value>キャッシュ・クリーン準備中</value>
|
||||
<value>キャッシュクリア準備中...</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||
<value>準備完了</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||
<value>ルートディレクトリのパスが検出された。フルディスクインデックスにはメディア以外のファイルが多数含まれている可能性があり、スキャンに時間がかかりすぎる。特定のサブディレクトリを指定することを推奨する。</value>
|
||||
<value>ルートディレクトリが指定されました。フルディスクのインデックス作成には大量の非メディアファイルが含まれる可能性があり、スキャンに時間がかかる恐れがあります。特定のサブディレクトリを指定することをお勧めします。</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
||||
<value>スキャンを待っています...</value>
|
||||
<value>スキャン準備中...</value>
|
||||
</data>
|
||||
<data name="FullscreenMode" xml:space="preserve">
|
||||
<value>全画面モード</value>
|
||||
@@ -259,13 +259,13 @@
|
||||
<value>歌詞の言語</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsProviderPrefix.Header" xml:space="preserve">
|
||||
<value>歌詞提供者</value>
|
||||
<value>歌詞提供元</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSearch.Text" xml:space="preserve">
|
||||
<value>歌詞検索</value>
|
||||
</data>
|
||||
<data name="LyricsPageLyricsSettings.Text" xml:space="preserve">
|
||||
<value>歌詞ウィンドウのショートカット</value>
|
||||
<value>歌詞ウィンドウのショートカット設定</value>
|
||||
</data>
|
||||
<data name="LyricsPageMatchPercentage.Header" xml:space="preserve">
|
||||
<value>一致率</value>
|
||||
@@ -289,7 +289,7 @@
|
||||
<value>翻訳提供元</value>
|
||||
</data>
|
||||
<data name="LyricsPageTransliterationProviderPrefix.Header" xml:space="preserve">
|
||||
<value>翻訳提供元</value>
|
||||
<value>ルビ提供元</value>
|
||||
</data>
|
||||
<data name="LyricsParseError" xml:space="preserve">
|
||||
<value>歌詞の解析に失敗しました</value>
|
||||
@@ -307,7 +307,7 @@
|
||||
<value>曲の長さ</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlHelp.Text" xml:space="preserve">
|
||||
<value>* 変更は保存後すぐに反映され、マッピング情報とターゲットの歌詞を使用して後続の曲の歌詞が取得されます。 「Instrumental」としてマークすると、「Instrumental」プレースホルダの歌詞に直接戻ります。 リセットすると元のデータの検索が復元されます。</value>
|
||||
<value>* 変更は保存後すぐに反映され、以降はマッピング情報に基づいた歌詞取得が行われます。「インストゥルメンタル」としてマークすると、専用のプレースホルダー歌詞に切り替わります。リセットを実行すると、元のデータ取得状態に戻ります。</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlIgnoreCache.Header" xml:space="preserve">
|
||||
<value>検索時にキャッシュを無視する</value>
|
||||
@@ -316,7 +316,7 @@
|
||||
<value>マッピング先</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlMarkAsPureMusic.Content" xml:space="preserve">
|
||||
<value>インストとしてマーク</value>
|
||||
<value>インストゥルメンタルとしてマーク</value>
|
||||
</data>
|
||||
<data name="LyricsSearchControlNotFound.Text" xml:space="preserve">
|
||||
<value>見つかりません</value>
|
||||
@@ -367,10 +367,10 @@
|
||||
<value>さらにモードを追加するには「設定」に移動します</value>
|
||||
</data>
|
||||
<data name="LyricsWindowSwitchWindowTitle" xml:space="preserve">
|
||||
<value>歌詞ウィンドウ切替</value>
|
||||
<value>歌詞ウィンドウスイッチャー</value>
|
||||
</data>
|
||||
<data name="MainPageAlbumArtOnly.Content" xml:space="preserve">
|
||||
<value>アルバムアートエリアのみ表示</value>
|
||||
<value>アルバムアートのみ表示</value>
|
||||
</data>
|
||||
<data name="MainPageLyriscOnly.Content" xml:space="preserve">
|
||||
<value>歌詞のみ表示</value>
|
||||
@@ -412,13 +412,13 @@
|
||||
<value>再生キューに追加</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||
<value>すべてのミュージック</value>
|
||||
<value>すべての曲</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
|
||||
<value>メディアライブラリー同期中...</value>
|
||||
<value>メディアライブラリーの同期中...</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value>メディアライブラリの同期に問題があります</value>
|
||||
<value>メディアライブラリーの同期に問題が発生しました</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>キューをクリア</value>
|
||||
@@ -460,7 +460,7 @@
|
||||
<value>年</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
|
||||
<value>メディアライブラリに曲が見つかりません</value>
|
||||
<value>メディアライブラリーに曲が見つかりません</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
|
||||
<value>フォルダ</value>
|
||||
@@ -514,7 +514,7 @@
|
||||
<value>タイトル</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageSortType.Text" xml:space="preserve">
|
||||
<value>ソートタイプ</value>
|
||||
<value>並べ替えタイプ</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageStopTrack.Text" xml:space="preserve">
|
||||
<value>停止</value>
|
||||
@@ -523,28 +523,28 @@
|
||||
<value>ミュージックギャラリー - BetterLyrics</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
|
||||
<value>自動同期周波数</value>
|
||||
<value>自動同期の頻度</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
||||
<value>一切なし</value>
|
||||
<value>なし</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
|
||||
<value>毎日</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
|
||||
<value>15分ごと</value>
|
||||
<value>15分間隔</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
|
||||
<value>1時間ごと</value>
|
||||
<value>1時間間隔</value>
|
||||
</data>
|
||||
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
|
||||
<value>6時間ごと</value>
|
||||
<value>6時間間隔</value>
|
||||
</data>
|
||||
<data name="NarrowMode" xml:space="preserve">
|
||||
<value>狭い表示モード</value>
|
||||
</data>
|
||||
<data name="PictureInPictureMode" xml:space="preserve">
|
||||
<value>ピクチャー イン ピクチャー モード</value>
|
||||
<value>ピクチャーインピクチャーモード</value>
|
||||
</data>
|
||||
<data name="Pinyin" xml:space="preserve">
|
||||
<value>ピンイン (中国語)</value>
|
||||
@@ -556,13 +556,13 @@
|
||||
<value>個人情報保護方針</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
|
||||
<value>ブラウズ</value>
|
||||
<value>参照</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
|
||||
<value>名称</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
|
||||
<value>空白のままにしておくと、自動的にデフォルトの名前が生成される。</value>
|
||||
<value>空欄のままにすると、デフォルトの名前が自動生成されます。</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||
<value>パスワード</value>
|
||||
@@ -571,7 +571,7 @@
|
||||
<value>パス</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
|
||||
<value>指定されたフォルダのパスが見つかりません</value>
|
||||
<value>指定されたパスが見つかりません</value>
|
||||
</data>
|
||||
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
|
||||
<value>パスが必要</value>
|
||||
@@ -613,7 +613,7 @@
|
||||
<value>デザイン参考</value>
|
||||
</data>
|
||||
<data name="SettingsPage3DLyrics.Header" xml:space="preserve">
|
||||
<value>[実験的] 深度エフェクト</value>
|
||||
<value>[実験的] 奥行きのエフェクト</value>
|
||||
</data>
|
||||
<data name="SettingsPage3DLyricsDepth.Header" xml:space="preserve">
|
||||
<value>深度</value>
|
||||
@@ -640,7 +640,7 @@
|
||||
<value>アルバムアートの高さ</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtLayer.Header" xml:space="preserve">
|
||||
<value>アルバムアートのレイヤー</value>
|
||||
<value>アルバムアートレイヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageAlbumArtSearchProvidersConfig.Text" xml:space="preserve">
|
||||
<value>アルバムアートのソースを編集する</value>
|
||||
@@ -688,10 +688,10 @@
|
||||
<value>起動時にデフォルトの歌詞ウィンドウを自動的に開く</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoOpenMusicGalleryWindow.Header" xml:space="preserve">
|
||||
<value>起動時にギャラリーウィンドウを開く</value>
|
||||
<value>起動時にミュージックギャラリーウィンドウを開く</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoPlayWhenOpenMusicGalleryWindow.Header" xml:space="preserve">
|
||||
<value>ギャラリーウィンドウを開いたときに自動的に再生を再開</value>
|
||||
<value>ミュージックギャラリーを開いたときに自動的に再生を再開する</value>
|
||||
</data>
|
||||
<data name="SettingsPageAutoStart.Header" xml:space="preserve">
|
||||
<value>自動起動</value>
|
||||
@@ -703,7 +703,7 @@
|
||||
<value>ぼかし量</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||
<value>ログファイル、オンライン歌詞キャッシュを含む</value>
|
||||
<value>ログファイル、オンライン歌詞キャッシュを含みます</value>
|
||||
</data>
|
||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||
<value>キャッシュ</value>
|
||||
@@ -712,13 +712,13 @@
|
||||
<value>中央揃え</value>
|
||||
</data>
|
||||
<data name="SettingsPageCheckShortcut.Content" xml:space="preserve">
|
||||
<value>キー割り当ての確認</value>
|
||||
<value>ショートカットキー確認</value>
|
||||
</data>
|
||||
<data name="SettingsPageChinese.Header" xml:space="preserve">
|
||||
<value>ピンインルビ</value>
|
||||
</data>
|
||||
<data name="SettingsPageChineseLyrics.Text" xml:space="preserve">
|
||||
<value>中国語の歌詞</value>
|
||||
<value>中国語歌詞</value>
|
||||
</data>
|
||||
<data name="SettingsPageChinesePreference.Header" xml:space="preserve">
|
||||
<value>簡体字から繁体字へ変換</value>
|
||||
@@ -730,7 +730,7 @@
|
||||
<value>クリア</value>
|
||||
</data>
|
||||
<data name="SettingsPageClearCache.Content" xml:space="preserve">
|
||||
<value>キャッシュファイルをクリア</value>
|
||||
<value>キャッシュファイルクリア</value>
|
||||
</data>
|
||||
<data name="SettingsPageCloseStatus.Text" xml:space="preserve">
|
||||
<value>閉じる</value>
|
||||
@@ -775,7 +775,7 @@
|
||||
<value>Discord Presence で視聴ステータスを表示</value>
|
||||
</data>
|
||||
<data name="SettingsPageDisplayTypeSwitcher.Header" xml:space="preserve">
|
||||
<value>レイアウト</value>
|
||||
<value>レイアウトモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageDockedMode.Text" xml:space="preserve">
|
||||
<value>ドッキングモード</value>
|
||||
@@ -859,7 +859,7 @@
|
||||
<value>歌詞ウィンドウを閉じたときにアプリを終了する</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||
<value>プレー履歴のエクスポート</value>
|
||||
<value>再生履歴をエクスポート</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||
<value>設定をエクスポート</value>
|
||||
@@ -874,19 +874,19 @@
|
||||
<value>流体レイヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageFogLayer.Header" xml:space="preserve">
|
||||
<value>霧のレイヤー</value>
|
||||
<value>霧レイヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageFollowSystem.Content" xml:space="preserve">
|
||||
<value>システムに従う</value>
|
||||
</data>
|
||||
<data name="SettingsPageFontColor.Header" xml:space="preserve">
|
||||
<value>フォントの色</value>
|
||||
<value>フォント色</value>
|
||||
</data>
|
||||
<data name="SettingsPageForceAlwaysOnTop.Description" xml:space="preserve">
|
||||
<value>定期チェックで最前面表示を強制維持します</value>
|
||||
</data>
|
||||
<data name="SettingsPageForceAlwaysOnTop.Header" xml:space="preserve">
|
||||
<value>常に上部に強制的に表示</value>
|
||||
<value>常にトップに強制的に表示</value>
|
||||
</data>
|
||||
<data name="SettingsPageForceWordByWordEffect.Description" xml:space="preserve">
|
||||
<value>現在の歌詞に文字単位の情報がない場合でも、文字単位の歌詞シミュレーションをします</value>
|
||||
@@ -907,7 +907,7 @@
|
||||
<value>このアプリの翻訳に協力する 🌏</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Description" xml:space="preserve">
|
||||
<value>音楽の再生が停止した場合、自動的に歌詞ウィンドウを非表示/表示する</value>
|
||||
<value>ミュージックの再生が停止した場合、自動的に歌詞ウィンドウを非表示/表示する</value>
|
||||
</data>
|
||||
<data name="SettingsPageHideWindow.Header" xml:space="preserve">
|
||||
<value>ウィンドウの自動非表示/表示</value>
|
||||
@@ -1021,7 +1021,7 @@
|
||||
<value>現在の行の位置</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsEffect.Text" xml:space="preserve">
|
||||
<value>歌詞エフェクト</value>
|
||||
<value>歌詞のエフェクト</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsExtraBlack.Content" xml:space="preserve">
|
||||
<value>超極太</value>
|
||||
@@ -1099,7 +1099,7 @@
|
||||
<value>ベストマッチ</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Text" xml:space="preserve">
|
||||
<value>歌詞元の設定</value>
|
||||
<value>歌詞ソースの設定</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchSequential.Content" xml:space="preserve">
|
||||
<value>順次</value>
|
||||
@@ -1108,7 +1108,7 @@
|
||||
<value>順次:以下のリストの優先順位に従って検索し、最初に見つかった結果を返します。ベストマッチ:有効なすべてのソースを検索し、一致スコアが最も高い結果を自動的に選択します</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSearchType.Header" xml:space="preserve">
|
||||
<value>歌詞検索方法</value>
|
||||
<value>歌詞検索の方法</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsSemiBold.Content" xml:space="preserve">
|
||||
<value>中太</value>
|
||||
@@ -1117,7 +1117,7 @@
|
||||
<value>準細</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsStyle.Text" xml:space="preserve">
|
||||
<value>歌詞スタイル</value>
|
||||
<value>歌詞のスタイル</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsThin.Content" xml:space="preserve">
|
||||
<value>極細</value>
|
||||
@@ -1138,16 +1138,16 @@
|
||||
<value>歌詞ウィンドウマネージャー</value>
|
||||
</data>
|
||||
<data name="SettingsPageLyricsWindowSwitchHotKey.Header" xml:space="preserve">
|
||||
<value>歌詞ウィンドウ状態切り替えショートカット</value>
|
||||
<value>歌詞ウィンドウスイッチャーのショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPageMatchingThreshold.Description" xml:space="preserve">
|
||||
<value>この値を調整すると、順次検索とベストマッチ検索の結果に影響しますが、手動歌詞検索インターフェイスの検索結果には影響しません</value>
|
||||
</data>
|
||||
<data name="SettingsPageMatchingThreshold.Header" xml:space="preserve">
|
||||
<value>一致度の最低ライン</value>
|
||||
<value>最小一致しきい値</value>
|
||||
</data>
|
||||
<data name="SettingsPageMediaLib.Content" xml:space="preserve">
|
||||
<value>メディアライブラリ</value>
|
||||
<value>メディアライブラリー</value>
|
||||
</data>
|
||||
<data name="SettingsPageMedianCut.Content" xml:space="preserve">
|
||||
<value>保守的</value>
|
||||
@@ -1156,10 +1156,10 @@
|
||||
<value>この再生ソースを監視する</value>
|
||||
</data>
|
||||
<data name="SettingsPageMockMusicPlaying.Header" xml:space="preserve">
|
||||
<value>テスト音楽を再生</value>
|
||||
<value>テストミュージックを再生</value>
|
||||
</data>
|
||||
<data name="SettingsPageMultiNowPlayingWindows.Header" xml:space="preserve">
|
||||
<value>マルチウィンドウ モード</value>
|
||||
<value>マルチウィンドウモード</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicGallery.Text" xml:space="preserve">
|
||||
<value>ミュージックギャラリー</value>
|
||||
@@ -1171,10 +1171,10 @@
|
||||
<value>ミュージック ギャラリーが開いているため、他の再生ソースを無視します</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Description" xml:space="preserve">
|
||||
<value>音楽または歌詞が含まれるフォルダを追加</value>
|
||||
<value>ミュージックまたは歌詞が含まれるフォルダを追加</value>
|
||||
</data>
|
||||
<data name="SettingsPageMusicLib.Header" xml:space="preserve">
|
||||
<value>ローカルメディアライブラリ</value>
|
||||
<value>ローカルメディアライブラリー</value>
|
||||
</data>
|
||||
<data name="SettingsPageNarrowMode.Text" xml:space="preserve">
|
||||
<value>狭い表示モード</value>
|
||||
@@ -1213,10 +1213,10 @@
|
||||
<value>スポンサー</value>
|
||||
</data>
|
||||
<data name="SettingsPagePhonetic.Text" xml:space="preserve">
|
||||
<value>歌詞のルビ</value>
|
||||
<value>歌詞ルビ</value>
|
||||
</data>
|
||||
<data name="SettingsPagePhoneticText.Header" xml:space="preserve">
|
||||
<value>ルビのサイズ</value>
|
||||
<value>ルビ</value>
|
||||
</data>
|
||||
<data name="SettingsPagePinToTaskbar.Header" xml:space="preserve">
|
||||
<value>タスクバーにピン留め</value>
|
||||
@@ -1243,13 +1243,13 @@
|
||||
<value>"soundcloud.com" で "Cut to the Feeling" を再生</value>
|
||||
</data>
|
||||
<data name="SettingsPagePlayOrPauseSongHotKey.Header" xml:space="preserve">
|
||||
<value>再生/一時停止のショートカット</value>
|
||||
<value>再生/一時停止のショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPagePreviousSongHotKey.Header" xml:space="preserve">
|
||||
<value>前の曲へのショートカット</value>
|
||||
<value>次のトラックのショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPagePureLayer.Header" xml:space="preserve">
|
||||
<value>単色のレイヤー</value>
|
||||
<value>単色レイヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageRealtimeStatus.Text" xml:space="preserve">
|
||||
<value>リアルタイムステータス</value>
|
||||
@@ -1315,13 +1315,13 @@
|
||||
<value>設定マネージャー</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>プレーの歴史</value>
|
||||
<value>再生履歴</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>オンライン共有センターを閲覧する</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcut.Text" xml:space="preserve">
|
||||
<value>ショートカット</value>
|
||||
<value>ショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
|
||||
<value>ホットキーの登録に失敗しました</value>
|
||||
@@ -1336,7 +1336,7 @@
|
||||
<value>アーティストを表示</value>
|
||||
</data>
|
||||
<data name="SettingsPageShowHideHotKey.Header" xml:space="preserve">
|
||||
<value>歌詞ウィンドウの表示/非表示ショートカット</value>
|
||||
<value>歌詞ウィンドウの表示/非表示のショートカットキー</value>
|
||||
</data>
|
||||
<data name="SettingsPageShowInSwitchers.Description" xml:space="preserve">
|
||||
<value>例: Alt + Tab、タスクバー</value>
|
||||
@@ -1351,7 +1351,7 @@
|
||||
<value>スライド</value>
|
||||
</data>
|
||||
<data name="SettingsPageSnowFlakeLayer.Header" xml:space="preserve">
|
||||
<value>雪のレイヤー</value>
|
||||
<value>雪レイヤー</value>
|
||||
</data>
|
||||
<data name="SettingsPageSongInfo.Text" xml:space="preserve">
|
||||
<value>楽曲情報</value>
|
||||
@@ -1393,7 +1393,7 @@
|
||||
<value>スタートアップ</value>
|
||||
</data>
|
||||
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||
<value>統計</value>
|
||||
<value>ミュージックレポート</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>ミュージックギャラリーウィンドウを閉じたときに再生を停止する</value>
|
||||
@@ -1435,7 +1435,7 @@
|
||||
<value>訳文</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslation.Text" xml:space="preserve">
|
||||
<value>歌詞の翻訳</value>
|
||||
<value>歌詞翻訳</value>
|
||||
</data>
|
||||
<data name="SettingsPageTranslationConfig.Header" xml:space="preserve">
|
||||
<value>LibreTranslate サービス</value>
|
||||
@@ -1467,38 +1467,71 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>標準モード</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>情報源</value>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>アクティブ時間帯</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>カスタム</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>終了</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>最少アクティブ</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>最多アクティブ</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>再生記録中...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>再生ソース</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>開始</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>今月</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>今期</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>今週</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>今年</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>期間</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>回</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>今日</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
<value>トップアーティスト</value>
|
||||
<value>よく聴くアーティスト</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
|
||||
<value>トップトラック</value>
|
||||
<value>よく聴く曲</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
|
||||
<value>トップ・ソース</value>
|
||||
<value>よく使う再生ソース</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>総所要時間</value>
|
||||
<value>総再生時間</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>回</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>回</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>演奏曲目</value>
|
||||
<value>再生された曲の数</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>終了</value>
|
||||
@@ -1516,16 +1549,16 @@
|
||||
<value>再起動</value>
|
||||
</data>
|
||||
<data name="SystemTraySearch.Text" xml:space="preserve">
|
||||
<value>歌詞の検索ウィンドウ</value>
|
||||
<value>歌詞検索ウィンドウ</value>
|
||||
</data>
|
||||
<data name="SystemTraySettings.Text" xml:space="preserve">
|
||||
<value>設定を開く</value>
|
||||
</data>
|
||||
<data name="SystemTraySwitch.Text" xml:space="preserve">
|
||||
<value>歌詞ウィンドウ切り替え</value>
|
||||
<value>歌詞ウィンドウスイッチャー</value>
|
||||
</data>
|
||||
<data name="TaskbarMode" xml:space="preserve">
|
||||
<value>タスクバー モード</value>
|
||||
<value>タスクバーモード</value>
|
||||
</data>
|
||||
<data name="TermsOfService.Content" xml:space="preserve">
|
||||
<value>利用規約</value>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>표준 모드</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>시간별 활동</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>사용자 지정</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>종료</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>최소 활성</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>가장 활동적인</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>스크러블...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>출처</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>시작</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>이번 달</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>이번 분기</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>이번 주</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>올해</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>시간 범위</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>시간</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>오늘</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>총 기간</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>시간</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>시간</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>재생된 트랙</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Mod Standard</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Aktiviti mengikut Jam</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Tersuai</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Penamat</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Kurang Aktif</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Paling Aktif</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Rakaman...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Sumber</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Mula</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Bulan Ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<value>Suku Tahun Ini</value>
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Suku ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Minggu Ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<value>Tahun Ini</value>
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Tahun ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Julat masa</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Kelas kedua</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Hari ini</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Jumlah Tempoh</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Kelas kedua</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Kelas kedua</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Trek Dimainkan</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Modo Padrão</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Atividade por hora</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Personalizado</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Fim</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Menos ativo</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Mais activos</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>A fazer barulho...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Fontes</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Início</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Este mês</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Este trimestre</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Esta semana</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Este ano</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Intervalo de tempo</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Tempos</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Hoje</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Duração total</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Tempos</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Tempos</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Faixas reproduzidas</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Стандартный режим</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Активность по часам</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Пользовательское</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Конец</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Наименее активный</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Самые активные</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Скроблинг...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Источники</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Начало</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Этот месяц</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Этот квартал</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>На этой неделе</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>В этом году</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Диапазон времени</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Сегодня</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Общая продолжительность</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Times</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Воспроизведенные треки</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>โหมดมาตรฐาน</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>กิจกรรมตามชั่วโมง</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>กำหนดเอง</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>สิ้นสุด</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>น้อยที่สุด</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>กิจกรรมล่าสุด</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>กำลังส่งข้อมูล...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>แหล่งข้อมูล</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>เริ่มต้น</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>เดือนนี้</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>ไตรมาสนี้</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>สัปดาห์นี้</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>ปีนี้</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>ช่วงเวลา</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>เวลา</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>วันนี้</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>ระยะเวลาทั้งหมด</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>เวลา</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>เวลา</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>เพลงที่เล่น</value>
|
||||
</data>
|
||||
|
||||
@@ -1467,22 +1467,49 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>Chế độ tiêu chuẩn</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>Hoạt động theo giờ</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>Tùy chỉnh</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>Kết thúc</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>Hoạt động ít nhất</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>Hoạt động nhiều nhất</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>Đang ghi lại...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>Nguồn</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>Bắt đầu</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>Tháng này</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>Quý này</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>Tuần này</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>Năm nay</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>Khoảng thời gian</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>Thời gian</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>Hôm nay</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
@@ -1497,6 +1524,12 @@
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>Thời gian tổng cộng</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>Thời gian</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>Thời gian</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>Các bài hát đã phát</value>
|
||||
</data>
|
||||
|
||||
@@ -59,46 +59,46 @@
|
||||
: using a System.ComponentModel.TypeConverter
|
||||
: and then encoded with base64 encoding.
|
||||
-->
|
||||
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
|
||||
<xsd:schema xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata" id="root">
|
||||
<xsd:import namespace="http://www.w3.org/XML/1998/namespace"/>
|
||||
<xsd:element name="root" msdata:IsDataSet="true">
|
||||
<xsd:complexType>
|
||||
<xsd:choice maxOccurs="unbounded">
|
||||
<xsd:element name="metadata">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" use="required" type="xsd:string" />
|
||||
<xsd:attribute name="type" type="xsd:string" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" use="required" type="xsd:string"/>
|
||||
<xsd:attribute name="type" type="xsd:string"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="assembly">
|
||||
<xsd:complexType>
|
||||
<xsd:attribute name="alias" type="xsd:string" />
|
||||
<xsd:attribute name="name" type="xsd:string" />
|
||||
<xsd:attribute name="alias" type="xsd:string"/>
|
||||
<xsd:attribute name="name" type="xsd:string"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="data">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
|
||||
<xsd:attribute ref="xml:space" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1"/>
|
||||
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3"/>
|
||||
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4"/>
|
||||
<xsd:attribute ref="xml:space"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
<xsd:element name="resheader">
|
||||
<xsd:complexType>
|
||||
<xsd:sequence>
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
|
||||
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1"/>
|
||||
</xsd:sequence>
|
||||
<xsd:attribute name="name" type="xsd:string" use="required" />
|
||||
<xsd:attribute name="name" type="xsd:string" use="required"/>
|
||||
</xsd:complexType>
|
||||
</xsd:element>
|
||||
</xsd:choice>
|
||||
@@ -418,7 +418,7 @@
|
||||
<value>正在同步媒体库...</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value>同步媒体库时出现问题</value>
|
||||
<value>媒体库同步出现问题</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放队列</value>
|
||||
@@ -859,7 +859,7 @@
|
||||
<value>关闭歌词窗口时退出程序</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||
<value />
|
||||
<value>导出播放记录</value>
|
||||
</data>
|
||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||
<value>导出设置</value>
|
||||
@@ -1314,6 +1314,9 @@
|
||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||
<value>设置管理器</value>
|
||||
</data>
|
||||
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||
<value>播放记录</value>
|
||||
</data>
|
||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||
<value>浏览在线资源共享中心</value>
|
||||
</data>
|
||||
@@ -1390,7 +1393,7 @@
|
||||
<value>启动</value>
|
||||
</data>
|
||||
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||
<value>统计数据</value>
|
||||
<value>音乐报告</value>
|
||||
</data>
|
||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||
<value>关闭音乐库窗口时停止播放</value>
|
||||
@@ -1464,38 +1467,71 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>标准模式</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>活跃时段</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>自定义</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>结束</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>最不活跃时段</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>最活跃时段</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>记录中...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>播放源</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>开始</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>本月</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>本季度</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>本周</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>本年度</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>时间范围</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>今日</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
<value>最热爱的艺人</value>
|
||||
<value>常听的歌手</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
|
||||
<value>最喜欢的歌曲</value>
|
||||
<value>常听的曲目</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
|
||||
<value>最常用的播放源</value>
|
||||
<value>常用的播放源</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>总时长</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>播放的曲目</value>
|
||||
<value>已播放的曲目数</value>
|
||||
</data>
|
||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||
<value>退出程序</value>
|
||||
|
||||
@@ -193,7 +193,7 @@
|
||||
<value>已偵測到根目錄路徑。全磁碟索引可能包含大量非媒體檔案,導致掃描時間過長。建議指定特定的子目錄。</value>
|
||||
</data>
|
||||
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
||||
<value>等待掃描...</value>
|
||||
<value>準備掃描...</value>
|
||||
</data>
|
||||
<data name="FullscreenMode" xml:space="preserve">
|
||||
<value>全螢幕模式</value>
|
||||
@@ -418,7 +418,7 @@
|
||||
<value>媒體庫同步中...</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
|
||||
<value>媒體庫同步有問題</value>
|
||||
<value>媒體庫同步出現問題</value>
|
||||
</data>
|
||||
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||
<value>清除播放佇列</value>
|
||||
@@ -1467,36 +1467,69 @@
|
||||
<data name="StandardMode" xml:space="preserve">
|
||||
<value>標準模式</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlActivityByHour.Text" xml:space="preserve">
|
||||
<value>每小時的活動</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlCustom.Content" xml:space="preserve">
|
||||
<value>自訂</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlEnd.Header" xml:space="preserve">
|
||||
<value>結束</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlLeastActive.Text" xml:space="preserve">
|
||||
<value>最不活躍</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlMostActive.Text" xml:space="preserve">
|
||||
<value>最活躍</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlRecording.Title" xml:space="preserve">
|
||||
<value>記錄中...</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||
<value>來源</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlStart.Header" xml:space="preserve">
|
||||
<value>開始</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
|
||||
<value>本月</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
|
||||
<value>本季</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
|
||||
<value>本週</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||
<value>今年</value>
|
||||
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
|
||||
<value>本年</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Header" xml:space="preserve">
|
||||
<value>今天</value>
|
||||
<data name="StatsDashboardControlTimeRange.Header" xml:space="preserve">
|
||||
<value>時間範圍</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTimes" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlToday.Content" xml:space="preserve">
|
||||
<value>今日</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopArtists.Text" xml:space="preserve">
|
||||
<value>頂級藝術家</value>
|
||||
<value>常聽的藝術家</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
|
||||
<value>熱門曲目</value>
|
||||
<value>常聽的曲目</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
|
||||
<value>頂端來源</value>
|
||||
<value>常用的播放來源</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTotalDuration.Text" xml:space="preserve">
|
||||
<value>總時間</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountAxis.AxisName" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTrackCountText.Text" xml:space="preserve">
|
||||
<value>次</value>
|
||||
</data>
|
||||
<data name="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||
<value>播放曲目</value>
|
||||
</data>
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:bwc="using:BetterLyrics.WinUI3.Converter"
|
||||
xmlns:cwc="using:CommunityToolkit.WinUI.Converters">
|
||||
|
||||
<bwc:EnumToIntConverter x:Key="EnumToIntConverter" />
|
||||
<bwc:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<bwc:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
<bwc:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<bwc:CornerRadiusToDoubleConverter x:Key="CornerRadiusToDoubleConverter" />
|
||||
<bwc:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<bwc:TranslationSearchProviderToDisplayNameConverter x:Key="TranslationSearchProviderToDisplayNameConverter" />
|
||||
<bwc:TransliterationSearchProviderToDisplayNameConverter x:Key="TransliterationSearchProviderToDisplayNameConverter" />
|
||||
<bwc:AlbumArtSearchProviderToDisplayNameConverter x:Key="AlbumArtSearchProviderToDisplayNameConverter" />
|
||||
<bwc:SecondsToFormattedTimeConverter x:Key="SecondsToFormattedTimeConverter" />
|
||||
<bwc:MillisecondsToFormattedTimeConverter x:Key="MillisecondsToFormattedTimeConverter" />
|
||||
<bwc:FPSToTimeSpanConverter x:Key="FPSToTimeSpanConverter" />
|
||||
<bwc:ShortcutToStringConverter x:Key="ShortcutToStringConverter" />
|
||||
<bwc:BoolNegationToVisibilityConverter x:Key="BoolNegationToVisibilityConverter" />
|
||||
<bwc:BoolToOpacityConverter x:Key="BoolToOpacityConverter" />
|
||||
<bwc:BoolToPartialOpacityConverter x:Key="BoolToPartialOpacityConverter" />
|
||||
<bwc:BoolNegationToOpacityConverter x:Key="BoolNegationToOpacityConverter" />
|
||||
<bwc:RectToMarginConverter x:Key="RectToMarginConverter" />
|
||||
<bwc:LanguageCodeToDisplayedNameConverter x:Key="LanguageCodeToDisplayedNameConverter" />
|
||||
<bwc:ByteArrayToImageConverter x:Key="ByteArrayToImageConverter" />
|
||||
<bwc:DisplayLanguageCodeToIndexConverter x:Key="DisplayLanguageCodeToIndexConverter" />
|
||||
<bwc:PathToParentFolderConverter x:Key="PathToParentFolderConverter" />
|
||||
<bwc:IntToBoolConverter x:Key="IntToBoolConverter" />
|
||||
<bwc:IndexToDisplayConverter x:Key="IndexToDisplayConverter" />
|
||||
<bwc:IntToDoubleConverter x:Key="IntToDoubleConverter" />
|
||||
<bwc:MillisecondsToSecondsConverter x:Key="MillisecondsToSecondsConverter" />
|
||||
<bwc:PictureInfosToImageSourceConverter x:Key="PictureInfosToImageSourceConverter" />
|
||||
<bwc:LyricsFontWeightToFontWeightConverter x:Key="LyricsFontWeightToFontWeightConverter" />
|
||||
<bwc:TextAlignmentTypeToHorizontalAlignmentConverter x:Key="TextAlignmentTypeToHorizontalAlignmentConverter" />
|
||||
<bwc:LyricsLayoutOrientationToOrientationConverter x:Key="LyricsLayoutOrientationToOrientationConverter" />
|
||||
<bwc:LyricsLayoutOrientationNegationToOrientationConverter x:Key="LyricsLayoutOrientationNegationToOrientationConverter" />
|
||||
<bwc:FileSourceTypeToIconConverter x:Key="FileSourceTypeToIconConverter" />
|
||||
<bwc:PathToImageConverter x:Key="PathToImageConverter" />
|
||||
<bwc:DoubleToDecimalConverter x:Key="DoubleToDecimalConverter" />
|
||||
<bwc:UriStringToDecodedAbsoluteUri x:Key="UriStringToDecodedAbsoluteUri" />
|
||||
|
||||
<cwc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<cwc:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<cwc:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
<cwc:CollectionVisibilityConverter x:Key="CollectionVisibilityConverter" />
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,188 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="GhostSliderStyle" TargetType="Slider">
|
||||
<Setter Property="Background" Value="{ThemeResource ControlStrokeColorOnAccentDefaultBrush}" />
|
||||
<Setter Property="BorderThickness" Value="{ThemeResource SliderBorderThemeThickness}" />
|
||||
<Setter Property="Foreground" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ControlContentThemeFontSize}" />
|
||||
<Setter Property="ManipulationMode" Value="None" />
|
||||
<Setter Property="UseSystemFocusVisuals" Value="{StaticResource UseSystemFocusVisuals}" />
|
||||
<Setter Property="FocusVisualMargin" Value="-7,0,-7,0" />
|
||||
<Setter Property="IsFocusEngagementEnabled" Value="True" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Slider">
|
||||
<Grid Margin="{TemplateBinding Padding}">
|
||||
<Grid.Resources>
|
||||
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||
<Setter Property="BorderThickness" Value="0" />
|
||||
<Setter Property="Background" Value="{ThemeResource TextFillColorPrimaryBrush}" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="Thumb">
|
||||
<Border
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
CornerRadius="0,1,1,0" />
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
</Grid.Resources>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
<ContentPresenter
|
||||
x:Name="HeaderContentPresenter"
|
||||
Grid.Row="0"
|
||||
Margin="{ThemeResource SliderTopHeaderMargin}"
|
||||
x:DeferLoadStrategy="Lazy"
|
||||
Content="{TemplateBinding Header}"
|
||||
ContentTemplate="{TemplateBinding HeaderTemplate}"
|
||||
FontWeight="{ThemeResource SliderHeaderThemeFontWeight}"
|
||||
Foreground="{ThemeResource SliderHeaderForeground}"
|
||||
TextWrapping="Wrap"
|
||||
Visibility="Collapsed" />
|
||||
<Grid
|
||||
x:Name="SliderContainer"
|
||||
Grid.Row="1"
|
||||
Background="{ThemeResource SliderContainerBackground}"
|
||||
Control.IsTemplateFocusTarget="True">
|
||||
<Grid x:Name="HorizontalTemplate" MinHeight="{ThemeResource SliderHorizontalHeight}">
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="{ThemeResource SliderPreContentMargin}" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.RowDefinitions>
|
||||
<Rectangle
|
||||
x:Name="HorizontalTrackRect"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="HorizontalDecreaseRect"
|
||||
Grid.Row="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="TopTickBar"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,0,4"
|
||||
VerticalAlignment="Bottom"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="HorizontalInlineTickBar"
|
||||
Grid.Row="1"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="2"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="BottomTickBar"
|
||||
Grid.Row="2"
|
||||
Grid.ColumnSpan="3"
|
||||
Height="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,4,0,0"
|
||||
VerticalAlignment="Top"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="HorizontalThumb"
|
||||
Grid.Row="0"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="2"
|
||||
Height="2"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-14,-6,-14,-6"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
<Grid
|
||||
x:Name="VerticalTemplate"
|
||||
MinWidth="{ThemeResource SliderVerticalWidth}"
|
||||
Visibility="Collapsed">
|
||||
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="*" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="{ThemeResource SliderPreContentMargin}" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="{ThemeResource SliderPostContentMargin}" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Rectangle
|
||||
x:Name="VerticalTrackRect"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{TemplateBinding Background}" />
|
||||
<Rectangle
|
||||
x:Name="VerticalDecreaseRect"
|
||||
Grid.Row="2"
|
||||
Grid.Column="1"
|
||||
Fill="{TemplateBinding Foreground}" />
|
||||
<TickBar
|
||||
x:Name="LeftTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="0,0,4,0"
|
||||
HorizontalAlignment="Right"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="VerticalInlineTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="1"
|
||||
Width="{ThemeResource SliderTrackThemeHeight}"
|
||||
Fill="{ThemeResource SliderInlineTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<TickBar
|
||||
x:Name="RightTickBar"
|
||||
Grid.RowSpan="3"
|
||||
Grid.Column="2"
|
||||
Width="{ThemeResource SliderOutsideTickBarThemeHeight}"
|
||||
Margin="4,0,0,0"
|
||||
HorizontalAlignment="Left"
|
||||
Fill="{ThemeResource SliderTickBarFill}"
|
||||
Visibility="Collapsed" />
|
||||
<Thumb
|
||||
x:Name="VerticalThumb"
|
||||
Grid.Row="1"
|
||||
Grid.Column="0"
|
||||
Grid.ColumnSpan="3"
|
||||
Width="24"
|
||||
Height="8"
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
DataContext="{TemplateBinding Value}"
|
||||
FocusVisualMargin="-6,-14,-6,-14"
|
||||
Style="{StaticResource SliderThumbStyle}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -0,0 +1,62 @@
|
||||
<?xml version="1.0" encoding="utf-8" ?>
|
||||
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||
|
||||
<Style x:Key="InteractiveListViewHeaderStyle" TargetType="ListViewHeaderItem">
|
||||
<Setter Property="FontFamily" Value="{ThemeResource ContentControlThemeFontFamily}" />
|
||||
<Setter Property="FontSize" Value="{ThemeResource ListViewHeaderItemThemeFontSize}" />
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Margin" Value="0,0,0,4" />
|
||||
<Setter Property="Padding" Value="12,8,12,8" />
|
||||
<Setter Property="CornerRadius" Value="4" />
|
||||
<Setter Property="HorizontalContentAlignment" Value="Left" />
|
||||
<Setter Property="VerticalContentAlignment" Value="Center" />
|
||||
<Setter Property="Template">
|
||||
<Setter.Value>
|
||||
<ControlTemplate TargetType="ListViewHeaderItem">
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
Background="{TemplateBinding Background}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<ContentPresenter
|
||||
x:Name="ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
|
||||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}"
|
||||
ContentTransitions="{TemplateBinding ContentTransitions}" />
|
||||
|
||||
<VisualStateManager.VisualStateGroups>
|
||||
<VisualStateGroup x:Name="CommonStates">
|
||||
<VisualStateGroup.Transitions>
|
||||
<VisualTransition GeneratedDuration="0:0:0.1" />
|
||||
</VisualStateGroup.Transitions>
|
||||
|
||||
<VisualState x:Name="Normal" />
|
||||
|
||||
<VisualState x:Name="PointerOver">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="{ThemeResource ListViewItemBackgroundPointerOver}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Pressed">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="RootGrid.Background" Value="{ThemeResource ListViewItemBackgroundPressed}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
|
||||
<VisualState x:Name="Disabled">
|
||||
<VisualState.Setters>
|
||||
<Setter Target="ContentPresenter.Opacity" Value="{ThemeResource ListViewItemDisabledThemeOpacity}" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
</VisualStateManager.VisualStateGroups>
|
||||
</Grid>
|
||||
</ControlTemplate>
|
||||
</Setter.Value>
|
||||
</Setter>
|
||||
</Style>
|
||||
|
||||
</ResourceDictionary>
|
||||
@@ -4,7 +4,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Parsers.LyricsParser;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -17,10 +17,10 @@ using System.Threading.Tasks;
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class LyricsSearchControlViewModel : BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>
|
||||
IRecipient<PropertyChangedMessage<SongInfo>>
|
||||
{
|
||||
private readonly ILyricsSearchService _lyricsSearchService;
|
||||
private readonly IMediaSessionsService _mediaSessionsService;
|
||||
private readonly IGSMTCService _gsmtcService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
private LatestOnlyTaskRunner _lyricsSearchRunner = new();
|
||||
@@ -43,10 +43,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsSearching { get; set; } = false;
|
||||
|
||||
public LyricsSearchControlViewModel(ILyricsSearchService lyricsSearchService, IMediaSessionsService mediaSessionsService, ISettingsService settingsService)
|
||||
public LyricsSearchControlViewModel(ILyricsSearchService lyricsSearchService, IGSMTCService gsmtcService, ISettingsService settingsService)
|
||||
{
|
||||
_lyricsSearchService = lyricsSearchService;
|
||||
_mediaSessionsService = mediaSessionsService;
|
||||
_gsmtcService = gsmtcService;
|
||||
_settingsService = settingsService;
|
||||
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
@@ -58,19 +58,19 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
LyricsSearchResults.Clear();
|
||||
LyricsDataArr = null;
|
||||
if (_mediaSessionsService.CurrentSongInfo != null)
|
||||
if (_gsmtcService.CurrentSongInfo != null)
|
||||
{
|
||||
var found = GetMappedSongSearchQueryFromSettings();
|
||||
if (found == null)
|
||||
{
|
||||
MappedSongSearchQuery = new MappedSongSearchQuery
|
||||
{
|
||||
OriginalTitle = _mediaSessionsService.CurrentSongInfo.Title,
|
||||
OriginalArtist = _mediaSessionsService.CurrentSongInfo.DisplayArtists,
|
||||
OriginalAlbum = _mediaSessionsService.CurrentSongInfo.Album,
|
||||
MappedTitle = _mediaSessionsService.CurrentSongInfo.Title,
|
||||
MappedArtist = _mediaSessionsService.CurrentSongInfo.DisplayArtists,
|
||||
MappedAlbum = _mediaSessionsService.CurrentSongInfo.Album,
|
||||
OriginalTitle = _gsmtcService.CurrentSongInfo.Title,
|
||||
OriginalArtist = _gsmtcService.CurrentSongInfo.DisplayArtists,
|
||||
OriginalAlbum = _gsmtcService.CurrentSongInfo.Album,
|
||||
MappedTitle = _gsmtcService.CurrentSongInfo.Title,
|
||||
MappedArtist = _gsmtcService.CurrentSongInfo.DisplayArtists,
|
||||
MappedAlbum = _gsmtcService.CurrentSongInfo.Album,
|
||||
};
|
||||
}
|
||||
else
|
||||
@@ -82,16 +82,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private MappedSongSearchQuery? GetMappedSongSearchQueryFromSettings()
|
||||
{
|
||||
if (_mediaSessionsService.CurrentSongInfo == null)
|
||||
if (_gsmtcService.CurrentSongInfo == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
var found = AppSettings.MappedSongSearchQueries
|
||||
.FirstOrDefault(x =>
|
||||
x.OriginalTitle == _mediaSessionsService.CurrentSongInfo.Title &&
|
||||
x.OriginalArtist == _mediaSessionsService.CurrentSongInfo.DisplayArtists &&
|
||||
x.OriginalAlbum == _mediaSessionsService.CurrentSongInfo.Album);
|
||||
x.OriginalTitle == _gsmtcService.CurrentSongInfo.Title &&
|
||||
x.OriginalArtist == _gsmtcService.CurrentSongInfo.DisplayArtists &&
|
||||
x.OriginalAlbum == _gsmtcService.CurrentSongInfo.Album);
|
||||
|
||||
return found;
|
||||
}
|
||||
@@ -102,7 +102,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
return;
|
||||
}
|
||||
_mediaSessionsService.ChangePosition(value.StartMs / 1000.0);
|
||||
_gsmtcService.ChangePosition(value.StartMs / 1000.0);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -121,7 +121,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
LyricsSearchResults = [..await Task.Run(async () =>
|
||||
{
|
||||
var result = await _lyricsSearchService.SearchAllAsync(
|
||||
((SongInfo?)_mediaSessionsService.CurrentSongInfo?.Clone() ?? new())
|
||||
((SongInfo)_gsmtcService.CurrentSongInfo.Clone())
|
||||
.WithTitle(MappedSongSearchQuery.MappedTitle)
|
||||
.WithArtist(MappedSongSearchQuery.MappedArtist.SplitByCommonSplitter())
|
||||
.WithAlbum(MappedSongSearchQuery.MappedAlbum),
|
||||
@@ -194,11 +194,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
public void Receive(PropertyChangedMessage<SongInfo> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo))
|
||||
{
|
||||
InitMappedSongSearchQuery();
|
||||
}
|
||||
|
||||
@@ -235,5 +235,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
await dialog.ShowAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenMusicGalleryWindow()
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,16 @@ using BetterLyrics.WinUI3.Constants;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
@@ -41,16 +44,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IFileSystemService _fileSystemService;
|
||||
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
private readonly MediaTimelineController _timelineController = new();
|
||||
private readonly SystemMediaTransportControls _smtc;
|
||||
[ObservableProperty] public partial ISMTCService SMTCService { get; set; }
|
||||
|
||||
private readonly DispatcherQueueTimer _refreshSongsTimer;
|
||||
|
||||
private IRandomAccessStream? _currentStream;
|
||||
private Stream? _currentNetStream;
|
||||
private IUnifiedFileSystem? _currentProvider;
|
||||
|
||||
// All songs
|
||||
private List<ExtendedTrack> _allTracks = [];
|
||||
// Songs in current playlist or songs in current file tree
|
||||
@@ -58,37 +55,22 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
// Filtered songs based on search query for current playlist
|
||||
private List<ExtendedTrack> _filteredTracks = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial AppSettings AppSettings { get; set; }
|
||||
[ObservableProperty] public partial AppSettings AppSettings { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsLocalMediaNotFound { get; set; }
|
||||
[ObservableProperty] public partial bool IsLocalMediaNotFound { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Grouped tracks after filtering and sorting for current playlist
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
|
||||
[ObservableProperty] public partial ObservableCollection<GroupInfoList> GroupedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<ExtendedTrack> SelectedTracks { get; set; } = [];
|
||||
[ObservableProperty] public partial List<ExtendedTrack> SelectedTracks { get; set; } = [];
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedTracksTotalDuration { get; set; } = 0;
|
||||
[ObservableProperty] public partial int SelectedTracksTotalDuration { get; set; } = 0;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<PlayQueueItem> TrackPlayingQueue { get; set; }
|
||||
[ObservableProperty] public partial CommonSongProperty SongOrderType { get; set; } = CommonSongProperty.Title;
|
||||
|
||||
public PlayQueueItem? PlayingQueueItem => TrackPlayingQueue.ElementAtOrDefault(AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ExtendedTrack? PlayingTrack { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial CommonSongProperty SongOrderType { get; set; } = CommonSongProperty.Title;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int SelectedSongsTabInfoIndex { get; set; } = 0;
|
||||
[ObservableProperty] public partial int SelectedSongsTabInfoIndex { get; set; } = 0;
|
||||
|
||||
public SongsTabInfo? SelectedSongsTabInfo => AppSettings.StarredPlaylists.ElementAtOrDefault(SelectedSongsTabInfoIndex);
|
||||
|
||||
@@ -97,47 +79,32 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
[ObservableProperty] public partial ExtendedTrack TrackRightTapped { get; set; } = new();
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string SongSearchQuery { get; set; } = string.Empty;
|
||||
[ObservableProperty] public partial string SongSearchQuery { get; set; } = string.Empty;
|
||||
|
||||
[ObservableProperty] public partial ListViewSelectionMode SongListViewSelectionMode { get; set; } = ListViewSelectionMode.Single;
|
||||
|
||||
public ObservableCollection<FolderNode> FolderRoots { get; } = new();
|
||||
|
||||
public MusicGalleryPageViewModel(
|
||||
ISettingsService settingsService,
|
||||
ILocalizationService localizationService,
|
||||
IFileSystemService fileSystemService
|
||||
IFileSystemService fileSystemService,
|
||||
ISMTCService smtcService
|
||||
)
|
||||
{
|
||||
_localizationService = localizationService;
|
||||
_fileSystemService = fileSystemService;
|
||||
SMTCService = smtcService;
|
||||
|
||||
_refreshSongsTimer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
_settingsService = settingsService;
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
|
||||
TrackPlayingQueue = [.. AppSettings.MusicGallerySettings.PlayQueuePaths.Select(x => new PlayQueueItem(new ExtendedTrack(x)))];
|
||||
TrackPlayingQueue.CollectionChanged += TrackPlayingQueue_CollectionChanged;
|
||||
|
||||
RefreshSongs();
|
||||
|
||||
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
||||
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
|
||||
|
||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||
_mediaPlayer.CommandManager.IsEnabled = false;
|
||||
|
||||
_timelineController = _mediaPlayer.TimelineController = new();
|
||||
_timelineController.PositionChanged += TimelineController_PositionChanged;
|
||||
|
||||
_smtc = _mediaPlayer.SystemMediaTransportControls;
|
||||
_smtc.IsPlayEnabled = true;
|
||||
_smtc.IsPauseEnabled = true;
|
||||
_smtc.IsNextEnabled = true;
|
||||
_smtc.IsPreviousEnabled = true;
|
||||
_smtc.ButtonPressed += Smtc_ButtonPressed;
|
||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||
@@ -145,134 +112,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsDataSyncError = AppSettings.LocalMediaFolders.Any(x => x.StatusSeverity == InfoBarSeverity.Error);
|
||||
}
|
||||
|
||||
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.DecodedAbsoluteUri)];
|
||||
}
|
||||
|
||||
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||
{
|
||||
RefreshSongs();
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaEnded(MediaPlayer sender, object args)
|
||||
{
|
||||
PlayNextTrack();
|
||||
}
|
||||
|
||||
public void PlayNextTrack()
|
||||
{
|
||||
switch (AppSettings.MusicGallerySettings.PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (AppSettings.MusicGallerySettings.PlayQueueIndex < TrackPlayingQueue.Count - 1)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex++;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex = 0;
|
||||
}
|
||||
await PlayTrackAsync(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
await PlayTrackAsync(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void PlayPreviousTrack()
|
||||
{
|
||||
switch (AppSettings.MusicGallerySettings.PlaybackOrder)
|
||||
{
|
||||
case PlaybackOrder.RepeatAll:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (AppSettings.MusicGallerySettings.PlayQueueIndex > 0)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex--;
|
||||
}
|
||||
else
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex = TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
await PlayTrackAsync(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
case PlaybackOrder.RepeatOne:
|
||||
_timelineController.Position = TimeSpan.Zero;
|
||||
break;
|
||||
case PlaybackOrder.Shuffle:
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (TrackPlayingQueue.Count > 0)
|
||||
{
|
||||
AppSettings.MusicGallerySettings.PlayQueueIndex = new Random().Next(0, TrackPlayingQueue.Count);
|
||||
}
|
||||
await PlayTrackAsync(PlayingQueueItem);
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void Smtc_PlaybackPositionChangeRequested(SystemMediaTransportControls sender, PlaybackPositionChangeRequestedEventArgs args)
|
||||
{
|
||||
_timelineController.Position = args.RequestedPlaybackPosition;
|
||||
}
|
||||
|
||||
private void MediaPlayer_MediaOpened(MediaPlayer sender, object args)
|
||||
{
|
||||
_timelineController.Start();
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
}
|
||||
|
||||
private void TimelineController_PositionChanged(MediaTimelineController sender, object args)
|
||||
{
|
||||
_smtc.UpdateTimelineProperties(new SystemMediaTransportControlsTimelineProperties()
|
||||
{
|
||||
Position = sender.Position,
|
||||
EndTime = _mediaPlayer.PlaybackSession.NaturalDuration
|
||||
});
|
||||
}
|
||||
|
||||
private void Smtc_ButtonPressed(SystemMediaTransportControls sender, SystemMediaTransportControlsButtonPressedEventArgs args)
|
||||
{
|
||||
switch (args.Button)
|
||||
{
|
||||
case SystemMediaTransportControlsButton.Play:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Playing;
|
||||
_timelineController.Resume();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Pause:
|
||||
_smtc.PlaybackStatus = MediaPlaybackStatus.Paused;
|
||||
_timelineController.Pause();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Next:
|
||||
PlayNextTrack();
|
||||
break;
|
||||
case SystemMediaTransportControlsButton.Previous:
|
||||
PlayPreviousTrack();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void CancelRefreshSongs()
|
||||
{
|
||||
}
|
||||
@@ -342,7 +186,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var m3uFileContent = File.ReadAllText(path);
|
||||
_middleTracks = _allTracks.Where(t => m3uFileContent.Contains(t.DecodedAbsoluteUri)).ToList();
|
||||
_middleTracks = _allTracks.Where(t => m3uFileContent.Contains(t.Uri.ToDecodedAbsoluteUri())).ToList();
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -458,133 +302,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
ApplyPlaylist();
|
||||
}
|
||||
|
||||
public async Task PlayTrackAtAsync(int index)
|
||||
{
|
||||
await PlayTrackAsync(TrackPlayingQueue.ElementAtOrDefault(index));
|
||||
}
|
||||
|
||||
public async Task PlayTrackAsync(PlayQueueItem? playQueueItem)
|
||||
{
|
||||
_timelineController.Pause();
|
||||
_mediaPlayer.Source = null;
|
||||
|
||||
// 清理旧资源
|
||||
_currentStream?.Dispose();
|
||||
_currentNetStream?.Dispose();
|
||||
_currentStream = null;
|
||||
_currentNetStream = null;
|
||||
|
||||
if (playQueueItem == null)
|
||||
{
|
||||
_smtc.IsEnabled = false;
|
||||
_smtc.DisplayUpdater.ClearAll();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayingTrack = playQueueItem.Track;
|
||||
_smtc.IsEnabled = true;
|
||||
|
||||
try
|
||||
{
|
||||
var targetFolder = _settingsService.AppSettings.LocalMediaFolders.FirstOrDefault(f =>
|
||||
{
|
||||
var fUri = f.GetStandardUri().AbsoluteUri;
|
||||
return PlayingTrack.Uri.StartsWith(fUri, StringComparison.OrdinalIgnoreCase);
|
||||
});
|
||||
|
||||
if (targetFolder == null)
|
||||
{
|
||||
throw new FileNotFoundException(null, PlayingTrack.DecodedAbsoluteUri);
|
||||
}
|
||||
|
||||
_currentProvider = targetFolder.CreateFileSystem();
|
||||
if (_currentProvider == null) return;
|
||||
|
||||
await _currentProvider.ConnectAsync();
|
||||
|
||||
var fileCacheStub = new FilesIndexItem
|
||||
{
|
||||
Uri = PlayingTrack.Uri
|
||||
};
|
||||
|
||||
var sourceStream = await _fileSystemService.OpenFileAsync(_currentProvider, fileCacheStub);
|
||||
|
||||
if (sourceStream == null)
|
||||
{
|
||||
throw new FileNotFoundException(null, fileCacheStub.Uri);
|
||||
}
|
||||
|
||||
if (sourceStream.CanSeek)
|
||||
{
|
||||
_currentNetStream = sourceStream;
|
||||
}
|
||||
else
|
||||
{
|
||||
var memStream = new MemoryStream();
|
||||
|
||||
await sourceStream.CopyToAsync(memStream);
|
||||
memStream.Position = 0;
|
||||
|
||||
sourceStream.Dispose();
|
||||
|
||||
_currentNetStream = memStream;
|
||||
}
|
||||
|
||||
_currentStream = _currentNetStream.AsRandomAccessStream();
|
||||
|
||||
string contentType = GetMimeType(PlayingTrack.FileName);
|
||||
var mediaSource = MediaSource.CreateFromStream(_currentStream, contentType);
|
||||
|
||||
_mediaPlayer.Source = mediaSource;
|
||||
|
||||
var updater = _smtc.DisplayUpdater;
|
||||
updater.Type = MediaPlaybackType.Music;
|
||||
|
||||
updater.MusicProperties.Title = PlayingTrack.Title ?? PlayingTrack.FileName;
|
||||
updater.MusicProperties.Artist = PlayingTrack.Artist ?? "";
|
||||
updater.MusicProperties.AlbumTitle = PlayingTrack.Album ?? "";
|
||||
|
||||
updater.MusicProperties.Genres.Clear();
|
||||
updater.MusicProperties.Genres.Add($"{ExtendedGenreFiled.FileName}{Path.GetFileNameWithoutExtension(PlayingTrack.FileName)}");
|
||||
|
||||
updater.AppMediaId = Package.Current.Id.FullName;
|
||||
|
||||
if (!string.IsNullOrEmpty(PlayingTrack.LocalAlbumArtPath) && File.Exists(PlayingTrack.LocalAlbumArtPath))
|
||||
{
|
||||
var storageFile = await StorageFile.GetFileFromPathAsync(PlayingTrack.LocalAlbumArtPath);
|
||||
updater.Thumbnail = RandomAccessStreamReference.CreateFromFile(storageFile);
|
||||
}
|
||||
else
|
||||
{
|
||||
updater.Thumbnail = null;
|
||||
}
|
||||
|
||||
updater.Update();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
ToastHelper.ShowToast("Error", ex.Message, InfoBarSeverity.Error);
|
||||
_timelineController.Pause();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private string GetMimeType(string path)
|
||||
{
|
||||
var ext = Path.GetExtension(path).ToLower();
|
||||
return ext switch
|
||||
{
|
||||
".mp3" => "audio/mpeg",
|
||||
".flac" => "audio/flac",
|
||||
".wav" => "audio/wav",
|
||||
".m4a" => "audio/mp4",
|
||||
".aac" => "audio/aac",
|
||||
".ogg" => "audio/ogg",
|
||||
".wma" => "audio/x-ms-wma",
|
||||
_ => "application/octet-stream"
|
||||
};
|
||||
}
|
||||
|
||||
partial void OnSongOrderTypeChanged(CommonSongProperty value)
|
||||
{
|
||||
ApplySongOrderType();
|
||||
@@ -639,7 +356,24 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[RelayCommand]
|
||||
private async Task StopTrackAsync()
|
||||
{
|
||||
await PlayTrackAtAsync(-1);
|
||||
await SMTCService.PlayTrackAtAsync(-1);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void OpenMediaSettings()
|
||||
{
|
||||
WindowHook.OpenOrShowWindow<SettingsWindow>();
|
||||
var settingsPageViewModel = Ioc.Default.GetRequiredService<SettingsPageViewModel>();
|
||||
settingsPageViewModel.NavViewSelectedItemTag = "MediaLib";
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void ToggleSongListViewSelectionMode()
|
||||
{
|
||||
SongListViewSelectionMode =
|
||||
SongListViewSelectionMode == ListViewSelectionMode.Single ?
|
||||
ListViewSelectionMode.Multiple :
|
||||
ListViewSelectionMode.Single;
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<DateTime?> message)
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
@@ -11,7 +12,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class NowPlayingBarViewModel : BaseViewModel
|
||||
{
|
||||
public IMediaSessionsService MediaSessionsService { get; private set; }
|
||||
public IGSMTCService GSMTCService { get; private set; }
|
||||
|
||||
private readonly ISMTCService _smtcService;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int Volume { get; set; }
|
||||
@@ -31,9 +34,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial double BottomCommandFlyoutTriggerOpacity { get; set; }
|
||||
|
||||
public NowPlayingBarViewModel(IMediaSessionsService mediaSessionsService)
|
||||
public NowPlayingBarViewModel(IGSMTCService mediaSessionsService, ISMTCService smtcService)
|
||||
{
|
||||
MediaSessionsService = mediaSessionsService;
|
||||
GSMTCService = mediaSessionsService;
|
||||
_smtcService = smtcService;
|
||||
|
||||
Volume = SystemVolumeHook.MasterVolume;
|
||||
SystemVolumeHook.VolumeNotification += SystemVolumeHelper_VolumeNotification;
|
||||
@@ -46,32 +50,38 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
partial void OnTimelineSliderThumbSecondsChanged(double value)
|
||||
{
|
||||
TimelineSliderThumbLyricsLine = MediaSessionsService.CurrentLyricsData?.GetLyricsLine(value);
|
||||
TimelineSliderThumbLyricsLine = GSMTCService.CurrentLyricsData?.GetLyricsLine(value);
|
||||
}
|
||||
|
||||
|
||||
[RelayCommand]
|
||||
private async Task PlaySongAsync()
|
||||
{
|
||||
await MediaSessionsService.PlayAsync();
|
||||
await GSMTCService.PlayAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task PauseSongAsync()
|
||||
{
|
||||
await MediaSessionsService.PauseAsync();
|
||||
await GSMTCService.PauseAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task PreviousSongAsync()
|
||||
{
|
||||
await MediaSessionsService.PreviousAsync();
|
||||
await GSMTCService.PreviousAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task NextSongAsync()
|
||||
{
|
||||
await MediaSessionsService.NextAsync();
|
||||
await GSMTCService.NextAsync();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task StopTrackAsync()
|
||||
{
|
||||
await _smtcService.PlayTrackAtAsync(-1);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Views;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class NowPlayingPageViewModel : BaseViewModel
|
||||
{
|
||||
public IMediaSessionsService MediaSessionsService { get; private set; }
|
||||
public IGSMTCService MediaSessionsService { get; private set; }
|
||||
|
||||
public NowPlayingPageViewModel(IMediaSessionsService mediaSessionsService)
|
||||
public NowPlayingPageViewModel(IGSMTCService mediaSessionsService)
|
||||
{
|
||||
MediaSessionsService = mediaSessionsService;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
using BetterLyrics.WinUI3.Controls;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class PlayQueueViewModel : BaseViewModel
|
||||
{
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public ISMTCService SMTCService { get; set; }
|
||||
|
||||
[ObservableProperty] public partial AppSettings AppSettings { get; set; }
|
||||
|
||||
public PlayQueueViewModel(ISMTCService smtcService, ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
SMTCService = smtcService;
|
||||
AppSettings = _settingsService.AppSettings;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,7 +2,7 @@
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||
@@ -20,7 +20,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class PlaybackSettingsControlViewModel : BaseViewModel
|
||||
{
|
||||
public IMediaSessionsService MediaSessionsService;
|
||||
public IGSMTCService GSMTCService;
|
||||
private readonly ITranslationService _translationService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ISettingsService _settingsService;
|
||||
@@ -55,12 +55,12 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public PlaybackSettingsControlViewModel(
|
||||
ISettingsService settingsService,
|
||||
IMediaSessionsService mediaSessionsService,
|
||||
IGSMTCService gsmtcService,
|
||||
ITranslationService libreTranslationService,
|
||||
ILastFMService lastFMService,
|
||||
ITransliterationService transliterationService)
|
||||
{
|
||||
MediaSessionsService = mediaSessionsService;
|
||||
GSMTCService = gsmtcService;
|
||||
|
||||
_settingsService = settingsService;
|
||||
_translationService = libreTranslationService;
|
||||
@@ -206,7 +206,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
PasswordVaultHelper.Delete(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey);
|
||||
PasswordVaultHelper.Save(Constants.App.AppName, Constants.AppleMusic.MediaUserTokenKey, AppleMusicMediaUserToken);
|
||||
MediaSessionsService.UpdateLyrics();
|
||||
GSMTCService.UpdateLyrics();
|
||||
}
|
||||
|
||||
partial void OnSelectedTargetLanguageIndexChanged(int value)
|
||||
|
||||
@@ -2,93 +2,132 @@
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Stats;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using CommunityToolkit.WinUI;
|
||||
using LiveChartsCore;
|
||||
using LiveChartsCore.Kernel;
|
||||
using LiveChartsCore.Kernel.Sketches;
|
||||
using LiveChartsCore.SkiaSharpView;
|
||||
using LiveChartsCore.SkiaSharpView.Painting;
|
||||
using LiveChartsCore.Themes;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Microsoft.UI.Xaml;
|
||||
using SkiaSharp;
|
||||
using SkiaSharp.Views.Windows;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Xml.Linq;
|
||||
|
||||
namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class StatsDashboardControlViewModel : ObservableObject
|
||||
public partial class StatsDashboardControlViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
|
||||
{
|
||||
private readonly IPlayHistoryService _playHistoryService;
|
||||
private readonly ILocalizationService _localizationService;
|
||||
private readonly IAlbumArtSearchService _albumArtSearchService;
|
||||
|
||||
public StatsDashboardControlViewModel(IPlayHistoryService playHistoryService)
|
||||
{
|
||||
_playHistoryService = playHistoryService;
|
||||
}
|
||||
private string _localizedTimesValue;
|
||||
|
||||
[ObservableProperty] public partial bool IsLoading { get; set; }
|
||||
private readonly DispatcherQueueTimer _timer;
|
||||
|
||||
[ObservableProperty] public partial IGSMTCService GSMTCService { get; set; }
|
||||
|
||||
[ObservableProperty] public partial bool IsLoading { get; set; } = false;
|
||||
|
||||
// 时间筛选
|
||||
[ObservableProperty] public partial StatsRange SelectedTimeRange { get; set; } = StatsRange.Today;
|
||||
[ObservableProperty] public partial bool IsCustomRangeSelected { get; set; } = false;
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomStartDate { get; set; } = DateTime.Now;
|
||||
[ObservableProperty] public partial DateTimeOffset? CustomEndDate { get; set; } = DateTime.Now;
|
||||
[ObservableProperty] public partial TimeSpan CustomStartTime { get; set; } = TimeSpan.Zero;
|
||||
[ObservableProperty] public partial TimeSpan CustomEndTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
// 顶部基础数据
|
||||
[ObservableProperty] public partial TimeSpan TotalDuration { get; set; }
|
||||
[ObservableProperty] public partial int TotalTracksPlayed { get; set; }
|
||||
[ObservableProperty] public partial string TopPlayerName { get; set; } = "N/A";
|
||||
|
||||
public ObservableCollection<SongPlayCount> TopSongs { get; } = new();
|
||||
public ObservableCollection<ArtistPlayCount> TopArtists { get; } = new();
|
||||
// 时段分布
|
||||
[ObservableProperty] public partial ObservableCollection<int> HourlySeriesValues { get; set; } = new();
|
||||
[ObservableProperty] public partial ObservableCollection<string> HourlyXAxisLabels { get; set; } = [.. Enumerable.Range(0, 24).Select(x => $"{x:D2}:00")];
|
||||
[ObservableProperty] public partial string PeakHourText { get; set; } = "--:--";
|
||||
[ObservableProperty] public partial string QuietHourText { get; set; } = "--:--";
|
||||
|
||||
public ObservableCollection<PlayerStatDisplayItem> PlayerStats { get; } = new();
|
||||
// 歌手
|
||||
[ObservableProperty] public partial ObservableCollection<ArtistPlayCount> TopArtists { get; set; } = new();
|
||||
|
||||
/// <summary>
|
||||
/// 核心方法:根据选中的 Tab 加载数据
|
||||
/// </summary>
|
||||
[RelayCommand]
|
||||
public async Task LoadDataAsync(StatsRange range)
|
||||
// 播放源
|
||||
[ObservableProperty] public partial ObservableCollection<ISeries> SourceSeries { get; set; } = new();
|
||||
|
||||
// 歌曲
|
||||
[ObservableProperty] public partial ObservableCollection<SongPlayCount> TopSongs { get; set; } = new();
|
||||
|
||||
public StatsDashboardControlViewModel(
|
||||
IPlayHistoryService playHistoryService,
|
||||
ILocalizationService localizationService,
|
||||
IAlbumArtSearchService albumArtSearchService,
|
||||
IGSMTCService gsmtcService)
|
||||
{
|
||||
if (IsLoading) return;
|
||||
IsLoading = true;
|
||||
_playHistoryService = playHistoryService;
|
||||
_localizationService = localizationService;
|
||||
_albumArtSearchService = albumArtSearchService;
|
||||
GSMTCService = gsmtcService;
|
||||
|
||||
try
|
||||
{
|
||||
var (start, end) = CalculateDateRange(range);
|
||||
_localizedTimesValue = _localizationService.GetLocalizedString("StatsDashboardControlTimes");
|
||||
|
||||
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start, end);
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start, end);
|
||||
var topSongsTask = _playHistoryService.GetTopSongsAsync(start, end, 10);
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start, end, 10);
|
||||
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start, end);
|
||||
_timer = _dispatcherQueue.CreateTimer();
|
||||
|
||||
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||
|
||||
TotalDuration = await durationTask;
|
||||
var logs = await logsTask;
|
||||
TotalTracksPlayed = logs.Count;
|
||||
|
||||
TopSongs.Clear();
|
||||
foreach (var item in await topSongsTask) TopSongs.Add(item);
|
||||
|
||||
TopArtists.Clear();
|
||||
foreach (var item in await topArtistsTask) TopArtists.Add(item);
|
||||
|
||||
UpdatePlayerStats(await playersTask);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
UpdateDateRange();
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GenerateTestDataAsync()
|
||||
partial void OnSelectedTimeRangeChanged(StatsRange value)
|
||||
{
|
||||
await _playHistoryService.GenerateTestDataAsync(10000);
|
||||
IsCustomRangeSelected = value == StatsRange.Custom;
|
||||
UpdateDateRange();
|
||||
}
|
||||
partial void OnCustomEndDateChanged(DateTimeOffset? value) => LoadData();
|
||||
partial void OnCustomStartDateChanged(DateTimeOffset? value) => LoadData();
|
||||
partial void OnCustomStartTimeChanged(TimeSpan value) => LoadData();
|
||||
partial void OnCustomEndTimeChanged(TimeSpan value) => LoadData();
|
||||
|
||||
private void ProcessHourlyStats(List<PlayHistoryItem> logs)
|
||||
{
|
||||
if (logs == null || !logs.Any())
|
||||
{
|
||||
PeakHourText = "--:--";
|
||||
QuietHourText = "--:--";
|
||||
HourlySeriesValues = new();
|
||||
return;
|
||||
}
|
||||
|
||||
var hourCounts = new int[24];
|
||||
foreach (var log in logs)
|
||||
{
|
||||
hourCounts[log.StartedAt.ToLocalTime().Hour]++;
|
||||
}
|
||||
|
||||
int peakHour = Array.IndexOf(hourCounts, hourCounts.Max());
|
||||
PeakHourText = $"{peakHour:D2}:00 - {peakHour + 1:D2}:00";
|
||||
|
||||
int quietHour = Array.IndexOf(hourCounts, hourCounts.Min());
|
||||
QuietHourText = $"{quietHour:D2}:00 - {quietHour + 1:D2}:00";
|
||||
|
||||
HourlySeriesValues = [.. hourCounts];
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将原始统计数据转换为带进度条宽度的 UI 数据
|
||||
/// </summary>
|
||||
private void UpdatePlayerStats(List<PlayerStats> stats)
|
||||
{
|
||||
PlayerStats.Clear();
|
||||
SourceSeries = new();
|
||||
|
||||
if (stats == null || stats.Count == 0)
|
||||
{
|
||||
@@ -96,53 +135,157 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
return;
|
||||
}
|
||||
|
||||
double maxCount = stats.Max(x => x.Count);
|
||||
if (maxCount == 0) maxCount = 1;
|
||||
|
||||
var topPlayer = stats.OrderByDescending(x => x.Count).FirstOrDefault();
|
||||
TopPlayerName = PlayerIdHelper.GetDisplayName(topPlayer?.PlayerId) ?? "N/A";
|
||||
|
||||
foreach (var item in stats.OrderByDescending(x => x.Count))
|
||||
var colors = PaletteHelper.GenerateChartColors(ColorHelper.GetSystemAccentColor(), stats.Count);
|
||||
|
||||
SourceSeries = [.. stats.OrderByDescending(x => x.Count).Select((x, i) => new PieSeries<int>
|
||||
{
|
||||
PlayerStats.Add(new PlayerStatDisplayItem
|
||||
{
|
||||
PlayerId = item.PlayerId,
|
||||
PlayCount = item.Count,
|
||||
});
|
||||
}
|
||||
Values = [x.Count],
|
||||
Name = PlayerIdHelper.GetDisplayName(x.PlayerId),
|
||||
ToolTipLabelFormatter = point => $"{x.Count} {_localizedTimesValue}",
|
||||
|
||||
Pushout = 4, // 间隙
|
||||
})];
|
||||
}
|
||||
|
||||
private (DateTime Start, DateTime End) CalculateDateRange(StatsRange range)
|
||||
private (DateTime? Start, DateTime? End) CalculateDateRange()
|
||||
{
|
||||
if (CustomStartDate == null || CustomEndDate == null) return (null, null);
|
||||
|
||||
return (
|
||||
new DateTime(
|
||||
DateOnly.FromDateTime(CustomStartDate.Value.LocalDateTime),
|
||||
TimeOnly.FromTimeSpan(CustomStartTime),
|
||||
DateTimeKind.Local)
|
||||
.ToUniversalTime(),
|
||||
new DateTime(
|
||||
DateOnly.FromDateTime(CustomEndDate.Value.LocalDateTime),
|
||||
TimeOnly.FromTimeSpan(CustomEndTime),
|
||||
DateTimeKind.Local)
|
||||
.ToUniversalTime()
|
||||
);
|
||||
}
|
||||
|
||||
private void UpdateDateRange()
|
||||
{
|
||||
DateTime nowLocal = DateTime.Now;
|
||||
DateTime startLocal = nowLocal.Date; // 默认为本地今天 00:00
|
||||
DateTime startLocal = nowLocal.Date;
|
||||
|
||||
switch (range)
|
||||
switch (SelectedTimeRange)
|
||||
{
|
||||
case StatsRange.Day:
|
||||
case StatsRange.Today:
|
||||
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, nowLocal.Day);
|
||||
break;
|
||||
case StatsRange.Week:
|
||||
case StatsRange.ThisWeek:
|
||||
int dayOfWeek = (int)nowLocal.DayOfWeek;
|
||||
if (dayOfWeek == 0) dayOfWeek = 7; // 处理周日
|
||||
if (dayOfWeek == 0) dayOfWeek = 7;
|
||||
startLocal = nowLocal.Date.AddDays(-(dayOfWeek - 1));
|
||||
startLocal = new DateTime(startLocal.Year, startLocal.Month, startLocal.Day);
|
||||
break;
|
||||
case StatsRange.Month:
|
||||
case StatsRange.ThisMonth:
|
||||
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, 1);
|
||||
break;
|
||||
case StatsRange.Quarter:
|
||||
case StatsRange.ThisQuarter:
|
||||
int quarterStartMonth = (nowLocal.Month - 1) / 3 * 3 + 1;
|
||||
startLocal = new DateTime(nowLocal.Year, quarterStartMonth, 1);
|
||||
break;
|
||||
case StatsRange.Year:
|
||||
case StatsRange.ThisYear:
|
||||
startLocal = new DateTime(nowLocal.Year, 1, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
// 数据库里的 StartedAt 是 UTC,所以查询条件必须也是 UTC
|
||||
DateTime startUtc = startLocal.ToUniversalTime();
|
||||
DateTime endUtc = nowLocal.ToUniversalTime();
|
||||
CustomStartDate = startLocal.Date;
|
||||
CustomEndDate = nowLocal.Date;
|
||||
|
||||
return (startUtc, endUtc);
|
||||
CustomStartTime = startLocal.TimeOfDay;
|
||||
CustomEndTime = nowLocal.TimeOfDay;
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private void RefreshData()
|
||||
{
|
||||
if (IsCustomRangeSelected)
|
||||
{
|
||||
LoadData();
|
||||
}
|
||||
else
|
||||
{
|
||||
UpdateDateRange();
|
||||
}
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
public void LoadData()
|
||||
{
|
||||
_timer.Debounce(async () =>
|
||||
{
|
||||
if (IsLoading) return;
|
||||
IsLoading = true;
|
||||
|
||||
try
|
||||
{
|
||||
await Task.Delay(Constants.Time.WaitingDuration);
|
||||
|
||||
var (start, end) = CalculateDateRange();
|
||||
|
||||
if (start == null || end == null)
|
||||
{
|
||||
start = end = DateTime.Now.ToUniversalTime();
|
||||
}
|
||||
|
||||
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start.Value, end.Value);
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start.Value, end.Value);
|
||||
var topSongsTask = _playHistoryService.GetTopSongsAsync(start.Value, end.Value, 10);
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start.Value, end.Value, 10);
|
||||
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start.Value, end.Value);
|
||||
|
||||
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||
|
||||
TotalDuration = await durationTask;
|
||||
var logs = await logsTask;
|
||||
TotalTracksPlayed = logs.Count;
|
||||
|
||||
TopSongs = [.. await topSongsTask];
|
||||
|
||||
var pStats = await playersTask;
|
||||
UpdatePlayerStats(pStats);
|
||||
|
||||
TopArtists = [.. await topArtistsTask];
|
||||
|
||||
ProcessHourlyStats(logs);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
{
|
||||
IsLoading = false;
|
||||
}
|
||||
}, Constants.Time.DebounceTimeout);
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
private async Task GenerateTestDataAsync()
|
||||
{
|
||||
await _playHistoryService.GenerateTestDataAsync(1000);
|
||||
LoadData(); // 生成完刷新
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IGSMTCService.IsScrobbled))
|
||||
{
|
||||
if (message.NewValue == true)
|
||||
{
|
||||
RefreshData();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -161,11 +161,21 @@
|
||||
|
||||
<NavigationViewItemSeparator Grid.Row="2" />
|
||||
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageFolder"
|
||||
<StackPanel
|
||||
Grid.Row="3"
|
||||
Margin="1,4,0,6"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
Orientation="Horizontal"
|
||||
Spacing="3">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageFolder"
|
||||
Margin="1,4,0,6"
|
||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||
<Button
|
||||
Command="{x:Bind ViewModel.OpenMediaSettingsCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=12,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}" />
|
||||
</StackPanel>
|
||||
|
||||
<TreeView
|
||||
x:Name="FolderTreeView"
|
||||
@@ -194,8 +204,12 @@
|
||||
|
||||
<controls:ContentSizer Grid.Column="1" TargetControl="{x:Bind LeftSidePanel}" />
|
||||
|
||||
<Grid x:Name="SongViewer" Grid.Column="2">
|
||||
<Grid
|
||||
x:Name="SongViewer"
|
||||
Grid.Column="2"
|
||||
RowSpacing="3">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" MinHeight="34" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
@@ -239,7 +253,10 @@
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoBitDepth" Value="{x:Bind ViewModel.TrackRightTapped.BitDepth, Mode=OneWay}" />
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoFormat" Value="{x:Bind ViewModel.TrackRightTapped.AudioFormatName, Mode=OneWay}" />
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoEncoder" Value="{x:Bind ViewModel.TrackRightTapped.Encoder, Mode=OneWay}" />
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoPath" Value="{x:Bind ViewModel.TrackRightTapped.DecodedAbsoluteUri, Mode=OneWay}" />
|
||||
<uc:PropertyRow
|
||||
x:Uid="MusicGalleryPageFileInfoPath"
|
||||
Link="{x:Bind ViewModel.TrackRightTapped.Uri, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}"
|
||||
Value="{x:Bind ViewModel.TrackRightTapped.Uri, Mode=OneWay, Converter={StaticResource UriStringToDecodedAbsoluteUri}}" />
|
||||
<uc:PropertyRow x:Uid="MusicGalleryPageFileInfoLyrics" Value="{x:Bind ViewModel.TrackRightTapped.RawLyrics, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
@@ -247,84 +264,105 @@
|
||||
</Flyout>
|
||||
</Grid.Tag>
|
||||
|
||||
<StackPanel Grid.Row="0" Spacing="6">
|
||||
|
||||
<AutoSuggestBox
|
||||
x:Name="SongSearchBox"
|
||||
x:Uid="MusicGalleryPageSongSearchBox"
|
||||
Margin="0,0,128,0"
|
||||
HorizontalAlignment="Stretch"
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
|
||||
|
||||
<Grid>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Left"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
|
||||
<CheckBox
|
||||
x:Name="SelectAllCheckBox"
|
||||
MinWidth="20"
|
||||
VerticalAlignment="Center"
|
||||
Checked="SelectAllCheckBox_Checked"
|
||||
Unchecked="SelectAllCheckBox_Unchecked"
|
||||
Visibility="{Binding ElementName=SongListViewSelectionModeToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{x:Bind GroupedTracksCVS.View.Count, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel VerticalAlignment="Center" Orientation="Horizontal">
|
||||
<TextBlock Text="{x:Bind ViewModel.SelectedTracksTotalDuration, Mode=OneWay, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
</StackPanel>
|
||||
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Right"
|
||||
Orientation="Horizontal"
|
||||
Spacing="6">
|
||||
<StackPanel Orientation="Horizontal" Spacing="12">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageSortType"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<controls:Segmented
|
||||
x:Name="Segmented"
|
||||
SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}"
|
||||
SelectionMode="Single">
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByTitle" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByAlbum" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByArtist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByFolder" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=}" />
|
||||
</controls:Segmented>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
</StackPanel>
|
||||
|
||||
<InfoBar
|
||||
x:Uid="MusicGalleryPageDataSync"
|
||||
Grid.Row="1"
|
||||
Grid.Row="0"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataSyncing, Mode=OneWay}" />
|
||||
|
||||
<InfoBar
|
||||
x:Uid="MusicGalleryPageDataSyncError"
|
||||
Grid.Row="1"
|
||||
Grid.Row="0"
|
||||
IsClosable="False"
|
||||
IsOpen="{x:Bind ViewModel.IsDataSyncError, Mode=OneWay}"
|
||||
Severity="Error" />
|
||||
|
||||
<SemanticZoom Grid.Row="2">
|
||||
<!-- 命令览 -->
|
||||
<Grid Grid.Row="1" ColumnSpacing="6">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 切换选择模式 -->
|
||||
<ToggleButton
|
||||
x:Name="SongListViewSelectionModeToggleButton"
|
||||
Grid.Column="0"
|
||||
Command="{x:Bind ViewModel.ToggleSongListViewSelectionModeCommand}"
|
||||
Content="{ui:FontIcon FontSize=16,
|
||||
FontFamily={StaticResource IconFontFamily},
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostToggleButtonStyle}" />
|
||||
|
||||
<!-- 为多选模式保留 -->
|
||||
<Grid Grid.Column="1" Visibility="{Binding ElementName=SongListViewSelectionModeToggleButton, Path=IsChecked, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<CheckBox
|
||||
x:Name="SelectAllCheckBox"
|
||||
Grid.Column="0"
|
||||
MinWidth="20"
|
||||
VerticalAlignment="Center"
|
||||
Checked="SelectAllCheckBox_Checked"
|
||||
Unchecked="SelectAllCheckBox_Unchecked" />
|
||||
<RichTextBlock Grid.Column="1" VerticalAlignment="Center">
|
||||
<Paragraph>
|
||||
<Run Text="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay}" />
|
||||
<Run Text="/" />
|
||||
<Run Text="{x:Bind GroupedTracksCVS.View.Count, Mode=OneWay}" />
|
||||
<Run Text="{x:Bind ViewModel.SelectedTracksTotalDuration, Mode=OneWay, Converter={StaticResource SecondsToFormattedTimeConverter}}" />
|
||||
</Paragraph>
|
||||
</RichTextBlock>
|
||||
|
||||
</Grid>
|
||||
|
||||
<AppBarSeparator Grid.Column="2" />
|
||||
|
||||
<!-- 排序选择 -->
|
||||
<Grid Grid.Column="3" ColumnSpacing="12">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition />
|
||||
<ColumnDefinition />
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPageSortType"
|
||||
Grid.Column="0"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
<ComboBox Grid.Column="1" SelectedIndex="{x:Bind ViewModel.SongOrderType, Converter={StaticResource EnumToIntConverter}, Mode=TwoWay}">
|
||||
<ComboBoxItem x:Uid="MusicGalleryPageSortByTitle" />
|
||||
<ComboBoxItem x:Uid="MusicGalleryPageSortByAlbum" />
|
||||
<ComboBoxItem x:Uid="MusicGalleryPageSortByArtist" />
|
||||
<ComboBoxItem x:Uid="MusicGalleryPageSortByFolder" />
|
||||
</ComboBox>
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Column="4">
|
||||
<AutoSuggestBox
|
||||
x:Name="SongSearchBox"
|
||||
x:Uid="MusicGalleryPageSongSearchBox"
|
||||
HorizontalAlignment="Stretch"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
QueryIcon="Find"
|
||||
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
|
||||
<NavigationViewItemSeparator Grid.Row="2" />
|
||||
|
||||
<SemanticZoom Grid.Row="3">
|
||||
<SemanticZoom.ZoomedInView>
|
||||
<ListView
|
||||
x:Name="SongListView"
|
||||
ItemsSource="{x:Bind GroupedTracksCVS.View, Mode=OneWay}"
|
||||
SelectionChanged="SongListView_SelectionChanged"
|
||||
SelectionMode="Multiple">
|
||||
SelectionMode="{x:Bind ViewModel.SongListViewSelectionMode, Mode=TwoWay}">
|
||||
<ListView.ContextFlyout>
|
||||
<MenuBarItemFlyout Opened="AddToMenuBarItemFlyout_Opened">
|
||||
<MenuFlyoutSubItem x:Uid="MusicGalleryPageAddToPlayingQueue" IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}">
|
||||
@@ -408,14 +446,12 @@
|
||||
</ListView.ItemsPanel>
|
||||
<ListView.GroupStyle>
|
||||
<GroupStyle>
|
||||
<GroupStyle.HeaderContainerStyle>
|
||||
<Style BasedOn="{StaticResource InteractiveListViewHeaderStyle}" TargetType="ListViewHeaderItem" />
|
||||
</GroupStyle.HeaderContainerStyle>
|
||||
<GroupStyle.HeaderTemplate>
|
||||
<DataTemplate x:DataType="models:GroupInfoList">
|
||||
<Border AutomationProperties.AccessibilityView="Raw">
|
||||
<TextBlock
|
||||
AutomationProperties.AccessibilityView="Raw"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind}" />
|
||||
</Border>
|
||||
<TextBlock Text="{x:Bind}" />
|
||||
</DataTemplate>
|
||||
</GroupStyle.HeaderTemplate>
|
||||
</GroupStyle>
|
||||
@@ -428,9 +464,7 @@
|
||||
MaxWidth="500"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
ItemsSource="{x:Bind GroupedTracksCVS.View.CollectionGroups, Mode=OneWay}"
|
||||
ScrollViewer.IsHorizontalScrollChainingEnabled="False"
|
||||
SelectionMode="None">
|
||||
ItemsSource="{x:Bind GroupedTracksCVS.View.CollectionGroups, Mode=OneWay}">
|
||||
<GridView.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:GroupInfoList">
|
||||
<TextBlock Style="{ThemeResource TitleTextBlockStyle}" Text="{Binding}" />
|
||||
@@ -440,7 +474,7 @@
|
||||
</SemanticZoom.ZoomedOutView>
|
||||
</SemanticZoom>
|
||||
|
||||
<Grid Grid.Row="2" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<Grid Grid.Row="3" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
@@ -457,155 +491,5 @@
|
||||
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
x:Name="PlayQueue"
|
||||
Width="300"
|
||||
Margin="0,4,4,72"
|
||||
Padding="12,16,12,0"
|
||||
HorizontalAlignment="Right"
|
||||
Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"
|
||||
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||
BorderThickness="1"
|
||||
CornerRadius="8"
|
||||
Translation="310,0,0">
|
||||
<Grid.TranslationTransition>
|
||||
<Vector3Transition />
|
||||
</Grid.TranslationTransition>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="*" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<Grid Grid.Row="0">
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueue"
|
||||
VerticalAlignment="Center"
|
||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||
</Grid>
|
||||
|
||||
<Grid Grid.Row="2">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<StackPanel
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Center"
|
||||
Orientation="Horizontal">
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
|
||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||
<TextBlock Text="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
<!-- Stop media session -->
|
||||
<Button
|
||||
Grid.Column="1"
|
||||
HorizontalAlignment="Right"
|
||||
Command="{x:Bind ViewModel.StopTrackCommand}"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Scroll to playing item -->
|
||||
<Button
|
||||
Grid.Column="3"
|
||||
HorizontalAlignment="Right"
|
||||
Click="ScrollToPlayingItemButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
<!-- Empty play queue -->
|
||||
<Button
|
||||
Grid.Column="4"
|
||||
HorizontalAlignment="Right"
|
||||
Click="EmptyPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
|
||||
<ListView
|
||||
x:Name="PlayingQueueListView"
|
||||
Grid.Row="3"
|
||||
ItemsSource="{x:Bind ViewModel.TrackPlayingQueue, Mode=OneWay}"
|
||||
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="0,6">
|
||||
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
||||
<StackPanel Margin="0,0,36,0">
|
||||
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
|
||||
<TextBlock
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||
Text="{Binding Track.Artist}"
|
||||
TextWrapping="Wrap" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Grid HorizontalAlignment="Right">
|
||||
<Button
|
||||
Click="RemoveFromPlayingQueueButton_Click"
|
||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||
FontSize=16,
|
||||
Glyph=}"
|
||||
Style="{StaticResource GhostButtonStyle}">
|
||||
<ToolTipService.ToolTip>
|
||||
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
|
||||
</ToolTipService.ToolTip>
|
||||
</Button>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
|
||||
<Grid Grid.Row="3">
|
||||
<interactivity:Interaction.Behaviors>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="Equal"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
<interactivity:DataTriggerBehavior
|
||||
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||
ComparisonCondition="NotEqual"
|
||||
Value="0">
|
||||
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||
</interactivity:DataTriggerBehavior>
|
||||
</interactivity:Interaction.Behaviors>
|
||||
<StackPanel
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Spacing="12">
|
||||
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
|
||||
<TextBlock
|
||||
x:Uid="MusicGalleryPagePlayingQueueEmpty"
|
||||
HorizontalAlignment="Center"
|
||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
</Grid>
|
||||
</Page>
|
||||
|
||||
@@ -3,7 +3,7 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
|
||||
using BetterLyrics.WinUI3.Services.SMTCService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using DevWinUI;
|
||||
@@ -14,6 +14,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -26,118 +27,38 @@ namespace BetterLyrics.WinUI3.Views
|
||||
public sealed partial class MusicGalleryPage : Page
|
||||
{
|
||||
public MusicGalleryPageViewModel ViewModel => (MusicGalleryPageViewModel)DataContext;
|
||||
|
||||
public bool IsPlayingQueueOpened
|
||||
{
|
||||
get { return (bool)GetValue(IsPlayingQueueOpenedProperty); }
|
||||
set { SetValue(IsPlayingQueueOpenedProperty, value); }
|
||||
}
|
||||
|
||||
public static readonly DependencyProperty IsPlayingQueueOpenedProperty =
|
||||
DependencyProperty.Register(nameof(IsPlayingQueueOpened), typeof(bool), typeof(MusicGalleryPage), new PropertyMetadata(false, OnDependencyPropertyChanged));
|
||||
private readonly ISMTCService _smtcService = Ioc.Default.GetRequiredService<ISMTCService>();
|
||||
|
||||
public MusicGalleryPage()
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = Ioc.Default.GetRequiredService<MusicGalleryPageViewModel>();
|
||||
ViewModel.AppSettings.MusicGallerySettings.PropertyChanged += MusicGallerySettings_PropertyChanged;
|
||||
}
|
||||
|
||||
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||
{
|
||||
if (d is MusicGalleryPage self)
|
||||
{
|
||||
if (e.Property == IsPlayingQueueOpenedProperty)
|
||||
{
|
||||
var newValue = (bool)e.NewValue;
|
||||
self.PlayQueue.Translation = newValue ? new() : new(310, 0, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItem()
|
||||
{
|
||||
if (ViewModel.PlayingQueueItem == null) return;
|
||||
if (PlayingQueueListView == null) return;
|
||||
PlayingQueueListView.ScrollIntoView(ViewModel.PlayingQueueItem);
|
||||
}
|
||||
|
||||
private void MusicGallerySettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
|
||||
{
|
||||
if (e.PropertyName == nameof(MusicGallerySettings.PlayQueueIndex))
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
}
|
||||
|
||||
private async void SongPathHyperlinkButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
await LauncherHelper.SelectAndShowFile(((ExtendedTrack)((HyperlinkButton)sender).DataContext).DecodedAbsoluteUri);
|
||||
}
|
||||
|
||||
private async void PlayingQueueListVireItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
||||
{
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
await ViewModel.PlayTrackAsync(item);
|
||||
PlayingQueueListView.ScrollIntoView(item);
|
||||
}
|
||||
|
||||
private async void EmptyPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ViewModel.TrackPlayingQueue.Clear();
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = -1;
|
||||
await ViewModel.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
|
||||
private void ScrollToPlayingItemButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
|
||||
private async void RemoveFromPlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool playNext = false;
|
||||
var item = (PlayQueueItem)((FrameworkElement)sender).DataContext;
|
||||
int index = ViewModel.TrackPlayingQueue.IndexOf(item);
|
||||
if (item == ViewModel.PlayingQueueItem)
|
||||
{
|
||||
playNext = true;
|
||||
}
|
||||
ViewModel.TrackPlayingQueue.Remove(item);
|
||||
if (playNext)
|
||||
{
|
||||
if (ViewModel.TrackPlayingQueue.Count == 0)
|
||||
{
|
||||
index = -1;
|
||||
}
|
||||
else if (index >= ViewModel.TrackPlayingQueue.Count)
|
||||
{
|
||||
index = ViewModel.TrackPlayingQueue.Count - 1;
|
||||
}
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = index;
|
||||
await ViewModel.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
await LauncherHelper.SelectAndShowFile(((ExtendedTrack)((HyperlinkButton)sender).DataContext).Uri.ToDecodedAbsoluteUri());
|
||||
}
|
||||
|
||||
private async void AddSongToQueueNextMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
bool startPlaying = _smtcService.TrackPlayingQueue.Count == 0;
|
||||
_smtcService.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1;
|
||||
await ViewModel.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
await _smtcService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
}
|
||||
|
||||
private async void AddSongToQueueEndMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||
{
|
||||
bool startPlaying = ViewModel.TrackPlayingQueue.Count == 0;
|
||||
ViewModel.TrackPlayingQueue.AddRange(SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
bool startPlaying = _smtcService.TrackPlayingQueue.Count == 0;
|
||||
_smtcService.TrackPlayingQueue.AddRange(SongListView.SelectedItems.Cast<ExtendedTrack>().Select(x => new PlayQueueItem(x)));
|
||||
if (startPlaying)
|
||||
{
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1;
|
||||
await ViewModel.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
await _smtcService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -229,7 +150,10 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void SelectAllCheckBox_Checked(object sender, RoutedEventArgs e)
|
||||
{
|
||||
SongListView.SelectAll();
|
||||
if (ViewModel.SongListViewSelectionMode == ListViewSelectionMode.Multiple)
|
||||
{
|
||||
SongListView.SelectAll();
|
||||
}
|
||||
}
|
||||
|
||||
private void SelectAllCheckBox_Unchecked(object sender, RoutedEventArgs e)
|
||||
@@ -243,12 +167,12 @@ namespace BetterLyrics.WinUI3.Views
|
||||
var track = (ExtendedTrack)((FrameworkElement)sender).DataContext;
|
||||
|
||||
// Play all the songs
|
||||
ViewModel.TrackPlayingQueue.Clear();
|
||||
_smtcService.TrackPlayingQueue.Clear();
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = -1;
|
||||
|
||||
ViewModel.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, displayedTracks.Select(x => new PlayQueueItem(x)));
|
||||
_smtcService.TrackPlayingQueue.InsertRange(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex + 1, displayedTracks.Select(x => new PlayQueueItem(x)));
|
||||
ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex = displayedTracks.ToList().IndexOf(track);
|
||||
await ViewModel.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
await _smtcService.PlayTrackAtAsync(ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex);
|
||||
}
|
||||
|
||||
private void Page_Loaded(object sender, RoutedEventArgs e)
|
||||
@@ -256,9 +180,12 @@ namespace BetterLyrics.WinUI3.Views
|
||||
var settings = ViewModel.AppSettings.MusicGallerySettings;
|
||||
if (settings.AutoPlay)
|
||||
{
|
||||
_ = ViewModel.PlayTrackAtAsync(settings.PlayQueueIndex);
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(1000);
|
||||
_ = _smtcService.PlayTrackAtAsync(settings.PlayQueueIndex);
|
||||
});
|
||||
}
|
||||
ScrollToPlayingItem();
|
||||
}
|
||||
|
||||
private void FolderTreeView_ItemInvoked(TreeView sender, TreeViewItemInvokedEventArgs args)
|
||||
@@ -280,7 +207,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
if (File.Exists(path))
|
||||
{
|
||||
var content = File.ReadAllText(path);
|
||||
foreach (var item in ViewModel.SelectedTracks.Select(x => x.DecodedAbsoluteUri).ToList())
|
||||
foreach (var item in ViewModel.SelectedTracks.Select(x => x.Uri.ToDecodedAbsoluteUri()).ToList())
|
||||
{
|
||||
if (!content.Contains(item))
|
||||
{
|
||||
@@ -316,5 +243,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
Loaded="RootGrid_Loaded"
|
||||
Unloaded="RootGrid_Unloaded">
|
||||
|
||||
<local:MusicGalleryPage x:Name="MusicGalleryPage" IsPlayingQueueOpened="{Binding ElementName=NowPlayingBar, Path=IsPlayingQueueOpened, Mode=OneWay}" />
|
||||
<local:MusicGalleryPage x:Name="MusicGalleryPage" />
|
||||
|
||||
<local:NowPlayingPage
|
||||
x:Name="NowPlayingPage"
|
||||
@@ -32,15 +32,28 @@
|
||||
x:Name="NowPlayingBar"
|
||||
VerticalAlignment="Bottom"
|
||||
IsAutoHideEnabled="False"
|
||||
PlayQueueButtonClick="NowPlayingBar_PlayingQueueClick"
|
||||
PlaybackOrder="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlaybackOrder, Mode=TwoWay}"
|
||||
ShowPlaybackOrderButton="True"
|
||||
ShowPlayingQueueButton="True"
|
||||
ShowSongInfo="True"
|
||||
ShowStopButton="True"
|
||||
SongInfoTapped="NowPlayingBar_SongInfoTapped"
|
||||
TimeTapped="NowPlayingBar_TimeTapped" />
|
||||
|
||||
<!-- Title bar -->
|
||||
<StackPanel VerticalAlignment="Top" Orientation="Horizontal" />
|
||||
TimeTapped="NowPlayingBar_TimeTapped">
|
||||
<uc:NowPlayingBar.ContextFlyout>
|
||||
<Flyout x:Name="PlayQueueFlyout" ShouldConstrainToRootBounds="False">
|
||||
<Flyout.FlyoutPresenterStyle>
|
||||
<Style BasedOn="{StaticResource FlyoutGhostStyle}" TargetType="FlyoutPresenter">
|
||||
<Setter Property="MaxHeight" Value="600" />
|
||||
</Style>
|
||||
</Flyout.FlyoutPresenterStyle>
|
||||
<uc:PlayQueue
|
||||
x:Name="PlayQueue"
|
||||
Width="300"
|
||||
MaxHeight="600" />
|
||||
</Flyout>
|
||||
</uc:NowPlayingBar.ContextFlyout>
|
||||
</uc:NowPlayingBar>
|
||||
|
||||
</Grid>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -14,6 +14,7 @@ using Microsoft.UI.Windowing;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media.Imaging;
|
||||
using System.Threading.Tasks;
|
||||
using static Vanara.PInvoke.AdvApi32;
|
||||
|
||||
// To learn more about WinUI, the WinUI project structure,
|
||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||
@@ -30,7 +31,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public MusicGalleryWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<MusicGalleryWindowViewModel>();
|
||||
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
|
||||
public MusicGalleryWindow()
|
||||
{
|
||||
@@ -47,7 +48,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void UpdateAlbumArtThemeColors()
|
||||
{
|
||||
var result = _mediaSessionsService.CalculateAlbumArtThemeColors(
|
||||
var result = _gsmtcService.CalculateAlbumArtThemeColors(
|
||||
ViewModel.AppSettings.MusicGallerySettings.LyricsWindowStatus, Colors.Transparent);
|
||||
|
||||
NowPlayingPage.AlbumArtThemeColors = result;
|
||||
@@ -69,9 +70,9 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
public void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
|
||||
if (message.PropertyName == nameof(IGSMTCService.AlbumArtBitmapImage))
|
||||
{
|
||||
UpdateAlbumArtThemeColors();
|
||||
}
|
||||
@@ -131,7 +132,14 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void NowPlayingBar_PlayingQueueClick(object sender, System.EventArgs e)
|
||||
{
|
||||
MusicGalleryPage.IsPlayingQueueOpened = !MusicGalleryPage.IsPlayingQueueOpened;
|
||||
if (PlayQueueFlyout.IsOpen)
|
||||
{
|
||||
PlayQueueFlyout.Hide();
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayQueueFlyout.ShowAt(NowPlayingBar);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -27,14 +27,14 @@ using Windows.Storage.Streams;
|
||||
namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
public sealed partial class NowPlayingPage : Page,
|
||||
IRecipient<PropertyChangedMessage<SongInfo?>>,
|
||||
IRecipient<PropertyChangedMessage<SongInfo>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsLayoutOrientation>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsDisplayType>>,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<string>>
|
||||
{
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
|
||||
private readonly DispatcherQueueTimer _layoutChangedTimer = App.Current.Resources.DispatcherQueue.CreateTimer();
|
||||
private readonly DispatcherQueueTimer _scrollChangedTimer = App.Current.Resources.DispatcherQueue.CreateTimer();
|
||||
@@ -114,9 +114,9 @@ namespace BetterLyrics.WinUI3.Views
|
||||
var artistsFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.ArtistNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
|
||||
var albumFontSize = albumArtLayoutSettings.IsAutoSongInfoFontSize ? lyricsLayoutMetrics.AlbumNameSize : albumArtLayoutSettings.SongInfoFontSize * 0.8;
|
||||
|
||||
RenderTextBlock(TitleTextBlock, _mediaSessionsService.CurrentSongInfo?.Title, titleFontSize);
|
||||
RenderTextBlock(ArtistsTextBlock, _mediaSessionsService.CurrentSongInfo?.DisplayArtists, artistsFontSize);
|
||||
RenderTextBlock(AlbumTextBlock, _mediaSessionsService.CurrentSongInfo?.Album, albumFontSize);
|
||||
RenderTextBlock(TitleTextBlock, _gsmtcService.CurrentSongInfo.Title, titleFontSize);
|
||||
RenderTextBlock(ArtistsTextBlock, _gsmtcService.CurrentSongInfo.DisplayArtists, artistsFontSize);
|
||||
RenderTextBlock(AlbumTextBlock, _gsmtcService.CurrentSongInfo.Album, albumFontSize);
|
||||
}
|
||||
|
||||
private void UpdateSongInfoOpacity()
|
||||
@@ -139,6 +139,15 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
}
|
||||
|
||||
private async void RefreshSongInfo()
|
||||
{
|
||||
SongInfoStackPanel.Opacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
RenderSongInfo();
|
||||
SongInfoStackPanel.Opacity = 1;
|
||||
UpdateSongInfoOpacity();
|
||||
}
|
||||
|
||||
// ==== AlbumArt
|
||||
private void UpdateAlbumArtOpacity()
|
||||
{
|
||||
@@ -555,7 +564,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
private void LyricsScrollViewer_PointerReleased(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
{
|
||||
LyricsCanvas.IsMousePressing = false;
|
||||
_mediaSessionsService.ChangeLyricsLine(LyricsCanvas.CurrentHoveringLineIndex);
|
||||
_gsmtcService.ChangeLyricsLine(LyricsCanvas.CurrentHoveringLineIndex);
|
||||
}
|
||||
|
||||
private void LyricsScrollViewer_PointerExited(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||
@@ -575,17 +584,13 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
// ====
|
||||
|
||||
public async void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||
public async void Receive(PropertyChangedMessage<SongInfo> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentSongInfo))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentSongInfo))
|
||||
{
|
||||
SongInfoStackPanel.Opacity = 0;
|
||||
await Task.Delay(Constants.Time.AnimationDuration);
|
||||
RenderSongInfo();
|
||||
SongInfoStackPanel.Opacity = 1;
|
||||
UpdateSongInfoOpacity();
|
||||
RefreshSongInfo();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -686,5 +691,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -47,7 +47,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
public LyricsWindowStatus LyricsWindowStatus { get; private set; }
|
||||
|
||||
public NowPlayingWindowViewModel ViewModel { get; private set; } = Ioc.Default.GetRequiredService<NowPlayingWindowViewModel>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
|
||||
public NowPlayingWindow(LyricsWindowStatus status)
|
||||
@@ -141,7 +141,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void UpdateAlbumArtThemeColors()
|
||||
{
|
||||
var result = _mediaSessionsService.CalculateAlbumArtThemeColors(LyricsWindowStatus, _backdropAccentColor);
|
||||
var result = _gsmtcService.CalculateAlbumArtThemeColors(LyricsWindowStatus, _backdropAccentColor);
|
||||
|
||||
NowPlayingPage.AlbumArtThemeColors = result;
|
||||
RootGrid.RequestedTheme = result.ThemeType;
|
||||
@@ -231,7 +231,7 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void OnAutoShowOrHideWindowChanged()
|
||||
{
|
||||
this.SetLyricsWindowVisibilityByPlayingStatus(_mediaSessionsService.CurrentIsPlaying, DispatcherQueue);
|
||||
this.SetLyricsWindowVisibilityByPlayingStatus(_gsmtcService.CurrentIsPlaying, DispatcherQueue);
|
||||
}
|
||||
|
||||
private void OnIsAdaptToEnvironmentChanged()
|
||||
@@ -466,9 +466,9 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.CurrentIsPlaying))
|
||||
if (message.PropertyName == nameof(IGSMTCService.CurrentIsPlaying))
|
||||
{
|
||||
OnAutoShowOrHideWindowChanged();
|
||||
}
|
||||
@@ -520,9 +520,9 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
public void Receive(PropertyChangedMessage<BitmapImage?> message)
|
||||
{
|
||||
if (message.Sender is IMediaSessionsService)
|
||||
if (message.Sender is IGSMTCService)
|
||||
{
|
||||
if (message.PropertyName == nameof(IMediaSessionsService.AlbumArtBitmapImage))
|
||||
if (message.PropertyName == nameof(IGSMTCService.AlbumArtBitmapImage))
|
||||
{
|
||||
UpdateAlbumArtThemeColors();
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ using BetterLyrics.WinUI3.Extensions;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Hooks;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||
using BetterLyrics.WinUI3.Services.GSMTCService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
@@ -24,7 +24,7 @@ namespace BetterLyrics.WinUI3.Views;
|
||||
public sealed partial class SystemTrayWindow : Window, IRecipient<PropertyChangedMessage<List<string>>>
|
||||
{
|
||||
private ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
|
||||
|
||||
private WindowMessageMonitor _wmm;
|
||||
|
||||
@@ -92,13 +92,13 @@ public sealed partial class SystemTrayWindow : Window, IRecipient<PropertyChange
|
||||
{
|
||||
GlobalHotKeyHook.UpdateHotKey(this, ShortcutID.PlayOrPauseSong, _settingsService.AppSettings.GeneralSettings.PlayOrPauseShortcut, (() =>
|
||||
{
|
||||
if (_mediaSessionsService.CurrentIsPlaying)
|
||||
if (_gsmtcService.CurrentIsPlaying)
|
||||
{
|
||||
_ = _mediaSessionsService.PauseAsync();
|
||||
_ = _gsmtcService.PauseAsync();
|
||||
}
|
||||
else
|
||||
{
|
||||
_ = _mediaSessionsService.PlayAsync();
|
||||
_ = _gsmtcService.PlayAsync();
|
||||
}
|
||||
}));
|
||||
}
|
||||
@@ -107,7 +107,7 @@ public sealed partial class SystemTrayWindow : Window, IRecipient<PropertyChange
|
||||
{
|
||||
GlobalHotKeyHook.UpdateHotKey(this, ShortcutID.PreviousSong, _settingsService.AppSettings.GeneralSettings.PreviousSongShortcut, () =>
|
||||
{
|
||||
_ = _mediaSessionsService.PreviousAsync();
|
||||
_ = _gsmtcService.PreviousAsync();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -115,7 +115,7 @@ public sealed partial class SystemTrayWindow : Window, IRecipient<PropertyChange
|
||||
{
|
||||
GlobalHotKeyHook.UpdateHotKey(this, ShortcutID.NextSong, _settingsService.AppSettings.GeneralSettings.NextSongShortcut, () =>
|
||||
{
|
||||
_ = _mediaSessionsService.NextAsync();
|
||||
_ = _gsmtcService.NextAsync();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
BIN
Promotion/banner_fade.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
300
README.CN.md
@@ -1,235 +1,185 @@
|
||||

|
||||
|
||||
<div align=center>
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="96">
|
||||
</div>
|
||||
|
||||
<h2 align=center>
|
||||
BetterLyrics
|
||||
</h2>
|
||||
|
||||
<h4 align="center">
|
||||
🤩 一款优雅且高度自定义的歌词/播放器应用,基于 WinUI3/Win2D 构建
|
||||
</h4>
|
||||
[**中文**](README.CN.md) | [**English**](README.md)
|
||||
|
||||
<div align="center">
|
||||
|
||||
[使用指南](https://github.com/jayfunc/BetterLyrics/wiki/使用指南) | [隐私政策](PrivacyPolicy.CN.md) | [服务协议](TermsofService.CN.md)
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="Logo" width="120">
|
||||
|
||||
<h1>BetterLyrics</h1>
|
||||
|
||||
<h4>
|
||||
🤩 一款优雅且高度自定义的歌词可视化与全能音乐播放应用 <br>
|
||||
基于 WinUI3 / Win2D 构建
|
||||
</h4>
|
||||
|
||||
<div>
|
||||
<img src="https://img.shields.io/badge/Language-C%23-purple" alt="C#">
|
||||
<img src="https://img.shields.io/badge/Framework-WinUI%203-blue" alt="WinUI 3">
|
||||
<img src="https://img.shields.io/badge/License-GPL_v3.0-blue" alt="License">
|
||||
<a href="https://github.com/jayfunc/BetterLyrics/stargazers"><img src="https://img.shields.io/github/stars/jayfunc/BetterLyrics" alt="Stars"></a>
|
||||
<a href="https://crowdin.com/project/betterlyrics"><img src="https://badges.crowdin.net/betterlyrics/localized.svg" alt="Crowdin"></a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<img src="Promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||
[](https://github.com/jayfunc/BetterLyrics/stargazers)
|
||||
[](https://crowdin.com/project/betterlyrics)
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||
[](https://deepwiki.com/jayfunc/BetterLyrics)
|
||||
[](https://zread.ai/jayfunc/BetterLyrics)
|
||||
|
||||
</div>
|
||||
## 🔥 精选推荐与社区
|
||||
|
||||
<div align="center">
|
||||
|
||||
<mark>**_💞 BetterLyrics 的发展离不开每一位贡献者、反馈者和用户的全力支持。_**</mark>
|
||||
| HelloGitHub 推荐 | 少数派 SSPAI 推荐 | 🤖 AI 问答 |
|
||||
| :---: | :---: | :---: |
|
||||
| <a href="https://hellogithub.com/repository/jayfunc/BetterLyrics" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=d2af74f0aea146ad8e4b2086982f5777&claim_uid=SgtQs9c54C8wjnv" alt="HelloGitHub" height="40"></a> | [**阅读评测文章**](https://sspai.com/post/101028) | [](https://deepwiki.com/jayfunc/BetterLyrics) <br> [](https://zread.ai/jayfunc/BetterLyrics) |
|
||||
|
||||
<mark>**_项目持续活跃开发中,可能会遇到未知问题。_**</mark>
|
||||
**交流群:** [QQ 群(1054700388)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) | [QQ 频道(BetterLyrics4U)](https://pd.qq.com/s/1u1ntkyzr?b=9) | [Discord](https://discord.gg/5yAQPnyCKv) | [Telegram](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
|
||||
</div>
|
||||
|
||||
## ✍️ 协助翻译
|
||||
## 🧪 下载与安装
|
||||
|
||||
找不到你的语言?有更好的翻译?没关系!😆 访问 [此处](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file) 查看如何贡献翻译!
|
||||
<div align="center">
|
||||
|
||||
## 🎉 该项目入选少数派推荐文章!
|
||||
| Microsoft Store (推荐) | 手动安装 |
|
||||
| :---: | :---: |
|
||||
| <a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="160"/></a><br>无限期免费试用(功能与付费版一致) | [**📦 最新版本 (.zip)**](https://github.com/jayfunc/BetterLyrics/releases/latest)<br>[查看安装指南](https://www.cnblogs.com/jayfunc/p/19212078) |
|
||||
|
||||
文章链接:[BetterLyrics - 一款专为 Windows 打造的沉浸式流畅歌词显示软件](https://sspai.com/post/101028)。
|
||||
[📖 用户指南](https://github.com/jayfunc/BetterLyrics/wiki/使用指南) | [🔒 隐私政策](PrivacyPolicy.CN.md) | [⚖️ 服务条款](TermsofService.CN.md)
|
||||
|
||||
## 🔈 反馈交流群
|
||||
</div>
|
||||
|
||||
[QQ 群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) | [Discord Server](https://discord.gg/5yAQPnyCKv) | [Telegram Group](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
## 🌟 核心功能
|
||||
|
||||
## 🌟 特色功能
|
||||
- 🎨 **绝美视觉与 UI**
|
||||
- **优雅设计:** 基于 WinUI3 & Win2D 的流畅、高度个性化风格。
|
||||
- **沉浸特效:** 支持流体背景、3D/扇形歌词、雪花粒子等多种效果。
|
||||
- **深度定制:** 按需配置动画、字体和行为逻辑,打造你的专属播放器。
|
||||
|
||||
- 🌠 **精美的用户界面**
|
||||
- 流畅、高度自定义的样式、动画、动效
|
||||
- 沉浸式流体背景
|
||||
- 透视/扇形歌词
|
||||
- 雪花效果
|
||||
- 多种歌词滚动函数
|
||||
- ...
|
||||
- ↔️ **强大的歌词翻译**
|
||||
- 本地机器翻译 (支持 30 多种语言)
|
||||
- 自动读取本地音乐文件内嵌歌词
|
||||
- 🧩 **多种歌词源**
|
||||
- 💾 本地源
|
||||
- 音乐文件 (内嵌歌词)
|
||||
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) 文件 (传统格式、增强格式)
|
||||
- [.eslrc](https://github.com/ESLyric/release) 文件
|
||||
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) 文件
|
||||
- ☁️ 在线源
|
||||
- QQ 音乐
|
||||
- 网易云音乐
|
||||
- 酷狗音乐
|
||||
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- <details><summary>⚠️ Apple Music (需要额外配置)</summary>
|
||||
- 🎧 **多功能播放与连接**
|
||||
- **内置播放器:** 支持播放 **本地硬盘** 文件或通过 **网络协议** (SMB, WebDAV) 流式播放。
|
||||
- **外部集成:** 可视化来自 Spotify, Apple Music, 网易云音乐及 [其他多种播放器](https://github.com/jayfunc/BetterLyrics/wiki/使用指南#已知支持的音乐播放器配置指南) 的音乐。
|
||||
|
||||
- 浏览器打开 Apple Music,打开开发者工具。刷新网页,回到开发者工具窗口,筛选出 Fetch/XHR,选择一个请求,在请求标头中找到 media-user-token 并复制其值。
|
||||
- 打开 BetterLyrics 转到播放源设置。在 Media-User-Token (for Apple Music) 中粘贴复制的值并点按右侧对勾。
|
||||
|
||||
- 🎶 **支持众多音乐播放器**
|
||||
- 🌐 **强大的歌词系统**
|
||||
- **离线翻译:** 注重隐私的本地机器翻译(支持 30+ 种语言)。
|
||||
- **全面源支持:** 支持 .lrc (标准/增强), .eslrc, .ttml, 内嵌标签,以及在线源(QQ 音乐, 网易云, LRCLIB)。
|
||||
- **Apple Music:** 支持歌词获取(需配置 Token)。
|
||||
|
||||
- 点击 [此处](https://github.com/jayfunc/BetterLyrics/wiki/使用指南#已知支持的音乐播放器配置指南) 查看详细信息
|
||||
- 🪟 **全场景显示模式**
|
||||
- **标准模式:** 全屏沉浸式体验。
|
||||
- **停靠模式:** 贴附于屏幕边缘的精致侧边栏。
|
||||
- **桌面悬浮:** 悬浮于所有应用之上的歌词挂件。
|
||||
|
||||
- 🪟 **多种显示模式**
|
||||
- **标准模式**
|
||||
- 标准的歌词窗口样式,沉浸式的音乐歌词体验。
|
||||
- **停靠模式**
|
||||
- 停靠在屏幕上/下边缘的轻量歌词窗口,工作休闲互不打扰。
|
||||
- **桌面模式**
|
||||
- 悬浮在所有应用上层,不能被选中,但能直击你的使用需求。
|
||||
- **更多模式...**
|
||||
- 等你来发现...
|
||||
- 🧠 **智能化行为**
|
||||
- 根据歌曲播放状态自动显隐歌词窗口
|
||||
- 🧠 **智能行为**
|
||||
- 音乐暂停时自动隐藏。
|
||||
|
||||
## 🖼️ 屏幕截图
|
||||
## 🖼️ 软件截图
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<div align="center">
|
||||
|
||||
> ⚠️ 由于 GIF 格式帧率限制,效果仅作展示。请以实机效果为准。
|
||||
| 标准视图 | 侧边栏模式 |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/std.png" width="100%"> | <img src="Screenshots/narrow.png" width="100%"> |
|
||||
|
||||

|
||||

|
||||
| 歌词视觉特效 | 多模式共存 |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/effect.png" width="100%"> | <img src="Screenshots/all-in-one.png" width="100%"> |
|
||||
|
||||
## 📹 演示
|
||||
| 全屏模式 | 全屏模式 |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/fs3.png" width="100%"> | <img src="Screenshots/fs2.png" width="100%"> |
|
||||
|
||||
在 [哔哩哔哩](https://www.bilibili.com/video/BV1QRstz1EGt/) 上观看于 2025 年 10 月 21 日上传的演示视频。
|
||||
| 音乐库 | 播放统计 |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/music-gallery.png" width="100%"> | <img src="Screenshots/stats.png" width="100%"> |
|
||||
|
||||
## 🧪 即刻体验
|
||||
</div>
|
||||
|
||||
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
|
||||
<img src="https://get.microsoft.com/images/zh-cn%20dark.svg" width="200"/>
|
||||
</a>
|
||||
## 📹 演示视频
|
||||
|
||||
**无限期**免费试用版和付费版**无任何区别**。
|
||||
> 观看我们在 Bilibili 发布的演示视频(上传于 2025 年 10 月 21 日):[点击观看](https://www.bilibili.com/video/BV1QRstz1EGt/)。
|
||||
|
||||
如果喜欢该软件,请考虑 [捐赠](#-捐赠) 或在 **Microsoft Store** 购买,感谢您的支持! 🥰
|
||||
## ✍️ 贡献与构建
|
||||
|
||||
无法从 Microsoft Store 下载?尝试以下方法:
|
||||
- [从 Microsoft Store 外部下载](https://www.cnblogs.com/jayfunc/p/19212083)
|
||||
- 转至 [最新发布页](https://github.com/jayfunc/BetterLyrics/releases/latest) 并从 `Assets`(资产)列表下载 `.zip` 文件。(安装方法参考 [此文档](https://www.cnblogs.com/jayfunc/p/19212078)。)
|
||||
**协助翻译:** 找不到你的语言?[点此开始翻译](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file)。
|
||||
|
||||
## 🏗️ 构建
|
||||
**从源码构建:**
|
||||
> 构建前,请确保已替换 `Constants` 文件夹下的 `DiscordTemplate.cs` 和 `LastFM.cs`。
|
||||
|
||||
在构建之前确保:
|
||||
- 替换文件 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\DiscordTemplate` 为 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\DiscordTemplate.cs`.
|
||||
- 替换文件 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFMTemplate` 为 `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFM.cs`。
|
||||
## 🤑 赞助与捐赠
|
||||
|
||||
## 🤑 捐赠
|
||||
如果你喜欢 BetterLyrics,请考虑支持它。你的支持有助于项目持续发展!
|
||||
|
||||
如果你喜欢本应用,请考虑捐赠支持开发者。这将有助于本应用的长远发展。
|
||||
<div align="center">
|
||||
|
||||
通过以下途径捐赠:
|
||||
- [PayPal](https://paypal.me/zhefangpay)
|
||||
- [Buy Me a Coffee](https://buymeacoffee.com/founchoo)
|
||||
- [爱发电](https://afdian.com/a/jayfunc)
|
||||
- <details><summary>支付宝</summary>
|
||||
|
||||

|
||||
|
||||
</detais>
|
||||
| 网页平台 | 支付宝 (扫码) | 微信 (扫码) |
|
||||
| :---: | :---: | :---: |
|
||||
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="Donate/Alipay.jpg" width="150"> | <img src="Donate/WeChatReward.png" width="150"> |
|
||||
|
||||
- <details><summary>微信</summary>
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
**[查看完整赞助者名单 (Hall of Fame)](SPONSORS.md)**
|
||||
|
||||
本项目的持续发展离不开大家的支持。**[查看完整鸣谢名单](SPONSORS.md)**
|
||||
</div>
|
||||
|
||||
## 📄 许可证
|
||||
## ⭐ Star 历史趋势
|
||||
|
||||
本项目采用 GNU 通用公共许可证 v3.0 授权。详情请参阅 [LICENSE](https://github.com/jayfunc/BetterLyrics/blob/dev/LICENSE) 文件。
|
||||
<div align="center">
|
||||
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date" width="100%">
|
||||
</div>
|
||||
|
||||
## 💖 感谢
|
||||
## 📄 许可与致谢
|
||||
|
||||
部分功能及代码引用或修改自公开资料库,包括但不限于下述开源项目/包、教程等,在此一并感谢。
|
||||
本项目采用 **GNU General Public License v3.0** 许可证。
|
||||
|
||||
| 项目/包 | 描述 |
|
||||
<details>
|
||||
<summary><b>💖 特别致谢、引用与灵感</b></summary>
|
||||
<br>
|
||||
|
||||
**依赖与引用:**
|
||||
|
||||
| 项目/包 (Projects/Packages) | 描述 (Description) |
|
||||
| :--- | :--- |
|
||||
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | 为 QQ、网易、酷狗在线歌词源提供歌词抓取、解密、解析等一系列方法 |
|
||||
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB 歌词 API |
|
||||
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | Apple Music 歌词抓取(Python 实现) |
|
||||
| [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) | 从音乐文件提取图片 |
|
||||
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | 提供有关窗口的开箱即用的 Win32 API |
|
||||
| [TagLib#](https://github.com/mono/taglib-sharp) | 读取音乐文件内嵌的原始歌词内容 |
|
||||
| [Vanara](https://github.com/dahall/Vanara) | 提供开箱即用的 Win32 API |
|
||||
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | 离线翻译核心 |
|
||||
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | 动态流体背景 |
|
||||
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | 频谱图 |
|
||||
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | 为 WinUI3 提供众多开箱即用的功能 |
|
||||
| ... | ... |
|
||||
| [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) | 用于从音乐文件中提取图片(封面) |
|
||||
| [cutlet](https://github.com/polm/cutlet) | 提供将日语转换为罗马音 (Romaji) 的功能 |
|
||||
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | 为构建 WinUI 3 应用程序提供许多开箱即用的功能 |
|
||||
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | 动态流体背景的实现 |
|
||||
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | 提供离线歌词翻译功能 |
|
||||
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB 歌词 API 数据源 |
|
||||
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | 提供 QQ、网易云音乐和酷狗音乐源的歌词抓取、解密及解析功能 |
|
||||
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | 使用 Python 获取 Apple Music 歌词 |
|
||||
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | 音频频谱可视化参考 |
|
||||
| [TagLib#](https://github.com/mono/taglib-sharp) | 用于读取原始歌词内容(元数据) |
|
||||
| [Vanara](https://github.com/dahall/Vanara) | Win32 API 封装库 |
|
||||
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | 提供访问与窗口管理相关的 Win32 API 的便捷方式 |
|
||||
|
||||
点按 [此处](https://github.com/jayfunc/BetterLyrics/network/dependencies) 查看所有依赖。
|
||||
查看 [完整依赖列表](https://github.com/jayfunc/BetterLyrics/network/dependencies)。
|
||||
|
||||
### 教程、博客等
|
||||
<br>
|
||||
|
||||
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
|
||||
- [Bilibili -【WinUI3】SystemBackdropController:定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
|
||||
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
|
||||
- [Win2D 中的游戏循环:CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
|
||||
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
|
||||
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
|
||||
|
||||
## 💡 灵感来源
|
||||
|
||||
部分设计思路参考自下述插件/软件(不含间接或直接引用、修改的代码,仅作为设计思路指导方向)。
|
||||
**💡 灵感来源:**
|
||||
|
||||
部分设计理念参考了以下插件/软件(仅作为设计思路参考,不涉及代码引用):
|
||||
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
|
||||
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
|
||||
|
||||
## ⭐ 星标记录
|
||||
</details>
|
||||
|
||||
<div style="display: flex; justify-content: space-around; align-items: flex-start;">
|
||||
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date" width="100%" >
|
||||
## 💭 分享到社交媒体
|
||||
|
||||
<details>
|
||||
<summary><b>点击展开</b></summary>
|
||||
<br>
|
||||
<div align="center">
|
||||
<img src="https://socialify.git.ci/jayfunc/BetterLyrics/image?description=1&forks=1&issues=1&language=1&name=1&owner=1&pulls=1&stargazers=1&theme=Light" width="48%">
|
||||
<img src="https://opengraph.githubassets.com/<any_hash_number>/jayfunc/BetterLyrics" width="48%">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
## 🤗 欢迎反馈问题、提交代码
|
||||
<br>
|
||||
|
||||
如果发现 Bug 请在 Issues 内提出,同时也欢迎任何想法、建议。
|
||||
|
||||
## ⚠️ 免责声明
|
||||
|
||||
本项目按“原样”提供,不提供任何形式的担保。
|
||||
所有歌词、字体、图标及其他第三方资源均为其各自版权所有者的财产。
|
||||
本项目作者不主张对这些资源的所有权。
|
||||
本项目为非商业用途,不得用于侵犯任何权利。
|
||||
用户有责任确保其使用符合适用的法律和许可协议。
|
||||
|
||||
## 💭 社交媒体分享
|
||||
|
||||

|
||||
|
||||

|
||||
<div align="center">
|
||||
<mark><i>本项目正处于积极开发阶段;可能会出现意外问题。</i></mark><br>
|
||||
<sub>免责声明:本项目“按原样”提供。所有第三方资源归其各自所有者所有。</sub>
|
||||
</div>
|
||||
|
||||
302
README.md
@@ -1,243 +1,185 @@
|
||||

|
||||
|
||||
<div align=center>
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="" width="96">
|
||||
</div>
|
||||
|
||||
<h2 align=center>
|
||||
BetterLyrics
|
||||
</h2>
|
||||
|
||||
<h4 align="center">
|
||||
🤩 An elegant and deeply customizable lyrics & player app, built with WinUI3/Win2D
|
||||
</h4>
|
||||
[**中文**](README.CN.md) | [**English**](README.md)
|
||||
|
||||
<div align="center">
|
||||
|
||||
[User Guide](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide) | [Privacy Policy](PrivacyPolicy.md) | [Terms of Service](TermsofService.md)
|
||||
<img src="BetterLyrics.WinUI3/BetterLyrics.WinUI3/Assets/Logo.png" alt="Logo" width="120">
|
||||
|
||||
<h1>BetterLyrics</h1>
|
||||
|
||||
<h4>
|
||||
🤩 An elegant and deeply customizable lyrics visualizer & versatile music player <br>
|
||||
Built with WinUI3 / Win2D
|
||||
</h4>
|
||||
|
||||
<div>
|
||||
<img src="https://img.shields.io/badge/Language-C%23-purple" alt="C#">
|
||||
<img src="https://img.shields.io/badge/Framework-WinUI%203-blue" alt="WinUI 3">
|
||||
<img src="https://img.shields.io/badge/License-GPL_v3.0-blue" alt="License">
|
||||
<a href="https://github.com/jayfunc/BetterLyrics/stargazers"><img src="https://img.shields.io/github/stars/jayfunc/BetterLyrics" alt="Stars"></a>
|
||||
<a href="https://crowdin.com/project/betterlyrics"><img src="https://badges.crowdin.net/betterlyrics/localized.svg" alt="Crowdin"></a>
|
||||
</div>
|
||||
|
||||
<br>
|
||||
|
||||
<img src="Promotion/banner.png" alt="Banner" width="100%" style="border-radius: 10px;">
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||
<br>
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||
[](https://github.com/jayfunc/BetterLyrics/stargazers)
|
||||
[](https://crowdin.com/project/betterlyrics)
|
||||
|
||||
</div>
|
||||
|
||||
<div align=center>
|
||||
|
||||
[](https://deepwiki.com/jayfunc/BetterLyrics)
|
||||
[](https://zread.ai/jayfunc/BetterLyrics)
|
||||
|
||||
</div>
|
||||
## 🔥 Featured & Community
|
||||
|
||||
<div align="center">
|
||||
|
||||
<mark>**_💞 BetterLyrics is made possible by all its contributors, bug reporters and users._**</mark>
|
||||
| Featured by HelloGitHub | Featured by SSPAI | 🤖 Ask AI |
|
||||
| :---: | :---: | :---: |
|
||||
| <a href="https://hellogithub.com/repository/jayfunc/BetterLyrics" target="_blank"><img src="https://abroad.hellogithub.com/v1/widgets/recommend.svg?rid=d2af74f0aea146ad8e4b2086982f5777&claim_uid=SgtQs9c54C8wjnv" alt="HelloGitHub" height="40"></a> | [**Read the Review Article**](https://sspai.com/post/101028) | [](https://deepwiki.com/jayfunc/BetterLyrics) <br> [](https://zread.ai/jayfunc/BetterLyrics) |
|
||||
|
||||
**Chat Groups:** [QQ Group (1054700388)](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) | [QQ Channel (BetterLyrics4U)](https://pd.qq.com/s/1u1ntkyzr?b=9) | [Discord](https://discord.gg/5yAQPnyCKv) | [Telegram](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
|
||||
</div>
|
||||
|
||||
## 🧪 Download & Install
|
||||
|
||||
<div align="center">
|
||||
|
||||
**_[中文版 README 请点按此处](https://github.com/jayfunc/BetterLyrics/blob/dev/README.CN.md)_**
|
||||
| Microsoft Store (Recommended) | Manual Install |
|
||||
| :---: | :---: |
|
||||
| <a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct"><img src="https://get.microsoft.com/images/en-us%20dark.svg" width="160"/></a><br>Unlimited free trial (Same as paid) | [**📦 Latest Release (.zip)**](https://github.com/jayfunc/BetterLyrics/releases/latest)<br>See [Installation Guide](https://jayfunc.blog/blog/how-to-install-zip) |
|
||||
|
||||
<mark>**_This project is under active development; unexpected issues may occur._**</mark>
|
||||
[📖 User Guide](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide) | [🔒 Privacy Policy](PrivacyPolicy.md) | [⚖️ Terms of Service](TermsofService.md)
|
||||
|
||||
</div>
|
||||
|
||||
## ✍️ Help us translate into your language
|
||||
## 🌟 Highlighted Features
|
||||
|
||||
Cannot find your language? Or have better translations? Don't worry! Start translating and becoming one of the contributors! 😆 See [here](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file) for more info on how to contribute.
|
||||
- 🎨 **Stunning Visuals & UI**
|
||||
- **Elegant Design:** Smooth, highly personalized style powered by WinUI3 & Win2D.
|
||||
- **Immersive Effects:** Fluid backgrounds, 3D/Fan-shaped lyrics, snowflake particles, and more.
|
||||
- **Deep Customization:** Configure animations, fonts, and behaviors to your taste.
|
||||
|
||||
## 🎉 This project was featured by SSPAI!
|
||||
- 🎧 **Versatile Playback & Connectivity**
|
||||
- **Built-in Player:** Play from **Local Drives** or stream via **Network Protocols** (SMB, WebDAV).
|
||||
- **External Integration:** Visualizes music from Spotify, Apple Music, NetEase, and [many others](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide#known-supported-music-players-configuration-guide).
|
||||
|
||||
Check out the article: [BetterLyrics – An immersive and smooth lyrics display tool designed for Windows](https://sspai.com/post/101028).
|
||||
- 🌐 **Advanced Lyrics System**
|
||||
- **Offline Translation:** Privacy-focused local machine translation (30+ languages).
|
||||
- **Comprehensive Sources:** .lrc (Standard/Enhanced), .eslrc, .ttml, embedded tags, and online sources (QQ Music, NetEase, LRCLIB).
|
||||
- **Apple Music:** Supports lyrics fetching (Requires token configuration).
|
||||
|
||||
## 🔈 Feedback and chat group
|
||||
- 🪟 **Display Modes for Every Scenario**
|
||||
- **Standard:** Full immersive experience.
|
||||
- **Docked:** A sleek bar attached to your screen edge.
|
||||
- **Desktop Overlay:** Lyrics floating above all apps.
|
||||
|
||||
[QQ 群](https://qun.qq.com/universal-share/share?ac=1&authKey=4Q%2BYTq3wZldYpF5SbS5c19ECFsiYoLZFAIcBNNzYpBUtiEjaZ8sZ%2F%2BnFN0qw3lad&busi_data=eyJncm91cENvZGUiOiIxMDU0NzAwMzg4IiwidG9rZW4iOiJiVnhqemVYN0N5QVc3b1ZkR24wWmZOTUtvUkJoWm1JRWlaWW5iZnlBcXJtZUtGc2FFTHNlUlFZMi9iRm03cWF5IiwidWluIjoiMTM5NTczOTY2MCJ9&data=39UmAihyH_o6CZaOs7nk2mO_lz2ruODoDou6pxxh7utcxP4WF5sbDBDOPvZ_Wqfzeey4441anegsLYQJxkrBAA&svctype=4&tempid=h5_group_info) (1054700388) | [Discord Server](https://discord.gg/5yAQPnyCKv) | [Telegram Group](https://t.me/+svhSLZ7awPsxNGY1)
|
||||
|
||||
## 🌟 Highlighted features
|
||||
|
||||
- 🌠 **Pleasing User Interface**
|
||||
- Smooth and highly personalized style, animations and effects
|
||||
- Immersive fluid background
|
||||
- Perspective/fan-shaped lyrics
|
||||
- Snowflake effect
|
||||
- Multiple lyrics scrolling functions
|
||||
- ... (and more)
|
||||
- ↔️ **Strong Lyrics Translation**
|
||||
- Offline machine translation (supporting 30+ languages)
|
||||
- Auto-reading local lyrics files for embedded translation
|
||||
- 🧩 **Various Lyrics Source**
|
||||
- 💾 Local storage
|
||||
- Music files (with embedded lyrics)
|
||||
- [.lrc](<https://en.wikipedia.org/wiki/LRC_(file_format)>) files (with both core format and enhanced format)
|
||||
- [.eslrc](https://github.com/ESLyric/release) files
|
||||
- [.ttml](https://en.wikipedia.org/wiki/Timed_Text_Markup_Language) files
|
||||
- ☁️ Online lyrics providers
|
||||
- QQ 音乐
|
||||
- 网易云音乐
|
||||
- 酷狗音乐
|
||||
- [amll-ttml-db](https://github.com/Steve-xmh/amll-ttml-db)
|
||||
- [LRCLIB](https://lrclib.net/)
|
||||
- <details><summary>⚠️ Apple Music (additional config needed)</summary>
|
||||
|
||||
- Open the Apple Music web app and the Developer Tools window. Refresh the page. Return to the Developer Tools window, select Fetch/XHR, select a request, find the Media-User-Token header in the request header, and copy its value.
|
||||
- Open BetterLyrics and go to the Playback Source settings. Enter the copied value in the Media-User-Token (for Apple Music) setting and click the accept icon on the right-hand side.
|
||||
|
||||
- 🎶 **Multiple Music Players Supported**
|
||||
|
||||
- Check it out [here](https://github.com/jayfunc/BetterLyrics/wiki/User-Guide#known-supported-music-players-configuration-guide) for detailed info
|
||||
|
||||
- 🪟 **Multiple Display Modes**
|
||||
- **Standard Mode**
|
||||
- Enjoy an immersive listening journey with rich lyrics, animations and beautifully dynamic backgrounds
|
||||
- **Docked Mode**
|
||||
- A smart animated lyrics bar docked to your screen edge
|
||||
- **Desktop Mode**
|
||||
- Enjoy immersive lyrics floating above your apps
|
||||
- **And More...**
|
||||
- Waiting for you to discover...
|
||||
- 🧠 **Smart Behaviors**
|
||||
- Auto hide when music paused
|
||||
- Auto-hides when music pauses.
|
||||
|
||||
## 🖼️ Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
<div align="center">
|
||||
|
||||
> ⚠️ Due to GIF format and frame rate limitations, the displayed effect is for preview only. Please refer to the actual device for the actual effect.
|
||||
| Standard View | Narrow Mode |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/std.png" width="100%"> | <img src="Screenshots/narrow.png" width="100%"> |
|
||||
|
||||

|
||||

|
||||
| Lyrics Visual Effects | Coexisting Modes |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/effect.png" width="100%"> | <img src="Screenshots/all-in-one.png" width="100%"> |
|
||||
|
||||
| Fullscreen Mode | Fullscreen Mode |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/fs3.png" width="100%"> | <img src="Screenshots/fs2.png" width="100%"> |
|
||||
|
||||
| Music Gallery | Playback Statistics |
|
||||
| :---: | :---: |
|
||||
| <img src="Screenshots/music-gallery.png" width="100%"> | <img src="Screenshots/stats.png" width="100%"> |
|
||||
|
||||
</div>
|
||||
|
||||
## 📹 Demonstration
|
||||
|
||||
Watch our demo video (uploaded on 21 Oct 2025) on Bilibili [here](https://www.bilibili.com/video/BV1QRstz1EGt/).
|
||||
> Watch our demo video (uploaded on 21 Oct 2025) on Bilibili [here](https://www.bilibili.com/video/BV1QRstz1EGt/).
|
||||
|
||||
## 🧪 Try it now
|
||||
## ✍️ Contribute & Build
|
||||
|
||||
<a href="https://apps.microsoft.com/detail/9P1WCD1P597R?referrer=appbadge&mode=direct">
|
||||
<img src="https://get.microsoft.com/images/en-us%20dark.svg" width="200"/>
|
||||
</a>
|
||||
**Help us translate:** Cannot find your language? [Start translating here](https://github.com/jayfunc/BetterLyrics?tab=contributing-ov-file).
|
||||
|
||||
**Unlimited** free trail or purchase (there is **no difference** between free and paid version).
|
||||
|
||||
If you find it useful, please consider [donating](#-donations) or purchasing it in **Microsoft Store**, I'll appreciate it! 🥰
|
||||
|
||||
Having trouble downloading and installing from the MS Store? Try the following options:
|
||||
- [Download from outside Microsoft Store](https://jayfunc.blog/blog/download-from-outside-ms-store)
|
||||
- Go to [latest release](https://github.com/jayfunc/BetterLyrics/releases/latest) and download `.zip` file from `Assets`. (See [this doc](https://jayfunc.blog/blog/how-to-install-zip) for how to install it.)
|
||||
|
||||
## 🏗️ Build
|
||||
|
||||
Before you build, make sure that you have already:
|
||||
- Replaced `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\DiscordTemplate` with `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\DiscordTemplate.cs`.
|
||||
- Replaced `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFMTemplate` with `BetterLyrics\BetterLyrics.WinUI3\BetterLyrics.WinUI3\Constants\LastFM.cs`.
|
||||
**Build from source:**
|
||||
> Before building, ensure you have replaced `DiscordTemplate.cs` and `LastFM.cs` in the `Constants` folder.
|
||||
|
||||
## 🤑 Donations
|
||||
|
||||
If you like this project, please consider supporting it by donating. Your support will help keep the project alive and encourage further development.
|
||||
If you like BetterLyrics, please consider supporting it. Your support helps keep the project alive!
|
||||
|
||||
You can donate via:
|
||||
- [PayPal](https://paypal.me/zhefangpay)
|
||||
- [Buy Me a Coffee](https://buymeacoffee.com/founchoo)
|
||||
- [爱发电](https://afdian.com/a/jayfunc)
|
||||
- <details><summary>支付宝</summary>
|
||||
|
||||

|
||||
|
||||
</detais>
|
||||
<div align="center">
|
||||
|
||||
- <details><summary>微信</summary>
|
||||
|
||||

|
||||
|
||||
</details>
|
||||
| Web Platforms | Alipay (QR) | WeChat (QR) |
|
||||
| :---: | :---: | :---: |
|
||||
| [PayPal](https://paypal.me/zhefangpay)<br><br>[Buy Me a Coffee](https://buymeacoffee.com/founchoo)<br><br>[爱发电 (Afdian)](https://afdian.com/a/jayfunc) | <img src="Donate/Alipay.jpg" width="150"> | <img src="Donate/WeChatReward.png" width="150"> |
|
||||
|
||||
This project is made possible by the generous support of our users. **[View the full Hall of Fame](SPONSORS.md)**
|
||||
**[View the full Hall of Fame (Sponsors)](SPONSORS.md)**
|
||||
|
||||
## 📄 License
|
||||
</div>
|
||||
|
||||
This project is licensed under the GNU General Public License v3.0. See the [LICENSE](https://github.com/jayfunc/BetterLyrics/blob/dev/LICENSE) file for details.
|
||||
## ⭐ Star History
|
||||
|
||||
## 💖 Many thanks to
|
||||
<div align="center">
|
||||
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date" width="100%">
|
||||
</div>
|
||||
|
||||
Some functions and code are referenced or modified from public repositories, including but not limited to the following open source projects/packages, tutorials, etc., and we would like to express our gratitude to them here.
|
||||
## 📄 License & Credits
|
||||
|
||||
This project is licensed under the **GNU General Public License v3.0**.
|
||||
|
||||
<details>
|
||||
<summary><b>💖 Special Thanks, Credits & Inspiration</b></summary>
|
||||
<br>
|
||||
|
||||
**Dependencies & References:**
|
||||
|
||||
| Projects/Packages | Description |
|
||||
| :--- | :--- |
|
||||
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | Provide lyrics fetch, decryption, and parsing for QQ, Netease, and Kugou sources |
|
||||
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB lyrics API provider |
|
||||
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | Apple Music lyrics fetch using Python |
|
||||
| [Audio Tools Library (ATL) for .NET](https://github.com/Zeugma440/atldotnet) | Used for extracting pictures from music files |
|
||||
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | Provide easy ways to access the Win32 API regarding windowing |
|
||||
| [cutlet](https://github.com/polm/cutlet) | Provide the ability for converting Japanese to romaji |
|
||||
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | Provide many out-of-the-box features for building WinUI 3 applications |
|
||||
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | Dynamic fluid background implementation |
|
||||
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | Provide the ability for offline lyrics translation |
|
||||
| [lrclib](https://github.com/tranxuanthang/lrclib) | LRCLIB lyrics API provider |
|
||||
| [Lyricify-Lyrics-Helper](https://github.com/WXRIW/Lyricify-Lyrics-Helper) | Provide lyrics fetch, decryption, and parsing for QQ, Netease, and Kugou sources |
|
||||
| [Manzana-Apple-Music-Lyrics](https://github.com/dropcreations/Manzana-Apple-Music-Lyrics) | Apple Music lyrics fetch using Python |
|
||||
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | Audio visualization reference |
|
||||
| [TagLib#](https://github.com/mono/taglib-sharp) | Used for reading the original lyrics content |
|
||||
| [Vanara](https://github.com/dahall/Vanara) | Win32 API wrapper |
|
||||
| [LibreTranslate](https://github.com/LibreTranslate/LibreTranslate) | Provide the ability for offline lyrics translation |
|
||||
| [Isolation](https://github.com/Storyteller-Studios/Isolation) | Dynamic fluid background implementation |
|
||||
| [SpectrumVisualization](https://github.com/Johnwikix/SpectrumVisualization) | Audio visualization reference |
|
||||
| [DevWinUI](https://github.com/ghost1372/DevWinUI) | Provide many out-of-the-box features for building WinUI 3 applications |
|
||||
| ... | ... |
|
||||
| [WinUIEx](https://github.com/dotMorten/WinUIEx) | Provide easy ways to access the Win32 API regarding windowing |
|
||||
|
||||
See all the dependencies [here](https://github.com/jayfunc/BetterLyrics/network/dependencies).
|
||||
|
||||
### Tutorials/Blogs/etc.
|
||||
See [dependencies](https://github.com/jayfunc/BetterLyrics/network/dependencies) for full list.
|
||||
|
||||
- [Stackoverflow - How to animate Margin property in WPF](https://stackoverflow.com/a/21542882/11048731)
|
||||
- [Bilibili -【WinUI3】SystemBackdropController:定义云母、亚克力效果](https://www.bilibili.com/video/BV1PY4FevEkS)
|
||||
- [cnblogs - .NET App 与 Windows 系统媒体控制(SMTC)交互](https://www.cnblogs.com/TwilightLemon/p/18279496)
|
||||
- [Win2D 中的游戏循环:CanvasAnimatedControl](https://www.cnblogs.com/walterlv/p/10236395.html)
|
||||
- [r2d2rigo/Win2D-Samples](https://github.com/r2d2rigo/Win2D-Samples/blob/master/IrisBlurWin2D/IrisBlurWin2D/MainPage.xaml.cs)
|
||||
- [CommunityToolkit - 从入门到精通](https://mvvm.coldwind.top/)
|
||||
<br>
|
||||
|
||||
## 💡 Inspired by
|
||||
|
||||
Some design ideas are referenced from the following plugins/software (excluding code that is indirectly or directly referenced or modified, and is only used as a guide for design ideas).
|
||||
**💡 Inspired by:**
|
||||
|
||||
Some design ideas are referenced from the following projects (design inspiration only):
|
||||
- [refined-now-playing-netease](https://github.com/solstice23/refined-now-playing-netease)
|
||||
- [Lyricify-App](https://github.com/WXRIW/Lyricify-App)
|
||||
- [椒盐音乐 Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [Salt Player](https://moriafly.com/program/salt-player)
|
||||
- [MyToolBar](https://github.com/TwilightLemon/MyToolBar)
|
||||
|
||||
## ⭐ Star history
|
||||
</details>
|
||||
|
||||
<div style="display: flex; justify-content: space-around; align-items: flex-start;">
|
||||
<img src="https://api.star-history.com/svg?repos=jayfunc/BetterLyrics&type=Date)](https://www.star-history.com/#jayfunc/BetterLyrics&Date" width="100%" >
|
||||
## 💭 Share on Social Media
|
||||
|
||||
<details>
|
||||
<summary><b>Click to expand</b></summary>
|
||||
<br>
|
||||
<div align="center">
|
||||
<img src="https://socialify.git.ci/jayfunc/BetterLyrics/image?description=1&forks=1&issues=1&language=1&name=1&owner=1&pulls=1&stargazers=1&theme=Light" width="48%">
|
||||
<img src="https://opengraph.githubassets.com/<any_hash_number>/jayfunc/BetterLyrics" width="48%">
|
||||
</div>
|
||||
</details>
|
||||
|
||||
## 🤗 Any issues and PRs are welcome
|
||||
<br>
|
||||
|
||||
If you find a bug, please file it in issues, or if you have any ideas, feel free to share them here.
|
||||
|
||||
## ⚠️ Disclaimer
|
||||
|
||||
This project is provided "as is" without warranty of any kind.
|
||||
|
||||
All lyrics, fonts, icons, and other third-party resources are the property of their respective copyright holders.
|
||||
The author of this project does not claim ownership of such resources.
|
||||
|
||||
This project is non-commercial and should not be used to infringe any rights.
|
||||
Users are responsible for ensuring their own use complies with applicable laws and licenses.
|
||||
|
||||
## 💭 Share it on social media
|
||||
|
||||

|
||||
|
||||

|
||||
<div align="center">
|
||||
<mark><i>This project is under active development; unexpected issues may occur.</i></mark><br>
|
||||
<sub>Disclaimer: This project is provided "as is". All third-party resources belong to their respective owners.</sub>
|
||||
</div>
|
||||
|
||||
|
Before Width: | Height: | Size: 37 MiB |
|
Before Width: | Height: | Size: 15 MiB |
|
Before Width: | Height: | Size: 620 KiB |
|
Before Width: | Height: | Size: 629 KiB |
BIN
Screenshots/all-in-one.png
Normal file
|
After Width: | Height: | Size: 895 KiB |
BIN
Screenshots/effect.png
Normal file
|
After Width: | Height: | Size: 1.0 MiB |
BIN
Screenshots/fs3.png
Normal file
|
After Width: | Height: | Size: 506 KiB |
BIN
Screenshots/music-gallery.png
Normal file
|
After Width: | Height: | Size: 507 KiB |
BIN
Screenshots/stats.png
Normal file
|
After Width: | Height: | Size: 173 KiB |