Files
xtremflow/web/player.html
T

220 lines
8.7 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>XtremFlow Player</title>
<style>
body, html { margin: 0; padding: 0; width: 100%; height: 100%; background: #000; overflow: hidden; }
#video { width: 100%; height: 100%; object-fit: contain; }
#error-overlay { display: none; position: absolute; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10; font-family: sans-serif; }
.hidden { display: none !important; }
</style>
<script src="https://cdn.jsdelivr.net/npm/hls.js@1.4.12/dist/hls.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/mpegts.js@1.7.3/dist/mpegts.min.js"></script>
</head>
<body>
<video id="video" autoplay playsinline></video>
<div id="error-overlay" class="hidden">
<h2 id="error-title">Playback Error</h2>
<p id="error-msg">Unknown error</p>
</div>
<script>
const video = document.getElementById('video');
const errorOverlay = document.getElementById('error-overlay');
const errorMsg = document.getElementById('error-msg');
let hlsKey = null;
let mpegtsPlayer = null;
// Parse URL params
const urlParams = new URLSearchParams(window.location.search);
const streamUrl = urlParams.get('url');
const streamType = urlParams.get('type'); // 'live' or 'vod'
const startTime = parseFloat(urlParams.get('t')) || 0;
function log(msg) {
console.log(`[Player] ${msg}`);
}
function showError(msg) {
log('Error: ' + msg);
errorMsg.innerText = msg;
errorOverlay.classList.remove('hidden');
// Notify Flutter
window.parent.postMessage({ type: 'error', message: msg }, '*');
}
// --- Communication with Flutter ---
window.addEventListener('message', (event) => {
const data = event.data;
if (!data) return;
switch(data.type) {
case 'play': video.play().catch(e => log('Play fail: ' + e)); break;
case 'pause': video.pause(); break;
case 'seek':
if (Number.isFinite(data.value)) video.currentTime = data.value;
break;
case 'set_volume':
video.volume = Math.max(0, Math.min(1, data.value));
break;
}
});
// Report progress to Flutter
setInterval(() => {
if (!video.paused) {
window.parent.postMessage({
type: 'playback_position',
currentTime: video.currentTime,
duration: video.duration || 0
}, '*');
window.parent.postMessage({
type: 'playback_status',
status: 'playing'
}, '*');
} else {
window.parent.postMessage({
type: 'playback_status',
status: 'paused'
}, '*');
}
}, 1000);
// --- Player Logic ---
function initPlayer() {
if (!streamUrl) {
showError('No stream URL provided');
return;
}
if (data.playlist) {
const playlistUrl = window.location.origin + data.playlist;
log('Got HLS playlist: ' + playlistUrl);
await playWithHLS(playlistUrl);
return;
} else {
showError('Invalid stream response');
return;
}
} catch (err) {
log('Error fetching playlist: ' + err);
showError('Stream init failed');
return;
}
}
// Determine stream type and try appropriate player
const lowerUrl = streamUrl.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
if (lowerUrl.includes('.m3u8') || lowerUrl.includes('/hls/')) {
await playWithHLS(streamUrl);
return;
}
// MPEG-TS streams (live or forced TS via ext param)
// Checked BEFORE generic MP4/MKV to handle transcoded streams correctly
if (ext === '.ts' || lowerUrl.includes('.ts')) {
log('Detected MPEG-TS (via extension or URL)');
// Check if mpegts.js is supported (may not work on mobile)
if (mpegts.isSupported()) {
log('Using mpegts.js');
const success = await playWithMpegTS(streamUrl);
if (success) return;
log('mpegts.js failed, trying fallbacks...');
}
// Fallback 1: Try HLS.js for better mobile compatibility
log('Trying HLS.js as fallback for MPEG-TS');
const hlsSuccess = await playWithHLS(streamUrl);
if (hlsSuccess) return;
// Fallback 2: Try direct play as last resort
log('Trying direct play as last resort');
await playDirect(streamUrl);
return;
}
// MP4 and WebM - play directly (supports seeking via Range)
if (lowerUrl.includes('.mp4') || lowerUrl.includes('.webm') || lowerUrl.includes('.mov')) {
log('Detected MP4/WebM/MOV, playing directly with seek support');
await playDirect(streamUrl);
return;
}
// MKV, AVI - not natively supported, try anyway with fallback
if (lowerUrl.includes('.mkv') || lowerUrl.includes('.avi') || lowerUrl.includes('.wmv') || lowerUrl.includes('.flv')) {
log('Detected MKV/AVI - limited browser support, trying direct play...');
showLoading('Loading video (format may not support seeking)...');
// Try direct first, some browsers/servers may transcode on the fly
video.src = streamUrl;
let playStarted = false;
const onCanPlay = () => {
playStarted = true;
hideLoading();
video.play().catch(e => {
log('Autoplay prevented: ' + e);
window.parent.postMessage({ type: 'playback_status', status: 'paused' }, '*');
});
};
const onError = async () => {
if (!playStarted) {
log('Direct play failed for MKV/AVI, trying HLS fallback...');
video.removeEventListener('canplay', onCanPlay);
video.removeEventListener('error', onError);
// Try HLS as fallback (in case URL was actually HLS disguised)
const hlsSuccess = await playWithHLS(streamUrl);
if (!hlsSuccess) {
showError('Format not supported by browser. Try MP4 version if available.');
}
}
};
video.addEventListener('canplay', onCanPlay, { once: true });
video.addEventListener('error', onError, { once: true });
return;
}
// Unknown format - try direct first (for VOD with Range support), then HLS
log('Unknown format, trying direct first then HLS fallback');
video.src = streamUrl;
video.addEventListener('canplay', () => {
hideLoading();
video.play().catch(e => {
log('Autoplay prevented: ' + e);
window.parent.postMessage({ type: 'playback_status', status: 'paused' }, '*');
});
}, { once: true });
video.addEventListener('error', async () => {
log('Direct play failed, trying HLS...');
await playWithHLS(streamUrl);
}, { once: true });
}
initPlayer();
</script>
</body>
</html>