- Added Cronos package for cron expression parsing
- Each playlist now has independent cron schedule (default: 0 8 * * 1)
- Cache persists until next cron run, not just cache duration
- Prevents excess Spotify API calls - only refreshes on cron trigger
- Manual refresh still allowed with 5-minute cooldown
- Added 429 rate limit handling for user playlist fetching
- Added crontab.guru link to UI for easy schedule building
- Both SpotifyPlaylistFetcher and SpotifyTrackMatchingService use cron
- Automatic matching only runs when cron schedule triggers
Each playlist now has its own cron schedule for syncing with Spotify. Default is 0 8 * * 1 (Monday 8 AM). Removed global MatchingIntervalHours in favor of per-playlist scheduling.
Enhanced admin configuration UI with missing fields, required indicators, and sp_dc warning. Added Spotify playlist selector for linking with auto-filtering of already-linked playlists. Configured forwarded headers to pass real client IPs from nginx to Jellyfin. Improved track view modal error handling.
- Change DOWNLOAD_PATH to Library__DownloadPath (ASP.NET Core standard)
- Add EnvMigrationService to automatically update old .env files on startup
- Update .env.example with new variable name
- Ensures cache cleanup and downloads use consistent path configuration
- No manual intervention needed - migration happens automatically
- Track lock state to prevent double-release in finally block
- Fixes exception when download is already in progress
- Prevents duplicate file downloads with (1), (2), (3) suffixes
- Ensures proper lock management during concurrent download requests
- Update CacheCleanupService to use downloads/cache instead of /tmp/allstarr-cache
- Matches the actual path used by download services
- Fixes cache files not being cleaned up after expiration
- Add ArtistIds list to Song model to store IDs for all artists
- Update SquidWTF ParseTidalTrack and ParseTidalTrackFull to populate ArtistIds from artists array
- Update Deezer ParseDeezerTrackFull to populate ArtistIds from contributors
- Update JellyfinResponseBuilder to use real ArtistIds instead of fake IDs
- Fixes UnprocessableEntity errors when clicking on secondary artists
- Enables proper navigation to all artist pages in Jellyfin clients
- Cache mode now uses downloads/cache/ instead of cache/Music/
- Permanent mode now uses downloads/permanent/ instead of downloads/
- Kept files already use downloads/kept/
- All download paths now unified under downloads/ base directory
- Changed from separate paths to unified structure under downloads/
- Structure: downloads/{permanent,cache,kept}/
- Removed Library:KeptPath config, now uses downloads/kept/
- Updated AdminController and JellyfinController to use new paths
- Web UI will now correctly show kept tracks in Active Playlists tab
- Matches user's actual folder structure on server
- Simplified SpotifyMissingTracksFetcher to remove complex sync window timing
- Now fetches on startup if cache missing, then checks every 5 minutes for stale cache (>24h)
- Removed SYNC_START_HOUR, SYNC_START_MINUTE, SYNC_WINDOW_HOURS from config
- Updated README and .env.example to reflect simpler configuration
- Sync window was only relevant for legacy Jellyfin plugin scraping method
- When using sp_dc cookie method (recommended), this service is dormant anyway
- Deleted MIGRATION.md (local-only file, not for repo)
- Health checks run in parallel with 3 second timeout
- Results cached for 30 seconds to avoid excessive checks
- Healthy endpoints tried first, unhealthy ones as fallback
- Prevents wasting time on dead endpoints (no more 5 min waits)
- Failed requests mark endpoint as unhealthy in cache
- Significantly improves response time when some endpoints are down
- Download service was still using default 100s timeout
- Large artist responses and slow endpoints were timing out
- Now both MetadataService and DownloadService use 5 minute timeout
- Fixes 'The request was canceled due to the configured HttpClient.Timeout of 100 seconds' errors
- Some artists have 100+ albums causing large API responses
- Default 100s timeout was insufficient
- Now set to 5 minutes to handle even the largest artist catalogs
- Prevents timeouts when fetching artists like Taylor Swift, etc.
- After Odesli converts Tidal ID to Spotify ID, immediately fetch lyrics
- Lyrics are cached and ready when client requests them
- Happens in background, doesn't block streaming
- Ensures best user experience with instant lyrics availability
- Override ConvertToSpotifyIdAsync in SquidWTFDownloadService
- Odesli API call now happens AFTER stream starts returning to client
- Reduces initial streaming latency by ~3-4 seconds
- Lyrics still work - Spotify ID is cached for on-demand lyrics requests
- Background conversion happens just-in-case for future lyrics needs
- Added 6 new monochrome.tf endpoints (eu-central, us-west, arran, api, samidy)
- Added https variant of hund.qqdl.site (was only http before)
- Total endpoints increased from 10 to 16 for better load distribution
- Optimized Odesli/Spotify ID conversion with 2-second timeout
- If Odesli is slow, download proceeds without waiting (Spotify ID added in background)
- This reduces download time by up to 2 seconds when Odesli is slow
- Spotify ID still obtained for lyrics, just doesn't block streaming
Performance improvement: Downloads that took 9.7s may now complete in 7.7s
- Reduced wait interval from 500ms to 100ms when waiting for in-progress downloads
- Added cancellation token checks during wait loops to handle client timeouts immediately
- Added detailed timing logs to track download performance
- Better error messages when downloads fail or are cancelled
- Prevents OperationCanceledException when client times out (typically 10 seconds)
This fixes the issue where concurrent requests for the same track would timeout
because they were waiting too long for the first download to complete.
- Remove broken JellyfinAuthFilter that was checking non-existent CLIENT_USERNAME
- Clients now authenticate directly with Jellyfin (transparent proxy model)
- Improved token expiration detection and session cleanup
- Better logging with reduced verbosity (removed emoji spam)
- Added support for X-Emby-Token header format
- Added detection of public endpoints that don't require auth
- SessionManager now properly detects 401 responses and removes expired sessions
- Clarified .env.example comments about server-side vs client-side auth
- All functionality preserved: Spotify injection, external providers, playback tracking
- Change WebSocket logs to Debug/Trace (connection events, message proxying)
- Change session management logs to Debug (creation, removal, capabilities)
- Change auth header forwarding logs to Debug
- Change playback forwarding logs to Debug
- Change 401 responses to Debug (expected when tokens expire)
- Keep Info level for significant business events (external playback, downloads)
- Keep Warning/Error for actual issues
This significantly reduces log noise while maintaining visibility of important events.
- Log when GetLyrics endpoint is called with itemId
- Log external vs local track determination
- Log Jellyfin lyrics check status code and result
- Will help identify why embedded lyrics aren't being found
- New 'API Analytics' tab displays endpoint usage statistics
- Shows total requests, unique endpoints, and most called endpoint
- Table view with top N endpoints (25/50/100/500 configurable)
- Color-coded by usage intensity and endpoint type
- Auto-refreshes every 30 seconds when tab is active
- Includes clear data functionality and helpful documentation
- Uses existing /api/admin/debug/endpoint-usage endpoint
- Replace endpoint racing with round-robin fallback for downloads to reduce CPU usage and prevent cancellation errors
- Fix cache cleanup to use LastWriteTimeUtc instead of unreliable LastAccessTimeUtc
- Add storage mode, cache duration, and download mode to admin config endpoint
- Show actual download path based on storage mode (cache/Music vs downloads)
- Remove complex Item object from playback start (was causing 400 errors)
- Send minimal playback info with just ghost UUID and playback state
- Progress reports already working (204 responses)
- Jellyfin will track session without full item details
- New EndpointBenchmarkService pings all endpoints on startup
- Measures average response time and success rate
- Reorders endpoints by performance (fastest first)
- RoundRobinFallbackHelper now uses benchmarked order
- Racing still happens, but starts with fastest endpoints
- Reduces latency by prioritizing known-fast servers
- Logs benchmark results for visibility
- GetTrackDownloadInfoAsync now uses RaceAllEndpointsAsync instead of TryWithFallbackAsync
- Prevents sequential fallback through all 10 endpoints on cancellation
- Eliminates cascade of 'task was canceled' warnings
- Consistent racing strategy across all download operations
- Respect SquidWTF/Tidal's native search ranking (better than fuzzy matching)
- Interleave local and external results based on average match quality
- Put better-matching source first, preserve original order within each source
- Remove unnecessary re-scoring that was disrupting optimal search results
- Simplifies search logic and improves result relevance
- Race all proxy endpoints in parallel for downloads (SquidWTF)
- Use fastest responding server, cancel slower ones
- Apply same racing strategy to search operations
- Reduces download wait times from 5-10s to sub-second
- Reduces search latency from ~1s to ~300-500ms
- Add RaceAllEndpointsAsync method to RoundRobinFallbackHelper
- Created RoundRobinFallbackHelper for SquidWTF services (eliminates 3 duplicates)
- Moved QueueRequestAsync to BaseDownloadService (eliminates 2 duplicates)
- Moved CalculateArtistMatchScore to FuzzyMatcher (eliminates 2 duplicates)
- Updated all SquidWTF services to use RoundRobinFallbackHelper
- Updated DeezerDownloadService and SquidWTFDownloadService to use base class rate limiting
- Updated SpotifyTrackMatchingService and JellyfinController to use FuzzyMatcher helper
- All 225 tests passing
- Created OdesliService to convert Tidal track IDs to Spotify IDs
- Integrated Odesli API calls into SquidWTF download workflow
- Updated SquidWTFDownloadService to use OdesliService for track metadata enrichment
- Fixed dependency injection in Program.cs for OdesliService
- All 225 tests passing
- Use linksByPlatform.spotify.url from Odesli response
- Extract track ID from Spotify URL using regex
- More reliable than parsing entityUniqueId format
- Matches the approach used in ConvertToSpotifyIdViaOdesliAsync
- Odesli returns entityUniqueId as 'SPOTIFY_SONG::trackid'
- Now extracts just the track ID part after '::'
- Fixes Spotify lyrics not working due to invalid ID format
- Spotify lyrics service expects clean track IDs like '0PgYPBGqF6Wm5KFHQ81nq5'
- Cache mode now uses cache/Music/ (survives restarts, cleaned after 24h)
- Permanent mode uses downloads/ (keeps forever)
- Fixed all three download services: SquidWTF, Deezer, Qobuz
- Files no longer stored in /tmp/allstarr-cache/ which gets wiped on restart
- Both folders are in project root alongside cache/ and downloads/ directories
- Added SpotifyId field to Song model
- SquidWTFMetadataService now calls Odesli API when fetching track metadata
- Spotify ID is populated immediately when track is loaded, not during lyrics fetch
- GetLyrics now checks song.SpotifyId first before falling back to cache/Odesli
- Enables Spotify lyrics for all SquidWTF (Tidal) tracks automatically
- Reduces latency - conversion happens once during track load, not every lyrics request
- Changed MaintainWebSocketForSessionAsync to use session.Headers instead of parameter
- Parameter headers might be disposed after HTTP request completes
- Session headers are cloned and stored safely in memory
- WebSocket now properly authenticates as the client instead of falling back to server API key
- Sessions will now appear under correct user in Jellyfin dashboard
- Extract AccessToken from auth response before background task
- Create new HeaderDictionary with token instead of using Request.Headers
- Prevents ObjectDisposedException when HTTP context is disposed
- Session capabilities now work correctly for all clients
Note: WebSocket support for external tracks already implemented via
JellyfinSessionManager.EnsureSessionAsync and WebSocketProxyMiddleware
- Pass through ALL Jellyfin responses (success and error) without modification
- Move session capabilities posting to background task (don't block auth response)
- Remove generic error fallbacks - always return Jellyfin's actual response
- Simplify logic: if Jellyfin returns a response, pass it through; if not, return status code only
- Modified PostJsonAsync to return error response body as JSON when available
- Updated AuthenticateByName to pass through Jellyfin's actual error response
- Clients now see Jellyfin's real error messages instead of generic ones