Compare commits

...

13 Commits

Author SHA1 Message Date
Zhe Fang
0606711023 Merge pull request #43 from jayfunc/dev
v1.0.17.0
2025-07-17 14:54:12 -04:00
Zhe Fang
8f997ca3d9 fix #42 2025-07-17 14:45:00 -04:00
Zhe Fang
042d557eb1 fix #40 fix #14 2025-07-17 14:29:47 -04:00
Zhe Fang
153679228d fix playbackservice 2025-07-17 13:04:50 -04:00
Zhe Fang
5a4fe54ac2 update webhook username 2025-07-17 10:42:12 -04:00
Zhe Fang
6ad79180e4 Merge pull request #19 from jayfunc/dev
github actions
2025-07-11 19:49:17 -04:00
Zhe Fang
42af22a7e3 Merge pull request #18 from jayfunc/dev
v1.0.11.0
2025-07-11 18:08:49 -04:00
Zhe Fang
509079e8c7 Merge pull request #10 from jayfunc/dev
1.0.9.0
2025-07-07 17:32:48 -04:00
Zhe Fang
c50c180aa0 Merge pull request #9 from jayfunc/dev
v1.0.7.0 release
2025-06-30 20:56:34 -04:00
Zhe Fang
5e74468194 Merge pull request #8 from jayfunc/dev
add multiple online lyrics providers; add desktop mode; improve blur/shadow/scrolling effect performance; fix bugs
2025-06-26 21:51:36 -04:00
Zhe Fang
a93b535667 Merge pull request #7 from jayfunc/dev
update to v1.0.5.0
2025-06-23 13:41:33 -04:00
Zhe Fang
2c55b11e70 Merge pull request #6 from jayfunc/dev
fix
2025-06-18 17:16:06 -04:00
Zhe Fang
7bca1d1205 Merge pull request #5 from jayfunc/dev
Add dock mode, improve glow effect, fix bugs ...
2025-06-17 21:50:22 -04:00
20 changed files with 180 additions and 78 deletions

View File

@@ -13,7 +13,7 @@ jobs:
with: with:
webhook_url: ${{ secrets.WEBHOOK_URL }} webhook_url: ${{ secrets.WEBHOOK_URL }}
color: "2105893" color: "2105893"
username: "Release Changelog" username: "GitHub"
avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png" avatar_url: "https://github.githubassets.com/assets/GitHub-Mark-ea2971cee799.png"
content: "||@everyone||" content: "||@everyone||"
footer_title: "Changelog" footer_title: "Changelog"

View File

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

View File

@@ -51,6 +51,7 @@
<PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" /> <PackageReference Include="Microsoft.Graphics.Win2D" Version="1.3.2" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" /> <PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.4188" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" /> <PackageReference Include="Microsoft.WindowsAppSDK" Version="1.7.250606001" />
<PackageReference Include="Nito.AsyncEx" Version="5.1.2" />
<PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" /> <PackageReference Include="Nito.AsyncEx.Tasks" Version="5.1.2" />
<PackageReference Include="NTextCat" Version="0.3.65" /> <PackageReference Include="NTextCat" Version="0.3.65" />
<PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" /> <PackageReference Include="Serilog.Extensions.Logging" Version="9.0.2" />

View File

@@ -8,9 +8,9 @@ using Windows.UI;
namespace BetterLyrics.WinUI3.Events namespace BetterLyrics.WinUI3.Events
{ {
public class AlbumArtChangedEventArgs : EventArgs public class AlbumArtChangedEventArgs(SoftwareBitmap? albumArtSwBitmap, Color? albumArtAccentColor) : EventArgs
{ {
public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = null; public SoftwareBitmap? AlbumArtSwBitmap { get; set; } = albumArtSwBitmap;
public Color? AlbumArtAccentColor { get; set; } = null; public Color? AlbumArtAccentColor { get; set; } = albumArtAccentColor;
} }
} }

View File

@@ -167,9 +167,11 @@ namespace BetterLyrics.WinUI3.Helper
public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef) public static async Task<byte[]> ToByteArrayAsync(IRandomAccessStreamReference streamRef)
{ {
using IRandomAccessStream stream = await streamRef.OpenReadAsync(); using IRandomAccessStream stream = await streamRef.OpenReadAsync();
using var memoryStream = new MemoryStream(); using var reader = new DataReader(stream);
await stream.AsStreamForRead().CopyToAsync(memoryStream); await reader.LoadAsync((uint)stream.Size);
return memoryStream.ToArray(); byte[] buffer = new byte[stream.Size];
reader.ReadBytes(buffer);
return buffer;
} }
public static float GetAverageLuminance(CanvasBitmap bitmap) public static float GetAverageLuminance(CanvasBitmap bitmap)

View File

@@ -1,4 +1,5 @@
using System; using Nito.AsyncEx;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text; using System.Text;
@@ -9,23 +10,34 @@ namespace BetterLyrics.WinUI3.Helper
{ {
public class LatestOnlyTaskRunner public class LatestOnlyTaskRunner
{ {
private CancellationTokenSource? _cts; private readonly AsyncLock _mutex = new();
private CancellationTokenSource _cts;
public async Task RunAsync(Func<CancellationToken, Task> func) public async Task RunAsync(Func<CancellationToken, Task> action)
{ {
_cts?.Cancel(); CancellationTokenSource oldCts;
_cts = new CancellationTokenSource();
var token = _cts.Token; // 使用 AsyncLock 保证线程安全
using (await _mutex.LockAsync())
{
// 取消旧的
oldCts = _cts;
_cts = new CancellationTokenSource();
}
oldCts?.Cancel();
oldCts?.Dispose();
CancellationToken token = _cts.Token;
try try
{ {
await func(token); await action(token);
}
catch (OperationCanceledException)
{
// 可以选择忽略取消异常
} }
catch (OperationCanceledException) { }
}
public void Cancel()
{
_cts?.Cancel();
} }
} }
} }

View File

@@ -48,6 +48,7 @@ namespace BetterLyrics.WinUI3.Helper
} }
} }
PostProcessLyricsLines(durationMs.Value); PostProcessLyricsLines(durationMs.Value);
_lyricsDataArr.Add(new LyricsData()); // 为机翻预留
return _lyricsDataArr; return _lyricsDataArr;
} }
@@ -113,8 +114,7 @@ namespace BetterLyrics.WinUI3.Helper
// 初始化每种语言的歌词列表 // 初始化每种语言的歌词列表
_lyricsDataArr.Clear(); _lyricsDataArr.Clear();
for (int i = 0; i < languageCount; i++) for (int i = 0; i < languageCount; i++) _lyricsDataArr.Add(new LyricsData());
_lyricsDataArr.Add(new LyricsData());
// 遍历每个时间分组 // 遍历每个时间分组
foreach (var group in grouped) foreach (var group in grouped)

View File

@@ -71,7 +71,7 @@ namespace BetterLyrics.WinUI3.Models
} }
else else
{ {
line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}({translationArr[i]})"; line.DisplayedText = $"{line.OriginalText}{StringHelper.NewLine}{translationArr[i]}";
} }
i++; i++;
} }
@@ -85,6 +85,30 @@ namespace BetterLyrics.WinUI3.Models
} }
} }
public LyricsData CreateLyricsDataFrom(string translation)
{
var result = new LyricsData(LyricsLines.Select(line => new LyricsLine
{
StartMs = line.StartMs,
EndMs = line.EndMs,
}).ToList());
List<string> translationArr = translation.Split(StringHelper.NewLine).ToList();
int i = 0;
foreach (var line in result.LyricsLines)
{
if (i >= translationArr.Count)
{
break;
}
else
{
line.OriginalText = translationArr[i];
}
i++;
}
return result;
}
public static LyricsData GetNotfoundPlaceholder(int durationMs) public static LyricsData GetNotfoundPlaceholder(int durationMs)
{ {
return new LyricsData([new LyricsLine return new LyricsData([new LyricsLine

View File

@@ -19,6 +19,8 @@ namespace BetterLyrics.WinUI3.Services
Task PauseAsync(); Task PauseAsync();
Task PreviousAsync(); Task PreviousAsync();
Task NextAsync(); Task NextAsync();
bool IsPlaying { get; }
} }
} }

View File

@@ -13,6 +13,7 @@ using Microsoft.UI.Dispatching;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime; using System.Runtime.InteropServices.WindowsRuntime;
using System.Text.Json; using System.Text.Json;
@@ -34,17 +35,18 @@ namespace BetterLyrics.WinUI3.Services
private readonly string _lxMusicId = "cn.toside.music.desktop"; private readonly string _lxMusicId = "cn.toside.music.desktop";
private bool _cachedIsPlaying = false;
private EventSourceReader? _sse = null; private EventSourceReader? _sse = null;
private readonly MediaManager _mediaManager = new(); private readonly MediaManager _mediaManager = new();
private readonly LatestOnlyTaskRunner _AlbumArtRefreshRunner = new(); private readonly LatestOnlyTaskRunner _albumArtRefreshRunner = new();
private readonly LatestOnlyTaskRunner _OnAnyMediaPropertyChangedRunner = new(); private readonly LatestOnlyTaskRunner _onAnyMediaPropertyChangedRunner = new();
private SongInfo? _cachedSongInfo; private SongInfo? _cachedSongInfo;
private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo; private List<MediaSourceProviderInfo> _mediaSourceProvidersInfo;
private byte[]? _SMTCAlbumArtBytes = null; private byte[]? _SMTCAlbumArtBytes = null;
private AlbumArtChangedEventArgs _albumArtChangedEventArgs = new();
public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged; public event EventHandler<IsPlayingChangedEventArgs>? IsPlayingChanged;
public event EventHandler<PositionChangedEventArgs>? PositionChanged; public event EventHandler<PositionChangedEventArgs>? PositionChanged;
@@ -61,6 +63,8 @@ namespace BetterLyrics.WinUI3.Services
InitMediaManager(); InitMediaManager();
} }
public bool IsPlaying => _cachedIsPlaying;
private bool IsMediaSourceEnabled(string id) private bool IsMediaSourceEnabled(string id)
{ {
return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true; return _mediaSourceProvidersInfo.FirstOrDefault(s => s.Provider == id)?.IsEnabled ?? true;
@@ -88,7 +92,7 @@ namespace BetterLyrics.WinUI3.Services
} }
else else
{ {
_dispatcherQueue.TryEnqueue(async () => Task.Run(async () =>
{ {
try try
{ {
@@ -96,11 +100,7 @@ namespace BetterLyrics.WinUI3.Services
MediaManager_OnAnyMediaPropertyChanged(mediaSession, props); MediaManager_OnAnyMediaPropertyChanged(mediaSession, props);
MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo()); MediaManager_OnAnyPlaybackStateChanged(mediaSession, mediaSession.ControlSession.GetPlaybackInfo());
} }
catch (Exception ex) catch (Exception) { }
{
_logger.LogWarning(ex, "TryGetMediaPropertiesAsync failed");
SendNullMessages();
}
}); });
} }
} }
@@ -123,7 +123,7 @@ namespace BetterLyrics.WinUI3.Services
RecordMediaSourceProviderInfo(mediaSession); RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return; if (!IsMediaSourceEnabled(mediaSession.ControlSession.SourceAppUserModelId) || mediaSession != _mediaManager.GetFocusedSession()) return;
bool isPlaying = playbackInfo.PlaybackStatus switch _cachedIsPlaying = playbackInfo.PlaybackStatus switch
{ {
GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true, GlobalSystemMediaTransportControlsSessionPlaybackStatus.Playing => true,
_ => false, _ => false,
@@ -132,24 +132,32 @@ namespace BetterLyrics.WinUI3.Services
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, _dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High,
() => () =>
{ {
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(isPlaying)); IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
} }
); );
} }
private void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties) private async void MediaManager_OnAnyMediaPropertyChanged(MediaManager.MediaSession mediaSession, GlobalSystemMediaTransportControlsSessionMediaProperties mediaProperties)
{ {
_ = _OnAnyMediaPropertyChangedRunner.RunAsync(async token => string id = mediaSession.ControlSession.SourceAppUserModelId;
RecordMediaSourceProviderInfo(mediaSession);
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Artist = mediaProperties.Artist,
Album = mediaProperties.AlbumTitle,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
SourceAppUserModelId = id,
};
await _onAnyMediaPropertyChangedRunner.RunAsync(async token =>
{ {
_logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}", _logger.LogInformation("Media properties changed: Title: {Title}, Artist: {Artist}, Album: {Album}",
mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle); mediaProperties.Title, mediaProperties.Artist, mediaProperties.AlbumTitle);
RecordMediaSourceProviderInfo(mediaSession);
string id = mediaSession.ControlSession.SourceAppUserModelId;
if (!IsMediaSourceEnabled(id) || mediaSession != _mediaManager.GetFocusedSession()) return;
token.ThrowIfCancellationRequested();
if (id == _lxMusicId) if (id == _lxMusicId)
{ {
StartSSE(); StartSSE();
@@ -159,30 +167,24 @@ namespace BetterLyrics.WinUI3.Services
StopSSE(); StopSSE();
} }
_cachedSongInfo = new SongInfo
{
Title = mediaProperties.Title,
Artist = mediaProperties.Artist,
Album = mediaProperties.AlbumTitle,
DurationMs = mediaSession.ControlSession.GetTimelineProperties().EndTime.TotalMilliseconds,
SourceAppUserModelId = id,
};
if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference) if (mediaProperties.Thumbnail is IRandomAccessStreamReference streamReference)
{ {
_SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference); _SMTCAlbumArtBytes = await ImageHelper.ToByteArrayAsync(streamReference);
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
} }
else
{
_SMTCAlbumArtBytes = null;
}
_ = _AlbumArtRefreshRunner.RunAsync(async tokne => await _albumArtRefreshRunner.RunAsync(async tokne =>
{ {
await UpdateAlbumArtRelated(tokne); await UpdateAlbumArtRelated(tokne);
}); });
if (!token.IsCancellationRequested) if (!token.IsCancellationRequested)
{ {
_dispatcherQueue.TryEnqueue(DispatcherQueuePriority.High, _dispatcherQueue.TryEnqueue(() =>
() =>
{ {
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo)); SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
}); });
@@ -227,8 +229,9 @@ namespace BetterLyrics.WinUI3.Services
() => () =>
{ {
_cachedSongInfo = null; _cachedSongInfo = null;
_cachedIsPlaying = false;
SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo)); SongInfoChanged?.Invoke(this, new SongInfoChangedEventArgs(_cachedSongInfo));
IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(false)); IsPlayingChanged?.Invoke(this, new IsPlayingChangedEventArgs(_cachedIsPlaying));
PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero)); PositionChanged?.Invoke(this, new PositionChangedEventArgs(TimeSpan.Zero));
}); });
} }
@@ -264,22 +267,34 @@ namespace BetterLyrics.WinUI3.Services
var decoder = await BitmapDecoder.CreateAsync(stream); var decoder = await BitmapDecoder.CreateAsync(stream);
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
_albumArtChangedEventArgs.AlbumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied); var _albumArtSwBitmap = await decoder.GetSoftwareBitmapAsync(BitmapPixelFormat.Rgba8, BitmapAlphaMode.Premultiplied);
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
_albumArtChangedEventArgs.AlbumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault(); var _albumArtAccentColor = ImageHelper.GetAccentColorsFromByte(bytes).FirstOrDefault();
_dispatcherQueue.TryEnqueue(() => _dispatcherQueue.TryEnqueue(() =>
{ {
AlbumArtChangedChanged?.Invoke(this, _albumArtChangedEventArgs); AlbumArtChangedChanged?.Invoke(this, new AlbumArtChangedEventArgs(_albumArtSwBitmap, _albumArtAccentColor));
}); });
} }
private void StartSSE() private void StartSSE()
{ {
_sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress")).Start(); try
_sse.MessageReceived += Sse_MessageReceived; {
_sse.Disconnected += Sse_Disconnected; _sse = new EventSourceReader(new Uri($"{_settingsService.LXMusicServer}/subscribe-player-status?filter=progress")).Start();
_sse.MessageReceived += Sse_MessageReceived;
_sse.Disconnected += Sse_Disconnected;
}
catch (Exception)
{
_logger.LogError("Failed to start SSE connection for LX Music.");
_dispatcherQueue.TryEnqueue(() =>
{
App.Current.LyricsWindowNotificationPanel?.Notify(App.ResourceLoader!.GetString("FailToStartLXMusicServer"), Microsoft.UI.Xaml.Controls.InfoBarSeverity.Error);
});
StopSSE();
}
} }
private void StopSSE() private void StopSSE()
@@ -364,7 +379,7 @@ namespace BetterLyrics.WinUI3.Services
} }
} }
public void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message) public async void Receive(PropertyChangedMessage<ObservableCollection<AlbumArtSearchProviderInfo>> message)
{ {
if (message.Sender is SettingsPageViewModel) if (message.Sender is SettingsPageViewModel)
{ {
@@ -372,7 +387,7 @@ namespace BetterLyrics.WinUI3.Services
{ {
// Album art search providers info changed, re-fetch album art // Album art search providers info changed, re-fetch album art
_logger.LogInformation("Album art search providers info changed, refreshing album art."); _logger.LogInformation("Album art search providers info changed, refreshing album art.");
_ = _AlbumArtRefreshRunner.RunAsync(async tokne => await _albumArtRefreshRunner.RunAsync(async tokne =>
{ {
await UpdateAlbumArtRelated(tokne); await UpdateAlbumArtRelated(tokne);
}); });

View File

@@ -772,4 +772,7 @@ If you encounter any problems, please go to the Settings page, About tab, and vi
<data name="SettingsPageFAQ.Header" xml:space="preserve"> <data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>Frequently asked questions</value> <value>Frequently asked questions</value>
</data> </data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>Unable to connect to LX Music server, please go to Settings - Advanced options to check if the link is entered correctly</value>
</data>
</root> </root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve"> <data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>よくある質問</value> <value>よくある質問</value>
</data> </data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>LX Music Serverに接続できない場合は、設定に移動してください。リンクが正しく入力されているかどうかを確認するための高度なオプションに移動してください</value>
</data>
</root> </root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve"> <data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>자주 묻는 질문</value> <value>자주 묻는 질문</value>
</data> </data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>LX Music Server에 연결할 수 없습니다. 설정으로 이동하십시오 - 고급 옵션이 링크가 올바르게 입력되었는지 확인하십시오.</value>
</data>
</root> </root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve"> <data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>常见问题与解答</value> <value>常见问题与解答</value>
</data> </data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>无法连接到 LX 音乐服务器,请转到设置 - 高级选项以检查是否正确输入链接</value>
</data>
</root> </root>

View File

@@ -772,4 +772,7 @@
<data name="SettingsPageFAQ.Header" xml:space="preserve"> <data name="SettingsPageFAQ.Header" xml:space="preserve">
<value>常見問題與解答</value> <value>常見問題與解答</value>
</data> </data>
<data name="FailToStartLXMusicServer" xml:space="preserve">
<value>無法連接到 LX 音樂服務器,請轉到設置 - 高級選項以檢查是否正確輸入鏈接</value>
</data>
</root> </root>

View File

@@ -10,6 +10,7 @@ using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.Mvvm.Messaging.Messages; using CommunityToolkit.Mvvm.Messaging.Messages;
using Microsoft.UI.Xaml; using Microsoft.UI.Xaml;
using System.Numerics;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace BetterLyrics.WinUI3.ViewModels namespace BetterLyrics.WinUI3.ViewModels
@@ -36,6 +37,8 @@ namespace BetterLyrics.WinUI3.ViewModels
_playbackService = playbackService; _playbackService = playbackService;
_playbackService.SongInfoChanged += PlaybackService_SongInfoChanged; _playbackService.SongInfoChanged += PlaybackService_SongInfoChanged;
_playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged; _playbackService.IsPlayingChanged += PlaybackService_IsPlayingChanged;
IsSongPlaying = _playbackService.IsPlaying;
} }
//private void SystemVolumeHelper_VolumeChanged(int volume) //private void SystemVolumeHelper_VolumeChanged(int volume)
@@ -60,6 +63,9 @@ namespace BetterLyrics.WinUI3.ViewModels
//[ObservableProperty] //[ObservableProperty]
//public partial int Volume { get; set; } //public partial int Volume { get; set; }
[ObservableProperty]
public partial Vector3 BottomCenterCommandGridTranslation { get; set; } = new Vector3(0, 0, 0);
[ObservableProperty] [ObservableProperty]
public partial bool IsImmersiveMode { get; set; } public partial bool IsImmersiveMode { get; set; }

View File

@@ -443,7 +443,8 @@ namespace BetterLyrics.WinUI3.ViewModels
else else
{ {
float height = 0f; float height = 0f;
var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length); //var regions = textLayout.GetCharacterRegions(0, string.Join("", line.LyricsChars.Select(x => x.Text)).Length);
var regions = textLayout.GetCharacterRegions(0, line.OriginalText.Length);
if (regions.Length > 0) if (regions.Length > 0)
{ {
height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top; height = (float)regions[^1].LayoutBounds.Bottom - (float)regions[0].LayoutBounds.Top;

View File

@@ -283,11 +283,21 @@ namespace BetterLyrics.WinUI3.ViewModels
} }
else else
{ {
// 没有逐字时间轴,直接线性,模拟逐字高亮 // 没有逐字时间轴,均匀分配每个字的高亮时间
int textLength = line.OriginalText.Length;
if (textLength == 0) return;
float lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs); float lineProgress = (now - line.StartMs) / (lineEndMs - line.StartMs);
charStartIndex = (int)(lineProgress * line.OriginalText.Length); lineProgress = Math.Clamp(lineProgress, 0f, 1f);
charProgress = lineProgress - (int)(lineProgress);
// 计算当前高亮到第几个字
float charFloatIndex = lineProgress * textLength;
int charIndex = (int)charFloatIndex;
charStartIndex = Math.Clamp(charIndex, 0, textLength - 1);
charLength = 1; charLength = 1;
// 当前字的进度0~1
charProgress = charFloatIndex - charIndex;
} }
} }
@@ -345,6 +355,7 @@ namespace BetterLyrics.WinUI3.ViewModels
_songInfoOpacityTransition.StartTransition(1f); _songInfoOpacityTransition.StartTransition(1f);
_logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist); _logger.LogInformation("Song info changed: Title={Title}, Artist={Artist}, refreshing lyrics...", _songTitle, _songArtist);
Debug.WriteLine($"Song info changed: Title={_songTitle}, Artist={_songArtist}");
_ = _refreshLyricsRunner.RunAsync(async token => _ = _refreshLyricsRunner.RunAsync(async token =>
{ {
await RefreshLyricsAsync(token); await RefreshLyricsAsync(token);
@@ -433,9 +444,9 @@ namespace BetterLyrics.WinUI3.ViewModels
token.ThrowIfCancellationRequested(); token.ThrowIfCancellationRequested();
if (_showTranslationOnly) if (_showTranslationOnly)
{ {
// TODO _lyricsDataArr[^1] = _lyricsDataArr[0].CreateLyricsDataFrom(translated);
_lyricsDataArr[0].SetDisplayedTextAlongWith(translated); _lyricsDataArr[^1].SetDisplayedTextInOriginalText();
_langIndex = 0; _langIndex = _lyricsDataArr.Count - 1;
} }
else else
{ {

View File

@@ -18,7 +18,7 @@
xmlns:ui="using:CommunityToolkit.WinUI" xmlns:ui="using:CommunityToolkit.WinUI"
mc:Ignorable="d"> mc:Ignorable="d">
<Grid x:Name="RootGrid"> <Grid x:Name="RootGrid" SizeChanged="RootGrid_SizeChanged">
<!-- Lyrics area --> <!-- Lyrics area -->
<renderer:LyricsRenderer /> <renderer:LyricsRenderer />
@@ -140,14 +140,15 @@
<Grid <Grid
Padding="3" Padding="3"
HorizontalAlignment="Center" HorizontalAlignment="Center"
Style="{StaticResource CardGridStyle}"> Style="{StaticResource CardGridStyle}"
Translation="{x:Bind ViewModel.BottomCenterCommandGridTranslation, Mode=OneWay}">
<Grid.TranslationTransition>
<Vector3Transition />
</Grid.TranslationTransition>
<StackPanel <StackPanel
x:Name="BottomCenterCommandStackPanel" x:Name="BottomCenterCommandStackPanel"
Orientation="Horizontal" Orientation="Horizontal"
Spacing="3"> Spacing="3">
<StackPanel.OpacityTransition>
<ScalarTransition />
</StackPanel.OpacityTransition>
<Button <Button
Command="{x:Bind ViewModel.PreviousSongCommand}" Command="{x:Bind ViewModel.PreviousSongCommand}"
Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily}, Content="{ui:FontIcon FontFamily={StaticResource IconFontFamily},

View File

@@ -14,7 +14,7 @@ namespace BetterLyrics.WinUI3.Views
public sealed partial class LyricsPage : Page public sealed partial class LyricsPage : Page
{ {
private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>(); private readonly ISettingsService _settingsService = Ioc.Default.GetRequiredService<ISettingsService>();
public LyricsPage() public LyricsPage()
{ {
this.InitializeComponent(); this.InitializeComponent();
@@ -82,5 +82,17 @@ namespace BetterLyrics.WinUI3.Views
{ {
TranslationFlyout.ShowAt(BottomRightCommandStackPanel); TranslationFlyout.ShowAt(BottomRightCommandStackPanel);
} }
private void RootGrid_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (e.NewSize.Width < 500)
{
ViewModel.BottomCenterCommandGridTranslation = new System.Numerics.Vector3(0, -48, 0);
}
else
{
ViewModel.BottomCenterCommandGridTranslation = new System.Numerics.Vector3(0, 0, 0);
}
}
} }
} }