From e5af0ec044a42a68bd78ccaf7295fe2dc89b1d0f Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Mon, 8 Dec 2025 22:12:42 +0100 Subject: [PATCH] feat: implement IPTV player screen with playback controls and VOD seeking --- lib/features/iptv/screens/player_screen.dart | 29 ++++++------------- verify_vod_seek_final.txt | Bin 0 -> 82860 bytes 2 files changed, 9 insertions(+), 20 deletions(-) create mode 100644 verify_vod_seek_final.txt diff --git a/lib/features/iptv/screens/player_screen.dart b/lib/features/iptv/screens/player_screen.dart index 75f3d18..4062001 100644 --- a/lib/features/iptv/screens/player_screen.dart +++ b/lib/features/iptv/screens/player_screen.dart @@ -179,31 +179,18 @@ class _PlayerScreenState extends ConsumerState { }); // Check if we are using server-side transcoding for VOD/Series - // If so, we must reload the player because we can't seek in a piped stream directly via JS final settings = ref.read(iptvSettingsProvider); - // We now force transcoding (API stream) for ALL VOD content to ensure audio compatibility. - // So isTranscoding is effectively true for all non-live content. - final isTranscoding = widget.streamType != StreamType.live; + // Only reload for seeking if we are rigorously Transcoding (FFmpeg pipe) + // If we are in Direct Proxy mode (default), we can seek natively via JS. + final bool usingTranscoding = settings.transcodingMode == TranscodingMode.forced; - if (isTranscoding) { + if (usingTranscoding) { debugPrint('Seeking in transcoded VOD - reloading player at ${value.round()}s'); - // Reload player with new start time - // This will trigger _initializePlayer with the current position effectively - // But _initializePlayer reads from _currentPosition? No, it reads from provider OR starts at 0. - // We need to force it to start at 'value'. - // Best way is to just call _initializePlayer which builds the URL. - // But _initializePlayer checks logic. - - // Actually _initializePlayer uses: startTime = positions.getPosition(_contentId); - // So if we save the position first, it might pick it up? - // Let's pass the seek time explicitly to a reload method or updating state. - - // Simplest: Update the provider, then re-init. ref.read(playbackPositionsProvider.notifier).savePosition(_contentId, value, _totalDuration); _initializePlayer(startTimeOverride: value); } else { - // Direct play or Live - standard seeking + // Direct play (Proxy) or Live - standard seeking happens in the browser via Range headers _sendMessage({'type': 'seek', 'value': value}); } @@ -432,10 +419,12 @@ class _PlayerScreenState extends ConsumerState { final settings = ref.read(iptvSettingsProvider); // We force transcoding/proxy usage for all VOD/Series now - final isTranscoding = widget.streamType != StreamType.live; + final bool usingTranscoding = settings.transcodingMode == TranscodingMode.forced; final encodedHlsUrl = Uri.encodeComponent(hlsUrl); - final playerUrl = (!isTranscoding && startTime > 0) + // Pass startTime to player.html if we are NOT using server-side transcoding (Direct/Proxy mode) + // For Transcoding, the server handles start time via the 'start' query param in hlsUrl. + final playerUrl = (!usingTranscoding && startTime > 0) ? 'player.html?url=$encodedHlsUrl&startTime=${startTime.toStringAsFixed(0)}' : 'player.html?url=$encodedHlsUrl'; diff --git a/verify_vod_seek_final.txt b/verify_vod_seek_final.txt new file mode 100644 index 0000000000000000000000000000000000000000..13fc6ba69a545b5de976657ac9286ba4ab9f3671 GIT binary patch literal 82860 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 K*eW_hoBto8?580B literal 0 HcmV?d00001