diff --git a/allstarr/Controllers/JellyfinController.cs b/allstarr/Controllers/JellyfinController.cs index a082c53..505f117 100644 --- a/allstarr/Controllers/JellyfinController.cs +++ b/allstarr/Controllers/JellyfinController.cs @@ -1670,6 +1670,64 @@ public class JellyfinController : ControllerBase #region Playback Session Reporting + #region Session Management + + /// + /// Reports session capabilities. Required for Jellyfin to track active sessions. + /// Handles both POST (with body) and GET (query params only) methods. + /// + [HttpPost("Sessions/Capabilities")] + [HttpPost("Sessions/Capabilities/Full")] + [HttpGet("Sessions/Capabilities")] + [HttpGet("Sessions/Capabilities/Full")] + public async Task ReportCapabilities() + { + try + { + var method = Request.Method; + var queryString = Request.QueryString.HasValue ? Request.QueryString.Value : ""; + + _logger.LogInformation("📡 Session capabilities reported - Method: {Method}, Query: {Query}", method, queryString); + _logger.LogInformation("Headers: {Headers}", + string.Join(", ", Request.Headers.Where(h => h.Key.Contains("Auth", StringComparison.OrdinalIgnoreCase) || h.Key.Contains("Device", StringComparison.OrdinalIgnoreCase) || h.Key.Contains("Client", StringComparison.OrdinalIgnoreCase)) + .Select(h => $"{h.Key}={h.Value}"))); + + // Forward to Jellyfin with query string and headers + var endpoint = $"Sessions/Capabilities{queryString}"; + + // Read body if present (POST requests) + string body = "{}"; + if (method == "POST" && Request.ContentLength > 0) + { + Request.EnableBuffering(); + using (var reader = new StreamReader(Request.Body, System.Text.Encoding.UTF8, detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true)) + { + body = await reader.ReadToEndAsync(); + } + Request.Body.Position = 0; + _logger.LogInformation("Capabilities body: {Body}", body); + } + + var (result, statusCode) = await _proxyService.PostJsonAsync(endpoint, body, Request.Headers); + + if (statusCode == 204 || statusCode == 200) + { + _logger.LogInformation("✓ Session capabilities forwarded to Jellyfin ({StatusCode})", statusCode); + } + else + { + _logger.LogWarning("⚠ Jellyfin returned {StatusCode} for capabilities", statusCode); + } + + return NoContent(); + } + catch (Exception ex) + { + _logger.LogError(ex, "Failed to report session capabilities"); + return StatusCode(500); + } + } + /// /// Reports playback start. Handles both local and external tracks. /// For local tracks, forwards to Jellyfin. For external tracks, logs locally. diff --git a/allstarr/Middleware/WebSocketProxyMiddleware.cs b/allstarr/Middleware/WebSocketProxyMiddleware.cs index fdcde9c..254b322 100644 --- a/allstarr/Middleware/WebSocketProxyMiddleware.cs +++ b/allstarr/Middleware/WebSocketProxyMiddleware.cs @@ -22,10 +22,21 @@ public class WebSocketProxyMiddleware _next = next; _settings = settings.Value; _logger = logger; + + _logger.LogInformation("🔧 WebSocketProxyMiddleware initialized - Jellyfin URL: {Url}", _settings.Url); } public async Task InvokeAsync(HttpContext context) { + // Log ALL requests to /socket path for debugging + if (context.Request.Path.StartsWithSegments("/socket", StringComparison.OrdinalIgnoreCase)) + { + _logger.LogInformation("📡 Request to /socket path - IsWebSocketRequest: {IsWs}, Method: {Method}, Headers: {Headers}", + context.WebSockets.IsWebSocketRequest, + context.Request.Method, + string.Join(", ", context.Request.Headers.Select(h => $"{h.Key}={h.Value}"))); + } + // Check if this is a WebSocket request to /socket if (context.Request.Path.StartsWithSegments("/socket", StringComparison.OrdinalIgnoreCase) && context.WebSockets.IsWebSocketRequest)