From d196b1dce0397387c7a88cf2120884788201d06e Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Wed, 10 Dec 2025 11:07:35 +0100 Subject: [PATCH] Feature: Mark movies/episodes as watched at 80% progress - Added _checkAndMarkWatched() method in NativePlayerScreen - Moved watch tracking from immediate play to 80% completion - Added seriesId/season/episodeNum params to NativePlayerScreen - Removed premature markWatched calls from movies/series tabs --- .../screens/mobile_series_detail_screen.dart | 5 ++- .../iptv/screens/native_player_screen.dart | 43 +++++++++++++++++++ .../iptv/widgets/mobile_movies_tab.dart | 3 +- 3 files changed, 48 insertions(+), 3 deletions(-) diff --git a/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart b/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart index 3649f42..89532be 100644 --- a/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart +++ b/lib/mobile/features/iptv/screens/mobile_series_detail_screen.dart @@ -264,7 +264,7 @@ class _MobileSeriesDetailScreenState extends ConsumerState? channels; final int initialIndex; + + // For series episodes (to track watch history) + final dynamic seriesId; + final int? season; + final int? episodeNum; const NativePlayerScreen({ super.key, @@ -37,6 +42,9 @@ class NativePlayerScreen extends ConsumerStatefulWidget { this.containerExtension, this.channels, this.initialIndex = 0, + this.seriesId, + this.season, + this.episodeNum, }); @override @@ -63,6 +71,7 @@ class _NativePlayerScreenState extends ConsumerState { int _reconnectAttempts = 0; static const int _maxReconnectAttempts = 5; String _currentTime = ''; + bool _hasMarkedAsWatched = false; // Flag to mark watched only once at 80% void _resetControlsTimer() { _controlsTimer?.cancel(); @@ -145,6 +154,9 @@ class _NativePlayerScreenState extends ConsumerState { _player.stream.position.listen((position) { if (mounted && !_isSeeking) { setState(() => _position = position); + + // Check if 80% of content watched (for VOD/Series only) + _checkAndMarkWatched(position); } }); @@ -256,6 +268,37 @@ class _NativePlayerScreenState extends ConsumerState { }); } + /// Check if 80% of content watched and mark as watched + void _checkAndMarkWatched(Duration position) { + // Skip if already marked, live TV, or no duration + if (_hasMarkedAsWatched) return; + if (widget.streamType == StreamType.live) return; + if (_duration.inSeconds <= 0) return; + + final progress = position.inSeconds / _duration.inSeconds; + + if (progress >= 0.80) { + _hasMarkedAsWatched = true; + debugPrint('MediaKitPlayer: 80% reached, marking as watched'); + + if (widget.streamType == StreamType.vod) { + // Mark movie as watched + ref.read(mobileWatchHistoryProvider.notifier).markMovieWatched(widget.streamId); + } else if (widget.streamType == StreamType.series && + widget.seriesId != null && + widget.season != null && + widget.episodeNum != null) { + // Mark episode as watched + final episodeKey = MobileWatchHistory.episodeKey( + widget.seriesId, + widget.season!, + widget.episodeNum!, + ); + ref.read(mobileWatchHistoryProvider.notifier).markEpisodeWatched(episodeKey); + } + } + } + /// Start watchdog timer for live streams void _startLiveWatchdog() { _liveWatchdog?.cancel(); diff --git a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart index 7c5dcfc..883810e 100644 --- a/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart +++ b/lib/mobile/features/iptv/widgets/mobile_movies_tab.dart @@ -160,8 +160,7 @@ class _MobileMoviesTabState extends ConsumerState with Automati } void _playMovie(Movie movie) { - ref.read(mobileWatchHistoryProvider.notifier).markMovieWatched(movie.streamId); - + // Watch progress is tracked in player at 80% completion Navigator.push( context, MaterialPageRoute(