diff --git a/.env.example b/.env.example
index f8f4992..5953953 100644
--- a/.env.example
+++ b/.env.example
@@ -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
diff --git a/README.md b/README.md
index f8a3c66..b3432b7 100644
--- a/README.md
+++ b/README.md
@@ -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
diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs
index 445f1d8..b4ef2f2 100644
--- a/allstarr/Controllers/JellyfinController.cs
+++ b/allstarr/Controllers/JellyfinController.cs
@@ -1210,6 +1210,17 @@ public class JellyfinController : ControllerBase
#region Playlists
+ ///
+ /// Intercepts playlist items requests to inject Spotify playlists.
+ /// This route must have lower Order than the catch-all ProxyRequest route.
+ ///
+ [HttpGet("Playlists/{playlistId}/Items", Order = 5)]
+ public async Task GetPlaylistItems(string playlistId)
+ {
+ _logger.LogInformation("GetPlaylistItems called for playlist {PlaylistId}", playlistId);
+ return await GetPlaylistTracks(playlistId);
+ }
+
///
/// Gets playlist tracks displayed as an album.
///
diff --git a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
index 0fb114a..893b883 100644
--- a/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
+++ b/allstarr/Services/Spotify/SpotifyMissingTracksFetcher.cs
@@ -13,6 +13,7 @@ public class SpotifyMissingTracksFetcher : BackgroundService
private readonly IHttpClientFactory _httpClientFactory;
private readonly RedisCacheService _cache;
private readonly ILogger _logger;
+ private bool _hasRunOnce = false;
public SpotifyMissingTracksFetcher(
IOptions 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 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;