feat: implement IPTV player using a web-based lite player and a dedicated Flutter screen.

This commit is contained in:
Michael committed 2025-12-14 00:01:50 +01:00
1 parent 211699d17e
commit b589a834a0
3 files changed
+107 -31

No files matched your search

+71 -28
View File
@@ -108,6 +108,7 @@
const urlParams = new URLSearchParams(window.location.search);
let streamUrl = urlParams.get('url');
const startTime = parseFloat(urlParams.get('t') || '0');
const streamType = urlParams.get('type') || ''; // 'live' or 'vod'
function log(msg) {
console.log('[LitePlayer] ' + msg);
@@ -289,33 +290,60 @@
showLoading('Loading video...');
video.preload = 'auto';
let playStarted = false;
video.addEventListener('waiting', () => {
log('Buffering...');
showLoading('Buffering...');
});
video.addEventListener('playing', hideLoading);
video.addEventListener('canplay', () => {
video.addEventListener('playing', () => {
playStarted = true;
hideLoading();
video.play().catch(e => log('Autoplay prevented: ' + e));
}, { once: true });
video.addEventListener('error', () => {
const err = video.error;
let msg = 'Playback error';
if (err) {
switch (err.code) {
case MediaError.MEDIA_ERR_NETWORK: msg = 'Network error'; break;
case MediaError.MEDIA_ERR_DECODE: msg = 'Video decode error'; break;
case MediaError.MEDIA_ERR_SRC_NOT_SUPPORTED: msg = 'Format not supported'; break;
}
}
showError(msg);
});
const onCanPlay = () => {
playStarted = true;
hideLoading();
// FORCE AUDIO SETTINGS
video.muted = false;
video.volume = 1.0;
video.play().catch(e => {
log('Autoplay prevented: ' + e);
// If autoplay prevents sound/video, we should show a UI to user but native controls are there
// Try playing muted if failed
video.muted = true;
video.play().then(() => {
log('Autoplay started muted');
showError('Click to unmute'); // User can verify context
}).catch(e2 => log('Autoplay failed completely: ' + e2));
});
};
const onError = async () => {
if (!playStarted) {
log('Direct playback failed, trying fallbacks...');
video.removeEventListener('canplay', onCanPlay);
video.removeEventListener('error', onError);
// Fallback 1: HLS (common for streams)
log('Trying HLS fallback...');
if (await playWithHLS(url)) return;
// Fallback 2: MpegTS (for TS streams)
log('Comparing HLS failed, trying MpegTS fallback...');
if (await playWithMpegTS(url)) return;
showError('Format not supported. Try Standard player mode.');
}
};
video.addEventListener('canplay', onCanPlay, { once: true });
video.addEventListener('error', onError, { once: true });
video.src = url;
video.load();
return true;
}
@@ -326,20 +354,35 @@
}
log('Stream URL: ' + streamUrl);
log('Stream type: ' + streamType);
const lowerUrl = streamUrl.toLowerCase();
// Route to appropriate player
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
log('Detected HLS stream');
await playWithHLS(streamUrl);
} else if (lowerUrl.includes('.ts') || lowerUrl.includes('/live/') || lowerUrl.includes('mpegts')) {
log('Detected MPEG-TS (via extension or URL)');
log('Using mpegts.js');
await playWithMpegTS(streamUrl);
} else {
// Try direct playback (mp4, etc.)
log('Trying direct playback');
// 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
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
log('Live HLS detected');
await playWithHLS(streamUrl);
} else {
log('Live MPEG-TS mode');
await playWithMpegTS(streamUrl);
}
} 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);
}
}
}