mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-11 17:30:00 +02:00
feat: implement IPTV player screen for live, VOD, and series streams with integrated controls and state management.
This commit is contained in:
1 parent
4ea1c2978b
commit
66a020416e
5 files changed
+79
-104
No files matched your search
+28
-24
@@ -147,28 +147,34 @@ Handler createStreamInitHandler() {
|
||||
'-hls_segment_filename', '${tempDir.path}/segment_%03d.ts',
|
||||
];
|
||||
|
||||
// Detect if this is likely a Live TV stream (TS format) vs VOD (MKV/MP4)
|
||||
// Video encoding: Always transcode for HLS timestamp compatibility
|
||||
// Using ultrafast preset for Live TV to minimize latency
|
||||
final isLiveStream = iptvUrl.endsWith('.ts') || iptvUrl.contains('/live/');
|
||||
|
||||
// Video encoding strategy:
|
||||
// - Live TV (.ts): Try to COPY video stream for maximum quality (no re-encoding loss)
|
||||
// - VOD (MKV/MP4): Transcode to ensure timestamp compatibility
|
||||
if (isLiveStream) {
|
||||
// LIVE TV: Copy video stream for best quality
|
||||
// Most IPTV live streams are already H.264 and browser-compatible
|
||||
ffmpegArgs.addAll([
|
||||
'-c:v', 'copy', // No re-encoding = best quality
|
||||
]);
|
||||
} else {
|
||||
// VOD: Transcode to fix timestamp issues in MKV containers
|
||||
// LIVE TV: Fast transcoding with ultrafast preset
|
||||
ffmpegArgs.addAll([
|
||||
'-c:v', 'libx264',
|
||||
'-preset', 'fast', // Better quality than ultrafast
|
||||
'-tune', 'film', // Better for movies/series
|
||||
'-profile:v', 'high', // High profile for better compression
|
||||
'-level', '4.1', // Supports 1080p60
|
||||
'-preset', 'ultrafast',
|
||||
'-tune', 'zerolatency',
|
||||
'-profile:v', 'main', // Main profile for better quality than baseline
|
||||
'-level', '4.0',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
'-g', '48', // 2s GOP
|
||||
'-g', '48', // 2s GOP at 24fps
|
||||
'-b:v', '4000k', // 4Mbps for good quality live
|
||||
'-maxrate', '4500k',
|
||||
'-bufsize', '8000k',
|
||||
]);
|
||||
} else {
|
||||
// VOD: Higher quality transcoding
|
||||
ffmpegArgs.addAll([
|
||||
'-c:v', 'libx264',
|
||||
'-preset', 'fast',
|
||||
'-tune', 'film',
|
||||
'-profile:v', 'high',
|
||||
'-level', '4.1',
|
||||
'-pix_fmt', 'yuv420p',
|
||||
'-g', '48',
|
||||
]);
|
||||
|
||||
if (quality == 'low') {
|
||||
@@ -179,26 +185,24 @@ Handler createStreamInitHandler() {
|
||||
'-bufsize', '3000k',
|
||||
]);
|
||||
} else {
|
||||
// High quality (default) - 6Mbps for 1080p
|
||||
ffmpegArgs.addAll([
|
||||
'-b:v', '6000k',
|
||||
'-maxrate', '8000k',
|
||||
'-bufsize', '12000k',
|
||||
'-crf', '18', // High quality CRF
|
||||
'-crf', '18',
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
// Audio: Always transcode to AAC (browsers don't support AC3/DTS)
|
||||
// Higher bitrate for better audio quality
|
||||
// Audio: Always transcode to AAC
|
||||
ffmpegArgs.addAll([
|
||||
'-c:a', 'aac',
|
||||
'-b:a', '192k', // Higher audio bitrate
|
||||
'-ar', '48000', // Standard sample rate
|
||||
'-b:a', '192k',
|
||||
'-ar', '48000',
|
||||
'-ac', '2',
|
||||
|
||||
playlistPath,
|
||||
]);
|
||||
|
||||
ffmpegArgs.add(playlistPath);
|
||||
|
||||
// Start FFmpeg process
|
||||
final process = await Process.start('ffmpeg', ffmpegArgs);
|
||||
|
||||
@@ -120,9 +120,14 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
|
||||
final encodedUrl = Uri.encodeComponent(streamUrl);
|
||||
final streamTypeParam = widget.streamType == StreamType.live ? 'live' : 'vod';
|
||||
var playerSrc = playerSettings.playerType == PlayerType.lite
|
||||
? 'player_lite.html?url=$encodedUrl&type=$streamTypeParam'
|
||||
: 'player.html?url=$encodedUrl';
|
||||
|
||||
// Force player choice based on stream type:
|
||||
// - Live TV: Player Lite (simple TS playback with mpegts.js)
|
||||
// - VOD/Series: Player Standard (fuller controls, HLS support)
|
||||
final isLiveTV = widget.streamType == StreamType.live;
|
||||
var playerSrc = isLiveTV
|
||||
? 'player_lite.html?url=$encodedUrl&type=live'
|
||||
: 'player.html?url=$encodedUrl&type=vod';
|
||||
|
||||
if (startTimeOverride != null) {
|
||||
playerSrc += '&t=$startTimeOverride';
|
||||
@@ -271,10 +276,13 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final settings = ref.watch(iptvSettingsProvider);
|
||||
// Use stream type to determine player UI:
|
||||
// - Live TV: Lite player UI (minimal overlay)
|
||||
// - VOD/Series: Standard player UI (full controls)
|
||||
final isLiveTV = widget.streamType == StreamType.live;
|
||||
|
||||
// LITE PLAYER MODE: Simple iframe with native HTML5 controls, minimal Flutter overlay
|
||||
if (settings.playerType == PlayerType.lite) {
|
||||
// LITE PLAYER MODE for Live TV: Simple iframe with native HTML5 controls, minimal Flutter overlay
|
||||
if (isLiveTV) {
|
||||
return Scaffold(
|
||||
backgroundColor: Colors.black,
|
||||
body: MouseRegion(
|
||||
|
||||
@@ -57,20 +57,18 @@ class XtreamService {
|
||||
_currentPlaylist = playlist;
|
||||
}
|
||||
|
||||
/// Generate stream URL for live TV via FFmpeg transcoding
|
||||
/// Generate stream URL for live TV
|
||||
///
|
||||
/// Returns the start URL for FFmpeg transcoding endpoint.
|
||||
/// The endpoint will start FFmpeg and return the local HLS URL.
|
||||
/// Returns the proxy URL for direct MPEG-TS streaming.
|
||||
/// Player will use mpegts.js for playback (no server-side transcoding).
|
||||
String getLiveStreamUrl(String streamId) {
|
||||
if (_currentPlaylist == null) throw Exception('No playlist configured');
|
||||
|
||||
// Use .ts format for direct MPEG-TS streaming (FFmpeg handles this better)
|
||||
// Direct .ts URL for MPEG-TS streaming
|
||||
final iptvUrl = '${_currentPlaylist!.dns}/live/${_currentPlaylist!.username}/${_currentPlaylist!.password}/$streamId.ts';
|
||||
|
||||
// Use FFmpeg transcoding endpoint
|
||||
final baseUrl = html.window.location.origin;
|
||||
final encodedUrl = Uri.encodeComponent(iptvUrl);
|
||||
return '$baseUrl/api/stream/$streamId?url=$encodedUrl';
|
||||
// Use proxy for direct streaming (no FFmpeg transcoding)
|
||||
return _wrapWithProxy(iptvUrl);
|
||||
}
|
||||
|
||||
/// Generate stream URL for VOD (movies)
|
||||
|
||||
@@ -34,26 +34,9 @@ class StreamingSettingsTab extends ConsumerWidget {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Player Type Section
|
||||
_buildSectionCard(
|
||||
context: context,
|
||||
title: 'Lecteur Vidéo',
|
||||
icon: Icons.play_circle_outline,
|
||||
children: [
|
||||
_buildDropdownTile<PlayerType>(
|
||||
title: 'Type de lecteur',
|
||||
subtitle: 'Changer si vous rencontrez des problèmes de lecture',
|
||||
value: settings.playerType,
|
||||
items: PlayerType.values,
|
||||
onChanged: (v) => notifier.setPlayerType(v!),
|
||||
labelBuilder: (v) => switch (v) {
|
||||
PlayerType.standard => 'Standard (Avancé)',
|
||||
PlayerType.lite => 'Lite (Compatibilité max)',
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
// Note: Player type is now automatically determined:
|
||||
// - Live TV uses Player Lite (MPEG-TS)
|
||||
// - Movies/Series use Player Standard
|
||||
|
||||
// Quality Section
|
||||
_buildSectionCard(
|
||||
|
||||
+28
-46
@@ -361,59 +361,41 @@
|
||||
log('Stream type: ' + streamType);
|
||||
const lowerUrl = streamUrl.toLowerCase();
|
||||
|
||||
// SPECIAL: /api/stream/ endpoint handling - ALWAYS returns JSON with HLS playlist
|
||||
if (streamUrl.includes('/api/stream/')) {
|
||||
// Both Live TV and VOD use /api/stream/ which returns JSON with HLS playlist URL
|
||||
log((streamType === 'live' ? 'Live TV' : 'VOD') + ' via /api/stream/ - fetching HLS...');
|
||||
showLoading(streamType === 'live' ? 'Connecting to live stream...' : 'Initializing stream...');
|
||||
// LIVE TV: Simple MPEG-TS playback with mpegts.js
|
||||
if (streamType === 'live') {
|
||||
log('Live TV mode: Using MPEG-TS direct playback');
|
||||
showLoading('Connecting to live stream...');
|
||||
|
||||
try {
|
||||
const response = await fetch(streamUrl);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.playlist) {
|
||||
const playlistUrl = window.location.origin + data.playlist;
|
||||
log('Got HLS playlist: ' + playlistUrl);
|
||||
showLoading('Loading HLS stream...');
|
||||
await playWithHLS(playlistUrl);
|
||||
return;
|
||||
} else {
|
||||
showError('Invalid stream response');
|
||||
return;
|
||||
}
|
||||
} catch (err) {
|
||||
log('Error fetching playlist: ' + err);
|
||||
showError('Failed to initialize stream');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Use type parameter if available, otherwise detect from URL
|
||||
if (streamType === 'vod') {
|
||||
// VOD content - use direct HTML5 playback
|
||||
log('VOD mode: Using direct playback');
|
||||
await playDirect(streamUrl);
|
||||
} else if (streamType === 'live') {
|
||||
// Live TV - check for HLS first, then MPEG-TS
|
||||
// Check for HLS first (.m3u8)
|
||||
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
|
||||
log('Live HLS detected');
|
||||
await playWithHLS(streamUrl);
|
||||
} else {
|
||||
log('Live MPEG-TS mode');
|
||||
await playWithMpegTS(streamUrl);
|
||||
// Default: MPEG-TS with mpegts.js
|
||||
log('Using mpegts.js for MPEG-TS');
|
||||
await playWithMpegTS(streamUrl, true); // isLive=true
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// VOD: Direct HTML5 playback with proxy
|
||||
if (streamType === 'vod') {
|
||||
log('VOD mode: Using direct playback');
|
||||
showLoading('Loading video...');
|
||||
await playDirect(streamUrl);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fallback: detect from URL pattern
|
||||
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
|
||||
await playWithHLS(streamUrl);
|
||||
} else if (lowerUrl.includes('.mp4') || lowerUrl.includes('.mkv') ||
|
||||
lowerUrl.includes('/movie/') || lowerUrl.includes('/series/')) {
|
||||
await playDirect(streamUrl);
|
||||
} else if (lowerUrl.includes('.ts') || lowerUrl.includes('/live/')) {
|
||||
await playWithMpegTS(streamUrl, true);
|
||||
} else {
|
||||
// Fallback: detect from URL pattern
|
||||
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
|
||||
await playWithHLS(streamUrl);
|
||||
} else if (lowerUrl.includes('.mp4') || lowerUrl.includes('.mkv') ||
|
||||
lowerUrl.includes('/movie/') || lowerUrl.includes('/series/')) {
|
||||
await playDirect(streamUrl);
|
||||
} else if (lowerUrl.includes('.ts') || lowerUrl.includes('/live/')) {
|
||||
await playWithMpegTS(streamUrl);
|
||||
} else {
|
||||
await playDirect(streamUrl);
|
||||
}
|
||||
await playDirect(streamUrl);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user