From e37863351199a42039f73bfa01c7f2862def4e58 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 21:57:04 +0100 Subject: [PATCH] feat: Introduce a new Dart server application providing API, static assets, streaming, proxying, and a cleanup service. --- bin/server.dart | 32 +++++++++++++++++++++++++++----- verify_vod_seeking_logging.txt | Bin 0 -> 84406 bytes 2 files changed, 27 insertions(+), 5 deletions(-) create mode 100644 verify_vod_seeking_logging.txt diff --git a/bin/server.dart b/bin/server.dart index 4c68f4e..17efc6b 100644 --- a/bin/server.dart +++ b/bin/server.dart @@ -344,7 +344,11 @@ Handler _createXtreamProxyHandler() { Future _streamVideoFile(Uri targetUrl, String? rangeHeader) async { final maxRedirects = 5; var currentUrl = targetUrl; + final cookies = {}; // Cookie Jar for redirects + print('Stream Request: $targetUrl'); + if (rangeHeader != null) print('Client Requested Range: $rangeHeader'); + for (var i = 0; i < maxRedirects; i++) { try { final client = HttpClient(); @@ -353,26 +357,37 @@ Future _streamVideoFile(Uri targetUrl, String? rangeHeader) async { client.idleTimeout = const Duration(minutes: 5); final req = await client.getUrl(currentUrl); - req.followRedirects = false; // We handle redirects manually to preserve Range header + req.followRedirects = false; // We handle redirects manually req.headers.set('User-Agent', 'VLC/3.0.18 LibVLC/3.0.18'); req.headers.set('Accept', '*/*'); req.headers.set('Connection', 'keep-alive'); req.headers.set('Accept-Encoding', 'identity'); - // Forward the Range header for seeking support + // Apply collected cookies + if (cookies.isNotEmpty) { + final cookieHeader = cookies.entries.map((e) => '${e.key}=${e.value}').join('; '); + req.headers.set(HttpHeaders.cookieHeader, cookieHeader); + } + + // Forward request headers if (rangeHeader != null && rangeHeader.isNotEmpty) { if (!rangeHeader.startsWith('bytes=')) { rangeHeader = 'bytes=$rangeHeader'; } req.headers.set(HttpHeaders.rangeHeader, rangeHeader); - print('Video streaming with Range: $rangeHeader (Redirect $i)'); } client.autoUncompress = false; final response = await req.close(); + print('Upstream Response: ${response.statusCode} (Redirect $i)'); + // Collect Cookies from response + response.cookies.forEach((cookie) { + cookies[cookie.name] = cookie.value; + }); + // Handle Redirects (301, 302, 303, 307, 308) if (response.statusCode >= 300 && response.statusCode < 400) { final location = response.headers.value(HttpHeaders.locationHeader); @@ -384,10 +399,10 @@ Future _streamVideoFile(Uri targetUrl, String? rangeHeader) async { } } - // Get content type from response + // Get content type final contentType = response.headers.contentType?.mimeType ?? 'video/mp4'; - // Build response headers for optimal streaming/buffering + // Build response headers final responseHeaders = { 'content-type': contentType, 'access-control-allow-origin': '*', @@ -396,6 +411,13 @@ Future _streamVideoFile(Uri targetUrl, String? rangeHeader) async { 'connection': 'keep-alive', }; + // Checks for 206 vs 200 + if (response.statusCode == 200 && rangeHeader != null) { + print('WARNING: Upstream ignored Range header and returned 200. Seeking might fail.'); + } else if (response.statusCode == 206) { + print('SUCCESS: Upstream returned 206 Partial Content.'); + } + // Add Content-Length if available if (response.contentLength > 0) { responseHeaders['content-length'] = response.contentLength.toString(); diff --git a/verify_vod_seeking_logging.txt b/verify_vod_seeking_logging.txt new file mode 100644 index 0000000000000000000000000000000000000000..003afb25f2dd85189042ed5721946569b27b5f2a GIT binary patch literal 84406 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 BSAzfm literal 0 HcmV?d00001