feat: Add new HTML player pages for streaming video with HLS and MPEG-TS support.

This commit is contained in:
Michael committed 2025-12-14 22:07:07 +01:00
1 parent b0de7bce86
commit 1d6fbfbe0e
2 files changed
+140 -50

No files matched your search

+53 -36
View File
@@ -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
View File
@@ -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;
} }