diff --git a/frontend/js/player.js b/frontend/js/player.js index cc93a55..efd1cff 100644 --- a/frontend/js/player.js +++ b/frontend/js/player.js @@ -78,6 +78,7 @@ App.player = App.player || {}; const refererParam = resolved.referer ? `&referer=${encodeURIComponent(resolved.referer)}` : ''; const streamUrl = `/api/stream?url=${encodeURIComponent(resolved.url)}${refererParam}`; let isHls = /\.m3u8($|\?)/i.test(resolved.url); + let isDirectMedia = /\.(mp4|m4v|m4s|webm|ts|mov)($|\?)/i.test(resolved.url); // Cleanup existing player instance to prevent aborted bindings. if (state.hlsPlayer) { @@ -98,6 +99,8 @@ App.player = App.player || {}; const contentType = headResp.headers.get('Content-Type') || ''; if (contentType.includes('application/vnd.apple.mpegurl')) { isHls = true; + } else if (contentType.startsWith('video/') || contentType.startsWith('audio/')) { + isDirectMedia = true; } } catch (err) { console.warn('Failed to detect stream type', err); @@ -155,41 +158,73 @@ App.player = App.player || {}; } }; - if (isHls) { - if (window.Hls && window.Hls.isSupported()) { - state.hlsPlayer = new window.Hls(); - state.hlsPlayer.loadSource(streamUrl); - state.hlsPlayer.attachMedia(video); - state.hlsPlayer.on(window.Hls.Events.MANIFEST_PARSED, function() { - startPlayback(); - }); - startPlayback(); - state.hlsPlayer.on(window.Hls.Events.ERROR, function(event, data) { - if (data && data.fatal) { - clearLoading(); - if (App.ui && App.ui.showError) { - App.ui.showError('Unable to play this stream.'); - } - App.player.close(); - } - }); - } else if (video.canPlayType('application/vnd.apple.mpegurl')) { - video.src = streamUrl; - startPlayback(); - } else { - console.error("HLS not supported in this browser."); - if (App.ui && App.ui.showError) { - App.ui.showError('HLS is not supported in this browser.'); - } - clearLoading(); - return; - } - } else { + const canUseHls = !!(window.Hls && window.Hls.isSupported()); + const prefersHls = isHls || (canUseHls && !isDirectMedia && !video.canPlayType('application/vnd.apple.mpegurl')); + let hlsTried = false; + let nativeTried = false; + let usingHls = false; + + const startNative = () => { + if (nativeTried) return; + nativeTried = true; + usingHls = false; video.src = streamUrl; startPlayback(); + }; + + const startHls = (allowFallback) => { + if (!canUseHls || hlsTried) return false; + hlsTried = true; + usingHls = true; + state.hlsPlayer = new window.Hls(); + state.hlsPlayer.loadSource(streamUrl); + state.hlsPlayer.attachMedia(video); + state.hlsPlayer.on(window.Hls.Events.MANIFEST_PARSED, function() { + startPlayback(); + }); + startPlayback(); + state.hlsPlayer.on(window.Hls.Events.ERROR, function(event, data) { + if (data && data.fatal) { + const shouldFallback = allowFallback && !nativeTried && !isHls; + if (state.hlsPlayer) { + state.hlsPlayer.destroy(); + state.hlsPlayer = null; + } + if (shouldFallback) { + startNative(); + return; + } + clearLoading(); + if (App.ui && App.ui.showError) { + App.ui.showError('Unable to play this stream.'); + } + App.player.close(); + } + }); + return true; + }; + + if (prefersHls) { + if (!startHls(true)) { + if (video.canPlayType('application/vnd.apple.mpegurl')) { + startNative(); + } else { + console.error("HLS not supported in this browser."); + if (App.ui && App.ui.showError) { + App.ui.showError('HLS is not supported in this browser.'); + } + clearLoading(); + return; + } + } + } else { + startNative(); } video.onerror = () => { + if (!usingHls && canUseHls && !hlsTried && !isDirectMedia) { + if (startHls(true)) return; + } clearLoading(); if (App.ui && App.ui.showError) { App.ui.showError('Video failed to load.');