Fix Spotify playlist injection: add dedicated route, startup fetch, and clarify config

This commit is contained in:
2026-01-31 16:57:52 -05:00
parent 4ba2245876
commit 2c3ef5c360
4 changed files with 58 additions and 7 deletions

View File

@@ -98,9 +98,9 @@ CACHE_DURATION_HOURS=1
# ===== SPOTIFY PLAYLIST INJECTION (JELLYFIN ONLY) =====
# REQUIRES: Jellyfin Spotify Import Plugin (https://github.com/Viperinius/jellyfin-plugin-spotify-import)
# This feature injects virtual Spotify playlists (Release Radar, Discover Weekly) into Jellyfin
# This feature intercepts Spotify Import plugin playlists (Release Radar, Discover Weekly) and fills them
# with tracks auto-matched from external providers (SquidWTF, Deezer, Qobuz)
# Uses JELLYFIN_URL and JELLYFIN_API_KEY for API access
# Uses JELLYFIN_URL and JELLYFIN_API_KEY configured above (no separate credentials needed)
# Enable Spotify playlist injection (optional, default: false)
SPOTIFY_IMPORT_ENABLED=false

View File

@@ -90,7 +90,7 @@ This project brings together all the music streaming providers into one unified
- **Artist Deduplication**: Merges local and streaming artists to avoid duplicates
- **Album Enrichment**: Adds missing tracks to local albums from streaming providers
- **Cover Art Proxy**: Serves cover art for external content
- **Spotify Playlist Injection** (Jellyfin only): Injects virtual Spotify playlists (Release Radar, Discover Weekly) with tracks auto-matched from streaming providers
- **Spotify Playlist Injection** (Jellyfin only): Intercepts Spotify Import plugin playlists (Release Radar, Discover Weekly) and fills them with tracks auto-matched from streaming providers
## Supported Backends
@@ -290,7 +290,7 @@ Subsonic__EnableExternalPlaylists=false
### Spotify Playlist Injection (Jellyfin Only)
Allstarr can inject virtual Spotify playlists (Release Radar, Discover Weekly) into Jellyfin with tracks automatically matched from your configured streaming provider.
Allstarr can intercept Spotify Import plugin playlists (Release Radar, Discover Weekly) and fill them with tracks automatically matched from your configured streaming provider (SquidWTF, Deezer, or Qobuz).
**Requirements:**
- [Jellyfin Spotify Import Plugin](https://github.com/Viperinius/jellyfin-plugin-spotify-import) installed and configured
@@ -308,11 +308,12 @@ Allstarr can inject virtual Spotify playlists (Release Radar, Discover Weekly) i
| `SpotifyImport:Playlists` | Array of playlists to inject (Name, SpotifyName, Enabled) |
**How it works:**
1. Jellyfin Spotify Import plugin runs daily and creates missing tracks files for playlists
2. Allstarr fetches these files within the configured time window
1. Jellyfin Spotify Import plugin runs daily and creates playlists + missing tracks files
2. Allstarr fetches these missing tracks files within the configured time window
3. For each missing track, Allstarr searches your streaming provider (SquidWTF, Deezer, or Qobuz)
4. Virtual playlists appear in Jellyfin with matched tracks ready to stream
4. When you open the playlist in Jellyfin, Allstarr intercepts the request and returns matched tracks
5. Tracks are downloaded on-demand when played
6. On startup, Allstarr will fetch missing tracks if it hasn't run in the last 24 hours
**Environment variables:**
```bash

View File

@@ -1210,6 +1210,17 @@ public class JellyfinController : ControllerBase
#region Playlists
/// <summary>
/// Intercepts playlist items requests to inject Spotify playlists.
/// This route must have lower Order than the catch-all ProxyRequest route.
/// </summary>
[HttpGet("Playlists/{playlistId}/Items", Order = 5)]
public async Task<IActionResult> GetPlaylistItems(string playlistId)
{
_logger.LogInformation("GetPlaylistItems called for playlist {PlaylistId}", playlistId);
return await GetPlaylistTracks(playlistId);
}
/// <summary>
/// Gets playlist tracks displayed as an album.
/// </summary>

View File

@@ -13,6 +13,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService
private readonly IHttpClientFactory _httpClientFactory;
private readonly RedisCacheService _cache;
private readonly ILogger<SpotifyMissingTracksFetcher> _logger;
private bool _hasRunOnce = false;
public SpotifyMissingTracksFetcher(
IOptions<SpotifyImportSettings> spotifySettings,
@@ -47,6 +48,30 @@ public class SpotifyMissingTracksFetcher : BackgroundService
_logger.LogInformation("Spotify missing tracks fetcher started");
// Run once on startup if we haven't run in the last 24 hours
if (!_hasRunOnce)
{
var shouldRunOnStartup = await ShouldRunOnStartupAsync();
if (shouldRunOnStartup)
{
_logger.LogInformation("Running initial fetch on startup");
try
{
await FetchMissingTracksAsync(stoppingToken);
_hasRunOnce = true;
}
catch (Exception ex)
{
_logger.LogError(ex, "Error during startup fetch");
}
}
else
{
_logger.LogInformation("Skipping startup fetch - already ran within last 24 hours");
_hasRunOnce = true;
}
}
while (!stoppingToken.IsCancellationRequested)
{
try
@@ -62,6 +87,20 @@ public class SpotifyMissingTracksFetcher : BackgroundService
}
}
private async Task<bool> ShouldRunOnStartupAsync()
{
// Check if any playlist has cached data from the last 24 hours
foreach (var playlist in _spotifySettings.Value.Playlists.Where(p => p.Enabled))
{
var cacheKey = $"spotify:missing:{playlist.SpotifyName}";
if (await _cache.ExistsAsync(cacheKey))
{
return false; // Already have recent data
}
}
return true; // No recent data, should fetch
}
private async Task FetchMissingTracksAsync(CancellationToken cancellationToken)
{
var settings = _spotifySettings.Value;