mirror of
https://github.com/jayfunc/BetterLyrics.git
synced 2026-01-12 10:54:55 +08:00
fix: record play history
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
namespace BetterLyrics.WinUI3.Constants
|
||||
{
|
||||
public static class PlayerID
|
||||
public static class PlayerId
|
||||
{
|
||||
public const string LXMusic = "cn.toside.music.desktop";
|
||||
public const string LXMusicPortable = "lx-music-desktop.exe";
|
||||
|
||||
@@ -42,7 +42,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
{
|
||||
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
|
||||
private readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
|
||||
private readonly ILastFMService _lastFMService = Ioc.Default.GetRequiredService<ILastFMService>();
|
||||
|
||||
private readonly LyricsRenderer _lyricsRenderer = new();
|
||||
private readonly FluidBackgroundRenderer _fluidRenderer = new();
|
||||
@@ -660,22 +659,6 @@ namespace BetterLyrics.WinUI3.Controls
|
||||
_songPosition += elapsedTime;
|
||||
_totalPlayedTime += elapsedTime;
|
||||
_songPositionWithOffset = _songPosition + TimeSpan.FromMilliseconds(_mediaSessionsService.CurrentMediaSourceProviderInfo?.PositionOffset ?? 0);
|
||||
CheckAndScrobbleLastFM();
|
||||
}
|
||||
}
|
||||
|
||||
private void CheckAndScrobbleLastFM()
|
||||
{
|
||||
bool isEnabled = _mediaSessionsService.CurrentMediaSourceProviderInfo?.IsLastFMTrackEnabled ?? false;
|
||||
if (!isEnabled || _isLastFMTracked) return;
|
||||
|
||||
var songInfo = _mediaSessionsService.CurrentSongInfo;
|
||||
if (songInfo == null || songInfo.Duration <= 0) return;
|
||||
|
||||
if (_totalPlayedTime.TotalSeconds >= songInfo.Duration * 0.5)
|
||||
{
|
||||
_isLastFMTracked = true;
|
||||
_lastFMService.TrackAsync(songInfo);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -54,6 +54,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- 总播放时长 -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<StackPanel>
|
||||
<StackPanel
|
||||
@@ -63,14 +64,26 @@
|
||||
<FontIcon FontSize="14" Glyph="" />
|
||||
<TextBlock x:Uid="StatsDashboardControlTotalDuration" Style="{ThemeResource CaptionTextBlockStyle}" />
|
||||
</StackPanel>
|
||||
<TextBlock
|
||||
<StackPanel
|
||||
Margin="0,8,0,0"
|
||||
Foreground="{ThemeResource AccentTextFillColorPrimaryBrush}"
|
||||
Style="{ThemeResource SubtitleTextBlockStyle}"
|
||||
Text="{x:Bind ViewModel.TotalDuration, Mode=OneWay, Converter={StaticResource MillisecondsToFormattedTimeConverter}}" />
|
||||
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
|
||||
@@ -87,6 +100,7 @@
|
||||
</StackPanel>
|
||||
</Border>
|
||||
|
||||
<!-- Top source -->
|
||||
<Border
|
||||
Grid.Column="2"
|
||||
Margin="0,0,0,12"
|
||||
@@ -113,6 +127,7 @@
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<!-- Top artists -->
|
||||
<Border Grid.Column="0" Style="{StaticResource StatsCardStyle}">
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
@@ -127,7 +142,7 @@
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.TopArtists, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="statsmodels:ArtistPlayCount">
|
||||
<Grid Margin="0,8">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="*" />
|
||||
<ColumnDefinition Width="Auto" />
|
||||
@@ -145,8 +160,14 @@
|
||||
<TextBlock
|
||||
Grid.Column="1"
|
||||
VerticalAlignment="Center"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="{x:Bind PlayCount}" />
|
||||
FontWeight="SemiBold">
|
||||
<Run Text="{x:Bind PlayCount}" />
|
||||
<Run
|
||||
FontSize="10"
|
||||
FontWeight="Normal"
|
||||
Foreground="{ThemeResource SystemControlForegroundBaseMediumBrush}"
|
||||
Text="plays" />
|
||||
</TextBlock>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
@@ -154,6 +175,7 @@
|
||||
</Grid>
|
||||
</Border>
|
||||
|
||||
<!-- Top sources -->
|
||||
<Border
|
||||
Grid.Column="1"
|
||||
Margin="0,0,0,12"
|
||||
@@ -171,7 +193,7 @@
|
||||
<ItemsControl Grid.Row="1" ItemsSource="{x:Bind ViewModel.PlayerStats, Mode=OneWay}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate x:DataType="models:PlayerStatDisplayItem">
|
||||
<Grid Margin="0,6">
|
||||
<Grid Margin="0,4">
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
@@ -184,9 +206,17 @@
|
||||
<TextBlock
|
||||
FontSize="13"
|
||||
Style="{ThemeResource BodyTextBlockStyle}"
|
||||
Text="{x:Bind PlayerId}" />
|
||||
<TextBlock Grid.Column="1" Style="{ThemeResource CaptionTextBlockStyle}">
|
||||
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>
|
||||
<Grid
|
||||
@@ -213,6 +243,7 @@
|
||||
</Border>
|
||||
</Grid>
|
||||
|
||||
<!-- Top song -->
|
||||
<Border
|
||||
Grid.Row="2"
|
||||
Margin="0,0,0,20"
|
||||
|
||||
@@ -3,18 +3,37 @@ using System;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Converter
|
||||
{
|
||||
public class MillisecondsToFormattedTimeConverter : IValueConverter
|
||||
public partial class MillisecondsToFormattedTimeConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, string language)
|
||||
{
|
||||
if (value is int milliseconds)
|
||||
double? milliseconds = null;
|
||||
|
||||
if (value is int iVal) milliseconds = iVal;
|
||||
else if (value is double dVal) milliseconds = dVal;
|
||||
else if (value is long lVal) milliseconds = lVal;
|
||||
|
||||
if (milliseconds.HasValue)
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(milliseconds).ToString(@"mm\:ss\.fff");
|
||||
}
|
||||
else if (value is double doubleMilliseconds)
|
||||
{
|
||||
return TimeSpan.FromMilliseconds(doubleMilliseconds).ToString(@"mm\:ss\.fff");
|
||||
var ts = TimeSpan.FromMilliseconds(milliseconds.Value);
|
||||
|
||||
string? format = parameter?.ToString();
|
||||
|
||||
if (string.IsNullOrEmpty(format))
|
||||
{
|
||||
format = @"mm\:ss\.fff";
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
return ts.ToString(format);
|
||||
}
|
||||
catch (FormatException)
|
||||
{
|
||||
return ts.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return value?.ToString() ?? "";
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ namespace BetterLyrics.WinUI3.Extensions
|
||||
Title = songInfo.Title,
|
||||
Artist = songInfo.DisplayArtists,
|
||||
Album = songInfo.Album,
|
||||
PlayerID = songInfo.PlayerId ?? "N/A",
|
||||
PlayerId = songInfo.PlayerId ?? "N/A",
|
||||
TotalDurationMs = songInfo.DurationMs,
|
||||
DurationPlayedMs = actualPlayedMs,
|
||||
StartedAt = DateTime.Now.AddMilliseconds(-actualPlayedMs)
|
||||
|
||||
@@ -58,7 +58,7 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
|
||||
public static string PlayQueuePath => Path.Combine(LocalFolder, "play-queue.m3u");
|
||||
public static string PlayHistoryPath => Path.Combine(LocalFolder, "play-history.db");
|
||||
public static string FilesCachePath => Path.Combine(CacheFolder, "files-cache.db");
|
||||
public static string FilesIndexPath => Path.Combine(LocalFolder, "files-index.db");
|
||||
|
||||
public static void EnsureDirectories()
|
||||
{
|
||||
|
||||
@@ -4,7 +4,7 @@ using System.Text.RegularExpressions;
|
||||
|
||||
namespace BetterLyrics.WinUI3.Helper
|
||||
{
|
||||
public static class PlayerIDHelper
|
||||
public static class PlayerIdHelper
|
||||
{
|
||||
private static readonly List<string> neteaseFamilyRegex =
|
||||
[
|
||||
@@ -25,64 +25,64 @@ namespace BetterLyrics.WinUI3.Helper
|
||||
return false;
|
||||
}
|
||||
|
||||
public static bool IsLXMusic(string? id) => id is PlayerID.LXMusic or PlayerID.LXMusicPortable;
|
||||
public static bool IsLXMusic(string? id) => id is PlayerId.LXMusic or PlayerId.LXMusicPortable;
|
||||
|
||||
public static bool IsAppleMusic(string? id) => id is PlayerID.AppleMusic or PlayerID.AppleMusicAlternative;
|
||||
public static bool IsAppleMusic(string? id) => id is PlayerId.AppleMusic or PlayerId.AppleMusicAlternative;
|
||||
|
||||
public static bool IsBetterLyrics(string? id) => id is PlayerID.BetterLyrics or PlayerID.BetterLyricsDebug;
|
||||
public static bool IsBetterLyrics(string? id) => id is PlayerId.BetterLyrics or PlayerId.BetterLyricsDebug;
|
||||
|
||||
public static string? GetDisplayName(string? id) => id switch
|
||||
{
|
||||
PlayerID.Spotify => PlayerName.Spotify,
|
||||
PlayerID.AppleMusic => PlayerName.AppleMusic,
|
||||
PlayerID.iTunes => PlayerName.iTunes,
|
||||
PlayerID.KugouMusic => PlayerName.KugouMusic,
|
||||
PlayerID.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||
PlayerID.QQMusic => PlayerName.QQMusic,
|
||||
PlayerID.LXMusic => PlayerName.LXMusic,
|
||||
PlayerID.LXMusicPortable => PlayerName.LXMusicPortable,
|
||||
PlayerID.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||
PlayerID.AIMP => PlayerName.AIMP,
|
||||
PlayerID.Foobar2000 => PlayerName.Foobar2000,
|
||||
PlayerID.MusicBee => PlayerName.MusicBee,
|
||||
PlayerID.PotPlayer => PlayerName.PotPlayer,
|
||||
PlayerID.Chrome => PlayerName.Chrome,
|
||||
PlayerID.Edge => PlayerName.Edge,
|
||||
PlayerID.BetterLyrics => PlayerName.BetterLyrics,
|
||||
PlayerID.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||
PlayerID.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
|
||||
PlayerID.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
|
||||
PlayerID.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
||||
PlayerID.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
||||
PlayerID.Listen1 => PlayerName.Listen1,
|
||||
PlayerId.Spotify => PlayerName.Spotify,
|
||||
PlayerId.AppleMusic => PlayerName.AppleMusic,
|
||||
PlayerId.iTunes => PlayerName.iTunes,
|
||||
PlayerId.KugouMusic => PlayerName.KugouMusic,
|
||||
PlayerId.NetEaseCloudMusic => PlayerName.NetEaseCloudMusic,
|
||||
PlayerId.QQMusic => PlayerName.QQMusic,
|
||||
PlayerId.LXMusic => PlayerName.LXMusic,
|
||||
PlayerId.LXMusicPortable => PlayerName.LXMusicPortable,
|
||||
PlayerId.MediaPlayerWindows11 => PlayerName.MediaPlayerWindows11,
|
||||
PlayerId.AIMP => PlayerName.AIMP,
|
||||
PlayerId.Foobar2000 => PlayerName.Foobar2000,
|
||||
PlayerId.MusicBee => PlayerName.MusicBee,
|
||||
PlayerId.PotPlayer => PlayerName.PotPlayer,
|
||||
PlayerId.Chrome => PlayerName.Chrome,
|
||||
PlayerId.Edge => PlayerName.Edge,
|
||||
PlayerId.BetterLyrics => PlayerName.BetterLyrics,
|
||||
PlayerId.BetterLyricsDebug => PlayerName.BetterLyricsDebug,
|
||||
PlayerId.SaltPlayerForWindowsMS => PlayerName.SaltPlayerForWindowsMS,
|
||||
PlayerId.SaltPlayerForWindowsSteam => PlayerName.SaltPlayerForWindowsSteam,
|
||||
PlayerId.MoeKoeMusic => PlayerName.MoeKoeMusic,
|
||||
PlayerId.MoeKoeMusicAlternative => PlayerName.MoeKoeMusic,
|
||||
PlayerId.Listen1 => PlayerName.Listen1,
|
||||
_ => id,
|
||||
};
|
||||
|
||||
public static string GetLogoPath(string? id) => id switch
|
||||
{
|
||||
PlayerID.Spotify => PathHelper.SpotifyLogoPath,
|
||||
PlayerID.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
||||
PlayerID.iTunes => PathHelper.iTunesLogoPath,
|
||||
PlayerID.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||
PlayerID.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||
PlayerID.QQMusic => PathHelper.QQMusicLogoPath,
|
||||
PlayerID.LXMusic => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
||||
PlayerID.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||
PlayerID.AIMP => PathHelper.AIMPLogoPath,
|
||||
PlayerID.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||
PlayerID.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||
PlayerID.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||
PlayerID.Chrome => PathHelper.ChromeLogoPath,
|
||||
PlayerID.Edge => PathHelper.EdgeLogoPath,
|
||||
PlayerID.BetterLyrics => PathHelper.LogoPath,
|
||||
PlayerID.BetterLyricsDebug => PathHelper.LogoPath,
|
||||
PlayerID.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerID.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerID.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerID.Listen1 => PathHelper.Listen1LogoPath,
|
||||
PlayerId.Spotify => PathHelper.SpotifyLogoPath,
|
||||
PlayerId.AppleMusic => PathHelper.AppleMusicLogoPath,
|
||||
PlayerId.AppleMusicAlternative => PathHelper.AppleMusicLogoPath,
|
||||
PlayerId.iTunes => PathHelper.iTunesLogoPath,
|
||||
PlayerId.KugouMusic => PathHelper.KugouMusicLogoPath,
|
||||
PlayerId.NetEaseCloudMusic => PathHelper.NetEaseCloudMusicLogoPath,
|
||||
PlayerId.QQMusic => PathHelper.QQMusicLogoPath,
|
||||
PlayerId.LXMusic => PathHelper.LXMusicLogoPath,
|
||||
PlayerId.LXMusicPortable => PathHelper.LXMusicLogoPath,
|
||||
PlayerId.MediaPlayerWindows11 => PathHelper.MediaPlayerWindows11LogoPath,
|
||||
PlayerId.AIMP => PathHelper.AIMPLogoPath,
|
||||
PlayerId.Foobar2000 => PathHelper.Foobar2000LogoPath,
|
||||
PlayerId.MusicBee => PathHelper.MusicBeeLogoPath,
|
||||
PlayerId.PotPlayer => PathHelper.PotPlayerLogoPath,
|
||||
PlayerId.Chrome => PathHelper.ChromeLogoPath,
|
||||
PlayerId.Edge => PathHelper.EdgeLogoPath,
|
||||
PlayerId.BetterLyrics => PathHelper.LogoPath,
|
||||
PlayerId.BetterLyricsDebug => PathHelper.LogoPath,
|
||||
PlayerId.SaltPlayerForWindowsMS => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerId.SaltPlayerForWindowsSteam => PathHelper.SaltPlayerForWindowsLogoPath,
|
||||
PlayerId.MoeKoeMusic => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerId.MoeKoeMusicAlternative => PathHelper.MoeKoeMusicLogoPath,
|
||||
PlayerId.Listen1 => PathHelper.Listen1LogoPath,
|
||||
_ => PathHelper.UnknownPlayerLogoPath,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -35,11 +35,11 @@ namespace BetterLyrics.WinUI3.Models
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial LyricsSearchType LyricsSearchType { get; set; } = LyricsSearchType.Sequential;
|
||||
[ObservableProperty][NotifyPropertyChangedRecipients] public partial int MatchingThreshold { get; set; } = 40;
|
||||
|
||||
[JsonIgnore] public string LogoPath => PlayerIDHelper.GetLogoPath(Provider);
|
||||
[JsonIgnore] public string LogoPath => PlayerIdHelper.GetLogoPath(Provider);
|
||||
|
||||
[JsonIgnore] public string? DisplayName => PlayerIDHelper.GetDisplayName(Provider);
|
||||
[JsonIgnore] public string? DisplayName => PlayerIdHelper.GetDisplayName(Provider);
|
||||
|
||||
[JsonIgnore] public bool IsLXMusic => PlayerIDHelper.IsLXMusic(Provider);
|
||||
[JsonIgnore] public bool IsLXMusic => PlayerIdHelper.IsLXMusic(Provider);
|
||||
|
||||
public MediaSourceProviderInfo()
|
||||
{
|
||||
@@ -53,7 +53,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
IsEnabled = isEnable;
|
||||
switch (provider)
|
||||
{
|
||||
case Constants.PlayerID.AppleMusic:
|
||||
case Constants.PlayerId.AppleMusic:
|
||||
// Apple Music 的特性
|
||||
TimelineSyncThreshold = 1000;
|
||||
PositionOffset = 1000;
|
||||
|
||||
@@ -20,6 +20,6 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public double TotalDurationMs { get; set; }
|
||||
|
||||
[Indexed]
|
||||
public string PlayerID { get; set; }
|
||||
public string PlayerId { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
|
||||
@@ -9,7 +10,7 @@ namespace BetterLyrics.WinUI3.Models
|
||||
public string PlayerId { get; set; }
|
||||
public int PlayCount { get; set; }
|
||||
|
||||
// 这就是 XAML 中 Rectangle Width 绑定的属性
|
||||
public double DisplayWidth { get; set; }
|
||||
public string PlayerName => PlayerIdHelper.GetDisplayName(PlayerId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ namespace BetterLyrics.WinUI3.Models.Stats
|
||||
{
|
||||
public class PlayerStats
|
||||
{
|
||||
public string PlayerID { get; set; }
|
||||
public string PlayerId { get; set; }
|
||||
public int Count { get; set; }
|
||||
|
||||
public double DisplayWidth => (TotalCount > 0) ? (Count / (double)TotalCount) * 150 : 0;
|
||||
|
||||
@@ -8,7 +8,6 @@ namespace BetterLyrics.WinUI3.Models.Stats
|
||||
{
|
||||
public string Title { get; set; }
|
||||
public string Artist { get; set; }
|
||||
public string AlbumArtHash { get; set; }
|
||||
public int PlayCount { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,7 +44,7 @@ namespace BetterLyrics.WinUI3.Services.FileSystemService
|
||||
_logger = logger;
|
||||
_localizationService = localizationService;
|
||||
_settingsService = settingsService;
|
||||
_db = new SQLiteAsyncConnection(PathHelper.FilesCachePath);
|
||||
_db = new SQLiteAsyncConnection(PathHelper.FilesIndexPath);
|
||||
}
|
||||
|
||||
public async Task InitializeAsync()
|
||||
|
||||
@@ -558,11 +558,11 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
|
||||
|
||||
ISearchResult? result;
|
||||
|
||||
if (songInfo.SongId != null && searcher == Searchers.Netease && PlayerIDHelper.IsNeteaseFamily(songInfo.PlayerId))
|
||||
if (songInfo.SongId != null && searcher == Searchers.Netease && PlayerIdHelper.IsNeteaseFamily(songInfo.PlayerId))
|
||||
{
|
||||
result = new NeteaseSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId);
|
||||
}
|
||||
else if (songInfo.SongId != null && searcher == Searchers.QQMusic && songInfo.PlayerId == Constants.PlayerID.QQMusic)
|
||||
else if (songInfo.SongId != null && searcher == Searchers.QQMusic && songInfo.PlayerId == Constants.PlayerId.QQMusic)
|
||||
{
|
||||
result = new QQMusicSearchResult(songInfo.Title, songInfo.Artists, songInfo.Album, [], (int)songInfo.DurationMs, songInfo.SongId, "");
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Settings;
|
||||
using BetterLyrics.WinUI3.Services.AlbumArtSearchService;
|
||||
using BetterLyrics.WinUI3.Services.DiscordService;
|
||||
using BetterLyrics.WinUI3.Services.LastFMService;
|
||||
using BetterLyrics.WinUI3.Services.LyricsSearchService;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
using BetterLyrics.WinUI3.Services.SettingsService;
|
||||
@@ -56,6 +57,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
private readonly ISettingsService _settingsService;
|
||||
private readonly IDiscordService _discordService;
|
||||
private readonly IPlayHistoryService _playHistoryService;
|
||||
private readonly ILastFMService _lastFMService;
|
||||
private readonly ILogger<MediaSessionsService> _logger;
|
||||
|
||||
private double _lxMusicPositionSeconds = 0;
|
||||
@@ -79,6 +81,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
ITranslationService libreTranslateService,
|
||||
ITransliterationService transliterationService,
|
||||
IPlayHistoryService playHistoryService,
|
||||
ILastFMService lastFMService,
|
||||
ILogger<MediaSessionsService> logger)
|
||||
{
|
||||
_settingsService = settingsService;
|
||||
@@ -88,6 +91,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_transliterationService = transliterationService;
|
||||
_discordService = discordService;
|
||||
_playHistoryService = playHistoryService;
|
||||
_lastFMService = lastFMService;
|
||||
_logger = logger;
|
||||
|
||||
_onMediaPropsChangedTimer = _dispatcherQueue.CreateTimer();
|
||||
@@ -150,7 +154,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
var found = _settingsService.AppSettings.MediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id);
|
||||
if (_settingsService.AppSettings.MusicGallerySettings.LyricsWindowStatus.IsOpened)
|
||||
{
|
||||
if (PlayerIDHelper.IsBetterLyrics(found?.Provider))
|
||||
if (PlayerIdHelper.IsBetterLyrics(found?.Provider))
|
||||
{
|
||||
return found?.IsEnabled ?? true;
|
||||
}
|
||||
@@ -275,7 +279,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
CurrentSongInfo = SongInfoExtensions.Placeholder;
|
||||
|
||||
if (PlayerIDHelper.IsLXMusic(sessionId))
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||
{
|
||||
StopSSE();
|
||||
}
|
||||
@@ -294,20 +298,20 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
string? fixedAlbum = mediaProperties?.AlbumTitle;
|
||||
string? songId = null;
|
||||
|
||||
if (PlayerIDHelper.IsAppleMusic(sessionId))
|
||||
if (PlayerIdHelper.IsAppleMusic(sessionId))
|
||||
{
|
||||
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault();
|
||||
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault();
|
||||
fixedAlbum = fixedAlbum?.Replace(" - Single", "");
|
||||
fixedAlbum = fixedAlbum?.Replace(" - EP", "");
|
||||
}
|
||||
else if (PlayerIDHelper.IsNeteaseFamily(sessionId))
|
||||
else if (PlayerIdHelper.IsNeteaseFamily(sessionId))
|
||||
{
|
||||
songId = mediaProperties?.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.NetEaseCloudMusicTrackID))?
|
||||
.Replace(ExtendedGenreFiled.NetEaseCloudMusicTrackID, "");
|
||||
}
|
||||
else if (sessionId == PlayerID.QQMusic)
|
||||
else if (sessionId == PlayerId.QQMusic)
|
||||
{
|
||||
songId = mediaProperties?.Genres
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.QQMusicTrackID))?
|
||||
@@ -318,8 +322,8 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
.FirstOrDefault(x => x.StartsWith(ExtendedGenreFiled.FileName))?
|
||||
.Replace(ExtendedGenreFiled.FileName, "");
|
||||
|
||||
// 统计
|
||||
if (CurrentSongInfo != null && CurrentSongInfo != SongInfoExtensions.Placeholder)
|
||||
// 写入播放记录
|
||||
if (CurrentSongInfo != null && CurrentSongInfo.Title != "N/A")
|
||||
{
|
||||
// 必须捕获一个副本给异步任务,因为 CurrentSongInfo 马上就要变了
|
||||
var lastSong = CurrentSongInfo;
|
||||
@@ -328,6 +332,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
if (lastSong.DurationMs > 0 &&
|
||||
_scrobbleStopwatch.Elapsed.TotalMilliseconds >= (lastSong.DurationMs / 2))
|
||||
{
|
||||
// 写入本地播放记录
|
||||
var playHistoryItem = CurrentSongInfo.ToPlayHistoryItem(_scrobbleStopwatch.Elapsed.TotalMilliseconds);
|
||||
if (playHistoryItem != null)
|
||||
{
|
||||
@@ -335,6 +340,13 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
_ = 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();
|
||||
@@ -350,7 +362,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
LinkedFileName = linkedFileName
|
||||
};
|
||||
|
||||
if (PlayerIDHelper.IsLXMusic(sessionId))
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId))
|
||||
{
|
||||
StartSSE();
|
||||
}
|
||||
@@ -359,7 +371,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
StopSSE();
|
||||
}
|
||||
|
||||
if (PlayerIDHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
||||
if (PlayerIdHelper.IsLXMusic(sessionId) && _lxMusicAlbumArtBytes != null)
|
||||
{
|
||||
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
|
||||
}
|
||||
@@ -550,7 +562,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
|
||||
{
|
||||
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
|
||||
{
|
||||
if (PlayerIDHelper.IsLXMusic(CurrentSongInfo?.PlayerId))
|
||||
if (PlayerIdHelper.IsLXMusic(CurrentSongInfo?.PlayerId))
|
||||
{
|
||||
var data = JsonSerializer.Deserialize(e.Message, Serialization.SourceGenerationContext.Default.JsonElement);
|
||||
if (data.ValueKind == JsonValueKind.Number)
|
||||
|
||||
@@ -71,17 +71,15 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
{
|
||||
await InitializeAsync();
|
||||
|
||||
// 使用 SQL 查询比在内存里 GroupBy 更快且省内存
|
||||
// SQLite 语法: Group By Title 和 Artist
|
||||
string query = @"
|
||||
SELECT Title, Artist, AlbumArtHash, COUNT(*) as PlayCount
|
||||
SELECT Title, Artist, COUNT(*) as PlayCount
|
||||
FROM PlayHistory
|
||||
WHERE StartedAt >= ? AND StartedAt <= ?
|
||||
GROUP BY Title, Artist
|
||||
ORDER BY PlayCount DESC
|
||||
LIMIT ?";
|
||||
|
||||
// 注意:SQLite存的是Ticks或者ISO8601,sqlite-net-pcl会自动处理DateTime参数
|
||||
return await _db.QueryAsync<SongPlayCount>(query, start, end, limit);
|
||||
}
|
||||
|
||||
@@ -126,10 +124,10 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
await InitializeAsync();
|
||||
|
||||
string query = @"
|
||||
SELECT PlayerID, COUNT(*) as Count
|
||||
SELECT PlayerId, COUNT(*) as Count
|
||||
FROM PlayHistory
|
||||
WHERE StartedAt >= ? AND StartedAt <= ?
|
||||
GROUP BY PlayerID
|
||||
GROUP BY PlayerId
|
||||
ORDER BY Count DESC";
|
||||
|
||||
return await _db.QueryAsync<PlayerStats>(query, start, end);
|
||||
@@ -265,7 +263,7 @@ namespace BetterLyrics.WinUI3.Services.PlayHistoryService
|
||||
Title = song.Title,
|
||||
Artist = song.Artist,
|
||||
Album = song.Album,
|
||||
PlayerID = playerId,
|
||||
PlayerId = playerId,
|
||||
StartedAt = startedAt,
|
||||
TotalDurationMs = totalDurationMs,
|
||||
DurationPlayedMs = playedDurationMs
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using BetterLyrics.WinUI3.Enums;
|
||||
using BetterLyrics.WinUI3.Helper;
|
||||
using BetterLyrics.WinUI3.Models;
|
||||
using BetterLyrics.WinUI3.Models.Stats;
|
||||
using BetterLyrics.WinUI3.Services.PlayHistoryService;
|
||||
@@ -44,37 +45,30 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
try
|
||||
{
|
||||
// 1. 计算时间范围
|
||||
var (start, end) = CalculateDateRange(range);
|
||||
|
||||
// 2. 并行获取所有数据 (性能优化:不等待一个查完再查下一个,而是同时查)
|
||||
var durationTask = _playHistoryService.GetTotalListeningDurationAsync(start, end);
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start, end); // 用来算总数
|
||||
var logsTask = _playHistoryService.GetLogsByDateRangeAsync(start, end);
|
||||
var topSongsTask = _playHistoryService.GetTopSongsAsync(start, end, 10);
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start, end, 5); // 只要前5名
|
||||
var topArtistsTask = _playHistoryService.GetTopArtistsAsync(start, end, 10);
|
||||
var playersTask = _playHistoryService.GetPlayerDistributionAsync(start, end);
|
||||
|
||||
await Task.WhenAll(durationTask, logsTask, topSongsTask, topArtistsTask, playersTask);
|
||||
|
||||
// 3. 更新 UI 数据
|
||||
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)
|
||||
{
|
||||
// 这里可以记录日志 _logger.LogError(ex, ...)
|
||||
System.Diagnostics.Debug.WriteLine($"Error loading stats: {ex.Message}");
|
||||
}
|
||||
finally
|
||||
@@ -98,7 +92,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
if (stats == null || stats.Count == 0)
|
||||
{
|
||||
TopPlayerName = "None";
|
||||
TopPlayerName = "N/A";
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -106,7 +100,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
if (maxCount == 0) maxCount = 1;
|
||||
|
||||
var topPlayer = stats.OrderByDescending(x => x.Count).FirstOrDefault();
|
||||
TopPlayerName = topPlayer?.PlayerID ?? "None";
|
||||
TopPlayerName = PlayerIdHelper.GetDisplayName(topPlayer?.PlayerId) ?? "N/A";
|
||||
|
||||
foreach (var item in stats.OrderByDescending(x => x.Count))
|
||||
{
|
||||
@@ -117,7 +111,7 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
PlayerStats.Add(new PlayerStatDisplayItem
|
||||
{
|
||||
PlayerId = item.PlayerID,
|
||||
PlayerId = item.PlayerId,
|
||||
PlayCount = item.Count,
|
||||
DisplayWidth = calculatedWidth
|
||||
});
|
||||
@@ -126,32 +120,35 @@ namespace BetterLyrics.WinUI3.ViewModels
|
||||
|
||||
private (DateTime Start, DateTime End) CalculateDateRange(StatsRange range)
|
||||
{
|
||||
DateTime now = DateTime.Now;
|
||||
DateTime start = now;
|
||||
DateTime nowLocal = DateTime.Now;
|
||||
DateTime startLocal = nowLocal.Date; // 默认为本地今天 00:00
|
||||
|
||||
switch (range)
|
||||
{
|
||||
case StatsRange.Day:
|
||||
start = now.Date; // 今天 00:00
|
||||
break;
|
||||
case StatsRange.Week:
|
||||
// 假设周一为一周开始
|
||||
int diff = (7 + (now.DayOfWeek - DayOfWeek.Monday)) % 7;
|
||||
start = now.Date.AddDays(-1 * diff);
|
||||
int dayOfWeek = (int)nowLocal.DayOfWeek;
|
||||
if (dayOfWeek == 0) dayOfWeek = 7; // 处理周日
|
||||
startLocal = nowLocal.Date.AddDays(-(dayOfWeek - 1));
|
||||
break;
|
||||
case StatsRange.Month:
|
||||
start = new DateTime(now.Year, now.Month, 1);
|
||||
startLocal = new DateTime(nowLocal.Year, nowLocal.Month, 1);
|
||||
break;
|
||||
case StatsRange.Quarter:
|
||||
int quarter = (now.Month - 1) / 3 + 1;
|
||||
start = new DateTime(now.Year, (quarter - 1) * 3 + 1, 1);
|
||||
int quarterStartMonth = (nowLocal.Month - 1) / 3 * 3 + 1;
|
||||
startLocal = new DateTime(nowLocal.Year, quarterStartMonth, 1);
|
||||
break;
|
||||
case StatsRange.Year:
|
||||
start = new DateTime(now.Year, 1, 1);
|
||||
startLocal = new DateTime(nowLocal.Year, 1, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
return (start, now);
|
||||
// 数据库里的 StartedAt 是 UTC,所以查询条件必须也是 UTC
|
||||
DateTime startUtc = startLocal.ToUniversalTime();
|
||||
DateTime endUtc = nowLocal.ToUniversalTime();
|
||||
|
||||
return (startUtc, endUtc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -102,7 +102,6 @@ namespace BetterLyrics.WinUI3.Views
|
||||
|
||||
private void NowPlayingBar_SongInfoTapped(object sender, System.EventArgs e)
|
||||
{
|
||||
NowPlayingBar.IsPlayingQueueOpened = false;
|
||||
NowPlayingBar.ShowSongInfo = false;
|
||||
NowPlayingBar.ShowTime = true;
|
||||
NowPlayingBar.IsAutoHideEnabled = true;
|
||||
|
||||
Reference in New Issue
Block a user