Compare commits

...

5 Commits

Author SHA1 Message Date
Zhe Fang
e354e29ec1 chores: update version code 2025-11-02 19:10:46 -05:00
Zhe Fang
dd5fb91764 chores: add delay when automatically show and hide window 2025-11-02 17:16:40 -05:00
Zhe Fang
041459e564 chores: remove gain 2025-11-02 11:26:27 -05:00
Zhe Fang
7de813b645 fix: audio graph 2025-11-02 11:05:13 -05:00
Zhe Fang
838d663834 chores: add null check in SearchQQNeteaseKugouAsync; fix search page result display issue 2025-11-02 08:47:49 -05:00
20 changed files with 205 additions and 215 deletions

View File

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

View File

@@ -117,6 +117,9 @@
<Content Update="Assets\AIMP.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AlbumArtPlaceholder.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>
<Content Update="Assets\AMLLPlayer.png">
<CopyToOutputDirectory>Always</CopyToOutputDirectory>
</Content>

View File

@@ -8,6 +8,7 @@
xmlns:interactivity="using:Microsoft.Xaml.Interactivity"
xmlns:local="using:BetterLyrics.WinUI3.Controls"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:models="using:BetterLyrics.WinUI3.Models"
xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d">
@@ -83,42 +84,42 @@
<Grid Grid.Column="1">
<ListView ItemsSource="{x:Bind ViewModel.LyricsSearchResults, Mode=OneWay}" SelectedItem="{x:Bind ViewModel.SelectedLyricsSearchResult, Mode=TwoWay}">
<ListView.ItemTemplate>
<DataTemplate>
<ListViewItem IsEnabled="{Binding IsFound}">
<Grid Opacity="{Binding IsFound, Converter={StaticResource BoolToOpacityConverter}}">
<DataTemplate x:DataType="models:LyricsSearchResult">
<ListViewItem IsEnabled="{x:Bind IsFound}">
<Grid Opacity="{x:Bind IsFound, Converter={StaticResource BoolToOpacityConverter}}">
<RelativePanel Padding="0,6">
<TextBlock
x:Name="SearchedTitle"
RelativePanel.AlignLeftWithPanel="True"
Text="{Binding Title}"
Text="{x:Bind Title}"
TextWrapping="Wrap"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedArtists"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.AlignLeftWithPanel="True"
RelativePanel.Below="SearchedTitle"
Text="{Binding Artist}"
Text="{x:Bind Artist}"
TextWrapping="Wrap"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedAlbum"
Foreground="{ThemeResource TextFillColorSecondaryBrush}"
RelativePanel.Below="SearchedArtists"
Text="{Binding Album}"
Text="{x:Bind Album}"
TextWrapping="Wrap"
Visibility="{Binding IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
Visibility="{x:Bind IsFound, Converter={StaticResource BoolToVisibilityConverter}}" />
<TextBlock
x:Name="SearchedProvider"
RelativePanel.AlignRightWithPanel="True"
RelativePanel.AlignVerticalCenterWithPanel="True"
Text="{Binding Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
Text="{x:Bind Provider, Converter={StaticResource LyricsSearchProviderToDisplayNameConverter}}" />
</RelativePanel>
<TextBlock
x:Uid="LyricsSearchControlNotFound"
VerticalAlignment="Center"
Text="Not found"
Visibility="{Binding IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
Visibility="{x:Bind IsFound, Converter={StaticResource BoolNegationToVisibilityConverter}}" />
</Grid>
</ListViewItem>
</DataTemplate>

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Threading.Tasks;
using Windows.Graphics.Imaging;
using Windows.Storage;
using Windows.Storage.Streams;
using Windows.UI;
using static Vanara.PInvoke.Ole32;
@@ -46,43 +47,14 @@ namespace BetterLyrics.WinUI3.Helper
return RandomAccessStreamReference.CreateFromStream(stream);
}
public static async Task<IRandomAccessStream> CreateTextPlaceholderBytesAsync(int width, int height)
public static async Task<IRandomAccessStream> GetAlbumArtPlaceholderAsync()
{
using var device = CanvasDevice.GetSharedDevice();
using var renderTarget = new CanvasRenderTarget(device, width, height, 96);
// 随机生成渐变色
Windows.UI.Color RandomColor()
{
var rand = new Random(Guid.NewGuid().GetHashCode());
double h = rand.NextDouble() * 360;
double s = 0.35 + rand.NextDouble() * 0.3; // 0.35~0.65,适中饱和度
double l = 0.5 + rand.NextDouble() * 0.3; // 0.5~0.8,明亮
return CommunityToolkit.WinUI.Helpers.ColorHelper.FromHsl(h, s, l);
}
Windows.UI.Color color1 = RandomColor();
Windows.UI.Color color2 = RandomColor();
using (var ds = renderTarget.CreateDrawingSession())
{
// 绘制线性渐变背景
using var gradientBrush = new Microsoft.Graphics.Canvas.Brushes.CanvasLinearGradientBrush(ds, color1, color2)
{
StartPoint = new Vector2(0, 0),
EndPoint = new Vector2(width, height)
};
ds.FillRectangle(0, 0, width, height, gradientBrush);
}
// 保存为 PNG 并转为 byte[]
var stream = new InMemoryRandomAccessStream();
await renderTarget.SaveAsync(stream, CanvasBitmapFileFormat.Png);
stream.Seek(0);
Uri uri = new Uri($"ms-appx:///Assets/AlbumArtPlaceholder.png");
StorageFile file = await StorageFile.GetFileFromApplicationUriAsync(uri);
IRandomAccessStream stream = await file.OpenAsync(FileAccessMode.Read);
return stream;
}
public static Task<ThemeColorResult> GetAccentColorAsync(BitmapDecoder decoder, PaletteGeneratorType generatorType)
{
return generatorType switch

View File

@@ -150,7 +150,8 @@ namespace BetterLyrics.WinUI3.Helper
{
// 补偿曲线
float[] frequencies = { 20, 50, 100, 200, 500, 1000, 2000, 4000, 8000, 16000, 20000 };
float[] gains = { 0.5f, 0.3f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.1f, 0.9f, 0.8f };
//float[] gains = { 0.5f, 0.3f, 0.4f, 0.6f, 0.8f, 1.0f, 1.2f, 1.3f, 1.1f, 0.9f, 0.8f };
float[] gains = { 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f, 1.0f };
if (freq <= frequencies[0])
{
return gains[0];

View File

@@ -6,6 +6,7 @@ using BetterLyrics.WinUI3.Services.MediaSessionsService;
using BetterLyrics.WinUI3.Views;
using CommunityToolkit.Mvvm.DependencyInjection;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using System;
@@ -31,6 +32,8 @@ namespace BetterLyrics.WinUI3.Helper
private static readonly ILiveStatesService _liveStatesService = Ioc.Default.GetRequiredService<ILiveStatesService>();
private static readonly IMediaSessionsService _mediaSessionsService = Ioc.Default.GetRequiredService<IMediaSessionsService>();
private static DispatcherQueueTimer? _setLyricsWindowVisibilityByPlayingStatusTimer;
public static void HideWindow<T>()
{
var window = _activeWindows.Find(w => w is T);
@@ -346,27 +349,36 @@ namespace BetterLyrics.WinUI3.Helper
Shell32.SHAppBarMessage(Shell32.ABM.ABM_SETPOS, ref abd);
}
public static void SetLyricsWindowVisibilityByPlayingStatus()
/// <summary>
///
/// </summary>
/// <param name="dispatcherQueue">请确保此参数指向同一个对象,建议传值 BaseViewModel._dispatcherQueue</param>
public static void SetLyricsWindowVisibilityByPlayingStatus(DispatcherQueue dispatcherQueue)
{
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
_setLyricsWindowVisibilityByPlayingStatusTimer ??= dispatcherQueue.CreateTimer();
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
_setLyricsWindowVisibilityByPlayingStatusTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
var window = GetWindowByWindowType<LyricsWindow>();
if (window == null) return;
if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && !_mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(false);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(false);
}
HideWindow<LyricsWindow>();
}
HideWindow<LyricsWindow>();
}
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
else if (_liveStatesService.LiveStates.LyricsWindowStatus.AutoShowOrHideWindow && _mediaSessionsService.IsPlaying)
{
SetIsWorkArea<LyricsWindow>(true);
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsWorkArea)
{
SetIsWorkArea<LyricsWindow>(true);
}
OpenOrShowWindow<LyricsWindow>();
}
OpenOrShowWindow<LyricsWindow>();
}
}, Constants.Time.DebounceTimeout);
}
}

View File

@@ -91,11 +91,6 @@ namespace BetterLyrics.WinUI3.Models
this.OnPropertyChanged(nameof(AlbumArtLayoutSettings));
}
partial void OnAutoShowOrHideWindowChanged(bool value)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
}
public void UpdateMonitorNameAndBounds()
{
var lyricsWindow = WindowHelper.GetWindowByWindowType<LyricsWindow>();

View File

@@ -1,6 +1,7 @@
// 2025/6/23 by Zhe Fang
using CommunityToolkit.Mvvm.ComponentModel;
using System;
using Windows.Graphics.Imaging;
using Windows.UI;
@@ -31,4 +32,14 @@ namespace BetterLyrics.WinUI3.Models
public SongInfo() { }
}
public static class SongInfoExtensions
{
public static SongInfo Placeholder => new()
{
Title = "N/A",
Album = "N/A",
Artist = "N/A",
};
}
}

View File

@@ -93,6 +93,9 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
case nameof(LyricsWindowStatus.TitleBarArea):
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
break;
case nameof(LyricsWindowStatus.AutoShowOrHideWindow):
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
break;
default:
break;
}
@@ -134,7 +137,7 @@ namespace BetterLyrics.WinUI3.Services.LiveStatesService
WindowHelper.SetIsAlwaysOnTop<LyricsWindow>(LiveStates.LyricsWindowStatus.IsAlwaysOnTop);
WindowHelper.SetIsClickThrough<LyricsWindow>(LiveStates.LyricsWindowStatus.IsClickThrough);
WindowHelper.SetIsBorderless<LyricsWindow>(LiveStates.LyricsWindowStatus.IsBorderless);
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
WindowHelper.SetTitleBarArea<LyricsWindow>(LiveStates.LyricsWindowStatus.TitleBarArea);
if (LiveStates.LyricsWindowStatus.IsWorkArea)

View File

@@ -491,84 +491,87 @@ namespace BetterLyrics.WinUI3.Services.LyricsSearchService
}, searcher);
}
if (result is QQMusicSearchResult qqResult)
if (result != null)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
var translated = response?.Trans;
if (!string.IsNullOrEmpty(translated))
if (result is QQMusicSearchResult qqResult)
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.QQTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
}
else if (result is NeteaseSearchResult neteaseResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
var original = response?.Lrc?.Lyric;
var translated = response?.Tlyric?.Lyric;
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.NeteaseTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
string? original = null;
var candidate = response?.Candidates.FirstOrDefault();
if (candidate != null)
{
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
if (original != null)
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.QQMusicApi.GetLyricsAsync(qqResult.Id);
var original = response?.Lyrics;
var translated = response?.Trans;
if (!string.IsNullOrEmpty(translated))
{
var parsedList = Lyricify.Lyrics.Parsers.KrcParser.ParseLyrics(original);
if (parsedList != null)
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.QQTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
}
else if (result is NeteaseSearchResult neteaseResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.NeteaseApi.GetLyric(neteaseResult.Id);
var original = response?.Lrc?.Lyric;
var translated = response?.Tlyric?.Lyric;
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.NeteaseTranslationCacheDirectory
);
}
lyricsSearchResult.Raw = original;
}
else if (result is KugouSearchResult kugouResult)
{
var response = await Lyricify.Lyrics.Helpers.ProviderHelper.KugouApi.GetSearchLyrics(hash: kugouResult.Hash);
string? original = null;
var candidate = response?.Candidates.FirstOrDefault();
if (candidate != null)
{
original = await Lyricify.Lyrics.Decrypter.Krc.Helper.GetLyricsAsync(candidate.Id, candidate.AccessKey);
if (original != null)
{
string translated = "";
foreach (var item in parsedList)
var parsedList = Lyricify.Lyrics.Parsers.KrcParser.ParseLyrics(original);
if (parsedList != null)
{
if (item is Lyricify.Lyrics.Models.FullSyllableLineInfo fullSyllableLineInfo)
string translated = "";
foreach (var item in parsedList)
{
var startTimeSpan = TimeSpan.FromMilliseconds(fullSyllableLineInfo.StartTime ?? 0);
string startTimeStr = startTimeSpan.ToString(@"mm\:ss\.ff");
string chTranslation = fullSyllableLineInfo.Translations.GetValueOrDefault("zh") ?? "";
translated += $"[{startTimeStr}]{chTranslation}\n";
if (item is Lyricify.Lyrics.Models.FullSyllableLineInfo fullSyllableLineInfo)
{
var startTimeSpan = TimeSpan.FromMilliseconds(fullSyllableLineInfo.StartTime ?? 0);
string startTimeStr = startTimeSpan.ToString(@"mm\:ss\.ff");
string chTranslation = fullSyllableLineInfo.Translations.GetValueOrDefault("zh") ?? "";
translated += $"[{startTimeStr}]{chTranslation}\n";
}
}
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.KugouTranslationCacheDirectory
);
}
}
if (!string.IsNullOrEmpty(translated))
{
FileHelper.WriteLyricsCache(
title,
artist,
album,
translated,
LyricsFormat.Lrc,
PathHelper.KugouTranslationCacheDirectory
);
}
}
}
}
lyricsSearchResult.Raw = original;
lyricsSearchResult.Raw = original;
}
}
lyricsSearchResult.Title = result?.Title;

View File

@@ -46,7 +46,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
if (buffer == null)
{
using var placeHolderStream = await ImageHelper.CreateTextPlaceholderBytesAsync(500, 500);
using var placeHolderStream = await ImageHelper.GetAlbumArtPlaceholderAsync();
var tempBuffer = new Windows.Storage.Streams.Buffer((uint)placeHolderStream.Size);
await placeHolderStream.ReadAsync(tempBuffer, (uint)placeHolderStream.Size, InputStreamOptions.None);
buffer = tempBuffer;

View File

@@ -213,14 +213,14 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
_mediaManager.CurrentMediaSessions.ToList().ForEach(x => RecordMediaSourceProviderInfo(x.Value));
}
private void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
private async void MediaManager_OnFocusedSessionChanged(MediaManager.MediaSession? mediaSession)
{
if (!_mediaManager.IsStarted) return;
SendFocusedMessagesAsync();
await SendFocusedMessagesAsync();
}
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties timelineProperties)
private void MediaManager_OnAnyTimelinePropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionTimelineProperties? timelineProperties)
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
@@ -241,16 +241,16 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
if (IsMediaSourceTimelineSyncEnabled(mediaSession.Id))
{
_cachedPosition = timelineProperties.Position;
_cachedPosition = timelineProperties?.Position ?? TimeSpan.Zero;
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties.EndTime));
TimelineChanged?.Invoke(this, new TimelineChangedEventArgs(_cachedPosition, timelineProperties?.EndTime ?? TimeSpan.Zero));
});
}
}
}
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo playbackInfo)
private void MediaManager_OnAnyPlaybackStateChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionPlaybackInfo? playbackInfo)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
@@ -268,7 +268,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
}
else
{
_cachedIsPlaying = playbackInfo.PlaybackStatus switch
_cachedIsPlaying = playbackInfo?.PlaybackStatus switch
{
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false,
@@ -279,26 +279,28 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
});
}
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession? mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProperties)
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, async () =>
{
if (!_mediaManager.IsStarted) return;
if (mediaSession == null) return;
if (mediaSession == null)
{
_cachedSongInfo = SongInfoExtensions.Placeholder;
}
string sessionId = mediaSession.Id;
string? sessionId = mediaSession?.Id;
var desiredSession = GetCurrentSession();
//RecordMediaSourceProviderInfo(mediaSession);
if (mediaSession != desiredSession) return;
if (!IsMediaSourceEnabled(sessionId))
if (sessionId != null && !IsMediaSourceEnabled(sessionId))
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -315,33 +317,33 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
currentMediaSourceProviderInfo?.PositionOffset = 0;
}
string fixedArtist = mediaProperties.Artist;
string fixedAlbum = mediaProperties.AlbumTitle;
string fixedArtist = mediaProperties?.Artist ?? "N/A";
string fixedAlbum = mediaProperties?.AlbumTitle ?? "N/A";
string? songId = null;
if (sessionId == Constants.PlayerID.AppleMusic || sessionId == Constants.PlayerID.AppleMusicAlternative)
{
fixedArtist = mediaProperties.Artist.Split(" — ").FirstOrDefault() ?? mediaProperties.Artist;
fixedAlbum = mediaProperties.Artist.Split(" — ").LastOrDefault() ?? mediaProperties.AlbumTitle;
fixedArtist = mediaProperties?.Artist.Split(" — ").FirstOrDefault() ?? (mediaProperties?.Artist ?? "N/A");
fixedAlbum = mediaProperties?.Artist.Split(" — ").LastOrDefault() ?? (mediaProperties?.AlbumTitle ?? "N/A");
}
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId))
else if (PlayerIdMatcher.IsNeteaseFamily(sessionId ?? ""))
{
songId = mediaProperties.Genres.FirstOrDefault()?.Replace("NCM-", "");
songId = mediaProperties?.Genres.FirstOrDefault()?.Replace("NCM-", "");
}
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Title = mediaProperties?.Title ?? "N/A",
Artist = fixedArtist,
Album = fixedAlbum,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
DurationMs = mediaSession?.ControlSession?.GetTimelineProperties().EndTime.TotalMilliseconds,
PlayerId = sessionId,
SongId = songId
};
_cachedSongInfo.Duration = (int)(_cachedSongInfo.DurationMs / 1000f);
_cachedSongInfo.Duration = (int)((_cachedSongInfo.DurationMs ?? 0) / 1000f);
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
mediaProperties?.Title, mediaProperties?.Artist, mediaProperties?.AlbumTitle);
if (sessionId == Constants.PlayerID.LXMusic)
{
@@ -356,7 +358,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_SMTCAlbumArtBuffer = _lxMusicAlbumArtBytes.AsBuffer();
}
else if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
else if (mediaProperties?.Thumbnail is IRandomAccessStreamReference streamReference)
{
_SMTCAlbumArtBuffer = await ImageHelper.ToBufferAsync(streamReference);
}
@@ -438,7 +440,7 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{
_cachedSongInfo = null;
_cachedSongInfo = SongInfoExtensions.Placeholder;
_cachedIsPlaying = false;
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
@@ -448,14 +450,21 @@ namespace BetterLyrics.WinUI3.Services.MediaSessionsService
private async Task SendFocusedMessagesAsync()
{
var desiredSession = GetCurrentSession();
if (desiredSession == null || desiredSession.ControlSession == null) return;
GlobalSystemMediaTransportControlsSessionMediaProperties? mediaProps = null;
var mediaProps = await desiredSession.ControlSession.TryGetMediaPropertiesAsync();
if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession.ControlSession.GetTimelineProperties());
var desiredSession = GetCurrentSession();
//if (desiredSession == null || desiredSession.ControlSession == null) return;
try
{
mediaProps = await desiredSession?.ControlSession?.TryGetMediaPropertiesAsync();
}
catch (Exception) { }
//if (desiredSession == null || desiredSession.ControlSession == null) return;
MediaManager_OnAnyTimelinePropertyChanged(desiredSession, desiredSession?.ControlSession?.GetTimelineProperties());
MediaManager_OnAnyMediaPropertyChanged(desiredSession, mediaProps);
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession.ControlSession.GetPlaybackInfo());
MediaManager_OnAnyPlaybackStateChanged(desiredSession, desiredSession?.ControlSession?.GetPlaybackInfo());
}
private void StartSSE()

View File

@@ -19,10 +19,14 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
// 新建一个 AppSettings 类
public partial class SettingsService : BaseViewModel, ISettingsService
{
private DispatcherQueueTimer _writeAppSettingsTimer;
public AppSettings AppSettings { get; set; }
public SettingsService()
{
_writeAppSettingsTimer = _dispatcherQueue.CreateTimer();
AppSettings = ReadAppSettings();
AppSettings.PropertyChanged += AppSettings_PropertyChanged;
@@ -94,12 +98,12 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
private void AppSettings_ItemPropertyChanged(object? sender, ItemPropertyChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
WriteAppSettingsDebounce();
WriteAppSettings();
}
private void AppSettings_PropertyChanged(object? sender, System.ComponentModel.PropertyChangedEventArgs e)
@@ -112,7 +116,7 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
default:
break;
}
WriteAppSettingsDebounce();
WriteAppSettings();
}
/// <summary>
@@ -161,9 +165,9 @@ namespace BetterLyrics.WinUI3.Services.SettingsService
return data;
}
private void WriteAppSettingsDebounce()
private void WriteAppSettings()
{
_dispatcherQueueTimer.Debounce(() =>
_writeAppSettingsTimer.Debounce(() =>
{
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.Low, () =>
{

View File

@@ -11,14 +11,12 @@ namespace BetterLyrics.WinUI3.ViewModels
public partial class BaseViewModel : ObservableRecipient
{
private protected readonly DispatcherQueue _dispatcherQueue;
private protected readonly DispatcherQueueTimer _dispatcherQueueTimer;
public BaseViewModel()
{
IsActive = true;
_dispatcherQueue = DispatcherQueue.GetForCurrentThread();
_dispatcherQueueTimer = _dispatcherQueue.CreateTimer();
}
}
}

View File

@@ -18,6 +18,7 @@ using System.Numerics;
using Windows.Foundation;
using Windows.Graphics.Effects;
using Windows.UI;
using static Vanara.PInvoke.Shell32;
namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
{
@@ -128,21 +129,20 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
for (int i = 0; i < _spectrumAnalyzer.BarCount; i++)
{
float x = i * pointSpacing;
float amplitude = _spectrumAnalyzer.SmoothSpectrum.Average() * 10 + _spectrumAnalyzer.SmoothSpectrum[i] * 0.5f;
float y = (float)_canvasHeight - amplitude;
float y = _spectrumAnalyzer.SmoothSpectrum[i];
points[i] = new Vector2(x, y);
}
// 限制最高点高度
var minY = points.OrderBy(p => p.Y).FirstOrDefault().Y;
var limitY = _canvasHeight * (1 - 0.1f);
if (minY < limitY)
var maxY = points.OrderByDescending(p => p.Y).FirstOrDefault().Y;
var limitY = _canvasHeight * 0.2f;
if (maxY > limitY)
{
var num = (float)(limitY / minY);
var num = (float)(limitY / maxY);
points = points.Select(p => new Vector2(p.X, p.Y * num)).ToArray();
}
// 防止越过画布边界
points = points.Select(p => new Vector2(p.X, (float)(Math.Min(_canvasHeight, p.Y)))).ToArray();
points = points.Select(p => new Vector2(p.X, (float)(_canvasHeight - p.Y))).ToArray();
// 用于填充的闭合路径
using var pathBuilder = new CanvasPathBuilder(ds);
@@ -176,7 +176,7 @@ namespace BetterLyrics.WinUI3.ViewModels.LyricsRendererViewModel
var gradientStops = new CanvasGradientStop[]
{
new() { Position = 0.0f, Color = Colors.Transparent },
new() { Position = 0.8f, Color = Colors.Transparent },
new() { Position = 0.7f, Color = Colors.Transparent },
new() { Position = 1.0f, Color = _adaptiveColoredFontColor ?? _albumArtAccentColor1Transition.Value }
};

View File

@@ -16,13 +16,13 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages;
using CommunityToolkit.WinUI;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using System.Collections.Generic;
using Vanara.PInvoke;
using Windows.Foundation;
using Windows.System;
using Windows.UI;
using WinRT.Interop;
using WinUIEx;
@@ -39,6 +39,7 @@ namespace BetterLyrics.WinUI3
private readonly ILiveStatesService _liveStatesService;
private ForegroundWindowWatcher? _fgWindowWatcher = null;
private DispatcherQueueTimer? _fgWindowWatcherTimer = null;
public LyricsWindowViewModel(ISettingsService settingsService, IMediaSessionsService mediaSessionsService, ILiveStatesService liveStatesService)
{
@@ -54,7 +55,7 @@ namespace BetterLyrics.WinUI3
private void PlaybackService_IsPlayingChanged(object? sender, Events.IsPlayingChangedEventArgs e)
{
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus();
WindowHelper.SetLyricsWindowVisibilityByPlayingStatus(_dispatcherQueue);
}
[ObservableProperty] public partial AppSettings AppSettings { get; set; }
@@ -142,11 +143,13 @@ namespace BetterLyrics.WinUI3
if (window == null) return;
var hwnd = WindowNative.GetWindowHandle(window);
_fgWindowWatcherTimer = _dispatcherQueue.CreateTimer();
_fgWindowWatcher = new ForegroundWindowWatcher(
hwnd,
fgHwnd =>
{
_dispatcherQueueTimer.Debounce(() =>
_fgWindowWatcherTimer.Debounce(() =>
{
if (_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTop &&
_liveStatesService.LiveStates.LyricsWindowStatus.IsAlwaysOnTopPolling &&

View File

@@ -37,6 +37,8 @@ namespace BetterLyrics.WinUI3.ViewModels
private readonly MediaTimelineController _timelineController = new();
private readonly SystemMediaTransportControls _smtc;
private readonly DispatcherQueueTimer _refreshSongsTimer;
// All songs
private List<Track> _tracks = [];
// Songs in current playlist
@@ -95,6 +97,8 @@ namespace BetterLyrics.WinUI3.ViewModels
public MusicGalleryViewModel(ISettingsService settingsService, ILibWatcherService libWatcherService)
{
_refreshSongsTimer = _dispatcherQueue.CreateTimer();
_settingsService = settingsService;
AppSettings = _settingsService.AppSettings;
@@ -263,7 +267,7 @@ namespace BetterLyrics.WinUI3.ViewModels
public void RefreshSongs()
{
_dispatcherQueueTimer.Debounce(() =>
_refreshSongsTimer.Debounce(() =>
{
IsDataLoading = true;
_tracks.Clear();

View File

@@ -29,35 +29,6 @@
<dev:SnowFlakeEffect FlakeCount="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.SnowFlakeOverlayAmount, Mode=OneWay}" Visibility="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsBackgroundSettings.IsSnowFlakeOverlayEnabled, Mode=OneWay, Converter={StaticResource BoolToVisibilityConverter}}" />
<!-- No music playing placeholder -->
<Grid x:Name="NoMusicPlayingGrid" Background="{ThemeResource AcrylicBackgroundFillColorBaseBrush}">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock
x:Uid="MainPageNoMusicPlaying"
HorizontalAlignment="Center"
FontFamily="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.LyricsCJKFontFamily, Mode=OneWay}"
FontSize="{x:Bind ViewModel.LiveStates.LyricsWindowStatus.LyricsStyleSettings.OriginalLyricsFontSize, Mode=OneWay}"
TextWrapping="Wrap" />
</StackPanel>
<Grid.OpacityTransition>
<ScalarTransition />
</Grid.OpacityTransition>
<interactivity:Interaction.Behaviors>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="Equal"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="1" />
</interactivity:DataTriggerBehavior>
<interactivity:DataTriggerBehavior
Binding="{x:Bind ViewModel.SongInfo, Mode=OneWay}"
ComparisonCondition="NotEqual"
Value="{x:Null}">
<interactivity:ChangePropertyAction PropertyName="Opacity" Value="0" />
</interactivity:DataTriggerBehavior>
</interactivity:Interaction.Behaviors>
</Grid>
<!-- Bottom command area -->
<Grid
x:Name="BottomCommandGrid"