perf(proxy): enable Keep-Alive and playlist caching to fix 5s zap delay; tuned mobile player for micro-cuts

This commit is contained in:
Michael committed 2026-03-10 22:01:52 +01:00
1 parent 79fae261aa
commit 2ef1db8a68
2 files changed
+49 -10

No files matched your search

+41 -2
View File
@@ -12,13 +12,42 @@ class ProxyHandler {
final AppDatabase _db; final AppDatabase _db;
final http.Client _client = http.Client(); final http.Client _client = http.Client();
final Map<String, (PlaylistConfig, DateTime)> _playlistCache = {};
static const _cacheDuration = Duration(minutes: 5);
static const _allowedHeaders = [ static const _allowedHeaders = [
'content-type', 'content-type',
'content-range', 'content-range',
'accept-ranges', 'accept-ranges',
'cache-control', 'cache-control',
'server',
'date',
]; ];
static const _allowedRequestHeaders = [
'user-agent',
'accept',
'range',
'referer',
];
Future<PlaylistConfig?> _getCachedPlaylist(Request request) async {
// Basic caching to avoid DB overhead on every video segment
final now = DateTime.now();
final cacheKey = 'global_playlist'; // Currently app has one primary playlist per user/global
if (_playlistCache.containsKey(cacheKey)) {
final (cached, expiry) = _playlistCache[cacheKey]!;
if (now.isBefore(expiry)) return cached;
}
final playlist = await _getPlaylist(request);
if (playlist != null) {
_playlistCache[cacheKey] = (playlist, now.add(_cacheDuration));
}
return playlist;
}
ProxyHandler(this._getPlaylist, this._db); ProxyHandler(this._getPlaylist, this._db);
/// Extract token from Authorization header or cookie /// Extract token from Authorization header or cookie
@@ -102,7 +131,7 @@ class ProxyHandler {
if (!isStaticAsset) { if (!isStaticAsset) {
// For API calls, enforce domain allowlist // For API calls, enforce domain allowlist
final playlist = await _getPlaylist(request); final playlist = await _getCachedPlaylist(request);
if (playlist == null) { if (playlist == null) {
return Response.forbidden( return Response.forbidden(
'No active playlist configuration found to validate request'); 'No active playlist configuration found to validate request');
@@ -123,7 +152,7 @@ class ProxyHandler {
'User-Agent': 'VLC/3.0.18 LibVLC/3.0.18', 'User-Agent': 'VLC/3.0.18 LibVLC/3.0.18',
'Accept': '*/*', 'Accept': '*/*',
'Accept-Encoding': 'identity', 'Accept-Encoding': 'identity',
'Connection': 'close', // Forced close for better IPTV server compatibility 'Keep-Alive': 'timeout=30, max=100', // Request persistent connection
}; };
// Forward Range header if present // Forward Range header if present
@@ -134,6 +163,14 @@ class ProxyHandler {
try { try {
print('[Proxy] Forwarding to: $targetUrl'); print('[Proxy] Forwarding to: $targetUrl');
final proxyRequest = http.Request(request.method, targetUrl); final proxyRequest = http.Request(request.method, targetUrl);
// Forward safe request headers
for (final header in _allowedRequestHeaders) {
if (request.headers.containsKey(header)) {
proxyHeaders[header] = request.headers[header]!;
}
}
proxyRequest.headers.addAll(proxyHeaders); proxyRequest.headers.addAll(proxyHeaders);
proxyRequest.followRedirects = true; proxyRequest.followRedirects = true;
@@ -150,6 +187,8 @@ class ProxyHandler {
// Build response headers from source response // Build response headers from source response
final responseHeaders = <String, String>{ final responseHeaders = <String, String>{
'access-control-allow-origin': '*', 'access-control-allow-origin': '*',
'connection': 'keep-alive',
'keep-alive': 'timeout=30',
}; };
// Forward specific safe headers // Forward specific safe headers
+8 -8
View File
@@ -233,18 +233,18 @@
const hls = new Hls({ const hls = new Hls({
maxBufferLength: 60, maxBufferLength: 60,
maxMaxBufferLength: 120, maxMaxBufferLength: 120,
liveSyncDurationCount: 3, // Reduced for faster initial sync on mobile liveSyncDurationCount: 5, // Balanced sync
liveMaxLatencyDurationCount: 15, liveMaxLatencyDurationCount: 20,
enableWorker: true, enableWorker: true,
lowLatencyMode: true, lowLatencyMode: false, // Disabled for stability against micro-cuts
// Robustness // Robustness
nudgeOffset: 0.2, nudgeOffset: 0.6, // More aggressive jump over corrupted frames
nudgeMaxRetries: 10, nudgeMaxRetries: 25,
maxLiveSyncPlaybackRate: 1.1, maxLiveSyncPlaybackRate: 1.2,
// Turbo-Start specific // Turbo-Start specific
startLevel: 0, // Force lowest level for instant start startLevel: 0,
initialLiveManifestSize: 1, initialLiveManifestSize: 1,
abrEwmaDefaultEstimate: 500000, abrEwmaDefaultEstimate: 700000,
manifestLoadingRetryDelay: 500, manifestLoadingRetryDelay: 500,
levelLoadingRetryDelay: 500 levelLoadingRetryDelay: 500
}); });