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()