From d92686883c9d40a9234d72742c6361faebd2754a Mon Sep 17 00:00:00 2001 From: Michael SCHAL Date: Sun, 28 Dec 2025 11:00:33 +0100 Subject: [PATCH] feat: Implement native video player screen using media_kit with optimized playback settings for various stream types. --- .../iptv/services/xtream_service_mobile.dart | 65 ++++++++++--------- .../iptv/screens/lite_player_screen.dart | 3 + .../iptv/screens/native_player_screen.dart | 27 +++++--- 3 files changed, 54 insertions(+), 41 deletions(-) diff --git a/lib/features/iptv/services/xtream_service_mobile.dart b/lib/features/iptv/services/xtream_service_mobile.dart index bd9b237..6f47d4c 100644 --- a/lib/features/iptv/services/xtream_service_mobile.dart +++ b/lib/features/iptv/services/xtream_service_mobile.dart @@ -1,3 +1,4 @@ +import 'package:flutter/foundation.dart'; import 'package:dio/dio.dart'; import 'package:dio_cache_interceptor/dio_cache_interceptor.dart'; import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart'; @@ -440,30 +441,29 @@ class XtreamServiceMobile { if (query.isEmpty) return []; try { - final categoryMap = await getVodCategories(); + // Ensure data is loaded in cache + if (_cachedMoviesRaw == null) { + await getMoviesPaginated(limit: 1); + } - final response = await _dio.get( - _effectiveApiBaseUrl, - queryParameters: { - 'username': _currentPlaylist!.username, - 'password': _currentPlaylist!.password, - 'action': 'get_vod_streams', - }, - options: _getOptions(), - ); + final allMovies = _cachedMoviesRaw ?? []; + final categoryMap = _cachedVodCategories ?? await getVodCategories(); - if (response.data is! List) return []; - final List allMovies = response.data as List; final queryLower = query.toLowerCase(); - // Filter by search query + // Filter by search query (Local Cache) final List results = []; - for (final movieData in allMovies.where((m) { + + // Efficient filtering: check name matches, limited to 100 results + final matches = allMovies.where((m) { final name = (m['name']?.toString() ?? '').toLowerCase(); return name.contains(queryLower); - }).take(100)) { + }).take(100); + + for (final movieData in matches) { try { - final data = movieData as Map; + final data = + Map.from(movieData as Map); final categoryId = data['category_id']?.toString() ?? ''; data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized'; results.add(xm.Movie.fromJson(data)); @@ -473,6 +473,7 @@ class XtreamServiceMobile { } return results; } catch (e) { + debugPrint('Search error: $e'); return []; } } @@ -545,30 +546,29 @@ class XtreamServiceMobile { if (query.isEmpty) return []; try { - final categoryMap = await _getSeriesCategories(); + // Ensure data is loaded in cache + if (_cachedSeriesRaw == null) { + await getSeriesPaginated(limit: 1); + } - final response = await _dio.get( - _effectiveApiBaseUrl, - queryParameters: { - 'username': _currentPlaylist!.username, - 'password': _currentPlaylist!.password, - 'action': 'get_series', - }, - options: _getOptions(), - ); + final allSeries = _cachedSeriesRaw ?? []; + final categoryMap = + _cachedSeriesCategories ?? await _getSeriesCategories(); - if (response.data is! List) return []; - final List allSeries = response.data as List; final queryLower = query.toLowerCase(); - // Filter by search query + // Filter by search query (Local Cache) final List results = []; - for (final seriesData in allSeries.where((s) { + + final matches = allSeries.where((s) { final name = (s['name']?.toString() ?? '').toLowerCase(); return name.contains(queryLower); - }).take(100)) { + }).take(100); + + for (final seriesData in matches) { try { - final data = seriesData as Map; + final data = + Map.from(seriesData as Map); final categoryId = data['category_id']?.toString() ?? ''; data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized'; results.add(xm.Series.fromJson(data)); @@ -578,6 +578,7 @@ class XtreamServiceMobile { } return results; } catch (e) { + debugPrint('Search series error: $e'); return []; } } diff --git a/lib/mobile/features/iptv/screens/lite_player_screen.dart b/lib/mobile/features/iptv/screens/lite_player_screen.dart index 295ed4b..70cc3cb 100644 --- a/lib/mobile/features/iptv/screens/lite_player_screen.dart +++ b/lib/mobile/features/iptv/screens/lite_player_screen.dart @@ -104,6 +104,9 @@ class _LitePlayerScreenState extends ConsumerState state == AppLifecycleState.inactive || state == AppLifecycleState.detached) { _controller?.pause(); + if (mounted) { + Navigator.of(context).pop(); + } } } diff --git a/lib/mobile/features/iptv/screens/native_player_screen.dart b/lib/mobile/features/iptv/screens/native_player_screen.dart index 22ea39c..7f678df 100644 --- a/lib/mobile/features/iptv/screens/native_player_screen.dart +++ b/lib/mobile/features/iptv/screens/native_player_screen.dart @@ -164,8 +164,8 @@ class _NativePlayerScreenState extends ConsumerState debugPrint( 'MediaKitPlayer: Applying LIVE optimization profile (STABILITY)', ); - (_player.platform as dynamic) - ?.setProperty('cache-secs', '15'); // 15s cache + (_player.platform as dynamic)?.setProperty( + 'cache-secs', '10'); // Reduced to 10s for faster startup (_player.platform as dynamic)?.setProperty( 'demuxer-max-bytes', '32000000', @@ -177,10 +177,9 @@ class _NativePlayerScreenState extends ConsumerState // Ensure temp files are cleaned up (_player.platform as dynamic)?.setProperty('cache-unlink-files', 'yes'); - // ALWAYS wait for buffer fill to prevent glitches - (_player.platform as dynamic)?.setProperty('cache-pause-initial', 'yes'); - (_player.platform as dynamic) - ?.setProperty('cache-pause-wait', '6'); // Wait 6s max + // Startup Speed: Disable initial cache pause to allow immediate playback + (_player.platform as dynamic)?.setProperty('cache-pause-initial', 'no'); + // (_player.platform as dynamic)?.setProperty('cache-pause-wait', '3'); } else { // VOD PROFILE: Max stability, large buffer // VOD PROFILE: Max stability, moderate buffer (reduced from 100MB to fix startup heap issues) @@ -224,7 +223,7 @@ class _NativePlayerScreenState extends ConsumerState // Removed: force-seekable and live_start_index=-1 which caused rollbacks (_player.platform as dynamic)?.setProperty( 'demuxer-lavf-o', - 'analyzeduration=5000000,probesize=2000000', + 'analyzeduration=2000000,probesize=1000000', ); // Optimized for performance and stability on progressive & interlaced streams @@ -424,14 +423,24 @@ class _NativePlayerScreenState extends ConsumerState if (state == AppLifecycleState.paused || state == AppLifecycleState.inactive || state == AppLifecycleState.detached) { - _player.pause(); + if (widget.streamType == StreamType.live) { + // Force stop for Live TV + _player.stop(); + } else { + _player.pause(); + } + + // Exit player screen so user returns to dashboard on resume + if (mounted) { + Navigator.of(context).pop(); + } } } @override void dispose() { // Remove lifecycle observer - // WidgetsBinding.instance.removeObserver(this); + WidgetsBinding.instance.removeObserver(this); // Unregister keyboard handler HardwareKeyboard.instance.removeHandler(_handleKeyEvent);