mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-11 17:29:26 +02:00
feat: Implement native video player screen using media_kit with optimized playback settings for various stream types.
This commit is contained in:
1 parent
aaa0e9624d
commit
d92686883c
3 files changed
+54
-41
No files matched your search
@@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:dio/dio.dart';
|
import 'package:dio/dio.dart';
|
||||||
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
import 'package:dio_cache_interceptor/dio_cache_interceptor.dart';
|
||||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||||
@@ -440,30 +441,29 @@ class XtreamServiceMobile {
|
|||||||
if (query.isEmpty) return [];
|
if (query.isEmpty) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final categoryMap = await getVodCategories();
|
// Ensure data is loaded in cache
|
||||||
|
if (_cachedMoviesRaw == null) {
|
||||||
|
await getMoviesPaginated(limit: 1);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await _dio.get(
|
final allMovies = _cachedMoviesRaw ?? [];
|
||||||
_effectiveApiBaseUrl,
|
final categoryMap = _cachedVodCategories ?? await getVodCategories();
|
||||||
queryParameters: {
|
|
||||||
'username': _currentPlaylist!.username,
|
|
||||||
'password': _currentPlaylist!.password,
|
|
||||||
'action': 'get_vod_streams',
|
|
||||||
},
|
|
||||||
options: _getOptions(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data is! List) return [];
|
|
||||||
final List<dynamic> allMovies = response.data as List<dynamic>;
|
|
||||||
final queryLower = query.toLowerCase();
|
final queryLower = query.toLowerCase();
|
||||||
|
|
||||||
// Filter by search query
|
// Filter by search query (Local Cache)
|
||||||
final List<xm.Movie> results = [];
|
final List<xm.Movie> 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();
|
final name = (m['name']?.toString() ?? '').toLowerCase();
|
||||||
return name.contains(queryLower);
|
return name.contains(queryLower);
|
||||||
}).take(100)) {
|
}).take(100);
|
||||||
|
|
||||||
|
for (final movieData in matches) {
|
||||||
try {
|
try {
|
||||||
final data = movieData as Map<String, dynamic>;
|
final data =
|
||||||
|
Map<String, dynamic>.from(movieData as Map<String, dynamic>);
|
||||||
final categoryId = data['category_id']?.toString() ?? '';
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
results.add(xm.Movie.fromJson(data));
|
results.add(xm.Movie.fromJson(data));
|
||||||
@@ -473,6 +473,7 @@ class XtreamServiceMobile {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
debugPrint('Search error: $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -545,30 +546,29 @@ class XtreamServiceMobile {
|
|||||||
if (query.isEmpty) return [];
|
if (query.isEmpty) return [];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final categoryMap = await _getSeriesCategories();
|
// Ensure data is loaded in cache
|
||||||
|
if (_cachedSeriesRaw == null) {
|
||||||
|
await getSeriesPaginated(limit: 1);
|
||||||
|
}
|
||||||
|
|
||||||
final response = await _dio.get(
|
final allSeries = _cachedSeriesRaw ?? [];
|
||||||
_effectiveApiBaseUrl,
|
final categoryMap =
|
||||||
queryParameters: {
|
_cachedSeriesCategories ?? await _getSeriesCategories();
|
||||||
'username': _currentPlaylist!.username,
|
|
||||||
'password': _currentPlaylist!.password,
|
|
||||||
'action': 'get_series',
|
|
||||||
},
|
|
||||||
options: _getOptions(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if (response.data is! List) return [];
|
|
||||||
final List<dynamic> allSeries = response.data as List<dynamic>;
|
|
||||||
final queryLower = query.toLowerCase();
|
final queryLower = query.toLowerCase();
|
||||||
|
|
||||||
// Filter by search query
|
// Filter by search query (Local Cache)
|
||||||
final List<xm.Series> results = [];
|
final List<xm.Series> results = [];
|
||||||
for (final seriesData in allSeries.where((s) {
|
|
||||||
|
final matches = allSeries.where((s) {
|
||||||
final name = (s['name']?.toString() ?? '').toLowerCase();
|
final name = (s['name']?.toString() ?? '').toLowerCase();
|
||||||
return name.contains(queryLower);
|
return name.contains(queryLower);
|
||||||
}).take(100)) {
|
}).take(100);
|
||||||
|
|
||||||
|
for (final seriesData in matches) {
|
||||||
try {
|
try {
|
||||||
final data = seriesData as Map<String, dynamic>;
|
final data =
|
||||||
|
Map<String, dynamic>.from(seriesData as Map<String, dynamic>);
|
||||||
final categoryId = data['category_id']?.toString() ?? '';
|
final categoryId = data['category_id']?.toString() ?? '';
|
||||||
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
data['category_name'] = categoryMap[categoryId] ?? 'Uncategorized';
|
||||||
results.add(xm.Series.fromJson(data));
|
results.add(xm.Series.fromJson(data));
|
||||||
@@ -578,6 +578,7 @@ class XtreamServiceMobile {
|
|||||||
}
|
}
|
||||||
return results;
|
return results;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
debugPrint('Search series error: $e');
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,6 +104,9 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen>
|
|||||||
state == AppLifecycleState.inactive ||
|
state == AppLifecycleState.inactive ||
|
||||||
state == AppLifecycleState.detached) {
|
state == AppLifecycleState.detached) {
|
||||||
_controller?.pause();
|
_controller?.pause();
|
||||||
|
if (mounted) {
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -164,8 +164,8 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
|||||||
debugPrint(
|
debugPrint(
|
||||||
'MediaKitPlayer: Applying LIVE optimization profile (STABILITY)',
|
'MediaKitPlayer: Applying LIVE optimization profile (STABILITY)',
|
||||||
);
|
);
|
||||||
(_player.platform as dynamic)
|
(_player.platform as dynamic)?.setProperty(
|
||||||
?.setProperty('cache-secs', '15'); // 15s cache
|
'cache-secs', '10'); // Reduced to 10s for faster startup
|
||||||
(_player.platform as dynamic)?.setProperty(
|
(_player.platform as dynamic)?.setProperty(
|
||||||
'demuxer-max-bytes',
|
'demuxer-max-bytes',
|
||||||
'32000000',
|
'32000000',
|
||||||
@@ -177,10 +177,9 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
|||||||
// Ensure temp files are cleaned up
|
// Ensure temp files are cleaned up
|
||||||
(_player.platform as dynamic)?.setProperty('cache-unlink-files', 'yes');
|
(_player.platform as dynamic)?.setProperty('cache-unlink-files', 'yes');
|
||||||
|
|
||||||
// ALWAYS wait for buffer fill to prevent glitches
|
// Startup Speed: Disable initial cache pause to allow immediate playback
|
||||||
(_player.platform as dynamic)?.setProperty('cache-pause-initial', 'yes');
|
(_player.platform as dynamic)?.setProperty('cache-pause-initial', 'no');
|
||||||
(_player.platform as dynamic)
|
// (_player.platform as dynamic)?.setProperty('cache-pause-wait', '3');
|
||||||
?.setProperty('cache-pause-wait', '6'); // Wait 6s max
|
|
||||||
} else {
|
} else {
|
||||||
// VOD PROFILE: Max stability, large buffer
|
// VOD PROFILE: Max stability, large buffer
|
||||||
// VOD PROFILE: Max stability, moderate buffer (reduced from 100MB to fix startup heap issues)
|
// VOD PROFILE: Max stability, moderate buffer (reduced from 100MB to fix startup heap issues)
|
||||||
@@ -224,7 +223,7 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
|||||||
// Removed: force-seekable and live_start_index=-1 which caused rollbacks
|
// Removed: force-seekable and live_start_index=-1 which caused rollbacks
|
||||||
(_player.platform as dynamic)?.setProperty(
|
(_player.platform as dynamic)?.setProperty(
|
||||||
'demuxer-lavf-o',
|
'demuxer-lavf-o',
|
||||||
'analyzeduration=5000000,probesize=2000000',
|
'analyzeduration=2000000,probesize=1000000',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Optimized for performance and stability on progressive & interlaced streams
|
// Optimized for performance and stability on progressive & interlaced streams
|
||||||
@@ -424,14 +423,24 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen>
|
|||||||
if (state == AppLifecycleState.paused ||
|
if (state == AppLifecycleState.paused ||
|
||||||
state == AppLifecycleState.inactive ||
|
state == AppLifecycleState.inactive ||
|
||||||
state == AppLifecycleState.detached) {
|
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
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
// Remove lifecycle observer
|
// Remove lifecycle observer
|
||||||
// WidgetsBinding.instance.removeObserver(this);
|
WidgetsBinding.instance.removeObserver(this);
|
||||||
|
|
||||||
// Unregister keyboard handler
|
// Unregister keyboard handler
|
||||||
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
|
||||||
|
|||||||
Reference in new issue
Block a user