From f5f70f73a6ba29be22d95d2a69f83e79497cb587 Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 21:47:10 +0100 Subject: [PATCH] feat: Implement initial server with API, streaming, Xtream proxy, and static file serving. --- bin/server.dart | 147 ++++++++++++--------- lib/features/iptv/widgets/live_tv_tab.dart | 6 +- verify_ui_changes.txt | Bin 0 -> 82154 bytes verify_vod_seeking.txt | Bin 0 -> 84288 bytes 4 files changed, 87 insertions(+), 66 deletions(-) create mode 100644 verify_ui_changes.txt create mode 100644 verify_vod_seeking.txt diff --git a/bin/server.dart b/bin/server.dart index 8dffec7..4c68f4e 100644 --- a/bin/server.dart +++ b/bin/server.dart @@ -342,72 +342,93 @@ Handler _createXtreamProxyHandler() { /// Stream video file using HttpClient with Range support for seeking Future _streamVideoFile(Uri targetUrl, String? rangeHeader) async { - try { - final client = HttpClient(); - // Increase timeouts for large video files - client.connectionTimeout = const Duration(seconds: 60); - client.idleTimeout = const Duration(minutes: 5); // Keep connection alive longer - - final req = await client.getUrl(targetUrl); - 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'); // Don't compress video - - // Forward the Range header for seeking support - if (rangeHeader != null && rangeHeader.isNotEmpty) { - // Ensure we request bytes - if (!rangeHeader.startsWith('bytes=')) { - rangeHeader = 'bytes=$rangeHeader'; + final maxRedirects = 5; + var currentUrl = targetUrl; + + for (var i = 0; i < maxRedirects; i++) { + try { + final client = HttpClient(); + // Increase timeouts for large video files + client.connectionTimeout = const Duration(seconds: 60); + client.idleTimeout = const Duration(minutes: 5); + + final req = await client.getUrl(currentUrl); + req.followRedirects = false; // We handle redirects manually to preserve Range header + + 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 + 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(); + + // Handle Redirects (301, 302, 303, 307, 308) + if (response.statusCode >= 300 && response.statusCode < 400) { + final location = response.headers.value(HttpHeaders.locationHeader); + if (location != null) { + print('Redirecting stream to: $location'); + currentUrl = Uri.parse(location); + await response.drain(); // Drain body to release connection + continue; // Loop to next request + } + } + + // Get content type from response + final contentType = response.headers.contentType?.mimeType ?? 'video/mp4'; + + // Build response headers for optimal streaming/buffering + final responseHeaders = { + 'content-type': contentType, + 'access-control-allow-origin': '*', + 'accept-ranges': 'bytes', + 'cache-control': 'no-cache', + 'connection': 'keep-alive', + }; + + // Add Content-Length if available + if (response.contentLength > 0) { + responseHeaders['content-length'] = response.contentLength.toString(); + } + + // Add Content-Range if this is a partial response (206) + final contentRange = response.headers.value(HttpHeaders.contentRangeHeader); + if (contentRange != null) { + responseHeaders[HttpHeaders.contentRangeHeader] = contentRange; + } + + // Propagate Accept-Ranges + responseHeaders[HttpHeaders.acceptRangesHeader] = 'bytes'; + + // Return appropriate status code + return Response( + response.statusCode, + body: response, + headers: responseHeaders, + ); + + } catch (e) { + print('Video streaming exception: $e'); + if (i == maxRedirects - 1) { + return Response.internalServerError( + body: jsonEncode({'error': 'Video streaming error', 'message': e.toString()}), + headers: {'content-type': 'application/json'}, + ); } - req.headers.set(HttpHeaders.rangeHeader, rangeHeader); - print('Video streaming with Range: $rangeHeader'); } - - // Disable auto-decompression to strictly proxy the stream - client.autoUncompress = false; - - final response = await req.close(); - - // Get content type from response - final contentType = response.headers.contentType?.mimeType ?? 'video/mp4'; - - // Build response headers for optimal streaming/buffering - final responseHeaders = { - 'content-type': contentType, - 'access-control-allow-origin': '*', - 'accept-ranges': 'bytes', - 'cache-control': 'no-cache', // Allow browser to cache but revalidate - 'connection': 'keep-alive', - }; - - // Add Content-Length if available (critical for seeking) - if (response.contentLength > 0) { - responseHeaders['content-length'] = response.contentLength.toString(); - } - - // Add Content-Range if this is a partial response (206) - final contentRange = response.headers.value(HttpHeaders.contentRangeHeader); - if (contentRange != null) { - responseHeaders[HttpHeaders.contentRangeHeader] = contentRange; - } - - // Propagate Accept-Ranges - responseHeaders[HttpHeaders.acceptRangesHeader] = 'bytes'; - - // Return appropriate status code (200 for full, 206 for partial) - return Response( - response.statusCode, - body: response, - headers: responseHeaders, - ); - } catch (e) { - print('Video streaming error: $e'); - return Response.internalServerError( - body: jsonEncode({'error': 'Video streaming error', 'message': e.toString()}), - headers: {'content-type': 'application/json'}, - ); } + + return Response.internalServerError(body: 'Too many redirects'); } /// Rewrite URLs in M3U8 playlist to go through the proxy diff --git a/lib/features/iptv/widgets/live_tv_tab.dart b/lib/features/iptv/widgets/live_tv_tab.dart index 1991c14..b962650 100644 --- a/lib/features/iptv/widgets/live_tv_tab.dart +++ b/lib/features/iptv/widgets/live_tv_tab.dart @@ -264,11 +264,11 @@ class _LiveTVTabState extends ConsumerState begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ - AppColors.surface, - AppColors.surface.withOpacity(0.8), + AppColors.surfaceVariant, + AppColors.surfaceVariant.withOpacity(0.8), ], ), - border: Border.all(color: AppColors.border.withOpacity(0.5)), + border: Border.all(color: Colors.white.withOpacity(0.2)), borderRadius: BorderRadius.circular(12), ), padding: const EdgeInsets.all(16), diff --git a/verify_ui_changes.txt b/verify_ui_changes.txt new file mode 100644 index 0000000000000000000000000000000000000000..8f7ed2a0f441078a914f84f4ce2cdb6930e2e660 GIT binary patch literal 82154 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 W-&4EDgns1Nn|`p3t)eru`Tqf77BrXu literal 0 HcmV?d00001 diff --git a/verify_vod_seeking.txt b/verify_vod_seeking.txt new file mode 100644 index 0000000000000000000000000000000000000000..c4e8cfad7fa42254e374ecebdad40904a8238d0e GIT binary patch literal 84288 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 IGDDmHA3{n)ng9R* literal 0 HcmV?d00001