mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
Compare commits
12 Commits
ded4cff1ba
...
db8627c684
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
db8627c684 | ||
|
|
390e30f7f5 | ||
|
|
900774668d | ||
|
|
6ca2d1f897 | ||
|
|
164bd077b8 | ||
|
|
8ec71fcfb7 | ||
|
|
f39ad54df8 | ||
|
|
9b809983df | ||
|
|
8006b3a443 | ||
|
|
26a7454de2 | ||
|
|
0793a074cf | ||
|
|
125bf1682e |
@@ -12,7 +12,7 @@
|
|||||||
<Identity
|
<Identity
|
||||||
Name="37412.BetterLyrics"
|
Name="37412.BetterLyrics"
|
||||||
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
Publisher="CN=E1428B0E-DC1D-4EA4-ACB1-4556569D5BA9"
|
||||||
Version="1.1.221.0" />
|
Version="1.2.232.0" />
|
||||||
|
|
||||||
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
<mp:PhoneIdentity PhoneProductId="ca4a4830-fc19-40d9-b823-53e2bff3d816" PhonePublisherId="00000000-0000-0000-0000-000000000000"/>
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
// 2025/6/23 by Zhe Fang
|
using BetterLyrics.WinUI3.Helper;
|
||||||
|
|
||||||
using BetterLyrics.WinUI3.Helper;
|
|
||||||
using BetterLyrics.WinUI3.Hooks;
|
using BetterLyrics.WinUI3.Hooks;
|
||||||
using BetterLyrics.WinUI3.Models.Settings;
|
using BetterLyrics.WinUI3.Models.Db;
|
||||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||||
@@ -10,42 +8,49 @@ using BetterLyrics.WinUI3.Services.LastFMService;
|
|||||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||||
|
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||||
using BetterLyrics.WinUI3.ViewModels;
|
using BetterLyrics.WinUI3.ViewModels;
|
||||||
using BetterLyrics.WinUI3.Views;
|
using BetterLyrics.WinUI3.Views;
|
||||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||||
|
using Microsoft.Data.Sqlite;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.DependencyInjection;
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.UI.Dispatching; // 关键:用于线程调度
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.Windows.ApplicationModel.Resources;
|
using Microsoft.Windows.AppLifecycle; // 关键:App生命周期管理
|
||||||
using Microsoft.Windows.Globalization;
|
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using System;
|
using System;
|
||||||
using System.Globalization;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Vanara.PInvoke;
|
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3
|
namespace BetterLyrics.WinUI3
|
||||||
{
|
{
|
||||||
public partial class App : Application
|
public partial class App : Application
|
||||||
{
|
{
|
||||||
|
private Window? m_window;
|
||||||
private readonly ILogger<App> _logger;
|
private readonly ILogger<App> _logger;
|
||||||
|
|
||||||
public static new App Current => (App)Application.Current;
|
public static new App Current => (App)Application.Current;
|
||||||
|
|
||||||
private static Mutex? _instanceMutex;
|
private readonly string _appKey = Windows.ApplicationModel.Package.Current.Id.FamilyName;
|
||||||
|
|
||||||
public App()
|
public App()
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
// Must be done before InitializeComponent
|
||||||
|
if (!TryHandleSingleInstance())
|
||||||
|
{
|
||||||
|
// 如果移交成功直接退出当前进程
|
||||||
|
Environment.Exit(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
EnsureSingleInstance();
|
this.InitializeComponent();
|
||||||
|
|
||||||
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
|
||||||
PathHelper.EnsureDirectories();
|
PathHelper.EnsureDirectories();
|
||||||
@@ -53,32 +58,94 @@ namespace BetterLyrics.WinUI3
|
|||||||
|
|
||||||
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
_logger = Ioc.Default.GetRequiredService<ILogger<App>>();
|
||||||
|
|
||||||
|
// 注册全局异常捕获
|
||||||
UnhandledException += App_UnhandledException;
|
UnhandledException += App_UnhandledException;
|
||||||
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
|
||||||
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
|
AppDomain.CurrentDomain.FirstChanceException += CurrentDomain_FirstChanceException;
|
||||||
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
|
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);
|
// 监听 "Activated" 事件。
|
||||||
Environment.Exit(0);
|
// 当第二个实例启动并重定向过来时,这个事件会被触发。
|
||||||
|
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>();
|
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();
|
fileSystemService.StartAllFolderTimers();
|
||||||
|
|
||||||
WindowHook.OpenOrShowWindow<SystemTrayWindow>();
|
// 初始化托盘
|
||||||
|
m_window = WindowHook.OpenOrShowWindow<SystemTrayWindow>();
|
||||||
|
|
||||||
|
// 根据设置打开歌词窗口
|
||||||
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
|
if (settingsService.AppSettings.GeneralSettings.AutoStartLyricsWindow)
|
||||||
{
|
{
|
||||||
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
|
var defaultStatus = settingsService.AppSettings.WindowBoundsRecords.Where(x => x.IsDefault);
|
||||||
@@ -94,12 +161,101 @@ namespace BetterLyrics.WinUI3
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 根据设置自动打开主界面
|
||||||
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
|
if (settingsService.AppSettings.MusicGallerySettings.AutoOpen)
|
||||||
{
|
{
|
||||||
WindowHook.OpenOrShowWindow<MusicGalleryWindow>();
|
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()
|
private static void ConfigureServices()
|
||||||
{
|
{
|
||||||
Log.Logger = new LoggerConfiguration()
|
Log.Logger = new LoggerConfiguration()
|
||||||
@@ -107,14 +263,19 @@ namespace BetterLyrics.WinUI3
|
|||||||
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
.WriteTo.File(PathHelper.LogFilePattern, rollingInterval: RollingInterval.Day)
|
||||||
.CreateLogger();
|
.CreateLogger();
|
||||||
|
|
||||||
// Register services
|
|
||||||
Ioc.Default.ConfigureServices(
|
Ioc.Default.ConfigureServices(
|
||||||
new ServiceCollection()
|
new ServiceCollection()
|
||||||
|
// 数据库工厂
|
||||||
|
.AddDbContextFactory<PlayHistoryDbContext>(options => options.UseSqlite($"Data Source={PathHelper.PlayHistoryPath}"))
|
||||||
|
.AddDbContextFactory<FilesIndexDbContext>(options => options.UseSqlite($"Data Source={PathHelper.FilesIndexPath}"))
|
||||||
|
|
||||||
|
// 日志
|
||||||
.AddLogging(loggingBuilder =>
|
.AddLogging(loggingBuilder =>
|
||||||
{
|
{
|
||||||
loggingBuilder.ClearProviders();
|
loggingBuilder.ClearProviders();
|
||||||
loggingBuilder.AddSerilog();
|
loggingBuilder.AddSerilog();
|
||||||
})
|
})
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
.AddSingleton<ISettingsService, SettingsService>()
|
.AddSingleton<ISettingsService, SettingsService>()
|
||||||
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
.AddSingleton<IMediaSessionsService, MediaSessionsService>()
|
||||||
@@ -126,6 +287,8 @@ namespace BetterLyrics.WinUI3
|
|||||||
.AddSingleton<IDiscordService, DiscordService>()
|
.AddSingleton<IDiscordService, DiscordService>()
|
||||||
.AddSingleton<ILocalizationService, LocalizationService>()
|
.AddSingleton<ILocalizationService, LocalizationService>()
|
||||||
.AddSingleton<IFileSystemService, FileSystemService>()
|
.AddSingleton<IFileSystemService, FileSystemService>()
|
||||||
|
.AddSingleton<IPlayHistoryService, PlayHistoryService>()
|
||||||
|
|
||||||
// ViewModels
|
// ViewModels
|
||||||
.AddSingleton<AppSettingsControlViewModel>()
|
.AddSingleton<AppSettingsControlViewModel>()
|
||||||
.AddSingleton<PlaybackSettingsControlViewModel>()
|
.AddSingleton<PlaybackSettingsControlViewModel>()
|
||||||
@@ -140,6 +303,7 @@ namespace BetterLyrics.WinUI3
|
|||||||
.AddSingleton<MusicGalleryPageViewModel>()
|
.AddSingleton<MusicGalleryPageViewModel>()
|
||||||
.AddSingleton<AboutControlViewModel>()
|
.AddSingleton<AboutControlViewModel>()
|
||||||
.AddSingleton<MusicGalleryWindowViewModel>()
|
.AddSingleton<MusicGalleryWindowViewModel>()
|
||||||
|
.AddSingleton<StatsDashboardControlViewModel>()
|
||||||
|
|
||||||
.AddTransient<NowPlayingWindowViewModel>()
|
.AddTransient<NowPlayingWindowViewModel>()
|
||||||
.AddTransient<NowPlayingPageViewModel>()
|
.AddTransient<NowPlayingPageViewModel>()
|
||||||
@@ -157,7 +321,8 @@ namespace BetterLyrics.WinUI3
|
|||||||
|
|
||||||
private void CurrentDomain_FirstChanceException(object? sender, System.Runtime.ExceptionServices.FirstChanceExceptionEventArgs e)
|
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)
|
private void CurrentDomain_UnhandledException(object sender, System.UnhandledExceptionEventArgs e)
|
||||||
@@ -170,4 +335,4 @@ namespace BetterLyrics.WinUI3
|
|||||||
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
_logger.LogError(e.Exception, "TaskScheduler_UnobservedTaskException");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 59 KiB |
@@ -46,6 +46,7 @@
|
|||||||
<None Remove="Controls\PropertyRow.xaml" />
|
<None Remove="Controls\PropertyRow.xaml" />
|
||||||
<None Remove="Controls\RemoteServerConfigControl.xaml" />
|
<None Remove="Controls\RemoteServerConfigControl.xaml" />
|
||||||
<None Remove="Controls\ShortcutTextBox.xaml" />
|
<None Remove="Controls\ShortcutTextBox.xaml" />
|
||||||
|
<None Remove="Controls\StatsDashboardControl.xaml" />
|
||||||
<None Remove="Controls\SystemTray.xaml" />
|
<None Remove="Controls\SystemTray.xaml" />
|
||||||
<None Remove="Controls\WindowSettingsControl.xaml" />
|
<None Remove="Controls\WindowSettingsControl.xaml" />
|
||||||
<None Remove="Views\LyricsSearchWindow.xaml" />
|
<None Remove="Views\LyricsSearchWindow.xaml" />
|
||||||
@@ -69,6 +70,7 @@
|
|||||||
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.251219" />
|
<PackageReference Include="CommunityToolkit.WinUI.Behaviors" Version="8.2.251219" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Controls.Primitives" 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.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.Converters" Version="8.2.251219" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
|
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.251219" />
|
||||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.251219" />
|
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.251219" />
|
||||||
@@ -85,6 +87,11 @@
|
|||||||
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
<PackageReference Include="Hqub.Last.fm" Version="2.5.1" />
|
||||||
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
<PackageReference Include="Interop.UIAutomationClient" Version="10.19041.0" />
|
||||||
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
<PackageReference Include="Lyricify.Lyrics.Helper-NativeAot" Version="0.1.4-alpha.5" />
|
||||||
|
<PackageReference Include="Microsoft.Data.SqlClient" Version="6.1.3" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Abstractions" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="10.0.1" />
|
||||||
|
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" 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.Extensions.Logging" Version="10.0.1" />
|
||||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||||
@@ -95,7 +102,6 @@
|
|||||||
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
<PackageReference Include="Serilog.Extensions.Logging" Version="10.0.0" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||||
<PackageReference Include="SMBLibrary" Version="1.5.5.1" />
|
<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.Drawing.Common" Version="10.0.1" />
|
||||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
|
<PackageReference Include="System.Text.Encoding.CodePages" Version="10.0.1" />
|
||||||
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
<PackageReference Include="TagLibSharp" Version="2.3.0" />
|
||||||
@@ -128,6 +134,10 @@
|
|||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<TrimmerRootAssembly Include="FlaUI.UIA3" />
|
<TrimmerRootAssembly Include="FlaUI.UIA3" />
|
||||||
<TrimmerRootAssembly Include="Interop.UIAutomationClient" />
|
<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="NAudio.Wasapi" />
|
||||||
<TrimmerRootAssembly Include="TagLibSharp" />
|
<TrimmerRootAssembly Include="TagLibSharp" />
|
||||||
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
|
<TrimmerRootAssembly Include="Vanara.PInvoke.DwmApi" />
|
||||||
@@ -215,6 +225,9 @@
|
|||||||
<Content Update="Assets\NetEaseCloudMusic.png">
|
<Content Update="Assets\NetEaseCloudMusic.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
|
<Content Update="Assets\OriginalSoundHQPlayer.png">
|
||||||
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
|
</Content>
|
||||||
<Content Update="Assets\Page.png">
|
<Content Update="Assets\Page.png">
|
||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
@@ -246,6 +259,11 @@
|
|||||||
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Page Update="Controls\StatsDashboardControl.xaml">
|
||||||
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
</Page>
|
||||||
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
|
<Page Update="Controls\FontFamilyAutoSuggestBox.xaml">
|
||||||
<Generator>MSBuild:Compile</Generator>
|
<Generator>MSBuild:Compile</Generator>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
namespace BetterLyrics.WinUI3.Constants
|
namespace BetterLyrics.WinUI3.Constants
|
||||||
{
|
{
|
||||||
public static class PlayerID
|
public static class PlayerId
|
||||||
{
|
{
|
||||||
public const string LXMusic = "cn.toside.music.desktop";
|
public const string LXMusic = "cn.toside.music.desktop";
|
||||||
public const string LXMusicPortable = "lx-music-desktop.exe";
|
public const string LXMusicPortable = "lx-music-desktop.exe";
|
||||||
@@ -25,5 +25,6 @@
|
|||||||
public const string MoeKoeMusic = "cn.MoeKoe.Music";
|
public const string MoeKoeMusic = "cn.MoeKoe.Music";
|
||||||
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
|
public const string MoeKoeMusicAlternative = "electron.app.MoeKoe Music";
|
||||||
public const string Listen1 = "com.listen1.listen1";
|
public const string Listen1 = "com.listen1.listen1";
|
||||||
|
public const string OriginalSoundHQPlayer = "SennpaiStudio.528762A6196EF_z79ft30j24epr!App";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,5 +24,6 @@
|
|||||||
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows (Steam)";
|
public const string SaltPlayerForWindowsSteam = "Salt Player for Windows (Steam)";
|
||||||
public const string MoeKoeMusic = "MoeKoe Music";
|
public const string MoeKoeMusic = "MoeKoe Music";
|
||||||
public const string Listen1 = "Listen 1";
|
public const string Listen1 = "Listen 1";
|
||||||
|
public const string OriginalSoundHQPlayer = "Original Sound HQ Player";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -118,13 +118,21 @@
|
|||||||
</HyperlinkButton>
|
</HyperlinkButton>
|
||||||
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
|
<HyperlinkButton Content="爱发电" NavigateUri="{x:Bind const:Link.Afdian}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
<StackPanel Orientation="Horizontal" Spacing="6">
|
<Grid ColumnSpacing="6">
|
||||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="*" />
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="0"
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Text="*" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="SetingsPageThanks"
|
x:Uid="SetingsPageThanks"
|
||||||
|
Grid.Column="1"
|
||||||
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
TextWrapping="Wrap" />
|
TextWrapping="Wrap" />
|
||||||
</StackPanel>
|
</Grid>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</dev:SettingsCard>
|
</dev:SettingsCard>
|
||||||
|
|
||||||
@@ -182,6 +190,12 @@
|
|||||||
</dev:SettingsExpander.ItemsHeader>
|
</dev:SettingsExpander.ItemsHeader>
|
||||||
</dev:SettingsExpander>
|
</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">
|
<dev:SettingsCard x:Uid="SettingsPageFixedTimeStep" Visibility="Collapsed">
|
||||||
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
<ToggleSwitch IsOn="{x:Bind ViewModel.AppSettings.AdvancedSettings.IsFixedTimeStep, Mode=TwoWay}" />
|
||||||
</dev:SettingsCard>
|
</dev:SettingsCard>
|
||||||
|
|||||||
@@ -42,7 +42,6 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
{
|
{
|
||||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||||
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
|
|
||||||
|
|
||||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||||
@@ -660,22 +659,6 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
_songPosition += elapsedTime;
|
_songPosition += elapsedTime;
|
||||||
_totalPlayedTime += elapsedTime;
|
_totalPlayedTime += elapsedTime;
|
||||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
_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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
xmlns:controls="using:CommunityToolkit.WinUI.Controls"
|
||||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
xmlns:dev="using:DevWinUI"
|
xmlns:dev="using:DevWinUI"
|
||||||
|
xmlns:enums="using:BetterLyrics.WinUI3.Enums"
|
||||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||||
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
@@ -88,14 +89,18 @@
|
|||||||
</dev:SettingsCard>
|
</dev:SettingsCard>
|
||||||
</dev:SettingsExpander.Items>
|
</dev:SettingsExpander.Items>
|
||||||
|
|
||||||
<dev:SettingsExpander.ItemsFooter>
|
<dev:SettingsExpander.ItemsHeader>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<!-- Index info -->
|
<!-- Index info -->
|
||||||
<InfoBar
|
<InfoBar
|
||||||
IsClosable="False"
|
IsClosable="False"
|
||||||
IsOpen="{x:Bind IsIndexing, Mode=OneWay}"
|
IsOpen="True"
|
||||||
Message="{x:Bind IndexingStatusText, Mode=OneWay}" />
|
Message="{x:Bind StatusText, Mode=OneWay}"
|
||||||
<ProgressBar Visibility="{x:Bind IsIndexing, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" Value="{x:Bind IndexingProgress, 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:Interaction.Behaviors>
|
||||||
<interactivity:DataTriggerBehavior
|
<interactivity:DataTriggerBehavior
|
||||||
Binding="{x:Bind IndexingProgress, Mode=OneWay}"
|
Binding="{x:Bind IndexingProgress, Mode=OneWay}"
|
||||||
@@ -111,14 +116,8 @@
|
|||||||
</interactivity:DataTriggerBehavior>
|
</interactivity:DataTriggerBehavior>
|
||||||
</interactivity:Interaction.Behaviors>
|
</interactivity:Interaction.Behaviors>
|
||||||
</ProgressBar>
|
</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>
|
</StackPanel>
|
||||||
</dev:SettingsExpander.ItemsFooter>
|
</dev:SettingsExpander.ItemsHeader>
|
||||||
|
|
||||||
</dev:SettingsExpander>
|
</dev:SettingsExpander>
|
||||||
</DataTemplate>
|
</DataTemplate>
|
||||||
|
|||||||
@@ -11,7 +11,6 @@
|
|||||||
mc:Ignorable="d">
|
mc:Ignorable="d">
|
||||||
|
|
||||||
<Grid x:Name="RootGrid">
|
<Grid x:Name="RootGrid">
|
||||||
|
|
||||||
<Grid
|
<Grid
|
||||||
x:Name="BottomCommandGrid"
|
x:Name="BottomCommandGrid"
|
||||||
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
|
Background="{ThemeResource LayerOnMicaBaseAltFillColorDefaultBrush}"
|
||||||
@@ -113,7 +112,58 @@
|
|||||||
x:Name="BottomCenterCommandStackPanel"
|
x:Name="BottomCenterCommandStackPanel"
|
||||||
Padding="16"
|
Padding="16"
|
||||||
Orientation="Horizontal"
|
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="">
|
||||||
|
<FontIcon.OpacityTransition>
|
||||||
|
<ScalarTransition />
|
||||||
|
</FontIcon.OpacityTransition>
|
||||||
|
</FontIcon>
|
||||||
|
<!-- Repeat one -->
|
||||||
|
<FontIcon
|
||||||
|
x:Name="PlaybackRepeatOne"
|
||||||
|
FontFamily="{StaticResource IconFontFamily}"
|
||||||
|
FontSize="16"
|
||||||
|
Glyph="">
|
||||||
|
<FontIcon.OpacityTransition>
|
||||||
|
<ScalarTransition />
|
||||||
|
</FontIcon.OpacityTransition>
|
||||||
|
</FontIcon>
|
||||||
|
<!-- Shuffle -->
|
||||||
|
<FontIcon
|
||||||
|
x:Name="PlaybackShuffle"
|
||||||
|
FontFamily="{StaticResource IconFontFamily}"
|
||||||
|
FontSize="16"
|
||||||
|
Glyph="">
|
||||||
|
<FontIcon.OpacityTransition>
|
||||||
|
<ScalarTransition />
|
||||||
|
</FontIcon.OpacityTransition>
|
||||||
|
</FontIcon>
|
||||||
|
</Grid>
|
||||||
|
</Button.Content>
|
||||||
|
</Button>
|
||||||
|
<!-- 上一曲目 -->
|
||||||
<Button
|
<Button
|
||||||
Command="{x:Bind ViewModel.PreviousSongCommand}"
|
Command="{x:Bind ViewModel.PreviousSongCommand}"
|
||||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
@@ -164,6 +214,13 @@
|
|||||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
Glyph=}"
|
Glyph=}"
|
||||||
Style="{StaticResource GhostButtonStyle}" />
|
Style="{StaticResource GhostButtonStyle}" />
|
||||||
|
<!-- 播放队列按钮 -->
|
||||||
|
<ToggleButton
|
||||||
|
Click="PlayingQueueButton_Click"
|
||||||
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
Glyph=}"
|
||||||
|
Style="{StaticResource GhostToggleButtonStyle}"
|
||||||
|
Visibility="{x:Bind ShowPlayingQueueButton, Converter={StaticResource BoolToVisibilityConverter}, Mode=OneWay}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
@@ -427,7 +484,58 @@
|
|||||||
</Grid.ContextFlyout>
|
</Grid.ContextFlyout>
|
||||||
</Grid>
|
</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>
|
</Grid>
|
||||||
|
|
||||||
</UserControl>
|
</UserControl>
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
|
using BetterLyrics.WinUI3.Enums;
|
||||||
using BetterLyrics.WinUI3.Hooks;
|
using BetterLyrics.WinUI3.Hooks;
|
||||||
using BetterLyrics.WinUI3.Models;
|
using BetterLyrics.WinUI3.Models;
|
||||||
|
using BetterLyrics.WinUI3.Models.Settings;
|
||||||
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
using BetterLyrics.WinUI3.Services.MediaSessionsService;
|
||||||
using BetterLyrics.WinUI3.ViewModels;
|
using BetterLyrics.WinUI3.ViewModels;
|
||||||
using BetterLyrics.WinUI3.Views;
|
using BetterLyrics.WinUI3.Views;
|
||||||
@@ -12,6 +14,7 @@ using Microsoft.UI.Xaml.Input;
|
|||||||
using Microsoft.UI.Xaml.Media.Imaging;
|
using Microsoft.UI.Xaml.Media.Imaging;
|
||||||
using System;
|
using System;
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
|
using BetterLyrics.WinUI3.Extensions;
|
||||||
|
|
||||||
// To learn more about WinUI, the WinUI project structure,
|
// To learn more about WinUI, the WinUI project structure,
|
||||||
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
// and more about our project templates, see: http://aka.ms/winui-project-info.
|
||||||
@@ -46,6 +49,42 @@ public sealed partial class NowPlayingBar : UserControl,
|
|||||||
public static readonly DependencyProperty ShowSongInfoProperty =
|
public static readonly DependencyProperty ShowSongInfoProperty =
|
||||||
DependencyProperty.Register(nameof(ShowSongInfo), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
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 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 IsPlayingQueueOpened
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsPlayingQueueOpenedProperty); }
|
||||||
|
set { SetValue(IsPlayingQueueOpenedProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty IsPlayingQueueOpenedProperty =
|
||||||
|
DependencyProperty.Register(nameof(IsPlayingQueueOpened), typeof(bool), typeof(NowPlayingBar), new PropertyMetadata(false));
|
||||||
|
|
||||||
public bool IsCompactMode
|
public bool IsCompactMode
|
||||||
{
|
{
|
||||||
get { return (bool)GetValue(IsCompactModeProperty); }
|
get { return (bool)GetValue(IsCompactModeProperty); }
|
||||||
@@ -210,12 +249,12 @@ public sealed partial class NowPlayingBar : UserControl,
|
|||||||
|
|
||||||
private void SongInfoStackPanel_Tapped(object sender, TappedRoutedEventArgs e)
|
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)
|
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)
|
private void BottomCommandGrid_PointerEntered(object sender, Microsoft.UI.Xaml.Input.PointerRoutedEventArgs e)
|
||||||
@@ -262,6 +301,16 @@ public sealed partial class NowPlayingBar : UserControl,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PlayingQueueButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
IsPlayingQueueOpened = !IsPlayingQueueOpened;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void PlaybackOrderButton_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
PlaybackOrder = PlaybackOrder.GetNext();
|
||||||
|
}
|
||||||
|
|
||||||
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
public void Receive(PropertyChangedMessage<SongInfo?> message)
|
||||||
{
|
{
|
||||||
if (message.Sender is IMediaSessionsService)
|
if (message.Sender is IMediaSessionsService)
|
||||||
|
|||||||
@@ -12,13 +12,13 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
{
|
{
|
||||||
public sealed partial class RemoteServerConfigControl : UserControl
|
public sealed partial class RemoteServerConfigControl : UserControl
|
||||||
{
|
{
|
||||||
private readonly string _protocolType;
|
private readonly FileSourceType _fileSourceType;
|
||||||
private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
|
private readonly ILocalizationService _localizationService = Ioc.Default.GetRequiredService<ILocalizationService>();
|
||||||
|
|
||||||
public RemoteServerConfigControl(string protocolType)
|
public RemoteServerConfigControl(FileSourceType fileSourceType)
|
||||||
{
|
{
|
||||||
this.InitializeComponent();
|
this.InitializeComponent();
|
||||||
_protocolType = protocolType;
|
_fileSourceType = fileSourceType;
|
||||||
|
|
||||||
SetupDefaults();
|
SetupDefaults();
|
||||||
CheckPathForWarning();
|
CheckPathForWarning();
|
||||||
@@ -26,7 +26,7 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
|
|
||||||
private void SetupDefaults()
|
private void SetupDefaults()
|
||||||
{
|
{
|
||||||
if (_protocolType.Equals("Local", StringComparison.OrdinalIgnoreCase))
|
if (_fileSourceType == FileSourceType.Local)
|
||||||
{
|
{
|
||||||
RemoteFieldsPanel.Visibility = Visibility.Collapsed;
|
RemoteFieldsPanel.Visibility = Visibility.Collapsed;
|
||||||
AuthFieldsPanel.Visibility = Visibility.Collapsed;
|
AuthFieldsPanel.Visibility = Visibility.Collapsed;
|
||||||
@@ -41,17 +41,17 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
RemoteFieldsPanel.Visibility = Visibility.Visible;
|
RemoteFieldsPanel.Visibility = Visibility.Visible;
|
||||||
AuthFieldsPanel.Visibility = Visibility.Visible;
|
AuthFieldsPanel.Visibility = Visibility.Visible;
|
||||||
|
|
||||||
switch (_protocolType.ToUpper())
|
switch (_fileSourceType)
|
||||||
{
|
{
|
||||||
case "SMB":
|
case FileSourceType.SMB:
|
||||||
PortBox.Value = 445;
|
PortBox.Value = 445;
|
||||||
PathBox.PlaceholderText = "SharedMusic";
|
PathBox.PlaceholderText = "SharedMusic";
|
||||||
break;
|
break;
|
||||||
case "FTP":
|
case FileSourceType.FTP:
|
||||||
PortBox.Value = 21;
|
PortBox.Value = 21;
|
||||||
PathBox.PlaceholderText = "/pub/music";
|
PathBox.PlaceholderText = "/pub/music";
|
||||||
break;
|
break;
|
||||||
case "WEBDAV":
|
case FileSourceType.WebDAV:
|
||||||
PortBox.Value = 80;
|
PortBox.Value = 80;
|
||||||
PathBox.PlaceholderText = "/dav/music";
|
PathBox.PlaceholderText = "/dav/music";
|
||||||
break;
|
break;
|
||||||
@@ -62,15 +62,15 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
private string GetScheme()
|
private string GetScheme()
|
||||||
{
|
{
|
||||||
string scheme = string.Empty;
|
string scheme = string.Empty;
|
||||||
switch (_protocolType.ToUpper())
|
switch (_fileSourceType)
|
||||||
{
|
{
|
||||||
case "SMB":
|
case FileSourceType.SMB:
|
||||||
scheme = "smb";
|
scheme = "smb";
|
||||||
break;
|
break;
|
||||||
case "FTP":
|
case FileSourceType.FTP:
|
||||||
scheme = "ftp";
|
scheme = "ftp";
|
||||||
break;
|
break;
|
||||||
case "WEBDAV":
|
case FileSourceType.WebDAV:
|
||||||
scheme = "https";
|
scheme = "https";
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -81,7 +81,7 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
{
|
{
|
||||||
string finalName = HostBox.Text.Trim();
|
string finalName = HostBox.Text.Trim();
|
||||||
|
|
||||||
if (_protocolType.Equals("Local", StringComparison.OrdinalIgnoreCase))
|
if (_fileSourceType == FileSourceType.Local)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrWhiteSpace(PathBox.Text))
|
if (string.IsNullOrWhiteSpace(PathBox.Text))
|
||||||
throw new ArgumentException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathRequired"));
|
throw new ArgumentException(_localizationService.GetLocalizedString("RemoteServerConfigControlPathRequired"));
|
||||||
@@ -109,17 +109,15 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
finalName = $"{_protocolType} - {HostBox.Text}";
|
finalName = $"{_fileSourceType} - {HostBox.Text}";
|
||||||
}
|
}
|
||||||
|
|
||||||
Enum.TryParse(_protocolType, true, out FileSourceType sourceType);
|
|
||||||
|
|
||||||
string scheme = GetScheme();
|
string scheme = GetScheme();
|
||||||
|
|
||||||
var folder = new MediaFolder
|
var folder = new MediaFolder
|
||||||
{
|
{
|
||||||
Name = finalName,
|
Name = finalName,
|
||||||
SourceType = sourceType,
|
SourceType = _fileSourceType,
|
||||||
|
|
||||||
UriScheme = scheme,
|
UriScheme = scheme,
|
||||||
UriHost = HostBox.Text.Trim(), // ȥ<><C8A5><EFBFBD><EFBFBD>β<EFBFBD>ո<EFBFBD>
|
UriHost = HostBox.Text.Trim(), // ȥ<><C8A5><EFBFBD><EFBFBD>β<EFBFBD>ո<EFBFBD>
|
||||||
@@ -144,12 +142,7 @@ namespace BetterLyrics.WinUI3.Controls
|
|||||||
{
|
{
|
||||||
ProgressBar.Visibility = visibility;
|
ProgressBar.Visibility = visibility;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void PathBox_TextChanged(object sender, TextChangedEventArgs e)
|
|
||||||
{
|
|
||||||
CheckPathForWarning();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void CheckPathForWarning()
|
private void CheckPathForWarning()
|
||||||
{
|
{
|
||||||
string? path = PathBox.Text?.Trim();
|
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)
|
private async void BrowseButton_Click(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
|
|||||||
@@ -0,0 +1,302 @@
|
|||||||
|
<?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:converters="using:BetterLyrics.WinUI3.Converter"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:local="using:BetterLyrics.WinUI3.Controls"
|
||||||
|
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 LayerFillColorDefaultBrush}" />
|
||||||
|
<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 Margin="0,24,0,0">
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="20,0">
|
||||||
|
<Pivot x:Name="TimeRangePivot" SelectionChanged="Pivot_SelectionChanged">
|
||||||
|
<PivotItem x:Uid="StatsDashboardControlToday" Tag="Day" />
|
||||||
|
<PivotItem x:Uid="StatsDashboardControlThisWeek" Tag="Week" />
|
||||||
|
<PivotItem x:Uid="StatsDashboardControlThisMonth" Tag="Month" />
|
||||||
|
<PivotItem x:Uid="StatsDashboardControlThisQuarter" Tag="Quarter" />
|
||||||
|
<PivotItem x:Uid="StatsDashboardControlThisYear" Tag="Year" />
|
||||||
|
</Pivot>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ScrollViewer Grid.Row="1" Padding="20,0">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0" Margin="0,20,0,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="" />
|
||||||
|
<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}" />
|
||||||
|
<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="" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<!-- Top source -->
|
||||||
|
<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="" />
|
||||||
|
<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>
|
||||||
|
|
||||||
|
<Grid Grid.Row="1">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="1.5*" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<!-- Top artists -->
|
||||||
|
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="StatsDashboardControlTopArtists"
|
||||||
|
Margin="0,0,0,12"
|
||||||
|
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="1" 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
|
||||||
|
FontSize="10"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||||
|
Text="plays" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
<!-- Top sources -->
|
||||||
|
<Border
|
||||||
|
Grid.Column="1"
|
||||||
|
Margin="0,0,0,12"
|
||||||
|
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}" />
|
||||||
|
|
||||||
|
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.PlayerStats, Mode=OneWay}">
|
||||||
|
<ItemsControl.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="models:PlayerStatDisplayItem">
|
||||||
|
<Grid Margin="0,4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
FontSize="13"
|
||||||
|
Style="{ThemeResource BodyTextBlockStyle}"
|
||||||
|
Text="{x:Bind PlayerName}" />
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="1"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold">
|
||||||
|
<Run Text="{x:Bind PlayCount}" />
|
||||||
|
<Run
|
||||||
|
FontSize="10"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||||
|
Text="plays" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ItemsControl.ItemTemplate>
|
||||||
|
</ItemsControl>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<!-- Top song -->
|
||||||
|
<Border
|
||||||
|
Grid.Row="2"
|
||||||
|
Margin="0,0,0,20"
|
||||||
|
Style="{StaticResource StatsCardStyle}">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="StatsDashboardControlTopSongs"
|
||||||
|
Margin="0,0,0,12"
|
||||||
|
Style="{ThemeResource SubtitleTextBlockStyle}" />
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
Grid.Row="1"
|
||||||
|
ItemContainerStyle="{StaticResource ListViewStretchedItemContainerStyle}"
|
||||||
|
ItemsSource="{x:Bind ViewModel.TopSongs, Mode=OneWay}"
|
||||||
|
SelectionMode="None">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate x:DataType="statsmodels:SongPlayCount">
|
||||||
|
<Grid Margin="0,4">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid
|
||||||
|
Width="40"
|
||||||
|
Height="40"
|
||||||
|
Margin="0,0,12,0"
|
||||||
|
Background="{ThemeResource LayerFillColorAltBrush}"
|
||||||
|
CornerRadius="4">
|
||||||
|
<FontIcon
|
||||||
|
FontSize="16"
|
||||||
|
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||||
|
Glyph="" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<StackPanel Grid.Column="1" VerticalAlignment="Center">
|
||||||
|
<TextBlock Style="{ThemeResource BodyStrongTextBlockStyle}" Text="{x:Bind Title}" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||||
|
Style="{ThemeResource CaptionTextBlockStyle}"
|
||||||
|
Text="{x:Bind Artist}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<TextBlock
|
||||||
|
Grid.Column="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
FontWeight="SemiBold">
|
||||||
|
<Run Text="{x:Bind PlayCount}" />
|
||||||
|
<Run
|
||||||
|
FontSize="10"
|
||||||
|
FontWeight="Normal"
|
||||||
|
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||||
|
Text="plays" />
|
||||||
|
</TextBlock>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
</Grid>
|
||||||
|
</Border>
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</ScrollViewer>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
Grid.Row="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
VerticalAlignment="Bottom"
|
||||||
|
Command="{x:Bind ViewModel.GenerateTestDataCommand}"
|
||||||
|
Content="Generate test data"
|
||||||
|
Visibility="Collapsed" />
|
||||||
|
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
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>();
|
||||||
|
this.Loaded += StatsDashboardControl_Loaded;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void StatsDashboardControl_Loaded(object sender, Microsoft.UI.Xaml.RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
await ViewModel.LoadDataAsync(StatsRange.Day);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async void Pivot_SelectionChanged(object sender, SelectionChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (ViewModel == null) return;
|
||||||
|
|
||||||
|
if (TimeRangePivot.SelectedItem is PivotItem item && item.Tag is string tag)
|
||||||
|
{
|
||||||
|
var range = tag switch
|
||||||
|
{
|
||||||
|
"Day" => StatsRange.Day,
|
||||||
|
"Week" => StatsRange.Week,
|
||||||
|
"Month" => StatsRange.Month,
|
||||||
|
"Quarter" => StatsRange.Quarter,
|
||||||
|
"Year" => StatsRange.Year,
|
||||||
|
_ => StatsRange.Day
|
||||||
|
};
|
||||||
|
await ViewModel.LoadDataAsync(range);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -15,7 +15,7 @@ namespace BetterLyrics.WinUI3.Converter
|
|||||||
FileSourceType.Local => "\uE8B7", // Folder
|
FileSourceType.Local => "\uE8B7", // Folder
|
||||||
FileSourceType.SMB => "\uE839", // Network
|
FileSourceType.SMB => "\uE839", // Network
|
||||||
FileSourceType.FTP => "\uE838", // Globe
|
FileSourceType.FTP => "\uE838", // Globe
|
||||||
FileSourceType.WebDav => "\uE753", // Cloud
|
FileSourceType.WebDAV => "\uE753", // Cloud
|
||||||
_ => "\uE8B7"
|
_ => "\uE8B7"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,18 +3,37 @@ using System;
|
|||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Converter
|
namespace BetterLyrics.WinUI3.Converter
|
||||||
{
|
{
|
||||||
public class MillisecondsToFormattedTimeConverter : IValueConverter
|
public partial class MillisecondsToFormattedTimeConverter : IValueConverter
|
||||||
{
|
{
|
||||||
public object Convert(object value, Type targetType, object parameter, string language)
|
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");
|
var ts = TimeSpan.FromMilliseconds(milliseconds.Value);
|
||||||
}
|
|
||||||
else if (value is double doubleMilliseconds)
|
string? format = parameter?.ToString();
|
||||||
{
|
|
||||||
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff");
|
if (string.IsNullOrEmpty(format))
|
||||||
|
{
|
||||||
|
format = @"mm\:ss\.fff";
|
||||||
|
}
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
return ts.ToString(format);
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
return ts.ToString();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return value?.ToString() ?? "";
|
return value?.ToString() ?? "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,6 @@
|
|||||||
Local,
|
Local,
|
||||||
SMB,
|
SMB,
|
||||||
FTP,
|
FTP,
|
||||||
WebDav
|
WebDAV
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
15
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/StatsRange.cs
Normal file
15
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Enums/StatsRange.cs
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Text;
|
||||||
|
|
||||||
|
namespace BetterLyrics.WinUI3.Enums
|
||||||
|
{
|
||||||
|
public enum StatsRange
|
||||||
|
{
|
||||||
|
Day,
|
||||||
|
Week,
|
||||||
|
Month,
|
||||||
|
Quarter,
|
||||||
|
Year
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using BetterLyrics.WinUI3.Models;
|
using BetterLyrics.WinUI3.Models;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Extensions
|
namespace BetterLyrics.WinUI3.Extensions
|
||||||
{
|
{
|
||||||
@@ -30,6 +31,22 @@ namespace BetterLyrics.WinUI3.Extensions
|
|||||||
songInfo.Album = value;
|
songInfo.Album = value;
|
||||||
return songInfo;
|
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.Now.AddMilliseconds(-actualPlayedMs)
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ using System.Collections.Generic;
|
|||||||
using System.IO;
|
using System.IO;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
using Ude;
|
using Ude;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Helper
|
namespace BetterLyrics.WinUI3.Helper
|
||||||
@@ -29,6 +30,18 @@ namespace BetterLyrics.WinUI3.Helper
|
|||||||
return Encoding.GetEncoding(encoding);
|
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 = '_')
|
public static string SanitizeFileName(string fileName, char replacement = '_')
|
||||||
{
|
{
|
||||||
var invalidChars = Path.GetInvalidFileNameChars();
|
var invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ namespace BetterLyrics.WinUI3.Helper
|
|||||||
public static string SaltPlayerForWindowsLogoPath => Path.Combine(AssetsFolder, "SaltPlayerForWindows.png");
|
public static string SaltPlayerForWindowsLogoPath => Path.Combine(AssetsFolder, "SaltPlayerForWindows.png");
|
||||||
public static string MoeKoeMusicLogoPath => Path.Combine(AssetsFolder, "MoeKoeMusic.png");
|
public static string MoeKoeMusicLogoPath => Path.Combine(AssetsFolder, "MoeKoeMusic.png");
|
||||||
public static string Listen1LogoPath => Path.Combine(AssetsFolder, "Listen1.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 UnknownPlayerLogoPath => Path.Combine(AssetsFolder, "Question.png");
|
||||||
|
|
||||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
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 iTunesAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "itunes");
|
||||||
public static string LocalAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "local");
|
public static string LocalAlbumArtCacheDirectory => Path.Combine(AlbumArtCacheDirectory, "local");
|
||||||
|
|
||||||
public static string PlayQueuePath => Path.Combine(CacheFolder, "play-queue.m3u");
|
public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u");
|
||||||
public static string FilesCachePath => Path.Combine(CacheFolder, "files_cache.db");
|
public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db");
|
||||||
|
public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db");
|
||||||
|
|
||||||
public static void EnsureDirectories()
|
public static void EnsureDirectories()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
|
|||||||
|
|
||||||
namespace BetterLyrics.WinUI3.Helper
|
namespace BetterLyrics.WinUI3.Helper
|
||||||
{
|
{
|
||||||
public static class PlayerIDHelper
|
public static class PlayerIdHelper
|
||||||
{
|
{
|
||||||
private static readonly List<string> neteaseFamilyRegex =
|
private static readonly List<string> neteaseFamilyRegex =
|
||||||
[
|
[
|
||||||
@@ -25,64 +25,66 @@ namespace BetterLyrics.WinUI3.Helper
|
|||||||
return false;
|
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
|
public static string? GetDisplayName(string? id) => id switch
|
||||||
{
|
{
|
||||||
PlayerID.Spotify => PlayerName.Spotify,
|
PlayerId.Spotify => PlayerName.Spotify,
|
||||||
PlayerID.AppleMusic => PlayerName.AppleMusic,
|
PlayerId.AppleMusic => PlayerName.AppleMusic,
|
||||||
PlayerID.iTunes => PlayerName.iTunes,
|
PlayerId.iTunes => PlayerName.iTunes,
|
||||||
PlayerID.KugouMusic => PlayerName.KugouMusic,
|
PlayerId.KugouMusic => PlayerName.KugouMusic,
|
||||||
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
PlayerId.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||||
PlayerID.QQMusic => PlayerName.QQMusic,
|
PlayerId.QQMusic => PlayerName.QQMusic,
|
||||||
PlayerID.LXMusic => PlayerName.LXMusic,
|
PlayerId.LXMusic => PlayerName.LXMusic,
|
||||||
PlayerID.LXMusicPortable => PlayerName.LXMusicPortable,
|
PlayerId.LXMusicPortable => PlayerName.LXMusicPortable,
|
||||||
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
PlayerId.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||||
PlayerID.AIMP => PlayerName.AIMP,
|
PlayerId.AIMP => PlayerName.AIMP,
|
||||||
PlayerID.Foobar2000 => PlayerName.Foobar2000,
|
PlayerId.Foobar2000 => PlayerName.Foobar2000,
|
||||||
PlayerID.MusicBee => PlayerName.MusicBee,
|
PlayerId.MusicBee => PlayerName.MusicBee,
|
||||||
PlayerID.PotPlayer => PlayerName.PotPlayer,
|
PlayerId.PotPlayer => PlayerName.PotPlayer,
|
||||||
PlayerID.Chrome => PlayerName.Chrome,
|
PlayerId.Chrome => PlayerName.Chrome,
|
||||||
PlayerID.Edge => PlayerName.Edge,
|
PlayerId.Edge => PlayerName.Edge,
|
||||||
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
|
PlayerId.BetterLyrics => PlayerName.BetterLyrics,
|
||||||
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
PlayerId.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||||
PlayerID.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
|
PlayerId.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
|
||||||
PlayerID.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
|
PlayerId.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
|
||||||
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
PlayerId.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
||||||
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
PlayerId.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
||||||
PlayerID.Listen1 => PlayerName.Listen1,
|
PlayerId.Listen1 => PlayerName.Listen1,
|
||||||
|
PlayerId.OriginalSoundHQPlayer => PlayerName.OriginalSoundHQPlayer,
|
||||||
_ => id,
|
_ => id,
|
||||||
};
|
};
|
||||||
|
|
||||||
public static string GetLogoPath(string? id) => id switch
|
public static string GetLogoPath(string? id) => id switch
|
||||||
{
|
{
|
||||||
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
|
PlayerId.Spotify => PathHelper.SpotifyLogoPath,
|
||||||
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
|
PlayerId.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||||
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
PlayerId.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
||||||
PlayerID.iTunes => PathHelper.iTunesLogoPath,
|
PlayerId.iTunes => PathHelper.iTunesLogoPath,
|
||||||
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
|
PlayerId.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||||
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
PlayerId.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||||
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
|
PlayerId.QQMusic => PathHelper.QQMusicLogoPath,
|
||||||
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
|
PlayerId.LXMusic => PathHelper.LXMusicLogoPath,
|
||||||
PlayerID.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
PlayerId.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
||||||
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
PlayerId.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||||
PlayerID.AIMP => PathHelper.AIMPLogoPath,
|
PlayerId.AIMP => PathHelper.AIMPLogoPath,
|
||||||
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
PlayerId.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||||
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
|
PlayerId.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||||
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
|
PlayerId.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||||
PlayerID.Chrome => PathHelper.ChromeLogoPath,
|
PlayerId.Chrome => PathHelper.ChromeLogoPath,
|
||||||
PlayerID.Edge => PathHelper.EdgeLogoPath,
|
PlayerId.Edge => PathHelper.EdgeLogoPath,
|
||||||
PlayerID.BetterLyrics => PathHelper.LogoPath,
|
PlayerId.BetterLyrics => PathHelper.LogoPath,
|
||||||
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
|
PlayerId.BetterLyricsDebug => PathHelper.LogoPath,
|
||||||
PlayerID.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
|
PlayerId.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||||
PlayerID.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
|
PlayerId.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||||
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
PlayerId.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
||||||
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
PlayerId.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
||||||
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
|
PlayerId.Listen1 => PathHelper.Listen1LogoPath,
|
||||||
|
PlayerId.OriginalSoundHQPlayer => PathHelper.OriginalSoundHQPlayerLogoPath,
|
||||||
_ => PathHelper.UnknownPlayerLogoPath,
|
_ => PathHelper.UnknownPlayerLogoPath,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,12 +17,20 @@ namespace BetterLyrics.WinUI3.Hooks
|
|||||||
|
|
||||||
static SystemVolumeHook()
|
static SystemVolumeHook()
|
||||||
{
|
{
|
||||||
_deviceEnumerator = new MMDeviceEnumerator();
|
try
|
||||||
_defaultDevice = _deviceEnumerator.GetDefaultAudioEndpoint(DataFlow.Render, Role.Multimedia);
|
|
||||||
|
|
||||||
if (_defaultDevice != null)
|
|
||||||
{
|
{
|
||||||
_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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -136,7 +136,7 @@ namespace BetterLyrics.WinUI3.Models
|
|||||||
SetFromTrack(track);
|
SetFromTrack(track);
|
||||||
}
|
}
|
||||||
|
|
||||||
public ExtendedTrack(FileCacheEntity? entity, Stream? stream = null) : base()
|
public ExtendedTrack(FilesIndexItem? entity, Stream? stream = null) : base()
|
||||||
{
|
{
|
||||||
if (entity == null) return;
|
if (entity == null) return;
|
||||||
|
|
||||||
|
|||||||
@@ -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; }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ using BetterLyrics.WinUI3.Helper;
|
|||||||
using BetterLyrics.WinUI3.Services.FileSystemService;
|
using BetterLyrics.WinUI3.Services.FileSystemService;
|
||||||
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
|
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.Json.Serialization;
|
using System.Text.Json.Serialization;
|
||||||
using System.Threading;
|
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 string UriHost { get; set; }
|
||||||
[ObservableProperty][NotifyPropertyChangedFor(nameof(UriString))] public partial int UriPort { get; set; } = -1;
|
[ObservableProperty][NotifyPropertyChangedFor(nameof(UriString))] public partial int UriPort { get; set; } = -1;
|
||||||
|
|
||||||
|
[JsonPropertyName("Path")]
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
[NotifyPropertyChangedRecipients]
|
[NotifyPropertyChangedRecipients]
|
||||||
[NotifyPropertyChangedFor(nameof(ConnectionSummary))]
|
[NotifyPropertyChangedFor(nameof(ConnectionSummary))]
|
||||||
@@ -40,12 +42,10 @@ namespace BetterLyrics.WinUI3.Models
|
|||||||
|
|
||||||
[JsonIgnore] public bool IsLocal => SourceType == FileSourceType.Local;
|
[JsonIgnore] public bool IsLocal => SourceType == FileSourceType.Local;
|
||||||
|
|
||||||
[JsonIgnore][ObservableProperty] public partial bool IsIndexing { get; set; } = false;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool IsProcessing { get; set; } = false;
|
||||||
[JsonIgnore][ObservableProperty] public partial double IndexingProgress { get; set; } = 0;
|
[ObservableProperty] public partial double IndexingProgress { get; set; } = 0;
|
||||||
[JsonIgnore][ObservableProperty] public partial string IndexingStatusText { get; set; } = "";
|
[ObservableProperty] public partial string StatusText { get; set; } = "";
|
||||||
|
[ObservableProperty] public partial InfoBarSeverity StatusSeverity { get; set; } = InfoBarSeverity.Informational;
|
||||||
[JsonIgnore][ObservableProperty] public partial bool IsCleaningUp { get; set; } = false;
|
|
||||||
[JsonIgnore][ObservableProperty] public partial string CleaningUpStatusText { get; set; } = "";
|
|
||||||
|
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DateTime? LastSyncTime { get; set; }
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial DateTime? LastSyncTime { get; set; }
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AutoScanInterval ScanInterval { get; set; } = AutoScanInterval.Disabled;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial AutoScanInterval ScanInterval { get; set; } = AutoScanInterval.Disabled;
|
||||||
@@ -118,7 +118,7 @@ namespace BetterLyrics.WinUI3.Models
|
|||||||
FileSourceType.Local => new LocalFileSystem(this),
|
FileSourceType.Local => new LocalFileSystem(this),
|
||||||
FileSourceType.SMB => new SMBFileSystem(this),
|
FileSourceType.SMB => new SMBFileSystem(this),
|
||||||
FileSourceType.FTP => new FTPFileSystem(this),
|
FileSourceType.FTP => new FTPFileSystem(this),
|
||||||
FileSourceType.WebDav => new WebDavFileSystem(this),
|
FileSourceType.WebDAV => new WebDavFileSystem(this),
|
||||||
_ => throw new NotImplementedException()
|
_ => throw new NotImplementedException()
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,11 +35,11 @@ namespace BetterLyrics.WinUI3.Models
|
|||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
|
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
|
[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()
|
public MediaSourceProviderInfo()
|
||||||
{
|
{
|
||||||
@@ -53,7 +53,7 @@ namespace BetterLyrics.WinUI3.Models
|
|||||||
IsEnabled = isEnable;
|
IsEnabled = isEnable;
|
||||||
switch (provider)
|
switch (provider)
|
||||||
{
|
{
|
||||||
case Constants.PlayerID.AppleMusic:
|
case Constants.PlayerId.AppleMusic:
|
||||||
// Apple Music 的特性
|
// Apple Music 的特性
|
||||||
TimelineSyncThreshold = 1000;
|
TimelineSyncThreshold = 1000;
|
||||||
PositionOffset = 1000;
|
PositionOffset = 1000;
|
||||||
|
|||||||
@@ -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; } = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -90,7 +90,7 @@ namespace BetterLyrics.WinUI3.Services.AlbumArtSearchService
|
|||||||
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
||||||
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
||||||
|
|
||||||
FileCacheEntity? bestMatch = null;
|
FilesIndexItem? bestMatch = null;
|
||||||
|
|
||||||
foreach (var item in allFiles)
|
foreach (var item in allFiles)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
using BetterLyrics.WinUI3.Enums;
|
using BetterLyrics.WinUI3.Enums;
|
||||||
using BetterLyrics.WinUI3.Helper;
|
using BetterLyrics.WinUI3.Helper;
|
||||||
using BetterLyrics.WinUI3.Models;
|
using BetterLyrics.WinUI3.Models;
|
||||||
|
using BetterLyrics.WinUI3.Models.Db;
|
||||||
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
|
using BetterLyrics.WinUI3.Services.FileSystemService.Providers;
|
||||||
using BetterLyrics.WinUI3.Services.LocalizationService;
|
using BetterLyrics.WinUI3.Services.LocalizationService;
|
||||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||||
using BetterLyrics.WinUI3.ViewModels;
|
using BetterLyrics.WinUI3.ViewModels;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
using SQLite;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Concurrent;
|
using System.Collections.Concurrent;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@@ -27,7 +29,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
private readonly ILocalizationService _localizationService;
|
private readonly ILocalizationService _localizationService;
|
||||||
private readonly ILogger<FileSystemService> _logger;
|
private readonly ILogger<FileSystemService> _logger;
|
||||||
|
|
||||||
private readonly SQLiteAsyncConnection _db;
|
private readonly IDbContextFactory<FilesIndexDbContext> _contextFactory;
|
||||||
|
|
||||||
private bool _isInitialized = false;
|
private bool _isInitialized = false;
|
||||||
|
|
||||||
// 定时器字典
|
// 定时器字典
|
||||||
@@ -35,54 +38,37 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
// 当前正在执行的扫描任务字典
|
// 当前正在执行的扫描任务字典
|
||||||
private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeScanTokens = new();
|
private readonly ConcurrentDictionary<string, CancellationTokenSource> _activeScanTokens = new();
|
||||||
|
|
||||||
private static readonly SemaphoreSlim _dbLock = new(1, 1);
|
|
||||||
private static readonly SemaphoreSlim _folderScanLock = 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;
|
_logger = logger;
|
||||||
_localizationService = localizationService;
|
_localizationService = localizationService;
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
_db = new SQLiteAsyncConnection(PathHelper.FilesCachePath);
|
_contextFactory = contextFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
var cachedEntities = await context.FilesIndex
|
||||||
}
|
.AsNoTracking() // 读操作不追踪,提升性能
|
||||||
|
.Where(x => x.MediaFolderId == configId && x.ParentUri == queryParentUri)
|
||||||
public async Task<List<FileCacheEntity>> GetFilesAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId, bool forceRefresh = false)
|
.ToListAsync();
|
||||||
{
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
bool needSync = forceRefresh || cachedEntities.Count == 0;
|
bool needSync = forceRefresh || cachedEntities.Count == 0;
|
||||||
|
|
||||||
if (needSync)
|
if (needSync)
|
||||||
{
|
{
|
||||||
|
// SyncAsync 内部自己管理 Context
|
||||||
cachedEntities = await SyncAsync(provider, parentFolder, configId);
|
cachedEntities = await SyncAsync(provider, parentFolder, configId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,17 +76,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从远端/本地同步文件至数据库,该阶段不会解析文件全部元数据。
|
/// 从远端/本地同步文件至数据库
|
||||||
/// <para/>
|
|
||||||
/// 如果某个已有文件被修改或有新文件被添加,会预留空位,等待后续填充(通常交给 <see cref="ScanMediaFolderAsync"/> 完成)
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="provider"></param>
|
private async Task<List<FilesIndexItem>> SyncAsync(IUnifiedFileSystem provider, FilesIndexItem? parentFolder, string configId)
|
||||||
/// <param name="parentFolder"></param>
|
|
||||||
/// <param name="configId"></param>
|
|
||||||
/// <returns></returns>
|
|
||||||
private async Task<List<FileCacheEntity>> SyncAsync(IUnifiedFileSystem provider, FileCacheEntity? parentFolder, string configId)
|
|
||||||
{
|
{
|
||||||
List<FileCacheEntity> remoteItems;
|
List<FilesIndexItem> remoteItems;
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
remoteItems = await provider.GetFilesAsync(parentFolder);
|
remoteItems = await provider.GetFilesAsync(parentFolder);
|
||||||
@@ -115,80 +95,79 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
|
|
||||||
string targetParentUri = "";
|
string targetParentUri = "";
|
||||||
if (remoteItems.Count > 0)
|
if (remoteItems.Count > 0)
|
||||||
{
|
|
||||||
targetParentUri = remoteItems[0].ParentUri ?? "";
|
targetParentUri = remoteItems[0].ParentUri ?? "";
|
||||||
}
|
|
||||||
else if (parentFolder != null)
|
else if (parentFolder != null)
|
||||||
{
|
|
||||||
targetParentUri = parentFolder.Uri;
|
targetParentUri = parentFolder.Uri;
|
||||||
}
|
|
||||||
else
|
else
|
||||||
{
|
|
||||||
return [];
|
return [];
|
||||||
}
|
|
||||||
|
|
||||||
try
|
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>()
|
remoteUris.Add(remote.Uri);
|
||||||
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
var dbMap = dbItems.ToDictionary(x => x.Uri, x => x);
|
if (dbMap.TryGetValue(remote.Uri, out var existing))
|
||||||
|
|
||||||
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))
|
// 检查是否变更
|
||||||
{
|
bool isChanged = existing.FileSize != remote.FileSize ||
|
||||||
bool isChanged = existing.FileSize != remote.FileSize ||
|
existing.LastModified != remote.LastModified;
|
||||||
existing.LastModified != remote.LastModified;
|
|
||||||
|
|
||||||
if (isChanged)
|
if (isChanged)
|
||||||
{
|
|
||||||
existing.FileSize = remote.FileSize;
|
|
||||||
existing.LastModified = remote.LastModified;
|
|
||||||
existing.IsMetadataParsed = false; // 标记为未解析,下次会重新读取元数据
|
|
||||||
|
|
||||||
toUpdate.Add(existing);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// 数据库里原有的 Title, Artist, LocalAlbumArtPath 都会被完美保留
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
{
|
||||||
toInsert.Add(remote);
|
existing.FileSize = remote.FileSize;
|
||||||
|
existing.LastModified = remote.LastModified;
|
||||||
|
existing.IsMetadataParsed = false; // 标记重新解析
|
||||||
|
|
||||||
|
// EF Core 自动追踪 existing 的变化,无需手动 Update
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
else
|
||||||
foreach (var dbItem in dbItems)
|
|
||||||
{
|
{
|
||||||
if (!remoteMap.ContainsKey(dbItem.Uri))
|
// 新增
|
||||||
{
|
// 注意:如果 Id 是自增的,不要手动赋值 Id,除非是 Guid
|
||||||
toDelete.Add(dbItem);
|
context.FilesIndex.Add(remote);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (toInsert.Count > 0) conn.InsertAll(toInsert);
|
// 4. 处理 删除 (数据库有,远端没有)
|
||||||
if (toUpdate.Count > 0) conn.UpdateAll(toUpdate);
|
foreach (var dbItem in dbItems)
|
||||||
if (toDelete.Count > 0)
|
{
|
||||||
|
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)
|
.Where(x => x.MediaFolderId == configId && x.ParentUri == targetParentUri)
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
@@ -203,37 +182,34 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task UpdateMetadataAsync(FileCacheEntity entity)
|
public async Task UpdateMetadataAsync(FilesIndexItem entity)
|
||||||
{
|
{
|
||||||
// 现在的实体已经包含了完整信息,直接 Update 即可
|
using var context = await _contextFactory.CreateDbContextAsync();
|
||||||
// 我们只需要确保 Where 子句用的是主键或者 Uri
|
|
||||||
|
|
||||||
// 简化版 SQL,直接用 ORM 的 Update
|
// 使用 EF Core 7.0+ 的 ExecuteUpdateAsync 高效更新
|
||||||
// 但因为 entity 对象可能包含一些不应该被覆盖的旧数据(如果多线程操作),
|
// 这会直接生成 UPDATE SQL,不经过内存加载,性能极高
|
||||||
// 手写 SQL 只更新 Metadata 字段更安全。
|
await context.FilesIndex
|
||||||
|
.Where(x => x.Id == entity.Id) // 优先用 Id
|
||||||
string sql = @"
|
.ExecuteUpdateAsync(setters => setters
|
||||||
UPDATE FileCache
|
.SetProperty(p => p.Title, entity.Title)
|
||||||
SET
|
.SetProperty(p => p.Artists, entity.Artists)
|
||||||
Title = ?, Artists = ?, Album = ?,
|
.SetProperty(p => p.Album, entity.Album)
|
||||||
Year = ?, Bitrate = ?, SampleRate = ?, BitDepth = ?,
|
.SetProperty(p => p.Year, entity.Year)
|
||||||
Duration = ?, AudioFormatName = ?, AudioFormatShortName = ?, Encoder = ?,
|
.SetProperty(p => p.Bitrate, entity.Bitrate)
|
||||||
EmbeddedLyrics = ?, LocalAlbumArtPath = ?,
|
.SetProperty(p => p.SampleRate, entity.SampleRate)
|
||||||
IsMetadataParsed = 1
|
.SetProperty(p => p.BitDepth, entity.BitDepth)
|
||||||
WHERE Id = ?"; // 推荐用 Id (主键) 最快,如果没有 Id 则用 Uri
|
.SetProperty(p => p.Duration, entity.Duration)
|
||||||
|
.SetProperty(p => p.AudioFormatName, entity.AudioFormatName)
|
||||||
await _db.ExecuteAsync(sql,
|
.SetProperty(p => p.AudioFormatShortName, entity.AudioFormatShortName)
|
||||||
entity.Title, entity.Artists, entity.Album,
|
.SetProperty(p => p.Encoder, entity.Encoder)
|
||||||
entity.Year, entity.Bitrate, entity.SampleRate, entity.BitDepth,
|
.SetProperty(p => p.EmbeddedLyrics, entity.EmbeddedLyrics)
|
||||||
entity.Duration, entity.AudioFormatName, entity.AudioFormatShortName, entity.Encoder,
|
.SetProperty(p => p.LocalAlbumArtPath, entity.LocalAlbumArtPath)
|
||||||
entity.EmbeddedLyrics, entity.LocalAlbumArtPath,
|
.SetProperty(p => p.IsMetadataParsed, true)
|
||||||
entity.Id // WHERE Id = ?
|
);
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity)
|
public async Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity)
|
||||||
{
|
{
|
||||||
// 直接传递实体给 Provider
|
|
||||||
return await provider.OpenReadAsync(entity);
|
return await provider.OpenReadAsync(entity);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -241,8 +217,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
{
|
{
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.CleaningUpStatusText = _localizationService.GetLocalizedString("FileSystemServicePrepareToClean");
|
folder.IndexingProgress = 0;
|
||||||
folder.IsCleaningUp = true;
|
folder.StatusSeverity = InfoBarSeverity.Informational;
|
||||||
|
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServicePrepareToClean");
|
||||||
|
folder.IsProcessing = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (_folderTimerTokens.TryRemove(folder.Id, out var timerCts))
|
if (_folderTimerTokens.TryRemove(folder.Id, out var timerCts))
|
||||||
@@ -255,7 +233,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
if (_activeScanTokens.TryGetValue(folder.Id, out var activeScanCts))
|
if (_activeScanTokens.TryGetValue(folder.Id, out var activeScanCts))
|
||||||
{
|
{
|
||||||
activeScanCts.Cancel();
|
activeScanCts.Cancel();
|
||||||
// 强制终止正在扫描的操作
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -266,20 +243,19 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
{
|
{
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.CleaningUpStatusText = _localizationService.GetLocalizedString("FileSystemServiceCleaningCache");
|
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceCleaningCache");
|
||||||
});
|
});
|
||||||
|
|
||||||
await InitializeAsync();
|
using var context = await _contextFactory.CreateDbContextAsync();
|
||||||
|
|
||||||
await _dbLock.WaitAsync();
|
await context.FilesIndex
|
||||||
try
|
.Where(x => x.MediaFolderId == folder.Id)
|
||||||
|
.ExecuteDeleteAsync();
|
||||||
|
|
||||||
|
// VACUUM 是 SQLite 特有的命令
|
||||||
|
if (context.Database.IsSqlite())
|
||||||
{
|
{
|
||||||
await _db.ExecuteAsync("DELETE FROM FileCache WHERE MediaFolderId = ?", folder.Id);
|
await context.Database.ExecuteSqlRawAsync("VACUUM");
|
||||||
await _db.ExecuteAsync("VACUUM");
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_dbLock.Release();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
@@ -295,8 +271,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
{
|
{
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.CleaningUpStatusText = "";
|
folder.IsProcessing = false;
|
||||||
folder.IsCleaningUp = false;
|
|
||||||
folder.LastSyncTime = null;
|
folder.LastSyncTime = null;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -311,30 +286,33 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
|
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.IsIndexing = true;
|
folder.StatusSeverity = InfoBarSeverity.Informational;
|
||||||
|
folder.IsProcessing = true;
|
||||||
folder.IndexingProgress = 0;
|
folder.IndexingProgress = 0;
|
||||||
folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceWaitingForScan");
|
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceWaitingForScan");
|
||||||
});
|
});
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await _folderScanLock.WaitAsync(scanCts.Token);
|
await _folderScanLock.WaitAsync(scanCts.Token);
|
||||||
|
|
||||||
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceConnecting"));
|
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceConnecting"));
|
||||||
|
|
||||||
await InitializeAsync();
|
|
||||||
|
|
||||||
using var fs = folder.CreateFileSystem();
|
using var fs = folder.CreateFileSystem();
|
||||||
if (fs == null || !await fs.ConnectAsync())
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = _localizationService.GetLocalizedString("FileSystemServiceFetchingFileList"));
|
_dispatcherQueue.TryEnqueue(() => folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceFetchingFileList"));
|
||||||
|
|
||||||
var filesToProcess = new List<FileCacheEntity>();
|
var filesToProcess = new List<FilesIndexItem>();
|
||||||
var foldersToScan = new Queue<FileCacheEntity?>();
|
var foldersToScan = new Queue<FilesIndexItem?>();
|
||||||
foldersToScan.Enqueue(null); // 根目录
|
foldersToScan.Enqueue(null); // 根目录
|
||||||
|
|
||||||
while (foldersToScan.Count > 0)
|
while (foldersToScan.Count > 0)
|
||||||
@@ -342,7 +320,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
if (scanCts.Token.IsCancellationRequested) return;
|
if (scanCts.Token.IsCancellationRequested) return;
|
||||||
|
|
||||||
var currentParent = foldersToScan.Dequeue();
|
var currentParent = foldersToScan.Dequeue();
|
||||||
|
|
||||||
var items = await GetFilesAsync(fs, currentParent, folder.Id, forceRefresh: true);
|
var items = await GetFilesAsync(fs, currentParent, folder.Id, forceRefresh: true);
|
||||||
|
|
||||||
foreach (var item in items)
|
foreach (var item in items)
|
||||||
@@ -374,10 +351,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
if (current % 10 == 0 || current == total)
|
if (current % 10 == 0 || current == total)
|
||||||
{
|
{
|
||||||
double progress = (double)current / total * 100;
|
double progress = (double)current / total * 100;
|
||||||
_dispatcherQueue.TryEnqueue(Microsoft.UI.Dispatching.DispatcherQueuePriority.Low, () =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.IndexingProgress = progress;
|
folder.IndexingProgress = progress;
|
||||||
folder.IndexingStatusText = $"{_localizationService.GetLocalizedString("FileSystemServiceParsing")} {current}/{total}";
|
folder.StatusText = $"{_localizationService.GetLocalizedString("FileSystemServiceParsing")} {current}/{total}";
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -407,10 +384,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
|
|
||||||
if (track.Duration > 0)
|
if (track.Duration > 0)
|
||||||
{
|
{
|
||||||
// 保存封面
|
|
||||||
string? artPath = await SaveAlbumArtToDiskAsync(track);
|
string? artPath = await SaveAlbumArtToDiskAsync(track);
|
||||||
|
|
||||||
// 填充实体
|
|
||||||
item.Title = track.Title;
|
item.Title = track.Title;
|
||||||
item.Artists = track.Artist;
|
item.Artists = track.Artist;
|
||||||
item.Album = track.Album;
|
item.Album = track.Album;
|
||||||
@@ -422,7 +397,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
item.AudioFormatName = track.AudioFormatName;
|
item.AudioFormatName = track.AudioFormatName;
|
||||||
item.AudioFormatShortName = track.AudioFormatShortName;
|
item.AudioFormatShortName = track.AudioFormatShortName;
|
||||||
item.Encoder = track.Encoder;
|
item.Encoder = track.Encoder;
|
||||||
item.EmbeddedLyrics = track.RawLyrics; // 内嵌歌词
|
item.EmbeddedLyrics = track.RawLyrics;
|
||||||
item.LocalAlbumArtPath = artPath;
|
item.LocalAlbumArtPath = artPath;
|
||||||
item.IsMetadataParsed = true;
|
item.IsMetadataParsed = true;
|
||||||
}
|
}
|
||||||
@@ -434,7 +409,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
{
|
{
|
||||||
using var reader = new StreamReader(stream);
|
using var reader = new StreamReader(stream);
|
||||||
string content = await reader.ReadToEndAsync();
|
string content = await reader.ReadToEndAsync();
|
||||||
|
|
||||||
item.EmbeddedLyrics = content;
|
item.EmbeddedLyrics = content;
|
||||||
item.IsMetadataParsed = true;
|
item.IsMetadataParsed = true;
|
||||||
}
|
}
|
||||||
@@ -442,15 +416,10 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
|
|
||||||
if (item.IsMetadataParsed)
|
if (item.IsMetadataParsed)
|
||||||
{
|
{
|
||||||
await _dbLock.WaitAsync(token);
|
// 更新操作:直接调用 UpdateMetadataAsync
|
||||||
try
|
// 此时不需要 _dbLock,因为 UpdateMetadataAsync 内部会 CreateDbContextAsync
|
||||||
{
|
// 而 _folderScanLock 已经保证了当前文件夹扫描的独占性
|
||||||
await UpdateMetadataAsync(item);
|
await UpdateMetadataAsync(item);
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
_dbLock.Release();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -461,6 +430,8 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
|
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
|
folder.StatusSeverity = InfoBarSeverity.Success;
|
||||||
|
folder.StatusText = _localizationService.GetLocalizedString("FileSystemServiceReady");
|
||||||
folder.LastSyncTime = DateTime.Now;
|
folder.LastSyncTime = DateTime.Now;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -470,40 +441,41 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_dispatcherQueue.TryEnqueue(() => folder.IndexingStatusText = ex.Message);
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
|
{
|
||||||
|
folder.StatusText = ex.Message;
|
||||||
|
folder.StatusSeverity = InfoBarSeverity.Error;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
_folderScanLock.Release();
|
_folderScanLock.Release();
|
||||||
|
|
||||||
_activeScanTokens.TryRemove(folder.Id, out _);
|
_activeScanTokens.TryRemove(folder.Id, out _);
|
||||||
|
|
||||||
_dispatcherQueue.TryEnqueue(() =>
|
_dispatcherQueue.TryEnqueue(() =>
|
||||||
{
|
{
|
||||||
folder.IsIndexing = false;
|
folder.IsProcessing = false;
|
||||||
folder.IndexingStatusText = "";
|
folder.IndexingProgress = 0;
|
||||||
folder.IndexingProgress = 100;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
|
public async Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds)
|
||||||
{
|
{
|
||||||
await InitializeAsync();
|
|
||||||
|
|
||||||
if (enabledConfigIds == null || !enabledConfigIds.Any())
|
if (enabledConfigIds == null || !enabledConfigIds.Any())
|
||||||
{
|
{
|
||||||
return new List<FileCacheEntity>();
|
return new List<FilesIndexItem>();
|
||||||
}
|
}
|
||||||
|
|
||||||
var idList = enabledConfigIds.ToList();
|
var idList = enabledConfigIds.ToList();
|
||||||
|
|
||||||
// SQL 逻辑: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
|
using var context = await _contextFactory.CreateDbContextAsync();
|
||||||
var results = await _db.Table<FileCacheEntity>()
|
|
||||||
|
// SQL: SELECT * FROM FileCache WHERE IsMetadataParsed = 1 AND MediaFolderId IN (...)
|
||||||
|
return await context.FilesIndex
|
||||||
|
.AsNoTracking()
|
||||||
.Where(x => x.IsMetadataParsed && idList.Contains(x.MediaFolderId))
|
.Where(x => x.IsMetadataParsed && idList.Contains(x.MediaFolderId))
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
return results;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void StartAllFolderTimers()
|
public void StartAllFolderTimers()
|
||||||
@@ -563,11 +535,11 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
}, newCts.Token);
|
}, newCts.Token);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 参数为 string parentUri,表示哪个文件夹的内容变了
|
|
||||||
public event EventHandler<string>? FolderUpdated;
|
public event EventHandler<string>? FolderUpdated;
|
||||||
|
|
||||||
private async Task<string?> SaveAlbumArtToDiskAsync(ExtendedTrack track)
|
private async Task<string?> SaveAlbumArtToDiskAsync(ExtendedTrack track)
|
||||||
{
|
{
|
||||||
|
// 代码未变,纯 IO 操作
|
||||||
var picData = track.AlbumArtByteArray;
|
var picData = track.AlbumArtByteArray;
|
||||||
if (picData == null || picData.Length == 0) return null;
|
if (picData == null || picData.Length == 0) return null;
|
||||||
|
|
||||||
@@ -620,6 +592,5 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -10,12 +10,6 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
{
|
{
|
||||||
public interface IFileSystemService
|
public interface IFileSystemService
|
||||||
{
|
{
|
||||||
/// <summary>
|
|
||||||
/// 初始化(连接)数据库
|
|
||||||
/// </summary>
|
|
||||||
/// <returns></returns>
|
|
||||||
Task InitializeAsync();
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从数据库拉取文件(必要时需要从远端/本地同步至数据库)
|
/// 从数据库拉取文件(必要时需要从远端/本地同步至数据库)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -24,7 +18,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
/// <param name="configId"></param>
|
/// <param name="configId"></param>
|
||||||
/// <param name="forceRefresh">强制需要从远端/本地同步至数据库</param>
|
/// <param name="forceRefresh">强制需要从远端/本地同步至数据库</param>
|
||||||
/// <returns></returns>
|
/// <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>
|
/// <summary>
|
||||||
/// 打开文件(通过远端/本地流)
|
/// 打开文件(通过远端/本地流)
|
||||||
@@ -32,14 +26,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
/// <param name="provider"></param>
|
/// <param name="provider"></param>
|
||||||
/// <param name="entity"></param>
|
/// <param name="entity"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FileCacheEntity entity);
|
Task<Stream?> OpenFileAsync(IUnifiedFileSystem provider, FilesIndexItem entity);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 更新数据库(单个文件)
|
/// 更新数据库(单个文件)
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="entity"></param>
|
/// <param name="entity"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task UpdateMetadataAsync(FileCacheEntity entity);
|
Task UpdateMetadataAsync(FilesIndexItem entity);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 从数据库删除
|
/// 从数据库删除
|
||||||
@@ -60,7 +54,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="enabledConfigIds"></param>
|
/// <param name="enabledConfigIds"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<List<FileCacheEntity>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
|
Task<List<FilesIndexItem>> GetParsedFilesAsync(IEnumerable<string> enabledConfigIds);
|
||||||
|
|
||||||
void StartAllFolderTimers();
|
void StartAllFolderTimers();
|
||||||
|
|
||||||
|
|||||||
@@ -14,13 +14,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="parentFolder"></param>
|
/// <param name="parentFolder"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<List<FileCacheEntity>> GetFilesAsync(FileCacheEntity? parentFolder = null);
|
Task<List<FilesIndexItem>> GetFilesAsync(FilesIndexItem? parentFolder = null);
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 打开流
|
/// 打开流
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="file"></param>
|
/// <param name="file"></param>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
Task<Stream?> OpenReadAsync(FileCacheEntity file);
|
Task<Stream?> OpenReadAsync(FilesIndexItem file);
|
||||||
Task DisconnectAsync();
|
Task DisconnectAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,22 +42,14 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
|
|
||||||
public async Task<bool> ConnectAsync()
|
public async Task<bool> ConnectAsync()
|
||||||
{
|
{
|
||||||
try
|
if (_client.IsConnected) return true;
|
||||||
{
|
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
|
||||||
if (_client.IsConnected) return true;
|
return _client.IsConnected;
|
||||||
await _client.AutoConnect(); // AutoConnect 会自动尝试 FTP/FTPS
|
|
||||||
return _client.IsConnected;
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
System.Diagnostics.Debug.WriteLine($"FTP连接失败: {ex.Message}");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 targetServerPath;
|
string targetServerPath;
|
||||||
Uri parentUri;
|
Uri parentUri;
|
||||||
@@ -104,7 +96,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
Path = item.FullName
|
Path = item.FullName
|
||||||
};
|
};
|
||||||
|
|
||||||
result.Add(new FileCacheEntity
|
result.Add(new FilesIndexItem
|
||||||
{
|
{
|
||||||
MediaFolderId = _config.Id,
|
MediaFolderId = _config.Id,
|
||||||
// 如果是根目录扫描,ParentUri 用 Config 的;否则用传入文件夹的
|
// 如果是根目录扫描,ParentUri 用 Config 的;否则用传入文件夹的
|
||||||
@@ -130,7 +122,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity file)
|
public async Task<Stream?> OpenReadAsync(FilesIndexItem file)
|
||||||
{
|
{
|
||||||
if (file == null) return null;
|
if (file == null) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -20,12 +20,20 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
|
|
||||||
public Task<bool> ConnectAsync()
|
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 targetPath;
|
||||||
string parentUriString;
|
string parentUriString;
|
||||||
@@ -70,7 +78,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
size = fi.Length;
|
size = fi.Length;
|
||||||
}
|
}
|
||||||
|
|
||||||
result.Add(new FileCacheEntity
|
result.Add(new FilesIndexItem
|
||||||
{
|
{
|
||||||
MediaFolderId = _config.Id, // 关联配置 ID
|
MediaFolderId = _config.Id, // 关联配置 ID
|
||||||
|
|
||||||
@@ -94,7 +102,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
return await Task.FromResult(result);
|
return await Task.FromResult(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
|
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
|
||||||
{
|
{
|
||||||
if (entity == null) return null;
|
if (entity == null) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -42,29 +42,22 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
|
|
||||||
public async Task<bool> ConnectAsync()
|
public async Task<bool> ConnectAsync()
|
||||||
{
|
{
|
||||||
try
|
_client = new SMB2Client();
|
||||||
{
|
|
||||||
_client = new SMB2Client();
|
|
||||||
|
|
||||||
// 连接主机
|
// 连接主机
|
||||||
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
|
bool connected = _client.Connect(_config.UriHost, SMBTransportType.DirectTCPTransport);
|
||||||
if (!connected) return false;
|
if (!connected) return false;
|
||||||
|
|
||||||
// 登录
|
// 登录
|
||||||
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
|
var status = _client.Login(string.Empty, _config.UserName, _config.Password);
|
||||||
if (status != NTStatus.STATUS_SUCCESS) return false;
|
if (status != NTStatus.STATUS_SUCCESS) return false;
|
||||||
|
|
||||||
// 连接共享目录 (TreeConnect)
|
// 连接共享目录 (TreeConnect)
|
||||||
// SMBLibrary 必须先连接到 Share,后续所有文件操作都是基于这个 Share 的相对路径
|
// SMBLibrary 必须先连接到 Share,后续所有文件操作都是基于这个 Share 的相对路径
|
||||||
if (string.IsNullOrEmpty(_shareName)) return false;
|
if (string.IsNullOrEmpty(_shareName)) return false;
|
||||||
|
|
||||||
_fileStore = _client.TreeConnect(_shareName, out status);
|
_fileStore = _client.TreeConnect(_shareName, out status);
|
||||||
return status == NTStatus.STATUS_SUCCESS;
|
return status == NTStatus.STATUS_SUCCESS;
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -74,9 +67,9 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
/// 传入要列出的文件夹实体。
|
/// 传入要列出的文件夹实体。
|
||||||
/// 如果传入 null,则默认列出 MediaFolder 配置的根目录。
|
/// 如果传入 null,则默认列出 MediaFolder 配置的根目录。
|
||||||
/// </param>
|
/// </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;
|
if (_fileStore == null) return result;
|
||||||
|
|
||||||
string smbPath = GetPathRelativeToShare(parentFolder);
|
string smbPath = GetPathRelativeToShare(parentFolder);
|
||||||
@@ -128,7 +121,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
var baseUri = new Uri(parentUriString);
|
var baseUri = new Uri(parentUriString);
|
||||||
var newUri = new Uri(baseUri, item.FileName);
|
var newUri = new Uri(baseUri, item.FileName);
|
||||||
|
|
||||||
result.Add(new FileCacheEntity
|
result.Add(new FilesIndexItem
|
||||||
{
|
{
|
||||||
MediaFolderId = _config.Id,
|
MediaFolderId = _config.Id,
|
||||||
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri,
|
ParentUri = parentFolder?.Uri ?? _config.GetStandardUri().AbsoluteUri,
|
||||||
@@ -155,7 +148,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
/// 打开文件流
|
/// 打开文件流
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="file">只需要传入文件实体即可</param>
|
/// <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;
|
if (_fileStore == null || file == null) return null;
|
||||||
|
|
||||||
@@ -181,7 +174,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
_client?.Disconnect();
|
_client?.Disconnect();
|
||||||
}
|
}
|
||||||
|
|
||||||
private string GetPathRelativeToShare(FileCacheEntity? entity)
|
private string GetPathRelativeToShare(FilesIndexItem? entity)
|
||||||
{
|
{
|
||||||
Uri targetUri;
|
Uri targetUri;
|
||||||
|
|
||||||
|
|||||||
@@ -36,22 +36,13 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
|
|
||||||
public async Task<bool> ConnectAsync()
|
public async Task<bool> ConnectAsync()
|
||||||
{
|
{
|
||||||
try
|
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
|
||||||
{
|
return result.IsSuccessful;
|
||||||
// 测试连接:Propfind 请求配置的根路径
|
|
||||||
// GetStandardUri 已经包含了用户设置的路径
|
|
||||||
var result = await _client.Propfind(_config.GetStandardUri().AbsoluteUri);
|
|
||||||
return result.IsSuccessful;
|
|
||||||
}
|
|
||||||
catch
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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>();
|
||||||
|
|
||||||
Uri targetUri;
|
Uri targetUri;
|
||||||
if (parentFolder == null)
|
if (parentFolder == null)
|
||||||
@@ -98,7 +89,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
|
if (string.IsNullOrEmpty(extension) || !FileHelper.AllSupportedExtensions.Contains(extension)) continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
list.Add(new FileCacheEntity
|
list.Add(new FilesIndexItem
|
||||||
{
|
{
|
||||||
MediaFolderId = _config.Id,
|
MediaFolderId = _config.Id,
|
||||||
|
|
||||||
@@ -118,7 +109,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService.Providers
|
|||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Stream?> OpenReadAsync(FileCacheEntity entity)
|
public async Task<Stream?> OpenReadAsync(FilesIndexItem entity)
|
||||||
{
|
{
|
||||||
if (entity == null) return null;
|
if (entity == null) return null;
|
||||||
|
|
||||||
|
|||||||
@@ -279,7 +279,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
|||||||
{
|
{
|
||||||
int maxScore = 0;
|
int maxScore = 0;
|
||||||
|
|
||||||
FileCacheEntity? bestFileEntity = null;
|
FilesIndexItem? bestFileEntity = null;
|
||||||
MediaFolder? bestFolderConfig = null;
|
MediaFolder? bestFolderConfig = null;
|
||||||
|
|
||||||
var lyricsSearchResult = new LyricsSearchResult();
|
var lyricsSearchResult = new LyricsSearchResult();
|
||||||
@@ -345,7 +345,7 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
|||||||
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
var allFiles = await _fileSystemService.GetParsedFilesAsync(enabledIds);
|
||||||
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
allFiles = allFiles.Where(x => FileHelper.MusicExtensions.Contains(Path.GetExtension(x.FileName))).ToList();
|
||||||
|
|
||||||
FileCacheEntity? bestFile = null;
|
FilesIndexItem? bestFile = null;
|
||||||
int maxScore = 0;
|
int maxScore = 0;
|
||||||
|
|
||||||
foreach (var item in allFiles)
|
foreach (var item in allFiles)
|
||||||
@@ -558,11 +558,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
|||||||
|
|
||||||
ISearchResult? result;
|
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);
|
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, "");
|
result = new QQMusicSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId, "");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,7 +11,9 @@ using BetterLyrics.WinUI3.Models;
|
|||||||
using BetterLyrics.WinUI3.Models.Settings;
|
using BetterLyrics.WinUI3.Models.Settings;
|
||||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||||
|
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||||
|
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||||
using BetterLyrics.WinUI3.Services.TranslationService;
|
using BetterLyrics.WinUI3.Services.TranslationService;
|
||||||
using BetterLyrics.WinUI3.Services.TransliterationService;
|
using BetterLyrics.WinUI3.Services.TransliterationService;
|
||||||
@@ -54,6 +56,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
private readonly ITransliterationService _transliterationService;
|
private readonly ITransliterationService _transliterationService;
|
||||||
private readonly ISettingsService _settingsService;
|
private readonly ISettingsService _settingsService;
|
||||||
private readonly IDiscordService _discordService;
|
private readonly IDiscordService _discordService;
|
||||||
|
private readonly IPlayHistoryService _playHistoryService;
|
||||||
|
private readonly ILastFMService _lastFMService;
|
||||||
private readonly ILogger<MediaSessionsService> _logger;
|
private readonly ILogger<MediaSessionsService> _logger;
|
||||||
|
|
||||||
private double _lxMusicPositionSeconds = 0;
|
private double _lxMusicPositionSeconds = 0;
|
||||||
@@ -61,6 +65,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
|
|
||||||
private readonly DispatcherQueueTimer? _onMediaPropsChangedTimer;
|
private readonly DispatcherQueueTimer? _onMediaPropsChangedTimer;
|
||||||
|
|
||||||
|
private readonly Stopwatch _scrobbleStopwatch = new();
|
||||||
|
|
||||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial bool CurrentIsPlaying { get; private set; } = false;
|
[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 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;
|
||||||
@@ -74,6 +80,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
IDiscordService discordService,
|
IDiscordService discordService,
|
||||||
ITranslationService libreTranslateService,
|
ITranslationService libreTranslateService,
|
||||||
ITransliterationService transliterationService,
|
ITransliterationService transliterationService,
|
||||||
|
IPlayHistoryService playHistoryService,
|
||||||
|
ILastFMService lastFMService,
|
||||||
ILogger<MediaSessionsService> logger)
|
ILogger<MediaSessionsService> logger)
|
||||||
{
|
{
|
||||||
_settingsService = settingsService;
|
_settingsService = settingsService;
|
||||||
@@ -82,6 +90,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
_translationService = libreTranslateService;
|
_translationService = libreTranslateService;
|
||||||
_transliterationService = transliterationService;
|
_transliterationService = transliterationService;
|
||||||
_discordService = discordService;
|
_discordService = discordService;
|
||||||
|
_playHistoryService = playHistoryService;
|
||||||
|
_lastFMService = lastFMService;
|
||||||
_logger = logger;
|
_logger = logger;
|
||||||
|
|
||||||
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
|
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
|
||||||
@@ -144,7 +154,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id);
|
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id);
|
||||||
if (_settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened)
|
if (_settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened)
|
||||||
{
|
{
|
||||||
if (PlayerIDHelper.IsBetterLyrics(found?.Provider))
|
if (PlayerIdHelper.IsBetterLyrics(found?.Provider))
|
||||||
{
|
{
|
||||||
return found?.IsEnabled ?? true;
|
return found?.IsEnabled ?? true;
|
||||||
}
|
}
|
||||||
@@ -269,7 +279,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
{
|
{
|
||||||
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
||||||
|
|
||||||
if (PlayerIDHelper.IsLXMusic(sessionId))
|
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||||
{
|
{
|
||||||
StopSSE();
|
StopSSE();
|
||||||
}
|
}
|
||||||
@@ -288,20 +298,20 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
string? fixedAlbum = mediaProperties?.AlbumTitle;
|
string? fixedAlbum = mediaProperties?.AlbumTitle;
|
||||||
string? songId = null;
|
string? songId = null;
|
||||||
|
|
||||||
if (PlayerIDHelper.IsAppleMusic(sessionId))
|
if (PlayerIdHelper.IsAppleMusic(sessionId))
|
||||||
{
|
{
|
||||||
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
|
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
|
||||||
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
|
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
|
||||||
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
|
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
|
||||||
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
|
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
|
||||||
}
|
}
|
||||||
else if (PlayerIDHelper.IsNeteaseFamily(sessionId))
|
else if (PlayerIdHelper.IsNeteaseFamily(sessionId))
|
||||||
{
|
{
|
||||||
songId = mediaProperties?.Genres
|
songId = mediaProperties?.Genres
|
||||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
|
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
|
||||||
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
|
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
|
||||||
}
|
}
|
||||||
else if (sessionId == PlayerID.QQMusic)
|
else if (sessionId == PlayerId.QQMusic)
|
||||||
{
|
{
|
||||||
songId = mediaProperties?.Genres
|
songId = mediaProperties?.Genres
|
||||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
|
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
|
||||||
@@ -312,6 +322,35 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
|
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
|
||||||
.Replace(ExtendedGenreFiled.FileName, "");
|
.Replace(ExtendedGenreFiled.FileName, "");
|
||||||
|
|
||||||
|
// 写入播放记录
|
||||||
|
if (CurrentSongInfo != null && CurrentSongInfo.Title != "N/A")
|
||||||
|
{
|
||||||
|
// 必须捕获一个副本给异步任务,因为 CurrentSongInfo 马上就要变了
|
||||||
|
var lastSong = CurrentSongInfo;
|
||||||
|
|
||||||
|
// 当前秒表时间 >= 上一首总时长 / 2
|
||||||
|
if (lastSong.DurationMs > 0 &&
|
||||||
|
_scrobbleStopwatch.Elapsed.TotalMilliseconds >= (lastSong.DurationMs / 2))
|
||||||
|
{
|
||||||
|
// 写入本地播放记录
|
||||||
|
var playHistoryItem = lastSong.ToPlayHistoryItem(_scrobbleStopwatch.Elapsed.TotalMilliseconds);
|
||||||
|
if (playHistoryItem != null)
|
||||||
|
{
|
||||||
|
// 后台
|
||||||
|
_ = Task.Run(() => _playHistoryService.AddLogAsync(playHistoryItem));
|
||||||
|
_logger.LogInformation($"[Scrobble] 结算成功: {lastSong.Title}");
|
||||||
|
}
|
||||||
|
// 写入 Last.fm 播放记录
|
||||||
|
var isLastFMEnabled = CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
|
||||||
|
if (isLastFMEnabled)
|
||||||
|
{
|
||||||
|
// 后台
|
||||||
|
_ = Task.Run(() => _lastFMService.TrackAsync(lastSong));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_scrobbleStopwatch.Restart();
|
||||||
|
|
||||||
CurrentSongInfo = new SongInfo
|
CurrentSongInfo = new SongInfo
|
||||||
{
|
{
|
||||||
Title = mediaProperties?.Title ?? "N/A",
|
Title = mediaProperties?.Title ?? "N/A",
|
||||||
@@ -323,7 +362,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
LinkedFileName = linkedFileName
|
LinkedFileName = linkedFileName
|
||||||
};
|
};
|
||||||
|
|
||||||
if (PlayerIDHelper.IsLXMusic(sessionId))
|
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||||
{
|
{
|
||||||
StartSSE();
|
StartSSE();
|
||||||
}
|
}
|
||||||
@@ -332,7 +371,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
StopSSE();
|
StopSSE();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (PlayerIDHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
if (PlayerIdHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
||||||
{
|
{
|
||||||
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
|
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
|
||||||
}
|
}
|
||||||
@@ -523,7 +562,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
|||||||
{
|
{
|
||||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
_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);
|
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||||
if (data.ValueKind == JsonValueKind.Number)
|
if (data.ValueKind == JsonValueKind.Number)
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,258 @@
|
|||||||
|
using BetterLyrics.WinUI3.Models;
|
||||||
|
using BetterLyrics.WinUI3.Models.Db;
|
||||||
|
using BetterLyrics.WinUI3.Models.Stats;
|
||||||
|
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 => x.DurationPlayedMs); // 直接在数据库层面求和
|
||||||
|
|
||||||
|
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[] { "Spotify", "Spotify", "Spotify", "MusicBee", "MusicBee", "QQMusic", "NeteaseCloudMusic", "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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>الوضع المثبت (Docked)</value>
|
<value>الوضع المثبت (Docked)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>خطأ</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>تم التصدير بنجاح</value>
|
<value>تم التصدير بنجاح</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>التحضير لتنظيف ذاكرة التخزين المؤقت...</value>
|
<value>التحضير لتنظيف ذاكرة التخزين المؤقت...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>جاهز</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>تم اكتشاف مسار الدليل الجذر. قد يحتوي فهرس القرص الكامل على عدد كبير من الملفات غير الوسائط ويتسبب في استغراق الفحص وقتاً طويلاً جداً. يوصى بتحديد دليل فرعي محدد.</value>
|
<value>تم اكتشاف مسار الدليل الجذر. قد يحتوي فهرس القرص الكامل على عدد كبير من الملفات غير الوسائط ويتسبب في استغراق الفحص وقتاً طويلاً جداً. يوصى بتحديد دليل فرعي محدد.</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>كل الموسيقى</value>
|
<value>كل الموسيقى</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>مسح قائمة الانتظار</value>
|
<value>مسح قائمة الانتظار</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>كمية الضبابية (Blur)</value>
|
<value>كمية الضبابية (Blur)</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||||
<value>يشمل ملفات السجل وذاكرة التخزين المؤقت للكلمات عبر الشبكة</value>
|
<value>يتضمن ملفات السجلات، وذاكرة التخزين المؤقت للكلمات عبر الإنترنت</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>ذاكرة التخزين المؤقت (Cache)</value>
|
<value>ذاكرة التخزين المؤقت (Cache)</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>الخروج من البرنامج عند إغلاق نافذة الكلمات</value>
|
<value>الخروج من البرنامج عند إغلاق نافذة الكلمات</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>تصدير تاريخ اللعب</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>تصدير الإعدادات</value>
|
<value>تصدير الإعدادات</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>مدير الإعدادات</value>
|
<value>مدير الإعدادات</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>تاريخ اللعب</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>تصفح مركز مشاركة الموارد عبر الإنترنت</value>
|
<value>تصفح مركز مشاركة الموارد عبر الإنترنت</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>بدء التشغيل</value>
|
<value>بدء التشغيل</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>الإحصائيات</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>إيقاف التشغيل عند إغلاق نافذة مكتبة الموسيقى</value>
|
<value>إيقاف التشغيل عند إغلاق نافذة مكتبة الموسيقى</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>الوضع القياسي</value>
|
<value>الوضع القياسي</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>المصادر</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>هذا الشهر</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>هذا الربع</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>هذا الأسبوع</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>هذا العام</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>المسارات التي تم تشغيلها</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>خروج</value>
|
<value>خروج</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Angedockter Modus</value>
|
<value>Angedockter Modus</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Fehler</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Export erfolgreich</value>
|
<value>Export erfolgreich</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Bereinigung des Caches vorbereiten...</value>
|
<value>Bereinigung des Caches vorbereiten...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Bereit</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Alle Musikstücke</value>
|
<value>Alle Musikstücke</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Warteschlange leeren</value>
|
<value>Warteschlange leeren</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Unschärfe-Stärke</value>
|
<value>Unschärfe-Stärke</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Cache</value>
|
<value>Cache</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>App beenden, wenn Songtext-Fenster geschlossen wird</value>
|
<value>App beenden, wenn Songtext-Fenster geschlossen wird</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Spielverlauf exportieren</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Einstellungen exportieren</value>
|
<value>Einstellungen exportieren</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Einstellungsmanager</value>
|
<value>Einstellungsmanager</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Geschichte spielen</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Online Share Hub durchsuchen</value>
|
<value>Online Share Hub durchsuchen</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Start</value>
|
<value>Start</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Statistik</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Wiedergabe stoppen, wenn Musikgalerie-Fenster geschlossen wird</value>
|
<value>Wiedergabe stoppen, wenn Musikgalerie-Fenster geschlossen wird</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Standard-Modus</value>
|
<value>Standard-Modus</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Quellen</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Dieser Monat</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Dieses Quartal</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Diese Woche</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Dieses Jahr</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Gespielte Tracks</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Beenden</value>
|
<value>Beenden</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Docked Mode</value>
|
<value>Docked Mode</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Export successful</value>
|
<value>Export successful</value>
|
||||||
</data>
|
</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>
|
<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>
|
||||||
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
||||||
<value>Cleaning cache...</value>
|
<value>Clearing cache...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
||||||
<value>Connection failed</value>
|
<value>Connection failed</value>
|
||||||
@@ -181,7 +184,10 @@
|
|||||||
<value>Parsing...</value>
|
<value>Parsing...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<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>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<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>
|
<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">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>All Music</value>
|
<value>All Music</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Clear queue</value>
|
<value>Clear queue</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Exit app when lyrics window is closed</value>
|
<value>Exit app when lyrics window is closed</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Export play history</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Export Settings</value>
|
<value>Export Settings</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Settings Manager</value>
|
<value>Settings Manager</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Play History</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Browse Online Share Hub</value>
|
<value>Browse Online Share Hub</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Startup</value>
|
<value>Startup</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Statistics</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Stop playback when Music Gallery window is closed</value>
|
<value>Stop playback when Music Gallery window is closed</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Standard Mode</value>
|
<value>Standard Mode</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Sources</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>This Month</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>This Quarter</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>This Week</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>This Year</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Tracks Played</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Exit</value>
|
<value>Exit</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Modo Acoplado</value>
|
<value>Modo Acoplado</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Error</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Exportación exitosa</value>
|
<value>Exportación exitosa</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Preparando la limpieza de caché...</value>
|
<value>Preparando la limpieza de caché...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Listo</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Toda la música</value>
|
<value>Toda la música</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Borrar cola</value>
|
<value>Borrar cola</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Salir de la app al cerrar la ventana de letras</value>
|
<value>Salir de la app al cerrar la ventana de letras</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Exportar historial de jugadas</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Exportar configuración</value>
|
<value>Exportar configuración</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Gestor de configuración</value>
|
<value>Gestor de configuración</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Historia del juego</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Explorar Centro de recursos en línea</value>
|
<value>Explorar Centro de recursos en línea</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Inicio</value>
|
<value>Inicio</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Estadísticas</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Detener reproducción al cerrar ventana de Galería de Música</value>
|
<value>Detener reproducción al cerrar ventana de Galería de Música</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Modo Estándar</value>
|
<value>Modo Estándar</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Fuentes</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Este mes</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Este trimestre</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Esta semana</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Este año</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Pistas reproducidas</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Salir</value>
|
<value>Salir</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Mode Ancré</value>
|
<value>Mode Ancré</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Erreur</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Exportation réussie</value>
|
<value>Exportation réussie</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Préparation du nettoyage du cache...</value>
|
<value>Préparation du nettoyage du cache...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Prêt</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Toute la musique</value>
|
<value>Toute la musique</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Vider la file d'attente</value>
|
<value>Vider la file d'attente</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Quantité de flou</value>
|
<value>Quantité de flou</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Cache</value>
|
<value>Cache</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Quitter l'app à la fermeture de la fenêtre des paroles</value>
|
<value>Quitter l'app à la fermeture de la fenêtre des paroles</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Exporter l'historique des jeux</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Exporter les paramètres</value>
|
<value>Exporter les paramètres</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Gestionnaire de paramètres</value>
|
<value>Gestionnaire de paramètres</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Histoire du jeu</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Parcourir le Hub de partage en ligne</value>
|
<value>Parcourir le Hub de partage en ligne</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Démarrage</value>
|
<value>Démarrage</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Statistiques</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Arrêter la lecture à la fermeture de la Galerie</value>
|
<value>Arrêter la lecture à la fermeture de la Galerie</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Mode Standard</value>
|
<value>Mode Standard</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Sources d'information</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Ce mois-ci</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Ce trimestre</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Cette semaine</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Cette année</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Pistes jouées</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Quitter</value>
|
<value>Quitter</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>डॉक्ड मोड</value>
|
<value>डॉक्ड मोड</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>त्रुटि</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>निर्यात सफल रहा</value>
|
<value>निर्यात सफल रहा</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -166,7 +169,7 @@
|
|||||||
<value>LX Music सर्वर से कनेक्ट नहीं हो सकता, कृपया सेटिंग्स - प्लेबैक स्रोत - LX Music - LX Music सर्वर पर जाएं और जांचें कि लिंक सही तरीके से दर्ज किया गया है या नहीं</value>
|
<value>LX Music सर्वर से कनेक्ट नहीं हो सकता, कृपया सेटिंग्स - प्लेबैक स्रोत - LX Music - LX Music सर्वर पर जाएं और जांचें कि लिंक सही तरीके से दर्ज किया गया है या नहीं</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
||||||
<value>Cleaning cache...</value>
|
<value>कैश साफ़ हो रहा है...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
||||||
<value>कनेक्शन विफल रहा</value>
|
<value>कनेक्शन विफल रहा</value>
|
||||||
@@ -175,19 +178,22 @@
|
|||||||
<value>कनेक्टिंग ...</value>
|
<value>कनेक्टिंग ...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
|
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
|
||||||
<value>Fetching file list...</value>
|
<value>फ़ाइल लिस्ट लाई जा रही है...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceParsing" xml:space="preserve">
|
<data name="FileSystemServiceParsing" xml:space="preserve">
|
||||||
<value>Parsing...</value>
|
<value>पार्सिंग...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Preparing to clean cache...</value>
|
<value>कैश साफ़ करने की तैयारी...</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>तैयार</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<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>
|
<value>रूट डायरेक्टरी पाथ का पता चल गया है। एक फुल डिस्क इंडेक्स में बड़ी संख्या में नॉन-मीडिया फ़ाइलें हो सकती हैं और स्कैन में बहुत ज़्यादा समय लग सकता है। एक खास सबडायरेक्टरी बताने की सलाह दी जाती है।</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
||||||
<value>Preparing to scan...</value>
|
<value>स्कैन करने की तैयारी...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FullscreenMode" xml:space="preserve">
|
<data name="FullscreenMode" xml:space="preserve">
|
||||||
<value>फुलस्क्रीन मोड</value>
|
<value>फुलस्क्रीन मोड</value>
|
||||||
@@ -382,13 +388,13 @@
|
|||||||
<value>विभाजित दृश्य</value>
|
<value>विभाजित दृश्य</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
|
<data name="MediaSettingsControlLastSyncTime.Header" xml:space="preserve">
|
||||||
<value>Last Sync Time</value>
|
<value>अंतिम सिंक समय</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
|
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
|
||||||
<value>Local Folder</value>
|
<value>स्थानीय फोल्डर</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
|
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>नाम</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
|
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
|
||||||
<value>अभी सिंक्रनाइज़ करें</value>
|
<value>अभी सिंक्रनाइज़ करें</value>
|
||||||
@@ -403,11 +409,17 @@
|
|||||||
<value>अगला आइटम</value>
|
<value>अगला आइटम</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
|
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Add to playing queue</value>
|
<value>प्लेइंग क्यू में जोड़ें</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>सभी गाने</value>
|
<value>सभी गाने</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>प्लेइंग कतार साफ़ करें</value>
|
<value>प्लेइंग कतार साफ़ करें</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -451,13 +463,13 @@
|
|||||||
<value>मीडिया लाइब्रेरी में कोई गाना नहीं मिला</value>
|
<value>मीडिया लाइब्रेरी में कोई गाना नहीं मिला</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
|
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
|
||||||
<value>Folders</value>
|
<value>फ़ोल्डर्स</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
|
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
|
||||||
<value>फ़ाइल से आयात करें</value>
|
<value>फ़ाइल से आयात करें</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||||
<value>Create playlist</value>
|
<value>प्लेलिस्ट बनायें</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||||
<value>प्लेइंग कतार</value>
|
<value>प्लेइंग कतार</value>
|
||||||
@@ -466,7 +478,7 @@
|
|||||||
<value>प्लेइंग कतार खाली है</value>
|
<value>प्लेइंग कतार खाली है</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
|
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
|
||||||
<value>Playlists</value>
|
<value>प्लेलिस्ट</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
|
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
|
||||||
<value>सूची लूप</value>
|
<value>सूची लूप</value>
|
||||||
@@ -511,7 +523,7 @@
|
|||||||
<value>संगीत लाइब्रेरी - BetterLyrics</value>
|
<value>संगीत लाइब्रेरी - BetterLyrics</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
|
||||||
<value>Auto-sync Frequency</value>
|
<value>ऑटो-सिंक आवृत्ति</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
||||||
<value>कभी नहीं</value>
|
<value>कभी नहीं</value>
|
||||||
@@ -544,13 +556,13 @@
|
|||||||
<value>गोपनीयता नीति</value>
|
<value>गोपनीयता नीति</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
|
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
|
||||||
<value>Browse</value>
|
<value>ब्राउज करें</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>नाम</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
|
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
|
||||||
<value>Leaving it blank will automatically generate a default name.</value>
|
<value>इसे खाली छोड़ने से अपने आप एक डिफ़ॉल्ट नाम बन जाएगा।</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||||
<value>पासवर्ड</value>
|
<value>पासवर्ड</value>
|
||||||
@@ -559,10 +571,10 @@
|
|||||||
<value>पथ</value>
|
<value>पथ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
|
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
|
||||||
<value>The specified folder path could not be found</value>
|
<value>बताया गया फ़ोल्डर पाथ नहीं मिला</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
|
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
|
||||||
<value>Path is required</value>
|
<value>पथ आवश्यक है</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||||
<value>पोर्ट</value>
|
<value>पोर्ट</value>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>धुंधलापन मात्रा</value>
|
<value>धुंधलापन मात्रा</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||||
<value>लॉग फ़ाइलें, नेटवर्क बोल कैश शामिल हैं</value>
|
<value>लॉग फ़ाइलें, ऑनलाइन लिरिक्स कैश शामिल हैं</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>कैश</value>
|
<value>कैश</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>बोल विंडो बंद होने पर बाहर निकलें</value>
|
<value>बोल विंडो बंद होने पर बाहर निकलें</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>प्ले इतिहास निर्यात करें</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>सेटिंग्स निर्यात करें</value>
|
<value>सेटिंग्स निर्यात करें</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>सेटिंग्स प्रबंधक</value>
|
<value>सेटिंग्स प्रबंधक</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>प्ले इतिहास</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>ऑनलाइन संसाधन शेयरिंग हब ब्राउज़ करें</value>
|
<value>ऑनलाइन संसाधन शेयरिंग हब ब्राउज़ करें</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>स्टार्टअप</value>
|
<value>स्टार्टअप</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>आंकड़े</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>संगीत गैलरी विंडो बंद होने पर प्लेबैक रोकें</value>
|
<value>संगीत गैलरी विंडो बंद होने पर प्लेबैक रोकें</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>मानक मोड</value>
|
<value>मानक मोड</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>स्रोत</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>इस महीने</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>इस तिमाही</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>इस सप्ताह</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>इस वर्ष</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>बजाए गए ट्रैक</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>कार्यक्रम से बाहर निकलें</value>
|
<value>कार्यक्रम से बाहर निकलें</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Mode Dok</value>
|
<value>Mode Dok</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Kesalahan</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Ekspor berhasil</value>
|
<value>Ekspor berhasil</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Bersiap untuk membersihkan cache...</value>
|
<value>Bersiap untuk membersihkan cache...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Siap</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Semua Musik</value>
|
<value>Semua Musik</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Bersihkan antrean putar</value>
|
<value>Bersihkan antrean putar</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Tingkat Keburaman</value>
|
<value>Tingkat Keburaman</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Cache</value>
|
<value>Cache</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Keluar dari program saat jendela lirik ditutup</value>
|
<value>Keluar dari program saat jendela lirik ditutup</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Riwayat permainan ekspor</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Ekspor Pengaturan</value>
|
<value>Ekspor Pengaturan</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Manajer Pengaturan</value>
|
<value>Manajer Pengaturan</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Riwayat Bermain</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Jelajahi Hub Berbagi Sumber Daya Online</value>
|
<value>Jelajahi Hub Berbagi Sumber Daya Online</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Startup</value>
|
<value>Startup</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Statistik</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Hentikan pemutaran saat jendela galeri musik ditutup</value>
|
<value>Hentikan pemutaran saat jendela galeri musik ditutup</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Mode Standar</value>
|
<value>Mode Standar</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Sumber</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Bulan ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Kuartal ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Minggu Ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Tahun ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Lagu yang Dimainkan</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Keluar dari Program</value>
|
<value>Keluar dari Program</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>ドッキングモード</value>
|
<value>ドッキングモード</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>エラー</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>エクスポートが成功しました</value>
|
<value>エクスポートが成功しました</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>キャッシュ・クリーン準備中</value>
|
<value>キャッシュ・クリーン準備中</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>準備完了</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>ルートディレクトリのパスが検出された。フルディスクインデックスにはメディア以外のファイルが多数含まれている可能性があり、スキャンに時間がかかりすぎる。特定のサブディレクトリを指定することを推奨する。</value>
|
<value>ルートディレクトリのパスが検出された。フルディスクインデックスにはメディア以外のファイルが多数含まれている可能性があり、スキャンに時間がかかりすぎる。特定のサブディレクトリを指定することを推奨する。</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>すべてのミュージック</value>
|
<value>すべてのミュージック</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>キューをクリア</value>
|
<value>キューをクリア</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>ぼかし量</value>
|
<value>ぼかし量</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||||
<value>ログファイル、オンライン歌詞キャッシュを含みます</value>
|
<value>ログファイル、オンライン歌詞キャッシュを含む</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>キャッシュ</value>
|
<value>キャッシュ</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>歌詞ウィンドウを閉じたときにアプリを終了する</value>
|
<value>歌詞ウィンドウを閉じたときにアプリを終了する</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>プレー履歴のエクスポート</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>設定をエクスポート</value>
|
<value>設定をエクスポート</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>設定マネージャー</value>
|
<value>設定マネージャー</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>プレーの歴史</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>オンライン共有センターを閲覧する</value>
|
<value>オンライン共有センターを閲覧する</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>スタートアップ</value>
|
<value>スタートアップ</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>統計</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>ミュージックギャラリーウィンドウを閉じたときに再生を停止する</value>
|
<value>ミュージックギャラリーウィンドウを閉じたときに再生を停止する</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>標準モード</value>
|
<value>標準モード</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>情報源</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>今月</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>今期</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>今週</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>今年</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>演奏曲目</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>終了</value>
|
<value>終了</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>도킹 모드</value>
|
<value>도킹 모드</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>오류</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>내보내기 성공</value>
|
<value>내보내기 성공</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>캐시 정리 준비 중...</value>
|
<value>캐시 정리 준비 중...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>준비</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>루트 디렉터리 경로가 감지되었습니다. 전체 디스크 인덱스에 미디어가 아닌 파일이 많이 포함되어 있어 스캔 시간이 너무 오래 걸릴 수 있습니다. 특정 하위 디렉터리를 지정하는 것이 좋습니다.</value>
|
<value>루트 디렉터리 경로가 감지되었습니다. 전체 디스크 인덱스에 미디어가 아닌 파일이 많이 포함되어 있어 스캔 시간이 너무 오래 걸릴 수 있습니다. 특정 하위 디렉터리를 지정하는 것이 좋습니다.</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>모든 음악</value>
|
<value>모든 음악</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>재생 대기열 비우기</value>
|
<value>재생 대기열 비우기</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>가사 창을 닫을 때 앱 종료</value>
|
<value>가사 창을 닫을 때 앱 종료</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>플레이 기록 내보내기</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>설정 내보내기</value>
|
<value>설정 내보내기</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>설정 관리자</value>
|
<value>설정 관리자</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>플레이 기록</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>온라인 공유 허브 탐색</value>
|
<value>온라인 공유 허브 탐색</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>시작</value>
|
<value>시작</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>통계</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>음악 갤러리 창을 닫을 때 재생 중지</value>
|
<value>음악 갤러리 창을 닫을 때 재생 중지</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>표준 모드</value>
|
<value>표준 모드</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>출처</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>이번 달</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>이번 분기</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>이번 주</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>올해</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>재생된 트랙</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>종료</value>
|
<value>종료</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Mod Dok</value>
|
<value>Mod Dok</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Ralat</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Eksport berjaya</value>
|
<value>Eksport berjaya</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -166,7 +169,7 @@
|
|||||||
<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>
|
<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>
|
||||||
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
<data name="FileSystemServiceCleaningCache" xml:space="preserve">
|
||||||
<value>Cleaning cache...</value>
|
<value>Membersihkan cache...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
<data name="FileSystemServiceConnectFailed" xml:space="preserve">
|
||||||
<value>Sambungan gagal</value>
|
<value>Sambungan gagal</value>
|
||||||
@@ -175,19 +178,22 @@
|
|||||||
<value>Menyambung...</value>
|
<value>Menyambung...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
|
<data name="FileSystemServiceFetchingFileList" xml:space="preserve">
|
||||||
<value>Fetching file list...</value>
|
<value>Mengambil senarai fail...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceParsing" xml:space="preserve">
|
<data name="FileSystemServiceParsing" xml:space="preserve">
|
||||||
<value>Menghurai...</value>
|
<value>Menghurai...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Preparing to clean cache...</value>
|
<value>Bersedia untuk membersihkan cache...</value>
|
||||||
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Sedia</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<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>
|
<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>
|
||||||
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
<data name="FileSystemServiceWaitingForScan" xml:space="preserve">
|
||||||
<value>Preparing to scan...</value>
|
<value>Bersedia untuk mengimbas...</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="FullscreenMode" xml:space="preserve">
|
<data name="FullscreenMode" xml:space="preserve">
|
||||||
<value>Mod Skrin Penuh</value>
|
<value>Mod Skrin Penuh</value>
|
||||||
@@ -385,10 +391,10 @@
|
|||||||
<value>Masa Segerak Akhir</value>
|
<value>Masa Segerak Akhir</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
|
<data name="MediaSettingsControlLocalFolder" xml:space="preserve">
|
||||||
<value>Local Folder</value>
|
<value>Folder setempat</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
|
<data name="MediaSettingsControlNameSetting.Header" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>Nama</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
|
<data name="MediaSettingsControlSyncNow.Content" xml:space="preserve">
|
||||||
<value>Segerak sekarang</value>
|
<value>Segerak sekarang</value>
|
||||||
@@ -403,11 +409,17 @@
|
|||||||
<value>Item seterusnya</value>
|
<value>Item seterusnya</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
|
<data name="MusicGalleryPageAddToPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Add to playing queue</value>
|
<value>Tambah ke turutan lagu</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Semua Muzik</value>
|
<value>Semua Muzik</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Kosongkan baris gilir main</value>
|
<value>Kosongkan baris gilir main</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -451,13 +463,13 @@
|
|||||||
<value>Tiada lagu ditemui dalam pustaka media</value>
|
<value>Tiada lagu ditemui dalam pustaka media</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
|
<data name="MusicGalleryPageFolder.Text" xml:space="preserve">
|
||||||
<value>Folders</value>
|
<value>Folder</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
|
<data name="MusicGalleryPageImportFromFile.Text" xml:space="preserve">
|
||||||
<value>Import dari fail</value>
|
<value>Import dari fail</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
<data name="MusicGalleryPageNewPlaylist.Text" xml:space="preserve">
|
||||||
<value>Create playlist</value>
|
<value>Cipta senarai main</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
<data name="MusicGalleryPagePlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Baris Gilir Main</value>
|
<value>Baris Gilir Main</value>
|
||||||
@@ -466,7 +478,7 @@
|
|||||||
<value>Baris gilir main kosong</value>
|
<value>Baris gilir main kosong</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
|
<data name="MusicGalleryPagePlaylist.Text" xml:space="preserve">
|
||||||
<value>Playlists</value>
|
<value>Senarai Main</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
|
<data name="MusicGalleryPageQueueLoop.Text" xml:space="preserve">
|
||||||
<value>Gelung senarai</value>
|
<value>Gelung senarai</value>
|
||||||
@@ -511,7 +523,7 @@
|
|||||||
<value>Galeri Muzik - BetterLyrics</value>
|
<value>Galeri Muzik - BetterLyrics</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncInterval.Header" xml:space="preserve">
|
||||||
<value>Auto-sync Frequency</value>
|
<value>Kekerapan penyegerakan automatik</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
||||||
<value>Tak pernah</value>
|
<value>Tak pernah</value>
|
||||||
@@ -526,7 +538,7 @@
|
|||||||
<value>Setiap jam</value>
|
<value>Setiap jam</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncIntervalEverySixHrs.Content" xml:space="preserve">
|
||||||
<value>Every 6 Hours</value>
|
<value>Setiap 6 Jam</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="NarrowMode" xml:space="preserve">
|
<data name="NarrowMode" xml:space="preserve">
|
||||||
<value>Mod Sempit</value>
|
<value>Mod Sempit</value>
|
||||||
@@ -544,13 +556,13 @@
|
|||||||
<value>Dasar Privasi</value>
|
<value>Dasar Privasi</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
|
<data name="RemoteServerConfigControlBrowse.Content" xml:space="preserve">
|
||||||
<value>Browse</value>
|
<value>Layari</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlName.Header" xml:space="preserve">
|
||||||
<value>Name</value>
|
<value>Nama</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
|
<data name="RemoteServerConfigControlName.PlaceholderText" xml:space="preserve">
|
||||||
<value>Leaving it blank will automatically generate a default name.</value>
|
<value>Membiarkan ia kosong akan menjana nama lalai secara automatik.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlPassword.Header" xml:space="preserve">
|
||||||
<value>Kata Laluan</value>
|
<value>Kata Laluan</value>
|
||||||
@@ -559,10 +571,10 @@
|
|||||||
<value>Laluan</value>
|
<value>Laluan</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
|
<data name="RemoteServerConfigControlPathNotExisted" xml:space="preserve">
|
||||||
<value>The specified folder path could not be found</value>
|
<value>Laluan folder yang dinyatakan tidak dapat ditemui</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
|
<data name="RemoteServerConfigControlPathRequired" xml:space="preserve">
|
||||||
<value>Path is required</value>
|
<value>Laluan diperlukan</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
<data name="RemoteServerConfigControlPort.Header" xml:space="preserve">
|
||||||
<value>Port</value>
|
<value>Port</value>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Jumlah Kabur</value>
|
<value>Jumlah Kabur</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Cache</value>
|
<value>Cache</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Keluar dari program apabila tetingkap lirik ditutup</value>
|
<value>Keluar dari program apabila tetingkap lirik ditutup</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Eksport sejarah main</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Eksport Tetapan</value>
|
<value>Eksport Tetapan</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Pengurus Tetapan</value>
|
<value>Pengurus Tetapan</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Sejarah Main</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Layari Hab Perkongsian Sumber Dalam Talian</value>
|
<value>Layari Hab Perkongsian Sumber Dalam Talian</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Permulaan</value>
|
<value>Permulaan</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Statistik</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Hentikan main balik apabila tetingkap galeri muzik ditutup</value>
|
<value>Hentikan main balik apabila tetingkap galeri muzik ditutup</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Mod Standard</value>
|
<value>Mod Standard</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Sumber</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Bulan Ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Suku Tahun Ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Minggu Ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Tahun Ini</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Trek Dimainkan</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Keluar Program</value>
|
<value>Keluar Program</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Modo Acoplado</value>
|
<value>Modo Acoplado</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Erro</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Exportação bem-sucedida</value>
|
<value>Exportação bem-sucedida</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>A preparar a limpeza da cache...</value>
|
<value>A preparar a limpeza da cache...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Pronto</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Todas as Músicas</value>
|
<value>Todas as Músicas</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Limpar fila de reprodução</value>
|
<value>Limpar fila de reprodução</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Quantidade de Desfoque</value>
|
<value>Quantidade de Desfoque</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Cache</value>
|
<value>Cache</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Sair do programa ao fechar a janela de letras</value>
|
<value>Sair do programa ao fechar a janela de letras</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Exportar histórico de jogos</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Exportar Definições</value>
|
<value>Exportar Definições</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Gestor de Definições</value>
|
<value>Gestor de Definições</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>História do jogo</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Explorar o Centro de Partilha de Recursos Online</value>
|
<value>Explorar o Centro de Partilha de Recursos Online</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Arranque</value>
|
<value>Arranque</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Estatísticas</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Parar reprodução ao fechar a janela da galeria de música</value>
|
<value>Parar reprodução ao fechar a janela da galeria de música</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Modo Padrão</value>
|
<value>Modo Padrão</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Fontes</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Este mês</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Este trimestre</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Esta semana</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Este ano</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Faixas reproduzidas</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Sair do Programa</value>
|
<value>Sair do Programa</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Закрепленный режим</value>
|
<value>Закрепленный режим</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Ошибка</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Экспорт выполнен успешно</value>
|
<value>Экспорт выполнен успешно</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Подготовка к очистке кэша...</value>
|
<value>Подготовка к очистке кэша...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Готовые</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>Обнаружен путь к корневому каталогу. Полный индекс диска может содержать большое количество файлов, не относящихся к мультимедиа, что приведет к слишком долгому сканированию. Рекомендуется указывать конкретный подкаталог.</value>
|
<value>Обнаружен путь к корневому каталогу. Полный индекс диска может содержать большое количество файлов, не относящихся к мультимедиа, что приведет к слишком долгому сканированию. Рекомендуется указывать конкретный подкаталог.</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Вся музыка</value>
|
<value>Вся музыка</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Очистить очередь воспроизведения</value>
|
<value>Очистить очередь воспроизведения</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Степень размытия</value>
|
<value>Степень размытия</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||||
<value>Включает лог-файлы, кэш онлайн-текстов</value>
|
<value>Включая файлы журналов, кэш онлайновых текстов песен</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Кэш</value>
|
<value>Кэш</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Выходить из программы при закрытии окна текста</value>
|
<value>Выходить из программы при закрытии окна текста</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>Экспорт истории игр</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Экспорт настроек</value>
|
<value>Экспорт настроек</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Менеджер настроек</value>
|
<value>Менеджер настроек</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>История игры</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Просмотреть онлайн-центр обмена</value>
|
<value>Просмотреть онлайн-центр обмена</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Запуск</value>
|
<value>Запуск</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Статистика</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Останавливать воспроизведение при закрытии Галереи</value>
|
<value>Останавливать воспроизведение при закрытии Галереи</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Стандартный режим</value>
|
<value>Стандартный режим</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Источники</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Этот месяц</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Этот квартал</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>На этой неделе</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>В этом году</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Воспроизведенные треки</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Выход</value>
|
<value>Выход</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>โหมดเชื่อมต่อ (Docked)</value>
|
<value>โหมดเชื่อมต่อ (Docked)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>ข้อผิดพลาด</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>ส่งออกสำเร็จ</value>
|
<value>ส่งออกสำเร็จ</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>กำลังเตรียมล้างแคช...</value>
|
<value>กำลังเตรียมล้างแคช...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>พร้อม</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>พบเส้นทางไดเรกทอรีรากแล้ว ดัชนีของดิสก์ทั้งหมดอาจมีไฟล์ที่ไม่ใช่สื่อจำนวนมากและทำให้การสแกนใช้เวลานานเกินไป ขอแนะนำให้ระบุไดเรกทอรีย่อยเฉพาะ</value>
|
<value>พบเส้นทางไดเรกทอรีรากแล้ว ดัชนีของดิสก์ทั้งหมดอาจมีไฟล์ที่ไม่ใช่สื่อจำนวนมากและทำให้การสแกนใช้เวลานานเกินไป ขอแนะนำให้ระบุไดเรกทอรีย่อยเฉพาะ</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>เพลงทั้งหมด</value>
|
<value>เพลงทั้งหมด</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>ล้างคิวการเล่น</value>
|
<value>ล้างคิวการเล่น</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>ปริมาณความเบลอ</value>
|
<value>ปริมาณความเบลอ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<data name="SettingsPageCache.Description" xml:space="preserve">
|
||||||
<value>รวมไฟล์บันทึก, แคชเนื้อเพลงจากเครือข่าย</value>
|
<value>รวมถึงไฟล์บันทึก, แคชเนื้อเพลงออนไลน์</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>แคช</value>
|
<value>แคช</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>ออกจากโปรแกรมเมื่อปิดหน้าต่างเนื้อเพลง</value>
|
<value>ออกจากโปรแกรมเมื่อปิดหน้าต่างเนื้อเพลง</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>ส่งออกประวัติการเล่น</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>ส่งออกการตั้งค่า</value>
|
<value>ส่งออกการตั้งค่า</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>ตัวจัดการการตั้งค่า</value>
|
<value>ตัวจัดการการตั้งค่า</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>ประวัติการเล่น</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>เรียกดูศูนย์แบ่งปันทรัพยากรออนไลน์</value>
|
<value>เรียกดูศูนย์แบ่งปันทรัพยากรออนไลน์</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>การเริ่มระบบ</value>
|
<value>การเริ่มระบบ</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>สถิติ</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>หยุดเล่นเมื่อปิดหน้าต่างคลังเพลง</value>
|
<value>หยุดเล่นเมื่อปิดหน้าต่างคลังเพลง</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>โหมดมาตรฐาน</value>
|
<value>โหมดมาตรฐาน</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>แหล่งข้อมูล</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>เดือนนี้</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>ไตรมาสนี้</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>สัปดาห์นี้</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>ปีนี้</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>เพลงที่เล่น</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>ออกจากโปรแกรม</value>
|
<value>ออกจากโปรแกรม</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>Chế độ neo (Docked)</value>
|
<value>Chế độ neo (Docked)</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>Lỗi</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>Xuất thành công</value>
|
<value>Xuất thành công</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>Đang chuẩn bị xóa bộ nhớ cache...</value>
|
<value>Đang chuẩn bị xóa bộ nhớ cache...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>Sẵn sàng</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<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>
|
<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>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>Tất cả bài hát</value>
|
<value>Tất cả bài hát</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>Xóa hàng đợi phát</value>
|
<value>Xóa hàng đợi phát</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -691,7 +703,7 @@
|
|||||||
<value>Độ mờ</value>
|
<value>Độ mờ</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Description" xml:space="preserve">
|
<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ý và bộ nhớ đệm lời bài hát trực tuyến.</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="SettingsPageCache.Header" xml:space="preserve">
|
<data name="SettingsPageCache.Header" xml:space="preserve">
|
||||||
<value>Bộ nhớ đệm</value>
|
<value>Bộ nhớ đệm</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<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>
|
<value>Thoát chương trình khi đóng cửa sổ lời bài hát</value>
|
||||||
</data>
|
</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">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>Xuất cài đặt</value>
|
<value>Xuất cài đặt</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>Trình quản lý cài đặt</value>
|
<value>Trình quản lý cài đặt</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>Lịch sử chơi</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>Duyệt trung tâm chia sẻ tài nguyên trực tuyến</value>
|
<value>Duyệt trung tâm chia sẻ tài nguyên trực tuyến</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>Khởi động</value>
|
<value>Khởi động</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>Thống kê</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>Dừng phát khi đóng cửa sổ thư viện nhạc</value>
|
<value>Dừng phát khi đóng cửa sổ thư viện nhạc</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>Chế độ tiêu chuẩn</value>
|
<value>Chế độ tiêu chuẩn</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>Nguồn</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>Tháng này</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>Quý này</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>Tuần này</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>Năm nay</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>Các bài hát đã phát</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>Thoát chương trình</value>
|
<value>Thoát chương trình</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>停靠模式</value>
|
<value>停靠模式</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>错误</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>导出成功</value>
|
<value>导出成功</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>正在准备清理缓存...</value>
|
<value>正在准备清理缓存...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>就绪</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>已检测到根目录路径。全磁盘索引可能包含大量非媒体文件,导致扫描时间过长。建议指定一个特定的子目录。</value>
|
<value>已检测到根目录路径。全磁盘索引可能包含大量非媒体文件,导致扫描时间过长。建议指定一个特定的子目录。</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>所有音乐</value>
|
<value>所有音乐</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>清除播放队列</value>
|
<value>清除播放队列</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>关闭歌词窗口时退出程序</value>
|
<value>关闭歌词窗口时退出程序</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>导出播放记录</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>导出设置</value>
|
<value>导出设置</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>设置管理器</value>
|
<value>设置管理器</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>播放记录</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>浏览在线资源共享中心</value>
|
<value>浏览在线资源共享中心</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>启动</value>
|
<value>启动</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>统计数据</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>关闭音乐库窗口时停止播放</value>
|
<value>关闭音乐库窗口时停止播放</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>标准模式</value>
|
<value>标准模式</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>播放源</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>本月</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>本季度</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>本周</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>本年度</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>播放的曲目</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>退出程序</value>
|
<value>退出程序</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -159,6 +159,9 @@
|
|||||||
<data name="DockedMode" xml:space="preserve">
|
<data name="DockedMode" xml:space="preserve">
|
||||||
<value>停靠模式</value>
|
<value>停靠模式</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="Error" xml:space="preserve">
|
||||||
|
<value>錯誤</value>
|
||||||
|
</data>
|
||||||
<data name="ExportSettingsSuccess" xml:space="preserve">
|
<data name="ExportSettingsSuccess" xml:space="preserve">
|
||||||
<value>匯出成功</value>
|
<value>匯出成功</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -183,6 +186,9 @@
|
|||||||
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
<data name="FileSystemServicePrepareToClean" xml:space="preserve">
|
||||||
<value>準備清除快取...</value>
|
<value>準備清除快取...</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="FileSystemServiceReady" xml:space="preserve">
|
||||||
|
<value>準備就緒</value>
|
||||||
|
</data>
|
||||||
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
<data name="FileSystemServiceRootDirectoryWarning" xml:space="preserve">
|
||||||
<value>已偵測到根目錄路徑。全磁碟索引可能包含大量非媒體檔案,導致掃描時間過長。建議指定特定的子目錄。</value>
|
<value>已偵測到根目錄路徑。全磁碟索引可能包含大量非媒體檔案,導致掃描時間過長。建議指定特定的子目錄。</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -408,6 +414,12 @@
|
|||||||
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
<data name="MusicGalleryPageAllSongs" xml:space="preserve">
|
||||||
<value>所有音樂</value>
|
<value>所有音樂</value>
|
||||||
</data>
|
</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">
|
<data name="MusicGalleryPageEmptyPlayingQueue.Text" xml:space="preserve">
|
||||||
<value>清除播放佇列</value>
|
<value>清除播放佇列</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -514,7 +526,7 @@
|
|||||||
<value>自動同步頻率</value>
|
<value>自動同步頻率</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncIntervalDisabled.Content" xml:space="preserve">
|
||||||
<value>決不</value>
|
<value>從不</value>
|
||||||
</data>
|
</data>
|
||||||
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
|
<data name="MusicSettingsControlAutoSyncIntervalEveryDay.Content" xml:space="preserve">
|
||||||
<value>每日</value>
|
<value>每日</value>
|
||||||
@@ -846,6 +858,9 @@
|
|||||||
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageExitOnLyricsWindowClosed.Header" xml:space="preserve">
|
||||||
<value>關閉歌詞視窗時結束程式</value>
|
<value>關閉歌詞視窗時結束程式</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageExportPlayHistoryButton.Content" xml:space="preserve">
|
||||||
|
<value>匯出播放記錄</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
<data name="SettingsPageExportSettingsButton.Content" xml:space="preserve">
|
||||||
<value>匯出設定</value>
|
<value>匯出設定</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1299,6 +1314,9 @@
|
|||||||
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
<data name="SettingsPageSettingsManager.Header" xml:space="preserve">
|
||||||
<value>設定管理器</value>
|
<value>設定管理器</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageSettingsPlayHistory.Header" xml:space="preserve">
|
||||||
|
<value>播放記錄</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
<data name="SettingsPageShareHub.Content" xml:space="preserve">
|
||||||
<value>瀏覽線上資源共享中心</value>
|
<value>瀏覽線上資源共享中心</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1374,6 +1392,9 @@
|
|||||||
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
<data name="SettingsPageStartup.Text" xml:space="preserve">
|
||||||
<value>啟動</value>
|
<value>啟動</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="SettingsPageStats.Content" xml:space="preserve">
|
||||||
|
<value>統計資料</value>
|
||||||
|
</data>
|
||||||
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
<data name="SettingsPageStopTrackOnGalleryWindowClosed.Header" xml:space="preserve">
|
||||||
<value>關閉音樂庫視窗時停止播放</value>
|
<value>關閉音樂庫視窗時停止播放</value>
|
||||||
</data>
|
</data>
|
||||||
@@ -1446,6 +1467,39 @@
|
|||||||
<data name="StandardMode" xml:space="preserve">
|
<data name="StandardMode" xml:space="preserve">
|
||||||
<value>標準模式</value>
|
<value>標準模式</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="StatsDashboardControlSources.Text" xml:space="preserve">
|
||||||
|
<value>來源</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisMonth.Header" xml:space="preserve">
|
||||||
|
<value>本月</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisQuarter.Header" xml:space="preserve">
|
||||||
|
<value>本季</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisWeek.Header" xml:space="preserve">
|
||||||
|
<value>本週</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlThisYear.Header" xml:space="preserve">
|
||||||
|
<value>今年</value>
|
||||||
|
</data>
|
||||||
|
<data name="StatsDashboardControlToday.Header" 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="StatsDashboardControlTracksPlayed.Text" xml:space="preserve">
|
||||||
|
<value>播放曲目</value>
|
||||||
|
</data>
|
||||||
<data name="SystemTrayExit.Text" xml:space="preserve">
|
<data name="SystemTrayExit.Text" xml:space="preserve">
|
||||||
<value>結束程式</value>
|
<value>結束程式</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
|||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
|
using System.IO;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
namespace BetterLyrics.WinUI3.ViewModels
|
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]
|
[RelayCommand]
|
||||||
private void ClearCacheFiles()
|
private void ClearCacheFiles()
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using BetterLyrics.WinUI3.Controls;
|
using BetterLyrics.WinUI3.Controls;
|
||||||
|
using BetterLyrics.WinUI3.Enums;
|
||||||
using BetterLyrics.WinUI3.Helper;
|
using BetterLyrics.WinUI3.Helper;
|
||||||
using BetterLyrics.WinUI3.Hooks;
|
using BetterLyrics.WinUI3.Hooks;
|
||||||
using BetterLyrics.WinUI3.Models;
|
using BetterLyrics.WinUI3.Models;
|
||||||
@@ -9,6 +10,7 @@ using BetterLyrics.WinUI3.Services.SettingsService;
|
|||||||
using BetterLyrics.WinUI3.Views;
|
using BetterLyrics.WinUI3.Views;
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Input;
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using CommunityToolkit.WinUI.Animations;
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
using System;
|
using System;
|
||||||
@@ -51,23 +53,25 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
|
|
||||||
public void SyncFolder(MediaFolder folder)
|
public void SyncFolder(MediaFolder folder)
|
||||||
{
|
{
|
||||||
if (folder.IsIndexing) return;
|
if (folder.IsProcessing) return;
|
||||||
|
|
||||||
_ = Task.Run(async () => await _fileSystemService.ScanMediaFolderAsync(folder, CancellationToken.None));
|
_ = Task.Run(async () => await _fileSystemService.ScanMediaFolderAsync(folder, CancellationToken.None));
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task AddMediaSourceAsync(string protocolType)
|
private async Task AddMediaSourceAsync(string fileSourceTypeName)
|
||||||
{
|
{
|
||||||
|
FileSourceType fileSourceType = Enum.Parse<FileSourceType>(fileSourceTypeName);
|
||||||
|
|
||||||
var dialog = new ContentDialog
|
var dialog = new ContentDialog
|
||||||
{
|
{
|
||||||
XamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot,
|
XamlRoot = WindowHook.GetWindow<SettingsWindow>()?.Content.XamlRoot,
|
||||||
Style = Application.Current.Resources["DefaultContentDialogStyle"] as Style,
|
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"),
|
PrimaryButtonText = _localizationService.GetLocalizedString("Add"),
|
||||||
CloseButtonText = _localizationService.GetLocalizedString("Cancel"),
|
CloseButtonText = _localizationService.GetLocalizedString("Cancel"),
|
||||||
DefaultButton = ContentDialogButton.Primary,
|
DefaultButton = ContentDialogButton.Primary,
|
||||||
Content = new RemoteServerConfigControl(protocolType)
|
Content = new RemoteServerConfigControl(fileSourceType)
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.PrimaryButtonClick += async (s, e) =>
|
dialog.PrimaryButtonClick += async (s, e) =>
|
||||||
@@ -86,13 +90,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
{
|
{
|
||||||
var tempFolder = configControl.GetConfig();
|
var tempFolder = configControl.GetConfig();
|
||||||
|
|
||||||
if (protocolType == "Local")
|
if (fileSourceType == FileSourceType.Local)
|
||||||
{
|
{
|
||||||
string path = tempFolder.UriPath;
|
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;
|
var normalizedPath = Path.GetFullPath(path).TrimEnd(Path.DirectorySeparatorChar) + Path.DirectorySeparatorChar;
|
||||||
@@ -126,6 +130,29 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
}
|
}
|
||||||
else
|
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('/') + "/";
|
var newUriString = tempFolder.GetStandardUri().AbsoluteUri.TrimEnd('/') + "/";
|
||||||
|
|
||||||
foreach (var existingFolder in AppSettings.LocalMediaFolders)
|
foreach (var existingFolder in AppSettings.LocalMediaFolders)
|
||||||
@@ -208,10 +235,5 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
await dialog.ShowAsync();
|
await dialog.ShowAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ShowErrorTip(RemoteServerConfigControl control, string message)
|
|
||||||
{
|
|
||||||
control.ShowError(message);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
|
|
||||||
public SongsTabInfo? SelectedSongsTabInfo => AppSettings.StarredPlaylists.ElementAtOrDefault(SelectedSongsTabInfoIndex);
|
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 ExtendedTrack TrackRightTapped { get; set; } = new();
|
||||||
|
|
||||||
@@ -121,6 +122,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
RefreshSongs();
|
RefreshSongs();
|
||||||
|
|
||||||
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
_settingsService.AppSettings.LocalMediaFolders.CollectionChanged += LocalMediaFolders_CollectionChanged;
|
||||||
|
_settingsService.AppSettings.LocalMediaFolders.ItemPropertyChanged += LocalMediaFolders_ItemPropertyChanged;
|
||||||
|
|
||||||
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
_mediaPlayer.MediaOpened += MediaPlayer_MediaOpened;
|
||||||
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
_mediaPlayer.MediaEnded += MediaPlayer_MediaEnded;
|
||||||
@@ -138,6 +140,11 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
_smtc.PlaybackPositionChangeRequested += Smtc_PlaybackPositionChangeRequested;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void LocalMediaFolders_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
IsDataSyncError = AppSettings.LocalMediaFolders.Any(x => x.StatusSeverity == InfoBarSeverity.Error);
|
||||||
|
}
|
||||||
|
|
||||||
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
private void TrackPlayingQueue_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
||||||
{
|
{
|
||||||
AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.DecodedAbsoluteUri)];
|
AppSettings.MusicGallerySettings.PlayQueuePaths = [.. TrackPlayingQueue.Select(x => x.Track.DecodedAbsoluteUri)];
|
||||||
@@ -274,8 +281,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
{
|
{
|
||||||
_refreshSongsTimer.Debounce(() =>
|
_refreshSongsTimer.Debounce(() =>
|
||||||
{
|
{
|
||||||
IsDataLoading = true;
|
|
||||||
|
|
||||||
_ = Task.Run(async () =>
|
_ = Task.Run(async () =>
|
||||||
{
|
{
|
||||||
var enabledFolderIds = _settingsService.AppSettings.LocalMediaFolders
|
var enabledFolderIds = _settingsService.AppSettings.LocalMediaFolders
|
||||||
@@ -304,8 +309,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
IsLocalMediaNotFound = !_filteredTracks.Any();
|
IsLocalMediaNotFound = !_filteredTracks.Any();
|
||||||
|
|
||||||
ApplySongOrderType();
|
ApplySongOrderType();
|
||||||
|
|
||||||
IsDataLoading = false;
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, Time.DebounceTimeout);
|
}, Time.DebounceTimeout);
|
||||||
@@ -491,8 +494,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
|
|
||||||
if (targetFolder == null)
|
if (targetFolder == null)
|
||||||
{
|
{
|
||||||
|
throw new FileNotFoundException(null, PlayingTrack.DecodedAbsoluteUri);
|
||||||
throw new Exception($"找不到文件 {PlayingTrack.FileName} 对应的存储配置。请检查服务器设置是否已启用。");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
_currentProvider = targetFolder.CreateFileSystem();
|
_currentProvider = targetFolder.CreateFileSystem();
|
||||||
@@ -500,7 +502,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
|
|
||||||
await _currentProvider.ConnectAsync();
|
await _currentProvider.ConnectAsync();
|
||||||
|
|
||||||
var fileCacheStub = new FileCacheEntity
|
var fileCacheStub = new FilesIndexItem
|
||||||
{
|
{
|
||||||
Uri = PlayingTrack.Uri
|
Uri = PlayingTrack.Uri
|
||||||
};
|
};
|
||||||
@@ -509,7 +511,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
|
|
||||||
if (sourceStream == null)
|
if (sourceStream == null)
|
||||||
{
|
{
|
||||||
throw new FileNotFoundException("无法打开文件流");
|
throw new FileNotFoundException(null, fileCacheStub.Uri);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (sourceStream.CanSeek)
|
if (sourceStream.CanSeek)
|
||||||
@@ -561,7 +563,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
ToastHelper.ShowToast($"PlayTrackAsync: Error", ex.Message, InfoBarSeverity.Error);
|
ToastHelper.ShowToast("Error", ex.Message, InfoBarSeverity.Error);
|
||||||
_timelineController.Pause();
|
_timelineController.Pause();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -634,12 +636,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[RelayCommand]
|
|
||||||
private void SwitchPlaybackOrder()
|
|
||||||
{
|
|
||||||
AppSettings.MusicGallerySettings.PlaybackOrder = AppSettings.MusicGallerySettings.PlaybackOrder.GetNext();
|
|
||||||
}
|
|
||||||
|
|
||||||
[RelayCommand]
|
[RelayCommand]
|
||||||
private async Task StopTrackAsync()
|
private async Task StopTrackAsync()
|
||||||
{
|
{
|
||||||
@@ -665,6 +661,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
|||||||
{
|
{
|
||||||
RefreshSongs();
|
RefreshSongs();
|
||||||
}
|
}
|
||||||
|
else if (message.PropertyName == nameof(MediaFolder.IsProcessing))
|
||||||
|
{
|
||||||
|
IsDataSyncing = message.NewValue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using BetterLyrics.WinUI3.Enums;
|
||||||
|
using BetterLyrics.WinUI3.Helper;
|
||||||
|
using BetterLyrics.WinUI3.Models;
|
||||||
|
using BetterLyrics.WinUI3.Models.Stats;
|
||||||
|
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using CommunityToolkit.Mvvm.Input;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.ObjectModel;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace BetterLyrics.WinUI3.ViewModels
|
||||||
|
{
|
||||||
|
public partial class StatsDashboardControlViewModel : ObservableObject
|
||||||
|
{
|
||||||
|
private readonly IPlayHistoryService _playHistoryService;
|
||||||
|
|
||||||
|
public StatsDashboardControlViewModel(IPlayHistoryService playHistoryService)
|
||||||
|
{
|
||||||
|
_playHistoryService = playHistoryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[ObservableProperty] public partial bool IsLoading { get; set; }
|
||||||
|
|
||||||
|
[ObservableProperty] public partial TimeSpan TotalDuration { get; set; }
|
||||||
|
[ObservableProperty] public partial int TotalTracksPlayed { get; set; }
|
||||||
|
[ObservableProperty] public partial string TopPlayerName { get; set; } = "N/A";
|
||||||
|
|
||||||
|
public ObservableCollection<SongPlayCount> TopSongs { get; } = new();
|
||||||
|
public ObservableCollection<ArtistPlayCount> TopArtists { get; } = new();
|
||||||
|
|
||||||
|
public ObservableCollection<PlayerStatDisplayItem> PlayerStats { get; } = new();
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 核心方法:根据选中的 Tab 加载数据
|
||||||
|
/// </summary>
|
||||||
|
[RelayCommand]
|
||||||
|
public async Task LoadDataAsync(StatsRange range)
|
||||||
|
{
|
||||||
|
if (IsLoading) return;
|
||||||
|
IsLoading = true;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var (start, end) = CalculateDateRange(range);
|
||||||
|
|
||||||
|
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start, end);
|
||||||
|
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start, end);
|
||||||
|
var topSongsTask = _playHistoryService.GetTopSongsAsync(start, end, 10);
|
||||||
|
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start, end, 10);
|
||||||
|
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start, end);
|
||||||
|
|
||||||
|
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||||
|
|
||||||
|
TotalDuration = await durationTask;
|
||||||
|
var logs = await logsTask;
|
||||||
|
TotalTracksPlayed = logs.Count;
|
||||||
|
|
||||||
|
TopSongs.Clear();
|
||||||
|
foreach (var item in await topSongsTask) TopSongs.Add(item);
|
||||||
|
|
||||||
|
TopArtists.Clear();
|
||||||
|
foreach (var item in await topArtistsTask) TopArtists.Add(item);
|
||||||
|
|
||||||
|
UpdatePlayerStats(await playersTask);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
IsLoading = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[RelayCommand]
|
||||||
|
private async Task GenerateTestDataAsync()
|
||||||
|
{
|
||||||
|
await _playHistoryService.GenerateTestDataAsync(10000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将原始统计数据转换为带进度条宽度的 UI 数据
|
||||||
|
/// </summary>
|
||||||
|
private void UpdatePlayerStats(List<PlayerStats> stats)
|
||||||
|
{
|
||||||
|
PlayerStats.Clear();
|
||||||
|
|
||||||
|
if (stats == null || stats.Count == 0)
|
||||||
|
{
|
||||||
|
TopPlayerName = "N/A";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
double maxCount = stats.Max(x => x.Count);
|
||||||
|
if (maxCount == 0) maxCount = 1;
|
||||||
|
|
||||||
|
var topPlayer = stats.OrderByDescending(x => x.Count).FirstOrDefault();
|
||||||
|
TopPlayerName = PlayerIdHelper.GetDisplayName(topPlayer?.PlayerId) ?? "N/A";
|
||||||
|
|
||||||
|
foreach (var item in stats.OrderByDescending(x => x.Count))
|
||||||
|
{
|
||||||
|
PlayerStats.Add(new PlayerStatDisplayItem
|
||||||
|
{
|
||||||
|
PlayerId = item.PlayerId,
|
||||||
|
PlayCount = item.Count,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private (DateTime Start, DateTime End) CalculateDateRange(StatsRange range)
|
||||||
|
{
|
||||||
|
DateTime nowLocal = DateTime.Now;
|
||||||
|
DateTime startLocal = nowLocal.Date; // 默认为本地今天 00:00
|
||||||
|
|
||||||
|
switch (range)
|
||||||
|
{
|
||||||
|
case StatsRange.Day:
|
||||||
|
break;
|
||||||
|
case StatsRange.Week:
|
||||||
|
int dayOfWeek = (int)nowLocal.DayOfWeek;
|
||||||
|
if (dayOfWeek == 0) dayOfWeek = 7; // 处理周日
|
||||||
|
startLocal = nowLocal.Date.AddDays(-(dayOfWeek - 1));
|
||||||
|
break;
|
||||||
|
case StatsRange.Month:
|
||||||
|
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, 1);
|
||||||
|
break;
|
||||||
|
case StatsRange.Quarter:
|
||||||
|
int quarterStartMonth = (nowLocal.Month - 1) / 3 * 3 + 1;
|
||||||
|
startLocal = new DateTime(nowLocal.Year, quarterStartMonth, 1);
|
||||||
|
break;
|
||||||
|
case StatsRange.Year:
|
||||||
|
startLocal = new DateTime(nowLocal.Year, 1, 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 数据库里的 StartedAt 是 UTC,所以查询条件必须也是 UTC
|
||||||
|
DateTime startUtc = startLocal.ToUniversalTime();
|
||||||
|
DateTime endUtc = nowLocal.ToUniversalTime();
|
||||||
|
|
||||||
|
return (startUtc, endUtc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,14 +29,14 @@
|
|||||||
</Page.Resources>
|
</Page.Resources>
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid Padding="12,16,12,64" ColumnSpacing="12">
|
<Grid Padding="12,4,12,68" ColumnSpacing="12">
|
||||||
<Grid.ColumnDefinitions>
|
<Grid.ColumnDefinitions>
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="3*" />
|
<ColumnDefinition Width="Auto" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
<ScrollViewer Grid.Column="0">
|
<ScrollViewer x:Name="LeftSidePanel" Grid.Column="0">
|
||||||
<Grid>
|
<Grid>
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
@@ -49,7 +49,7 @@
|
|||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="MusicGalleryPagePlaylist"
|
x:Uid="MusicGalleryPagePlaylist"
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Margin="1,0,0,6"
|
Margin="1,8,0,6"
|
||||||
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
Style="{StaticResource SettingsSectionHeaderTextBlockStyle}" />
|
||||||
|
|
||||||
<Grid Grid.Row="1">
|
<Grid Grid.Row="1">
|
||||||
@@ -98,7 +98,7 @@
|
|||||||
Click="RemoveFromPlaylistButton_Click"
|
Click="RemoveFromPlaylistButton_Click"
|
||||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
FontSize=16,
|
FontSize=16,
|
||||||
Glyph=}"
|
Glyph=}"
|
||||||
Style="{StaticResource GhostButtonStyle}"
|
Style="{StaticResource GhostButtonStyle}"
|
||||||
Visibility="{x:Bind IsDefault, Converter={StaticResource BoolNegationToVisibilityConverter}}">
|
Visibility="{x:Bind IsDefault, Converter={StaticResource BoolNegationToVisibilityConverter}}">
|
||||||
<ToolTipService.ToolTip>
|
<ToolTipService.ToolTip>
|
||||||
@@ -192,8 +192,11 @@
|
|||||||
</Grid>
|
</Grid>
|
||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
|
|
||||||
<Grid x:Name="SongViewer" Grid.Column="1">
|
<controls:ContentSizer Grid.Column="1" TargetControl="{x:Bind LeftSidePanel}" />
|
||||||
|
|
||||||
|
<Grid x:Name="SongViewer" Grid.Column="2">
|
||||||
<Grid.RowDefinitions>
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="Auto" />
|
<RowDefinition Height="Auto" />
|
||||||
<RowDefinition Height="*" />
|
<RowDefinition Height="*" />
|
||||||
</Grid.RowDefinitions>
|
</Grid.RowDefinitions>
|
||||||
@@ -249,12 +252,12 @@
|
|||||||
<AutoSuggestBox
|
<AutoSuggestBox
|
||||||
x:Name="SongSearchBox"
|
x:Name="SongSearchBox"
|
||||||
x:Uid="MusicGalleryPageSongSearchBox"
|
x:Uid="MusicGalleryPageSongSearchBox"
|
||||||
|
Margin="0,0,128,0"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
QueryIcon="Find"
|
QueryIcon="Find"
|
||||||
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
|
Text="{x:Bind ViewModel.SongSearchQuery, Mode=TwoWay}" />
|
||||||
|
|
||||||
<Grid>
|
<Grid>
|
||||||
|
|
||||||
<StackPanel
|
<StackPanel
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
Orientation="Horizontal"
|
Orientation="Horizontal"
|
||||||
@@ -302,7 +305,20 @@
|
|||||||
|
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
||||||
<SemanticZoom Grid.Row="1">
|
<InfoBar
|
||||||
|
x:Uid="MusicGalleryPageDataSync"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="{x:Bind ViewModel.IsDataSyncing, Mode=OneWay}" />
|
||||||
|
|
||||||
|
<InfoBar
|
||||||
|
x:Uid="MusicGalleryPageDataSyncError"
|
||||||
|
Grid.Row="1"
|
||||||
|
IsClosable="False"
|
||||||
|
IsOpen="{x:Bind ViewModel.IsDataSyncError, Mode=OneWay}"
|
||||||
|
Severity="Error" />
|
||||||
|
|
||||||
|
<SemanticZoom Grid.Row="2">
|
||||||
<SemanticZoom.ZoomedInView>
|
<SemanticZoom.ZoomedInView>
|
||||||
<ListView
|
<ListView
|
||||||
x:Name="SongListView"
|
x:Name="SongListView"
|
||||||
@@ -310,39 +326,15 @@
|
|||||||
SelectionChanged="SongListView_SelectionChanged"
|
SelectionChanged="SongListView_SelectionChanged"
|
||||||
SelectionMode="Multiple">
|
SelectionMode="Multiple">
|
||||||
<ListView.ContextFlyout>
|
<ListView.ContextFlyout>
|
||||||
<MenuBarItemFlyout>
|
<MenuBarItemFlyout Opened="AddToMenuBarItemFlyout_Opened">
|
||||||
<MenuFlyoutSubItem x:Uid="MusicGalleryPageAddToPlayingQueue" IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}">
|
<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="MusicGalleryPageAddToNext" Click="AddSongToQueueNextMenuFlyoutItem_Click" />
|
||||||
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
|
<MenuFlyoutItem x:Uid="MusicGalleryPageAddToEnd" Click="AddSongToQueueEndMenuFlyoutItem_Click" />
|
||||||
</MenuFlyoutSubItem>
|
</MenuFlyoutSubItem>
|
||||||
<MenuFlyoutItem
|
<MenuFlyoutSubItem
|
||||||
|
x:Name="AddToCustomListMenuFlyoutSubItem"
|
||||||
x:Uid="MusicGalleryPageAddToCustomList"
|
x:Uid="MusicGalleryPageAddToCustomList"
|
||||||
Click="AddToPlaylistMenuFlyoutItem_Click"
|
IsEnabled="{x:Bind ViewModel.SelectedTracks.Count, Mode=OneWay, Converter={StaticResource IntToBoolConverter}}" />
|
||||||
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>
|
|
||||||
</MenuBarItemFlyout>
|
</MenuBarItemFlyout>
|
||||||
</ListView.ContextFlyout>
|
</ListView.ContextFlyout>
|
||||||
<ListView.ItemTemplate>
|
<ListView.ItemTemplate>
|
||||||
@@ -448,7 +440,7 @@
|
|||||||
</SemanticZoom.ZoomedOutView>
|
</SemanticZoom.ZoomedOutView>
|
||||||
</SemanticZoom>
|
</SemanticZoom>
|
||||||
|
|
||||||
<Grid Grid.Row="1" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
<Grid Grid.Row="2" Visibility="{x:Bind ViewModel.IsLocalMediaNotFound, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}">
|
||||||
<StackPanel
|
<StackPanel
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
VerticalAlignment="Center"
|
||||||
@@ -463,264 +455,157 @@
|
|||||||
|
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
<Grid x:Name="PlayQueue" Grid.Column="2">
|
</Grid>
|
||||||
<Grid.RowDefinitions>
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="Auto" />
|
|
||||||
<RowDefinition Height="*" />
|
|
||||||
</Grid.RowDefinitions>
|
|
||||||
|
|
||||||
<Grid Grid.Row="0">
|
<Grid
|
||||||
|
x:Name="PlayQueue"
|
||||||
|
Width="300"
|
||||||
|
Margin="0,4,4,72"
|
||||||
|
Padding="12,16,12,0"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Background="{ThemeResource AcrylicBackgroundFillColorDefaultBrush}"
|
||||||
|
BorderBrush="{ThemeResource ControlElevationBorderBrush}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="8"
|
||||||
|
Translation="310,0,0">
|
||||||
|
<Grid.TranslationTransition>
|
||||||
|
<Vector3Transition />
|
||||||
|
</Grid.TranslationTransition>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
|
<Grid Grid.Row="0">
|
||||||
|
<TextBlock
|
||||||
|
x:Uid="MusicGalleryPagePlayingQueue"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<Grid Grid.Row="2">
|
||||||
|
<Grid.ColumnDefinitions>
|
||||||
|
<ColumnDefinition Width="*" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
<ColumnDefinition Width="Auto" />
|
||||||
|
</Grid.ColumnDefinitions>
|
||||||
|
|
||||||
|
<StackPanel
|
||||||
|
Grid.Column="0"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Orientation="Horizontal">
|
||||||
|
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
|
||||||
|
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
||||||
|
<TextBlock Text="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}" />
|
||||||
|
</StackPanel>
|
||||||
|
|
||||||
|
<!-- Stop media session -->
|
||||||
|
<Button
|
||||||
|
Grid.Column="1"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Command="{x:Bind ViewModel.StopTrackCommand}"
|
||||||
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
FontSize=16,
|
||||||
|
Glyph=}"
|
||||||
|
Style="{StaticResource GhostButtonStyle}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</Button>
|
||||||
|
<!-- Scroll to playing item -->
|
||||||
|
<Button
|
||||||
|
Grid.Column="3"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="ScrollToPlayingItemButton_Click"
|
||||||
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
FontSize=16,
|
||||||
|
Glyph=}"
|
||||||
|
Style="{StaticResource GhostButtonStyle}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</Button>
|
||||||
|
<!-- Empty play queue -->
|
||||||
|
<Button
|
||||||
|
Grid.Column="4"
|
||||||
|
HorizontalAlignment="Right"
|
||||||
|
Click="EmptyPlayingQueueButton_Click"
|
||||||
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
FontSize=16,
|
||||||
|
Glyph=}"
|
||||||
|
Style="{StaticResource GhostButtonStyle}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
<ListView
|
||||||
|
x:Name="PlayingQueueListView"
|
||||||
|
Grid.Row="3"
|
||||||
|
ItemsSource="{x:Bind ViewModel.TrackPlayingQueue, Mode=OneWay}"
|
||||||
|
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
|
||||||
|
<ListView.ItemTemplate>
|
||||||
|
<DataTemplate>
|
||||||
|
<Grid Padding="0,6">
|
||||||
|
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
||||||
|
<StackPanel Margin="0,0,36,0">
|
||||||
|
<TextBlock Text="{Binding Track.Title}" TextWrapping="Wrap" />
|
||||||
|
<TextBlock
|
||||||
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
|
||||||
|
Text="{Binding Track.Artist}"
|
||||||
|
TextWrapping="Wrap" />
|
||||||
|
</StackPanel>
|
||||||
|
</Grid>
|
||||||
|
<Grid HorizontalAlignment="Right">
|
||||||
|
<Button
|
||||||
|
Click="RemoveFromPlayingQueueButton_Click"
|
||||||
|
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
FontSize=16,
|
||||||
|
Glyph=}"
|
||||||
|
Style="{StaticResource GhostButtonStyle}">
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<TextBlock x:Uid="MusicGalleryPageRemoveFromPlayingQueue" />
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</DataTemplate>
|
||||||
|
</ListView.ItemTemplate>
|
||||||
|
</ListView>
|
||||||
|
|
||||||
|
<Grid Grid.Row="3">
|
||||||
|
<interactivity:Interaction.Behaviors>
|
||||||
|
<interactivity:DataTriggerBehavior
|
||||||
|
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||||
|
ComparisonCondition="Equal"
|
||||||
|
Value="0">
|
||||||
|
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Visible" />
|
||||||
|
</interactivity:DataTriggerBehavior>
|
||||||
|
<interactivity:DataTriggerBehavior
|
||||||
|
Binding="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}"
|
||||||
|
ComparisonCondition="NotEqual"
|
||||||
|
Value="0">
|
||||||
|
<interactivity:ChangePropertyAction PropertyName="Visibility" Value="Collapsed" />
|
||||||
|
</interactivity:DataTriggerBehavior>
|
||||||
|
</interactivity:Interaction.Behaviors>
|
||||||
|
<StackPanel
|
||||||
|
HorizontalAlignment="Center"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Spacing="12">
|
||||||
|
<Image MaxWidth="100" Source="/Assets/EmptyBox.png" />
|
||||||
<TextBlock
|
<TextBlock
|
||||||
x:Uid="MusicGalleryPagePlayingQueue"
|
x:Uid="MusicGalleryPagePlayingQueueEmpty"
|
||||||
VerticalAlignment="Center"
|
|
||||||
Style="{StaticResource BodyStrongTextBlockStyle}" />
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<Grid Grid.Row="2">
|
|
||||||
<Grid.ColumnDefinitions>
|
|
||||||
<ColumnDefinition Width="*" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
<ColumnDefinition Width="Auto" />
|
|
||||||
</Grid.ColumnDefinitions>
|
|
||||||
|
|
||||||
<StackPanel
|
|
||||||
Grid.Column="0"
|
|
||||||
HorizontalAlignment="Stretch"
|
|
||||||
VerticalAlignment="Center"
|
|
||||||
Orientation="Horizontal">
|
|
||||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=OneWay, Converter={StaticResource IndexToDisplayConverter}}" />
|
|
||||||
<TextBlock Foreground="{ThemeResource TextFillColorSecondaryBrush}" Text="/" />
|
|
||||||
<TextBlock Text="{x:Bind ViewModel.TrackPlayingQueue.Count, Mode=OneWay}" />
|
|
||||||
</StackPanel>
|
|
||||||
|
|
||||||
<!-- Stop media session -->
|
|
||||||
<Button
|
|
||||||
Grid.Column="1"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Command="{x:Bind ViewModel.StopTrackCommand}"
|
|
||||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
|
||||||
FontSize=16,
|
|
||||||
Glyph=}"
|
|
||||||
Style="{StaticResource GhostButtonStyle}">
|
|
||||||
<ToolTipService.ToolTip>
|
|
||||||
<TextBlock x:Uid="MusicGalleryPageStopTrack" />
|
|
||||||
</ToolTipService.ToolTip>
|
|
||||||
</Button>
|
|
||||||
<!-- 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="">
|
|
||||||
<FontIcon.OpacityTransition>
|
|
||||||
<ScalarTransition />
|
|
||||||
</FontIcon.OpacityTransition>
|
|
||||||
</FontIcon>
|
|
||||||
<!-- Repeat one -->
|
|
||||||
<FontIcon
|
|
||||||
x:Name="PlaybackRepeatOne"
|
|
||||||
FontFamily="{StaticResource IconFontFamily}"
|
|
||||||
FontSize="16"
|
|
||||||
Glyph="">
|
|
||||||
<FontIcon.OpacityTransition>
|
|
||||||
<ScalarTransition />
|
|
||||||
</FontIcon.OpacityTransition>
|
|
||||||
</FontIcon>
|
|
||||||
<!-- Shuffle -->
|
|
||||||
<FontIcon
|
|
||||||
x:Name="PlaybackShuffle"
|
|
||||||
FontFamily="{StaticResource IconFontFamily}"
|
|
||||||
FontSize="16"
|
|
||||||
Glyph="">
|
|
||||||
<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=}"
|
|
||||||
Style="{StaticResource GhostButtonStyle}">
|
|
||||||
<ToolTipService.ToolTip>
|
|
||||||
<TextBlock x:Uid="MusicGalleryPageScrollToPlayingItem" />
|
|
||||||
</ToolTipService.ToolTip>
|
|
||||||
</Button>
|
|
||||||
<!-- Empty play queue -->
|
|
||||||
<Button
|
|
||||||
Grid.Column="4"
|
|
||||||
HorizontalAlignment="Right"
|
|
||||||
Click="EmptyPlayingQueueButton_Click"
|
|
||||||
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
|
||||||
FontSize=16,
|
|
||||||
Glyph=}"
|
|
||||||
Style="{StaticResource GhostButtonStyle}">
|
|
||||||
<ToolTipService.ToolTip>
|
|
||||||
<TextBlock x:Uid="MusicGalleryPageEmptyPlayingQueue" />
|
|
||||||
</ToolTipService.ToolTip>
|
|
||||||
</Button>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
<ListView
|
|
||||||
x:Name="PlayingQueueListView"
|
|
||||||
Grid.Row="3"
|
|
||||||
ItemsSource="{x:Bind ViewModel.TrackPlayingQueue, Mode=OneWay}"
|
|
||||||
SelectedIndex="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlayQueueIndex, Mode=TwoWay}">
|
|
||||||
<ListView.ItemTemplate>
|
|
||||||
<DataTemplate>
|
|
||||||
<Grid Padding="0,6">
|
|
||||||
<Grid Tapped="PlayingQueueListVireItemGrid_Tapped">
|
|
||||||
<StackPanel>
|
|
||||||
<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=}"
|
|
||||||
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"
|
HorizontalAlignment="Center"
|
||||||
VerticalAlignment="Center"
|
Foreground="{ThemeResource TextFillColorSecondaryBrush}" />
|
||||||
Spacing="12">
|
</StackPanel>
|
||||||
<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>
|
||||||
</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>
|
</Grid>
|
||||||
</Page>
|
</Page>
|
||||||
|
|||||||
@@ -27,6 +27,15 @@ namespace BetterLyrics.WinUI3.Views
|
|||||||
{
|
{
|
||||||
public MusicGalleryPageViewModel ViewModel => (MusicGalleryPageViewModel)DataContext;
|
public MusicGalleryPageViewModel ViewModel => (MusicGalleryPageViewModel)DataContext;
|
||||||
|
|
||||||
|
public bool IsPlayingQueueOpened
|
||||||
|
{
|
||||||
|
get { return (bool)GetValue(IsPlayingQueueOpenedProperty); }
|
||||||
|
set { SetValue(IsPlayingQueueOpenedProperty, value); }
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly DependencyProperty IsPlayingQueueOpenedProperty =
|
||||||
|
DependencyProperty.Register(nameof(IsPlayingQueueOpened), typeof(bool), typeof(MusicGalleryPage), new PropertyMetadata(false, OnDependencyPropertyChanged));
|
||||||
|
|
||||||
public MusicGalleryPage()
|
public MusicGalleryPage()
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
@@ -34,6 +43,18 @@ namespace BetterLyrics.WinUI3.Views
|
|||||||
ViewModel.AppSettings.MusicGallerySettings.PropertyChanged += MusicGallerySettings_PropertyChanged;
|
ViewModel.AppSettings.MusicGallerySettings.PropertyChanged += MusicGallerySettings_PropertyChanged;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void OnDependencyPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (d is MusicGalleryPage self)
|
||||||
|
{
|
||||||
|
if (e.Property == IsPlayingQueueOpenedProperty)
|
||||||
|
{
|
||||||
|
var newValue = (bool)e.NewValue;
|
||||||
|
self.PlayQueue.Translation = newValue ? new() : new(310, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void ScrollToPlayingItem()
|
private void ScrollToPlayingItem()
|
||||||
{
|
{
|
||||||
if (ViewModel.PlayingQueueItem == null) return;
|
if (ViewModel.PlayingQueueItem == null) return;
|
||||||
@@ -216,40 +237,6 @@ namespace BetterLyrics.WinUI3.Views
|
|||||||
SongListView.SelectedItems.Clear();
|
SongListView.SelectedItems.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void AddToPlaylistMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
//((MenuFlyoutItem)sender).ContextFlyout.ShowAt(PlaylistButton);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void ToBeAddedPlaylistsListViewItemGrid_Tapped(object sender, TappedRoutedEventArgs e)
|
|
||||||
{
|
|
||||||
var songsTabInfo = ((SongsTabInfo)((FrameworkElement)sender).DataContext);
|
|
||||||
if (songsTabInfo.FilterProperty == CommonSongProperty.M3UFilePath)
|
|
||||||
{
|
|
||||||
if (songsTabInfo.FilterValue is string path)
|
|
||||||
{
|
|
||||||
if (File.Exists(path))
|
|
||||||
{
|
|
||||||
var content = File.ReadAllText(path);
|
|
||||||
foreach (var item in ViewModel.SelectedTracks.Select(x => x.DecodedAbsoluteUri).ToList())
|
|
||||||
{
|
|
||||||
if (!content.Contains(item))
|
|
||||||
{
|
|
||||||
content += Environment.NewLine;
|
|
||||||
content += item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
File.WriteAllText(path, content);
|
|
||||||
ToastHelper.ShowToast("TracksAddToPlaylistSuccessfully", null, InfoBarSeverity.Success);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
ToastHelper.ShowToast("TracksAddToPlaylistFailed", null, InfoBarSeverity.Error);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private async void SongListViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
private async void SongListViewItem_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var displayedTracks = SongListView.Items.Cast<ExtendedTrack>();
|
var displayedTracks = SongListView.Items.Cast<ExtendedTrack>();
|
||||||
@@ -282,5 +269,52 @@ namespace BetterLyrics.WinUI3.Views
|
|||||||
ViewModel.SelectFolder(selectedFolder);
|
ViewModel.SelectFolder(selectedFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void ToBeAddedPlaylistsMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
var songsTabInfo = ((SongsTabInfo)((FrameworkElement)sender).DataContext);
|
||||||
|
if (songsTabInfo.FilterProperty == CommonSongProperty.M3UFilePath)
|
||||||
|
{
|
||||||
|
if (songsTabInfo.FilterValue is string path)
|
||||||
|
{
|
||||||
|
if (File.Exists(path))
|
||||||
|
{
|
||||||
|
var content = File.ReadAllText(path);
|
||||||
|
foreach (var item in ViewModel.SelectedTracks.Select(x => x.DecodedAbsoluteUri).ToList())
|
||||||
|
{
|
||||||
|
if (!content.Contains(item))
|
||||||
|
{
|
||||||
|
content += Environment.NewLine;
|
||||||
|
content += item;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
File.WriteAllText(path, content);
|
||||||
|
ToastHelper.ShowToast("TracksAddToPlaylistSuccessfully", null, InfoBarSeverity.Success);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ToastHelper.ShowToast("TracksAddToPlaylistFailed", null, InfoBarSeverity.Error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void AddToMenuBarItemFlyout_Opened(object sender, object e)
|
||||||
|
{
|
||||||
|
AddToCustomListMenuFlyoutSubItem.Items.Clear();
|
||||||
|
foreach (var item in ViewModel.AppSettings.StarredPlaylists)
|
||||||
|
{
|
||||||
|
if (item.FilterProperty == CommonSongProperty.M3UFilePath)
|
||||||
|
{
|
||||||
|
var menuFlyoutItem = new MenuFlyoutItem
|
||||||
|
{
|
||||||
|
Text = item.Name,
|
||||||
|
DataContext = item,
|
||||||
|
};
|
||||||
|
menuFlyoutItem.Click += ToBeAddedPlaylistsMenuFlyoutItem_Click;
|
||||||
|
AddToCustomListMenuFlyoutSubItem.Items.Add(menuFlyoutItem);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@
|
|||||||
Loaded="RootGrid_Loaded"
|
Loaded="RootGrid_Loaded"
|
||||||
Unloaded="RootGrid_Unloaded">
|
Unloaded="RootGrid_Unloaded">
|
||||||
|
|
||||||
<local:MusicGalleryPage x:Name="MusicGalleryPage" />
|
<local:MusicGalleryPage x:Name="MusicGalleryPage" IsPlayingQueueOpened="{Binding ElementName=NowPlayingBar, Path=IsPlayingQueueOpened, Mode=OneWay}" />
|
||||||
|
|
||||||
<local:NowPlayingPage
|
<local:NowPlayingPage
|
||||||
x:Name="NowPlayingPage"
|
x:Name="NowPlayingPage"
|
||||||
@@ -32,6 +32,9 @@
|
|||||||
x:Name="NowPlayingBar"
|
x:Name="NowPlayingBar"
|
||||||
VerticalAlignment="Bottom"
|
VerticalAlignment="Bottom"
|
||||||
IsAutoHideEnabled="False"
|
IsAutoHideEnabled="False"
|
||||||
|
PlaybackOrder="{x:Bind ViewModel.AppSettings.MusicGallerySettings.PlaybackOrder, Mode=TwoWay}"
|
||||||
|
ShowPlaybackOrderButton="True"
|
||||||
|
ShowPlayingQueueButton="True"
|
||||||
ShowSongInfo="True"
|
ShowSongInfo="True"
|
||||||
SongInfoTapped="NowPlayingBar_SongInfoTapped"
|
SongInfoTapped="NowPlayingBar_SongInfoTapped"
|
||||||
TimeTapped="NowPlayingBar_TimeTapped" />
|
TimeTapped="NowPlayingBar_TimeTapped" />
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
using BetterLyrics.WinUI3.Controls;
|
||||||
using BetterLyrics.WinUI3.Enums;
|
using BetterLyrics.WinUI3.Enums;
|
||||||
using BetterLyrics.WinUI3.Extensions;
|
using BetterLyrics.WinUI3.Extensions;
|
||||||
using BetterLyrics.WinUI3.Hooks;
|
using BetterLyrics.WinUI3.Hooks;
|
||||||
@@ -127,5 +128,10 @@ namespace BetterLyrics.WinUI3.Views
|
|||||||
{
|
{
|
||||||
ViewModel.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened = false;
|
ViewModel.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void NowPlayingBar_PlayingQueueClick(object sender, System.EventArgs e)
|
||||||
|
{
|
||||||
|
MusicGalleryPage.IsPlayingQueueOpened = !MusicGalleryPage.IsPlayingQueueOpened;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,12 @@
|
|||||||
Glyph=}"
|
Glyph=}"
|
||||||
Tag="PlaybackLib" />
|
Tag="PlaybackLib" />
|
||||||
|
|
||||||
|
<NavigationViewItem
|
||||||
|
x:Uid="SettingsPageStats"
|
||||||
|
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
|
Glyph=}"
|
||||||
|
Tag="Stats" />
|
||||||
|
|
||||||
<NavigationViewItem
|
<NavigationViewItem
|
||||||
x:Uid="SettingsPageAbout"
|
x:Uid="SettingsPageAbout"
|
||||||
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
Icon="{ui:FontIcon FontFamily={StaticResource IconFontFamily},
|
||||||
@@ -85,6 +91,11 @@
|
|||||||
<uc:MediaSettingsControl />
|
<uc:MediaSettingsControl />
|
||||||
</controls:Case>
|
</controls:Case>
|
||||||
|
|
||||||
|
<!-- Stats -->
|
||||||
|
<controls:Case Value="Stats">
|
||||||
|
<uc:StatsDashboardControl />
|
||||||
|
</controls:Case>
|
||||||
|
|
||||||
<!-- About -->
|
<!-- About -->
|
||||||
<controls:Case Value="About">
|
<controls:Case Value="About">
|
||||||
<uc:AboutControl />
|
<uc:AboutControl />
|
||||||
|
|||||||
Reference in New Issue
Block a user