on mobile go directly to full screen video
This commit is contained in:
118
frontend/app.js
118
frontend/app.js
@@ -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) {
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user