diff --git a/allstarr/Services/Common/BaseDownloadService.cs b/allstarr/Services/Common/BaseDownloadService.cs
index 9e971a7..9ccfad8 100644
--- a/allstarr/Services/Common/BaseDownloadService.cs
+++ b/allstarr/Services/Common/BaseDownloadService.cs
@@ -110,6 +110,9 @@ public abstract class BaseDownloadService : IDownloadService
IOFile.SetLastAccessTime(localPath, DateTime.UtcNow);
}
+ // Start background Odesli conversion for lyrics (if not already cached)
+ StartBackgroundOdesliConversion(externalProvider, externalId);
+
return IOFile.OpenRead(localPath);
}
@@ -125,6 +128,10 @@ public abstract class BaseDownloadService : IDownloadService
localPath = await DownloadSongInternalAsync(externalProvider, externalId, triggerAlbumDownload: true, cancellationToken);
var elapsed = (DateTime.UtcNow - startTime).TotalMilliseconds;
Logger.LogInformation("Download completed, starting stream ({ElapsedMs}ms total): {Path}", elapsed, localPath);
+
+ // Start background Odesli conversion for lyrics (after stream starts)
+ StartBackgroundOdesliConversion(externalProvider, externalId);
+
return IOFile.OpenRead(localPath);
}
catch (OperationCanceledException)
@@ -141,6 +148,37 @@ public abstract class BaseDownloadService : IDownloadService
}
}
+ ///
+ /// Starts background Odesli conversion for lyrics support.
+ /// This is called AFTER streaming starts so it doesn't block the client.
+ ///
+ private void StartBackgroundOdesliConversion(string externalProvider, string externalId)
+ {
+ _ = Task.Run(async () =>
+ {
+ try
+ {
+ // Provider-specific conversion (override in subclasses if needed)
+ await ConvertToSpotifyIdAsync(externalProvider, externalId);
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex, "Background Spotify ID conversion failed for {Provider}:{ExternalId}", externalProvider, externalId);
+ }
+ });
+ }
+
+ ///
+ /// Converts external track ID to Spotify ID for lyrics support.
+ /// Override in provider-specific services if needed.
+ ///
+ protected virtual Task ConvertToSpotifyIdAsync(string externalProvider, string externalId)
+ {
+ // Default implementation does nothing
+ // Provider-specific services can override this
+ return Task.CompletedTask;
+ }
+
public DownloadInfo? GetDownloadStatus(string songId)
{
ActiveDownloads.TryGetValue(songId, out var info);
diff --git a/allstarr/Services/SquidWTF/SquidWTFDownloadService.cs b/allstarr/Services/SquidWTF/SquidWTFDownloadService.cs
index 53b0494..6468a33 100644
--- a/allstarr/Services/SquidWTF/SquidWTFDownloadService.cs
+++ b/allstarr/Services/SquidWTF/SquidWTFDownloadService.cs
@@ -108,9 +108,6 @@ public class SquidWTFDownloadService : BaseDownloadService
Logger.LogInformation("Track download URL obtained from hifi-api: {Url}", downloadInfo.DownloadUrl);
Logger.LogInformation("Using format: {Format} (Quality: {Quality})", downloadInfo.MimeType, downloadInfo.AudioQuality);
- // Start Spotify ID conversion in parallel with download (don't await yet)
- var spotifyIdTask = _odesliService.ConvertTidalToSpotifyIdAsync(trackId, cancellationToken);
-
// Determine extension from MIME type
var extension = downloadInfo.MimeType?.ToLower() switch
{
@@ -199,42 +196,26 @@ public class SquidWTFDownloadService : BaseDownloadService
// Close file before writing metadata
await outputFile.DisposeAsync();
- // Wait for Spotify ID conversion to complete (with 2 second timeout)
- // If Odesli is slow, we'll skip it and add the Spotify ID later in background
- var spotifyId = await Task.WhenAny(
- spotifyIdTask,
- Task.Delay(2000, cancellationToken)
- ) == spotifyIdTask ? await spotifyIdTask : null;
-
- if (!string.IsNullOrEmpty(spotifyId))
+ // Start Spotify ID conversion in background (for lyrics support)
+ // This doesn't block streaming - lyrics endpoint will fetch it on-demand if needed
+ _ = Task.Run(async () =>
{
- song.SpotifyId = spotifyId;
- Logger.LogDebug("Spotify ID obtained: {SpotifyId}", spotifyId);
- }
- else
- {
- Logger.LogDebug("Spotify ID not available yet (Odesli timeout), will update in background");
-
- // Continue Odesli conversion in background (for future lyrics requests)
- _ = Task.Run(async () =>
+ try
{
- try
+ var spotifyId = await _odesliService.ConvertTidalToSpotifyIdAsync(trackId, CancellationToken.None);
+ if (!string.IsNullOrEmpty(spotifyId))
{
- var bgSpotifyId = await spotifyIdTask;
- if (!string.IsNullOrEmpty(bgSpotifyId))
- {
- Logger.LogDebug("Background Spotify ID obtained: {SpotifyId}", bgSpotifyId);
- // Note: We don't re-write metadata here, just cache the ID for lyrics
- }
+ Logger.LogDebug("Background Spotify ID obtained for Tidal/{TrackId}: {SpotifyId}", trackId, spotifyId);
+ // Spotify ID is cached by Odesli service for future lyrics requests
}
- catch (Exception ex)
- {
- Logger.LogDebug(ex, "Background Spotify ID conversion failed");
- }
- });
- }
+ }
+ catch (Exception ex)
+ {
+ Logger.LogDebug(ex, "Background Spotify ID conversion failed for Tidal/{TrackId}", trackId);
+ }
+ });
- // Write metadata and cover art
+ // Write metadata and cover art (without Spotify ID - it's only needed for lyrics)
await WriteMetadataAsync(outputPath, song, cancellationToken);
return outputPath;
@@ -327,6 +308,24 @@ public class SquidWTFDownloadService : BaseDownloadService
#region Utility Methods
+ ///
+ /// Converts Tidal track ID to Spotify ID for lyrics support.
+ /// Called in background after streaming starts.
+ ///
+ protected override async Task ConvertToSpotifyIdAsync(string externalProvider, string externalId)
+ {
+ if (externalProvider != "squidwtf")
+ {
+ return;
+ }
+
+ var spotifyId = await _odesliService.ConvertTidalToSpotifyIdAsync(externalId, CancellationToken.None);
+ if (!string.IsNullOrEmpty(spotifyId))
+ {
+ Logger.LogDebug("Background Spotify ID obtained for Tidal/{TrackId}: {SpotifyId}", externalId, spotifyId);
+ // Spotify ID is cached by Odesli service for future lyrics requests
+ }
+ }
#endregion