mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
perf(proxy): enable Keep-Alive and playlist caching to fix 5s zap delay; tuned mobile player for micro-cuts
This commit is contained in:
1 parent
79fae261aa
commit
2ef1db8a68
2 files changed
+49
-10
No files matched your search
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in new issue
Block a user