diff --git a/FantasyPlayer.Mock/FantasyPlayer.Mock.csproj b/FantasyPlayer.Mock/FantasyPlayer.Mock.csproj index 084ce0b..261005d 100644 --- a/FantasyPlayer.Mock/FantasyPlayer.Mock.csproj +++ b/FantasyPlayer.Mock/FantasyPlayer.Mock.csproj @@ -85,7 +85,7 @@ Always - + Always @@ -94,7 +94,7 @@ Always - + Always @@ -103,7 +103,7 @@ Always - + Always diff --git a/FantasyPlayer.Mock/MockPlugin.cs b/FantasyPlayer.Mock/MockPlugin.cs index 9b834ad..6a6cdea 100644 --- a/FantasyPlayer.Mock/MockPlugin.cs +++ b/FantasyPlayer.Mock/MockPlugin.cs @@ -1,5 +1,3 @@ -using Dalamud.Interface.Windowing; - namespace FantasyPlayer.Mock; using Autofac; @@ -7,7 +5,6 @@ namespace FantasyPlayer.Mock; using DalaMock.Core.Windows; using DalaMock.Shared.Interfaces; using Dalamud.Plugin; -using Dalamud.Plugin.Services; public class MockPlugin : Plugin { diff --git a/FantasyPlayer.Spotify/FantasyPlayer.Spotify.csproj b/FantasyPlayer.Spotify/FantasyPlayer.Spotify.csproj index 13e743b..fd305bd 100644 --- a/FantasyPlayer.Spotify/FantasyPlayer.Spotify.csproj +++ b/FantasyPlayer.Spotify/FantasyPlayer.Spotify.csproj @@ -8,13 +8,20 @@ Copyright © 2020 false bin\$(Configuration)\ + AnyCPU;x64 full + + full + pdbonly + + pdbonly + diff --git a/FantasyPlayer.sln b/FantasyPlayer.sln index 556f573..6a0fd00 100644 --- a/FantasyPlayer.sln +++ b/FantasyPlayer.sln @@ -1,5 +1,8 @@  Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 18 +VisualStudioVersion = 18.5.11619.145 insiders +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FantasyPlayer.Spotify", "FantasyPlayer.Spotify\FantasyPlayer.Spotify.csproj", "{64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FantasyPlayer", "FantasyPlayer\FantasyPlayer.csproj", "{7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}" @@ -11,24 +14,45 @@ EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU + Debug|x64 = Debug|x64 Release|Any CPU = Release|Any CPU + Release|x64 = Release|x64 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Debug|x64.ActiveCfg = Debug|x64 + {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Debug|x64.Build.0 = Debug|x64 {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Release|Any CPU.ActiveCfg = Release|Any CPU {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Release|Any CPU.Build.0 = Release|Any CPU + {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Release|x64.ActiveCfg = Release|x64 + {64E9DE35-E1B3-467B-8AD5-E36BAB2A3F27}.Release|x64.Build.0 = Release|x64 {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Debug|x64.ActiveCfg = Debug|x64 + {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Debug|x64.Build.0 = Debug|x64 {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Release|Any CPU.ActiveCfg = Release|Any CPU {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Release|Any CPU.Build.0 = Release|Any CPU + {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Release|x64.ActiveCfg = Release|x64 + {7C8F3B5E-12F3-43F5-9080-B58471ABB7A1}.Release|x64.Build.0 = Release|x64 {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Debug|x64.ActiveCfg = Debug|x64 + {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Debug|x64.Build.0 = Debug|x64 {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Release|Any CPU.ActiveCfg = Release|Any CPU {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Release|Any CPU.Build.0 = Release|Any CPU + {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Release|x64.ActiveCfg = Release|x64 + {DFFAFA7A-9860-473E-BBD7-D39ACE6ED7EB}.Release|x64.Build.0 = Release|x64 {668953BC-B270-43F7-952B-706ABFDD6DEC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {668953BC-B270-43F7-952B-706ABFDD6DEC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {668953BC-B270-43F7-952B-706ABFDD6DEC}.Debug|x64.ActiveCfg = Debug|x64 + {668953BC-B270-43F7-952B-706ABFDD6DEC}.Debug|x64.Build.0 = Debug|x64 {668953BC-B270-43F7-952B-706ABFDD6DEC}.Release|Any CPU.ActiveCfg = Release|Any CPU {668953BC-B270-43F7-952B-706ABFDD6DEC}.Release|Any CPU.Build.0 = Release|Any CPU + {668953BC-B270-43F7-952B-706ABFDD6DEC}.Release|x64.ActiveCfg = Release|x64 + {668953BC-B270-43F7-952B-706ABFDD6DEC}.Release|x64.Build.0 = Release|x64 + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE EndGlobalSection EndGlobal diff --git a/FantasyPlayer/Config/Configuration.cs b/FantasyPlayer/Config/Configuration.cs index f981780..7aebd84 100644 --- a/FantasyPlayer/Config/Configuration.cs +++ b/FantasyPlayer/Config/Configuration.cs @@ -14,7 +14,7 @@ public class Configuration : IPluginConfiguration, INotifyPropertyChanged private bool isDirty; public bool IsDirty { - get => isDirty || PlayerSettings.IsDirty || SpotifySettings.IsDirty || AutoPlaySettings.IsDirty; + get => isDirty || PlayerSettings.IsDirty || SpotifySettings.IsDirty || AutoPlaySettings.IsDirty || LyricsSettings.IsDirty; set => SetField(ref isDirty, value, false); } @@ -24,6 +24,7 @@ public void MarkClean() PlayerSettings.MarkClean(); SpotifySettings.MarkClean(); AutoPlaySettings.MarkClean(); + LyricsSettings.MarkClean(); } private bool displayChatMessages; private bool configShown; @@ -32,6 +33,7 @@ public void MarkClean() public PlayerSettings PlayerSettings { get; set; } = new PlayerSettings(); public SpotifySettings SpotifySettings { get; set; } = new SpotifySettings(); public AutoPlaySettings AutoPlaySettings { get; set; } = new AutoPlaySettings(); + public LyricsSettings LyricsSettings { get; set; } = new LyricsSettings(); public bool DisplayChatMessages { diff --git a/FantasyPlayer/Config/LyricsSettings.cs b/FantasyPlayer/Config/LyricsSettings.cs new file mode 100644 index 0000000..6de2d52 --- /dev/null +++ b/FantasyPlayer/Config/LyricsSettings.cs @@ -0,0 +1,77 @@ +using Dalamud.Game.Text; + +namespace FantasyPlayer.Config +{ + using System.Collections.Generic; + using System.ComponentModel; + using System.Runtime.CompilerServices; + + public enum LyricDisplayMode + { + Chat = 0, + FlyText = 1, + } + + public class LyricsSettings : INotifyPropertyChanged + { + private bool isDirty; + public bool IsDirty + { + get => isDirty; + private set => SetField(ref isDirty, value, false); + } + + public void MarkClean() + { + IsDirty = false; + } + + private bool enableLyrics; + public bool EnableLyrics + { + get => enableLyrics; + set => SetField(ref enableLyrics, value); + } + + private LyricDisplayMode displayMode = LyricDisplayMode.Chat; + public LyricDisplayMode DisplayMode + { + get => displayMode; + set => SetField(ref displayMode, value); + } + + private XivChatType chatType = XivChatType.Echo; + public XivChatType ChatType + { + get => chatType; + set => SetField(ref chatType, value); + } + + private uint flyTextColor = 0xFF60D71E; + public uint FlyTextColor + { + get => flyTextColor; + set => SetField(ref flyTextColor, value); + } + + public event PropertyChangedEventHandler? PropertyChanged; + + protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) + { + PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); + } + + protected bool SetField(ref T field, T value, bool markDirty = true, [CallerMemberName] string? propertyName = null) + { + if (EqualityComparer.Default.Equals(field, value)) return false; + field = value; + OnPropertyChanged(propertyName); + if (markDirty) + { + IsDirty = true; + } + + return true; + } + } +} diff --git a/FantasyPlayer/Interface/Window/SettingsWindow.cs b/FantasyPlayer/Interface/Window/SettingsWindow.cs index 9200b43..29bf2d7 100644 --- a/FantasyPlayer/Interface/Window/SettingsWindow.cs +++ b/FantasyPlayer/Interface/Window/SettingsWindow.cs @@ -1,4 +1,5 @@ using Dalamud.Bindings.ImGui; +using System; using System.Numerics; using AllaganLib.Shared.Extensions; using Dalamud.Interface; @@ -262,7 +263,59 @@ private void MainWindow() } } + if (ImGui.CollapsingHeader("Lyrics Settings")) + { + var enableLyrics = _configuration.LyricsSettings.EnableLyrics; + if (ImGui.Checkbox("Enable Lyrics", ref enableLyrics)) + { + _configuration.LyricsSettings.EnableLyrics = enableLyrics; + } + + var displayModeIndex = (int)_configuration.LyricsSettings.DisplayMode; + string[] displayModes = { "Chat", "Flytext" }; + if (ImGui.Combo("Lyric Display Mode", ref displayModeIndex, displayModes, displayModes.Length)) + { + _configuration.LyricsSettings.DisplayMode = (Config.LyricDisplayMode)displayModeIndex; + } + + if (_configuration.LyricsSettings.DisplayMode == Config.LyricDisplayMode.Chat) + { + if (Widget.DrawChatTypeSelector("Lyrics chat output channel", + "To which chat channel should the lyrics be printed?", + _configuration.LyricsSettings.ChatType, + type => { _configuration.LyricsSettings.ChatType = type; })) + { + } + } + else + { + var lyricColor = AbgrToVector4(_configuration.LyricsSettings.FlyTextColor); + if (ImGui.ColorEdit4("Flytext Color", ref lyricColor)) + { + _configuration.LyricsSettings.FlyTextColor = Vector4ToAbgr(lyricColor); + } + } + } + ImGui.Separator(); } + + private static Vector4 AbgrToVector4(uint abgr) + { + var r = (abgr & 0xFF) / 255f; + var g = ((abgr >> 8) & 0xFF) / 255f; + var b = ((abgr >> 16) & 0xFF) / 255f; + var a = ((abgr >> 24) & 0xFF) / 255f; + return new Vector4(r, g, b, a); + } + + private static uint Vector4ToAbgr(Vector4 color) + { + var r = (uint)Math.Clamp(color.X * 255f, 0f, 255f); + var g = (uint)Math.Clamp(color.Y * 255f, 0f, 255f); + var b = (uint)Math.Clamp(color.Z * 255f, 0f, 255f); + var a = (uint)Math.Clamp(color.W * 255f, 0f, 255f); + return (a << 24) | (b << 16) | (g << 8) | r; + } } } \ No newline at end of file diff --git a/FantasyPlayer/Lyrics/LrcLibResponse.cs b/FantasyPlayer/Lyrics/LrcLibResponse.cs new file mode 100644 index 0000000..772c92b --- /dev/null +++ b/FantasyPlayer/Lyrics/LrcLibResponse.cs @@ -0,0 +1,25 @@ +using System.Text.Json.Serialization; + +namespace FantasyPlayer.Lyrics +{ + public class LrcLibResponse + { + [JsonPropertyName("id")] + public int Id { get; set; } + + [JsonPropertyName("trackName")] + public string? TrackName { get; set; } + + [JsonPropertyName("artistName")] + public string? ArtistName { get; set; } + + [JsonPropertyName("albumName")] + public string? AlbumName { get; set; } + + [JsonPropertyName("duration")] + public double Duration { get; set; } + + [JsonPropertyName("syncedLyrics")] + public string? SyncedLyrics { get; set; } + } +} diff --git a/FantasyPlayer/Lyrics/LyricsManager.cs b/FantasyPlayer/Lyrics/LyricsManager.cs new file mode 100644 index 0000000..af797c8 --- /dev/null +++ b/FantasyPlayer/Lyrics/LyricsManager.cs @@ -0,0 +1,195 @@ +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using Dalamud.Game.Gui.FlyText; +using Dalamud.Game.Text; +using Dalamud.Game.Text.SeStringHandling; +using Dalamud.Game.Text.SeStringHandling.Payloads; +using Dalamud.Plugin.Services; +using FantasyPlayer.Config; +using FantasyPlayer.Manager; +using Microsoft.Extensions.Hosting; + +namespace FantasyPlayer.Lyrics +{ + public class LyricsManager : BackgroundService + { + private readonly IPluginLog _pluginLog; + private readonly Configuration _configuration; + private readonly PlayerManager _playerManager; + private readonly LyricsService _lyricsService; + private readonly IChatGui _chatGui; + private readonly IFlyTextGui _flyTextGui; + private readonly IFramework _framework; + + private string? _lastTrackId; + private List<(TimeSpan Time, string Text)> _currentLyrics = new(); + private int _lastLyricIndex = -1; + private bool _isFetching; + + private int _lastProgressMs = -1; + private DateTime _lastProgressTime = DateTime.MinValue; + + public LyricsManager( + IPluginLog pluginLog, + Configuration configuration, + PlayerManager playerManager, + LyricsService lyricsService, + IChatGui chatGui, + IFlyTextGui flyTextGui, + IFramework framework) + { + _pluginLog = pluginLog; + _configuration = configuration; + _playerManager = playerManager; + _lyricsService = lyricsService; + _chatGui = chatGui; + _flyTextGui = flyTextGui; + _framework = framework; + } + + public override async Task StartAsync(CancellationToken cancellationToken) + { + _framework.Update += OnFrameworkUpdate; + await base.StartAsync(cancellationToken); + } + + public override async Task StopAsync(CancellationToken cancellationToken) + { + _framework.Update -= OnFrameworkUpdate; + await base.StopAsync(cancellationToken); + } + + protected override Task ExecuteAsync(CancellationToken stoppingToken) + { + return Task.CompletedTask; + } + + private void OnFrameworkUpdate(IFramework framework) + { + if (!_configuration.LyricsSettings.EnableLyrics) + return; + + var provider = _playerManager.CurrentPlayerProvider; + if (provider == null) + return; + + var state = provider.PlayerState; + if (!state.IsPlaying) + return; + + var currentTrack = state.CurrentlyPlaying; + var trackId = currentTrack.Id; + + if (trackId != _lastTrackId) + { + _lastTrackId = trackId; + _lastLyricIndex = -1; + _lastProgressMs = -1; + _lastProgressTime = DateTime.MinValue; + _currentLyrics = new List<(TimeSpan, string)>(); + + if (!string.IsNullOrEmpty(trackId) && !_isFetching) + { + var artist = currentTrack.Artists.Length > 0 ? currentTrack.Artists[0] : string.Empty; + _ = FetchLyricsAsync(artist, currentTrack.Name, currentTrack.Album.Name, currentTrack.DurationMs); + } + } + + if (_currentLyrics.Count == 0) + return; + + var apiProgressMs = state.ProgressMs; + var now = DateTime.UtcNow; + + TimeSpan currentTime; + if (_lastProgressMs >= 0 && apiProgressMs == _lastProgressMs) + { + currentTime = TimeSpan.FromMilliseconds(_lastProgressMs) + (now - _lastProgressTime); + } + else + { + if (_lastProgressMs >= 0) + { + var expectedMs = _lastProgressMs + (int)(now - _lastProgressTime).TotalMilliseconds; + if (apiProgressMs < _lastProgressMs || Math.Abs(apiProgressMs - expectedMs) > 5000) + { + _lastLyricIndex = -1; + } + } + + _lastProgressMs = apiProgressMs; + _lastProgressTime = now; + currentTime = TimeSpan.FromMilliseconds(apiProgressMs); + } + + for (var i = _lastLyricIndex + 1; i < _currentLyrics.Count; i++) + { + var lyric = _currentLyrics[i]; + if (currentTime < lyric.Time) + break; + + var isLastLine = i + 1 >= _currentLyrics.Count; + var beforeNext = isLastLine || currentTime < _currentLyrics[i + 1].Time; + + if (beforeNext) + { + DisplayLyric(lyric.Text); + _lastLyricIndex = i; + break; + } + } + } + + private async Task FetchLyricsAsync(string artist, string title, string album, int durationMs) + { + _isFetching = true; + try + { + var lyrics = await _lyricsService.FetchSyncedLyricsAsync(artist, title, album, durationMs); + _currentLyrics = lyrics; + _lastLyricIndex = -1; + } + catch (Exception ex) + { + _pluginLog.Error($"Failed to fetch lyrics: {ex.Message}"); + } + finally + { + _isFetching = false; + } + } + + private void DisplayLyric(string text) + { + try + { + var line = $"♪ {text}"; + if (_configuration.LyricsSettings.DisplayMode == LyricDisplayMode.FlyText) + { + _flyTextGui.AddFlyText( + FlyTextKind.Named, + 1, 0, 0, + new SeString(new List { new TextPayload(line) }), + SeString.Empty, + _configuration.LyricsSettings.FlyTextColor, + 0, 0); + } + else + { + _chatGui.Print(new XivChatEntry + { + Message = new SeString(new List { new TextPayload(line) }), + Name = SeString.Empty, + Type = _configuration.LyricsSettings.ChatType, + }); + } + } + catch (Exception ex) + { + _pluginLog.Error($"Failed to display lyric: {ex.Message}"); + } + } + } +} diff --git a/FantasyPlayer/Lyrics/LyricsService.cs b/FantasyPlayer/Lyrics/LyricsService.cs new file mode 100644 index 0000000..0222540 --- /dev/null +++ b/FantasyPlayer/Lyrics/LyricsService.cs @@ -0,0 +1,88 @@ +using System; +using System.Collections.Generic; +using System.Net.Http; +using System.Text.Json; +using System.Text.RegularExpressions; +using System.Threading.Tasks; +using Dalamud.Plugin.Services; + +namespace FantasyPlayer.Lyrics +{ + public class LyricsService : IDisposable + { + private static readonly HttpClient HttpClient = new HttpClient(); + private static readonly Regex LrcLineRegex = new Regex(@"\[(\d+):(\d+)\.(\d+)\](.*)", RegexOptions.Compiled); + + private readonly IPluginLog _pluginLog; + + public LyricsService(IPluginLog pluginLog) + { + _pluginLog = pluginLog; + HttpClient.DefaultRequestHeaders.UserAgent.TryParseAdd("FantasyPlayer/1.0"); + } + + public async Task> FetchSyncedLyricsAsync( + string artist, string title, string album, int durationMs) + { + try + { + var durationSeconds = durationMs / 1000; + var url = $"https://lrclib.net/api/get" + + $"?artist_name={Uri.EscapeDataString(artist)}" + + $"&track_name={Uri.EscapeDataString(title)}" + + $"&album_name={Uri.EscapeDataString(album)}" + + $"&duration={durationSeconds}"; + + var response = await HttpClient.GetAsync(url); + if (!response.IsSuccessStatusCode) + { + _pluginLog.Debug($"LRCLIB returned {(int)response.StatusCode} for {artist} - {title}"); + return new List<(TimeSpan, string)>(); + } + + var content = await response.Content.ReadAsStringAsync(); + var lrcResponse = JsonSerializer.Deserialize(content); + if (lrcResponse?.SyncedLyrics == null) + { + _pluginLog.Debug($"No synced lyrics found for {artist} - {title}"); + return new List<(TimeSpan, string)>(); + } + + var lines = ParseLrc(lrcResponse.SyncedLyrics); + _pluginLog.Debug($"Parsed {lines.Count} lyric lines for {artist} - {title}"); + return lines; + } + catch (Exception ex) + { + _pluginLog.Error($"Failed to fetch lyrics from LRCLIB: {ex.Message}"); + return new List<(TimeSpan, string)>(); + } + } + + private static List<(TimeSpan Time, string Text)> ParseLrc(string lrc) + { + var lines = new List<(TimeSpan, string)>(); + foreach (var rawLine in lrc.Split('\n')) + { + var match = LrcLineRegex.Match(rawLine.Trim()); + if (!match.Success) continue; + + var minutes = int.Parse(match.Groups[1].Value); + var seconds = int.Parse(match.Groups[2].Value); + var centiseconds = int.Parse(match.Groups[3].Value); + var text = match.Groups[4].Value.Trim(); + + if (string.IsNullOrWhiteSpace(text)) continue; + + var time = new TimeSpan(0, 0, minutes, seconds, centiseconds * 10); + lines.Add((time, text)); + } + + return lines; + } + + public void Dispose() + { + } + } +} diff --git a/FantasyPlayer/Plugin.cs b/FantasyPlayer/Plugin.cs index 0a52105..d9560b3 100644 --- a/FantasyPlayer/Plugin.cs +++ b/FantasyPlayer/Plugin.cs @@ -1,8 +1,8 @@ using Dalamud.Plugin; -using Dalamud.Plugin.Services; using FantasyPlayer.Config; using FantasyPlayer.Interface; using FantasyPlayer.Interfaces; +using FantasyPlayer.Lyrics; using FantasyPlayer.Ipc; using FantasyPlayer.Manager; @@ -53,6 +53,8 @@ public override void ConfigureContainer(ContainerBuilder containerBuilder) containerBuilder.RegisterType().As().SingleInstance(); containerBuilder.RegisterType().As().SingleInstance(); containerBuilder.RegisterType().SingleInstance(); + containerBuilder.RegisterType().SingleInstance(); + containerBuilder.RegisterType().SingleInstance(); containerBuilder.RegisterType().As().SingleInstance(); containerBuilder.RegisterType().As().SingleInstance(); containerBuilder.RegisterType().SingleInstance(); @@ -65,6 +67,7 @@ public override void ConfigureServices(IServiceCollection serviceCollection) serviceCollection.AddHostedService(p => p.GetRequiredService()); serviceCollection.AddHostedService(p => p.GetRequiredService()); serviceCollection.AddHostedService(p => p.GetRequiredService()); + serviceCollection.AddHostedService(p => p.GetRequiredService()); serviceCollection.AddHostedService(p => p.GetRequiredService()); serviceCollection.AddHostedService(p => p.GetRequiredService()); } diff --git a/FantasyPlayer/Provider/SpotifyProvider.cs b/FantasyPlayer/Provider/SpotifyProvider.cs index c63068e..0e2a422 100644 --- a/FantasyPlayer/Provider/SpotifyProvider.cs +++ b/FantasyPlayer/Provider/SpotifyProvider.cs @@ -132,6 +132,16 @@ private void OnLoggedIn(PrivateUser privateUser, PKCETokenResponse tokenResponse public void Update() { + if (_spotifyState?.CurrentlyPlaying != null) + { + var progressMs = _spotifyState.CurrentlyPlaying.ProgressMs; + if (PlayerState.ProgressMs != progressMs) + { + var playerStateStruct = PlayerState; + playerStateStruct.ProgressMs = progressMs; + PlayerState = playerStateStruct; + } + } } public void ReAuth()