Merge branch 'main' into dev

This commit is contained in:
Trevor Mears
2026-01-01 13:07:34 -08:00
3 changed files with 34 additions and 12 deletions
+19 -8
View File
@@ -181,7 +181,7 @@ class VideoPlayer {
btnMute.addEventListener('click', () => { btnMute.addEventListener('click', () => {
this.video.muted = !this.video.muted; this.video.muted = !this.video.muted;
btnMute.classList.toggle('muted', this.video.muted); btnMute.classList.toggle('muted', this.video.muted);
// Update icon // Update icon
const icon = btnMute.querySelector('.icon'); const icon = btnMute.querySelector('.icon');
if (this.video.muted) { if (this.video.muted) {
@@ -236,13 +236,13 @@ class VideoPlayer {
this.video.addEventListener('click', showControls); this.video.addEventListener('click', showControls);
this.video.addEventListener('touchstart', showControls); this.video.addEventListener('touchstart', showControls);
// Keep controls visible when video is paused // Keep controls visible when video is paused
this.video.addEventListener('pause', () => { this.video.addEventListener('pause', () => {
controls.classList.add('show'); controls.classList.add('show');
clearTimeout(controlsTimeout); clearTimeout(controlsTimeout);
}); });
this.video.addEventListener('play', () => { this.video.addEventListener('play', () => {
hideControls(); hideControls();
}); });
@@ -474,7 +474,9 @@ class VideoPlayer {
// Transcoded streams are fragmented MP4 - play directly with <video> element // Transcoded streams are fragmented MP4 - play directly with <video> element
console.log('[Player] Playing transcoded stream directly:', transcodeUrl); console.log('[Player] Playing transcoded stream directly:', transcodeUrl);
this.video.src = transcodeUrl; this.video.src = transcodeUrl;
this.video.play().catch(e => console.log('[Player] Autoplay prevented:', e)); this.video.play().catch(e => {
if (e.name !== 'AbortError') console.log('[Player] Autoplay prevented:', e);
});
// Update UI and dispatch events // Update UI and dispatch events
this.updateNowPlaying(channel); this.updateNowPlaying(channel);
@@ -513,7 +515,9 @@ class VideoPlayer {
console.log('[Player] Stream type:', isRawTs ? 'Raw TS' : 'Extension-less (assumed TS)'); console.log('[Player] Stream type:', isRawTs ? 'Raw TS' : 'Extension-less (assumed TS)');
const remuxUrl = this.getRemuxUrl(streamUrl); const remuxUrl = this.getRemuxUrl(streamUrl);
this.video.src = remuxUrl; this.video.src = remuxUrl;
this.video.play().catch(e => console.log('[Player] Autoplay prevented:', e)); this.video.play().catch(e => {
if (e.name !== 'AbortError') console.log('[Player] Autoplay prevented:', e);
});
// Update UI and dispatch events // Update UI and dispatch events
this.updateNowPlaying(channel); this.updateNowPlaying(channel);
@@ -542,7 +546,9 @@ class VideoPlayer {
this.hls.attachMedia(this.video); this.hls.attachMedia(this.video);
this.hls.on(Hls.Events.MANIFEST_PARSED, () => { this.hls.on(Hls.Events.MANIFEST_PARSED, () => {
this.video.play().catch(e => console.log('Autoplay prevented:', e)); this.video.play().catch(e => {
if (e.name !== 'AbortError') console.log('Autoplay prevented:', e);
});
}); });
// Re-attach error handler for the new Hls instance // Re-attach error handler for the new Hls instance
@@ -592,17 +598,22 @@ class VideoPlayer {
// Priority 2: Native HLS support (Safari on iOS/macOS where HLS.js may not work) // Priority 2: Native HLS support (Safari on iOS/macOS where HLS.js may not work)
this.video.src = finalUrl; this.video.src = finalUrl;
this.video.play().catch(e => { this.video.play().catch(e => {
if (e.name === 'AbortError') return; // Ignore interruption by new load
console.log('Autoplay prevented, trying proxy if CORS error:', e); console.log('Autoplay prevented, trying proxy if CORS error:', e);
if (!this.isUsingProxy) { if (!this.isUsingProxy) {
this.isUsingProxy = true; this.isUsingProxy = true;
this.video.src = this.getProxiedUrl(streamUrl); this.video.src = this.getProxiedUrl(streamUrl);
this.video.play().catch(err => console.error('Proxy play failed:', err)); this.video.play().catch(err => {
if (err.name !== 'AbortError') console.error('Proxy play failed:', err);
});
} }
}); });
} else { } else {
// Priority 3: Try direct playback for non-HLS streams // Priority 3: Try direct playback for non-HLS streams
this.video.src = finalUrl; this.video.src = finalUrl;
this.video.play().catch(e => console.log('Autoplay prevented:', e)); this.video.play().catch(e => {
if (e.name !== 'AbortError') console.log('Autoplay prevented:', e);
});
} }
// Update now playing info // Update now playing info
+10 -2
View File
@@ -26,10 +26,15 @@ router.get('/', (req, res) => {
const args = [ const args = [
'-hide_banner', '-hide_banner',
'-loglevel', 'warning', '-loglevel', 'warning',
// Error resilience: discard corrupt packets, generate timestamps // Low-latency startup: reduce probe/analyze time for faster first bytes
'-fflags', '+genpts+discardcorrupt+igndts', '-probesize', '32768',
'-analyzeduration', '0',
// Error resilience: discard corrupt packets, generate timestamps, ignore DTS, no buffering
'-fflags', '+genpts+discardcorrupt+igndts+nobuffer',
// Ignore errors in stream and continue // Ignore errors in stream and continue
'-err_detect', 'ignore_err', '-err_detect', 'ignore_err',
// Limit max demux delay to prevent buffering issues with bad timestamps
'-max_delay', '5000000',
// Reconnect settings for network drops // Reconnect settings for network drops
'-reconnect', '1', '-reconnect', '1',
'-reconnect_streamed', '1', '-reconnect_streamed', '1',
@@ -40,6 +45,9 @@ router.get('/', (req, res) => {
// Convert AAC from ADTS format (TS) to ASC format (MP4) // Convert AAC from ADTS format (TS) to ASC format (MP4)
// Required when remuxing MPEG-TS with AAC audio to MP4 // Required when remuxing MPEG-TS with AAC audio to MP4
'-bsf:a', 'aac_adtstoasc', '-bsf:a', 'aac_adtstoasc',
// Handle timestamp discontinuities at output
'-fps_mode', 'passthrough',
'-max_muxing_queue_size', '1024',
// Fragmented MP4 for streaming (browser-compatible) // Fragmented MP4 for streaming (browser-compatible)
'-f', 'mp4', '-f', 'mp4',
'-movflags', 'frag_keyframe+empty_moov+default_base_moof', '-movflags', 'frag_keyframe+empty_moov+default_base_moof',
+5 -2
View File
@@ -25,8 +25,11 @@ router.get('/', (req, res) => {
const args = [ const args = [
'-hide_banner', '-hide_banner',
'-loglevel', 'warning', '-loglevel', 'warning',
// Error resilience: discard corrupt packets, generate timestamps, ignore DTS // Low-latency startup: reduce probe/analyze time for faster first bytes
'-fflags', '+genpts+discardcorrupt+igndts', '-probesize', '32768',
'-analyzeduration', '0',
// Error resilience: discard corrupt packets, generate timestamps, ignore DTS, no buffering
'-fflags', '+genpts+discardcorrupt+igndts+nobuffer',
// Ignore errors in stream and continue // Ignore errors in stream and continue
'-err_detect', 'ignore_err', '-err_detect', 'ignore_err',
// Limit max demux delay to prevent buffering issues with bad timestamps // Limit max demux delay to prevent buffering issues with bad timestamps