From 80320d0eae6c5c0e7fb6da36a23daeb44ad7375b Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Fri, 26 Dec 2025 13:32:26 +0100 Subject: [PATCH] feat: Add native video player screen using media_kit with extensive playback optimizations for live and VOD streams. --- .../iptv/screens/native_player_screen.dart | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/lib/mobile/features/iptv/screens/native_player_screen.dart b/lib/mobile/features/iptv/screens/native_player_screen.dart index 28c0fca..22ea39c 100644 --- a/lib/mobile/features/iptv/screens/native_player_screen.dart +++ b/lib/mobile/features/iptv/screens/native_player_screen.dart @@ -185,9 +185,9 @@ class _NativePlayerScreenState extends ConsumerState // VOD PROFILE: Max stability, large buffer // VOD PROFILE: Max stability, moderate buffer (reduced from 100MB to fix startup heap issues) debugPrint('MediaKitPlayer: Applying VOD optimization profile (FIX)'); - (_player.platform as dynamic)?.setProperty('cache-secs', '50'); + (_player.platform as dynamic)?.setProperty('cache-secs', '100'); (_player.platform as dynamic) - ?.setProperty('demuxer-max-bytes', '50000000'); // 50MB + ?.setProperty('demuxer-max-bytes', '100000000'); // 100MB (_player.platform as dynamic) ?.setProperty('demuxer-readahead-secs', '60'); (_player.platform as dynamic) @@ -356,13 +356,26 @@ class _NativePlayerScreenState extends ConsumerState }); }); - // Listen for stream completion (live streams shouldn't complete - means it stopped) + // Listen for stream completion _player.stream.completed.listen((completed) { - if (completed && mounted && widget.streamType == StreamType.live) { - debugPrint( - 'MediaKitPlayer: Live stream completed unexpectedly, attempting reconnect...', - ); - _attemptReconnect(); + if (completed && mounted) { + // Logic for Live TV + if (widget.streamType == StreamType.live) { + debugPrint( + 'MediaKitPlayer: Live stream completed unexpectedly, attempting reconnect...', + ); + _attemptReconnect(); + } + // Logic for VOD/Series: Check if really finished or cut off + else if (_duration.inSeconds > 0) { + final progress = _position.inSeconds / _duration.inSeconds; + if (progress < 0.95) { + debugPrint( + 'MediaKitPlayer: VOD stopped prematurely at ${(progress * 100).toStringAsFixed(1)}%, attempting reconnect...', + ); + _attemptReconnect(); + } + } } }); @@ -568,7 +581,12 @@ class _NativePlayerScreenState extends ConsumerState // Delay before reconnect to avoid hammering the server Future.delayed(Duration(seconds: 2 * _reconnectAttempts), () { if (mounted) { - _loadStream(); + // If VOD/Series, try to resume from current position + if (widget.streamType != StreamType.live && _position.inSeconds > 0) { + _loadStream(startAt: _position); + } else { + _loadStream(); + } } }); } @@ -651,7 +669,7 @@ class _NativePlayerScreenState extends ConsumerState // ... (existing initState) - Future _loadStream() async { + Future _loadStream({Duration? startAt}) async { try { if (_xtreamService == null) return; @@ -719,8 +737,15 @@ class _NativePlayerScreenState extends ConsumerState // Resume from saved position (VOD/Series only) if (widget.streamType != StreamType.live) { + // Priority 0: Explicit reconnect position (passed from _attemptReconnect) + if (startAt != null && startAt.inSeconds > 0) { + debugPrint( + 'MediaKitPlayer: Resuming after reconnect at: ${startAt.inSeconds}s', + ); + _seekToResume(startAt.inSeconds); + } // Priority 1: Explicit initial position passed from caller verification - if (widget.initialPosition != null && + else if (widget.initialPosition != null && widget.initialPosition!.inSeconds > 0) { debugPrint( 'MediaKitPlayer: Using explicit initial position: ${widget.initialPosition!.inSeconds}s',