mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-13 03:34:55 +08:00
add: qq music lyrics provider
This commit is contained in:
@@ -45,6 +45,7 @@
|
||||
<converter:ColorToBrushConverter x:Key="ColorToBrushConverter" />
|
||||
<converter:MatchedLocalFilesPathToVisibilityConverter x:Key="MatchedLocalFilesPathToVisibilityConverter" />
|
||||
<converter:IntToCornerRadius x:Key="IntToCornerRadius" />
|
||||
<converter:LyricsSearchProviderToDisplayNameConverter x:Key="LyricsSearchProviderToDisplayNameConverter" />
|
||||
<converters:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter" />
|
||||
<converters:BoolNegationConverter x:Key="BoolNegationConverter" />
|
||||
<converters:ColorToDisplayNameConverter x:Key="ColorToDisplayNameConverter" />
|
||||
|
||||
@@ -73,7 +73,7 @@ namespace BetterLyrics.WinUI3
|
||||
// Services
|
||||
.AddSingleton<ISettingsService, SettingsService>()
|
||||
.AddSingleton<IPlaybackService, PlaybackService>()
|
||||
.AddSingleton<ILrcLibService, LrcLibService>()
|
||||
.AddSingleton<IMusicSearchService, MusicSearchService>()
|
||||
.AddSingleton<ILibWatcherService, LibWatcherService>()
|
||||
// ViewModels
|
||||
.AddTransient<HostWindowViewModel>()
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Extensions" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Helpers" Version="8.2.250402" />
|
||||
<PackageReference Include="CommunityToolkit.WinUI.Media" Version="8.2.250402" />
|
||||
<PackageReference Include="H.NotifyIcon.WinUI" Version="2.3.0" />
|
||||
<PackageReference Include="Lyricify.Lyrics.Helper" Version="0.1.4" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Extensions.Logging" Version="9.0.6" />
|
||||
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
|
||||
@@ -38,6 +38,7 @@
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />
|
||||
<PackageReference Include="Serilog.Sinks.File" Version="7.0.0" />
|
||||
<PackageReference Include="SharpZipLib" Version="1.4.2" />
|
||||
<PackageReference Include="sqlite-net-pcl" Version="1.9.172" />
|
||||
<PackageReference Include="System.Drawing.Common" Version="9.0.6" />
|
||||
<PackageReference Include="System.Text.Encoding.CodePages" Version="9.0.6" />
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using Microsoft.UI.Xaml.Data;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class LyricsSearchProviderToDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LocalLrcFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalLrcFile"
|
||||
),
|
||||
LyricsSearchProvider.LocalMusicFile => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLocalMusicFile"
|
||||
),
|
||||
LyricsSearchProvider.LrcLib => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderLrcLib"
|
||||
),
|
||||
LyricsSearchProvider.QQMusic => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderQQMusic"
|
||||
),
|
||||
LyricsSearchProvider.KugouMusic => App.ResourceLoader!.GetString(
|
||||
"LyricsSearchProviderKugouMusic"
|
||||
),
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
|
||||
};
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,6 +17,10 @@ namespace BetterLyrics.WinUI3.Enums
|
||||
cdet.Feed(bytes, 0, bytes.Length);
|
||||
cdet.DataEnd();
|
||||
var encoding = cdet.Charset;
|
||||
if (encoding == null)
|
||||
{
|
||||
return Encoding.UTF8;
|
||||
}
|
||||
return Encoding.GetEncoding(encoding);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsFormat
|
||||
{
|
||||
Lrc,
|
||||
DecryptedQrc,
|
||||
DecryptedKrc,
|
||||
}
|
||||
|
||||
public static class LyricsFormatExtensions
|
||||
{
|
||||
public static string ToFileExtension(this LyricsFormat format)
|
||||
{
|
||||
return format switch
|
||||
{
|
||||
LyricsFormat.Lrc => ".lrc",
|
||||
LyricsFormat.DecryptedQrc => ".decryptedqrc",
|
||||
LyricsFormat.DecryptedKrc => ".decryptedkrc",
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(format), format, null),
|
||||
};
|
||||
}
|
||||
|
||||
public static List<string> GetSupportedLyricsFormatAsList()
|
||||
{
|
||||
return [.. Enum.GetValues<LyricsFormat>().Select(format => format.ToFileExtension())];
|
||||
}
|
||||
|
||||
public static LyricsFormat FromFileExtension(string extension)
|
||||
{
|
||||
return extension.ToLowerInvariant() switch
|
||||
{
|
||||
".lrc" => LyricsFormat.Lrc,
|
||||
".qrc" => LyricsFormat.DecryptedQrc,
|
||||
".krc" => LyricsFormat.DecryptedKrc,
|
||||
_ => throw new ArgumentException(
|
||||
$"Unsupported lyrics format: {extension}",
|
||||
nameof(extension)
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum LyricsSearchProvider
|
||||
{
|
||||
LocalLrcFile,
|
||||
LocalMusicFile,
|
||||
LrcLib,
|
||||
QQMusic,
|
||||
KugouMusic,
|
||||
}
|
||||
|
||||
public static class OnlineLyricsProviderExtensions
|
||||
{
|
||||
public static LyricsFormat ToLyricsFormat(this LyricsSearchProvider provider)
|
||||
{
|
||||
return provider switch
|
||||
{
|
||||
LyricsSearchProvider.LocalLrcFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LocalMusicFile => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.LrcLib => LyricsFormat.Lrc,
|
||||
LyricsSearchProvider.QQMusic => LyricsFormat.DecryptedQrc,
|
||||
LyricsSearchProvider.KugouMusic => LyricsFormat.DecryptedKrc,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(provider), provider, null),
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Enums
|
||||
{
|
||||
public enum SearchMatchMode
|
||||
public enum MusicSearchMatchMode
|
||||
{
|
||||
TitleAndArtist,
|
||||
TitleArtistAlbumAndDuration,
|
||||
@@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events
|
||||
public class IsPlayingChangedEventArgs(bool isPlaying) : EventArgs
|
||||
{
|
||||
public bool IsPlaying { get; set; } = isPlaying;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,5 +9,5 @@ namespace BetterLyrics.WinUI3.Events
|
||||
public class PositionChangedEventArgs(TimeSpan position) : EventArgs()
|
||||
{
|
||||
public TimeSpan Position { get; set; } = position;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,5 +10,5 @@ namespace BetterLyrics.WinUI3.Events
|
||||
public class SongInfoChangedEventArgs(SongInfo? songInfo) : EventArgs
|
||||
{
|
||||
public SongInfo? SongInfo { get; set; } = songInfo;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,19 +41,21 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
public static string AssetsFolder => Path.Combine(Package.Current.InstalledPath, "Assets");
|
||||
|
||||
// Data Files
|
||||
private static string DatabaseFileName => "database.db";
|
||||
public static string DatabasePath => Path.Combine(LocalFolder, DatabaseFileName);
|
||||
|
||||
public static string LogDirectory => Path.Combine(CacheFolder, "logs");
|
||||
public static string LogFilePattern => Path.Combine(LogDirectory, "log-.txt");
|
||||
|
||||
public static string OnlineLyricsCacheDirectory =>
|
||||
Path.Combine(CacheFolder, "online-lyrics");
|
||||
|
||||
private static string TestMusicFileName => "AI - 甜度爆表.mp3";
|
||||
public static string TestMusicPath => Path.Combine(AssetsFolder, TestMusicFileName);
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(LocalFolder);
|
||||
Directory.CreateDirectory(LogDirectory);
|
||||
Directory.CreateDirectory(OnlineLyricsCacheDirectory);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
136
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
Normal file
136
BetterLyrics.WinUI3/BetterLyrics.WinUI3/Helper/LyricsParser.cs
Normal file
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public class LyricsParser
|
||||
{
|
||||
private List<LyricsLine> _lyricsLines = [];
|
||||
|
||||
public List<LyricsLine> Parse(
|
||||
string raw,
|
||||
LyricsFormat? lyricsFormat = null,
|
||||
string? title = null,
|
||||
string? artist = null,
|
||||
int durationMs = 0
|
||||
)
|
||||
{
|
||||
switch (lyricsFormat)
|
||||
{
|
||||
case LyricsFormat.Lrc:
|
||||
ParseLyricsFromLrc(raw);
|
||||
break;
|
||||
case LyricsFormat.DecryptedQrc:
|
||||
ParseLyricsFromQrc(raw, durationMs);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (
|
||||
title != null
|
||||
&& artist != null
|
||||
&& _lyricsLines != null
|
||||
&& _lyricsLines.Count > 0
|
||||
&& _lyricsLines[0].StartTimestampMs > 0
|
||||
)
|
||||
{
|
||||
_lyricsLines.Insert(
|
||||
0,
|
||||
new LyricsLine
|
||||
{
|
||||
StartTimestampMs = 0,
|
||||
EndTimestampMs = _lyricsLines[0].StartTimestampMs,
|
||||
Texts = [$"{artist} - {title}"],
|
||||
}
|
||||
);
|
||||
}
|
||||
return _lyricsLines;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Try to parse lyrics from the track, optionally override the raw lyrics string.
|
||||
/// </summary>
|
||||
/// <param name="track"></param>
|
||||
/// <param name="raw"></param>
|
||||
private void ParseLyricsFromLrc(string raw)
|
||||
{
|
||||
Track track = new() { Lyrics = new() };
|
||||
track.Lyrics.ParseLRC(raw);
|
||||
var lines = track.Lyrics.SynchronizedLyrics;
|
||||
|
||||
if (lines != null && lines.Count > 0)
|
||||
{
|
||||
_lyricsLines = [];
|
||||
LyricsLine? lyricsLine = null;
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var lyricsPhrase = lines[i];
|
||||
int startTimestampMs = lyricsPhrase.TimestampMs;
|
||||
int endTimestampMs;
|
||||
|
||||
if (i + 1 < lines.Count)
|
||||
{
|
||||
endTimestampMs = lines[i + 1].TimestampMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimestampMs = (int)track.DurationMs;
|
||||
}
|
||||
|
||||
lyricsLine ??= new LyricsLine { StartTimestampMs = startTimestampMs };
|
||||
|
||||
lyricsLine.Texts.Add(lyricsPhrase.Text);
|
||||
|
||||
if (endTimestampMs == startTimestampMs)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lyricsLine.EndTimestampMs = endTimestampMs;
|
||||
_lyricsLines.Add(lyricsLine);
|
||||
lyricsLine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void ParseLyricsFromQrc(string raw, int? durationMs)
|
||||
{
|
||||
var lines = Lyricify
|
||||
.Lyrics.Parsers.QrcParser.Parse(raw)
|
||||
.Lines?.Where(x => !string.IsNullOrWhiteSpace(x.Text))
|
||||
.ToList();
|
||||
|
||||
if (lines != null && lines.Count > 0)
|
||||
{
|
||||
_lyricsLines = [];
|
||||
for (int i = 0; i < lines.Count; i++)
|
||||
{
|
||||
var lineRead = lines[i];
|
||||
var lineWrite = new LyricsLine
|
||||
{
|
||||
StartTimestampMs = lineRead.StartTime ?? 0,
|
||||
Texts = [lineRead.Text],
|
||||
};
|
||||
if (i + 1 < lines.Count)
|
||||
{
|
||||
lineWrite.EndTimestampMs = lines[i + 1].StartTime ?? 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lineWrite.EndTimestampMs = (int)(durationMs ?? 0);
|
||||
}
|
||||
_lyricsLines.Add(lineWrite);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,5 +11,6 @@ using Microsoft.UI.Xaml.Controls;
|
||||
namespace BetterLyrics.WinUI3.Messages
|
||||
{
|
||||
public class ShowNotificatonMessage(Notification value)
|
||||
: ValueChangedMessage<Notification>(value) { }
|
||||
: ValueChangedMessage<Notification>(value)
|
||||
{ }
|
||||
}
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
using SQLite;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public class LocalFileRecord
|
||||
{
|
||||
public string? Path { get; set; }
|
||||
public string? Title { get; set; }
|
||||
public string? Artist { get; set; }
|
||||
public string? Album { get; set; }
|
||||
public byte[]? AlbumArt { get; set; }
|
||||
public string? LyricsRaw { get; set; }
|
||||
public int? Duration { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -13,12 +13,12 @@ namespace BetterLyrics.WinUI3.Models
|
||||
|
||||
public string Text => Texts[LanguageIndex];
|
||||
|
||||
public int StartPlayingTimestampMs { get; set; }
|
||||
public int EndPlayingTimestampMs { get; set; }
|
||||
public int StartTimestampMs { get; set; }
|
||||
public int EndTimestampMs { get; set; }
|
||||
|
||||
public LyricsPlayingState PlayingState { get; set; }
|
||||
|
||||
public int DurationMs => EndPlayingTimestampMs - StartPlayingTimestampMs;
|
||||
public int DurationMs => EndTimestampMs - StartTimestampMs;
|
||||
|
||||
public float EnteringProgress { get; set; }
|
||||
|
||||
@@ -40,8 +40,8 @@ namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
Texts = new List<string>(this.Texts),
|
||||
LanguageIndex = this.LanguageIndex,
|
||||
StartPlayingTimestampMs = this.StartPlayingTimestampMs,
|
||||
EndPlayingTimestampMs = this.EndPlayingTimestampMs,
|
||||
StartTimestampMs = this.StartTimestampMs,
|
||||
EndTimestampMs = this.EndTimestampMs,
|
||||
PlayingState = this.PlayingState,
|
||||
EnteringProgress = this.EnteringProgress,
|
||||
ExitingProgress = this.ExitingProgress,
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Newtonsoft.Json;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
public partial class LyricsSearchProviderInfo : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
public partial LyricsSearchProvider Provider { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool IsEnabled { get; set; }
|
||||
|
||||
public LyricsSearchProviderInfo() { }
|
||||
|
||||
public LyricsSearchProviderInfo(LyricsSearchProvider provider, bool isEnabled)
|
||||
{
|
||||
Provider = provider;
|
||||
IsEnabled = isEnabled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,19 +12,19 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public partial class Notification : ObservableObject
|
||||
{
|
||||
[ObservableProperty]
|
||||
private InfoBarSeverity _severity;
|
||||
public partial InfoBarSeverity Severity { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _message;
|
||||
public partial string? Message { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private bool _isForeverDismissable;
|
||||
public partial bool IsForeverDismissable { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private Visibility _visibility;
|
||||
public partial Visibility Visibility { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
private string? _relatedSettingsKeyName;
|
||||
public partial string? RelatedSettingsKeyName { get; set; }
|
||||
|
||||
public Notification(
|
||||
string? message = null,
|
||||
|
||||
@@ -1,13 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI;
|
||||
using Windows.UI;
|
||||
using static ATL.LyricsInfo;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Models
|
||||
{
|
||||
@@ -22,87 +13,17 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty]
|
||||
public partial string? Album { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// In milliseconds
|
||||
/// </summary>
|
||||
[ObservableProperty]
|
||||
public partial int? Duration { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsStatus? LyricsStatus { get; set; }
|
||||
public partial double? DurationMs { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial string? SourceAppUserModelId { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial List<LyricsLine>? LyricsLines { get; set; } = null;
|
||||
public byte[]? AlbumArt { get; set; } = null;
|
||||
|
||||
public SongInfo() { }
|
||||
|
||||
/// <summary>
|
||||
/// Try to parse lyrics from the track, optionally override the raw lyrics string.
|
||||
/// </summary>
|
||||
/// <param name="track"></param>
|
||||
/// <param name="raw"></param>
|
||||
public void ParseLyrics(string? raw = null)
|
||||
{
|
||||
List<LyricsLine>? result = null;
|
||||
|
||||
Track track = new() { Lyrics = new() };
|
||||
if (raw != null)
|
||||
{
|
||||
track.Lyrics.ParseLRC(raw);
|
||||
var lyricsPhrases = track.Lyrics.SynchronizedLyrics;
|
||||
|
||||
if (lyricsPhrases?.Count > 0)
|
||||
{
|
||||
if (lyricsPhrases[0].TimestampMs > 0)
|
||||
{
|
||||
var placeholder = new LyricsPhrase(0, $"{Artist} - {Title}");
|
||||
lyricsPhrases.Insert(0, placeholder);
|
||||
lyricsPhrases.Insert(0, placeholder);
|
||||
}
|
||||
}
|
||||
|
||||
LyricsLine? lyricsLine = null;
|
||||
|
||||
for (int i = 0; i < lyricsPhrases?.Count; i++)
|
||||
{
|
||||
var lyricsPhrase = lyricsPhrases[i];
|
||||
int startTimestampMs = lyricsPhrase.TimestampMs;
|
||||
int endTimestampMs;
|
||||
|
||||
if (i + 1 < lyricsPhrases.Count)
|
||||
{
|
||||
endTimestampMs = lyricsPhrases[i + 1].TimestampMs;
|
||||
}
|
||||
else
|
||||
{
|
||||
endTimestampMs = (int)track.DurationMs;
|
||||
}
|
||||
|
||||
lyricsLine ??= new LyricsLine { StartPlayingTimestampMs = startTimestampMs };
|
||||
|
||||
lyricsLine.Texts.Add(lyricsPhrase.Text);
|
||||
|
||||
if (endTimestampMs == startTimestampMs)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else
|
||||
{
|
||||
lyricsLine.EndPlayingTimestampMs = endTimestampMs;
|
||||
result ??= [];
|
||||
result.Add(lyricsLine);
|
||||
lyricsLine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result != null && result.Count == 0)
|
||||
{
|
||||
result = null;
|
||||
}
|
||||
|
||||
LyricsLines = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,16 +0,0 @@
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface ILrcLibService
|
||||
{
|
||||
Task<string?> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int duration,
|
||||
SearchMatchMode matchMode = SearchMatchMode.TitleAndArtist
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public interface IMusicSearchService
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="title"></param>
|
||||
/// <param name="artist"></param>
|
||||
/// <param name="album"></param>
|
||||
/// <param name="durationMs"></param>
|
||||
/// <param name="matchMode"></param>
|
||||
/// <param name="targetProps"></param>
|
||||
/// <param name="searchProviders"></param>
|
||||
/// <returns>Return a tuple (raw lyrics, lyrics format, album art)</returns>
|
||||
Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
|
||||
event EventHandler<PositionChangedEventArgs>? PositionChanged;
|
||||
|
||||
void ReSendingMessages();
|
||||
SongInfo? SongInfo { get; }
|
||||
bool IsPlaying { get; }
|
||||
TimeSpan Position { get; }
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Microsoft.UI.Text;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Windows.UI.Text;
|
||||
@@ -12,7 +13,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
// Lyrics lib
|
||||
List<string> MusicLibraries { get; set; }
|
||||
bool IsOnlineLyricsEnabled { get; set; }
|
||||
List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
// App appearance
|
||||
ElementTheme ThemeType { get; set; }
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using global::BetterLyrics.WinUI3.Events;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Net.Http;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class LrcLibService : ILrcLibService
|
||||
{
|
||||
private static readonly HttpClient _httpClient;
|
||||
|
||||
static LrcLibService()
|
||||
{
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<string?> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int duration,
|
||||
SearchMatchMode matchMode = SearchMatchMode.TitleAndArtist
|
||||
)
|
||||
{
|
||||
// Build API query URL
|
||||
var url =
|
||||
$"https://lrclib.net/api/search?"
|
||||
+ $"track_name={Uri.EscapeDataString(title)}&"
|
||||
+ $"artist_name={Uri.EscapeDataString(artist)}";
|
||||
|
||||
if (matchMode == SearchMatchMode.TitleArtistAlbumAndDuration)
|
||||
{
|
||||
url +=
|
||||
$"&album_name={Uri.EscapeDataString(album)}"
|
||||
+ $"&duration={Uri.EscapeDataString(duration.ToString())}";
|
||||
}
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var jObj = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
|
||||
var jArr = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
|
||||
if (jArr is not null && jArr.Count > 0)
|
||||
{
|
||||
var syncedLyrics = jArr![0]?.syncedLyrics?.ToString();
|
||||
return string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,304 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
using System.Text;
|
||||
using System.Text.Json;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using Lyricify.Lyrics.Providers;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Services
|
||||
{
|
||||
public class MusicSearchService : IMusicSearchService
|
||||
{
|
||||
private readonly HttpClient _httpClient;
|
||||
private readonly ISettingsService _settingsService;
|
||||
|
||||
public MusicSearchService(ISettingsService settingsService)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_httpClient = new HttpClient();
|
||||
_httpClient.DefaultRequestHeaders.Add(
|
||||
"User-Agent",
|
||||
$"{AppInfo.AppName} {AppInfo.AppVersion} ({AppInfo.GithubUrl})"
|
||||
);
|
||||
}
|
||||
|
||||
public async Task<(string?, LyricsFormat?)> SearchLyricsAsync(
|
||||
string title,
|
||||
string artist,
|
||||
string album = "",
|
||||
double durationMs = 0.0,
|
||||
MusicSearchMatchMode matchMode = MusicSearchMatchMode.TitleAndArtist
|
||||
)
|
||||
{
|
||||
foreach (var provider in _settingsService.LyricsSearchProvidersInfo)
|
||||
{
|
||||
if (!provider.IsEnabled)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
case LyricsSearchProvider.QQMusic:
|
||||
case LyricsSearchProvider.KugouMusic:
|
||||
// Check cache first
|
||||
var cachedLyrics = ReadCache(title, artist, provider.Provider);
|
||||
if (!string.IsNullOrWhiteSpace(cachedLyrics))
|
||||
{
|
||||
return (cachedLyrics, provider.Provider.ToLyricsFormat());
|
||||
}
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
string? searchedLyrics = null;
|
||||
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LocalMusicFile:
|
||||
searchedLyrics = LocalLyricsSearchInMusicFiles(title, artist);
|
||||
break;
|
||||
case LyricsSearchProvider.LocalLrcFile:
|
||||
searchedLyrics = await LocalLyricsSearchInLyricsFiles(
|
||||
title,
|
||||
artist,
|
||||
provider.Provider.ToLyricsFormat()
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
searchedLyrics = await SearchLrcLib(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)(durationMs / 1000),
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.QQMusic:
|
||||
searchedLyrics = await SearchQQMusic(
|
||||
title,
|
||||
artist,
|
||||
album,
|
||||
(int)durationMs,
|
||||
matchMode
|
||||
);
|
||||
break;
|
||||
case LyricsSearchProvider.KugouMusic:
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(searchedLyrics))
|
||||
{
|
||||
switch (provider.Provider)
|
||||
{
|
||||
case LyricsSearchProvider.LrcLib:
|
||||
case LyricsSearchProvider.QQMusic:
|
||||
case LyricsSearchProvider.KugouMusic:
|
||||
WriteCache(title, artist, searchedLyrics, provider.Provider);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return (searchedLyrics, provider.Provider.ToLyricsFormat());
|
||||
}
|
||||
}
|
||||
|
||||
return (null, null);
|
||||
}
|
||||
|
||||
private string? LocalLyricsSearchInMusicFiles(string title, string artist)
|
||||
{
|
||||
foreach (var path in _settingsService.MusicLibraries)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(path, $"*.*", SearchOption.AllDirectories)
|
||||
)
|
||||
{
|
||||
if (file.Contains(title) && file.Contains(artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
if (track.Lyrics.SynchronizedLyrics.Count > 0)
|
||||
{
|
||||
// Get synchronized lyrics from the track (metadata)
|
||||
var lrc = track.Lyrics.FormatSynchToLRC();
|
||||
if (lrc != null)
|
||||
{
|
||||
return lrc;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> LocalLyricsSearchInLyricsFiles(
|
||||
string title,
|
||||
string artist,
|
||||
LyricsFormat format
|
||||
)
|
||||
{
|
||||
foreach (var path in _settingsService.MusicLibraries)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(
|
||||
path,
|
||||
$"*{format.ToFileExtension()}",
|
||||
SearchOption.AllDirectories
|
||||
)
|
||||
)
|
||||
{
|
||||
if (file.Contains(title) && file.Contains(artist))
|
||||
{
|
||||
string? raw = await File.ReadAllTextAsync(
|
||||
file,
|
||||
FileHelper.GetEncoding(file)
|
||||
);
|
||||
if (raw != null)
|
||||
{
|
||||
return raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchLrcLib(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int duration,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
// Build API query URL
|
||||
var url =
|
||||
$"https://lrclib.net/api/search?"
|
||||
+ $"track_name={Uri.EscapeDataString(title)}&"
|
||||
+ $"artist_name={Uri.EscapeDataString(artist)}";
|
||||
|
||||
if (matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration)
|
||||
{
|
||||
url +=
|
||||
$"&album_name={Uri.EscapeDataString(album)}"
|
||||
+ $"&durationMs={Uri.EscapeDataString(duration.ToString())}";
|
||||
}
|
||||
|
||||
var response = await _httpClient.GetAsync(url);
|
||||
if (!response.IsSuccessStatusCode)
|
||||
return null;
|
||||
|
||||
var json = await response.Content.ReadAsStringAsync();
|
||||
|
||||
var jArr = Newtonsoft.Json.JsonConvert.DeserializeObject<dynamic>(json);
|
||||
if (jArr is not null && jArr.Count > 0)
|
||||
{
|
||||
var syncedLyrics = jArr![0]?.syncedLyrics?.ToString();
|
||||
var result = string.IsNullOrWhiteSpace(syncedLyrics) ? null : syncedLyrics;
|
||||
if (!string.IsNullOrWhiteSpace(result))
|
||||
{
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private async Task<string?> SearchQQMusic(
|
||||
string title,
|
||||
string artist,
|
||||
string album,
|
||||
int durationMs,
|
||||
MusicSearchMatchMode matchMode
|
||||
)
|
||||
{
|
||||
string? queryId = (
|
||||
(
|
||||
await new Lyricify.Lyrics.Searchers.QQMusicSearcher().SearchForResult(
|
||||
new Lyricify.Lyrics.Models.TrackMultiArtistMetadata()
|
||||
{
|
||||
DurationMs =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? durationMs
|
||||
: null,
|
||||
Album =
|
||||
matchMode == MusicSearchMatchMode.TitleArtistAlbumAndDuration
|
||||
? album
|
||||
: null,
|
||||
AlbumArtists = [artist],
|
||||
Artists = [artist],
|
||||
Title = title,
|
||||
}
|
||||
)
|
||||
) as Lyricify.Lyrics.Searchers.QQMusicSearchResult
|
||||
)?.Id;
|
||||
if (queryId is string id)
|
||||
{
|
||||
return (await Lyricify.Lyrics.Decrypter.Qrc.Helper.GetLyricsAsync(id))?.Lyrics;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void WriteCache(
|
||||
string title,
|
||||
string artist,
|
||||
string lyrics,
|
||||
LyricsSearchProvider provider
|
||||
)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
AppInfo.OnlineLyricsCacheDirectory,
|
||||
$"{safeArtist} - {safeTitle}{provider.ToLyricsFormat().ToFileExtension()}"
|
||||
);
|
||||
File.WriteAllText(cacheFilePath, lyrics);
|
||||
}
|
||||
|
||||
private string? ReadCache(string title, string artist, LyricsSearchProvider provider)
|
||||
{
|
||||
var safeArtist = SanitizeFileName(artist);
|
||||
var safeTitle = SanitizeFileName(title);
|
||||
var cacheFilePath = Path.Combine(
|
||||
AppInfo.OnlineLyricsCacheDirectory,
|
||||
$"{safeArtist} - {safeTitle}{provider.ToLyricsFormat().ToFileExtension()}"
|
||||
);
|
||||
if (File.Exists(cacheFilePath))
|
||||
{
|
||||
return File.ReadAllText(cacheFilePath);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string SanitizeFileName(string fileName, char replacement = '_')
|
||||
{
|
||||
var invalidChars = Path.GetInvalidFileNameChars();
|
||||
var sb = new StringBuilder(fileName.Length);
|
||||
foreach (var c in fileName)
|
||||
{
|
||||
sb.Append(Array.IndexOf(invalidChars, c) >= 0 ? replacement : c);
|
||||
}
|
||||
return sb.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,12 +1,14 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using ATL;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Events;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.WinUI;
|
||||
using Microsoft.UI.Dispatching;
|
||||
using Windows.Media.Control;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -28,132 +30,36 @@ namespace BetterLyrics.WinUI3.Services
|
||||
public bool IsPlaying { get; private set; }
|
||||
public TimeSpan Position { get; private set; }
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILrcLibService _lrcLibService;
|
||||
private readonly IMusicSearchService _musicSearchService;
|
||||
|
||||
public PlaybackService(ISettingsService settingsService, ILrcLibService lrcLibService)
|
||||
public PlaybackService(
|
||||
ISettingsService settingsService,
|
||||
IMusicSearchService musicSearchService
|
||||
)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_lrcLibService = lrcLibService;
|
||||
_musicSearchService = musicSearchService;
|
||||
InitMediaManager().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private async Task RefreshSongInfoAsync()
|
||||
private async Task SendSongInfoAsync(
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps
|
||||
)
|
||||
{
|
||||
var mediaProps = await _currentSession?.TryGetMediaPropertiesAsync();
|
||||
SongInfo = new SongInfo
|
||||
{
|
||||
Title = mediaProps?.Title ?? string.Empty,
|
||||
Artist = mediaProps?.Artist ?? string.Empty,
|
||||
Album = mediaProps?.AlbumTitle ?? string.Empty,
|
||||
Duration = (int?)_currentSession?.GetTimelineProperties().EndTime.TotalSeconds,
|
||||
DurationMs = _currentSession?.GetTimelineProperties().EndTime.TotalMilliseconds,
|
||||
SourceAppUserModelId = _currentSession?.SourceAppUserModelId,
|
||||
LyricsStatus = LyricsStatus.Loading,
|
||||
};
|
||||
|
||||
string? lyricsRaw = null;
|
||||
byte[]? albumArt = null;
|
||||
|
||||
if (mediaProps?.Thumbnail is IRandomAccessStreamReference streamReference)
|
||||
{
|
||||
// Local search for lyrics only
|
||||
SongInfo.AlbumArt = await ImageHelper.ToByteArrayAsync(streamReference);
|
||||
(lyricsRaw, _) = await Search(SongInfo.Title, SongInfo.Artist);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Local search for lyrics and album art
|
||||
(lyricsRaw, albumArt) = await Search(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
targetProps: LocalSearchTargetProps.LyricsAndAlbumArt
|
||||
);
|
||||
SongInfo.AlbumArt = albumArt;
|
||||
}
|
||||
SongInfo.ParseLyrics(lyricsRaw);
|
||||
|
||||
// If no lyrics found locally, search online
|
||||
if (_settingsService.IsOnlineLyricsEnabled && SongInfo.LyricsLines == null)
|
||||
{
|
||||
SongInfo.ParseLyrics(
|
||||
await _lrcLibService.SearchLyricsAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album,
|
||||
SongInfo.Duration ?? 0
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
if (SongInfo.LyricsLines == null || SongInfo.LyricsLines.Count == 0)
|
||||
{
|
||||
SongInfo.LyricsStatus = LyricsStatus.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
SongInfo.LyricsStatus = LyricsStatus.Found;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<(string?, byte[]?)> Search(
|
||||
string title,
|
||||
string artist,
|
||||
LocalSearchTargetProps targetProps = LocalSearchTargetProps.LyricsOnly
|
||||
)
|
||||
{
|
||||
string? lyricsRaw = null;
|
||||
byte[]? albumArt = null;
|
||||
|
||||
foreach (var path in _settingsService.MusicLibraries)
|
||||
{
|
||||
if (Directory.Exists(path))
|
||||
{
|
||||
foreach (
|
||||
var file in Directory.GetFiles(path, "*.*", SearchOption.AllDirectories)
|
||||
)
|
||||
{
|
||||
if (file.Contains(title) && file.Contains(artist))
|
||||
{
|
||||
Track track = new(file);
|
||||
albumArt ??= track.EmbeddedPictures.FirstOrDefault()?.PictureData;
|
||||
|
||||
if (file.EndsWith(".lrc"))
|
||||
{
|
||||
lyricsRaw ??= await File.ReadAllTextAsync(
|
||||
file,
|
||||
FileHelper.GetEncoding(file)
|
||||
);
|
||||
}
|
||||
else if (file.EndsWith("qm.qrc"))
|
||||
{
|
||||
lyricsRaw ??= Lyricify.Lyrics.Decrypter.Qrc.Decrypter.DecryptLyrics(
|
||||
await File.ReadAllTextAsync(file, System.Text.Encoding.UTF8)
|
||||
);
|
||||
}
|
||||
else if (track.Lyrics.SynchronizedLyrics.Count > 0)
|
||||
{
|
||||
lyricsRaw ??= track.Lyrics.FormatSynchToLRC();
|
||||
}
|
||||
|
||||
if (
|
||||
lyricsRaw != null
|
||||
&& (
|
||||
(
|
||||
targetProps == LocalSearchTargetProps.LyricsAndAlbumArt
|
||||
&& albumArt != null
|
||||
)
|
||||
|| targetProps == LocalSearchTargetProps.LyricsOnly
|
||||
)
|
||||
)
|
||||
{
|
||||
return (lyricsRaw, albumArt);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (lyricsRaw, albumArt);
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
|
||||
private async Task InitMediaManager()
|
||||
@@ -164,14 +70,6 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SessionManager_CurrentSessionChanged(_sessionManager, null);
|
||||
}
|
||||
|
||||
public void ReSendingMessages()
|
||||
{
|
||||
// Re-send messages to update UI
|
||||
CurrentSession_MediaPropertiesChanged(_currentSession, null);
|
||||
CurrentSession_PlaybackInfoChanged(_currentSession, null);
|
||||
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Note: Non-UI thread
|
||||
/// </summary>
|
||||
@@ -240,9 +138,11 @@ namespace BetterLyrics.WinUI3.Services
|
||||
_currentSession.PlaybackInfoChanged += CurrentSession_PlaybackInfoChanged;
|
||||
_currentSession.TimelinePropertiesChanged +=
|
||||
CurrentSession_TimelinePropertiesChanged;
|
||||
}
|
||||
|
||||
ReSendingMessages();
|
||||
CurrentSession_MediaPropertiesChanged(_currentSession, null);
|
||||
CurrentSession_PlaybackInfoChanged(_currentSession, null);
|
||||
CurrentSession_TimelinePropertiesChanged(_currentSession, null);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -250,23 +150,29 @@ namespace BetterLyrics.WinUI3.Services
|
||||
/// </summary>
|
||||
/// <param name="sender"></param>
|
||||
/// <param name="args"></param>
|
||||
private void CurrentSession_MediaPropertiesChanged(
|
||||
private async void CurrentSession_MediaPropertiesChanged(
|
||||
GlobalSystemMediaTransportControlsSession? sender,
|
||||
MediaPropertiesChangedEventArgs? args
|
||||
)
|
||||
{
|
||||
// _logger.LogDebug("CurrentSession_MediaPropertiesChanged");
|
||||
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
|
||||
if (sender != null)
|
||||
{
|
||||
mediaProps = await sender.TryGetMediaPropertiesAsync();
|
||||
}
|
||||
_dispatcherQueue.TryEnqueue(
|
||||
DispatcherQueuePriority.High,
|
||||
async () =>
|
||||
{
|
||||
if (sender == null)
|
||||
{
|
||||
SongInfo = null;
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
else
|
||||
{
|
||||
await RefreshSongInfoAsync();
|
||||
await SendSongInfoAsync(mediaProps);
|
||||
}
|
||||
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(SongInfo));
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Newtonsoft.Json;
|
||||
@@ -19,7 +20,7 @@ namespace BetterLyrics.WinUI3.Services
|
||||
|
||||
// Lyrics lib
|
||||
private const string MusicLibrariesKey = "MusicLibraries";
|
||||
private const string IsOnlineLyricsEnabledKey = "IsOnlineLyricsEnabled";
|
||||
private const string LyricsSearchProvidersInfoKey = "LyricsSearchProvidersInfo";
|
||||
|
||||
// App appearance
|
||||
private const string ThemeTypeKey = "ThemeType";
|
||||
@@ -61,10 +62,13 @@ namespace BetterLyrics.WinUI3.Services
|
||||
set => SetValue(MusicLibrariesKey, JsonConvert.SerializeObject(value));
|
||||
}
|
||||
|
||||
public bool IsOnlineLyricsEnabled
|
||||
public List<LyricsSearchProviderInfo> LyricsSearchProvidersInfo
|
||||
{
|
||||
get => GetValue<bool>(IsOnlineLyricsEnabledKey);
|
||||
set => SetValue(IsOnlineLyricsEnabledKey, value);
|
||||
get =>
|
||||
JsonConvert.DeserializeObject<List<LyricsSearchProviderInfo>>(
|
||||
GetValue<string>(LyricsSearchProvidersInfoKey) ?? "[]"
|
||||
)!;
|
||||
set => SetValue(LyricsSearchProvidersInfoKey, JsonConvert.SerializeObject(value));
|
||||
}
|
||||
|
||||
public ElementTheme ThemeType
|
||||
@@ -188,7 +192,18 @@ namespace BetterLyrics.WinUI3.Services
|
||||
SetDefault(IsFirstRunKey, true);
|
||||
// Lyrics lib
|
||||
SetDefault(MusicLibrariesKey, "[]");
|
||||
SetDefault(IsOnlineLyricsEnabledKey, true);
|
||||
SetDefault(
|
||||
LyricsSearchProvidersInfoKey,
|
||||
JsonConvert.SerializeObject(
|
||||
new List<LyricsSearchProviderInfo>()
|
||||
{
|
||||
new(LyricsSearchProvider.LocalMusicFile, true),
|
||||
new(LyricsSearchProvider.LocalLrcFile, true),
|
||||
new(LyricsSearchProvider.LrcLib, true),
|
||||
new(LyricsSearchProvider.QQMusic, true),
|
||||
}
|
||||
)
|
||||
);
|
||||
// App appearance
|
||||
SetDefault(ThemeTypeKey, (int)ElementTheme.Default);
|
||||
SetDefault(LanguageKey, (int)Language.FollowSystem);
|
||||
|
||||
@@ -294,8 +294,8 @@
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>Glow effect scope</value>
|
||||
</data>
|
||||
<data name="SettingsPageOnlineLyrics.Header" xml:space="preserve">
|
||||
<value>Search lyrics online</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>Configure lyrics search providers</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>Add</value>
|
||||
@@ -462,4 +462,22 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>Loading lyrics...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>Local .lrc files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>Local music files</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LrcLib</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQMusic" xml:space="preserve">
|
||||
<value>QQ Music</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugouMusic" xml:space="preserve">
|
||||
<value>Kugou Music</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -294,8 +294,8 @@
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>辉光效果作用范围</value>
|
||||
</data>
|
||||
<data name="SettingsPageOnlineLyrics.Header" xml:space="preserve">
|
||||
<value>在线搜索歌词</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌词搜索服务</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
@@ -462,4 +462,22 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>设置</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>加载歌词中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>本地 .lrc 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音乐文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LrcLib</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQMusic" xml:space="preserve">
|
||||
<value>QQ 音乐</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugouMusic" xml:space="preserve">
|
||||
<value>酷狗音乐</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -294,8 +294,8 @@
|
||||
<data name="SettingsPageLyricsGlowEffectScope.Header" xml:space="preserve">
|
||||
<value>輝光效果作用範圍</value>
|
||||
</data>
|
||||
<data name="SettingsPageOnlineLyrics.Header" xml:space="preserve">
|
||||
<value>在線搜尋歌詞</value>
|
||||
<data name="SettingsPageLyricsSearchProvidersConfig.Header" xml:space="preserve">
|
||||
<value>配置歌詞搜尋服務</value>
|
||||
</data>
|
||||
<data name="SettingsPageAddFolderButton.Content" xml:space="preserve">
|
||||
<value>添加</value>
|
||||
@@ -462,4 +462,22 @@
|
||||
<data name="HostWindowSettingsFlyoutItem.Text" xml:space="preserve">
|
||||
<value>設定</value>
|
||||
</data>
|
||||
<data name="MainPageLyricsLoading.Text" xml:space="preserve">
|
||||
<value>載入歌詞中...</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalLrcFile" xml:space="preserve">
|
||||
<value>本地 .lrc 文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLocalMusicFile" xml:space="preserve">
|
||||
<value>本地音樂文件</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderLrcLib" xml:space="preserve">
|
||||
<value>LrcLib</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderQQMusic" xml:space="preserve">
|
||||
<value>QQ 音樂</value>
|
||||
</data>
|
||||
<data name="LyricsSearchProviderKugouMusic" xml:space="preserve">
|
||||
<value>酷狗音乐</value>
|
||||
</data>
|
||||
</root>
|
||||
@@ -9,15 +9,15 @@ namespace BetterLyrics.WinUI3.Rendering
|
||||
: BaseViewModel(settingsService)
|
||||
{
|
||||
public TimeSpan TotalTime { get; set; } = TimeSpan.Zero;
|
||||
public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
|
||||
public TimeSpan ElapsedTime { get; set; } = TimeSpan.Zero;
|
||||
|
||||
public virtual void Calculate(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasAnimatedUpdateEventArgs args
|
||||
)
|
||||
{
|
||||
TotalTime += args.Timing.ElapsedTime;
|
||||
ElapsedTime = args.Timing.ElapsedTime;
|
||||
}
|
||||
public virtual void Calculate(
|
||||
ICanvasAnimatedControl control,
|
||||
CanvasAnimatedUpdateEventArgs args
|
||||
)
|
||||
{
|
||||
TotalTime += args.Timing.ElapsedTime;
|
||||
ElapsedTime = args.Timing.ElapsedTime;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Input;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
using H.NotifyIcon.Interop;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Windows.UI;
|
||||
|
||||
@@ -22,10 +22,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
: BaseViewModel,
|
||||
IRecipient<PropertyChangedMessage<int>>,
|
||||
IRecipient<PropertyChangedMessage<bool>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<string>>>
|
||||
IRecipient<PropertyChangedMessage<LyricsStatus>>
|
||||
{
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
|
||||
private LyricsDisplayType? _preferredDisplayTypeBeforeSwitchToDockMode;
|
||||
|
||||
[ObservableProperty]
|
||||
@@ -43,10 +41,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; } = null;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial LyricsDisplayType? PreferredDisplayType { get; set; } =
|
||||
LyricsDisplayType.SplitView;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial int LyricsFontSize { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool AboutToUpdateUI { get; set; }
|
||||
|
||||
@@ -72,17 +76,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public LyricsPageViewModel(
|
||||
ISettingsService settingsService,
|
||||
IPlaybackService playbackService,
|
||||
ILibWatcherService libWatcherService
|
||||
IPlaybackService playbackService
|
||||
)
|
||||
: base(settingsService)
|
||||
{
|
||||
LyricsFontSize = _settingsService.LyricsFontSize;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
|
||||
_libWatcherService = libWatcherService;
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_playbackService.SongInfoChanged += async (_, args) =>
|
||||
await UpdateSongInfoUI(args.SongInfo).ConfigureAwait(true);
|
||||
@@ -92,14 +92,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
UpdateSongInfoUI(_playbackService.SongInfo).ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(
|
||||
object? sender,
|
||||
Events.LibChangedEventArgs e
|
||||
)
|
||||
{
|
||||
_playbackService.ReSendingMessages();
|
||||
}
|
||||
|
||||
partial void OnCoverImageRadiusChanged(int value)
|
||||
{
|
||||
if (double.IsNaN(CoverImageGridActualHeight))
|
||||
@@ -189,13 +181,20 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public void Receive(PropertyChangedMessage<int> message)
|
||||
{
|
||||
if (message.Sender.GetType() == typeof(SettingsViewModel))
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.CoverImageRadius))
|
||||
{
|
||||
CoverImageRadius = message.NewValue;
|
||||
}
|
||||
}
|
||||
if (message.Sender is LyricsSettingsControlViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(LyricsSettingsControlViewModel.LyricsFontSize))
|
||||
{
|
||||
LyricsFontSize = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<bool> message)
|
||||
@@ -217,22 +216,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
TrySwitchToPreferredDisplayType(SongInfo);
|
||||
}
|
||||
}
|
||||
else if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.MusicLibraries))
|
||||
{
|
||||
_playbackService.ReSendingMessages();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<string>> message)
|
||||
public void Receive(PropertyChangedMessage<LyricsStatus> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
if (message.Sender is LyricsRendererViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.MusicLibraries))
|
||||
if (message.PropertyName == nameof(LyricsRendererViewModel.LyricsStatus))
|
||||
{
|
||||
_playbackService.ReSendingMessages();
|
||||
LyricsStatus = message.NewValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
@@ -14,6 +15,7 @@ using BetterLyrics.WinUI3.Messages;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Rendering;
|
||||
using BetterLyrics.WinUI3.Services;
|
||||
using BetterLyrics.WinUI3.Services.BetterLyrics.WinUI3.Services;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using CommunityToolkit.Mvvm.Messaging;
|
||||
using CommunityToolkit.Mvvm.Messaging.Messages;
|
||||
@@ -46,7 +48,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IRecipient<PropertyChangedMessage<LyricsAlignmentType>>,
|
||||
IRecipient<PropertyChangedMessage<ElementTheme>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsFontWeight>>,
|
||||
IRecipient<PropertyChangedMessage<LyricsGlowEffectScope>>
|
||||
IRecipient<PropertyChangedMessage<LyricsGlowEffectScope>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>>>,
|
||||
IRecipient<PropertyChangedMessage<ObservableCollection<string>>>
|
||||
{
|
||||
private protected CanvasTextFormat _textFormat = new()
|
||||
{
|
||||
@@ -67,6 +71,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial SongInfo? SongInfo { get; set; }
|
||||
|
||||
private List<LyricsLine> _lyrics = [];
|
||||
|
||||
private List<LyricsLine>? _lyricsForGlowEffect = [];
|
||||
|
||||
private SoftwareBitmap? _lastSoftwareBitmap = null;
|
||||
@@ -97,6 +103,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[ObservableProperty]
|
||||
public partial bool IsPlaying { get; set; }
|
||||
|
||||
[NotifyPropertyChangedRecipients]
|
||||
[ObservableProperty]
|
||||
public partial LyricsStatus LyricsStatus { get; set; } = LyricsStatus.Loading;
|
||||
|
||||
private protected Color _fontColor;
|
||||
|
||||
private Color _lightFontColor = Colors.White;
|
||||
@@ -152,6 +162,8 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public LyricsGlowEffectScope LyricsGlowEffectScope { get; set; }
|
||||
|
||||
private protected readonly IPlaybackService _playbackService;
|
||||
private protected readonly IMusicSearchService _musicSearchService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
|
||||
private float _transitionAlpha = 1f;
|
||||
private TimeSpan _transitionDuration = TimeSpan.FromMilliseconds(1000);
|
||||
@@ -168,10 +180,16 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
public LyricsRendererViewModel(
|
||||
ISettingsService settingsService,
|
||||
IPlaybackService playbackService
|
||||
IPlaybackService playbackService,
|
||||
IMusicSearchService musicSearchService,
|
||||
ILibWatcherService libWatcherService
|
||||
)
|
||||
: base(settingsService)
|
||||
{
|
||||
_libWatcherService = libWatcherService;
|
||||
_musicSearchService = musicSearchService;
|
||||
_playbackService = playbackService;
|
||||
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
@@ -188,16 +206,56 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
IsLyricsGlowEffectEnabled = _settingsService.IsLyricsGlowEffectEnabled;
|
||||
LyricsGlowEffectScope = _settingsService.LyricsGlowEffectScope;
|
||||
|
||||
_playbackService = playbackService;
|
||||
_libWatcherService.MusicLibraryFilesChanged +=
|
||||
LibWatcherService_MusicLibraryFilesChanged;
|
||||
|
||||
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
|
||||
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
|
||||
_playbackService.PositionChanged += PlaybackService_PositionChanged;
|
||||
|
||||
RefreshPlaybackInfo();
|
||||
|
||||
UpdateFontColor();
|
||||
}
|
||||
|
||||
private void LibWatcherService_MusicLibraryFilesChanged(
|
||||
object? sender,
|
||||
Events.LibChangedEventArgs e
|
||||
)
|
||||
{
|
||||
GetLyrics().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Should invoke this function when:
|
||||
/// 1. The song info is changed (new song is played).
|
||||
/// 2. Lyrics search provider info is changed (change order, enable or disable any provider).
|
||||
/// 3. Local music/lyrics files are changed (added, removed, renamed).
|
||||
/// </summary>
|
||||
/// <returns></returns>
|
||||
private async Task GetLyrics()
|
||||
{
|
||||
_lyrics = [];
|
||||
_isRelayoutNeeded = true;
|
||||
LyricsStatus = LyricsStatus.Loading;
|
||||
(var lyricsRaw, var lyricsFormat) = await _musicSearchService.SearchLyricsAsync(
|
||||
SongInfo.Title,
|
||||
SongInfo.Artist,
|
||||
SongInfo.Album,
|
||||
SongInfo.DurationMs ?? 0
|
||||
);
|
||||
|
||||
if (lyricsRaw == null)
|
||||
{
|
||||
LyricsStatus = LyricsStatus.NotFound;
|
||||
}
|
||||
else
|
||||
{
|
||||
_lyrics = new LyricsParser().Parse(lyricsRaw, lyricsFormat);
|
||||
_isRelayoutNeeded = true;
|
||||
LyricsStatus = LyricsStatus.Found;
|
||||
}
|
||||
}
|
||||
|
||||
public void RequestRelayout()
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
@@ -211,6 +269,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
private void PlaybackService_SongInfoChanged(object? sender, SongInfoChangedEventArgs e)
|
||||
{
|
||||
SongInfo = e.SongInfo;
|
||||
GetLyrics().ConfigureAwait(true);
|
||||
}
|
||||
|
||||
private void PlaybackService_IsPlayingChanged(object? sender, IsPlayingChangedEventArgs e)
|
||||
@@ -230,9 +289,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_isRelayoutNeeded = true;
|
||||
}
|
||||
|
||||
async partial void OnSongInfoChanged(SongInfo? value)
|
||||
async partial void OnSongInfoChanged(SongInfo? oldValue, SongInfo? newValue)
|
||||
{
|
||||
if (value?.AlbumArt is byte[] bytes)
|
||||
if (newValue == oldValue)
|
||||
{
|
||||
_isRelayoutNeeded = true;
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue?.AlbumArt is byte[] bytes)
|
||||
{
|
||||
SoftwareBitmap = await (
|
||||
await ImageHelper.GetDecoderFromByte(bytes)
|
||||
@@ -319,10 +384,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private int GetCurrentPlayingLineIndex()
|
||||
{
|
||||
for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
|
||||
for (int i = 0; i < _lyrics?.Count; i++)
|
||||
{
|
||||
var line = SongInfo?.LyricsLines?[i];
|
||||
if (line.EndPlayingTimestampMs < TotalTime.TotalMilliseconds)
|
||||
var line = _lyrics?[i];
|
||||
if (line?.EndTimestampMs < TotalTime.TotalMilliseconds)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
@@ -340,12 +405,12 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private Tuple<int, int> GetMaxLyricsLineIndexBoundaries()
|
||||
{
|
||||
if (SongInfo == null || SongInfo.LyricsLines == null || SongInfo.LyricsLines.Count == 0)
|
||||
if (SongInfo == null || _lyrics == null || _lyrics.Count == 0)
|
||||
{
|
||||
return new Tuple<int, int>(-1, -1);
|
||||
}
|
||||
|
||||
return new Tuple<int, int>(0, SongInfo.LyricsLines.Count - 1);
|
||||
return new Tuple<int, int>(0, _lyrics.Count - 1);
|
||||
}
|
||||
|
||||
private void DrawLyrics(
|
||||
@@ -621,7 +686,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
DrawLyrics(
|
||||
control,
|
||||
lyricsDs,
|
||||
SongInfo?.LyricsLines,
|
||||
_lyrics,
|
||||
_defaultOpacity,
|
||||
LyricsHighlightType.LineByLine
|
||||
);
|
||||
@@ -793,9 +858,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
float y = _topMargin;
|
||||
|
||||
// Init Positions
|
||||
for (int i = 0; i < SongInfo?.LyricsLines?.Count; i++)
|
||||
for (int i = 0; i < _lyrics?.Count; i++)
|
||||
{
|
||||
var line = SongInfo?.LyricsLines?[i];
|
||||
var line = _lyrics?[i];
|
||||
|
||||
// Calculate layout bounds
|
||||
using var textLayout = new CanvasTextLayout(
|
||||
@@ -872,13 +937,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
int currentPlayingLineIndex = GetCurrentPlayingLineIndex();
|
||||
|
||||
CalculateLinesProps(SongInfo?.LyricsLines, currentPlayingLineIndex, _defaultOpacity);
|
||||
CalculateLinesProps(_lyrics, currentPlayingLineIndex, _defaultOpacity);
|
||||
CalculateCanvasYScrollOffset(control, currentPlayingLineIndex);
|
||||
|
||||
if (IsLyricsGlowEffectEnabled)
|
||||
{
|
||||
// Deep copy lyrics lines for glow effect
|
||||
_lyricsForGlowEffect = SongInfo?.LyricsLines?.Select(line => line.Clone()).ToList();
|
||||
_lyricsForGlowEffect = _lyrics?.Select(line => line.Clone()).ToList();
|
||||
switch (LyricsGlowEffectScope)
|
||||
{
|
||||
case LyricsGlowEffectScope.WholeLyrics:
|
||||
@@ -938,11 +1003,10 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
opacity = _highlightedOpacity;
|
||||
|
||||
playProgress =
|
||||
((float)TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs)
|
||||
((float)TotalTime.TotalMilliseconds - line.StartTimestampMs)
|
||||
/ line.DurationMs;
|
||||
|
||||
var durationFromStartMs =
|
||||
TotalTime.TotalMilliseconds - line.StartPlayingTimestampMs;
|
||||
var durationFromStartMs = TotalTime.TotalMilliseconds - line.StartTimestampMs;
|
||||
lineEntering = durationFromStartMs <= lineEnteringDurationMs;
|
||||
if (lineEntering)
|
||||
{
|
||||
@@ -962,8 +1026,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
line.PlayingState = LyricsPlayingState.Played;
|
||||
playProgress = 1;
|
||||
|
||||
var durationToEndMs =
|
||||
TotalTime.TotalMilliseconds - line.EndPlayingTimestampMs;
|
||||
var durationToEndMs = TotalTime.TotalMilliseconds - line.EndTimestampMs;
|
||||
lineExiting = durationToEndMs <= lineExitingDurationMs;
|
||||
if (lineExiting)
|
||||
{
|
||||
@@ -1011,7 +1074,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
|
||||
// Set _scrollOffsetY
|
||||
LyricsLine? currentPlayingLine = SongInfo?.LyricsLines?[currentPlayingLineIndex];
|
||||
LyricsLine? currentPlayingLine = _lyrics?[currentPlayingLineIndex];
|
||||
|
||||
if (currentPlayingLine == null)
|
||||
{
|
||||
@@ -1027,13 +1090,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
);
|
||||
|
||||
var lineScrollingProgress =
|
||||
(TotalTime.TotalMilliseconds - currentPlayingLine.StartPlayingTimestampMs)
|
||||
(TotalTime.TotalMilliseconds - currentPlayingLine.StartTimestampMs)
|
||||
/ Math.Min(_lineScrollDurationMs, currentPlayingLine.DurationMs);
|
||||
|
||||
float targetYScrollOffset =
|
||||
(float?)(
|
||||
-currentPlayingLine.Position.Y
|
||||
+ SongInfo?.LyricsLines?[0].Position.Y
|
||||
+ _lyrics?[0].Position.Y
|
||||
- playingTextLayout.LayoutBounds.Height / 2
|
||||
- _lastTotalYScroll
|
||||
) ?? 0f;
|
||||
@@ -1061,17 +1124,13 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_startVisibleLineIndex = _endVisibleLineIndex = -1;
|
||||
|
||||
// Update visible line indices
|
||||
for (
|
||||
int i = startLineIndex;
|
||||
i >= 0 && i <= endLineIndex && i < SongInfo?.LyricsLines?.Count;
|
||||
i++
|
||||
)
|
||||
for (int i = startLineIndex; i >= 0 && i <= endLineIndex && i < _lyrics?.Count; i++)
|
||||
{
|
||||
var line = SongInfo?.LyricsLines?[i];
|
||||
var line = _lyrics?[i];
|
||||
|
||||
using var textLayout = new CanvasTextLayout(
|
||||
control,
|
||||
line.Text,
|
||||
line?.Text,
|
||||
_textFormat,
|
||||
(float)LimitedLineWidth,
|
||||
(float)control.Size.Height
|
||||
@@ -1330,5 +1389,31 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(
|
||||
PropertyChangedMessage<ObservableCollection<LyricsSearchProviderInfo>> message
|
||||
)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.LyricsSearchProvidersInfo))
|
||||
{
|
||||
// Lyrics search providers info changed, re-fetch lyrics
|
||||
GetLyrics().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void Receive(PropertyChangedMessage<ObservableCollection<string>> message)
|
||||
{
|
||||
if (message.Sender is SettingsViewModel)
|
||||
{
|
||||
if (message.PropertyName == nameof(SettingsViewModel.MusicLibraries))
|
||||
{
|
||||
// Music lib changed, re-fetch lyrics
|
||||
GetLyrics().ConfigureAwait(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
@@ -23,10 +24,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
public partial class SettingsViewModel : ObservableRecipient
|
||||
{
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial bool IsOnlineLyricsEnabled { get; set; } = false;
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ElementTheme ThemeType { get; set; }
|
||||
@@ -43,9 +40,12 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public partial AutoStartWindowType AutoStartWindowType { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<string> MusicLibraries { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial ObservableCollection<LyricsSearchProviderInfo> LyricsSearchProvidersInfo { get; set; }
|
||||
|
||||
[ObservableProperty]
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverImageRadius { get; set; }
|
||||
@@ -66,36 +66,50 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
[NotifyPropertyChangedRecipients]
|
||||
public partial int CoverOverlayBlurAmount { get; set; }
|
||||
|
||||
partial void OnMusicLibrariesChanged(
|
||||
ObservableCollection<string> oldValue,
|
||||
ObservableCollection<string> newValue
|
||||
)
|
||||
{
|
||||
if (oldValue != null)
|
||||
{
|
||||
oldValue.CollectionChanged -= MusicLib_CollectionChanged;
|
||||
}
|
||||
if (newValue != null)
|
||||
{
|
||||
newValue.CollectionChanged += MusicLib_CollectionChanged;
|
||||
_settingsService.MusicLibraries = [.. newValue];
|
||||
_libWatcherService.UpdateWatchers([.. newValue]);
|
||||
}
|
||||
}
|
||||
|
||||
private void MusicLib_CollectionChanged(
|
||||
object? sender,
|
||||
System.Collections.Specialized.NotifyCollectionChangedEventArgs e
|
||||
)
|
||||
{
|
||||
_settingsService.MusicLibraries = [.. MusicLibraries];
|
||||
_libWatcherService.UpdateWatchers([.. MusicLibraries]);
|
||||
Broadcast(MusicLibraries, MusicLibraries, nameof(MusicLibraries));
|
||||
}
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Enums.Language Language { get; set; }
|
||||
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
|
||||
public string Version { get; set; } = AppInfo.AppVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
|
||||
|
||||
public SettingsViewModel(
|
||||
ISettingsService settingsService,
|
||||
ILibWatcherService libWatcherService,
|
||||
IPlaybackService playbackService
|
||||
)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_libWatcherService = libWatcherService;
|
||||
|
||||
RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
|
||||
|
||||
MusicLibraries = [.. _settingsService.MusicLibraries];
|
||||
LyricsSearchProvidersInfo = [.. _settingsService.LyricsSearchProvidersInfo];
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
ThemeType = _settingsService.ThemeType;
|
||||
BackdropType = _settingsService.BackdropType;
|
||||
TitleBarType = _settingsService.TitleBarType;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
|
||||
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
}
|
||||
|
||||
partial void OnLanguageChanged(Enums.Language value)
|
||||
{
|
||||
switch (value)
|
||||
@@ -118,55 +132,6 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
_settingsService.Language = Language;
|
||||
}
|
||||
|
||||
private readonly MediaPlayer _mediaPlayer = new();
|
||||
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly ILibWatcherService _libWatcherService;
|
||||
private readonly IPlaybackService _playbackService;
|
||||
|
||||
public string Version { get; set; } = AppInfo.AppVersion;
|
||||
|
||||
[ObservableProperty]
|
||||
public partial object NavViewSelectedItemTag { get; set; } = "LyricsLib";
|
||||
|
||||
[ObservableProperty]
|
||||
public partial Thickness RootGridMargin { get; set; } = new(0, 0, 0, 0);
|
||||
|
||||
public SettingsViewModel(
|
||||
ISettingsService settingsService,
|
||||
ILibWatcherService libWatcherService,
|
||||
IPlaybackService playbackService
|
||||
)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
_libWatcherService = libWatcherService;
|
||||
_playbackService = playbackService;
|
||||
|
||||
RootGridMargin = new Thickness(0, _settingsService.TitleBarType.GetHeight(), 0, 0);
|
||||
|
||||
MusicLibraries = [.. _settingsService.MusicLibraries];
|
||||
IsOnlineLyricsEnabled = _settingsService.IsOnlineLyricsEnabled;
|
||||
|
||||
Language = _settingsService.Language;
|
||||
CoverImageRadius = _settingsService.CoverImageRadius;
|
||||
ThemeType = _settingsService.ThemeType;
|
||||
BackdropType = _settingsService.BackdropType;
|
||||
TitleBarType = _settingsService.TitleBarType;
|
||||
|
||||
AutoStartWindowType = _settingsService.AutoStartWindowType;
|
||||
|
||||
IsCoverOverlayEnabled = _settingsService.IsCoverOverlayEnabled;
|
||||
IsDynamicCoverOverlayEnabled = _settingsService.IsDynamicCoverOverlayEnabled;
|
||||
CoverOverlayOpacity = _settingsService.CoverOverlayOpacity;
|
||||
CoverOverlayBlurAmount = _settingsService.CoverOverlayBlurAmount;
|
||||
}
|
||||
|
||||
partial void OnIsOnlineLyricsEnabledChanged(bool value)
|
||||
{
|
||||
_settingsService.IsOnlineLyricsEnabled = value;
|
||||
_playbackService.ReSendingMessages();
|
||||
}
|
||||
|
||||
partial void OnThemeTypeChanged(ElementTheme value)
|
||||
{
|
||||
_settingsService.ThemeType = value;
|
||||
@@ -216,6 +181,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
public void RemoveFolderAsync(string path)
|
||||
{
|
||||
MusicLibraries.Remove(path);
|
||||
_settingsService.MusicLibraries = [.. MusicLibraries];
|
||||
_libWatcherService.UpdateWatchers([.. MusicLibraries]);
|
||||
Broadcast(MusicLibraries, MusicLibraries, nameof(MusicLibraries));
|
||||
}
|
||||
|
||||
[RelayCommand]
|
||||
@@ -265,6 +233,9 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
else
|
||||
{
|
||||
MusicLibraries.Add(path);
|
||||
_settingsService.MusicLibraries = [.. MusicLibraries];
|
||||
_libWatcherService.UpdateWatchers([.. MusicLibraries]);
|
||||
Broadcast(MusicLibraries, MusicLibraries, nameof(MusicLibraries));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -325,5 +296,15 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
{
|
||||
OpenFolderInFileExplorer(AppInfo.LogDirectory);
|
||||
}
|
||||
|
||||
public void ToggleLyricsSearchProvider(LyricsSearchProviderInfo providerInfo)
|
||||
{
|
||||
_settingsService.LyricsSearchProvidersInfo = [.. LyricsSearchProvidersInfo];
|
||||
Broadcast(
|
||||
LyricsSearchProvidersInfo,
|
||||
LyricsSearchProvidersInfo,
|
||||
nameof(LyricsSearchProvidersInfo)
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,7 +12,10 @@
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
mc:Ignorable="d">
|
||||
|
||||
<Grid x:Name="RootGrid" PointerMoved="RootGrid_PointerMoved" RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
<Grid
|
||||
x:Name="RootGrid"
|
||||
PointerMoved="RootGrid_PointerMoved"
|
||||
RequestedTheme="{x:Bind ViewModel.ThemeType, Mode=OneWay}">
|
||||
|
||||
<Frame
|
||||
x:Name="RootFrame"
|
||||
@@ -24,8 +27,8 @@
|
||||
Height="{x:Bind ViewModel.TitleBarHeight, Mode=OneWay}"
|
||||
VerticalAlignment="Top"
|
||||
Background="Transparent"
|
||||
PointerMoved="TopCommandGrid_PointerMoved"
|
||||
Opacity="0">
|
||||
Opacity="0"
|
||||
PointerMoved="TopCommandGrid_PointerMoved">
|
||||
<Grid.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
|
||||
@@ -26,7 +26,7 @@
|
||||
<renderer:LyricsRenderer />
|
||||
</Grid>
|
||||
|
||||
<Grid Margin="36">
|
||||
<Grid Margin="36,0">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="36" />
|
||||
@@ -45,24 +45,34 @@
|
||||
<ScalarTransition />
|
||||
</Grid.OpacityTransition>
|
||||
<StackPanel
|
||||
x:Name="LyricsPlaceholderStackPanel"
|
||||
x:Name="LyricsNotFoundPlaceholder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<FontIcon
|
||||
FontFamily="Segoe Fluent Icons"
|
||||
FontSize="{StaticResource DisplayTextBlockFontSize}"
|
||||
Glyph="" />
|
||||
<TextBlock x:Uid="MainPageLyricsNotFound" FontSize="{StaticResource TitleTextBlockFontSize}" />
|
||||
<TextBlock x:Uid="MainPageLyricsNotFound" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
x:Name="LyricsLoadingPlaceholder"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Opacity="0"
|
||||
Orientation="Horizontal"
|
||||
Spacing="12">
|
||||
<StackPanel.OpacityTransition>
|
||||
<ScalarTransition />
|
||||
</StackPanel.OpacityTransition>
|
||||
<TextBlock x:Uid="MainPageLyricsLoading" FontSize="{x:Bind ViewModel.LyricsFontSize, Mode=OneWay}" />
|
||||
</StackPanel>
|
||||
|
||||
</Grid>
|
||||
|
||||
<!-- Song info area -->
|
||||
<Grid x:Name="SongInfoInnerGrid">
|
||||
<Grid x:Name="SongInfoInnerGrid" Margin="0,36">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="2*" />
|
||||
<!-- Cover area -->
|
||||
@@ -416,26 +426,29 @@
|
||||
<VisualStateGroup x:Name="LyricsStatus">
|
||||
<VisualState x:Name="Loading">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.SongInfo.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="2" />
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="2" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsPlaceholderStackPanel.Opacity" Value="0" />
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value=".5" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="Found">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.SongInfo.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="1" />
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="1" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsPlaceholderStackPanel.Opacity" Value="0" />
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value="0" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
<VisualState x:Name="NotFound">
|
||||
<VisualState.StateTriggers>
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.SongInfo.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="0" />
|
||||
<ui:IsEqualStateTrigger Value="{x:Bind ViewModel.LyricsStatus, Mode=OneWay, Converter={StaticResource EnumToIntConverter}}" To="0" />
|
||||
</VisualState.StateTriggers>
|
||||
<VisualState.Setters>
|
||||
<Setter Target="LyricsPlaceholderStackPanel.Opacity" Value=".5" />
|
||||
<Setter Target="LyricsNotFoundPlaceholder.Opacity" Value=".5" />
|
||||
<Setter Target="LyricsLoadingPlaceholder.Opacity" Value="0" />
|
||||
</VisualState.Setters>
|
||||
</VisualState>
|
||||
</VisualStateGroup>
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:local="using:BetterLyrics.WinUI3.Views"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:models="using:BetterLyrics.WinUI3.Models"
|
||||
xmlns:ui="using:CommunityToolkit.WinUI"
|
||||
xmlns:vm="using:BetterLyrics.WinUI3.ViewModels"
|
||||
mc:Ignorable="d">
|
||||
@@ -129,9 +130,22 @@
|
||||
</controls:SettingsExpander.ItemsFooter>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
<controls:SettingsCard x:Uid="SettingsPageOnlineLyrics" HeaderIcon="{ui:FontIcon Glyph=}">
|
||||
<ToggleSwitch IsOn="{x:Bind ViewModel.IsOnlineLyricsEnabled, Mode=TwoWay}" />
|
||||
</controls:SettingsCard>
|
||||
<controls:SettingsExpander
|
||||
x:Uid="SettingsPageLyricsSearchProvidersConfig"
|
||||
HeaderIcon="{ui:FontIcon Glyph=}"
|
||||
IsExpanded="True"
|
||||
ItemsSource="{x:Bind ViewModel.LyricsSearchProvidersInfo, Mode=OneWay}">
|
||||
<controls:SettingsExpander.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:LyricsSearchProviderInfo">
|
||||
<controls:SettingsCard
|
||||
AllowDrop="True"
|
||||
CanDrag="True"
|
||||
Header="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}, Mode=OneWay}">
|
||||
<ToggleSwitch IsOn="{Binding IsEnabled, Mode=TwoWay}" Toggled="LyricsSearchProviderToggleSwitch_Toggled" />
|
||||
</controls:SettingsCard>
|
||||
</DataTemplate>
|
||||
</controls:SettingsExpander.ItemTemplate>
|
||||
</controls:SettingsExpander>
|
||||
|
||||
</StackPanel>
|
||||
</controls:Case>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using BetterInAppLyrics.WinUI3.ViewModels;
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.ViewModels;
|
||||
using CommunityToolkit.Mvvm.DependencyInjection;
|
||||
using Microsoft.UI.Xaml;
|
||||
@@ -47,5 +49,16 @@ namespace BetterLyrics.WinUI3.Views
|
||||
{
|
||||
ViewModel.NavViewSelectedItemTag = (args.SelectedItem as NavigationViewItem)!.Tag;
|
||||
}
|
||||
|
||||
private void LyricsSearchProviderToggleSwitch_Toggled(object sender, RoutedEventArgs e)
|
||||
{
|
||||
if (sender is ToggleSwitch toggleSwitch)
|
||||
{
|
||||
if (toggleSwitch.DataContext is LyricsSearchProviderInfo providerInfo)
|
||||
{
|
||||
ViewModel.ToggleLyricsSearchProvider(providerInfo);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user