feat: introduce Xtream Codes API service for IPTV content access and streaming.

This commit is contained in:
Michael committed 2025-12-14 21:48:37 +01:00
1 parent 66a020416e
commit b0de7bce86
4 files changed
+39 -29

No files matched your search

+11 -9
View File
@@ -152,18 +152,20 @@ Handler createStreamInitHandler() {
final isLiveStream = iptvUrl.endsWith('.ts') || iptvUrl.contains('/live/'); final isLiveStream = iptvUrl.endsWith('.ts') || iptvUrl.contains('/live/');
if (isLiveStream) { if (isLiveStream) {
// LIVE TV: Fast transcoding with ultrafast preset // LIVE TV: Transcode to H.264 for browser compatibility
// Source may be HEVC/H.265 which browsers don't support
// Using superfast preset for minimal CPU usage
ffmpegArgs.addAll([ ffmpegArgs.addAll([
'-c:v', 'libx264', '-c:v', 'libx264',
'-preset', 'ultrafast', '-preset', 'superfast', // Fast encoding to prevent buffering
'-tune', 'zerolatency', '-tune', 'zerolatency', // Minimize latency
'-profile:v', 'main', // Main profile for better quality than baseline '-profile:v', 'baseline', // Maximum browser compatibility
'-level', '4.0', '-level', '3.1',
'-pix_fmt', 'yuv420p', '-pix_fmt', 'yuv420p',
'-g', '48', // 2s GOP at 24fps '-g', '30', // Keyframe every second at 30fps
'-b:v', '4000k', // 4Mbps for good quality live '-b:v', '2500k', // 2.5Mbps - balance quality/speed
'-maxrate', '4500k', '-maxrate', '3000k',
'-bufsize', '8000k', '-bufsize', '5000k',
]); ]);
} else { } else {
// VOD: Higher quality transcoding // VOD: Higher quality transcoding
@@ -60,14 +60,14 @@ class XtreamService {
/// Generate stream URL for live TV /// Generate stream URL for live TV
/// ///
/// Returns the proxy URL for direct MPEG-TS streaming. /// Returns the proxy URL for direct MPEG-TS streaming.
/// Player will use mpegts.js for playback (no server-side transcoding). /// Player uses mpegts.js for playback.
String getLiveStreamUrl(String streamId) { String getLiveStreamUrl(String streamId) {
if (_currentPlaylist == null) throw Exception('No playlist configured'); if (_currentPlaylist == null) throw Exception('No playlist configured');
// Direct .ts URL for MPEG-TS streaming // Direct .ts URL for MPEG-TS streaming
final iptvUrl = '${_currentPlaylist!.dns}/live/${_currentPlaylist!.username}/${_currentPlaylist!.password}/$streamId.ts'; final iptvUrl = '${_currentPlaylist!.dns}/live/${_currentPlaylist!.username}/${_currentPlaylist!.password}/$streamId.ts';
// Use proxy for direct streaming (no FFmpeg transcoding) // Use proxy for direct streaming
return _wrapWithProxy(iptvUrl); return _wrapWithProxy(iptvUrl);
} }
+9
View File
@@ -541,6 +541,15 @@
// Determine stream type and try appropriate player // Determine stream type and try appropriate player
const lowerUrl = streamUrl.toLowerCase(); const lowerUrl = streamUrl.toLowerCase();
const ext = urlParams.get('ext') ? urlParams.get('ext').toLowerCase() : ''; const ext = urlParams.get('ext') ? urlParams.get('ext').toLowerCase() : '';
const streamTypeParam = urlParams.get('type');
// VOD via proxy (/api/xtream/) - use direct playback
if (streamUrl.includes('/api/xtream/') && streamTypeParam === 'vod') {
log('VOD via proxy - using direct playback');
showLoading('Loading video...');
await playDirect(streamUrl);
return;
}
// HLS streams first // HLS streams first
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) { if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
+17 -18
View File
@@ -252,20 +252,28 @@
mpegts.getLoggingControl().enableError = true; mpegts.getLoggingControl().enableError = true;
} }
// Configure player differently for VOD vs Live // Optimized configuration for stable Live TV playback
const playerConfig = { const playerConfig = {
enableWorker: true, enableWorker: true,
enableStashBuffer: !isLive, // Enable for VOD, disable for live enableStashBuffer: true, // Enable buffering for stability
stashInitialSize: isLive ? 384 * 1024 : 512 * 1024, stashInitialSize: 1024 * 1024, // 1MB initial buffer
autoCleanupSourceBuffer: true, autoCleanupSourceBuffer: true,
fixAudioTimestampGap: isLive, // Only for live streams autoCleanupMaxBackwardDuration: 60, // Keep 1 min of buffer
liveBufferLatencyChasing: isLive, // Disable for VOD autoCleanupMinBackwardDuration: 30, // Cleanup after 30s
fixAudioTimestampGap: true, // Fix audio sync issues
liveBufferLatencyChasing: false, // Disable for stability (prevents skipping)
liveSync: false, // Don't auto-sync to edge
lazyLoad: false, // Load immediately
lazyLoadMaxDuration: 0,
lazyLoadRecoverDuration: 0,
}; };
const player = mpegts.createPlayer({ const player = mpegts.createPlayer({
type: 'mpegts', type: 'mpegts',
isLive: isLive, // Use parameter to enable/disable seeking isLive: isLive,
url: url, url: url,
hasAudio: true,
hasVideo: true,
}, playerConfig); }, playerConfig);
player.attachMediaElement(video); player.attachMediaElement(video);
@@ -361,20 +369,11 @@
log('Stream type: ' + streamType); log('Stream type: ' + streamType);
const lowerUrl = streamUrl.toLowerCase(); const lowerUrl = streamUrl.toLowerCase();
// LIVE TV: Simple MPEG-TS playback with mpegts.js // LIVE TV: Direct MPEG-TS playback with mpegts.js
if (streamType === 'live') { if (streamType === 'live') {
log('Live TV mode: Using MPEG-TS direct playback'); log('Live TV mode - using mpegts.js direct');
showLoading('Connecting to live stream...'); showLoading('Connecting to live stream...');
await playWithMpegTS(streamUrl, true);
// Check for HLS first (.m3u8)
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
log('Live HLS detected');
await playWithHLS(streamUrl);
} else {
// Default: MPEG-TS with mpegts.js
log('Using mpegts.js for MPEG-TS');
await playWithMpegTS(streamUrl, true); // isLive=true
}
return; return;
} }