using CommunityToolkit.Mvvm.ComponentModel;
using HaloPixelToolBox.Client.Profiles.CrossVersionProfiles;
using HaloPixelToolBox.Client.Utilities;
using HaloPixelToolBox.Core.Models.Bar;
using HaloPixelToolBox.Core.Utilities;
using XFEExtension.NetCore.StringExtension;
using XFEExtension.NetCore.WinUIHelper.Implements;
using XFEExtension.NetCore.WinUIHelper.Interface.Services;
using XFEExtension.NetCore.WinUIHelper.Utilities;
namespace HaloPixelToolBox.Client.ViewModels;
public partial class CloudMusicLyricsToolPageViewModel : ServiceBaseViewModelBase<string>
{
[ObservableProperty] public partial bool DeviceReady { get; set; }
[ObservableProperty] public partial bool CloudMusicReady { get; set; }
[ObservableProperty] public partial bool EnableCloudMusicLyrics { get; set; } = CloudMusicLyricsProfile.EnableCloudMusicLyrics;
[ObservableProperty] public partial bool SwitchBackWhenPause { get; set; } = CloudMusicLyricsProfile.SwitchBackWhenPause;
[ObservableProperty] public partial bool UseInputedAddress { get; set; } = CloudMusicLyricsProfile.UseInputedAddress;
[ObservableProperty] public partial string InputedAddress { get; set; } = CloudMusicLyricsProfile.InputedAddress;
[ObservableProperty] public partial int SwitchBackTimeout { get; set; } = CloudMusicLyricsProfile.SwitchBackTimeout;
[ObservableProperty] public partial bool EnableScreenColorSync { get; set; } = CloudMusicLyricsProfile.EnableScreenColorSync;
[ObservableProperty] public partial bool EnableAmbientColorSync { get; set; } = CloudMusicLyricsProfile.EnableAmbientColorSync;
[ObservableProperty] public partial Core.Models.Lighting.AmbientLightEffect SyncAmbientLightEffect { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightEffect;
[ObservableProperty] public partial Core.Models.Lighting.AmbientLightBrightness SyncAmbientLightBrightness { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightBrightness;
[ObservableProperty] public partial int SyncAmbientLightSpeed { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightSpeed;
[ObservableProperty] public partial string CloudMusicVersion { get; set; } = string.Empty;
[ObservableProperty] public partial string SupportedVersion { get; set; } = "等待检测";
public HaloPixelDevice Device { get; set; } = new();
public CloudMusicLyricsReader Reader { get; set; }
public CloudMusicOnlineLyricsReader OnlineReader { get; } = new();
public ISettingService SettingService { get; } = ServiceManager.GetService<ISettingService>();
private string _lastResolverAttemptVersion = string.Empty;
private DateTime _lastResolverAttemptTime = DateTime.MinValue;
/// <summary>
/// Safely parse a hexadecimal address string to nint, returning 0 if parsing fails
/// </summary>
private static nint ParseHexAddress(string hexAddress)
{
if (string.IsNullOrWhiteSpace(hexAddress))
return 0;
try
{
return new nint(Convert.ToInt64(hexAddress, 16));
}
catch (Exception ex) when (ex is FormatException or OverflowException or ArgumentException)
{
Console.WriteLine($"[WARNING]无法解析内存地址 '{hexAddress}',使用默认值 0。错误:{ex.Message}");
return 0;
}
}
private bool _forceRefresh;
private bool _forcePauseLightRefresh;
private bool _forcePauseScreenRefresh;
private bool _forcePauseUIModelRefresh;
private bool _isClockUI;
public void OnNavigatedTo()
{
_forceRefresh = true;
}
public static (byte R, byte G, byte B) ParseHexColor(string hex)
{
try
{
if (hex.StartsWith("#"))
hex = hex.Substring(1);
if (hex.Length == 6)
{
byte r = Convert.ToByte(hex.Substring(0, 2), 16);
byte g = Convert.ToByte(hex.Substring(2, 2), 16);
byte b = Convert.ToByte(hex.Substring(4, 2), 16);
return (r, g, b);
}
}
catch { }
return (0xf0, 0xb4, 0xc8); // default fallback color
}
public void ForcePauseLightRefresh()
{
if (_isClockUI)
{
_forcePauseLightRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseLightRefresh invoked");
}
}
public void ForcePauseScreenRefresh()
{
if (_isClockUI)
{
_forcePauseScreenRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseScreenRefresh invoked");
}
}
public void ForcePauseUIModelRefresh()
{
if (_isClockUI)
{
_forcePauseUIModelRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseUIModelRefresh invoked");
}
}
partial void OnEnableCloudMusicLyricsChanged(bool value)
{
CloudMusicLyricsProfile.EnableCloudMusicLyrics = value;
if (value)
_forceRefresh = true;
}
partial void OnUseInputedAddressChanged(bool value)
{
CloudMusicLyricsProfile.UseInputedAddress = value;
Reader.UseInputedAddress = value;
}
partial void OnInputedAddressChanged(string value)
{
CloudMusicLyricsProfile.InputedAddress = value;
Reader.Address = ParseHexAddress(value);
}
partial void OnSwitchBackWhenPauseChanged(bool value) => CloudMusicLyricsProfile.SwitchBackWhenPause = value;
partial void OnSwitchBackTimeoutChanged(int value) => CloudMusicLyricsProfile.SwitchBackTimeout = value;
partial void OnEnableScreenColorSyncChanged(bool value)
{
CloudMusicLyricsProfile.EnableScreenColorSync = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnEnableAmbientColorSyncChanged(bool value)
{
CloudMusicLyricsProfile.EnableAmbientColorSync = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightEffectChanged(Core.Models.Lighting.AmbientLightEffect value)
{
CloudMusicLyricsProfile.SyncAmbientLightEffect = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightBrightnessChanged(Core.Models.Lighting.AmbientLightBrightness value)
{
CloudMusicLyricsProfile.SyncAmbientLightBrightness = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightSpeedChanged(int value)
{
CloudMusicLyricsProfile.SyncAmbientLightSpeed = value;
if (!_isClockUI)
_forceRefresh = true;
}
public bool IsPlaying => OnlineReader.IsPlaying;
public string CurrentTitle => OnlineReader.CurrentTitle;
public string CurrentArtist => OnlineReader.CurrentArtist;
public (byte R, byte G, byte B)? CurrentAlbumColor => OnlineReader.CurrentAlbumColor;
public CloudMusicLyricsToolPageViewModel()
{
Console.WriteLine("初始化网易云歌词读取器");
AddressResolverProvider.LoadCachedResolvers();
SupportedVersion = CloudMusicLyricsReader.VersionResolverDictionary.Keys
.OrderByDescending(static version => Version.TryParse(version, out var parsed) ? parsed : new Version())
.FirstOrDefault() ?? "由服务器动态获取";
Reader = new CloudMusicLyricsReader
{
UseInputedAddress = UseInputedAddress,
Address = ParseHexAddress(InputedAddress)
};
Reader.AutomaticResolverDiscovered += AddressResolverProvider.SaveAutomaticResolver;
Console.WriteLine("准备启动网易云后台线程");
Task.Run(async () =>
{
try
{
Console.WriteLine("正在搜索花再设备...");
while (!DeviceReady)
{
var ready = Device.Initialize();
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
{
DeviceReady = ready;
});
await Task.Delay(500);
}
Console.WriteLine("花再设备已连接");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]搜索花再设备时发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
});
Task.Run(async () =>
{
try
{
Console.WriteLine("正在搜索云音乐...");
while (!CloudMusicReady)
{
OnlineReader.Initialize();
var onlineReady = OnlineReader.IsSessionAvailable;
var memoryReady = Reader.Initialize();
if (!memoryReady && !UseInputedAddress)
memoryReady = await TryUpdateAddressResolverAsync();
var ready = onlineReady || memoryReady;
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
{
CloudMusicReady = ready;
CloudMusicVersion = Reader.VersionInfo is not null ? $"{Reader.VersionInfo.FileVersion}" : "未检测到云音乐";
SupportedVersion = onlineReady
? (memoryReady ? "SMTC 在线歌词(内存兜底可用)" : "SMTC 在线歌词(无需解析地址)")
: (memoryReady ? $"{Reader.ResolverDescription}({Reader.Version.ToString(3)})" : SupportedVersion);
});
await Task.Delay(500);
}
Console.WriteLine("云音乐已准备就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]搜索云音乐时发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
});
Task.Run(async () =>
{
Console.WriteLine("启动云音乐地址重解析线程");
while (true)
{
try
{
Reader.ReresolveAddress();
await Task.Delay(500);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]云音乐地址重解析线程发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
});
Task.Run(async () =>
{
Console.WriteLine("启动网易云歌词主线程");
Console.WriteLine("等待花再设备...");
while (!DeviceReady)
await Task.Delay(500);
if (DeviceReady && EnableCloudMusicLyrics)
{
Console.WriteLine("花再设备已就绪,显示启动信息");
Device.SetTextLayout(HaloPixelTextLayout.Center);
Device.ShowText("花再工具箱已启动~");
await Task.Delay(3000);
Console.WriteLine("OK");
}
while (true)
{
_isClockUI = false;
var time = 0;
try
{
if (DeviceReady && CloudMusicReady && EnableCloudMusicLyrics)
{
Console.WriteLine("[DEBUG]设备均在线,准备进入主循环");
var lastRead = string.Empty;
var lastTrackTitle = string.Empty;
var lastTrackArtist = string.Empty;
var scrolled = false;
var wasPlaying = false;
(byte R, byte G, byte B) lastScreenColor = (0, 0, 0);
(byte R, byte G, byte B) lastAmbientColor = (0, 0, 0);
while (true)
{
try
{
if (!DeviceReady || !CloudMusicReady || !EnableCloudMusicLyrics)
break;
bool isPlaying = IsPlaying;
bool trackChanged = lastTrackTitle != CurrentTitle || lastTrackArtist != CurrentArtist;
var lyricsRead = OnlineReader.TryReadLyrics(out var lyrics);
if (!OnlineReader.HasSynchronizedLyrics && Reader.TryReadLyrics(out var memoryLyrics))
{
lyrics = memoryLyrics;
lyricsRead = true;
}
bool lyricsChanged = lyricsRead && lastRead != lyrics;
if (isPlaying && (_isClockUI || trackChanged))
{
if (!wasPlaying && isPlaying)
{
_isClockUI = false;
_forceRefresh = true;
time = 0;
}
else if (lyricsChanged || trackChanged)
{
_isClockUI = false;
_forceRefresh = true;
time = 0;
}
}
wasPlaying = isPlaying;
lastTrackTitle = CurrentTitle ?? string.Empty;
lastTrackArtist = CurrentArtist ?? string.Empty;
var albumColor = CurrentAlbumColor;
// 1. Determine screen text color
(byte R, byte G, byte B) screenColor = ((byte)0xf0, (byte)0xb4, (byte)0xc8);
if (EnableScreenColorSync && albumColor.HasValue)
{
screenColor = albumColor.Value;
}
// 2. Determine ambient backlight color
(byte R, byte G, byte B) ambientColor = ((byte)0xf0, (byte)0xb4, (byte)0xc8);
if (EnableAmbientColorSync && albumColor.HasValue)
{
ambientColor = albumColor.Value;
}
bool colorChanged = lastScreenColor != screenColor || lastAmbientColor != ambientColor;
if (!_isClockUI && (lyricsChanged || colorChanged || _forceRefresh))
{
_forceRefresh = false;
lastScreenColor = screenColor;
lastAmbientColor = ambientColor;
HidPacketBuilder.CurrentColor = screenColor;
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = Core.Models.Lighting.AmbientLightEffect.Static,
Color = new Core.Models.Display.HaloPixelColor(ambientColor.R, ambientColor.G, ambientColor.B),
Brightness = Core.Models.Lighting.AmbientLightBrightness.High,
Speed = 5
});
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] SetAmbientLight failed: {ex.Message}");
}
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(screenColor.R, screenColor.G, screenColor.B));
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] SetPixelScreenColor failed: {ex.Message}");
}
if (lyricsChanged)
{
Console.WriteLine($"已读取到歌词:{lyrics}");
lastRead = lyrics;
if (_isClockUI)
{
_isClockUI = false;
_forceRefresh = true;
}
time = 0;
if (scrolled)
{
Device.ShowText(string.Empty);
await Task.Delay(100);
scrolled = false;
}
}
Device.SetTextLayout(CloudMusicLyricsProfile.DefaultHaloPixelTextLayout);
Device.ShowText(lastRead);
if (lyricsChanged && lastRead.DisplayLength() > 30)
{
scrolled = true;
await Task.Delay(500);
Device.SetTextLayout(HaloPixelTextLayout.ScrollRightToLeft);
}
}
await Task.Delay(50);
time = isPlaying ? 0 : time + 50;
if (SwitchBackWhenPause && !isPlaying && !_isClockUI && time >= CloudMusicLyricsProfile.SwitchBackTimeout * 1000)
{
_isClockUI = true;
(byte R, byte G, byte B) finalScreenColor = ParseHexColor(CloudMusicLyricsProfile.DefaultScreenColor);
(byte R, byte G, byte B) finalAmbientColor = ParseHexColor(CloudMusicLyricsProfile.DefaultAmbientLightColor);
HidPacketBuilder.CurrentColor = finalScreenColor;
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = CloudMusicLyricsProfile.DefaultAmbientLightEffect,
Color = new Core.Models.Display.HaloPixelColor(finalAmbientColor.R, finalAmbientColor.G, finalAmbientColor.B),
Brightness = CloudMusicLyricsProfile.DefaultAmbientLightBrightness,
Speed = (byte)CloudMusicLyricsProfile.DefaultAmbientLightSpeed
});
}
catch {}
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(finalScreenColor.R, finalScreenColor.G, finalScreenColor.B));
}
catch {}
Device.SetUIModel(CloudMusicLyricsProfile.DefaultHaloPixelUIModel);
Console.WriteLine("已切换至时钟界面");
}
if (_isClockUI && _forcePauseLightRefresh)
{
_forcePauseLightRefresh = false;
(byte R, byte G, byte B) finalAmbientColor = ParseHexColor(CloudMusicLyricsProfile.DefaultAmbientLightColor);
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = CloudMusicLyricsProfile.DefaultAmbientLightEffect,
Color = new Core.Models.Display.HaloPixelColor(finalAmbientColor.R, finalAmbientColor.G, finalAmbientColor.B),
Brightness = CloudMusicLyricsProfile.DefaultAmbientLightBrightness,
Speed = (byte)CloudMusicLyricsProfile.DefaultAmbientLightSpeed
});
}
catch {}
}
if (_isClockUI && _forcePauseScreenRefresh)
{
_forcePauseScreenRefresh = false;
(byte R, byte G, byte B) finalScreenColor = ParseHexColor(CloudMusicLyricsProfile.DefaultScreenColor);
HidPacketBuilder.CurrentColor = finalScreenColor;
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(finalScreenColor.R, finalScreenColor.G, finalScreenColor.B));
}
catch {}
}
if (_isClockUI && _forcePauseUIModelRefresh)
{
_forcePauseUIModelRefresh = false;
try
{
Device.SetUIModel(CloudMusicLyricsProfile.DefaultHaloPixelUIModel);
}
catch {}
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]网易云歌词主循环发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
Console.WriteLine($"[DEBUG]主循环已退出");
}
Console.WriteLine($"[DEBUG]状态\t音响:{DeviceReady} 软件:{CloudMusicReady} 启用状态:{EnableCloudMusicLyrics}");
await Task.Delay(500);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]网易云歌词主线程发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
});
Console.WriteLine("网易云后台线程启动完成");
}
private async Task<bool> TryUpdateAddressResolverAsync()
{
if (Reader.Version.Major == 0)
return false;
var version = Reader.Version.ToString(3);
if (string.Equals(version, _lastResolverAttemptVersion, StringComparison.OrdinalIgnoreCase) &&
DateTime.Now - _lastResolverAttemptTime < TimeSpan.FromMinutes(1))
return Reader.ReresolveAddress();
_lastResolverAttemptVersion = version;
_lastResolverAttemptTime = DateTime.Now;
var resolver = await AddressResolverProvider.GetAsync(version);
if (resolver is null)
return false;
CloudMusicLyricsReader.SetAddressResolver(resolver);
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() => SupportedVersion = resolver.Version);
return Reader.ReresolveAddress();
}
}
using CommunityToolkit.Mvvm.ComponentModel;
using HaloPixelToolBox.Client.Profiles.CrossVersionProfiles;
using HaloPixelToolBox.Client.Utilities;
using HaloPixelToolBox.Core.Models.Bar;
using HaloPixelToolBox.Core.Utilities;
using XFEExtension.NetCore.StringExtension;
using XFEExtension.NetCore.WinUIHelper.Implements;
using XFEExtension.NetCore.WinUIHelper.Interface.Services;
using XFEExtension.NetCore.WinUIHelper.Utilities;
namespace HaloPixelToolBox.Client.ViewModels;
public partial class CloudMusicLyricsToolPageViewModel : ServiceBaseViewModelBase<string>
{
[ObservableProperty] public partial bool DeviceReady { get; set; }
[ObservableProperty] public partial bool CloudMusicReady { get; set; }
[ObservableProperty] public partial bool EnableCloudMusicLyrics { get; set; } = CloudMusicLyricsProfile.EnableCloudMusicLyrics;
[ObservableProperty] public partial bool SwitchBackWhenPause { get; set; } = CloudMusicLyricsProfile.SwitchBackWhenPause;
[ObservableProperty] public partial bool UseInputedAddress { get; set; } = CloudMusicLyricsProfile.UseInputedAddress;
[ObservableProperty] public partial string InputedAddress { get; set; } = CloudMusicLyricsProfile.InputedAddress;
[ObservableProperty] public partial int SwitchBackTimeout { get; set; } = CloudMusicLyricsProfile.SwitchBackTimeout;
[ObservableProperty] public partial bool EnableScreenColorSync { get; set; } = CloudMusicLyricsProfile.EnableScreenColorSync;
[ObservableProperty] public partial bool EnableAmbientColorSync { get; set; } = CloudMusicLyricsProfile.EnableAmbientColorSync;
[ObservableProperty] public partial Core.Models.Lighting.AmbientLightEffect SyncAmbientLightEffect { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightEffect;
[ObservableProperty] public partial Core.Models.Lighting.AmbientLightBrightness SyncAmbientLightBrightness { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightBrightness;
[ObservableProperty] public partial int SyncAmbientLightSpeed { get; set; } = CloudMusicLyricsProfile.SyncAmbientLightSpeed;
[ObservableProperty] public partial string CloudMusicVersion { get; set; } = string.Empty;
[ObservableProperty] public partial string SupportedVersion { get; set; } = "等待检测";
public HaloPixelDevice Device { get; set; } = new();
public CloudMusicLyricsReader Reader { get; set; }
public CloudMusicOnlineLyricsReader OnlineReader { get; } = new();
public ISettingService SettingService { get; } = ServiceManager.GetService<ISettingService>();
private string _lastResolverAttemptVersion = string.Empty;
private DateTime _lastResolverAttemptTime = DateTime.MinValue;
/// <summary>
/// Safely parse a hexadecimal address string to nint, returning 0 if parsing fails
/// </summary>
private static nint ParseHexAddress(string hexAddress)
{
if (string.IsNullOrWhiteSpace(hexAddress))
return 0;
try
{
return new nint(Convert.ToInt64(hexAddress, 16));
}
catch (Exception ex) when (ex is FormatException or OverflowException or ArgumentException)
{
Console.WriteLine($"[WARNING]无法解析内存地址 '{hexAddress}',使用默认值 0。错误:{ex.Message}");
return 0;
}
}
private bool _forceRefresh;
private bool _forcePauseLightRefresh;
private bool _forcePauseScreenRefresh;
private bool _forcePauseUIModelRefresh;
private bool _isClockUI;
public void OnNavigatedTo()
{
_forceRefresh = true;
}
public static (byte R, byte G, byte B) ParseHexColor(string hex)
{
try
{
if (hex.StartsWith("#"))
hex = hex.Substring(1);
if (hex.Length == 6)
{
byte r = Convert.ToByte(hex.Substring(0, 2), 16);
byte g = Convert.ToByte(hex.Substring(2, 2), 16);
byte b = Convert.ToByte(hex.Substring(4, 2), 16);
return (r, g, b);
}
}
catch { }
return (0xf0, 0xb4, 0xc8); // default fallback color
}
public void ForcePauseLightRefresh()
{
if (_isClockUI)
{
_forcePauseLightRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseLightRefresh invoked");
}
}
public void ForcePauseScreenRefresh()
{
if (_isClockUI)
{
_forcePauseScreenRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseScreenRefresh invoked");
}
}
public void ForcePauseUIModelRefresh()
{
if (_isClockUI)
{
_forcePauseUIModelRefresh = true;
Console.WriteLine("[DEBUG] NetEase ForcePauseUIModelRefresh invoked");
}
}
partial void OnEnableCloudMusicLyricsChanged(bool value)
{
CloudMusicLyricsProfile.EnableCloudMusicLyrics = value;
if (value)
_forceRefresh = true;
}
partial void OnUseInputedAddressChanged(bool value)
{
CloudMusicLyricsProfile.UseInputedAddress = value;
Reader.UseInputedAddress = value;
}
partial void OnInputedAddressChanged(string value)
{
CloudMusicLyricsProfile.InputedAddress = value;
Reader.Address = ParseHexAddress(value);
}
partial void OnSwitchBackWhenPauseChanged(bool value) => CloudMusicLyricsProfile.SwitchBackWhenPause = value;
partial void OnSwitchBackTimeoutChanged(int value) => CloudMusicLyricsProfile.SwitchBackTimeout = value;
partial void OnEnableScreenColorSyncChanged(bool value)
{
CloudMusicLyricsProfile.EnableScreenColorSync = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnEnableAmbientColorSyncChanged(bool value)
{
CloudMusicLyricsProfile.EnableAmbientColorSync = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightEffectChanged(Core.Models.Lighting.AmbientLightEffect value)
{
CloudMusicLyricsProfile.SyncAmbientLightEffect = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightBrightnessChanged(Core.Models.Lighting.AmbientLightBrightness value)
{
CloudMusicLyricsProfile.SyncAmbientLightBrightness = value;
if (!_isClockUI)
_forceRefresh = true;
}
partial void OnSyncAmbientLightSpeedChanged(int value)
{
CloudMusicLyricsProfile.SyncAmbientLightSpeed = value;
if (!_isClockUI)
_forceRefresh = true;
}
public bool IsPlaying => OnlineReader.IsPlaying;
public string CurrentTitle => OnlineReader.CurrentTitle;
public string CurrentArtist => OnlineReader.CurrentArtist;
public (byte R, byte G, byte B)? CurrentAlbumColor => OnlineReader.CurrentAlbumColor;
public CloudMusicLyricsToolPageViewModel()
{
Console.WriteLine("初始化网易云歌词读取器");
AddressResolverProvider.LoadCachedResolvers();
SupportedVersion = CloudMusicLyricsReader.VersionResolverDictionary.Keys
.OrderByDescending(static version => Version.TryParse(version, out var parsed) ? parsed : new Version())
.FirstOrDefault() ?? "由服务器动态获取";
Reader = new CloudMusicLyricsReader
{
UseInputedAddress = UseInputedAddress,
Address = ParseHexAddress(InputedAddress)
};
Reader.AutomaticResolverDiscovered += AddressResolverProvider.SaveAutomaticResolver;
Console.WriteLine("准备启动网易云后台线程");
Task.Run(async () =>
{
try
{
Console.WriteLine("正在搜索花再设备...");
while (!DeviceReady)
{
var ready = Device.Initialize();
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
{
DeviceReady = ready;
});
await Task.Delay(500);
}
Console.WriteLine("花再设备已连接");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]搜索花再设备时发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
});
Task.Run(async () =>
{
try
{
Console.WriteLine("正在搜索云音乐...");
while (!CloudMusicReady)
{
OnlineReader.Initialize();
var onlineReady = OnlineReader.IsSessionAvailable;
var memoryReady = Reader.Initialize();
if (!memoryReady && !UseInputedAddress)
memoryReady = await TryUpdateAddressResolverAsync();
var ready = onlineReady || memoryReady;
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() =>
{
CloudMusicReady = ready;
CloudMusicVersion = Reader.VersionInfo is not null ? $"{Reader.VersionInfo.FileVersion}" : "未检测到云音乐";
SupportedVersion = onlineReady
? (memoryReady ? "SMTC 在线歌词(内存兜底可用)" : "SMTC 在线歌词(无需解析地址)")
: (memoryReady ? $"{Reader.ResolverDescription}({Reader.Version.ToString(3)})" : SupportedVersion);
});
await Task.Delay(500);
}
Console.WriteLine("云音乐已准备就绪");
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]搜索云音乐时发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
});
Task.Run(async () =>
{
Console.WriteLine("启动云音乐地址重解析线程");
while (true)
{
try
{
Reader.ReresolveAddress();
await Task.Delay(500);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]云音乐地址重解析线程发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
});
Task.Run(async () =>
{
Console.WriteLine("启动网易云歌词主线程");
Console.WriteLine("等待花再设备...");
while (!DeviceReady)
await Task.Delay(500);
if (DeviceReady && EnableCloudMusicLyrics)
{
Console.WriteLine("花再设备已就绪,显示启动信息");
Device.SetTextLayout(HaloPixelTextLayout.Center);
Device.ShowText("花再工具箱已启动~");
await Task.Delay(3000);
Console.WriteLine("OK");
}
while (true)
{
_isClockUI = false;
var time = 0;
try
{
if (DeviceReady && CloudMusicReady && EnableCloudMusicLyrics)
{
Console.WriteLine("[DEBUG]设备均在线,准备进入主循环");
var lastRead = string.Empty;
var lastTrackTitle = string.Empty;
var lastTrackArtist = string.Empty;
var scrolled = false;
var wasPlaying = false;
(byte R, byte G, byte B) lastScreenColor = (0, 0, 0);
(byte R, byte G, byte B) lastAmbientColor = (0, 0, 0);
while (true)
{
try
{
if (!DeviceReady || !CloudMusicReady || !EnableCloudMusicLyrics)
break;
bool isPlaying = IsPlaying;
bool trackChanged = lastTrackTitle != CurrentTitle || lastTrackArtist != CurrentArtist;
var lyricsRead = OnlineReader.TryReadLyrics(out var lyrics);
if (!OnlineReader.HasSynchronizedLyrics && Reader.TryReadLyrics(out var memoryLyrics))
{
lyrics = memoryLyrics;
lyricsRead = true;
}
bool lyricsChanged = lyricsRead && lastRead != lyrics;
if (isPlaying && (_isClockUI || trackChanged))
{
if (!wasPlaying && isPlaying)
{
_isClockUI = false;
_forceRefresh = true;
time = 0;
}
else if (lyricsChanged || trackChanged)
{
_isClockUI = false;
_forceRefresh = true;
time = 0;
}
}
wasPlaying = isPlaying;
lastTrackTitle = CurrentTitle ?? string.Empty;
lastTrackArtist = CurrentArtist ?? string.Empty;
var albumColor = CurrentAlbumColor;
// 1. Determine screen text color
(byte R, byte G, byte B) screenColor = ((byte)0xf0, (byte)0xb4, (byte)0xc8);
if (EnableScreenColorSync && albumColor.HasValue)
{
screenColor = albumColor.Value;
}
// 2. Determine ambient backlight color
(byte R, byte G, byte B) ambientColor = ((byte)0xf0, (byte)0xb4, (byte)0xc8);
if (EnableAmbientColorSync && albumColor.HasValue)
{
ambientColor = albumColor.Value;
}
bool colorChanged = lastScreenColor != screenColor || lastAmbientColor != ambientColor;
if (!_isClockUI && (lyricsChanged || colorChanged || _forceRefresh))
{
_forceRefresh = false;
lastScreenColor = screenColor;
lastAmbientColor = ambientColor;
HidPacketBuilder.CurrentColor = screenColor;
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = Core.Models.Lighting.AmbientLightEffect.Static,
Color = new Core.Models.Display.HaloPixelColor(ambientColor.R, ambientColor.G, ambientColor.B),
Brightness = Core.Models.Lighting.AmbientLightBrightness.High,
Speed = 5
});
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] SetAmbientLight failed: {ex.Message}");
}
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(screenColor.R, screenColor.G, screenColor.B));
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR] SetPixelScreenColor failed: {ex.Message}");
}
if (lyricsChanged)
{
Console.WriteLine($"已读取到歌词:{lyrics}");
lastRead = lyrics;
if (_isClockUI)
{
_isClockUI = false;
_forceRefresh = true;
}
time = 0;
if (scrolled)
{
Device.ShowText(string.Empty);
await Task.Delay(100);
scrolled = false;
}
}
Device.SetTextLayout(CloudMusicLyricsProfile.DefaultHaloPixelTextLayout);
Device.ShowText(lastRead);
if (lyricsChanged && lastRead.DisplayLength() > 30)
{
scrolled = true;
await Task.Delay(500);
Device.SetTextLayout(HaloPixelTextLayout.ScrollRightToLeft);
}
}
await Task.Delay(50);
time = isPlaying ? 0 : time + 50;
if (SwitchBackWhenPause && !isPlaying && !_isClockUI && time >= CloudMusicLyricsProfile.SwitchBackTimeout * 1000)
{
_isClockUI = true;
(byte R, byte G, byte B) finalScreenColor = ParseHexColor(CloudMusicLyricsProfile.DefaultScreenColor);
(byte R, byte G, byte B) finalAmbientColor = ParseHexColor(CloudMusicLyricsProfile.DefaultAmbientLightColor);
HidPacketBuilder.CurrentColor = finalScreenColor;
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = CloudMusicLyricsProfile.DefaultAmbientLightEffect,
Color = new Core.Models.Display.HaloPixelColor(finalAmbientColor.R, finalAmbientColor.G, finalAmbientColor.B),
Brightness = CloudMusicLyricsProfile.DefaultAmbientLightBrightness,
Speed = (byte)CloudMusicLyricsProfile.DefaultAmbientLightSpeed
});
}
catch {}
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(finalScreenColor.R, finalScreenColor.G, finalScreenColor.B));
}
catch {}
Device.SetUIModel(CloudMusicLyricsProfile.DefaultHaloPixelUIModel);
Console.WriteLine("已切换至时钟界面");
}
if (_isClockUI && _forcePauseLightRefresh)
{
_forcePauseLightRefresh = false;
(byte R, byte G, byte B) finalAmbientColor = ParseHexColor(CloudMusicLyricsProfile.DefaultAmbientLightColor);
try
{
Device.SetAmbientLight(new Core.Models.Lighting.AmbientLightOptions
{
Effect = CloudMusicLyricsProfile.DefaultAmbientLightEffect,
Color = new Core.Models.Display.HaloPixelColor(finalAmbientColor.R, finalAmbientColor.G, finalAmbientColor.B),
Brightness = CloudMusicLyricsProfile.DefaultAmbientLightBrightness,
Speed = (byte)CloudMusicLyricsProfile.DefaultAmbientLightSpeed
});
}
catch {}
}
if (_isClockUI && _forcePauseScreenRefresh)
{
_forcePauseScreenRefresh = false;
(byte R, byte G, byte B) finalScreenColor = ParseHexColor(CloudMusicLyricsProfile.DefaultScreenColor);
HidPacketBuilder.CurrentColor = finalScreenColor;
try
{
Device.SetPixelScreenColor(new Core.Models.Display.HaloPixelColor(finalScreenColor.R, finalScreenColor.G, finalScreenColor.B));
}
catch {}
}
if (_isClockUI && _forcePauseUIModelRefresh)
{
_forcePauseUIModelRefresh = false;
try
{
Device.SetUIModel(CloudMusicLyricsProfile.DefaultHaloPixelUIModel);
}
catch {}
}
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]网易云歌词主循环发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
Console.WriteLine($"[DEBUG]主循环已退出");
}
Console.WriteLine($"[DEBUG]状态\t音响:{DeviceReady} 软件:{CloudMusicReady} 启用状态:{EnableCloudMusicLyrics}");
await Task.Delay(500);
}
catch (Exception ex)
{
Console.WriteLine($"[ERROR]网易云歌词主线程发生错误:{ex.Message}");
Console.WriteLine($"[TRACE]{ex.StackTrace}");
}
}
});
Console.WriteLine("网易云后台线程启动完成");
}
private async Task<bool> TryUpdateAddressResolverAsync()
{
if (Reader.Version.Major == 0)
return false;
var version = Reader.Version.ToString(3);
if (string.Equals(version, _lastResolverAttemptVersion, StringComparison.OrdinalIgnoreCase) &&
DateTime.Now - _lastResolverAttemptTime < TimeSpan.FromMinutes(1))
return Reader.ReresolveAddress();
_lastResolverAttemptVersion = version;
_lastResolverAttemptTime = DateTime.Now;
var resolver = await AddressResolverProvider.GetAsync(version);
if (resolver is null)
return false;
CloudMusicLyricsReader.SetAddressResolver(resolver);
AutoNavigationParameterService.CurrentPage?.DispatcherQueue.TryEnqueue(() => SupportedVersion = resolver.Version);
return Reader.ReresolveAddress();
}
}