on mobile go directly to full screen video

This commit is contained in:
Simon
2026-02-09 12:31:07 +00:00
parent df8aaa5f9f
commit 1651e5a375
2 changed files with 128 additions and 3 deletions

View File

@@ -7,6 +7,10 @@ let isLoading = false;
let hlsPlayer = null; let hlsPlayer = null;
let currentLoadController = null; let currentLoadController = null;
let errorToastTimer = null; let errorToastTimer = null;
let playerMode = 'modal';
let playerHome = null;
let onFullscreenChange = null;
let onWebkitEndFullscreen = null;
// 2. Observer Definition (Must be defined before initApp uses it) // 2. Observer Definition (Must be defined before initApp uses it)
const observer = new IntersectionObserver((entries) => { const observer = new IntersectionObserver((entries) => {
@@ -178,6 +182,25 @@ function formatDuration(seconds) {
return `${minutes}m`; return `${minutes}m`;
} }
function isMobilePlayback() {
if (navigator.userAgentData && typeof navigator.userAgentData.mobile === 'boolean') {
return navigator.userAgentData.mobile;
}
const ua = navigator.userAgent || '';
if (/iPhone|iPad|iPod|Android/i.test(ua)) return true;
return window.matchMedia('(pointer: coarse)').matches && window.matchMedia('(max-width: 900px)').matches;
}
function getMobileVideoHost() {
let host = document.getElementById('mobile-video-host');
if (!host) {
host = document.createElement('div');
host.id = 'mobile-video-host';
document.body.appendChild(host);
}
return host;
}
// 4. Initialization (Run this last) // 4. Initialization (Run this last)
async function initApp() { async function initApp() {
// Clear old data if you want a fresh start every refresh // Clear old data if you want a fresh start every refresh
@@ -238,6 +261,12 @@ function showError(message) {
async function openPlayer(url) { async function openPlayer(url) {
const modal = document.getElementById('video-modal'); const modal = document.getElementById('video-modal');
const video = document.getElementById('player'); const video = document.getElementById('player');
const useMobileFullscreen = isMobilePlayback();
let playbackStarted = false;
if (!playerHome) {
playerHome = video.parentElement;
}
// 1. Define isHls (the missing piece!) // 1. Define isHls (the missing piece!)
let refererParam = ''; let refererParam = '';
@@ -275,14 +304,65 @@ async function openPlayer(url) {
} }
} }
if (useMobileFullscreen) {
const host = getMobileVideoHost();
if (video.parentElement !== host) {
host.appendChild(video);
}
playerMode = 'mobile';
video.removeAttribute('playsinline');
video.removeAttribute('webkit-playsinline');
video.playsInline = false;
} else {
if (playerHome && video.parentElement !== playerHome) {
playerHome.appendChild(video);
}
playerMode = 'modal';
video.setAttribute('playsinline', '');
video.setAttribute('webkit-playsinline', '');
video.playsInline = true;
}
const requestFullscreen = () => {
if (playerMode !== 'mobile') return;
if (typeof video.webkitEnterFullscreen === 'function') {
try {
video.webkitEnterFullscreen();
} catch (err) {
// Ignore if fullscreen is not allowed.
}
return;
}
if (video.requestFullscreen) {
video.requestFullscreen().catch(() => {});
}
};
const startPlayback = () => {
if (playbackStarted) return;
playbackStarted = true;
const playPromise = video.play();
if (playPromise && typeof playPromise.catch === 'function') {
playPromise.catch(() => {});
}
if (playerMode === 'mobile') {
if (video.readyState >= 1) {
requestFullscreen();
} else {
video.addEventListener('loadedmetadata', requestFullscreen, { once: true });
}
}
};
if (isHls) { if (isHls) {
if (window.Hls && window.Hls.isSupported()) { if (window.Hls && window.Hls.isSupported()) {
hlsPlayer = new window.Hls(); hlsPlayer = new window.Hls();
hlsPlayer.loadSource(streamUrl); hlsPlayer.loadSource(streamUrl);
hlsPlayer.attachMedia(video); hlsPlayer.attachMedia(video);
hlsPlayer.on(window.Hls.Events.MANIFEST_PARSED, function() { hlsPlayer.on(window.Hls.Events.MANIFEST_PARSED, function() {
video.play(); startPlayback();
}); });
startPlayback();
hlsPlayer.on(window.Hls.Events.ERROR, function(event, data) { hlsPlayer.on(window.Hls.Events.ERROR, function(event, data) {
if (data && data.fatal) { if (data && data.fatal) {
showError('Unable to play this stream.'); showError('Unable to play this stream.');
@@ -291,6 +371,7 @@ async function openPlayer(url) {
}); });
} else if (video.canPlayType('application/vnd.apple.mpegurl')) { } else if (video.canPlayType('application/vnd.apple.mpegurl')) {
video.src = streamUrl; video.src = streamUrl;
startPlayback();
} else { } else {
console.error("HLS not supported in this browser."); console.error("HLS not supported in this browser.");
showError('HLS is not supported in this browser.'); showError('HLS is not supported in this browser.');
@@ -298,6 +379,7 @@ async function openPlayer(url) {
} }
} else { } else {
video.src = streamUrl; video.src = streamUrl;
startPlayback();
} }
video.onerror = () => { video.onerror = () => {
@@ -305,8 +387,29 @@ async function openPlayer(url) {
closePlayer(); closePlayer();
}; };
if (playerMode === 'modal') {
modal.style.display = 'flex'; modal.style.display = 'flex';
document.body.style.overflow = 'hidden'; document.body.style.overflow = 'hidden';
} else {
modal.style.display = 'none';
document.body.style.overflow = 'auto';
if (!onFullscreenChange) {
onFullscreenChange = () => {
if (playerMode === 'mobile' && !document.fullscreenElement) {
closePlayer();
}
};
}
document.addEventListener('fullscreenchange', onFullscreenChange);
if (!onWebkitEndFullscreen) {
onWebkitEndFullscreen = () => {
if (playerMode === 'mobile') {
closePlayer();
}
};
}
video.addEventListener('webkitendfullscreen', onWebkitEndFullscreen);
}
} }
function closePlayer() { function closePlayer() {
@@ -316,11 +419,24 @@ function closePlayer() {
hlsPlayer.destroy(); hlsPlayer.destroy();
hlsPlayer = null; hlsPlayer = null;
} }
if (document.fullscreenElement && document.exitFullscreen) {
document.exitFullscreen().catch(() => {});
}
if (onFullscreenChange) {
document.removeEventListener('fullscreenchange', onFullscreenChange);
}
if (onWebkitEndFullscreen) {
video.removeEventListener('webkitendfullscreen', onWebkitEndFullscreen);
}
video.onerror = null; video.onerror = null;
video.pause(); video.pause();
video.src = ''; video.src = '';
modal.style.display = 'none'; modal.style.display = 'none';
document.body.style.overflow = 'auto'; document.body.style.overflow = 'auto';
if (playerHome && video.parentElement !== playerHome) {
playerHome.appendChild(video);
}
playerMode = 'modal';
} }
function handleSearch(value) { function handleSearch(value) {

View File

@@ -768,6 +768,15 @@ video {
object-fit: contain; object-fit: contain;
} }
#mobile-video-host {
position: fixed;
left: -9999px;
top: 0;
width: 1px;
height: 1px;
overflow: hidden;
}
.error-toast { .error-toast {
position: fixed; position: fixed;
right: 20px; right: 20px;