mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-21 14:08:04 +02:00
feat: Add IPTV player screen with FFmpeg transcoding, resume playback, and dedicated movie/series tabs.
This commit is contained in:
1 parent
5c4488864b
commit
5671afbd26
6 files changed
+475
-87
No files matched your search
@@ -3,8 +3,10 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'dart:html' as html;
|
||||
import 'dart:ui_web' as ui_web;
|
||||
import 'dart:convert';
|
||||
import 'dart:async';
|
||||
import 'package:http/http.dart' as http;
|
||||
import '../providers/xtream_provider.dart';
|
||||
import '../providers/playback_positions_provider.dart';
|
||||
import '../widgets/epg_overlay.dart';
|
||||
import '../../../core/models/playlist_config.dart';
|
||||
|
||||
@@ -37,14 +39,51 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
String? _errorMessage;
|
||||
String? _statusMessage;
|
||||
late String _viewId;
|
||||
late String _contentId;
|
||||
bool _isInitialized = false;
|
||||
bool _isLoading = true;
|
||||
StreamSubscription? _messageSubscription;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_viewId = 'iptv-player-${widget.streamId}-${DateTime.now().millisecondsSinceEpoch}';
|
||||
// Content ID for resume feature
|
||||
_contentId = widget.streamType == StreamType.live
|
||||
? '' // Live streams don't need resume
|
||||
: '${widget.streamType.name}_${widget.streamId}';
|
||||
_initializePlayer();
|
||||
_setupMessageListener();
|
||||
}
|
||||
|
||||
void _setupMessageListener() {
|
||||
// Listen for postMessage from player.html iframe
|
||||
_messageSubscription = html.window.onMessage.listen((event) {
|
||||
try {
|
||||
final data = event.data;
|
||||
if (data is Map) {
|
||||
final type = data['type'];
|
||||
if (type == 'playback_position' && _contentId.isNotEmpty) {
|
||||
final currentTime = (data['currentTime'] as num).toDouble();
|
||||
final duration = (data['duration'] as num).toDouble();
|
||||
ref.read(playbackPositionsProvider.notifier).savePosition(
|
||||
_contentId, currentTime, duration
|
||||
);
|
||||
} else if (type == 'playback_ended' && _contentId.isNotEmpty) {
|
||||
// Clear position when video ends
|
||||
ref.read(playbackPositionsProvider.notifier).clearPosition(_contentId);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugPrint('Error handling postMessage: $e');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_messageSubscription?.cancel();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initializePlayer() async {
|
||||
@@ -145,9 +184,21 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
_statusMessage = 'Loading player...';
|
||||
});
|
||||
|
||||
// Get resume position for VOD/Series
|
||||
double startTime = 0;
|
||||
if (widget.streamType != StreamType.live && _contentId.isNotEmpty) {
|
||||
final positions = ref.read(playbackPositionsProvider);
|
||||
startTime = positions.getPosition(_contentId);
|
||||
if (startTime > 0) {
|
||||
debugPrint('PlayerScreen: Resuming from ${startTime.toStringAsFixed(0)}s');
|
||||
}
|
||||
}
|
||||
|
||||
// Register the HTML view with an iframe pointing to our player.html
|
||||
final encodedHlsUrl = Uri.encodeComponent(hlsUrl);
|
||||
final playerUrl = 'player.html?url=$encodedHlsUrl';
|
||||
final playerUrl = startTime > 0
|
||||
? 'player.html?url=$encodedHlsUrl&startTime=${startTime.toStringAsFixed(0)}'
|
||||
: 'player.html?url=$encodedHlsUrl';
|
||||
|
||||
debugPrint('PlayerScreen: Player URL: $playerUrl');
|
||||
|
||||
|
||||
Reference in new issue
Block a user