Fix: TV stream auto-reconnect and stability improvements

- Added MPV reconnect options (stream-lavf-o reconnect parameters)
- Implemented auto-reconnect on stream completion for live TV
- Added watchdog timer to detect stalled streams
- Max 5 reconnect attempts with exponential backoff
This commit is contained in:
Michael committed 2025-12-10 10:44:29 +01:00
1 parent ccda497236
commit 1f66fde536
1 file changed
+64 -3
@@ -59,6 +59,9 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
Timer? _clockTimer;
Timer? _controlsTimer; // Auto-hide timer
Timer? _liveWatchdog; // Watchdog for live stream reconnection
int _reconnectAttempts = 0;
static const int _maxReconnectAttempts = 5;
String _currentTime = '';
void _resetControlsTimer() {
@@ -107,14 +110,19 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
// - Disable SSL verification to prevent handshake drops
// - Enable reconnect
// Reliable Network Configuration (Fix for 30s cutoff)
// Reliable Network Configuration (Fix for stream cutoff)
(_player.platform as dynamic)?.setProperty('cache', 'yes');
(_player.platform as dynamic)?.setProperty('cache-secs', '120'); // Cache up to 2 minutes
(_player.platform as dynamic)?.setProperty('demuxer-max-bytes', '50000000'); // 50MB max buffer
(_player.platform as dynamic)?.setProperty('network-timeout', '120'); // 2 minutes timeout
(_player.platform as dynamic)?.setProperty('network-timeout', '60'); // 1 minute timeout
(_player.platform as dynamic)?.setProperty('tls-verify', 'no'); // Ignore SSL errors
(_player.platform as dynamic)?.setProperty('http-header-fields', 'User-Agent: XtremFlow/1.0');
// Additional stability options for live streams
(_player.platform as dynamic)?.setProperty('stream-lavf-o', 'reconnect=1,reconnect_streamed=1,reconnect_delay_max=5');
(_player.platform as dynamic)?.setProperty('force-seekable', 'yes');
(_player.platform as dynamic)?.setProperty('demuxer-lavf-o', 'live_start_index=-1');
_controller = VideoController(_player);
// Listen to player state
@@ -173,6 +181,14 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
});
});
// Listen for stream completion (live streams shouldn't complete - means it stopped)
_player.stream.completed.listen((completed) {
if (completed && mounted && widget.streamType == StreamType.live) {
debugPrint('MediaKitPlayer: Live stream completed unexpectedly, attempting reconnect...');
_attemptReconnect();
}
});
// Lock to landscape for video playback
SystemChrome.setPreferredOrientations([
DeviceOrientation.landscapeLeft,
@@ -202,6 +218,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
void dispose() {
_clockTimer?.cancel();
_controlsTimer?.cancel();
_liveWatchdog?.cancel();
_player.dispose();
_xtreamService?.dispose();
@@ -217,6 +234,45 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
super.dispose();
}
/// Attempt to reconnect a live stream that stopped
void _attemptReconnect() {
if (!mounted) return;
if (_reconnectAttempts >= _maxReconnectAttempts) {
setState(() {
_errorMessage = 'Flux interrompu après plusieurs tentatives';
_isLoading = false;
});
return;
}
_reconnectAttempts++;
debugPrint('MediaKitPlayer: Reconnect attempt $_reconnectAttempts/$_maxReconnectAttempts');
// Delay before reconnect to avoid hammering the server
Future.delayed(Duration(seconds: 2 * _reconnectAttempts), () {
if (mounted) {
_loadStream();
}
});
}
/// Start watchdog timer for live streams
void _startLiveWatchdog() {
_liveWatchdog?.cancel();
if (widget.streamType != StreamType.live) return;
// Check every 30 seconds if stream is still playing
_liveWatchdog = Timer.periodic(const Duration(seconds: 30), (_) {
if (!mounted) return;
// If we're supposedly playing but position hasn't changed, reconnect
if (!_isPlaying && !_isLoading && _errorMessage == null) {
debugPrint('MediaKitPlayer: Watchdog detected stalled stream, reconnecting...');
_attemptReconnect();
}
});
}
// Update build method to show clock in top bar
// ...
@@ -287,8 +343,13 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> {
setState(() {
_isLoading = false;
// Reset fallback flag on success? No, keep it if it works to avoid future crashes on same stream
_reconnectAttempts = 0; // Reset reconnect counter on success
});
// Start watchdog for live streams
if (widget.streamType == StreamType.live) {
_startLiveWatchdog();
}
} catch (e) {
debugPrint('MediaKitPlayer error: $e');