mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-17 20:29:30 +02:00
feat: Add new HTML player pages for streaming video with HLS and MPEG-TS support.
This commit is contained in:
1 parent
b0de7bce86
commit
1d6fbfbe0e
2 files changed
+140
-50
No files matched your search
+53
-36
@@ -315,39 +315,43 @@
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimized HLS.js configuration for stable live streaming
|
// Optimized HLS.js configuration for stable streaming (prevents micro-cutouts)
|
||||||
const hls = new Hls({
|
const hls = new Hls({
|
||||||
// Increase buffer for smoother playback
|
// LARGER buffers to prevent stalls
|
||||||
maxBufferLength: 30, // Max 30 seconds of buffer
|
maxBufferLength: 60, // Max 60 seconds of buffer (doubled)
|
||||||
maxMaxBufferLength: 60, // Allow up to 60 seconds in stressed conditions
|
maxMaxBufferLength: 120, // Allow up to 120 seconds in stressed conditions
|
||||||
maxBufferSize: 60 * 1000000, // 60MB max buffer size
|
maxBufferSize: 120 * 1000000, // 120MB max buffer size
|
||||||
maxBufferHole: 0.5, // Tolerate 0.5s gaps in buffer
|
maxBufferHole: 1.0, // Tolerate 1s gaps in buffer (more forgiving)
|
||||||
|
|
||||||
// Low latency settings (balanced)
|
// Live settings - more tolerant
|
||||||
liveSyncDurationCount: 3, // Sync to 3 segments behind live
|
liveSyncDurationCount: 4, // Sync to 4 segments behind live (more buffer)
|
||||||
liveMaxLatencyDurationCount: 6, // Max 6 segments behind before seeking
|
liveMaxLatencyDurationCount: 10, // Max 10 segments behind before seeking
|
||||||
liveDurationInfinity: true, // Treat live streams as infinite
|
liveDurationInfinity: true, // Treat live streams as infinite
|
||||||
|
liveBackBufferLength: 30, // Keep 30s of back buffer
|
||||||
|
|
||||||
// Faster recovery from errors
|
// AGGRESSIVE retry settings for network issues
|
||||||
manifestLoadingTimeOut: 15000, // 15s timeout for manifest
|
manifestLoadingTimeOut: 20000, // 20s timeout for manifest
|
||||||
manifestLoadingMaxRetry: 4, // Retry manifest 4 times
|
manifestLoadingMaxRetry: 8, // Retry manifest 8 times
|
||||||
manifestLoadingRetryDelay: 1000, // 1s between retries
|
manifestLoadingRetryDelay: 500, // 500ms between retries (faster)
|
||||||
levelLoadingTimeOut: 15000, // 15s for level playlist
|
levelLoadingTimeOut: 20000, // 20s for level playlist
|
||||||
levelLoadingMaxRetry: 4,
|
levelLoadingMaxRetry: 8,
|
||||||
levelLoadingRetryDelay: 1000,
|
levelLoadingRetryDelay: 500,
|
||||||
fragLoadingTimeOut: 20000, // 20s for fragments
|
fragLoadingTimeOut: 30000, // 30s for fragments (longer)
|
||||||
fragLoadingMaxRetry: 6, // Retry fragments 6 times
|
fragLoadingMaxRetry: 10, // Retry fragments 10 times
|
||||||
fragLoadingRetryDelay: 1000,
|
fragLoadingRetryDelay: 500, // 500ms between retries
|
||||||
|
|
||||||
// Better quality selection
|
// Better quality selection
|
||||||
startLevel: -1, // Auto-select start level
|
startLevel: -1, // Auto-select start level
|
||||||
abrEwmaDefaultEstimate: 500000, // Start assuming 500kbps
|
abrEwmaDefaultEstimate: 500000, // Start assuming 500kbps
|
||||||
abrBandWidthFactor: 0.95, // Use 95% of measured bandwidth
|
abrBandWidthFactor: 0.9, // Use 90% of measured bandwidth (more conservative)
|
||||||
abrBandWidthUpFactor: 0.7, // Be cautious when upgrading quality
|
abrBandWidthUpFactor: 0.6, // Be more cautious when upgrading quality
|
||||||
|
|
||||||
// Enable worker for better performance
|
// Enable worker for better performance
|
||||||
enableWorker: true,
|
enableWorker: true,
|
||||||
|
|
||||||
|
// Lowlatency mode disabled for stability
|
||||||
|
lowLatencyMode: false,
|
||||||
|
|
||||||
// Don't add extra headers
|
// Don't add extra headers
|
||||||
xhrSetup: function (xhr, url) { }
|
xhrSetup: function (xhr, url) { }
|
||||||
});
|
});
|
||||||
@@ -366,25 +370,38 @@
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Better error recovery
|
// Better error recovery (including non-fatal bufferStalledError)
|
||||||
hls.on(Hls.Events.ERROR, (event, data) => {
|
hls.on(Hls.Events.ERROR, (event, data) => {
|
||||||
log('HLS Error: ' + data.type + ' - ' + data.details);
|
log('HLS Error: ' + data.type + ' - ' + data.details);
|
||||||
|
|
||||||
if (data.fatal) {
|
// Handle non-fatal buffer errors (bufferStalledError)
|
||||||
switch (data.type) {
|
if (!data.fatal) {
|
||||||
case Hls.ErrorTypes.NETWORK_ERROR:
|
if (data.details === 'bufferStalledError') {
|
||||||
log('Network error, attempting recovery...');
|
log('Buffer stalled, attempting recovery...');
|
||||||
hls.startLoad(); // Try to recover network errors
|
// Nudge playback if stuck
|
||||||
break;
|
if (video.paused) {
|
||||||
case Hls.ErrorTypes.MEDIA_ERROR:
|
video.play().catch(e => log('Resume failed: ' + e));
|
||||||
log('Media error, attempting recovery...');
|
}
|
||||||
hls.recoverMediaError(); // Try to recover media errors
|
// Force reload from current position
|
||||||
break;
|
hls.startLoad(video.currentTime);
|
||||||
default:
|
|
||||||
showError('HLS Error: ' + data.details);
|
|
||||||
hls.destroy();
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
return; // Non-fatal errors can often be ignored
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle fatal errors
|
||||||
|
switch (data.type) {
|
||||||
|
case Hls.ErrorTypes.NETWORK_ERROR:
|
||||||
|
log('Network error, attempting recovery...');
|
||||||
|
hls.startLoad(); // Try to recover network errors
|
||||||
|
break;
|
||||||
|
case Hls.ErrorTypes.MEDIA_ERROR:
|
||||||
|
log('Media error, attempting recovery...');
|
||||||
|
hls.recoverMediaError(); // Try to recover media errors
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
showError('HLS Error: ' + data.details);
|
||||||
|
hls.destroy();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
+87
-14
@@ -268,16 +268,80 @@
|
|||||||
lazyLoadRecoverDuration: 0,
|
lazyLoadRecoverDuration: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
const player = mpegts.createPlayer({
|
let player = null;
|
||||||
type: 'mpegts',
|
let reconnectAttempts = 0;
|
||||||
isLive: isLive,
|
const maxReconnectAttempts = 10;
|
||||||
url: url,
|
let isDestroyed = false;
|
||||||
hasAudio: true,
|
|
||||||
hasVideo: true,
|
|
||||||
}, playerConfig);
|
|
||||||
|
|
||||||
player.attachMediaElement(video);
|
function createPlayer() {
|
||||||
player.load();
|
if (isDestroyed) return;
|
||||||
|
|
||||||
|
player = mpegts.createPlayer({
|
||||||
|
type: 'mpegts',
|
||||||
|
isLive: isLive,
|
||||||
|
url: url,
|
||||||
|
hasAudio: true,
|
||||||
|
hasVideo: true,
|
||||||
|
}, playerConfig);
|
||||||
|
|
||||||
|
player.attachMediaElement(video);
|
||||||
|
player.load();
|
||||||
|
|
||||||
|
// Handle stream end (auto-reconnect for live streams)
|
||||||
|
player.on(mpegts.Events.LOADING_COMPLETE, () => {
|
||||||
|
if (isLive && !isDestroyed && reconnectAttempts < maxReconnectAttempts) {
|
||||||
|
log('Stream ended, auto-reconnecting... (' + (reconnectAttempts + 1) + '/' + maxReconnectAttempts + ')');
|
||||||
|
reconnectAttempts++;
|
||||||
|
|
||||||
|
// Destroy and recreate player after delay
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isDestroyed) {
|
||||||
|
try {
|
||||||
|
player.unload();
|
||||||
|
player.detachMediaElement();
|
||||||
|
player.destroy();
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
showLoading('Reconnecting...');
|
||||||
|
setTimeout(createPlayer, 500);
|
||||||
|
}
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Reset reconnect counter on successful playback
|
||||||
|
video.addEventListener('playing', () => {
|
||||||
|
reconnectAttempts = 0;
|
||||||
|
hideLoading();
|
||||||
|
});
|
||||||
|
|
||||||
|
player.on(mpegts.Events.ERROR, (errType, errDetail) => {
|
||||||
|
log('mpegts error: ' + errType + ' - ' + errDetail);
|
||||||
|
|
||||||
|
// Try to reconnect on network errors for live streams
|
||||||
|
if (isLive && !isDestroyed && reconnectAttempts < maxReconnectAttempts) {
|
||||||
|
log('Error occurred, attempting reconnect...');
|
||||||
|
reconnectAttempts++;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!isDestroyed) {
|
||||||
|
try {
|
||||||
|
player.unload();
|
||||||
|
player.detachMediaElement();
|
||||||
|
player.destroy();
|
||||||
|
} catch (e) { }
|
||||||
|
|
||||||
|
showLoading('Reconnecting...');
|
||||||
|
setTimeout(createPlayer, 1000);
|
||||||
|
}
|
||||||
|
}, 500);
|
||||||
|
} else {
|
||||||
|
showError('Stream error: ' + errDetail);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createPlayer();
|
||||||
|
|
||||||
video.addEventListener('canplay', () => {
|
video.addEventListener('canplay', () => {
|
||||||
log('MPEG-TS stream ready');
|
log('MPEG-TS stream ready');
|
||||||
@@ -286,13 +350,22 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
video.addEventListener('error', () => {
|
video.addEventListener('error', () => {
|
||||||
showError('Video error: ' + (video.error?.message || 'Unknown error'));
|
if (!isLive || reconnectAttempts >= maxReconnectAttempts) {
|
||||||
|
showError('Video error: ' + (video.error?.message || 'Unknown error'));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
player.on(mpegts.Events.ERROR, (errType, errDetail) => {
|
// Cleanup function
|
||||||
log('mpegts error: ' + errType + ' - ' + errDetail);
|
window.cleanupMpegtsPlayer = () => {
|
||||||
showError('Stream error: ' + errDetail);
|
isDestroyed = true;
|
||||||
});
|
if (player) {
|
||||||
|
try {
|
||||||
|
player.unload();
|
||||||
|
player.detachMediaElement();
|
||||||
|
player.destroy();
|
||||||
|
} catch (e) { }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in new issue
Block a user