Compare commits

...

74 Commits

Author SHA1 Message Date
Zhe Fang
a13bb6e8e4 chores: bump to 1.2.243.0 2026-01-04 16:43:02 -05:00
Zhe Fang
0b436c1ea9 chores: rollback UpdateLyrics 2026-01-04 16:21:33 -05:00
Zhe Fang
5d332fdfc6 fix: media sessions record issue 2026-01-04 16:02:11 -05:00
Zhe Fang
572d2cd8ba fix 2026-01-04 14:54:15 -05:00
Zhe Fang
1e5a95c55e chores: bump to v1.2.240.0 2026-01-04 12:05:53 -05:00
Zhe Fang
18ce6d3a57 chores: improve thanks list 2026-01-04 11:39:47 -05:00
Zhe Fang
427aed6857 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-04 09:51:53 -05:00
Zhe Fang
ebfa484a2e fix: GSMTC 2026-01-04 09:51:51 -05:00
Zhe Fang
3ef9d81bea Update README.CN.md 2026-01-04 06:45:25 -05:00
Zhe Fang
e999d07834 Update README.md 2026-01-04 06:43:52 -05:00
Zhe Fang
838b8de94f Update README.md 2026-01-04 06:43:20 -05:00
Zhe Fang
b3059dbeb1 chores: improve GSMTC service 2026-01-03 22:02:08 -05:00
Zhe Fang
6fea88a6a1 fix: stats dashboard ui 2026-01-03 17:27:47 -05:00
Zhe Fang
abca9ae5fb fix: auto-play in music gallery wont show song title and artist when first play 2026-01-03 12:50:33 -05:00
Zhe Fang
a062897e1a fix: music gallery play issue (and improve ui/ux) 2026-01-03 12:14:54 -05:00
Zhe Fang
8b4748df1b fix: stats dashboard data selection issue 2026-01-02 12:05:10 -05:00
Zhe Fang
1df5ea6bab fix: playback realtime info encoded cache path 2026-01-02 08:27:03 -05:00
Zhe Fang
c576635af2 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2026-01-01 21:49:33 -05:00
Zhe Fang
c8590202ec chores: improve music stats 2026-01-01 21:49:31 -05:00
Zhe Fang
2dc8b1283f Update README.CN.md 2026-01-01 12:38:31 -05:00
Zhe Fang
c482edea0f Update README.md 2026-01-01 12:36:39 -05:00
Zhe Fang
315722252c Update README.md 2026-01-01 12:32:01 -05:00
Zhe Fang
32ba453264 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 16:30:16 -05:00
Zhe Fang
d4902329bb chores: update readme 2025-12-31 16:30:14 -05:00
Zhe Fang
83aee8948b Update README.CN.md 2025-12-31 16:13:48 -05:00
Zhe Fang
1f9fab3228 Update README.CN.md 2025-12-31 16:10:42 -05:00
Zhe Fang
7a3a659dfc Update README.CN.md 2025-12-31 16:09:17 -05:00
Zhe Fang
a14afd3eb5 Update README.md 2025-12-31 16:01:37 -05:00
Zhe Fang
c2af7f3186 Update README.CN.md 2025-12-31 16:00:16 -05:00
Zhe Fang
cd026dd2bf Update README.CN.md 2025-12-31 15:56:08 -05:00
Zhe Fang
4bc1a9975d Update README.md 2025-12-31 15:52:05 -05:00
Zhe Fang
07eecf0930 Update README.md 2025-12-31 15:49:22 -05:00
Zhe Fang
35fba5abb0 chores 2025-12-31 15:44:58 -05:00
Zhe Fang
03ef231a3f chores 2025-12-31 15:39:44 -05:00
Zhe Fang
f41879f4e5 chores: add pic 2025-12-31 15:38:18 -05:00
Zhe Fang
bda7510ed6 chores: update readme 2025-12-31 14:28:54 -05:00
Zhe Fang
5ec8c7c61f chores: bump to 1.2.236 2025-12-31 13:32:45 -05:00
Zhe Fang
7e6bd9dade fix: _scrobbleStopwatch 2025-12-31 10:55:56 -05:00
Zhe Fang
56244cb793 fix: scrollable timer 2025-12-31 10:47:43 -05:00
Zhe Fang
cb5f70ab55 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 10:12:29 -05:00
Zhe Fang
3cc018bb1f chores: bump to 1.2.234.0 2025-12-31 10:12:28 -05:00
Zhe Fang
c517d2b008 Update README.md 2025-12-31 10:08:58 -05:00
Zhe Fang
e79f2a0223 Update README.CN.md 2025-12-31 10:07:53 -05:00
Zhe Fang
39122b9147 Update README.CN.md 2025-12-31 10:07:01 -05:00
Zhe Fang
accbdc1806 Update README.md 2025-12-31 10:06:01 -05:00
Zhe Fang
de014d1ad7 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 09:52:12 -05:00
Zhe Fang
cc2ce5f8cf chores: i18n 2025-12-31 09:52:10 -05:00
Zhe Fang
2a2d80436e Update README.CN.md 2025-12-31 09:34:29 -05:00
Zhe Fang
ce3f79f35c Update README.md 2025-12-31 09:33:01 -05:00
Zhe Fang
12e6000cb3 Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 08:43:44 -05:00
Zhe Fang
c1dc684411 fix: scrobble timer is still running when music is paused 2025-12-31 08:43:43 -05:00
Zhe Fang
69ea2cb495 Update README.md 2025-12-31 08:21:33 -05:00
Zhe Fang
e2ee03c4be Update README.CN.md 2025-12-31 08:21:03 -05:00
Zhe Fang
c6fe33d6ae Merge branch 'dev' of https://github.com/jayfunc/BetterLyrics into dev 2025-12-31 08:12:12 -05:00
Zhe Fang
7744e145fa chores: i18n 2025-12-31 08:12:10 -05:00
Zhe Fang
0284b1de81 Update README.CN.md 2025-12-30 21:46:32 -05:00
Zhe Fang
108c2cd34b Update README.md 2025-12-30 21:45:56 -05:00
Zhe Fang
390e30f7f5 chores: bump to v1.2.232.0 2025-12-30 20:54:40 -05:00
Zhe Fang
900774668d chores: i18n 2025-12-30 20:09:02 -05:00
Zhe Fang
6ca2d1f897 chores 2025-12-30 19:49:54 -05:00
Zhe Fang
164bd077b8 fix: app crash when audio device was not found 2025-12-30 14:30:10 -05:00
Zhe Fang
8ec71fcfb7 chores: update StatsDashboardControl 2025-12-30 14:04:08 -05:00
Zhe Fang
f39ad54df8 fix: record play history 2025-12-30 13:02:23 -05:00
Zhe Fang
9b809983df chores: i18n 2025-12-30 08:59:30 -05:00
Zhe Fang
8006b3a443 chores: i18n 2025-12-30 08:41:12 -05:00
Zhe Fang
26a7454de2 feat: play history 2025-12-30 08:02:48 -05:00
Zhe Fang
0793a074cf fix: add to playlists 2025-12-29 16:11:21 -05:00
Zhe Fang
125bf1682e chores 2025-12-29 12:15:39 -05:00
Zhe Fang
48bdffb2fe Merge pull request #179 from jayfunc/l10n_dev
New Crowdin updates
2025-12-28 21:37:57 -05:00
Zhe Fang
d324a7552f New translations resources.resw (Chinese Traditional) 2025-12-28 21:37:17 -05:00
Zhe Fang
78c308c393 New translations resources.resw (Japanese) 2025-12-28 21:37:15 -05:00
Zhe Fang
a1bba00db6 chores: i18n 2025-12-28 21:31:37 -05:00
Zhe Fang
0787f5b111 fix 2025-12-28 21:10:02 -05:00
Zhe Fang
884026594b fix: ignore system, hidden files and unsupported format files 2025-12-28 21:03:56 -05:00
119 changed files with 6545 additions and 3291 deletions

View File

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

View File

@@ -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 -->

View File

@@ -1,51 +1,56 @@
// 2025/6/23 by Zhe Fang
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models.Settings;
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;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using Microsoft.Data.Sqlite;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.UI.Xaml;
using Microsoft.Windows.ApplicationModel.Resources;
using Microsoft.Windows.Globalization;
using Microsoft.Windows.AppLifecycle; // 关键App生命周期管理
using Serilog;
using System;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Vanara.PInvoke;
namespace BetterLyrics.WinUI3
{
public partial class App : Application
{
private Window? m_window;
private readonly ILogger<App> _logger;
public static new App Current => (App)Application.Current;
private static Mutex? _instanceMutex;
private readonly string _appKey = Windows.ApplicationModel.Package.Current.Id.FamilyName;
public App()
{
this.InitializeComponent();
// Must be done before InitializeComponent
if (!TryHandleSingleInstance())
{
// 如果移交成功直接退出当前进程
Environment.Exit(0);
return;
}
EnsureSingleInstance();
this.InitializeComponent();
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
PathHelper.EnsureDirectories();
@@ -53,32 +58,94 @@ namespace BetterLyrics.WinUI3
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
// 注册全局异常捕获
UnhandledException += App_UnhandledException;
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
}
private void EnsureSingleInstance()
/// <summary>
/// 处理单实例逻辑。
/// 返回 true 表示我是主实例,继续运行。
/// 返回 false 表示我是第二个实例,已通知主实例,我应该退出。
/// </summary>
private bool TryHandleSingleInstance()
{
_instanceMutex = new Mutex(true, Constants.App.AppName, out bool createdNew);
// 尝试查找或注册当前实例
var mainInstance = AppInstance.FindOrRegisterForKey(_appKey);
if (!createdNew)
// 如果当前实例就是注册的那个主实例
if (mainInstance.IsCurrent)
{
User32.MessageBox(HWND.NULL, new ResourceLoader().GetString("TryRunMultipleInstance"), null, User32.MB_FLAGS.MB_APPLMODAL);
Environment.Exit(0);
// 监听 "Activated" 事件。
// 当第二个实例启动并重定向过来时,这个事件会被触发。
mainInstance.Activated += OnMainInstanceActivated;
return true;
}
else
{
// 我不是主实例,我是后来者。
// 获取当前实例的激活参数(比如是通过文件双击打开的,这里能拿到文件路径)
var args = AppInstance.GetCurrent().GetActivatedEventArgs();
// 将激活请求重定向给主实例
// 注意:这里是同步等待,确保发送成功后再退出
try
{
mainInstance.RedirectActivationToAsync(args).AsTask().Wait();
}
catch (Exception)
{
// 即使重定向失败,作为第二个实例也应该退出
}
return false;
}
}
protected override void OnLaunched(LaunchActivatedEventArgs args)
/// <summary>
/// 当第二个实例试图启动时,主实例会收到此回调
/// </summary>
private void OnMainInstanceActivated(object? sender, AppActivationArguments e)
{
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
// 这个事件是在后台线程触发的,必须切回 UI 线程操作窗口
m_window?.DispatcherQueue.TryEnqueue(() =>
{
HandleActivation();
});
}
/// <summary>
/// 唤醒逻辑
/// </summary>
private void HandleActivation()
{
WindowHook.OpenOrShowWindow<LyricsWindowSwitchWindow>();
}
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
// 初始化数据库
await EnsureDatabasesAsync();
var settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
var fileSystemService = Ioc.Default.GetRequiredService<IFileSystemService>();
// 开始后台扫描任务
foreach (var item in settingsService.AppSettings.LocalMediaFolders)
{
if (item.LastSyncTime == null)
{
_ = Task.Run(async () => await fileSystemService.ScanMediaFolderAsync(item, CancellationToken.None));
}
}
fileSystemService.StartAllFolderTimers();
WindowHook.OpenOrShowWindow<SystemTrayWindow>();
// 初始化托盘
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
// 根据设置打开歌词窗口
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
{
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
@@ -94,30 +161,126 @@ namespace BetterLyrics.WinUI3
}
}
}
// 根据设置自动打开主界面
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
{
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
}
}
private async Task EnsureDatabasesAsync()
{
var playHistoryFactory = Ioc.Default.GetRequiredService<IDbContextFactory<PlayHistoryDbContext>>();
var fileCacheFactory = Ioc.Default.GetRequiredService<IDbContextFactory<FilesIndexDbContext>>();
await SafeInitDatabaseAsync(
"PlayHistory",
PathHelper.PlayHistoryPath,
async () =>
{
using var db = await playHistoryFactory.CreateDbContextAsync();
await db.Database.EnsureCreatedAsync();
},
isCritical: true
);
await SafeInitDatabaseAsync(
"FileCache",
PathHelper.FilesIndexPath,
async () =>
{
using var db = await fileCacheFactory.CreateDbContextAsync();
await db.Database.EnsureCreatedAsync();
},
isCritical: false
);
}
private async Task SafeInitDatabaseAsync(string dbName, string dbPath, Func<Task> initAction, bool isCritical)
{
try
{
await initAction();
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"[DB Error] {dbName} init failed: {ex.Message}");
try
{
if (File.Exists(dbPath))
{
// 尝试清理连接池
SqliteConnection.ClearAllPools();
if (isCritical)
{
var backupPath = dbPath + ".bak_" + DateTime.Now.ToString("yyyyMMddHHmmss");
File.Move(dbPath, backupPath, true);
await ShowErrorDialogAsync("Database Recovery", $"Database {dbName} is damaged, the old database has been backed up to {backupPath}, and the program will create a new database.");
}
else
{
File.Delete(dbPath);
}
}
await initAction();
System.Diagnostics.Debug.WriteLine($"[DB Info] {dbName} recovered successfully.");
}
catch (Exception fatalEx)
{
System.Diagnostics.Debug.WriteLine($"[] : {fatalEx.Message}");
await ShowErrorDialogAsync("Fatal Error", $"{dbName} recovery failed, please delete the file at {dbPath} and try again by restarting the program. ({fatalEx.Message})");
}
}
}
private async Task ShowErrorDialogAsync(string title, string content)
{
// 这里假设 m_window 已经存在。如果没有显示主窗口,这个弹窗可能无法显示。
// 在 App 启动极早期的错误,可能需要退化为 Log 或者 System.Diagnostics.Process.Start 打开记事本报错
if (m_window != null)
{
m_window.DispatcherQueue.TryEnqueue(async () =>
{
var dialog = new Microsoft.UI.Xaml.Controls.ContentDialog
{
Title = title,
Content = content,
CloseButtonText = "OK",
XamlRoot = m_window.Content?.XamlRoot // 确保 Content 不为空
};
if (dialog.XamlRoot != null) await dialog.ShowAsync();
});
}
}
private static void ConfigureServices()
{
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();
// Register services
Ioc.Default.ConfigureServices(
new ServiceCollection()
// 数据库工厂
.AddDbContextFactory<PlayHistoryDbContext>(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}"))
.AddDbContextFactory<FilesIndexDbContext>(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}"))
// 日志
.AddLogging(loggingBuilder =>
{
loggingBuilder.ClearProviders();
loggingBuilder.AddSerilog();
})
// Services
.AddSingleton<ISettingsService, SettingsService>()
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
.AddSingleton<ISMTCService, SMTCService>()
.AddSingleton<IGSMTCService, GSMTCService>()
.AddSingleton<IAlbumArtSearchService, AlbumArtSearchService>()
.AddSingleton<ILyricsSearchService, LyricsSearchService>()
.AddSingleton<ITranslationService, TranslationService>()
@@ -126,6 +289,8 @@ namespace BetterLyrics.WinUI3
.AddSingleton<IDiscordService, DiscordService>()
.AddSingleton<ILocalizationService, LocalizationService>()
.AddSingleton<IFileSystemService, FileSystemService>()
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
// ViewModels
.AddSingleton<AppSettingsControlViewModel>()
.AddSingleton<PlaybackSettingsControlViewModel>()
@@ -140,6 +305,8 @@ namespace BetterLyrics.WinUI3
.AddSingleton<MusicGalleryPageViewModel>()
.AddSingleton<AboutControlViewModel>()
.AddSingleton<MusicGalleryWindowViewModel>()
.AddSingleton<StatsDashboardControlViewModel>()
.AddSingleton<PlayQueueViewModel>()
.AddTransient<NowPlayingWindowViewModel>()
.AddTransient<NowPlayingPageViewModel>()
@@ -157,7 +324,8 @@ namespace BetterLyrics.WinUI3
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
{
_logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
// FirstChance 异常非常多(比如内部 try-catch 也会触发),通常建议只在 Debug 模式记录,或者过滤特定类型
// _logger.LogError(e.Exception, "CurrentDomain_FirstChanceException");
}
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
@@ -170,4 +338,4 @@ namespace BetterLyrics.WinUI3
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
}
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 593 KiB

View File

@@ -42,12 +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" />
@@ -69,6 +74,7 @@
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Segmented" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Controls.Sizers" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Converters" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.251219" />
@@ -84,7 +90,12 @@
<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.EntityFrameworkCore" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="10.0.1" />
<PackageReference Include="Microsoft.Extensions.Logging" Version="10.0.1" />
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
@@ -95,7 +106,6 @@
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
<PackageReference Include="SMBLibrary" Version="1.5.5.1" />
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
<PackageReference Include="System.Drawing.Common" Version="10.0.1" />
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
<PackageReference Include="TagLibSharp" Version="2.3.0" />
@@ -128,6 +138,10 @@
<ItemGroup>
<TrimmerRootAssembly Include="FlaUI.UIA3" />
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Abstractions" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Relational" />
<TrimmerRootAssembly Include="Microsoft.EntityFrameworkCore.Sqlite" />
<TrimmerRootAssembly Include="NAudio.Wasapi" />
<TrimmerRootAssembly Include="TagLibSharp" />
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
@@ -215,6 +229,9 @@
<Content Update="Assets\NetEaseCloudMusic.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\OriginalSoundHQPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\Page.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
@@ -230,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>
@@ -246,6 +266,31 @@
<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>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
<Generator>MSBuild:Compile</Generator>
@@ -391,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>

View File

@@ -1,6 +1,6 @@
namespace BetterLyrics.WinUI3.Constants
{
public static class PlayerID
public static class PlayerId
{
public const string LXMusic = "cn.toside.music.desktop";
public const string LXMusicPortable = "lx-music-desktop.exe";
@@ -25,5 +25,6 @@
public const string MoeKoeMusic = "cn.MoeKoe.Music";
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
public const string Listen1 = "com.listen1.listen1";
public const string OriginalSoundHQPlayer = "SennpaiStudio.528762A6196EF_z79ft30j24epr!App";
}
}

View File

@@ -24,5 +24,6 @@
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows (Steam)";
public const string MoeKoeMusic = "MoeKoe Music";
public const string Listen1 = "Listen 1";
public const string OriginalSoundHQPlayer = "Original Sound HQ Player";
}
}

View File

@@ -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);
}
}

View File

@@ -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,26 +117,25 @@
</HyperlinkButton.ContextFlyout>
</HyperlinkButton>
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
</StackPanel>
<StackPanel Orientation="Horizontal" Spacing="6">
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="*" />
</dev:WrapPanel>
<Grid ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
Text="*" />
<TextBlock
x:Uid="SetingsPageThanks"
Grid.Column="1"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
TextWrapping="Wrap" />
</StackPanel>
</Grid>
</StackPanel>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageThanksList">
<Button
Click="Patron_Click"
Content="{ui:FontIcon FontSize=16,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE7FD;}"
Style="{StaticResource AccentButtonStyle}" />
</dev:SettingsCard>
</dev:SettingsExpander.Items>
<dev:SettingsExpander.ItemsFooter>
<InfoBar
@@ -149,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>
@@ -182,6 +271,12 @@
</dev:SettingsExpander.ItemsHeader>
</dev:SettingsExpander>
<dev:SettingsCard x:Uid="SettingsPageSettingsPlayHistory" Visibility="Collapsed">
<StackPanel Orientation="Horizontal" Spacing="6">
<Button x:Uid="SettingsPageExportPlayHistoryButton" Command="{x:Bind ViewModel.ExportPlayHistoryCommand}" />
</StackPanel>
</dev:SettingsCard>
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
</dev:SettingsCard>
@@ -195,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 &amp; 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>

View File

@@ -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);

View File

@@ -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,8 +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 ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
private readonly IGSMTCService _gsmtcService = Ioc.Default.GetRequiredService<IGSMTCService>();
private readonly LyricsRenderer _lyricsRenderer = new();
private readonly FluidBackgroundRenderer _fluidRenderer = new();
@@ -99,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;
@@ -346,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;
@@ -460,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;
@@ -655,41 +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);
CheckAndScrobbleLastFM();
}
}
private void CheckAndScrobbleLastFM()
{
bool isEnabled = _mediaSessionsService.CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
if (!isEnabled || _isLastFMTracked) return;
var songInfo = _mediaSessionsService.CurrentSongInfo;
if (songInfo == null || songInfo.Duration <= 0) return;
if (_totalPlayedTime.TotalSeconds >= songInfo.Duration * 0.5)
{
_isLastFMTracked = true;
_lastFMService.TrackAsync(songInfo);
_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,
@@ -702,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);
@@ -712,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>
@@ -745,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;
@@ -755,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();
}
@@ -912,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();
}
}
}
}
}

View File

@@ -5,6 +5,7 @@
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:dev="using:DevWinUI"
xmlns:enums="using:BetterLyrics.WinUI3.Enums"
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
@@ -88,14 +89,18 @@
</dev:SettingsCard>
</dev:SettingsExpander.Items>
<dev:SettingsExpander.ItemsFooter>
<dev:SettingsExpander.ItemsHeader>
<StackPanel>
<!-- Index info -->
<InfoBar
IsClosable="False"
IsOpen="{x:Bind IsIndexing, Mode=OneWay}"
Message="{x:Bind IndexingStatusText, Mode=OneWay}" />
<ProgressBar Visibility="{x:Bind IsIndexing, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" Value="{x:Bind IndexingProgress, Mode=OneWay}">
IsOpen="True"
Message="{x:Bind StatusText, Mode=OneWay}"
Severity="{x:Bind StatusSeverity, Mode=OneWay}" />
<ProgressBar
Background="Transparent"
Visibility="{x:Bind IsProcessing, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}"
Value="{x:Bind IndexingProgress, Mode=OneWay}">
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind IndexingProgress, Mode=OneWay}"
@@ -111,21 +116,22 @@
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</ProgressBar>
<!-- Clean up info -->
<InfoBar
IsClosable="False"
IsOpen="{x:Bind IsCleaningUp, Mode=OneWay}"
Message="{x:Bind CleaningUpStatusText, Mode=OneWay}" />
<ProgressBar IsIndeterminate="True" Visibility="{x:Bind IsCleaningUp, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
</StackPanel>
</dev:SettingsExpander.ItemsFooter>
</dev:SettingsExpander.ItemsHeader>
</dev:SettingsExpander>
</DataTemplate>
</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>
@@ -170,7 +176,7 @@
</MenuFlyout>
</DropDownButton.Flyout>
</DropDownButton>
</dev:SettingsCard>
</StackPanel>
</StackPanel>
</Grid>

View File

@@ -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"
@@ -11,7 +12,6 @@
mc:Ignorable="d">
<Grid x:Name="RootGrid">
<Grid
x:Name="BottomCommandGrid"
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
@@ -60,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>
@@ -113,7 +113,58 @@
x:Name="BottomCenterCommandStackPanel"
Padding="16"
Orientation="Horizontal"
Spacing="3">
Spacing="12">
<!-- Playback order -->
<Button
Grid.Column="2"
Click="PlaybackOrderButton_Click"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ShowPlaybackOrderButton, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<ToolTipService.ToolTip>
<ToolTip>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip>
</ToolTipService.ToolTip>
<Button.Content>
<Grid>
<!-- Repeat all -->
<FontIcon
x:Name="PlaybackRepeatAll"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8EE;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Repeat one -->
<FontIcon
x:Name="PlaybackRepeatOne"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8ED;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Shuffle -->
<FontIcon
x:Name="PlaybackShuffle"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8B1;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
</Button.Content>
</Button>
<!-- 上一曲目 -->
<Button
Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
@@ -126,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" />
@@ -146,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" />
@@ -164,6 +215,17 @@
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE623;}"
Style="{StaticResource GhostButtonStyle}" />
<!-- 播放队列按钮 -->
<Button
Click="PlayingQueueButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
Glyph=&#xE8FD;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPagePlayingQueue" />
</ToolTipService.ToolTip>
</Button>
</StackPanel>
</Grid>
@@ -174,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=&#xE71A;}"
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>
@@ -345,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"
@@ -427,7 +503,58 @@
</Grid.ContextFlyout>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlaybackOrderState">
<VisualState x:Name="RepeatAll">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="1" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Visible" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="RepeatOne">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="1" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="1" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Visible" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Shuffle">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="2" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="1" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</UserControl>

View File

@@ -1,6 +1,8 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.GSMTCService;
using BetterLyrics.WinUI3.ViewModels;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
@@ -12,21 +14,20 @@ using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media.Imaging;
using System;
using System.Numerics;
using BetterLyrics.WinUI3.Extensions;
// 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 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
{
@@ -46,6 +47,42 @@ public sealed partial class NowPlayingBar : UserControl,
public static readonly DependencyProperty ShowSongInfoProperty =
DependencyProperty.Register(nameof(ShowSongInfo), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowPlayingQueueButton
{
get { return (bool)GetValue(ShowPlayingQueueButtonProperty); }
set { SetValue(ShowPlayingQueueButtonProperty, value); }
}
public static readonly DependencyProperty ShowPlayingQueueButtonProperty =
DependencyProperty.Register(nameof(ShowPlayingQueueButton), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
public bool ShowPlaybackOrderButton
{
get { return (bool)GetValue(ShowPlaybackOrderButtonProperty); }
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));
public PlaybackOrder PlaybackOrder
{
get { return (PlaybackOrder)GetValue(PlaybackOrderProperty); }
set { SetValue(PlaybackOrderProperty, value); }
}
public static readonly DependencyProperty PlaybackOrderProperty =
DependencyProperty.Register(nameof(PlaybackOrder), typeof(PlaybackOrder), typeof(NowPlayingBar), new PropertyMetadata(PlaybackOrder.RepeatAll));
public bool IsCompactMode
{
get { return (bool)GetValue(IsCompactModeProperty); }
@@ -70,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)
@@ -167,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)
@@ -210,12 +245,12 @@ public sealed partial class NowPlayingBar : UserControl,
private void SongInfoStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
SongInfoTapped?.Invoke(this, EventArgs.Empty);
SongInfoTapped?.Invoke(sender, EventArgs.Empty);
}
private void TimeStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
{
TimeTapped?.Invoke(this, EventArgs.Empty);
TimeTapped?.Invoke(sender, EventArgs.Empty);
}
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
@@ -262,37 +297,13 @@ public sealed partial class NowPlayingBar : UserControl,
}
}
public void Receive(PropertyChangedMessage<SongInfo?> message)
private void PlayingQueueButton_Click(object sender, RoutedEventArgs e)
{
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;
}
}
PlayQueueButtonClick?.Invoke(sender, EventArgs.Empty);
}
public void Receive(PropertyChangedMessage<TimeSpan> message)
private void PlaybackOrderButton_Click(object sender, RoutedEventArgs e)
{
if (message.Sender is IMediaSessionsService)
{
if (message.PropertyName == nameof(IMediaSessionsService.CurrentPosition))
{
TimelineSlider.Value = message.NewValue.TotalSeconds;
}
}
PlaybackOrder = PlaybackOrder.GetNext();
}
}

View File

@@ -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>

View File

@@ -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();
}
}
}

View 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=&#xE7B7;}"
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=&#xE738;}"
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=&#xE738;}"
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>

View File

@@ -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();
}
}
}
}
}

View File

@@ -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>

View File

@@ -12,13 +12,13 @@ namespace BetterLyrics.WinUI3.Controls
{
public sealed partial class RemoteServerConfigControl : UserControl
{
private readonly string _protocolType;
private readonly FileSourceType _fileSourceType;
private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
public RemoteServerConfigControl(string protocolType)
public RemoteServerConfigControl(FileSourceType fileSourceType)
{
this.InitializeComponent();
_protocolType = protocolType;
_fileSourceType = fileSourceType;
SetupDefaults();
CheckPathForWarning();
@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Controls
private void SetupDefaults()
{
if (_protocolType.Equals("Local", StringComparison.OrdinalIgnoreCase))
if (_fileSourceType == FileSourceType.Local)
{
RemoteFieldsPanel.Visibility = Visibility.Collapsed;
AuthFieldsPanel.Visibility = Visibility.Collapsed;
@@ -41,17 +41,17 @@ namespace BetterLyrics.WinUI3.Controls
RemoteFieldsPanel.Visibility = Visibility.Visible;
AuthFieldsPanel.Visibility = Visibility.Visible;
switch (_protocolType.ToUpper())
switch (_fileSourceType)
{
case "SMB":
case FileSourceType.SMB:
PortBox.Value = 445;
PathBox.PlaceholderText = "SharedMusic";
break;
case "FTP":
case FileSourceType.FTP:
PortBox.Value = 21;
PathBox.PlaceholderText = "/pub/music";
break;
case "WEBDAV":
case FileSourceType.WebDAV:
PortBox.Value = 80;
PathBox.PlaceholderText = "/dav/music";
break;
@@ -62,15 +62,15 @@ namespace BetterLyrics.WinUI3.Controls
private string GetScheme()
{
string scheme = string.Empty;
switch (_protocolType.ToUpper())
switch (_fileSourceType)
{
case "SMB":
case FileSourceType.SMB:
scheme = "smb";
break;
case "FTP":
case FileSourceType.FTP:
scheme = "ftp";
break;
case "WEBDAV":
case FileSourceType.WebDAV:
scheme = "https";
break;
}
@@ -81,7 +81,7 @@ namespace BetterLyrics.WinUI3.Controls
{
string finalName = HostBox.Text.Trim();
if (_protocolType.Equals("Local", StringComparison.OrdinalIgnoreCase))
if (_fileSourceType == FileSourceType.Local)
{
if (string.IsNullOrWhiteSpace(PathBox.Text))
throw new ArgumentException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathRequired"));
@@ -109,17 +109,15 @@ namespace BetterLyrics.WinUI3.Controls
}
else
{
finalName = $"{_protocolType} - {HostBox.Text}";
finalName = $"{_fileSourceType} - {HostBox.Text}";
}
Enum.TryParse(_protocolType, true, out FileSourceType sourceType);
string scheme = GetScheme();
var folder = new MediaFolder
{
Name = finalName,
SourceType = sourceType,
SourceType = _fileSourceType,
UriScheme = scheme,
UriHost = HostBox.Text.Trim(), // ȥ<><C8A5><EFBFBD><EFBFBD>β<EFBFBD>ո<EFBFBD>
@@ -144,12 +142,7 @@ namespace BetterLyrics.WinUI3.Controls
{
ProgressBar.Visibility = visibility;
}
private void PathBox_TextChanged(object sender, TextChangedEventArgs e)
{
CheckPathForWarning();
}
private void CheckPathForWarning()
{
string? path = PathBox.Text?.Trim();
@@ -178,6 +171,11 @@ namespace BetterLyrics.WinUI3.Controls
}
}
private void PathBox_TextChanged(object sender, TextChangedEventArgs e)
{
CheckPathForWarning();
}
private async void BrowseButton_Click(object sender, RoutedEventArgs e)
{
try

View File

@@ -0,0 +1,410 @@
<?xml version="1.0" encoding="utf-8" ?>
<UserControl
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"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
<UserControl.Resources>
<Style x:Key="StatsCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}" />
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}" />
<Setter Property="BorderThickness" Value="1" />
<Setter Property="CornerRadius" Value="8" />
<Setter Property="Padding" Value="16" />
<Setter Property="Margin" Value="0,0,12,12" />
</Style>
</UserControl.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<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>
<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=&#xE72C;}" />
</controls:WrapPanel>
<ScrollViewer Grid.Row="3" Padding="36,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<Grid Grid.Row="0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE916;" />
<TextBlock x:Uid="StatsDashboardControlTotalDuration" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<StackPanel
Margin="0,8,0,0"
Orientation="Horizontal"
Spacing="4">
<TextBlock
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TotalDuration.TotalHours, Mode=OneWay, Converter={StaticResource DoubleToDecimalConverter}}" />
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Bottom"
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
Opacity="0.8"
Style="{ThemeResource CaptionTextBlockStyle}"
Text="Hrs" />
</StackPanel>
</StackPanel>
</Border>
<Border Grid.Column="1" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xE8D6;" />
<TextBlock x:Uid="StatsDashboardControlTracksPlayed" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<TextBlock
Margin="0,8,0,0"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TotalTracksPlayed, Mode=OneWay}" />
</StackPanel>
</Border>
<Border
Grid.Column="2"
Margin="0,0,0,12"
Style="{StaticResource StatsCardStyle}">
<StackPanel>
<StackPanel
Opacity="0.8"
Orientation="Horizontal"
Spacing="8">
<FontIcon FontSize="14" Glyph="&#xEC4A;" />
<TextBlock x:Uid="StatsDashboardControlTopSource" Style="{ThemeResource CaptionTextBlockStyle}" />
</StackPanel>
<TextBlock
Margin="0,8,0,0"
Style="{ThemeResource SubtitleTextBlockStyle}"
Text="{x:Bind ViewModel.TopPlayerName, Mode=OneWay}" />
</StackPanel>
</Border>
</Grid>
<!-- 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="*" />
<ColumnDefinition Width="*" />
</Grid.ColumnDefinitions>
<!-- Top artists -->
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
<StackPanel>
<TextBlock
x:Uid="StatsDashboardControlTopArtists"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<ItemsControl ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
<ItemsControl.ItemTemplate>
<DataTemplate x:DataType="statsmodels:ArtistPlayCount">
<Grid Margin="0,4">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
</Grid.ColumnDefinitions>
<StackPanel Orientation="Horizontal" Spacing="10">
<PersonPicture
Width="32"
Height="32"
DisplayName="{x:Bind Artist}" />
<TextBlock
VerticalAlignment="Center"
Style="{ThemeResource BodyStrongTextBlockStyle}"
Text="{x:Bind Artist}" />
</StackPanel>
<TextBlock
Grid.Column="1"
VerticalAlignment="Center"
FontWeight="SemiBold">
<Run Text="{x:Bind PlayCount}" />
<Run
x:Uid="StatsDashboardControlTrackCountText"
FontSize="10"
FontWeight="Normal"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
<!-- Top tracks -->
<Border
Grid.Column="1"
Margin="0,0,0,12"
Style="{StaticResource StatsCardStyle}">
<StackPanel>
<TextBlock
x:Uid="StatsDashboardControlTopSongs"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<ItemsControl ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}">
<ItemsControl.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="&#xE8D6;" />
</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
Grid.Column="2"
VerticalAlignment="Center"
FontWeight="SemiBold">
<Run Text="{x:Bind PlayCount}" />
<Run
x:Uid="StatsDashboardControlTrackCountText"
FontSize="10"
FontWeight="Normal"
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}" />
</TextBlock>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Border>
</Grid>
<!-- 播放源分布 -->
<Border
Grid.Row="3"
Margin="0,0,0,20"
Style="{StaticResource StatsCardStyle}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<TextBlock
x:Uid="StatsDashboardControlSources"
Margin="0,0,0,12"
Style="{ThemeResource SubtitleTextBlockStyle}" />
<lvc:PieChart
Grid.Row="1"
MinHeight="250"
Background="Transparent"
LegendPosition="Bottom"
LegendTextSize="{StaticResource BodyTextBlockFontSize}"
Series="{x:Bind ViewModel.SourceSeries, Mode=OneWay}"
TooltipPosition="Center" />
</Grid>
</Border>
</Grid>
</ScrollViewer>
<!--<Button
Grid.Row="2"
HorizontalAlignment="Right"
VerticalAlignment="Bottom"
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
Content="Generate test data" />-->
</Grid>
</UserControl>

View File

@@ -0,0 +1,34 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
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 BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.DependencyInjection;
using BetterLyrics.WinUI3.Enums;
// 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 StatsDashboardControl : UserControl
{
public StatsDashboardControlViewModel ViewModel => (StatsDashboardControlViewModel)DataContext;
public StatsDashboardControl()
{
InitializeComponent();
DataContext = Ioc.Default.GetRequiredService<StatsDashboardControlViewModel>();
}
}

View File

@@ -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();
}
}
}

View File

@@ -15,7 +15,7 @@ namespace BetterLyrics.WinUI3.Converter
FileSourceType.Local => "\uE8B7", // Folder
FileSourceType.SMB => "\uE839", // Network
FileSourceType.FTP => "\uE838", // Globe
FileSourceType.WebDav => "\uE753", // Cloud
FileSourceType.WebDAV => "\uE753", // Cloud
_ => "\uE8B7"
};
}

View File

@@ -3,18 +3,37 @@ using System;
namespace BetterLyrics.WinUI3.Converter
{
public class MillisecondsToFormattedTimeConverter : IValueConverter
public partial class MillisecondsToFormattedTimeConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, string language)
{
if (value is int milliseconds)
double? milliseconds = null;
if (value is int iVal) milliseconds = iVal;
else if (value is double dVal) milliseconds = dVal;
else if (value is long lVal) milliseconds = lVal;
if (milliseconds.HasValue)
{
return TimeSpan.FromMilliseconds(milliseconds).ToString(@"mm\:ss\.fff");
}
else if (value is double doubleMilliseconds)
{
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff");
var ts = TimeSpan.FromMilliseconds(milliseconds.Value);
string? format = parameter?.ToString();
if (string.IsNullOrEmpty(format))
{
format = @"mm\:ss\.fff";
}
try
{
return ts.ToString(format);
}
catch (FormatException)
{
return ts.ToString();
}
}
return value?.ToString() ?? "";
}

View File

@@ -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();
}
}
}

View File

@@ -5,6 +5,6 @@
Local,
SMB,
FTP,
WebDav
WebDAV
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Enums
{
public enum StatsRange
{
Today,
ThisWeek,
ThisMonth,
ThisQuarter,
ThisYear,
Custom
}
}

View File

@@ -1,10 +1,11 @@
using BetterLyrics.WinUI3.Models;
using System;
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",
@@ -30,6 +31,22 @@ namespace BetterLyrics.WinUI3.Extensions
songInfo.Album = value;
return songInfo;
}
public PlayHistoryItem? ToPlayHistoryItem(double actualPlayedMs)
{
if (songInfo == null) return null;
return new PlayHistoryItem
{
Title = songInfo.Title,
Artist = songInfo.DisplayArtists,
Album = songInfo.Album,
PlayerId = songInfo.PlayerId ?? "N/A",
TotalDurationMs = songInfo.DurationMs,
DurationPlayedMs = actualPlayedMs,
StartedAt = DateTime.FromBinary(songInfo.StartedAt)
};
}
}
}
}

View File

@@ -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; }
}
}
}
}

View File

@@ -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公式

View File

@@ -9,6 +9,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Ude;
namespace BetterLyrics.WinUI3.Helper
@@ -29,6 +30,18 @@ namespace BetterLyrics.WinUI3.Helper
return Encoding.GetEncoding(encoding);
}
public static async Task CopyFileAsync(string sourcePath, string destinationPath)
{
var dir = Path.GetDirectoryName(destinationPath);
if (!Directory.Exists(dir)) Directory.CreateDirectory(dir);
using (var sourceStream = new FileStream(sourcePath, FileMode.Open, FileAccess.Read, FileShare.Read))
using (var destinationStream = new FileStream(destinationPath, FileMode.Create, FileAccess.Write, FileShare.None))
{
await sourceStream.CopyToAsync(destinationStream);
}
}
public static string SanitizeFileName(string fileName, char replacement = '_')
{
var invalidChars = Path.GetInvalidFileNameChars();

View File

@@ -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();

View File

@@ -33,6 +33,7 @@ namespace BetterLyrics.WinUI3.Helper
public static string SaltPlayerForWindowsLogoPath => Path.Combine(AssetsFolder, "SaltPlayerForWindows.png");
public static string MoeKoeMusicLogoPath => Path.Combine(AssetsFolder, "MoeKoeMusic.png");
public static string Listen1LogoPath => Path.Combine(AssetsFolder, "Listen1.png");
public static string OriginalSoundHQPlayerLogoPath => Path.Combine(AssetsFolder, "OriginalSoundHQPlayer.png");
public static string UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
@@ -56,8 +57,9 @@ namespace BetterLyrics.WinUI3.Helper
public static string iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
public static string LocalAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "local");
public static string PlayQueuePath => Path.Combine(CacheFolder, "play-queue.m3u");
public static string FilesCachePath => Path.Combine(CacheFolder, "files_cache.db");
public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u");
public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db");
public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db");
public static void EnsureDirectories()
{
@@ -79,5 +81,6 @@ namespace BetterLyrics.WinUI3.Helper
Directory.CreateDirectory(iTunesAlbumArtCacheDirectory);
Directory.CreateDirectory(LocalAlbumArtCacheDirectory);
}
}
}

View File

@@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
namespace BetterLyrics.WinUI3.Helper
{
public static class PlayerIDHelper
public static class PlayerIdHelper
{
private static readonly List<string> neteaseFamilyRegex =
[
@@ -25,64 +25,66 @@ namespace BetterLyrics.WinUI3.Helper
return false;
}
public static bool IsLXMusic(string? id) => id is PlayerID.LXMusic or PlayerID.LXMusicPortable;
public static bool IsLXMusic(string? id) => id is PlayerId.LXMusic or PlayerId.LXMusicPortable;
public static bool IsAppleMusic(string? id) => id is PlayerID.AppleMusic or PlayerID.AppleMusicAlternative;
public static bool IsAppleMusic(string? id) => id is PlayerId.AppleMusic or PlayerId.AppleMusicAlternative;
public static bool IsBetterLyrics(string? id) => id is PlayerID.BetterLyrics or PlayerID.BetterLyricsDebug;
public static bool IsBetterLyrics(string? id) => id is PlayerId.BetterLyrics or PlayerId.BetterLyricsDebug;
public static string? GetDisplayName(string? id) => id switch
{
PlayerID.Spotify => PlayerName.Spotify,
PlayerID.AppleMusic => PlayerName.AppleMusic,
PlayerID.iTunes => PlayerName.iTunes,
PlayerID.KugouMusic => PlayerName.KugouMusic,
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
PlayerID.QQMusic => PlayerName.QQMusic,
PlayerID.LXMusic => PlayerName.LXMusic,
PlayerID.LXMusicPortable => PlayerName.LXMusicPortable,
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
PlayerID.AIMP => PlayerName.AIMP,
PlayerID.Foobar2000 => PlayerName.Foobar2000,
PlayerID.MusicBee => PlayerName.MusicBee,
PlayerID.PotPlayer => PlayerName.PotPlayer,
PlayerID.Chrome => PlayerName.Chrome,
PlayerID.Edge => PlayerName.Edge,
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
PlayerID.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
PlayerID.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
PlayerID.Listen1 => PlayerName.Listen1,
PlayerId.Spotify => PlayerName.Spotify,
PlayerId.AppleMusic => PlayerName.AppleMusic,
PlayerId.iTunes => PlayerName.iTunes,
PlayerId.KugouMusic => PlayerName.KugouMusic,
PlayerId.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
PlayerId.QQMusic => PlayerName.QQMusic,
PlayerId.LXMusic => PlayerName.LXMusic,
PlayerId.LXMusicPortable => PlayerName.LXMusicPortable,
PlayerId.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
PlayerId.AIMP => PlayerName.AIMP,
PlayerId.Foobar2000 => PlayerName.Foobar2000,
PlayerId.MusicBee => PlayerName.MusicBee,
PlayerId.PotPlayer => PlayerName.PotPlayer,
PlayerId.Chrome => PlayerName.Chrome,
PlayerId.Edge => PlayerName.Edge,
PlayerId.BetterLyrics => PlayerName.BetterLyrics,
PlayerId.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
PlayerId.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
PlayerId.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
PlayerId.MoeKoeMusic => PlayerName.MoeKoeMusic,
PlayerId.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
PlayerId.Listen1 => PlayerName.Listen1,
PlayerId.OriginalSoundHQPlayer => PlayerName.OriginalSoundHQPlayer,
_ => id,
};
public static string GetLogoPath(string? id) => id switch
{
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
PlayerID.iTunes => PathHelper.iTunesLogoPath,
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
PlayerID.LXMusicPortable => PathHelper.LXMusicLogoPath,
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
PlayerID.AIMP => PathHelper.AIMPLogoPath,
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
PlayerID.Chrome => PathHelper.ChromeLogoPath,
PlayerID.Edge => PathHelper.EdgeLogoPath,
PlayerID.BetterLyrics => PathHelper.LogoPath,
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
PlayerID.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
PlayerId.Spotify => PathHelper.SpotifyLogoPath,
PlayerId.AppleMusic => PathHelper.AppleMusicLogoPath,
PlayerId.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
PlayerId.iTunes => PathHelper.iTunesLogoPath,
PlayerId.KugouMusic => PathHelper.KugouMusicLogoPath,
PlayerId.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
PlayerId.QQMusic => PathHelper.QQMusicLogoPath,
PlayerId.LXMusic => PathHelper.LXMusicLogoPath,
PlayerId.LXMusicPortable => PathHelper.LXMusicLogoPath,
PlayerId.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
PlayerId.AIMP => PathHelper.AIMPLogoPath,
PlayerId.Foobar2000 => PathHelper.Foobar2000LogoPath,
PlayerId.MusicBee => PathHelper.MusicBeeLogoPath,
PlayerId.PotPlayer => PathHelper.PotPlayerLogoPath,
PlayerId.Chrome => PathHelper.ChromeLogoPath,
PlayerId.Edge => PathHelper.EdgeLogoPath,
PlayerId.BetterLyrics => PathHelper.LogoPath,
PlayerId.BetterLyricsDebug => PathHelper.LogoPath,
PlayerId.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerId.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
PlayerId.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
PlayerId.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
PlayerId.Listen1 => PathHelper.Listen1LogoPath,
PlayerId.OriginalSoundHQPlayer => PathHelper.OriginalSoundHQPlayerLogoPath,
_ => PathHelper.UnknownPlayerLogoPath,
};
}

View File

@@ -0,0 +1,72 @@
using System;
using System.Collections.Generic;
using System.Net;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Helper
{
public static class WebDavProbeHelper
{
/// <summary>
/// 自动检测目标主机是 HTTP 还是 HTTPS
/// </summary>
/// <returns>返回 "https" 或 "http",如果都连不上返回 null</returns>
public static async Task<string?> DetectSchemeAsync(string host, int port, string? path, string? user, string? pwd)
{
if (port == 443) return "https";
if (port == 80) return "http";
// 忽略 SSL 证书错误,因为很多 NAS 是自签名的
var handler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => true,
UseProxy = false
};
// 设置认证
if (!string.IsNullOrEmpty(user) && !string.IsNullOrEmpty(pwd))
{
handler.Credentials = new NetworkCredential(user, pwd);
handler.PreAuthenticate = true;
}
using var client = new HttpClient(handler);
client.Timeout = TimeSpan.FromSeconds(3);
if (await ProbeUrlAsync(client, "https", host, port, path))
{
return "https";
}
if (await ProbeUrlAsync(client, "http", host, port, path))
{
return "http";
}
// 都失败
return null;
}
private static async Task<bool> ProbeUrlAsync(HttpClient client, string scheme, string host, int port, string? path)
{
try
{
var uriBuilder = new UriBuilder(scheme, host, port, path);
// 使用 PROPFIND 方法,且 Depth 为 0只检测根节点是否存在不拉取列表
var request = new HttpRequestMessage(new HttpMethod("PROPFIND"), uriBuilder.Uri);
request.Headers.Add("Depth", "0");
var response = await client.SendAsync(request);
return response.StatusCode != HttpStatusCode.BadRequest;
}
catch
{
return false;
}
}
}
}

View File

@@ -17,12 +17,20 @@ namespace BetterLyrics.WinUI3.Hooks
static SystemVolumeHook()
{
_deviceEnumerator = new MMDeviceEnumerator();
_defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
if (_defaultDevice != null)
try
{
_defaultDevice.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
_deviceEnumerator = new MMDeviceEnumerator();
// 找不到设备会抛出异常,在这里截获它
_defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
if (_defaultDevice != null)
{
_defaultDevice.AudioEndpointVolume.OnVolumeNotification += AudioEndpointVolume_OnVolumeNotification;
}
}
catch (Exception ex)
{
_defaultDevice = null;
}
}

View File

@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Db
{
public partial class FilesIndexDbContext : DbContext
{
public FilesIndexDbContext(DbContextOptions<FilesIndexDbContext> options) : base(options) { }
public DbSet<FilesIndexItem> FilesIndex { get; set; }
}
}

View File

@@ -0,0 +1,14 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Db
{
public partial class PlayHistoryDbContext : DbContext
{
public PlayHistoryDbContext(DbContextOptions<PlayHistoryDbContext> options) : base(options) { }
public DbSet<PlayHistoryItem> PlayHistory { get; set; }
}
}

View File

@@ -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 { }
@@ -136,7 +121,7 @@ namespace BetterLyrics.WinUI3.Models
SetFromTrack(track);
}
public ExtendedTrack(FileCacheEntity? entity, Stream? stream = null) : base()
public ExtendedTrack(FilesIndexItem? entity, Stream? stream = null) : base()
{
if (entity == null) return;

View File

@@ -1,57 +0,0 @@
using SQLite;
using System;
namespace BetterLyrics.WinUI3.Models
{
[Table("FileCache")]
public class FileCacheEntity
{
[PrimaryKey, AutoIncrement]
public int Id { get; set; }
// 【新增】关键字段!
// 关联到 MediaFolder.Id。
// 作用:
// 1. 区分不同配置(即使两个配置连的是同一个 SMB但在 APP 里视为不同源)。
// 2. 删除配置时,可以由 MediaFolderId 快速级联删除所有缓存。
[Indexed]
public string MediaFolderId { get; set; }
// 【修改】从 ParentPath 改为 ParentUri
// 存储父文件夹的标准 URI (smb://host/share/parent)
// 根目录文件的 ParentUri 可以为空,或者等于 MediaFolder 的 Base Uri
[Indexed]
public string? ParentUri { get; set; }
// 【核心】标准化的完整 URI (smb://host/share/folder/file.ext)
// 确保它是 URL 编码过且格式统一的
[Indexed(Unique = true)]
public string Uri { get; set; }
public string FileName { get; set; } = "";
public bool IsDirectory { get; set; }
// 记录文件大小,同步时用来对比文件是否变化
public long FileSize { get; set; }
// 记录修改时间,同步时对比使用
public DateTime? LastModified { get; set; }
// ------ 元数据部分 (保持不变) ------
public string Title { get; set; } = "";
public string Artists { get; set; } = "";
public string Album { get; set; } = "";
public int? Year { get; set; }
public int Bitrate { get; set; }
public double SampleRate { get; set; }
public int BitDepth { get; set; }
public int Duration { get; set; }
public string AudioFormatName { get; set; } = "";
public string AudioFormatShortName { get; set; } = "";
public string Encoder { get; set; } = "";
public string? EmbeddedLyrics { get; set; }
public string? LocalAlbumArtPath { get; set; }
public bool IsMetadataParsed { get; set; }
}
}

View File

@@ -0,0 +1,67 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BetterLyrics.WinUI3.Models
{
[Index(nameof(MediaFolderId))] // 普通索引
[Index(nameof(ParentUri))] // 普通索引
[Index(nameof(Uri), IsUnique = true)] // 唯一索引
public class FilesIndexItem
{
[Key] // 主键
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // 明确指定为自增 (Identity)
public int Id { get; set; }
// 关联到 MediaFolder.Id
// 注意:作为索引列,必须限制长度,否则 SQL Server 会报错 (索引最大900字节)
[MaxLength(450)]
public string MediaFolderId { get; set; }
// 存储父文件夹的标准 URI
// 允许为空
[MaxLength(450)]
public string? ParentUri { get; set; }
// 唯一索引列
// 必须限制长度。450字符 * 2字节/字符 = 900字节 (正好卡在 SQL Server 限制内)
[Required]
[MaxLength(450)]
public string Uri { get; set; }
public string FileName { get; set; } = "";
public bool IsDirectory { get; set; }
public long FileSize { get; set; }
public DateTime? LastModified { get; set; }
// 下面的元数据字段通常不需要索引,可以使用 MaxLength 稍微优化空间,
// 或者直接留空(默认为 nvarchar(max)
public string Title { get; set; } = "";
public string Artists { get; set; } = "";
public string Album { get; set; } = "";
public int? Year { get; set; }
public int Bitrate { get; set; }
public double SampleRate { get; set; }
public int BitDepth { get; set; }
public int Duration { get; set; }
[MaxLength(50)] // 格式名称通常很短,限制一下是个好习惯
public string AudioFormatName { get; set; } = "";
[MaxLength(20)]
public string AudioFormatShortName { get; set; } = "";
public string Encoder { get; set; } = "";
// 歌词可能会很长,保留默认的 nvarchar(max) 即可
public string? EmbeddedLyrics { get; set; }
public string? LocalAlbumArtPath { get; set; }
public bool IsMetadataParsed { get; set; }
}
}

View File

@@ -3,6 +3,7 @@ using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Services.FileSystemService;
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Text.Json.Serialization;
using System.Threading;
@@ -30,6 +31,7 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedFor(nameof(UriString))] public partial string UriHost { get; set; }
[ObservableProperty][NotifyPropertyChangedFor(nameof(UriString))] public partial int UriPort { get; set; } = -1;
[JsonPropertyName("Path")]
[ObservableProperty]
[NotifyPropertyChangedRecipients]
[NotifyPropertyChangedFor(nameof(ConnectionSummary))]
@@ -40,12 +42,10 @@ namespace BetterLyrics.WinUI3.Models
[JsonIgnore] public bool IsLocal => SourceType == FileSourceType.Local;
[JsonIgnore][ObservableProperty] public partial bool IsIndexing { get; set; } = false;
[JsonIgnore][ObservableProperty] public partial double IndexingProgress { get; set; } = 0;
[JsonIgnore][ObservableProperty] public partial string IndexingStatusText { get; set; } = "";
[JsonIgnore][ObservableProperty] public partial bool IsCleaningUp { get; set; } = false;
[JsonIgnore][ObservableProperty] public partial string CleaningUpStatusText { get; set; } = "";
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsProcessing { get; set; } = false;
[ObservableProperty] public partial double IndexingProgress { get; set; } = 0;
[ObservableProperty] public partial string StatusText { get; set; } = "";
[ObservableProperty] public partial InfoBarSeverity StatusSeverity { get; set; } = InfoBarSeverity.Informational;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DateTime? LastSyncTime { get; set; }
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AutoScanInterval ScanInterval { get; set; } = AutoScanInterval.Disabled;
@@ -118,7 +118,7 @@ namespace BetterLyrics.WinUI3.Models
FileSourceType.Local => new LocalFileSystem(this),
FileSourceType.SMB => new SMBFileSystem(this),
FileSourceType.FTP => new FTPFileSystem(this),
FileSourceType.WebDav => new WebDavFileSystem(this),
FileSourceType.WebDAV => new WebDavFileSystem(this),
_ => throw new NotImplementedException()
};
}

View File

@@ -35,11 +35,11 @@ namespace BetterLyrics.WinUI3.Models
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
[JsonIgnore] public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);
[JsonIgnore] public string LogoPath => PlayerIdHelper.GetLogoPath(Provider);
[JsonIgnore] public string? DisplayName => PlayerIDHelper.GetDisplayName(Provider);
[JsonIgnore] public string? DisplayName => PlayerIdHelper.GetDisplayName(Provider);
[JsonIgnore] public bool IsLXMusic => PlayerIDHelper.IsLXMusic(Provider);
[JsonIgnore] public bool IsLXMusic => PlayerIdHelper.IsLXMusic(Provider);
public MediaSourceProviderInfo()
{
@@ -53,7 +53,7 @@ namespace BetterLyrics.WinUI3.Models
IsEnabled = isEnable;
switch (provider)
{
case Constants.PlayerID.AppleMusic:
case Constants.PlayerId.AppleMusic:
// Apple Music 的特性
TimelineSyncThreshold = 1000;
PositionOffset = 1000;

View File

@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore;
using System;
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace BetterLyrics.WinUI3.Models
{
[Index(nameof(Title))]
[Index(nameof(Artist))]
[Index(nameof(StartedAt))] // 用于按时间排序查询(如:最近播放)
[Index(nameof(PlayerId))]
public class PlayHistoryItem
{
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)] // AutoIncrement
public int Id { get; set; }
// 注意:作为索引列,必须加 MaxLength。
// 如果不加,默认为 nvarchar(max)SQL Server 无法对其建立高效索引。
[MaxLength(450)]
public string Title { get; set; } = "";
[MaxLength(450)]
public string Artist { get; set; } = "";
// Album 没有索引,可以不限制长度,或者为了规范也限制一下
public string Album { get; set; } = "";
public DateTime StartedAt { get; set; }
public double DurationPlayedMs { get; set; }
public double TotalDurationMs { get; set; }
// PlayerId 通常是个 GUID 或者短字符串,给 100 长度通常足够了,节省索引空间
[MaxLength(100)]
public string PlayerId { get; set; } = "";
}
}

View File

@@ -0,0 +1,15 @@
using BetterLyrics.WinUI3.Helper;
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models
{
public class PlayerStatDisplayItem
{
public string PlayerId { get; set; }
public int PlayCount { get; set; }
public string PlayerName => PlayerIdHelper.GetDisplayName(PlayerId);
}
}

View File

@@ -1,5 +1,6 @@
using BetterLyrics.WinUI3.Enums;
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using System.Collections.ObjectModel;
namespace BetterLyrics.WinUI3.Models.Settings

View File

@@ -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,
};
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Stats
{
public class ArtistPlayCount
{
public string Artist { get; set; }
public int PlayCount { get; set; }
public double TotalDurationSeconds { get; set; }
}
}

View File

@@ -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"
}
}

View File

@@ -0,0 +1,16 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Stats
{
public class PlayerStats
{
public string PlayerId { get; set; }
public int Count { get; set; }
public double DisplayWidth => (TotalCount > 0) ? (Count / (double)TotalCount) * 150 : 0;
public int TotalCount { get; set; }
}
}

View File

@@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
namespace BetterLyrics.WinUI3.Models.Stats
{
public class SongPlayCount
{
public string Title { get; set; }
public string Artist { get; set; }
public int PlayCount { get; set; }
}
}

View File

@@ -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,

View File

@@ -90,7 +90,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
FileCacheEntity? bestMatch = null;
FilesIndexItem? bestMatch = null;
foreach (var item in allFiles)
{

View File

@@ -1,14 +1,16 @@
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Db;
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
using BetterLyrics.WinUI3.Services.LocalizationService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.ViewModels;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using SQLite;
using Microsoft.UI.Xaml.Controls;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
@@ -27,66 +29,44 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
private readonly ILocalizationService _localizationService;
private readonly ILogger<FileSystemService> _logger;
private readonly SQLiteAsyncConnection _db;
private bool _isInitialized = false;
private readonly IDbContextFactory<FilesIndexDbContext> _contextFactory;
// 定时器字典
private readonly ConcurrentDictionary<string, CancellationTokenSource> _folderTimerTokens = new();
// 当前正在执行的扫描任务字典
private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeScanTokens = new();
private static readonly SemaphoreSlim _dbLock = new(1, 1);
private static readonly SemaphoreSlim _folderScanLock = new(1, 1);
public FileSystemService(ISettingsService settingsService, ILocalizationService localizationService, ILogger<FileSystemService> logger)
public FileSystemService(
ISettingsService settingsService,
ILocalizationService localizationService,
ILogger<FileSystemService> logger,
IDbContextFactory<FilesIndexDbContext> contextFactory)
{
_logger = logger;
_localizationService = localizationService;
_settingsService = settingsService;
_db = new SQLiteAsyncConnection(PathHelper.FilesCachePath);
_contextFactory = contextFactory;
}
/// <summary>
/// 初始化(连接)数据库
/// </summary>
/// <returns></returns>
public async Task InitializeAsync()
public async Task<List<FilesIndexItem>> GetFilesAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId, bool forceRefresh = false)
{
if (_isInitialized) return;
string queryParentUri = parentFolder == null ? "" : parentFolder.Uri;
if (parentFolder == null && !forceRefresh) forceRefresh = true;
await _db.CreateTableAsync<FileCacheEntity>();
using var context = await _contextFactory.CreateDbContextAsync();
_isInitialized = true;
}
public async Task<List<FileCacheEntity>> GetFilesAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId, bool forceRefresh = false)
{
await InitializeAsync();
string queryParentUri;
if (parentFolder == null)
{
if (!forceRefresh) forceRefresh = true;
queryParentUri = "";
}
else
{
queryParentUri = parentFolder.Uri;
}
List<FileCacheEntity> cachedEntities = new List<FileCacheEntity>();
if (parentFolder != null)
{
cachedEntities = await _db.Table<FileCacheEntity>()
.Where(x => x.MediaFolderId == configId && x.ParentUri == queryParentUri)
.ToListAsync();
}
var cachedEntities = await context.FilesIndex
.AsNoTracking() // 读操作不追踪,提升性能
.Where(x => x.MediaFolderId == configId && x.ParentUri == queryParentUri)
.ToListAsync();
bool needSync = forceRefresh || cachedEntities.Count == 0;
if (needSync)
{
// SyncAsync 内部自己管理 Context
cachedEntities = await SyncAsync(provider, parentFolder, configId);
}
@@ -94,17 +74,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
}
/// <summary>
/// 从远端/本地同步文件至数据库,该阶段不会解析文件全部元数据。
/// <para/>
/// 如果某个已有文件被修改或有新文件被添加,会预留空位,等待后续填充(通常交给 <see cref="ScanMediaFolderAsync"/> 完成)
/// 从远端/本地同步文件至数据库
/// </summary>
/// <param name="provider"></param>
/// <param name="parentFolder"></param>
/// <param name="configId"></param>
/// <returns></returns>
private async Task<List<FileCacheEntity>> SyncAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId)
private async Task<List<FilesIndexItem>> SyncAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId)
{
List<FileCacheEntity> remoteItems;
List<FilesIndexItem> remoteItems;
try
{
remoteItems = await provider.GetFilesAsync(parentFolder);
@@ -119,79 +93,79 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
string targetParentUri = "";
if (remoteItems.Count > 0)
{
targetParentUri = remoteItems[0].ParentUri ?? "";
}
else if (parentFolder != null)
{
targetParentUri = parentFolder.Uri;
}
else
{
return [];
}
try
{
await _db.RunInTransactionAsync(conn =>
using var context = await _contextFactory.CreateDbContextAsync();
// 开启事务 (EF Core 也能管理事务)
using var transaction = await context.Database.BeginTransactionAsync();
// 1. 获取数据库中现有的该目录下的文件
var dbItems = await context.FilesIndex
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
.ToListAsync();
var dbMap = dbItems.ToDictionary(x => x.Uri, x => x);
// 2. 远端数据去重(防止 Provider 返回重复 Uri
var remoteDistinct = remoteItems
.GroupBy(x => x.Uri)
.Select(g => g.First())
.ToList();
var remoteUris = new HashSet<string>();
// 3. 处理 新增 和 更新
foreach (var remote in remoteDistinct)
{
var dbItems = conn.Table<FileCacheEntity>()
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
.ToList();
remoteUris.Add(remote.Uri);
var dbMap = dbItems.ToDictionary(x => x.Uri, x => x);
var remoteMap = remoteItems.GroupBy(x => x.Uri)
.Select(g => g.First())
.ToDictionary(x => x.Uri, x => x);
var toInsert = new List<FileCacheEntity>();
var toUpdate = new List<FileCacheEntity>();
var toDelete = new List<FileCacheEntity>();
foreach (var remote in remoteItems)
if (dbMap.TryGetValue(remote.Uri, out var existing))
{
if (dbMap.TryGetValue(remote.Uri, out var existing))
{
bool isChanged = existing.FileSize != remote.FileSize ||
existing.LastModified != remote.LastModified;
// 检查是否变更
bool isChanged = existing.FileSize != remote.FileSize ||
existing.LastModified != remote.LastModified;
if (isChanged)
{
existing.FileSize = remote.FileSize;
existing.LastModified = remote.LastModified;
existing.IsMetadataParsed = false; // 标记为未解析,下次会重新读取元数据
toUpdate.Add(existing);
}
else
{
// 数据库里原有的 Title, Artist, LocalAlbumArtPath 都会被完美保留
}
}
else
if (isChanged)
{
toInsert.Add(remote);
existing.FileSize = remote.FileSize;
existing.LastModified = remote.LastModified;
existing.IsMetadataParsed = false; // 标记重新解析
// EF Core 自动追踪 existing 的变化,无需手动 Update
}
}
foreach (var dbItem in dbItems)
else
{
if (!remoteMap.ContainsKey(dbItem.Uri))
{
toDelete.Add(dbItem);
}
// 新增
// 注意:如果 Id 是自增的,不要手动赋值 Id除非是 Guid
context.FilesIndex.Add(remote);
}
}
if (toInsert.Count > 0) conn.InsertAll(toInsert);
if (toUpdate.Count > 0) conn.UpdateAll(toUpdate);
if (toDelete.Count > 0)
// 4. 处理 删除 (数据库有,远端没有)
foreach (var dbItem in dbItems)
{
if (!remoteUris.Contains(dbItem.Uri))
{
foreach (var item in toDelete) conn.Delete(item);
context.FilesIndex.Remove(dbItem);
}
});
}
var finalItems = await _db.Table<FileCacheEntity>()
await context.SaveChangesAsync();
await transaction.CommitAsync();
// 5. 返回最新数据
// 这里的 dbItems 已经被 Update 更新了内存状态,但 Remove 的还在列表里Add 的不在列表里
// 所以最稳妥的是重新查一次,或者手动维护列表。为了准确性,重新查询 (AsNoTracking)
var finalItems = await context.FilesIndex
.AsNoTracking()
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
.ToListAsync();
@@ -206,37 +180,34 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
}
}
public async Task UpdateMetadataAsync(FileCacheEntity entity)
public async Task UpdateMetadataAsync(FilesIndexItem entity)
{
// 现在的实体已经包含了完整信息,直接 Update 即可
// 我们只需要确保 Where 子句用的是主键或者 Uri
using var context = await _contextFactory.CreateDbContextAsync();
// 简化版 SQL直接用 ORM 的 Update
// 但因为 entity 对象可能包含一些不应该被覆盖的旧数据(如果多线程操作),
// 手写 SQL 只更新 Metadata 字段更安全。
string sql = @"
UPDATE FileCache
SET
Title = ?, Artists = ?, Album = ?,
Year = ?, Bitrate = ?, SampleRate = ?, BitDepth = ?,
Duration = ?, AudioFormatName = ?, AudioFormatShortName = ?, Encoder = ?,
EmbeddedLyrics = ?, LocalAlbumArtPath = ?,
IsMetadataParsed = 1
WHERE Id = ?"; // 推荐用 Id (主键) 最快,如果没有 Id 则用 Uri
await _db.ExecuteAsync(sql,
entity.Title, entity.Artists, entity.Album,
entity.Year, entity.Bitrate, entity.SampleRate, entity.BitDepth,
entity.Duration, entity.AudioFormatName, entity.AudioFormatShortName, entity.Encoder,
entity.EmbeddedLyrics, entity.LocalAlbumArtPath,
entity.Id // WHERE Id = ?
);
// 使用 EF Core 7.0+ 的 ExecuteUpdateAsync 高效更新
// 这会直接生成 UPDATE SQL不经过内存加载性能极高
await context.FilesIndex
.Where(x => x.Id == entity.Id) // 优先用 Id
.ExecuteUpdateAsync(setters => setters
.SetProperty(p => p.Title, entity.Title)
.SetProperty(p => p.Artists, entity.Artists)
.SetProperty(p => p.Album, entity.Album)
.SetProperty(p => p.Year, entity.Year)
.SetProperty(p => p.Bitrate, entity.Bitrate)
.SetProperty(p => p.SampleRate, entity.SampleRate)
.SetProperty(p => p.BitDepth, entity.BitDepth)
.SetProperty(p => p.Duration, entity.Duration)
.SetProperty(p => p.AudioFormatName, entity.AudioFormatName)
.SetProperty(p => p.AudioFormatShortName, entity.AudioFormatShortName)
.SetProperty(p => p.Encoder, entity.Encoder)
.SetProperty(p => p.EmbeddedLyrics, entity.EmbeddedLyrics)
.SetProperty(p => p.LocalAlbumArtPath, entity.LocalAlbumArtPath)
.SetProperty(p => p.IsMetadataParsed, true)
);
}
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity)
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity)
{
// 直接传递实体给 Provider
return await provider.OpenReadAsync(entity);
}
@@ -244,8 +215,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
{
_dispatcherQueue.TryEnqueue(() =>
{
folder.CleaningUpStatusText = _localizationService.GetLocalizedString("FileSystemServicePrepareToClean");
folder.IsCleaningUp = true;
folder.IndexingProgress = 0;
folder.StatusSeverity = InfoBarSeverity.Informational;
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServicePrepareToClean");
folder.IsProcessing = true;
});
if (_folderTimerTokens.TryRemove(folder.Id, out var timerCts))
@@ -258,7 +231,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
if (_activeScanTokens.TryGetValue(folder.Id, out var activeScanCts))
{
activeScanCts.Cancel();
// 强制终止正在扫描的操作
}
try
@@ -269,20 +241,19 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
{
_dispatcherQueue.TryEnqueue(() =>
{
folder.CleaningUpStatusText = _localizationService.GetLocalizedString("FileSystemServiceCleaningCache");
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceCleaningCache");
});
await InitializeAsync();
using var context = await _contextFactory.CreateDbContextAsync();
await _dbLock.WaitAsync();
try
await context.FilesIndex
.Where(x => x.MediaFolderId == folder.Id)
.ExecuteDeleteAsync();
// VACUUM 是 SQLite 特有的命令
if (context.Database.IsSqlite())
{
await _db.ExecuteAsync("DELETE FROM FileCache WHERE MediaFolderId = ?", folder.Id);
await _db.ExecuteAsync("VACUUM");
}
finally
{
_dbLock.Release();
await context.Database.ExecuteSqlRawAsync("VACUUM");
}
}
finally
@@ -298,8 +269,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
{
_dispatcherQueue.TryEnqueue(() =>
{
folder.CleaningUpStatusText = "";
folder.IsCleaningUp = false;
folder.IsProcessing = false;
folder.LastSyncTime = null;
});
}
@@ -314,30 +284,33 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
_dispatcherQueue.TryEnqueue(() =>
{
folder.IsIndexing = true;
folder.StatusSeverity = InfoBarSeverity.Informational;
folder.IsProcessing = true;
folder.IndexingProgress = 0;
folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceWaitingForScan");
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceWaitingForScan");
});
try
{
await _folderScanLock.WaitAsync(scanCts.Token);
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceConnecting"));
await InitializeAsync();
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceConnecting"));
using var fs = folder.CreateFileSystem();
if (fs == null || !await fs.ConnectAsync())
{
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceConnectFailed"));
_dispatcherQueue.TryEnqueue(() =>
{
folder.StatusSeverity = InfoBarSeverity.Error;
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceConnectFailed");
});
return;
}
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceFetchingFileList"));
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceFetchingFileList"));
var filesToProcess = new List<FileCacheEntity>();
var foldersToScan = new Queue<FileCacheEntity?>();
var filesToProcess = new List<FilesIndexItem>();
var foldersToScan = new Queue<FilesIndexItem?>();
foldersToScan.Enqueue(null); // 根目录
while (foldersToScan.Count > 0)
@@ -345,7 +318,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
if (scanCts.Token.IsCancellationRequested) return;
var currentParent = foldersToScan.Dequeue();
var items = await GetFilesAsync(fs, currentParent, folder.Id, forceRefresh: true);
foreach (var item in items)
@@ -377,10 +349,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
if (current % 10 == 0 || current == total)
{
double progress = (double)current / total * 100;
_dispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
_dispatcherQueue.TryEnqueue(() =>
{
folder.IndexingProgress = progress;
folder.IndexingStatusText = $"{_localizationService.GetLocalizedString("FileSystemServiceParsing")} {current}/{total}";
folder.StatusText = $"{_localizationService.GetLocalizedString("FileSystemServiceParsing")} {current}/{total}";
});
}
@@ -410,10 +382,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
if (track.Duration > 0)
{
// 保存封面
string? artPath = await SaveAlbumArtToDiskAsync(track);
// 填充实体
item.Title = track.Title;
item.Artists = track.Artist;
item.Album = track.Album;
@@ -425,7 +395,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
item.AudioFormatName = track.AudioFormatName;
item.AudioFormatShortName = track.AudioFormatShortName;
item.Encoder = track.Encoder;
item.EmbeddedLyrics = track.RawLyrics; // 内嵌歌词
item.EmbeddedLyrics = track.RawLyrics;
item.LocalAlbumArtPath = artPath;
item.IsMetadataParsed = true;
}
@@ -437,7 +407,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
{
using var reader = new StreamReader(stream);
string content = await reader.ReadToEndAsync();
item.EmbeddedLyrics = content;
item.IsMetadataParsed = true;
}
@@ -445,15 +414,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
if (item.IsMetadataParsed)
{
await _dbLock.WaitAsync(token);
try
{
await UpdateMetadataAsync(item);
}
finally
{
_dbLock.Release();
}
// 更新操作:直接调用 UpdateMetadataAsync
// 此时不需要 _dbLock因为 UpdateMetadataAsync 内部会 CreateDbContextAsync
// 而 _folderScanLock 已经保证了当前文件夹扫描的独占性
await UpdateMetadataAsync(item);
}
}
catch (Exception ex)
@@ -464,6 +428,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
_dispatcherQueue.TryEnqueue(() =>
{
folder.StatusSeverity = InfoBarSeverity.Success;
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceReady");
folder.LastSyncTime = DateTime.Now;
});
}
@@ -473,40 +439,52 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
}
catch (Exception ex)
{
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = ex.Message);
_dispatcherQueue.TryEnqueue(() =>
{
folder.StatusText = ex.Message;
folder.StatusSeverity = InfoBarSeverity.Error;
});
}
finally
{
_folderScanLock.Release();
_activeScanTokens.TryRemove(folder.Id, out _);
_dispatcherQueue.TryEnqueue(() =>
{
folder.IsIndexing = false;
folder.IndexingStatusText = "";
folder.IndexingProgress = 100;
folder.IsProcessing = false;
folder.IndexingProgress = 0;
});
}
}
public async Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
public async Task<List<FilesIndexItem>> GetParsedFilesAsync()
{
await InitializeAsync();
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())
{
return new List<FileCacheEntity>();
return new List<FilesIndexItem>();
}
var idList = enabledConfigIds.ToList();
// SQL 逻辑: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
var results = await _db.Table<FileCacheEntity>()
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 && idList.Contains(x.MediaFolderId))
.ToListAsync();
return results;
}
public void StartAllFolderTimers()
@@ -566,11 +544,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
}, newCts.Token);
}
// 参数为 string parentUri表示哪个文件夹的内容变了
public event EventHandler<string>? FolderUpdated;
private async Task<string?> SaveAlbumArtToDiskAsync(ExtendedTrack track)
{
// 代码未变,纯 IO 操作
var picData = track.AlbumArtByteArray;
if (picData == null || picData.Length == 0) return null;
@@ -623,6 +601,5 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
}
}
}
}
}

View File

@@ -10,8 +10,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
{
public interface IFileSystemService
{
Task InitializeAsync();
/// <summary>
/// 从数据库拉取文件(必要时需要从远端/本地同步至数据库)
/// </summary>
@@ -20,7 +18,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
/// <param name="configId"></param>
/// <param name="forceRefresh">强制需要从远端/本地同步至数据库</param>
/// <returns></returns>
Task<List<FileCacheEntity>> GetFilesAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId, bool forceRefresh = false);
Task<List<FilesIndexItem>> GetFilesAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId, bool forceRefresh = false);
/// <summary>
/// 打开文件(通过远端/本地流)
@@ -28,14 +26,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
/// <param name="provider"></param>
/// <param name="entity"></param>
/// <returns></returns>
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity);
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity);
/// <summary>
/// 更新数据库(单个文件)
/// </summary>
/// <param name="entity"></param>
/// <returns></returns>
Task UpdateMetadataAsync(FileCacheEntity entity);
Task UpdateMetadataAsync(FilesIndexItem entity);
/// <summary>
/// 从数据库删除
@@ -52,11 +50,17 @@ 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>
Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
void StartAllFolderTimers();

View File

@@ -14,13 +14,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
/// </summary>
/// <param name="parentFolder"></param>
/// <returns></returns>
Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null);
Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null);
/// <summary>
/// 打开流
/// </summary>
/// <param name="file"></param>
/// <returns></returns>
Task<Stream?> OpenReadAsync(FileCacheEntity file);
Task<Stream?> OpenReadAsync(FilesIndexItem file);
Task DisconnectAsync();
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using FluentFTP;
using System;
using System.Collections.Generic;
@@ -41,51 +42,37 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
public async Task<bool> ConnectAsync()
{
try
{
if (_client.IsConnected) return true;
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
return _client.IsConnected;
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine($"FTP连接失败: {ex.Message}");
return false;
}
if (_client.IsConnected) return true;
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
return _client.IsConnected;
}
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
{
var result = new List<FileCacheEntity>();
var result = new List<FilesIndexItem>();
// 1. 确定 FTP 服务器上的绝对路径
string targetServerPath;
Uri parentUri;
if (parentFolder == null)
{
// 根目录:从配置中提取
var rootUri = _config.GetStandardUri();
targetServerPath = rootUri.AbsolutePath;
parentUri = rootUri;
}
else
{
// 子目录:从实体中提取
targetServerPath = GetServerPathFromUri(parentFolder.Uri);
parentUri = new Uri(parentFolder.Uri);
}
// 2. 路径清洗:解码 URL (比如 %20 -> 空格),并统一分隔符
targetServerPath = WebUtility.UrlDecode(targetServerPath).Replace("\\", "/");
if (string.IsNullOrEmpty(targetServerPath)) targetServerPath = "/";
try
{
// 3. 获取列表 (FluentFTP 自动处理列表解析)
var items = await _client.GetListing(targetServerPath, FtpListOption.Auto);
// 准备 Base URI Scheme (ftp://192.168.1.5:21) 用于拼接子项
string baseUriSchema = $"{parentUri.Scheme}://{parentUri.Host}";
if (parentUri.Port > 0) baseUriSchema += $":{parentUri.Port}";
@@ -97,16 +84,19 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
// 只处理文件和文件夹
if (item.Type != FtpObjectType.File && item.Type != FtpObjectType.Directory) continue;
// 4. 构建标准 URI
// FluentFTP 的 item.FullName 通常是 "/Music/Song.mp3"
// 我们用 UriBuilder 把它封装成 "ftp://192.168.1.5:21/Music/Song.mp3"
// UriBuilder 会自动处理路径中的特殊字符编码
// 只处理特定后缀文件
if (item.Type == FtpObjectType.File)
{
string extension = Path.GetExtension(item.Name);
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
}
var builder = new UriBuilder(baseUriSchema)
{
Path = item.FullName
};
result.Add(new FileCacheEntity
result.Add(new FilesIndexItem
{
MediaFolderId = _config.Id,
// 如果是根目录扫描ParentUri 用 Config 的;否则用传入文件夹的
@@ -132,7 +122,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
return result;
}
public async Task<Stream?> OpenReadAsync(FileCacheEntity file)
public async Task<Stream?> OpenReadAsync(FilesIndexItem file)
{
if (file == null) return null;

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
@@ -19,12 +20,20 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
public Task<bool> ConnectAsync()
{
return Task.FromResult(Directory.Exists(_rootLocalPath));
var isExisted = Directory.Exists(_rootLocalPath);
if (isExisted)
{
return Task.FromResult(true);
}
else
{
throw new FileNotFoundException(null, _rootLocalPath);
}
}
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
{
var result = new List<FileCacheEntity>();
var result = new List<FilesIndexItem>();
string targetPath;
string parentUriString;
@@ -33,13 +42,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
{
if (parentFolder == null)
{
// 根目录
targetPath = _rootLocalPath;
parentUriString = _config.GetStandardUri().AbsoluteUri;
}
else
{
// 子目录:从标准 URI (file:///...) 还原为本地路径 (C:\...)
var uri = new Uri(parentFolder.Uri);
targetPath = uri.LocalPath;
parentUriString = parentFolder.Uri;
@@ -49,28 +56,35 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
var dirInfo = new DirectoryInfo(targetPath);
foreach (var item in dirInfo.GetFileSystemInfos())
foreach (var item in dirInfo.EnumerateFileSystemInfos())
{
// 生成标准 URI 作为唯一 ID
// new Uri("C:\Path\File") 会自动生成 file:///C:/Path/File
var itemUri = new Uri(item.FullName).AbsoluteUri;
// 跳过系统/隐藏文件
if ((item.Attributes & FileAttributes.Hidden) != 0 || (item.Attributes & FileAttributes.System) != 0) continue;
bool isDir = (item.Attributes & FileAttributes.Directory) == FileAttributes.Directory;
if (!isDir)
{
// 过滤后缀名
if (string.IsNullOrEmpty(item.Extension) || !FileHelper.AllSupportedExtensions.Contains(item.Extension)) continue;
}
var itemUri = new Uri(item.FullName).AbsoluteUri;
long size = 0;
// DirectoryInfo 没有 Length 属性,只有 FileInfo 有
if (!isDir && item is FileInfo fi)
{
size = fi.Length;
}
result.Add(new FileCacheEntity
result.Add(new FilesIndexItem
{
MediaFolderId = _config.Id, // 关联配置 ID
ParentUri = parentUriString, // 记录父级 URI
Uri = itemUri, // 标准化 URI (file:///...)
Uri = itemUri,
FileName = item.Name,
IsDirectory = isDir,
@@ -88,11 +102,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
return await Task.FromResult(result);
}
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
{
if (entity == null) return null;
// 将标准 URI (file:///C:/...) 还原为本地路径 (C:\...)
string localPath = new Uri(entity.Uri).LocalPath;
// 使用 FileShare.Read 允许其他程序同时读取

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using SMBLibrary;
using SMBLibrary.Client;
using System;
@@ -14,7 +15,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
private SMB2Client? _client;
private ISMBFileStore? _fileStore;
// 保存配置对象的引用,它是我们的“真理来源”
// 保存配置对象的引用
private readonly MediaFolder _config;
// 缓存解析出来的 Share 名称,因为 TreeConnect 要用
@@ -25,8 +26,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
_config = config ?? throw new ArgumentNullException(nameof(config));
// 在构造时就解析好 Share 名称,避免后续重复解析
// 假设 URI 是 smb://host/ShareName/Folder/Sub
// 我们需要提取 "ShareName"
var uri = _config.GetStandardUri();
// Segments[0] 是 "/", Segments[1] 是 "ShareName/"
@@ -43,29 +42,22 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
public async Task<bool> ConnectAsync()
{
try
{
_client = new SMB2Client();
_client = new SMB2Client();
// 1. 连接主机
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
if (!connected) return false;
// 连接主机
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
if (!connected) return false;
// 2. 登录
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
if (status != NTStatus.STATUS_SUCCESS) return false;
// 登录
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
if (status != NTStatus.STATUS_SUCCESS) return false;
// 3. 连接共享目录 (TreeConnect)
// 注意:SMBLibrary 必须先连接到 Share后续所有文件操作都是基于这个 Share 的相对路径
if (string.IsNullOrEmpty(_shareName)) return false;
// 连接共享目录 (TreeConnect)
// SMBLibrary 必须先连接到 Share后续所有文件操作都是基于这个 Share 的相对路径
if (string.IsNullOrEmpty(_shareName)) return false;
_fileStore = _client.TreeConnect(_shareName, out status);
return status == NTStatus.STATUS_SUCCESS;
}
catch (Exception)
{
return false;
}
_fileStore = _client.TreeConnect(_shareName, out status);
return status == NTStatus.STATUS_SUCCESS;
}
/// <summary>
@@ -75,9 +67,9 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
/// 传入要列出的文件夹实体。
/// 如果传入 null则默认列出 MediaFolder 配置的根目录。
/// </param>
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
{
var result = new List<FileCacheEntity>();
var result = new List<FilesIndexItem>();
if (_fileStore == null) return result;
string smbPath = GetPathRelativeToShare(parentFolder);
@@ -88,7 +80,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
if (statusRet != NTStatus.STATUS_SUCCESS) return result;
// 确保 parentUriString 总是以 / 结尾,方便后续拼接
string parentUriString = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri;
List<QueryDirectoryFileInformation> fileInfo;
@@ -97,7 +88,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
{
statusRet = _fileStore.QueryDirectory(out fileInfo, handle, "*", FileInformationClass.FileDirectoryInformation);
// 【安全检查】如果查询失败或者没有更多文件fileInfo 可能是 null直接跳出
// 如果查询失败或者没有更多文件fileInfo 可能是 null直接跳出
if (statusRet != NTStatus.STATUS_SUCCESS && statusRet != NTStatus.STATUS_NO_MORE_FILES)
{
break;
@@ -110,35 +101,32 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
{
if (item.FileName == "." || item.FileName == "..") continue;
// ==================================================
// ★ 修正后的 URI 构建逻辑
// ==================================================
// 过滤隐藏文件和系统文件
if ((item.FileAttributes & SMBLibrary.FileAttributes.Hidden) == SMBLibrary.FileAttributes.Hidden ||
(item.FileAttributes & SMBLibrary.FileAttributes.System) == SMBLibrary.FileAttributes.System)
{
continue;
}
bool isDir = (item.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory;
// 后缀名过滤
if (!isDir)
{
string extension = Path.GetExtension(item.FileName);
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
}
// 方法 A (推荐): 使用 Uri 构造函数自动合并
// 1. 确保 Base Uri 以 / 结尾 (否则 "folder" + "file" 会变成 "file" 替换掉 "folder")
if (!parentUriString.EndsWith("/")) parentUriString += "/";
var baseUri = new Uri(parentUriString);
// 2. 直接利用 Uri 的构造函数处理相对路径
// new Uri(baseUri, "filename") 会自动处理编码和斜杠
// 注意:如果 item.FileName 包含特殊字符Uri 类会自动帮我们编码
var newUri = new Uri(baseUri, item.FileName);
// 如果你还是想用 UriBuilder (手动控制更强),请用下面这行代替上面:
/*
var builder = new UriBuilder(baseUri);
// 关键:先 Unescape 解码,变回原始字符串,再拼接,最后赋值给 builder 让它重新编码
string cleanBasePath = Uri.UnescapeDataString(baseUri.AbsolutePath);
builder.Path = Path.Combine(cleanBasePath, item.FileName).Replace("\\", "/");
var newUri = builder.Uri;
*/
result.Add(new FileCacheEntity
result.Add(new FilesIndexItem
{
MediaFolderId = _config.Id,
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri, // 保持原始父级 URI (不带末尾斜杠的)
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri,
Uri = newUri.AbsoluteUri, // 使用修正后的 URI
Uri = newUri.AbsoluteUri,
FileName = item.FileName,
IsDirectory = (item.FileAttributes & SMBLibrary.FileAttributes.Directory) == SMBLibrary.FileAttributes.Directory,
@@ -160,11 +148,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
/// 打开文件流
/// </summary>
/// <param name="file">只需要传入文件实体即可</param>
public async Task<Stream?> OpenReadAsync(FileCacheEntity file)
public async Task<Stream?> OpenReadAsync(FilesIndexItem file)
{
if (_fileStore == null || file == null) return null;
// ★ 核心简化:直接把对象扔进去,获取路径
string smbPath = GetPathRelativeToShare(file);
var ret = _fileStore.CreateFile(out object handle, out FileStatus status, smbPath,
@@ -187,10 +174,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
_client?.Disconnect();
}
// =========================================================
// ★ 私有魔法方法:处理所有令人头大的路径逻辑
// =========================================================
private string GetPathRelativeToShare(FileCacheEntity? entity)
private string GetPathRelativeToShare(FilesIndexItem? entity)
{
Uri targetUri;
@@ -203,29 +187,17 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
targetUri = new Uri(entity.Uri);
}
// 1. 获取绝对路径
// ★★★ 关键修正:必须解码!把 %20 变回空格 ★★★
// targetUri.AbsolutePath -> "/Share/My%20Music/Song.mp3"
// Uri.UnescapeDataString -> "/Share/My Music/Song.mp3"
string absolutePath = Uri.UnescapeDataString(targetUri.AbsolutePath);
// 2. 移除 ShareName 部分
// 确保移除开头的 /
string cleanPath = absolutePath.TrimStart('/');
// 找到 ShareName 后的第一个斜杠
int slashIndex = cleanPath.IndexOf('/');
if (slashIndex == -1)
{
// 如果没有斜杠,说明就是 Share 根目录
return string.Empty;
}
// 截取 Share 之后的部分
string relativePath = cleanPath.Substring(slashIndex + 1);
// 3. 转换为 Windows 风格的反斜杠 (SMB 协议要求)
return relativePath.Replace("/", "\\");
}
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Models;
using System;
using System.Collections.Generic;
using System.IO;
@@ -18,9 +19,9 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
{
_config = config ?? throw new ArgumentNullException(nameof(config));
// 1. 构建 BaseAddress (只包含 http://host:port/)
// 构建 BaseAddress (只包含 http://host:port/)
// MediaFolder.GetStandardUri() 返回的是带路径的完整 URI (http://host:port/path)
// 我们需要提取出根用于初始化 WebDavClient
// 提取出根用于初始化 WebDavClient
var fullUri = _config.GetStandardUri();
// 提取 "http://host:port"
@@ -35,24 +36,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
public async Task<bool> ConnectAsync()
{
try
{
// 测试连接Propfind 请求配置的根路径
// GetStandardUri 已经包含了用户设置的路径
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
return result.IsSuccessful;
}
catch
{
return false;
}
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
return result.IsSuccessful;
}
public async Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null)
public async Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null)
{
var list = new List<FileCacheEntity>();
var list = new List<FilesIndexItem>();
// 1. 确定目标 URI
Uri targetUri;
if (parentFolder == null)
{
@@ -63,60 +54,54 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
targetUri = new Uri(parentFolder.Uri);
}
// 2. 发送请求 (使用绝对 URI)
// WebDavClient 允许传入绝对路径,它会自动处理
var result = await _client.Propfind(targetUri.AbsoluteUri);
if (result.IsSuccessful)
{
// 3. 准备父级 URI 字符串 (用于填充 Entity)
// 确保以 / 结尾,方便后续逻辑判断或数据库查询
string parentUriString = targetUri.AbsoluteUri;
if (!parentUriString.EndsWith("/")) parentUriString += "/";
// WebDAV 可能会把文件夹自己作为结果返回,我们需要过滤它
// 比较时忽略末尾斜杠
string targetPathClean = targetUri.AbsolutePath.TrimEnd('/');
foreach (var res in result.Resources)
{
// res.Uri 通常是相对路径,例如 "/dav/music/file.mp3"
// 我们需要将其转换为绝对 URI
var itemUri = new Uri(_baseAddress, res.Uri);
// 过滤掉文件夹自身
// 比较 AbsolutePath (例如 /dav/music vs /dav/music)
// 过滤掉文件夹自身
if (itemUri.AbsolutePath.TrimEnd('/') == targetPathClean) continue;
// 获取文件名 (解码)
// res.DisplayName 有时候是空的,这时候需要从 Uri 解析
string name = res.DisplayName;
string? name = res.DisplayName;
if (string.IsNullOrEmpty(name))
{
// 取最后一段,忽略末尾斜杠
name = itemUri.AbsolutePath.TrimEnd('/').Split('/').Last();
name = System.Net.WebUtility.UrlDecode(name);
}
if (string.IsNullOrEmpty(name)) continue;
list.Add(new FileCacheEntity
if (name.StartsWith(".")) continue;
bool isDir = res.IsCollection;
if (!isDir)
{
string extension = System.IO.Path.GetExtension(name);
// 如果后缀为空或不在白名单,跳过
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
}
list.Add(new FilesIndexItem
{
MediaFolderId = _config.Id,
// 记录父级 URI (保持传入时的形式,或者统一标准)
// 注意:对于 WebDAVParentUri 最好不带末尾斜杠,除非是根
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri,
// ★ 存储完整的 http://... 标准 URI
Uri = itemUri.AbsoluteUri,
FileName = name,
IsDirectory = res.IsCollection,
// WebDAV 通常能提供这些信息
FileSize = res.ContentLength ?? 0,
LastModified = res.LastModifiedDate
LastModified = res.LastModifiedDate ?? DateTime.MinValue,
});
}
}
@@ -124,7 +109,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
return list;
}
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
{
if (entity == null) return null;

View File

@@ -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();

View File

@@ -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()

View File

@@ -11,7 +11,9 @@ using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Settings;
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
using BetterLyrics.WinUI3.Services.DiscordService;
using BetterLyrics.WinUI3.Services.LastFMService;
using BetterLyrics.WinUI3.Services.LyricsSearchService;
using BetterLyrics.WinUI3.Services.PlayHistoryService;
using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Services.TranslationService;
using BetterLyrics.WinUI3.Services.TransliterationService;
@@ -24,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;
@@ -36,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>>,
@@ -48,33 +51,44 @@ 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;
private readonly ITransliterationService _transliterationService;
private readonly ISettingsService _settingsService;
private readonly IDiscordService _discordService;
private readonly ILogger<MediaSessionsService> _logger;
private readonly IPlayHistoryService _playHistoryService;
private readonly ILastFMService _lastFMService;
private readonly ILogger<GSMTCService> _logger;
private double _lxMusicPositionSeconds = 0;
private byte[]? _lxMusicAlbumArtBytes = null;
private readonly DispatcherQueueTimer? _onMediaPropsChangedTimer;
private readonly DispatcherTimer _scrobbleTimer;
[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,
IDiscordService discordService,
ITranslationService libreTranslateService,
ITransliterationService transliterationService,
ILogger<MediaSessionsService> logger)
IPlayHistoryService playHistoryService,
ILastFMService lastFMService,
ILogger<GSMTCService> logger)
{
_settingsService = settingsService;
_albumArtSearchService = albumArtSearchService;
@@ -82,8 +96,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_translationService = libreTranslateService;
_transliterationService = transliterationService;
_discordService = discordService;
_playHistoryService = playHistoryService;
_lastFMService = lastFMService;
_logger = logger;
_scrobbleTimer = new();
_scrobbleTimer.Interval = TimeSpan.FromSeconds(1);
_scrobbleTimer.Tick += ScrobbleTimer_Tick;
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
_settingsService.AppSettings.MediaSourceProvidersInfo.ItemPropertyChanged += MediaSourceProvidersInfo_ItemPropertyChanged;
@@ -96,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();
@@ -133,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)
@@ -144,7 +198,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id);
if (_settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened)
{
if (PlayerIDHelper.IsBetterLyrics(found?.Provider))
if (PlayerIdHelper.IsBetterLyrics(found?.Provider))
{
return found?.IsEnabled ?? true;
}
@@ -166,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;
@@ -173,182 +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, "");
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();
@@ -361,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()
@@ -458,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()
@@ -523,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)
@@ -534,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);
}
@@ -570,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)
@@ -633,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));
}
}
@@ -643,7 +659,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (message.PropertyName == nameof(MediaSourceProviderInfo.IsEnabled))
{
MediaManager_OnFocusedSessionChanged(null);
OnDesiredSessionChanged();
}
}
else if (message.Sender is TranslationSettings)
@@ -673,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)

View File

@@ -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; }

View File

@@ -279,7 +279,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
{
int maxScore = 0;
FileCacheEntity? bestFileEntity = null;
FilesIndexItem? bestFileEntity = null;
MediaFolder? bestFolderConfig = null;
var lyricsSearchResult = new LyricsSearchResult();
@@ -345,7 +345,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
FileCacheEntity? bestFile = null;
FilesIndexItem? bestFile = null;
int maxScore = 0;
foreach (var item in allFiles)
@@ -558,11 +558,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
ISearchResult? result;
if (songInfo.SongId != null && searcher == Searchers.Netease && PlayerIDHelper.IsNeteaseFamily(songInfo.PlayerId))
if (songInfo.SongId != null && searcher == Searchers.Netease && PlayerIdHelper.IsNeteaseFamily(songInfo.PlayerId))
{
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId);
}
else if (songInfo.SongId != null && searcher == Searchers.QQMusic && songInfo.PlayerId == Constants.PlayerID.QQMusic)
else if (songInfo.SongId != null && searcher == Searchers.QQMusic && songInfo.PlayerId == Constants.PlayerId.QQMusic)
{
result = new QQMusicSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId, "");
}

View File

@@ -0,0 +1,25 @@
using BetterLyrics.WinUI3.Models;
using BetterLyrics.WinUI3.Models.Stats;
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.PlayHistoryService
{
public interface IPlayHistoryService
{
Task AddLogAsync(PlayHistoryItem item);
Task<List<PlayHistoryItem>> GetRecentLogsAsync(int limit = 50);
Task<List<PlayHistoryItem>> GetLogsByDateRangeAsync(DateTime start, DateTime end);
Task<List<SongPlayCount>> GetTopSongsAsync(DateTime start, DateTime end, int limit = 10);
Task<List<ArtistPlayCount>> GetTopArtistsAsync(DateTime start, DateTime end, int limit = 10);
Task<TimeSpan> GetTotalListeningDurationAsync(DateTime start, DateTime end);
Task<List<PlayerStats>> GetPlayerDistributionAsync(DateTime start, DateTime end);
Task DeleteLogAsync(int id);
Task ClearHistoryAsync();
Task GenerateTestDataAsync(int count = 100);
}
}

View File

@@ -0,0 +1,267 @@
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;
using System.Linq;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.Services.PlayHistoryService
{
public class PlayHistoryService : IPlayHistoryService
{
private readonly IDbContextFactory<PlayHistoryDbContext> _contextFactory;
public PlayHistoryService(IDbContextFactory<PlayHistoryDbContext> contextFactory)
{
_contextFactory = contextFactory;
}
public async Task AddLogAsync(PlayHistoryItem item)
{
using var context = await _contextFactory.CreateDbContextAsync();
// 确保 UTC
if (item.StartedAt.Kind != DateTimeKind.Utc)
{
item.StartedAt = item.StartedAt.ToUniversalTime();
}
context.PlayHistory.Add(item);
await context.SaveChangesAsync();
}
public async Task<List<PlayHistoryItem>> GetRecentLogsAsync(int limit = 50)
{
using var context = await _contextFactory.CreateDbContextAsync();
return await context.PlayHistory
.AsNoTracking() // 读操作,不需要追踪状态,提升性能
.OrderByDescending(x => x.StartedAt)
.Take(limit)
.ToListAsync();
}
public async Task<List<PlayHistoryItem>> GetLogsByDateRangeAsync(DateTime start, DateTime end)
{
using var context = await _contextFactory.CreateDbContextAsync();
return await context.PlayHistory
.AsNoTracking()
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
.ToListAsync();
}
public async Task<List<SongPlayCount>> GetTopSongsAsync(DateTime start, DateTime end, int limit = 10)
{
using var context = await _contextFactory.CreateDbContextAsync();
// EF Core 会自动将这个 LINQ 翻译成高效的 GROUP BY SQL
return await context.PlayHistory
.AsNoTracking()
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
.GroupBy(x => new { x.Title, x.Artist }) // 组合分组
.Select(g => new SongPlayCount
{
Title = g.Key.Title,
Artist = g.Key.Artist,
PlayCount = g.Count()
})
.OrderByDescending(x => x.PlayCount)
.Take(limit)
.ToListAsync();
}
public async Task<List<ArtistPlayCount>> GetTopArtistsAsync(DateTime start, DateTime end, int limit = 10)
{
using var context = await _contextFactory.CreateDbContextAsync();
return await context.PlayHistory
.AsNoTracking()
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
.GroupBy(x => x.Artist)
.Select(g => new ArtistPlayCount
{
Artist = g.Key,
PlayCount = g.Count(),
// 注意SQLite 存储 double 精度,这里求和后转秒
TotalDurationSeconds = g.Sum(x => x.DurationPlayedMs) / 1000.0
})
.OrderByDescending(x => x.PlayCount)
.Take(limit)
.ToListAsync();
}
public async Task<TimeSpan> GetTotalListeningDurationAsync(DateTime start, DateTime end)
{
using var context = await _contextFactory.CreateDbContextAsync();
var totalMs = await context.PlayHistory
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
.SumAsync(x => Math.Min(x.DurationPlayedMs, x.TotalDurationMs)); // 防止超过歌曲本身时长
return TimeSpan.FromMilliseconds(totalMs);
}
public async Task<List<PlayerStats>> GetPlayerDistributionAsync(DateTime start, DateTime end)
{
using var context = await _contextFactory.CreateDbContextAsync();
return await context.PlayHistory
.AsNoTracking()
.Where(x => x.StartedAt >= start && x.StartedAt <= end)
.GroupBy(x => x.PlayerId)
.Select(g => new PlayerStats
{
PlayerId = g.Key,
Count = g.Count()
})
.OrderByDescending(x => x.Count)
.ToListAsync();
}
public async Task DeleteLogAsync(int id)
{
using var context = await _contextFactory.CreateDbContextAsync();
// EF Core 删除需要先查询,或者使用 ExecuteDeleteAsync (EF Core 7+)
// 写法 1 (传统):
// var item = await context.PlayHistory.FindAsync(id);
// if (item != null) { context.PlayHistory.Remove(item); await context.SaveChangesAsync(); }
// 写法 2 (EF Core 7.0+ 高效写法,直接生成 DELETE SQL):
await context.PlayHistory
.Where(x => x.Id == id)
.ExecuteDeleteAsync();
}
public async Task ClearHistoryAsync()
{
using var context = await _contextFactory.CreateDbContextAsync();
// 高效清空表
await context.PlayHistory.ExecuteDeleteAsync();
}
public async Task GenerateTestDataAsync(int count = 100)
{
// 这里的逻辑稍微重构了一下,使用批量插入提升性能
var random = new Random();
var presetSongs = new List<(string Title, string Artist, string Album)>
{
("Anti-Hero", "Taylor Swift", "Midnights"),
("Cruel Summer", "Taylor Swift", "Lover"),
("Blank Space", "Taylor Swift", "1989"),
("As It Was", "Harry Styles", "Harry's House"),
("Late Night Talking", "Harry Styles", "Harry's House"),
("Die For You", "The Weeknd", "Starboy"),
("Blinding Lights", "The Weeknd", "After Hours"),
("Starboy", "The Weeknd", "Starboy"),
("Shape of You", "Ed Sheeran", "Divide"),
("Bad Guy", "Billie Eilish", "When We All Fall Asleep, Where Do We Go?"),
("Flowers", "Miley Cyrus", "Endless Summer Vacation"),
("Stay", "The Kid LAROI & Justin Bieber", "F*ck Love 3: Over You"),
("七里香", "周杰伦", "七里香"),
("晴天", "周杰伦", "叶惠美"),
("一路向北", "周杰伦", "11月的肖邦"),
("告白气球", "周杰伦", "周杰伦的床边故事"),
("十年", "陈奕迅", "黑·白·灰"),
("富士山下", "陈奕迅", "What's Going On...?"),
("孤勇者", "陈奕迅", "孤勇者"),
("修炼爱情", "林俊杰", "因你而在"),
("江南", "林俊杰", "第二天堂"),
("光年之外", "G.E.M. 邓紫棋", "摩天动物园"),
("泡沫", "G.E.M. 邓紫棋", "Xposed"),
("因为爱情", "王菲 & 陈奕迅", "Stranger Under My Skin"),
("红豆", "王菲", "唱游"),
("Bohemian Rhapsody", "Queen", "A Night at the Opera"),
("Don't Stop Me Now", "Queen", "Jazz"),
("Numb", "Linkin Park", "Meteora"),
("In the End", "Linkin Park", "Hybrid Theory"),
("Yellow", "Coldplay", "Parachutes"),
("Viva La Vida", "Coldplay", "Viva La Vida"),
("Smells Like Teen Spirit", "Nirvana", "Nevermind"),
("Hotel California", "Eagles", "Hotel California"),
("Lemon", "米津玄師", "Lemon"),
("Kick Back", "米津玄師", "KICK BACK"),
("アイドル", "YOASOBI", "アイドル"),
("夜に駆ける", "YOASOBI", "THE BOOK"),
("First Love", "宇多田ヒカル", "First Love"),
("Dynamite", "BTS", "BE"),
("Butter", "BTS", "Butter"),
("How You Like That", "BLACKPINK", "The Album"),
("Ditto", "NewJeans", "OMG"),
("Get Lucky", "Daft Punk", "Random Access Memories"),
("The Nights", "Avicii", "The Days / Nights"),
("Summer", "Calvin Harris", "Motion"),
};
var playerIds = new[]
{
PlayerId.Spotify, PlayerId.Spotify, PlayerId.Spotify,
PlayerId.MusicBee, PlayerId.MusicBee,
PlayerId.QQMusic,
PlayerId.NetEaseCloudMusic,
PlayerId.AppleMusic,
};
var batchList = new List<PlayHistoryItem>();
// 我们尝试生成 count 条有效数据
// 为了防止死循环,加个硬上限
int attempts = 0;
while (batchList.Count < count && attempts < count * 5)
{
attempts++;
var song = presetSongs[random.Next(presetSongs.Count)];
var playerId = playerIds[random.Next(playerIds.Length)];
var daysBack = random.Next(0, 365);
var hoursBack = random.Next(0, 24);
var minutesBack = random.Next(0, 60);
var secondsBack = random.Next(0, 60);
var startedAt = DateTime.UtcNow // 直接用 UTC
.AddDays(-daysBack)
.AddHours(-hoursBack)
.AddMinutes(-minutesBack)
.AddSeconds(-secondsBack);
var totalDurationMs = random.Next(180, 300) * 1000.0;
double playedRatio;
double roll = random.NextDouble();
if (roll > 0.3) playedRatio = 0.9 + (random.NextDouble() * 0.1);
else if (roll > 0.1) playedRatio = 0.3 + (random.NextDouble() * 0.5);
else playedRatio = 0.05 + (random.NextDouble() * 0.25);
var playedDurationMs = totalDurationMs * playedRatio;
// 只有听了一半以上的才算作记录
if (playedDurationMs >= (totalDurationMs / 2))
{
batchList.Add(new PlayHistoryItem
{
Title = song.Title,
Artist = song.Artist,
Album = song.Album,
PlayerId = playerId,
StartedAt = startedAt,
TotalDurationMs = totalDurationMs,
DurationPlayedMs = playedDurationMs
});
}
}
if (batchList.Count > 0)
{
using var context = await _contextFactory.CreateDbContextAsync();
await context.PlayHistory.AddRangeAsync(batchList);
await context.SaveChangesAsync();
}
}
}
}

View File

@@ -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);
}
}

View File

@@ -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));
}
}
}

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>الوضع المثبت (Docked)</value>
</data>
<data name="Error" xml:space="preserve">
<value>خطأ</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>تم التصدير بنجاح</value>
</data>
@@ -166,28 +169,31 @@
<value>تعذر الاتصال بخادم موسيقى LX، يرجى الانتقال إلى الإعدادات - مصدر التشغيل - LX Music - خادم موسيقى LX للتحقق مما إذا تم إدخال الرابط بشكل صحيح</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>تنظيف ذاكرة التخزين المؤقت...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>فشل الاتصال</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>الاتصال جارِ...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>جلب قائمة الملفات...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>جاري التحليل...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>التحضير لتنظيف ذاكرة التخزين المؤقت...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>جاهز</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>تم اكتشاف مسار الدليل الجذر. قد يحتوي فهرس القرص الكامل على عدد كبير من الملفات غير الوسائط ويتسبب في استغراق الفحص وقتاً طويلاً جداً. يوصى بتحديد دليل فرعي محدد.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>في انتظار المسح...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>وضع ملء الشاشة</value>
@@ -382,16 +388,16 @@
<value>عرض مقسم</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>آخر مزامنة للوقت</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>المجلد المحلي</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>الاسم</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>مزامنة الآن</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>إضافة إلى قائمة التشغيل</value>
@@ -403,11 +409,17 @@
<value>العنصر التالي</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>إضافة إلى قائمة الانتظار</value>
<value>إضافة إلى قائمة انتظار اللعب</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>كل الموسيقى</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>مزامنة مكتبة الوسائط جارية...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>توجد مشكلة في مزامنة مكتبة الوسائط</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>مسح قائمة الانتظار</value>
</data>
@@ -451,7 +463,7 @@
<value>لم يتم العثور على أغاني في مكتبة الوسائط</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>المجلدات</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>استيراد من ملف</value>
@@ -466,7 +478,7 @@
<value>قائمة الانتظار فارغة</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>قائمة التشغيل</value>
<value>قوائم التشغيل</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>تكرار القائمة</value>
@@ -511,22 +523,22 @@
<value>مكتبة الموسيقى - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>تردد المزامنة التلقائية</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>مطلقًا</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>كل يوم</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>كل 15 دقيقة</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>كُل ساعة</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>كُل 6 ساعات</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>وضع الشاشة الضيقة</value>
@@ -544,13 +556,13 @@
<value>سياسة الخصوصية</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>تصفح</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>الاسم</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>سيؤدي تركه فارغاً إلى إنشاء اسم افتراضي تلقائياً.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>كلمة المرور</value>
@@ -559,10 +571,10 @@
<value>المسار</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>تعذر العثور على مسار المجلد المحدد</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>المسار مطلوب</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>المنفذ</value>
@@ -691,7 +703,7 @@
<value>كمية الضبابية (Blur)</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>يشمل ملفات السجل وذاكرة التخزين المؤقت للكلمات عبر الشبكة</value>
<value>يتضمن ملفات السجلات، وذاكرة التخزين المؤقت للكلمات عبر الإنترنت</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>ذاكرة التخزين المؤقت (Cache)</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>الخروج من البرنامج عند إغلاق نافذة الكلمات</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>تصدير تاريخ اللعب</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>تصدير الإعدادات</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>بدء التشغيل</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>الإحصائيات</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>إيقاف التشغيل عند إغلاق نافذة مكتبة الموسيقى</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>ابدأ</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>هذا الشهر</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>هذا الربع</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>هذا الأسبوع</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>هذا العام</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>أعلى المسارات</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>خروج</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Angedockter Modus</value>
</data>
<data name="Error" xml:space="preserve">
<value>Fehler</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Export erfolgreich</value>
</data>
@@ -166,28 +169,31 @@
<value>Verbindung zum LX Music Server fehlgeschlagen. Bitte prüfen Sie unter Einstellungen - Wiedergabequelle - LX Music - LX Music Server, ob der Link korrekt ist</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Cache wird gereinigt...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Verbindung fehlgeschlagen</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Verbinde...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Lade Dateiliste...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Analysiere...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Bereinigung des Caches vorbereiten...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Bereit</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Der Pfad zum Stammverzeichnis wurde erkannt. Ein vollständiger Festplattenindex kann eine große Anzahl von Nicht-Mediendateien enthalten und dazu führen, dass die Suche zu lange dauert. Es wird empfohlen, ein bestimmtes Unterverzeichnis anzugeben.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Auf Scans warten...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Vollbildmodus</value>
@@ -382,16 +388,16 @@
<value>Geteilte Ansicht</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Letzte Sync-Zeit</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Lokaler Ordner</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Name</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Jetzt synchronisieren</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Zur Wiedergabeliste hinzufügen</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Alle Musikstücke</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Synchronisierung der Medienbibliothek läuft...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Es gibt ein Problem mit der Synchronisierung der Medienbibliothek</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Warteschlange leeren</value>
</data>
@@ -451,7 +463,7 @@
<value>Keine Songs in der Medienbibliothek gefunden</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Ordner</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Aus Datei importieren</value>
@@ -466,7 +478,7 @@
<value>Wiedergabewarteschlange ist leer</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Wiedergabeliste</value>
<value>Wiedergabelisten</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Liste wiederholen</value>
@@ -511,22 +523,22 @@
<value>Musikgalerie - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Auto-Sync-Frequenz</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Niemals</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Täglich</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Alle 15 Minuten</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Stündlich</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Alle 6 Stunden</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Schmaler Modus</value>
@@ -544,13 +556,13 @@
<value>Datenschutzrichtlinie</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Durchsuchen Sie</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Name</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Wenn Sie das Feld leer lassen, wird automatisch ein Standardname generiert.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Passwort</value>
@@ -559,10 +571,10 @@
<value>Pfad</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Der angegebene Ordnerpfad konnte nicht gefunden werden</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Pfad ist erforderlich</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Port</value>
@@ -691,7 +703,7 @@
<value>Unschärfe-Stärke</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Einschließlich Protokolldateien, Online-Songtext-Cache</value>
<value>Enthält Protokolldateien, Online-Liedtext-Cache</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>App beenden, wenn Songtext-Fenster geschlossen wird</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Spielverlauf exportieren</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Einstellungen exportieren</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Einstellungsmanager</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Geschichte spielen</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Online Share Hub durchsuchen</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Start</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Statistik</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Wiedergabe stoppen, wenn Musikgalerie-Fenster geschlossen wird</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Start</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>Dieser Monat</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>Dieses Quartal</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Diese Woche</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Dieses Jahr</value>
</data>
<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">
<value>Top Künstler</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Top Tracks</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Oberste Quelle</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Beenden</value>
</data>

View File

@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Docked Mode</value>
</data>
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Export successful</value>
</data>
@@ -166,7 +169,7 @@
<value>Unable to connect to LX Music Server. Please go to Settings - Playback Source - LX Music - LX Music Server to check if the link is entered correctly</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value>Cleaning cache...</value>
<value>Clearing cache...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value>Connection failed</value>
@@ -181,7 +184,10 @@
<value>Parsing...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value>Preparing to clean cache...</value>
<value>Preparing to clear cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Ready</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value>The root directory path has been detected. A full disk index may contain a large number of non-media files and cause the scan to take too long. It is recommended to specify a specific subdirectory.</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>All Music</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Media Library sync in progress...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>There is a problem with Media Library sync</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Clear queue</value>
</data>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Exit app when lyrics window is closed</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Export play history</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Export Settings</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Settings Manager</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Play History</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Browse Online Share Hub</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Startup</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Statistics</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Stop playback when Music Gallery window is closed</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Start</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>This Month</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>This Quarter</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>This Week</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>This Year</value>
</data>
<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">
<value>Top Artists</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Top Tracks</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Top Source</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Exit</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Modo Acoplado</value>
</data>
<data name="Error" xml:space="preserve">
<value>Error</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Exportación exitosa</value>
</data>
@@ -166,28 +169,31 @@
<value>No se puede conectar al servidor LX Music. Vaya a Configuración - Fuente de reproducción - LX Music - Servidor LX Music para verificar si el enlace es correcto</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Limpiando caché...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Conexión fallida</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Conectando...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Obteniendo lista de archivos...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Parsing...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Preparando la limpieza de caché...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Listo</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Se ha detectado la ruta del directorio raíz. Un índice de disco completo puede contener un gran número de archivos no multimedia y hacer que la exploración dure demasiado tiempo. Se recomienda especificar un subdirectorio concreto.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Esperando para escanear...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Modo Pantalla Completa</value>
@@ -382,16 +388,16 @@
<value>Vista dividida</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Última sincronización</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Carpeta local</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Nombre</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Sincronizar ahora</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Añadir a lista de reproducción</value>
@@ -403,11 +409,17 @@
<value>Siguiente elemento</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>Añadir a la cola</value>
<value>Añadir a la cola de reproducción</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Toda la música</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Sincronización de la biblioteca multimedia en curso...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Hay un problema con la sincronización de la biblioteca multimedia</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Borrar cola</value>
</data>
@@ -451,7 +463,7 @@
<value>No se encontraron canciones en la biblioteca multimedia</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Carpetas</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Importar desde archivo</value>
@@ -466,7 +478,7 @@
<value>La cola de reproducción está vacía</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Lista de reproducción</value>
<value>Listas de reproducción</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Repetir lista</value>
@@ -511,22 +523,22 @@
<value>Galería de Música - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Frecuencia de sincronización automática</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Nunca</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Todos los días</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Cada 15 minutos</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Cada hora</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Cada 6 horas</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Modo Estrecho</value>
@@ -544,13 +556,13 @@
<value>Política de privacidad</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Visite</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Nombre</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Si lo deja en blanco, se generará automáticamente un nombre por defecto.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Contraseña</value>
@@ -559,10 +571,10 @@
<value>Ruta</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>No se ha encontrado la ruta de la carpeta especificada</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>La ruta es obligatoria</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Puerto</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Salir de la app al cerrar la ventana de letras</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Exportar historial de jugadas</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Exportar configuración</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Gestor de configuración</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Historia del juego</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Explorar Centro de recursos en línea</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Inicio</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Estadísticas</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Detener reproducción al cerrar ventana de Galería de Música</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Inicio</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>Este mes</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>Este trimestre</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Esta semana</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Este año</value>
</data>
<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">
<value>Artistas principales</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Top Tracks</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Fuente superior</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Salir</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Mode Ancré</value>
</data>
<data name="Error" xml:space="preserve">
<value>Erreur</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Exportation réussie</value>
</data>
@@ -166,28 +169,31 @@
<value>Impossible de se connecter au serveur LX Music. Veuillez aller dans Paramètres - Source de lecture - LX Music - Serveur LX Music pour vérifier si le lien est correct</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Nettoyage du cache...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Échec de la connexion</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Connexion en cours...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Récupération de la liste des fichiers...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Parsing...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Préparation du nettoyage du cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Prêt</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Le chemin du répertoire racine a été détecté. Un index de disque complet peut contenir un grand nombre de fichiers non multimédias et faire durer l'analyse trop longtemps. Il est recommandé de spécifier un sous-répertoire spécifique.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>En attente d'analyse...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Mode Plein écran</value>
@@ -382,16 +388,16 @@
<value>Vue scindée</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Date de la dernière synchronisation</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Dossier local</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Nom</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Synchroniser maintenant</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Ajouter à la liste de lecture</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Toute la musique</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Synchronisation de la médiathèque en cours...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Il y a un problème avec la synchronisation de la médiathèque</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Vider la file d'attente</value>
</data>
@@ -451,7 +463,7 @@
<value>Aucune chanson trouvée dans la bibliothèque multimédia</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Dossiers</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Importer depuis un fichier</value>
@@ -466,7 +478,7 @@
<value>La file d'attente est vide</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Liste de lecture</value>
<value>Listes de lecture</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Boucle de liste</value>
@@ -511,22 +523,22 @@
<value>Galerie de musique - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Fréquence de synchronisation automatique</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Jamais</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Tous les jours</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Toutes les 15 minutes</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Toutes les heures</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Toutes les 6 heures</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Mode Étroit</value>
@@ -544,13 +556,13 @@
<value>Politique de confidentialité</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Parcourir</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Nom</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Si vous laissez le champ vide, un nom par défaut sera automatiquement généré.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Mot de passe</value>
@@ -559,10 +571,10 @@
<value>Chemin</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Le chemin d'accès au dossier spécifié n'a pas été trouvé</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Le chemin d'accès est requis</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Port</value>
@@ -691,7 +703,7 @@
<value>Quantité de flou</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Comprend les fichiers journaux, le cache des paroles en ligne</value>
<value>Inclut les fichiers journaux, le cache des paroles en ligne</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Quitter l'app à la fermeture de la fenêtre des paroles</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Exporter l'historique des jeux</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Exporter les paramètres</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Gestionnaire de paramètres</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Histoire du jeu</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Parcourir le Hub de partage en ligne</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Démarrage</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Statistiques</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Arrêter la lecture à la fermeture de la Galerie</value>
</data>
@@ -1446,6 +1467,72 @@
<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="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.Content" xml:space="preserve">
<value>Ce trimestre</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Cette semaine</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Cette année</value>
</data>
<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">
<value>Artistes de haut niveau</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Top Tracks</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Source supérieure</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Quitter</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>डॉक्ड मोड</value>
</data>
<data name="Error" xml:space="preserve">
<value>त्रुटि</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>निर्यात सफल रहा</value>
</data>
@@ -166,28 +169,31 @@
<value>LX Music सर्वर से कनेक्ट नहीं हो सकता, कृपया सेटिंग्स - प्लेबैक स्रोत - LX Music - LX Music सर्वर पर जाएं और जांचें कि लिंक सही तरीके से दर्ज किया गया है या नहीं</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>कैश साफ़ हो रहा है...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>कनेक्शन विफल रहा</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>कनेक्टिंग ...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>फ़ाइल लिस्ट लाई जा रही है...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>पार्सिंग...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>कैश साफ़ करने की तैयारी...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>तैयार</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>रूट डायरेक्टरी पाथ का पता चल गया है। एक फुल डिस्क इंडेक्स में बड़ी संख्या में नॉन-मीडिया फ़ाइलें हो सकती हैं और स्कैन में बहुत ज़्यादा समय लग सकता है। एक खास सबडायरेक्टरी बताने की सलाह दी जाती है।</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>स्कैन करने की तैयारी...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>फुलस्क्रीन मोड</value>
@@ -382,16 +388,16 @@
<value>विभाजित दृश्य</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>अंतिम सिंक समय</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>स्थानीय फोल्डर</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>नाम</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>अभी सिंक्रनाइज़ करें</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>प्लेलिस्ट में जोड़ें</value>
@@ -403,11 +409,17 @@
<value>अगला आइटम</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>प्लेइंग कतार में जोड़ें</value>
<value>प्लेइंग क्यू में जोड़ें</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>सभी गाने</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>मीडिया लाइब्रेरी सिंक चल रहा है...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>मीडिया लाइब्रेरी सिंक में कोई समस्या है</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>प्लेइंग कतार साफ़ करें</value>
</data>
@@ -451,13 +463,13 @@
<value>मीडिया लाइब्रेरी में कोई गाना नहीं मिला</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>फ़ोल्डर्स</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>फ़ाइल से आयात करें</value>
</data>
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
<value>प्लेलिस्ट बनां</value>
<value>प्लेलिस्ट बनायें</value>
</data>
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
<value>प्लेइंग कतार</value>
@@ -511,22 +523,22 @@
<value>संगीत लाइब्रेरी - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>ऑटो-सिंक आवृत्ति</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>कभी नहीं</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>प्रतिदिन</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>हर 15 मिनट</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>हर घंटे</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>हर ६ घंटे</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>संकीर्ण मोड</value>
@@ -544,13 +556,13 @@
<value>गोपनीयता नीति</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>ब्राउज करें</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>नाम</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>इसे खाली छोड़ने से अपने आप एक डिफ़ॉल्ट नाम बन जाएगा।</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>पासवर्ड</value>
@@ -559,10 +571,10 @@
<value>पथ</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>बताया गया फ़ोल्डर पाथ नहीं मिला</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>पथ आवश्यक है</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>पोर्ट</value>
@@ -691,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>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>बोल विंडो बंद होने पर बाहर निकलें</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>प्ले इतिहास निर्यात करें</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>सेटिंग्स निर्यात करें</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>स्टार्टअप</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>आंकड़े</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>संगीत गैलरी विंडो बंद होने पर प्लेबैक रोकें</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>शुरू करें</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>इस महीने</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>इस तिमाही</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>इस सप्ताह</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>इस वर्ष</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>शीर्ष रास्ता</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>कार्यक्रम से बाहर निकलें</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Mode Dok</value>
</data>
<data name="Error" xml:space="preserve">
<value>Kesalahan</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Ekspor berhasil</value>
</data>
@@ -166,28 +169,31 @@
<value>Tidak dapat terhubung ke server LX Music, silakan buka Pengaturan - Sumber Pemutaran - LX Music - Server LX Music untuk memeriksa apakah tautan telah dimasukkan dengan benar</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Membersihkan cache...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Koneksi gagal</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Menghubungkan...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Mengambil daftar file...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Penguraian...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Bersiap untuk membersihkan cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Siap</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Jalur direktori root telah terdeteksi. Indeks disk penuh mungkin berisi sejumlah besar file non-media dan menyebabkan pemindaian memakan waktu terlalu lama. Disarankan untuk menentukan subdirektori tertentu.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Menunggu pemindaian...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Mode Layar Penuh</value>
@@ -382,16 +388,16 @@
<value>Tampilan Terpisah</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Waktu Sinkronisasi Terakhir</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Folder Lokal</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Nama</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Sinkronisasi sekarang</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Tambahkan ke daftar putar</value>
@@ -403,11 +409,17 @@
<value>Item berikutnya</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>Tambahkan ke antrean putar</value>
<value>Tambahkan ke antrian bermain</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Semua Musik</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Sinkronisasi Perpustakaan Media sedang berlangsung...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Ada masalah dengan sinkronisasi Perpustakaan Media</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Bersihkan antrean putar</value>
</data>
@@ -451,13 +463,13 @@
<value>Tidak ada lagu yang ditemukan di pustaka media</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Folder</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Impor dari file</value>
</data>
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
<value>Buat daftar putar</value>
<value>Membuat daftar putar</value>
</data>
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
<value>Antrean Putar</value>
@@ -466,7 +478,7 @@
<value>Antrean putar kosong</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Daftar Putar</value>
<value>Daftar putar</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Loop daftar</value>
@@ -511,22 +523,22 @@
<value>Galeri Musik - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Frekuensi sinkronisasi otomatis</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Tidak pernah</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Setiap hari</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Setiap 15 Menit</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Setiap Jam</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Setiap 6 Jam</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Mode Sempit</value>
@@ -544,13 +556,13 @@
<value>Kebijakan Privasi</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Jelajahi</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Nama</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Membiarkannya kosong akan secara otomatis menghasilkan nama default.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Kata Sandi</value>
@@ -559,10 +571,10 @@
<value>Jalur</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Jalur folder yang ditentukan tidak dapat ditemukan</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Diperlukan jalur</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Port</value>
@@ -691,7 +703,7 @@
<value>Tingkat Keburaman</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Termasuk file log, cache lirik jaringan</value>
<value>Termasuk file log, cache lirik online</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Keluar dari program saat jendela lirik ditutup</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Riwayat permainan ekspor</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Ekspor Pengaturan</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Manajer Pengaturan</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Riwayat Bermain</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Jelajahi Hub Berbagi Sumber Daya Online</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Startup</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Statistik</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Hentikan pemutaran saat jendela galeri musik ditutup</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Mulai</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>Bulan ini</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>Kuartal ini</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Minggu Ini</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Tahun ini</value>
</data>
<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">
<value>Artis Top</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Trek Teratas</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Sumber Teratas</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Keluar dari Program</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>ドッキングモード</value>
</data>
<data name="Error" xml:space="preserve">
<value>エラー</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>エクスポートが成功しました</value>
</data>
@@ -166,28 +169,31 @@
<value>LX Music サーバーに接続できません。「設定」-「再生ソース」-「LX Music」-「LX Music サーバー」に移動し、リンクが正しく入力されているか確認してください</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>キャッシュクリア中...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>接続に失敗しました</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>接続中...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>ファイルリストを取得しています...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>解析中...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>キャッシュクリア準備中...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>準備完了</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>ルートディレクトリが指定されました。フルディスクのインデックス作成には大量の非メディアファイルが含まれる可能性があり、スキャンに時間がかかる恐れがあります。特定のサブディレクトリを指定することをお勧めします。</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>スキャン準備中...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>全画面モード</value>
@@ -253,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>
@@ -283,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>
@@ -301,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>
@@ -310,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>
@@ -361,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>
@@ -382,16 +388,16 @@
<value>分割表示</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>最終同期時間</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>ローカルフォルダー</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>名称</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>今すぐ同期</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>プレイリストに追加</value>
@@ -406,7 +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>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>メディアライブラリーの同期に問題が発生しました</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>キューをクリア</value>
@@ -448,10 +460,10 @@
<value>年</value>
</data>
<data name="MusicGalleryPageFileNotFound.Text" xml:space="preserve">
<value>メディアライブラリに曲が見つかりません</value>
<value>メディアライブラリに曲が見つかりません</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>フォルダ</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>ファイルからインポート</value>
@@ -502,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>
@@ -511,28 +523,28 @@
<value>ミュージックギャラリー - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>自動同期の頻度</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>なし</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>毎日</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>15分間隔</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>1時間間隔</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<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>
@@ -544,13 +556,13 @@
<value>個人情報保護方針</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>参照</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>名称</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>空欄のままにすると、デフォルトの名前が自動生成されます。</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>パスワード</value>
@@ -559,10 +571,10 @@
<value>パス</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>指定されたパスが見つかりません</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>パスが必要</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>ポート</value>
@@ -601,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>
@@ -628,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>
@@ -676,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>
@@ -700,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>
@@ -718,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>
@@ -763,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>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>歌詞ウィンドウを閉じたときにアプリを終了する</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>再生履歴をエクスポート</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>設定をエクスポート</value>
</data>
@@ -859,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>
@@ -892,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>
@@ -1006,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>
@@ -1084,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>
@@ -1093,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>
@@ -1102,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>
@@ -1123,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>
@@ -1141,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>
@@ -1156,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>
@@ -1198,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>
@@ -1228,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>
@@ -1299,11 +1314,14 @@
<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>
<data name="SettingsPageShortcut.Text" xml:space="preserve">
<value>ショートカット</value>
<value>ショートカットキー</value>
</data>
<data name="SettingsPageShortcutRegFailInfo" xml:space="preserve">
<value>ホットキーの登録に失敗しました</value>
@@ -1318,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>
@@ -1333,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>スタートアップ</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>ミュージックレポート</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>ミュージックギャラリーウィンドウを閉じたときに再生を停止する</value>
</data>
@@ -1414,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>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>開始</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>今月</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>今期</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>今週</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>今年</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>よく聴く曲</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>終了</value>
</data>
@@ -1462,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>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>도킹 모드</value>
</data>
<data name="Error" xml:space="preserve">
<value>오류</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>내보내기 성공</value>
</data>
@@ -166,28 +169,31 @@
<value>LX Music 서버에 연결할 수 없습니다. 설정 - 재생 소스 - LX Music - LX Music 서버에서 링크가 올바르게 입력되었는지 확인하세요</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>캐시 정리...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>연결 실패</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>연결 중...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>파일 목록 가져오기...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>파싱...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>캐시 정리 준비 중...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>준비</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>루트 디렉터리 경로가 감지되었습니다. 전체 디스크 인덱스에 미디어가 아닌 파일이 많이 포함되어 있어 스캔 시간이 너무 오래 걸릴 수 있습니다. 특정 하위 디렉터리를 지정하는 것이 좋습니다.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>스캔 대기 중...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>전체 화면 모드</value>
@@ -382,16 +388,16 @@
<value>분할 보기</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>마지막 동기화 시간</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>로컬 폴더</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>이름</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>지금 동기화</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>재생 목록에 추가</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>모든 음악</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>미디어 라이브러리 동기화 진행 중...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>미디어 라이브러리 동기화에 문제가 있습니다.</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>재생 대기열 비우기</value>
</data>
@@ -451,13 +463,13 @@
<value>미디어 라이브러리에서 곡을 찾지 못했습니다</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>폴더</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>파일에서 가져오기</value>
</data>
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
<value>재생 목록 생성</value>
<value>재생 목록 만들기</value>
</data>
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
<value>재생 대기열</value>
@@ -511,22 +523,22 @@
<value>음악 갤러리 - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>자동 동기화 빈도</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>절대로</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>매일</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>15분마다</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>매시간</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>6시간마다</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>좁은 화면 모드</value>
@@ -544,10 +556,13 @@
<value>개인정보 처리방침</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>찾아보기</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value>이름</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>비워두면 기본 이름이 자동으로 생성됩니다.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>비밀번호</value>
@@ -556,10 +571,10 @@
<value>경로</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>지정한 폴더 경로를 찾을 수 없습니다.</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>경로 필수</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>포트</value>
@@ -843,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>가사 창을 닫을 때 앱 종료</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>플레이 기록 내보내기</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>설정 내보내기</value>
</data>
@@ -1296,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>
@@ -1371,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>시작</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>통계</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>음악 갤러리 창을 닫을 때 재생 중지</value>
</data>
@@ -1443,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>시작</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>이번 달</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>이번 분기</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>이번 주</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>올해</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>인기 트랙</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>종료</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Mod Dok</value>
</data>
<data name="Error" xml:space="preserve">
<value>Ralat</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Eksport berjaya</value>
</data>
@@ -166,28 +169,31 @@
<value>Tidak dapat menyambung ke pelayan LX Music, sila pergi ke Tetapan - Sumber Main Balik - LX Music - Pelayan LX Music untuk menyemak sama ada pautan dimasukkan dengan betul</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Membersihkan cache...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Sambungan gagal</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Menyambung...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Mengambil senarai fail...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Menghurai...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Bersedia untuk membersihkan cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Sedia</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Laluan direktori akar telah dikesan. Indeks cakera penuh mungkin mengandungi sejumlah besar fail bukan media dan menyebabkan imbasan mengambil masa terlalu lama. Adalah disyorkan untuk menentukan subdirektori tertentu.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Bersedia untuk mengimbas...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Mod Skrin Penuh</value>
@@ -382,16 +388,16 @@
<value>Paparan Pisah</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Masa Segerak Akhir</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Folder setempat</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Nama</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Segerak sekarang</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Tambah ke senarai main</value>
@@ -403,11 +409,17 @@
<value>Item seterusnya</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>Tambah ke baris gilir main</value>
<value>Tambah ke turutan lagu</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Semua Muzik</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Penyegerakan Pustaka Media sedang dijalankan...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Terdapat masalah dengan penyegerakan Pustaka Media</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Kosongkan baris gilir main</value>
</data>
@@ -451,7 +463,7 @@
<value>Tiada lagu ditemui dalam pustaka media</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Folder</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Import dari fail</value>
@@ -511,22 +523,22 @@
<value>Galeri Muzik - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Kekerapan penyegerakan automatik</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Tak pernah</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Setiap Hari</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Setiap 15 minit</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Setiap jam</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Setiap 6 Jam</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Mod Sempit</value>
@@ -544,13 +556,13 @@
<value>Dasar Privasi</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Layari</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Nama</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Membiarkan ia kosong akan menjana nama lalai secara automatik.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Kata Laluan</value>
@@ -559,10 +571,10 @@
<value>Laluan</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Laluan folder yang dinyatakan tidak dapat ditemui</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Laluan diperlukan</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Port</value>
@@ -691,7 +703,7 @@
<value>Jumlah Kabur</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Termasuk fail log, cache lirik rangkaian</value>
<value>Termasuk fail log, cache lirik dalam talian</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Keluar dari program apabila tetingkap lirik ditutup</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Eksport sejarah main</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Eksport Tetapan</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Pengurus Tetapan</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Sejarah Main</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Layari Hab Perkongsian Sumber Dalam Talian</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Permulaan</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Statistik</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Hentikan main balik apabila tetingkap galeri muzik ditutup</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Mula</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>Bulan Ini</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>Suku ini</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Minggu Ini</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Tahun ini</value>
</data>
<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">
<value>Artis Teratas</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Lagu top</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Sumber Utama</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Keluar Program</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Modo Acoplado</value>
</data>
<data name="Error" xml:space="preserve">
<value>Erro</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Exportação bem-sucedida</value>
</data>
@@ -166,28 +169,31 @@
<value>Não foi possível ligar ao servidor LX Music. Aceda a Definições - Fonte de Reprodução - LX Music - Servidor LX Music para verificar se a ligação foi introduzida corretamente</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Limpando o cache...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Conexão falhou</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Conectandochar@@0</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Buscando lista de arquivos...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Parsing...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>A preparar a limpeza da cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Pronto</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>O caminho do diretório raiz foi detectado. Um índice de disco completo pode conter um grande número de ficheiros não multimédia e fazer com que a pesquisa demore demasiado tempo. Recomenda-se a especificação de um subdiretório específico.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Aguardando verificação...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Modo de Ecrã Inteiro</value>
@@ -382,16 +388,16 @@
<value>Vista Dividida</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Última sincronização</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Pasta local</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Nome</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Sincronizar agora</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Adicionar à lista de reprodução</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Todas as Músicas</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Sincronização da biblioteca multimédia em curso...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Existe um problema com a sincronização da Biblioteca Multimédia</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Limpar fila de reprodução</value>
</data>
@@ -451,7 +463,7 @@
<value>Nenhuma música encontrada na biblioteca multimédia</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Pastas</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Importar de ficheiro</value>
@@ -466,7 +478,7 @@
<value>A fila de reprodução está vazia</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Lista de Reprodução</value>
<value>Listas de reprodução</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Ciclo da lista</value>
@@ -511,22 +523,22 @@
<value>Galeria de Música - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Frequência de sincronização automática</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>nunca</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Todos os dias</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>A cada 15 minutos</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>A cada hora</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>A cada 6 horas</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Modo Estreito</value>
@@ -544,13 +556,13 @@
<value>Política de Privacidade</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Navegar</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Nome</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Se o deixar em branco, será gerado automaticamente um nome predefinido.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Palavra-passe</value>
@@ -559,10 +571,10 @@
<value>Caminho</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Não foi possível encontrar o caminho da pasta especificada</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>O caminho é necessário</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Porta</value>
@@ -691,7 +703,7 @@
<value>Quantidade de Desfoque</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Inclui ficheiros de registo e cache de letras da rede</value>
<value>Inclui ficheiros de registo, cache de letras online</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Cache</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Sair do programa ao fechar a janela de letras</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Exportar histórico de jogos</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Exportar Definições</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Gestor de Definições</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>História do jogo</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Explorar o Centro de Partilha de Recursos Online</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Arranque</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Estatísticas</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Parar reprodução ao fechar a janela da galeria de música</value>
</data>
@@ -1446,6 +1467,72 @@
<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="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.Content" xml:space="preserve">
<value>Este trimestre</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Esta semana</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Este ano</value>
</data>
<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">
<value>Artistas de topo</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Faixas superiores</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Fonte superior</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Sair do Programa</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Закрепленный режим</value>
</data>
<data name="Error" xml:space="preserve">
<value>Ошибка</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Экспорт выполнен успешно</value>
</data>
@@ -166,28 +169,31 @@
<value>Не удалось подключиться к серверу LX Music. Перейдите в Настройки - Источник воспроизведения - LX Music - Сервер LX Music, чтобы проверить правильность ссылки</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Очистка кэша...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Сбой подключения</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Подключение...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Загрузка списка файлов...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Парсинг...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Подготовка к очистке кэша...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Готовые</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Обнаружен путь к корневому каталогу. Полный индекс диска может содержать большое количество файлов, не относящихся к мультимедиа, что приведет к слишком долгому сканированию. Рекомендуется указывать конкретный подкаталог.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Ожидание сканирования...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Полноэкранный режим</value>
@@ -382,16 +388,16 @@
<value>Разделенный вид</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Время последней синхронизации</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Локальная папка</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Имя</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Синхронизировать</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Добавить в плейлист</value>
@@ -403,11 +409,17 @@
<value>Следующим пунктом</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>Добавить в очередь</value>
<value>Добавить в очередь на воспроизведение</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Вся музыка</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Выполняется синхронизация медиатеки...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Возникла проблема с синхронизацией медиатеки</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Очистить очередь воспроизведения</value>
</data>
@@ -451,13 +463,13 @@
<value>Песни в медиатеке не найдены</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Папки</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Импорт из файла</value>
</data>
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
<value>Создать плейлист</value>
<value>Создайте плейлист</value>
</data>
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
<value>Очередь воспроизведения</value>
@@ -466,7 +478,7 @@
<value>Очередь воспроизведения пуста</value>
</data>
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
<value>Плейлист</value>
<value>Плейлисты</value>
</data>
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
<value>Зациклить список</value>
@@ -511,22 +523,22 @@
<value>Музыкальная галерея - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Частота автосинхронизации</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Никогда</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Каждый день</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>Каждые 15 минут</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Каждый час</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Каждые 6 часов</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Узкий режим</value>
@@ -544,13 +556,13 @@
<value>Политика конфиденциальности</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Просмотреть</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Имя</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Если оставить этот параметр пустым, имя по умолчанию будет сгенерировано автоматически.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Пароль</value>
@@ -559,10 +571,10 @@
<value>Путь</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Указанный путь к папке не найден</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Путь обязателен</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Порт</value>
@@ -691,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>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Выходить из программы при закрытии окна текста</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Экспорт истории игр</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Экспорт настроек</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Запуск</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Статистика</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Останавливать воспроизведение при закрытии Галереи</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>Начало</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>Этот месяц</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>Этот квартал</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>На этой неделе</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>В этом году</value>
</data>
<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">
<value>Лучшие художники</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Лучшие треки</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Верхний источник</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Выход</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>โหมดเชื่อมต่อ (Docked)</value>
</data>
<data name="Error" xml:space="preserve">
<value>ข้อผิดพลาด</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>ส่งออกสำเร็จ</value>
</data>
@@ -166,28 +169,31 @@
<value>ไม่สามารถเชื่อมต่อกับเซิร์ฟเวอร์ LX Music โปรดไปที่ การตั้งค่า - แหล่งเล่นเพลง - LX Music - เซิร์ฟเวอร์ LX Music เพื่อตรวจสอบว่าป้อนลิงก์ถูกต้องหรือไม่</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>กำลังล้างแคช...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>การเชื่อมต่อล้มเหลว</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>กำลังเชื่อมต่อ...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>กำลังดึงรายการไฟล์...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>กำลังแยกวิเคราะห์...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>กำลังเตรียมล้างแคช...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>พร้อม</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>พบเส้นทางไดเรกทอรีรากแล้ว ดัชนีของดิสก์ทั้งหมดอาจมีไฟล์ที่ไม่ใช่สื่อจำนวนมากและทำให้การสแกนใช้เวลานานเกินไป ขอแนะนำให้ระบุไดเรกทอรีย่อยเฉพาะ</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>กำลังรอสแกน...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>โหมดเต็มหน้าจอ</value>
@@ -382,16 +388,16 @@
<value>มุมมองแยก</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>เวลาซิงค์ครั้งล่าสุด</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>โฟลเดอร์ท้องถิ่น</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>ชื่อ</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>ซิงค์ตอนนี้</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>เพิ่มลงในเพลย์ลิสต์</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>เพลงทั้งหมด</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>กำลังซิงค์คลังสื่อ...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>มีปัญหาเกี่ยวกับการซิงค์ไลบรารีมีเดีย</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>ล้างคิวการเล่น</value>
</data>
@@ -451,7 +463,7 @@
<value>ไม่พบเพลงในไลบรารีสื่อ</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>โฟลเดอร์</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>นำเข้าจากไฟล์</value>
@@ -511,22 +523,22 @@
<value>คลังเพลง - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>ความถี่การซิงค์อัตโนมัติ</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>ไม่เคย</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>ทุกวัน</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>ทุก 15 นาที</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>ทุกชั่วโมง</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>ทุก 6 ชั่วโมง</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>โหมดหน้าแคบ</value>
@@ -544,13 +556,13 @@
<value>นโยบายความเป็นส่วนตัว</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>เรียกดู</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>ชื่อ</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>การเว้นว่างไว้จะสร้างชื่อเริ่มต้นโดยอัตโนมัติ</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>รหัสผ่าน</value>
@@ -559,10 +571,10 @@
<value>เส้นทาง</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>ไม่สามารถค้นหาเส้นทางโฟลเดอร์ที่ระบุได้</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>จำเป็นต้องมีเส้นทาง</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>พอร์ต</value>
@@ -691,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>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>ออกจากโปรแกรมเมื่อปิดหน้าต่างเนื้อเพลง</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>ส่งออกประวัติการเล่น</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>ส่งออกการตั้งค่า</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>การเริ่มระบบ</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>สถิติ</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>หยุดเล่นเมื่อปิดหน้าต่างคลังเพลง</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>เริ่มต้น</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>เดือนนี้</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>ไตรมาสนี้</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>สัปดาห์นี้</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>ปีนี้</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>เพลงยอดนิยม</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>ออกจากโปรแกรม</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>Chế độ neo (Docked)</value>
</data>
<data name="Error" xml:space="preserve">
<value>Lỗi</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>Xuất thành công</value>
</data>
@@ -166,28 +169,31 @@
<value>Không thể kết nối với máy chủ LX Music, vui lòng vào Cài đặt - Nguồn phát - LX Music - Máy chủ LX Music để kiểm tra xem liên kết đã được nhập chính xác chưa</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>Đang xóa bộ nhớ đệm...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>Kết nối thất bại</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>Đang kết nối...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>Đang lấy danh sách tệp...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>Đang phân tích...</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>Đang chuẩn bị xóa bộ nhớ cache...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>Sẵn sàng</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>Đường dẫn thư mục gốc đã được phát hiện. Chỉ mục đĩa đầy đủ có thể chứa một lượng lớn tệp không phải phương tiện và khiến quá trình quét mất quá nhiều thời gian. Khuyến nghị chỉ định một thư mục con cụ thể.</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>Đang chờ quét...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>Chế độ toàn màn hình</value>
@@ -382,16 +388,16 @@
<value>Chế độ xem chia tách</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>Thời gian đồng bộ hóa gần nhất</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>Thư mục cục bộ</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>Tên</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>Đồng bộ hóa ngay</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>Thêm vào danh sách phát</value>
@@ -403,11 +409,17 @@
<value>Mục tiếp theo</value>
</data>
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
<value>Thêm vào hàng đợi phát</value>
<value>Thêm vào danh sách phát</value>
</data>
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>Tất cả bài hát</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>Đang đồng bộ thư viện phương tiện...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>Có vấn đề với việc đồng bộ hóa Thư viện Phương tiện.</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>Xóa hàng đợi phát</value>
</data>
@@ -451,7 +463,7 @@
<value>Không tìm thấy bài hát nào trong thư viện phương tiện</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>Thư mục</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>Nhập từ tệp</value>
@@ -511,22 +523,22 @@
<value>Thư viện nhạc - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>Tần suất đồng bộ tự động</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>Không bao giờ</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>Hàng ngày</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>15 phút một lần</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>Mỗi giờ</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>Mỗi 6 tiếng</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>Chế độ hẹp</value>
@@ -544,13 +556,13 @@
<value>Chính sách bảo mật</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>Duyệt</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>Tên</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>Nếu để trống, hệ thống sẽ tự động tạo tên mặc định.</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>Mật khẩu</value>
@@ -559,10 +571,10 @@
<value>Đường dẫn</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>Đường dẫn thư mục đã chỉ định không thể tìm thấy.</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>Đường dẫn là bắt buộc.</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>Cổng</value>
@@ -691,7 +703,7 @@
<value>Độ mờ</value>
</data>
<data name="SettingsPageCache.Description" xml:space="preserve">
<value>Bao gồm tệp nhật ký, bộ nhớ đệm lời bài hát mạng</value>
<value>Gồm các tệp nhật ký bộ nhớ đệm lời bài hát trực tuyến.</value>
</data>
<data name="SettingsPageCache.Header" xml:space="preserve">
<value>Bộ nhớ đệm</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>Thoát chương trình khi đóng cửa sổ lời bài hát</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>Xuất lịch sử phát lại</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>Xuất cài đặt</value>
</data>
@@ -1299,6 +1314,9 @@
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
<value>Trình quản lý cài đặt</value>
</data>
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
<value>Lịch sử chơi</value>
</data>
<data name="SettingsPageShareHub.Content" xml:space="preserve">
<value>Duyệt trung tâm chia sẻ tài nguyên trực tuyến</value>
</data>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>Khởi động</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>Thống kê</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>Dừng phát khi đóng cửa sổ thư viện nhạc</value>
</data>
@@ -1446,6 +1467,72 @@
<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="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.Content" xml:space="preserve">
<value>Quý này</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>Tuần này</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>Năm nay</value>
</data>
<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">
<value>Những nghệ sĩ hàng đầu</value>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>Các bài hát hàng đầu</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<value>Nguồn hàng đầu</value>
</data>
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>Thoát chương trình</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>停靠模式</value>
</data>
<data name="Error" xml:space="preserve">
<value>错误</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>导出成功</value>
</data>
@@ -181,10 +184,13 @@
<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>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>所有音乐</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>正在同步媒体库...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>媒体库同步出现问题</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>清除播放队列</value>
</data>
@@ -550,7 +562,7 @@
<value>名称</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value>留空自动生成默认名称</value>
<value>留空自动生成一个默认名称</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>密码</value>
@@ -559,10 +571,10 @@
<value>路径</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value>找到指定的文件夹路径</value>
<value>无法找到指定的文件夹路径</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value>路径为必填项</value>
<value>路径是必需的</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>端口</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>关闭歌词窗口时退出程序</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>导出播放记录</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>导出设置</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>启动</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>音乐报告</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>关闭音乐库窗口时停止播放</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>开始</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>本月</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>本季度</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>本周</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>本年度</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>常听的曲目</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>退出程序</value>
</data>

View File

@@ -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>
@@ -159,6 +159,9 @@
<data name="DockedMode" xml:space="preserve">
<value>停靠模式</value>
</data>
<data name="Error" xml:space="preserve">
<value>錯誤</value>
</data>
<data name="ExportSettingsSuccess" xml:space="preserve">
<value>匯出成功</value>
</data>
@@ -166,28 +169,31 @@
<value>無法連線到 LX 音樂伺服器,請轉到設定 - 播放來源 - LX Music - LX 音樂伺服器以檢查是否正確輸入連結</value>
</data>
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
<value />
<value>清除快取...</value>
</data>
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
<value />
<value>連線失敗</value>
</data>
<data name="FileSystemServiceConnecting" xml:space="preserve">
<value />
<value>連線中...</value>
</data>
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
<value />
<value>擷取檔案清單...</value>
</data>
<data name="FileSystemServiceParsing" xml:space="preserve">
<value />
<value>解析中…</value>
</data>
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
<value />
<value>準備清除快取...</value>
</data>
<data name="FileSystemServiceReady" xml:space="preserve">
<value>準備就緒</value>
</data>
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
<value />
<value>已偵測到根目錄路徑。全磁碟索引可能包含大量非媒體檔案,導致掃描時間過長。建議指定特定的子目錄。</value>
</data>
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
<value />
<value>準備掃描...</value>
</data>
<data name="FullscreenMode" xml:space="preserve">
<value>全螢幕模式</value>
@@ -382,16 +388,16 @@
<value>分割檢視</value>
</data>
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
<value />
<value>上次同步時間</value>
</data>
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
<value />
<value>本機資料夾</value>
</data>
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
<value />
<value>名稱</value>
</data>
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
<value />
<value>立即同步</value>
</data>
<data name="MusicGalleryPageAddToCustomList.Text" xml:space="preserve">
<value>加入播放清單</value>
@@ -408,6 +414,12 @@
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
<value>所有音樂</value>
</data>
<data name="MusicGalleryPageDataSync.Message" xml:space="preserve">
<value>媒體庫同步中...</value>
</data>
<data name="MusicGalleryPageDataSyncError.Message" xml:space="preserve">
<value>媒體庫同步出現問題</value>
</data>
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
<value>清除播放佇列</value>
</data>
@@ -451,7 +463,7 @@
<value>未在媒體櫃內找到任何歌曲</value>
</data>
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
<value />
<value>資料夾</value>
</data>
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
<value>從檔案匯入</value>
@@ -511,22 +523,22 @@
<value>音樂庫 - BetterLyrics</value>
</data>
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
<value />
<value>自動同步頻率</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
<value />
<value>從不</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
<value />
<value>每日</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryFifteenMin.Content" xml:space="preserve">
<value />
<value>每 15 分鐘</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEveryHour.Content" xml:space="preserve">
<value />
<value>每小時</value>
</data>
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
<value />
<value>每 6 小時</value>
</data>
<data name="NarrowMode" xml:space="preserve">
<value>窄屏模式</value>
@@ -544,13 +556,13 @@
<value>隱私權政策</value>
</data>
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
<value />
<value>瀏覽</value>
</data>
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
<value />
<value>名稱</value>
</data>
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
<value />
<value>留空會自動產生預設名稱。</value>
</data>
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
<value>密碼</value>
@@ -559,10 +571,10 @@
<value>路徑</value>
</data>
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
<value />
<value>無法找到指定的資料夾路徑</value>
</data>
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
<value />
<value>需要路徑</value>
</data>
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
<value>連接埠</value>
@@ -846,6 +858,9 @@
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
<value>關閉歌詞視窗時結束程式</value>
</data>
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
<value>匯出播放記錄</value>
</data>
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
<value>匯出設定</value>
</data>
@@ -1299,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>
@@ -1374,6 +1392,9 @@
<data name="SettingsPageStartup.Text" xml:space="preserve">
<value>啟動</value>
</data>
<data name="SettingsPageStats.Content" xml:space="preserve">
<value>統計資料</value>
</data>
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
<value>關閉音樂庫視窗時停止播放</value>
</data>
@@ -1446,6 +1467,72 @@
<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="StatsDashboardControlStart.Header" xml:space="preserve">
<value>開始</value>
</data>
<data name="StatsDashboardControlThisMonth.Content" xml:space="preserve">
<value>本月</value>
</data>
<data name="StatsDashboardControlThisQuarter.Content" xml:space="preserve">
<value>本季</value>
</data>
<data name="StatsDashboardControlThisWeek.Content" xml:space="preserve">
<value>本週</value>
</data>
<data name="StatsDashboardControlThisYear.Content" xml:space="preserve">
<value>本年</value>
</data>
<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>
</data>
<data name="StatsDashboardControlTopSongs.Text" xml:space="preserve">
<value>常聽的曲目</value>
</data>
<data name="StatsDashboardControlTopSource.Text" xml:space="preserve">
<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>
<data name="SystemTrayExit.Text" xml:space="preserve">
<value>結束程式</value>
</data>

View File

@@ -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>

View File

@@ -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>

View File

@@ -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>

View File

@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.UI.Xaml.Controls;
using System;
using System.IO;
using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels
@@ -74,6 +75,19 @@ namespace BetterLyrics.WinUI3.ViewModels
}
}
[RelayCommand]
private async Task ExportPlayHistoryAsync()
{
var folder = await PickerHelper.PickSingleFolderAsync<SettingsWindow>();
if (folder != null)
{
var dest = Path.Combine(folder.Path, $"BetterLyrics_Play_History_Export_{DateTime.Now:yyyyMMdd_HHmmss}.db");
await FileHelper.CopyFileAsync(PathHelper.PlayHistoryPath, dest);
ToastHelper.ShowToast("ExportSettingsSuccess", null, InfoBarSeverity.Success);
}
}
[RelayCommand]
private void ClearCacheFiles()
{

View File

@@ -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();
}

View File

@@ -1,4 +1,5 @@
using BetterLyrics.WinUI3.Controls;
using BetterLyrics.WinUI3.Enums;
using BetterLyrics.WinUI3.Helper;
using BetterLyrics.WinUI3.Hooks;
using BetterLyrics.WinUI3.Models;
@@ -9,6 +10,7 @@ using BetterLyrics.WinUI3.Services.SettingsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI.Animations;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System;
@@ -51,23 +53,25 @@ namespace BetterLyrics.WinUI3.ViewModels
public void SyncFolder(MediaFolder folder)
{
if (folder.IsIndexing) return;
if (folder.IsProcessing) return;
_ = Task.Run(async () => await _fileSystemService.ScanMediaFolderAsync(folder, CancellationToken.None));
}
[RelayCommand]
private async Task AddMediaSourceAsync(string protocolType)
private async Task AddMediaSourceAsync(string fileSourceTypeName)
{
FileSourceType fileSourceType = Enum.Parse<FileSourceType>(fileSourceTypeName);
var dialog = new ContentDialog
{
XamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot,
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
Title = protocolType == "Local" ? _localizationService.GetLocalizedString("MediaSettingsControlLocalFolder") : protocolType,
Title = fileSourceType == FileSourceType.Local ? _localizationService.GetLocalizedString("MediaSettingsControlLocalFolder") : Enum.GetName(fileSourceType),
PrimaryButtonText = _localizationService.GetLocalizedString("Add"),
CloseButtonText = _localizationService.GetLocalizedString("Cancel"),
DefaultButton = ContentDialogButton.Primary,
Content = new RemoteServerConfigControl(protocolType)
Content = new RemoteServerConfigControl(fileSourceType)
};
dialog.PrimaryButtonClick += async (s, e) =>
@@ -86,13 +90,13 @@ namespace BetterLyrics.WinUI3.ViewModels
{
var tempFolder = configControl.GetConfig();
if (protocolType == "Local")
if (fileSourceType == FileSourceType.Local)
{
string path = tempFolder.UriPath;
if (!System.IO.Directory.Exists(path))
if (!Directory.Exists(path))
{
throw new System.IO.DirectoryNotFoundException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathNotExisted"));
throw new DirectoryNotFoundException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathNotExisted"));
}
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
@@ -126,6 +130,63 @@ namespace BetterLyrics.WinUI3.ViewModels
}
else
{
if (fileSourceType == FileSourceType.WebDAV)
{
// 使用辅助类探测协议
string? detectedScheme = await WebDavProbeHelper.DetectSchemeAsync(
tempFolder.UriHost,
tempFolder.UriPort,
tempFolder.UriPath,
tempFolder.UserName,
tempFolder.Password
);
if (detectedScheme == null)
{
// 探测失败,直接报错返回
configControl.ShowError(_localizationService.GetLocalizedString("SettingsPageServerTestFailedInfo"));
deferral.Complete();
return;
}
// 将探测到的正确协议 (http 或 https) 写入配置对象
tempFolder.UriScheme = detectedScheme;
}
var newUriString = tempFolder.GetStandardUri().AbsoluteUri.TrimEnd('/') + "/";
foreach (var existingFolder in AppSettings.LocalMediaFolders)
{
// 只比对同类型的远程源 (可选,或者是比对所有源)
// 这里建议比对所有,防止逻辑上的冲突
var existingUriString = existingFolder.GetStandardUri().AbsoluteUri.TrimEnd('/') + "/";
// 是否完全重复 (忽略大小写)
if (newUriString.Equals(existingUriString, StringComparison.OrdinalIgnoreCase))
{
configControl.ShowError(_localizationService.GetLocalizedString("SettingsPagePathExistedInfo"));
deferral.Complete();
return;
}
// 新路径是否是现有路径的“子文件夹”
if (newUriString.StartsWith(existingUriString, StringComparison.OrdinalIgnoreCase))
{
configControl.ShowError(_localizationService.GetLocalizedString("SettingsPagePathBeIncludedInfo"));
deferral.Complete();
return;
}
// 新路径是否是现有路径的“父文件夹”
if (existingUriString.StartsWith(newUriString, StringComparison.OrdinalIgnoreCase))
{
configControl.ShowError(_localizationService.GetLocalizedString("SettingsPagePathIncludingOthersInfo"));
deferral.Complete();
return;
}
}
bool isConnected = await Task.Run(async () =>
{
try
@@ -174,9 +235,10 @@ namespace BetterLyrics.WinUI3.ViewModels
await dialog.ShowAsync();
}
private void ShowErrorTip(RemoteServerConfigControl control, string message)
[RelayCommand]
private void OpenMusicGalleryWindow()
{
control.ShowError(message);
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
}
}

View File

@@ -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,89 +55,61 @@ 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);
[ObservableProperty] public partial bool IsDataLoading { get; set; } = false;
[ObservableProperty] public partial bool IsDataSyncing { get; set; } = false;
[ObservableProperty] public partial bool IsDataSyncError { get; set; } = false;
[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;
_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;
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
}
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.DecodedAbsoluteUri)];
IsDataSyncError = AppSettings.LocalMediaFolders.Any(x => x.StatusSeverity == InfoBarSeverity.Error);
}
private void LocalMediaFolders_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
@@ -148,124 +117,6 @@ namespace BetterLyrics.WinUI3.ViewModels
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()
{
}
@@ -274,8 +125,6 @@ namespace BetterLyrics.WinUI3.ViewModels
{
_refreshSongsTimer.Debounce(() =>
{
IsDataLoading = true;
_ = Task.Run(async () =>
{
var enabledFolderIds = _settingsService.AppSettings.LocalMediaFolders
@@ -304,8 +153,6 @@ namespace BetterLyrics.WinUI3.ViewModels
IsLocalMediaNotFound = !_filteredTracks.Any();
ApplySongOrderType();
IsDataLoading = false;
});
});
}, Time.DebounceTimeout);
@@ -339,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
{
@@ -455,134 +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 Exception($"找不到文件 {PlayingTrack.FileName} 对应的存储配置。请检查服务器设置是否已启用。");
}
_currentProvider = targetFolder.CreateFileSystem();
if (_currentProvider == null) return;
await _currentProvider.ConnectAsync();
var fileCacheStub = new FileCacheEntity
{
Uri = PlayingTrack.Uri
};
var sourceStream = await _fileSystemService.OpenFileAsync(_currentProvider, fileCacheStub);
if (sourceStream == null)
{
throw new FileNotFoundException("无法打开文件流");
}
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($"PlayTrackAsync: 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();
@@ -635,15 +354,26 @@ namespace BetterLyrics.WinUI3.ViewModels
}
[RelayCommand]
private void SwitchPlaybackOrder()
private async Task StopTrackAsync()
{
AppSettings.MusicGallerySettings.PlaybackOrder = AppSettings.MusicGallerySettings.PlaybackOrder.GetNext();
await SMTCService.PlayTrackAtAsync(-1);
}
[RelayCommand]
private async Task StopTrackAsync()
private void OpenMediaSettings()
{
await PlayTrackAtAsync(-1);
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)
@@ -665,6 +395,10 @@ namespace BetterLyrics.WinUI3.ViewModels
{
RefreshSongs();
}
else if (message.PropertyName == nameof(MediaFolder.IsProcessing))
{
IsDataSyncing = message.NewValue;
}
}
}

View File

@@ -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]

View File

@@ -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;
}

View File

@@ -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;
}
}
}

View File

@@ -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)

View File

@@ -0,0 +1,291 @@
using BetterLyrics.WinUI3.Enums;
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.Threading.Tasks;
using System.Xml.Linq;
namespace BetterLyrics.WinUI3.ViewModels
{
public partial class StatsDashboardControlViewModel : BaseViewModel, IRecipient<PropertyChangedMessage<bool>>
{
private readonly IPlayHistoryService _playHistoryService;
private readonly ILocalizationService _localizationService;
private readonly IAlbumArtSearchService _albumArtSearchService;
private string _localizedTimesValue;
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";
// 时段分布
[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; } = "--:--";
// 歌手
[ObservableProperty] public partial ObservableCollection<ArtistPlayCount> TopArtists { get; set; } = new();
// 播放源
[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)
{
_playHistoryService = playHistoryService;
_localizationService = localizationService;
_albumArtSearchService = albumArtSearchService;
GSMTCService = gsmtcService;
_localizedTimesValue = _localizationService.GetLocalizedString("StatsDashboardControlTimes");
_timer = _dispatcherQueue.CreateTimer();
UpdateDateRange();
}
partial void OnSelectedTimeRangeChanged(StatsRange value)
{
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];
}
private void UpdatePlayerStats(List<PlayerStats> stats)
{
SourceSeries = new();
if (stats == null || stats.Count == 0)
{
TopPlayerName = "N/A";
return;
}
var topPlayer = stats.OrderByDescending(x => x.Count).FirstOrDefault();
TopPlayerName = PlayerIdHelper.GetDisplayName(topPlayer?.PlayerId) ?? "N/A";
var colors = PaletteHelper.GenerateChartColors(ColorHelper.GetSystemAccentColor(), stats.Count);
SourceSeries = [.. stats.OrderByDescending(x => x.Count).Select((x, i) => new PieSeries<int>
{
Values = [x.Count],
Name = PlayerIdHelper.GetDisplayName(x.PlayerId),
ToolTipLabelFormatter = point => $"{x.Count} {_localizedTimesValue}",
Pushout = 4, // 间隙
})];
}
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;
switch (SelectedTimeRange)
{
case StatsRange.Today:
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, nowLocal.Day);
break;
case StatsRange.ThisWeek:
int dayOfWeek = (int)nowLocal.DayOfWeek;
if (dayOfWeek == 0) dayOfWeek = 7;
startLocal = nowLocal.Date.AddDays(-(dayOfWeek - 1));
startLocal = new DateTime(startLocal.Year, startLocal.Month, startLocal.Day);
break;
case StatsRange.ThisMonth:
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, 1);
break;
case StatsRange.ThisQuarter:
int quarterStartMonth = (nowLocal.Month - 1) / 3 * 3 + 1;
startLocal = new DateTime(nowLocal.Year, quarterStartMonth, 1);
break;
case StatsRange.ThisYear:
startLocal = new DateTime(nowLocal.Year, 1, 1);
break;
}
CustomStartDate = startLocal.Date;
CustomEndDate = nowLocal.Date;
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();
}
}
}
}
}
}

View File

@@ -29,14 +29,14 @@
</Page.Resources>
<Grid>
<Grid Padding="12,8,12,64" ColumnSpacing="12">
<Grid Padding="12,4,12,68" ColumnSpacing="12">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*" />
<ColumnDefinition Width="3*" />
<ColumnDefinition Width="*" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<ScrollViewer Grid.Column="0">
<ScrollViewer x:Name="LeftSidePanel" Grid.Column="0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
@@ -49,7 +49,7 @@
<TextBlock
x:Uid="MusicGalleryPagePlaylist"
Grid.Row="0"
Margin="1,4,0,6"
Margin="1,8,0,6"
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
<Grid Grid.Row="1">
@@ -98,7 +98,7 @@
Click="RemoveFromPlaylistButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE711;}"
Glyph=&#xE738;}"
Style="{StaticResource GhostButtonStyle}"
Visibility="{x:Bind IsDefault, Converter={StaticResource BoolNegationToVisibilityConverter}}">
<ToolTipService.ToolTip>
@@ -161,16 +161,27 @@
<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=&#xE713;}"
Style="{StaticResource GhostButtonStyle}" />
</StackPanel>
<TreeView
x:Name="FolderTreeView"
Grid.Row="4"
AllowDrop="False"
CanDrag="False"
CanDragItems="False"
ItemInvoked="FolderTreeView_ItemInvoked"
ItemsSource="{x:Bind ViewModel.FolderRoots, Mode=OneWay}"
@@ -191,8 +202,15 @@
</Grid>
</ScrollViewer>
<Grid x:Name="SongViewer" Grid.Column="1">
<controls:ContentSizer Grid.Column="1" TargetControl="{x:Bind LeftSidePanel}" />
<Grid
x:Name="SongViewer"
Grid.Column="2"
RowSpacing="3">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" MinHeight="34" />
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
@@ -235,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>
@@ -243,105 +264,115 @@
</Flyout>
</Grid.Tag>
<StackPanel Grid.Row="0" Spacing="6">
<InfoBar
x:Uid="MusicGalleryPageDataSync"
Grid.Row="0"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsDataSyncing, Mode=OneWay}" />
<AutoSuggestBox
x:Name="SongSearchBox"
x:Uid="MusicGalleryPageSongSearchBox"
HorizontalAlignment="Stretch"
QueryIcon="Find"
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
<InfoBar
x:Uid="MusicGalleryPageDataSyncError"
Grid.Row="0"
IsClosable="False"
IsOpen="{x:Bind ViewModel.IsDataSyncError, Mode=OneWay}"
Severity="Error" />
<Grid>
<!-- 命令览 -->
<Grid Grid.Row="1" ColumnSpacing="6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition Width="Auto" />
<ColumnDefinition />
</Grid.ColumnDefinitions>
<StackPanel
HorizontalAlignment="Left"
Orientation="Horizontal"
Spacing="6">
<!-- 切换选择模式 -->
<ToggleButton
x:Name="SongListViewSelectionModeToggleButton"
Grid.Column="0"
Command="{x:Bind ViewModel.ToggleSongListViewSelectionModeCommand}"
Content="{ui:FontIcon FontSize=16,
FontFamily={StaticResource IconFontFamily},
Glyph=&#xE762;}"
Style="{StaticResource GhostToggleButtonStyle}" />
<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}" />
<!-- 为多选模式保留 -->
<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>
<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=&#xEC4F;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByAlbum" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE93C;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByArtist" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xEFA9;}" />
<controls:SegmentedItem x:Uid="MusicGalleryPageSortByFolder" Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Glyph=&#xE8B7;}" />
</controls:Segmented>
</StackPanel>
</StackPanel>
</Grid>
</StackPanel>
<AppBarSeparator Grid.Column="2" />
<SemanticZoom Grid.Row="1">
<!-- 排序选择 -->
<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>
<MenuBarItemFlyout Opened="AddToMenuBarItemFlyout_Opened">
<MenuFlyoutSubItem x:Uid="MusicGalleryPageAddToPlayingQueue" IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}">
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToNext" Click="AddSongToQueueNextMenuFlyoutItem_Click" />
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
</MenuFlyoutSubItem>
<MenuFlyoutItem
<MenuFlyoutSubItem
x:Name="AddToCustomListMenuFlyoutSubItem"
x:Uid="MusicGalleryPageAddToCustomList"
Click="AddToPlaylistMenuFlyoutItem_Click"
IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}">
<MenuFlyoutItem.ContextFlyout>
<Flyout FlyoutPresenterStyle="{StaticResource FlyoutGhostStyle}" Placement="Bottom">
<ListView ItemsSource="{x:Bind ViewModel.AppSettings.StarredPlaylists, Mode=OneWay}">
<ListView.ItemTemplate>
<DataTemplate x:DataType="models:SongsTabInfo">
<Grid Tapped="ToBeAddedPlaylistsListViewItemGrid_Tapped">
<StackPanel Orientation="Horizontal" Spacing="6">
<FontIcon
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="{x:Bind Icon}" />
<TextBlock
Margin="0,0,0,2"
VerticalAlignment="Center"
Style="{StaticResource BodyTextBlockStyle}"
Text="{x:Bind Name}" />
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
</Flyout>
</MenuFlyoutItem.ContextFlyout>
</MenuFlyoutItem>
IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}" />
</MenuBarItemFlyout>
</ListView.ContextFlyout>
<ListView.ItemTemplate>
@@ -415,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>
@@ -435,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}" />
@@ -447,7 +474,7 @@
</SemanticZoom.ZoomedOutView>
</SemanticZoom>
<Grid Grid.Row="1" 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"
@@ -462,264 +489,7 @@
</Grid>
<Grid x:Name="PlayQueue" Grid.Column="2">
<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=&#xE71A;}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
</ToolTipService.ToolTip>
</Button>
<!-- Playback order -->
<Button
Grid.Column="2"
HorizontalAlignment="Right"
Command="{x:Bind ViewModel.SwitchPlaybackOrderCommand}"
Style="{StaticResource GhostButtonStyle}">
<ToolTipService.ToolTip>
<ToolTip>
<Grid>
<TextBlock x:Name="PlaybackRepeatAllHint" x:Uid="MusicGalleryPageQueueLoop" />
<TextBlock x:Name="PlaybackRepeatOneHint" x:Uid="MusicGalleryPageSingleLoop" />
<TextBlock x:Name="PlaybackShuffleHint" x:Uid="MusicGalleryPageQueueRandom" />
</Grid>
</ToolTip>
</ToolTipService.ToolTip>
<Button.Content>
<Grid>
<!-- Repeat all -->
<FontIcon
x:Name="PlaybackRepeatAll"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8EE;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Repeat one -->
<FontIcon
x:Name="PlaybackRepeatOne"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8ED;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
<!-- Shuffle -->
<FontIcon
x:Name="PlaybackShuffle"
FontFamily="{StaticResource IconFontFamily}"
FontSize="16"
Glyph="&#xE8B1;">
<FontIcon.OpacityTransition>
<ScalarTransition />
</FontIcon.OpacityTransition>
</FontIcon>
</Grid>
</Button.Content>
</Button>
<!-- Scroll to playing item -->
<Button
Grid.Column="3"
HorizontalAlignment="Right"
Click="ScrollToPlayingItemButton_Click"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
FontSize=16,
Glyph=&#xE7B7;}"
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=&#xE738;}"
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>
<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=12,
Glyph=&#xE738;}"
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>
<Grid Background="{ThemeResource SolidBackgroundFillColorBaseBrush}" Visibility="{x:Bind ViewModel.IsDataLoading, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid Margin="12">
<Grid.RowDefinitions>
<RowDefinition Height="48" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
<RowDefinition Height="12" />
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<labs:Shimmer Grid.Row="0" CornerRadius="6" />
<labs:Shimmer Grid.Row="2" CornerRadius="6" />
<labs:Shimmer Grid.Row="4" CornerRadius="6" />
<labs:Shimmer Grid.Row="6" CornerRadius="6" />
</Grid>
</Grid>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="PlaybackOrderState">
<VisualState x:Name="RepeatAll">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="0" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="1" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Visible" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="RepeatOne">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="1" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="1" />
<Setter Target="PlaybackShuffle.Opacity" Value="0" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Visible" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Collapsed" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="Shuffle">
<VisualState.StateTriggers>
<ui:CompareStateTrigger
Comparison="Equal"
Value="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlaybackOrder, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}"
To="2" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="PlaybackRepeatAll.Opacity" Value="0" />
<Setter Target="PlaybackRepeatOne.Opacity" Value="0" />
<Setter Target="PlaybackShuffle.Opacity" Value="1" />
<Setter Target="PlaybackRepeatAllHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackRepeatOneHint.Visibility" Value="Collapsed" />
<Setter Target="PlaybackShuffleHint.Visibility" Value="Visible" />
</VisualState.Setters>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>

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