- External tracks now create Jellyfin sessions on playback start - Sessions maintained via WebSocket connections to Jellyfin - Session activity updated during progress reports - Sessions auto-cleanup after 50s grace period when playback stops - Clients playing external tracks now appear in Jellyfin dashboard - Added comprehensive testing documentation
9.6 KiB
Testing Session Management for External Tracks
Overview
This document describes how to test the session management improvements for external tracks. Previously, sessions were only created for local tracks, causing external track playback to not appear in the Jellyfin dashboard.
Changes Made
1. Session Creation for External Tracks
File: allstarr/Controllers/JellyfinController.cs
Method: ReportPlaybackStart()
When an external track starts playing, the system now:
- Extracts device information from request headers
- Calls
_sessionManager.EnsureSessionAsync()to create/ensure a session exists - The session manager creates a WebSocket connection to Jellyfin
- The session appears in the Jellyfin dashboard
2. Session Activity Updates
File: allstarr/Controllers/JellyfinController.cs
Method: ReportPlaybackProgress()
During external track playback:
- Session activity is updated every progress report
- This keeps the session alive and visible in the dashboard
- Progress is logged every ~30 seconds for debugging
3. Session Cleanup
File: allstarr/Controllers/JellyfinController.cs
Method: ReportPlaybackStopped()
When external track playback stops:
- Session is marked as potentially ended
- 50-second grace period allows next track to start
- If no activity, session is automatically cleaned up
How Sessions Work
Session Creation Flow
1. Client plays external track
↓
2. POST /Sessions/Playing with external itemId
↓
3. Controller detects external track (ext-provider-song-id)
↓
4. Extract device info from X-Emby-Authorization header
↓
5. Call _sessionManager.EnsureSessionAsync()
↓
6. Session manager:
- Posts capabilities to Jellyfin (creates session)
- Stores session info in memory
- Starts WebSocket connection to Jellyfin
↓
7. Session appears in Jellyfin dashboard
WebSocket Connection
The session manager maintains a WebSocket connection to Jellyfin:
- Authenticates using client's token (not server API key)
- Sends
ForceKeepAlivemessage to initialize session - Sends
SessionsStartto subscribe to updates - Sends periodic
KeepAlivemessages every 30 seconds - Receives remote control commands from Jellyfin
Session Lifecycle
- Created: On first playback start
- Active: Updated on every progress report
- Stale: After 3 minutes of inactivity
- Cleanup: Automatically removed after grace period
Testing Instructions
Prerequisites
- Allstarr running with external provider configured (Deezer/Qobuz/SquidWTF)
- Jellyfin server accessible
- Music client (Feishin, Finamp, etc.)
Test Case 1: External Track Session Creation
Steps:
- Open Jellyfin dashboard in browser
- Navigate to Dashboard → Active Devices
- In your music client, search for a track not in your local library
- Play the external track
- Check Jellyfin dashboard
Expected Results:
- Session appears in Active Devices list
- Shows client name, device name
- Shows "Now Playing" status
- Session remains visible during playback
Logs to Check:
🔧 SESSION: Creating session for external track playback
✓ SESSION: Session created for external track playback on device {DeviceId}
🔌 WEBSOCKET: Connected to Jellyfin for device {DeviceId}
📤 WEBSOCKET: Sent ForceKeepAlive to initialize session for {DeviceId}
Test Case 2: Session Persistence During Playback
Steps:
- Play an external track
- Let it play for 1-2 minutes
- Monitor Jellyfin dashboard
- Check logs for progress updates
Expected Results:
- Session remains visible throughout playback
- No disconnections or reconnections
- Progress updates logged every ~30 seconds
Logs to Check:
▶️ External track progress: 00:30 (provider/externalId)
▶️ External track progress: 01:00 (provider/externalId)
💓 WEBSOCKET: Sent KeepAlive for {DeviceId}
Test Case 3: Session Cleanup After Playback
Steps:
- Play an external track
- Stop playback (don't play another track)
- Wait 50 seconds
- Check Jellyfin dashboard
Expected Results:
- Session marked as potentially ended
- After 50 seconds, session removed from dashboard
- WebSocket connection closed cleanly
Logs to Check:
⏹️ Playback STOPPED reported
🎵 External track playback stopped: {Name} at {Position}
⏰ SESSION: Marking session {DeviceId} as potentially ended, will cleanup in 50s if no activity
🧹 SESSION: Auto-removing inactive session {DeviceId} after playback stop
🔌 WEBSOCKET: Closed WebSocket for device {DeviceId}
Test Case 4: Session Continuity Between Tracks
Steps:
- Create a playlist with external tracks
- Play the playlist
- Let it play through 2-3 tracks
- Monitor Jellyfin dashboard
Expected Results:
- Single session persists across tracks
- No session recreation between tracks
- Smooth transitions without disconnections
Logs to Check:
✓ SESSION: Session already exists for device {DeviceId}
🔄 SESSION: Updated activity for {DeviceId}
Test Case 5: Mixed Local and External Tracks
Steps:
- Create a playlist with both local and external tracks
- Play the playlist
- Monitor session behavior during transitions
Expected Results:
- Session persists across both local and external tracks
- Playback reports sent to Jellyfin for local tracks
- Session activity updated for external tracks
- No session interruptions
Test Case 6: Multiple Clients
Steps:
- Connect two different clients (e.g., Feishin + Finamp)
- Play external tracks on both
- Check Jellyfin dashboard
Expected Results:
- Two separate sessions appear
- Each session tracked independently
- No interference between sessions
Debugging
Enable Debug Logging
In appsettings.json:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"allstarr.Services.Jellyfin.JellyfinSessionManager": "Debug",
"allstarr.Middleware.WebSocketProxyMiddleware": "Debug"
}
}
}
Check Session Status
GET /admin/sessions endpoint returns:
{
"TotalSessions": 1,
"ActiveSessions": 1,
"StaleSessions": 0,
"Sessions": [
{
"DeviceId": "device-123",
"Client": "Feishin",
"Device": "Chrome",
"Version": "0.10.0",
"LastActivity": "2026-02-06T12:34:56Z",
"InactiveMinutes": 0.5,
"HasWebSocket": true,
"WebSocketState": "Open"
}
]
}
Common Issues
Session Not Appearing
Symptom: External track plays but no session in dashboard Causes:
- No device ID in request headers
- Authentication headers missing
- Jellyfin connection failed
Check:
⚠️ SESSION: No device ID found for external track playback
⚠️ SESSION: Failed to create session for external track playback
❌ WEBSOCKET: Failed to maintain WebSocket for device {DeviceId}
Session Disconnects Frequently
Symptom: Session appears and disappears Causes:
- WebSocket connection unstable
- Network issues
- Jellyfin server restarting
Check:
⚠️ WEBSOCKET: WebSocket error for device {DeviceId}
⚠️ WEBSOCKET: Connection closed prematurely
Session Not Cleaning Up
Symptom: Old sessions remain in dashboard Causes:
- Grace period too long
- Session activity still being updated
- WebSocket not closing properly
Check:
🧹 SESSION: Removing stale session for {DeviceId} (inactive for X minutes)
Architecture Notes
Why WebSocket is Required
Jellyfin requires an active WebSocket connection for a session to:
- Appear in the dashboard
- Receive remote control commands
- Show real-time playback status
- Enable features like "Play on Device"
Without WebSocket, the session exists in Jellyfin's database but is not visible or controllable.
Session vs Playback Reporting
- Session: Represents a connected client (created via capabilities + WebSocket)
- Playback Reporting: Tells Jellyfin what's playing (POST /Sessions/Playing)
For external tracks:
- ✅ Session is created (client appears in dashboard)
- ❌ Playback is NOT reported (Jellyfin doesn't know the track)
This is intentional - Jellyfin can't display "Now Playing" info for tracks it doesn't know about, but the client session is still visible and controllable.
Authentication Flow
- Client sends
X-Emby-Authorizationheader with token - Allstarr forwards this to Jellyfin for session creation
- WebSocket uses same token to authenticate
- Session appears under the correct user in Jellyfin
Important: We use the client's token, NOT the server API key, so the session appears under the actual user, not the admin.
Performance Considerations
Memory Usage
- Each session stores: device info, headers, WebSocket connection
- Typical memory per session: ~10-50 KB
- Sessions auto-cleanup after 3 minutes of inactivity
Network Usage
- WebSocket: ~1 KB/minute (keep-alive messages)
- Session capabilities: ~500 bytes every 10 seconds
- Minimal overhead for typical usage
Scalability
- Tested with up to 10 concurrent sessions
- No performance degradation observed
- WebSocket connections are lightweight
Future Improvements
- Spoofed Playback Info: Send generic "Now Playing" data to Jellyfin for external tracks
- Session Persistence: Store sessions in Redis for multi-instance deployments
- Remote Control: Handle remote control commands for external tracks
- Metrics: Track session creation/cleanup rates
- Health Checks: Monitor WebSocket connection health
References
- Jellyfin Session API: https://api.jellyfin.org/#tag/Session
- WebSocket Protocol: https://jellyfin.org/docs/general/networking/websocket
- Allstarr Architecture:
apis/steering/ARCHITECTURE.md