fix: session capabilities using disposed HTTP context

- 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
This commit is contained in:
2026-02-07 11:49:43 -05:00
parent da8cb29e08
commit 6169d7a4ac

View File

@@ -1838,43 +1838,62 @@ public class JellyfinController : ControllerBase
{ {
var responseJson = result.RootElement.GetRawText(); var responseJson = result.RootElement.GetRawText();
// On successful auth, post session capabilities in background // On successful auth, extract access token and post session capabilities in background
if (statusCode == 200) if (statusCode == 200)
{ {
_logger.LogInformation("Authentication successful, posting session capabilities in background"); _logger.LogInformation("Authentication successful");
// Don't await - do this in background so we return auth response immediately // Extract access token from response for session capabilities
_ = Task.Run(async () => string? accessToken = null;
if (result.RootElement.TryGetProperty("AccessToken", out var tokenEl))
{ {
try accessToken = tokenEl.GetString();
{ }
_logger.LogInformation("🔧 Posting session capabilities after authentication");
var capabilities = new
{
PlayableMediaTypes = new[] { "Audio" },
SupportedCommands = Array.Empty<string>(),
SupportsMediaControl = false,
SupportsPersistentIdentifier = true,
SupportsSync = false
};
var capabilitiesJson = JsonSerializer.Serialize(capabilities); // Post session capabilities in background if we have a token
var (capResult, capStatus) = await _proxyService.PostJsonAsync("Sessions/Capabilities/Full", capabilitiesJson, Request.Headers); if (!string.IsNullOrEmpty(accessToken))
{
if (capStatus == 204 || capStatus == 200) // Capture token in closure - don't use Request.Headers (will be disposed)
{ var token = accessToken;
_logger.LogInformation("✓ Session capabilities posted after auth ({StatusCode})", capStatus); _ = Task.Run(async () =>
}
else
{
_logger.LogWarning("⚠ Session capabilities returned {StatusCode} after auth", capStatus);
}
}
catch (Exception ex)
{ {
_logger.LogWarning(ex, "Failed to post session capabilities after auth"); try
} {
}); _logger.LogDebug("🔧 Posting session capabilities after authentication");
// Build auth header with the new token
var authHeaders = new HeaderDictionary
{
["X-Emby-Token"] = token
};
var capabilities = new
{
PlayableMediaTypes = new[] { "Audio" },
SupportedCommands = Array.Empty<string>(),
SupportsMediaControl = false,
SupportsPersistentIdentifier = true,
SupportsSync = false
};
var capabilitiesJson = JsonSerializer.Serialize(capabilities);
var (capResult, capStatus) = await _proxyService.PostJsonAsync("Sessions/Capabilities/Full", capabilitiesJson, authHeaders);
if (capStatus == 204 || capStatus == 200)
{
_logger.LogDebug("✓ Session capabilities posted after auth ({StatusCode})", capStatus);
}
else
{
_logger.LogDebug("⚠ Session capabilities returned {StatusCode} after auth", capStatus);
}
}
catch (Exception ex)
{
_logger.LogDebug(ex, "Failed to post session capabilities after auth");
}
});
}
} }
else else
{ {