feat: Implement Xtream Codes IPTV streaming, VOD transcoding, and content browsing UI with a dedicated web player.

This commit is contained in:
Michael committed 2025-12-15 21:18:36 +01:00
1 parent 6d69617e40
commit 3f9a88c09a
4 files changed
+142 -21

No files matched your search

@@ -501,6 +501,62 @@ class XtreamService {
}
}
/// Get VOD (movie) info including duration
///
/// Returns detailed movie information including duration in seconds
Future<int?> getVodDuration(String vodId) async {
if (_currentPlaylist == null) return null;
try {
final response = await _dio.get(
_wrapWithProxy(_currentPlaylist!.apiBaseUrl),
queryParameters: {
'username': _currentPlaylist!.username,
'password': _currentPlaylist!.password,
'action': 'get_vod_info',
'vod_id': vodId,
},
options: Options(extra: _cacheOptions.toExtra()),
);
final data = response.data as Map<String, dynamic>;
// Try to get duration from movie_properties or info
final movieData = data['movie_data'] as Map<String, dynamic>?;
final info = data['info'] as Map<String, dynamic>?;
// Duration can be in different formats: seconds (int), "HH:MM:SS", or minutes
String? durationStr = movieData?['duration']?.toString() ??
info?['duration']?.toString() ??
info?['duration_secs']?.toString();
if (durationStr == null || durationStr.isEmpty) return null;
// Parse duration - could be "01:30:00" format or seconds
if (durationStr.contains(':')) {
// HH:MM:SS format
final parts = durationStr.split(':');
if (parts.length == 3) {
final hours = int.tryParse(parts[0]) ?? 0;
final minutes = int.tryParse(parts[1]) ?? 0;
final seconds = int.tryParse(parts[2]) ?? 0;
return hours * 3600 + minutes * 60 + seconds;
} else if (parts.length == 2) {
// MM:SS format
final minutes = int.tryParse(parts[0]) ?? 0;
final seconds = int.tryParse(parts[1]) ?? 0;
return minutes * 60 + seconds;
}
}
// Try parsing as seconds directly
return int.tryParse(durationStr);
} catch (e) {
// Duration is optional, don't fail
return null;
}
}
/// Get short EPG for a specific stream
///
/// Returns "Now" and "Next" program info
+17 -2
View File
@@ -149,9 +149,24 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
return originalUrl;
}
void _playMovie(Movie movie) {
Future<void> _playMovie(Movie movie) async {
ref.read(watchHistoryProvider.notifier).markMovieWatched(movie.streamId);
// Fetch actual duration from API if not available in movie data
Duration? movieDuration;
if (movie.durationSecs != null && movie.durationSecs! > 0) {
movieDuration = Duration(seconds: movie.durationSecs!);
} else {
// Try to fetch from API
final service = ref.read(xtreamServiceProvider(widget.playlist));
final durationSecs = await service.getVodDuration(movie.streamId);
if (durationSecs != null && durationSecs > 0) {
movieDuration = Duration(seconds: durationSecs);
}
}
if (!mounted) return;
Navigator.push(
context,
MaterialPageRoute(
@@ -161,7 +176,7 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
playlist: widget.playlist,
streamType: StreamType.vod,
containerExtension: movie.containerExtension ?? 'mp4',
duration: movie.durationSecs != null ? Duration(seconds: movie.durationSecs!) : null,
duration: movieDuration,
),
),
);