diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index 95f0ae2..e76ba4b 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -140,6 +140,12 @@ class _PlayerScreenState extends ConsumerState { streamUrl = service.getLiveStreamUrlTs(currentStreamId); } + // STABILIZATION: Lock background requests during stream init + service.isPlaybackLoading = true; + Future.delayed(const Duration(seconds: 10), () { + service.isPlaybackLoading = false; + }); + _currentStreamUrl = streamUrl; final encodedUrl = Uri.encodeComponent(streamUrl); diff --git a/lib/features/iptv/services/xtream_service.dart b/lib/features/iptv/services/xtream_service.dart index 211931a..4e87ae5 100644 --- a/lib/features/iptv/services/xtream_service.dart +++ b/lib/features/iptv/services/xtream_service.dart @@ -15,6 +15,13 @@ class XtreamService { PlaylistConfig? _currentPlaylist; + /// Flag to indicate that a playback is currently loading + /// Used to throttle background requests (EPG, images) + bool isPlaybackLoading = false; + + /// Minimum delay for background requests when playback is loading + static const _throttleDelay = Duration(seconds: 8); + XtreamService() { _dio = Dio( BaseOptions( @@ -616,6 +623,11 @@ class XtreamService { Future> getShortEpg(String streamId) async { if (_currentPlaylist == null) throw Exception('No playlist configured'); + if (isPlaybackLoading) { + debugPrint('[XtreamService] Throttling short EPG for $streamId'); + await Future.delayed(_throttleDelay); + } + try { final response = await _dio.get( _wrapWithProxy(_currentPlaylist!.apiBaseUrl), @@ -653,6 +665,11 @@ class XtreamService { Future getShortEPG(String streamId) async { if (_currentPlaylist == null) throw Exception('No playlist configured'); + if (isPlaybackLoading) { + debugPrint('[XtreamService] Throttling short EPG metadata for $streamId'); + await Future.delayed(_throttleDelay); + } + try { final response = await _dio.get( _wrapWithProxy(_currentPlaylist!.apiBaseUrl), diff --git a/web/player.html b/web/player.html index 97067a4..153262e 100644 --- a/web/player.html +++ b/web/player.html @@ -253,15 +253,15 @@ const isTurbo = urlParams.get('turbo') === 'true'; const hls = new Hls({ - maxBufferLength: isTurbo ? 30 : 45, + maxBufferLength: isTurbo ? 45 : 50, enableWorker: true, - liveSyncDurationCount: isTurbo ? 2 : 4, + liveSyncDurationCount: isTurbo ? 3 : 4, // Improved stability // Turbo-Start specific startLevel: isTurbo ? 0 : -1, initialLiveManifestSize: 1, abrEwmaDefaultEstimate: 500000, - manifestLoadingRetryDelay: 500, - levelLoadingRetryDelay: 500 + manifestLoadingRetryDelay: 800, + levelLoadingRetryDelay: 800 }); window.hlsInstance = hls; hls.loadSource(url); diff --git a/web/player_lite.html b/web/player_lite.html index eaaf4ff..e9e9397 100644 --- a/web/player_lite.html +++ b/web/player_lite.html @@ -245,18 +245,18 @@ const isTurbo = urlParams.get('turbo') === 'true'; const hls = new Hls({ - maxBufferLength: isTurbo ? 30 : 60, - maxMaxBufferLength: isTurbo ? 60 : 120, + maxBufferLength: isTurbo ? 45 : 60, + maxMaxBufferLength: isTurbo ? 90 : 120, enableWorker: true, - liveSyncDurationCount: isTurbo ? 2 : 5, // Aggressive sync for Turbo - liveMaxLatencyDurationCount: isTurbo ? 10 : 15, + liveSyncDurationCount: isTurbo ? 3 : 5, // Improved stability + liveMaxLatencyDurationCount: isTurbo ? 15 : 20, lowLatencyMode: true, // Turbo-Start specific - startLevel: isTurbo ? 0 : -1, // Lowest bitrate first + startLevel: isTurbo ? 0 : -1, initialLiveManifestSize: 1, - abrEwmaDefaultEstimate: 500000, // Start with conservative estimate - manifestLoadingRetryDelay: 500, - levelLoadingRetryDelay: 500 + abrEwmaDefaultEstimate: 500000, + manifestLoadingRetryDelay: 800, + levelLoadingRetryDelay: 800 }); window.hlsInstance = hls; @@ -292,9 +292,12 @@ url: url }, { enableWorker: true, - stashInitialSize: isTurbo ? 128 * 1024 : 512 * 1024, // Tiny stash for Turbo zapping - enableStashBuffer: !isTurbo, // Direct pass-through for Live TV Turbo - lazyLoad: false + stashInitialSize: isTurbo ? 512 * 1024 : 1024 * 1024, // 512KB for better stability + enableStashBuffer: true, // Re-enable stash buffer for stability + liveBufferLatencyChasing: true, // Correct drift + liveBufferLatencyChasingOnPaused: true, + lazyLoad: false, + maxStashSize: 1024 * 1024 // 1MB Max }); player.attachMediaElement(video); diff --git a/web/player_mobile.html b/web/player_mobile.html index 344664c..9f37be8 100644 --- a/web/player_mobile.html +++ b/web/player_mobile.html @@ -231,18 +231,18 @@ const isTurbo = urlParams.get('turbo') === 'true'; const hls = new Hls({ - maxBufferLength: isTurbo ? 30 : 60, - maxMaxBufferLength: isTurbo ? 60 : 120, - liveSyncDurationCount: isTurbo ? 2 : 5, - liveMaxLatencyDurationCount: isTurbo ? 10 : 15, + maxBufferLength: isTurbo ? 45 : 60, + maxMaxBufferLength: isTurbo ? 90 : 120, + liveSyncDurationCount: isTurbo ? 3 : 5, // Improved stability + liveMaxLatencyDurationCount: isTurbo ? 15 : 20, enableWorker: true, - lowLatencyMode: !isTurbo, // Prioritize stability unless Turbo requested + lowLatencyMode: true, // Turbo-Start specific startLevel: isTurbo ? 0 : -1, initialLiveManifestSize: 1, abrEwmaDefaultEstimate: 500000, - manifestLoadingRetryDelay: 500, - levelLoadingRetryDelay: 500 + manifestLoadingRetryDelay: 800, + levelLoadingRetryDelay: 800 }); hls.loadSource(streamUrl); hls.attachMedia(video);