(function (root) { 'use strict'; const choices = ['auto', 'landscape', 'portrait']; const normalize = value => choices.includes(value) ? value : 'auto'; function rotation(value, width, height) { value = normalize(value); return value === 'landscape' && width < height ? 90 : value === 'portrait' && width > height ? -90 : 0; } function coordinates(x, y, rect, angle = 0) { if (angle === 90) return { x: y - rect.top, y: rect.right - x, width: rect.height, height: rect.width }; if (angle === -90) return { x: rect.bottom - y, y: x - rect.left, width: rect.height, height: rect.width }; return { x: x - rect.left, y: y - rect.top, width: rect.width, height: rect.height }; } function create(options) { const { stage, settings, persist, toggle, seek, state } = options; let active = false, custom = false, native = false, locked = false, angle = 0, timer, hideTimer; const ancestors = []; for (let el = stage.parentElement; el && el !== document.body; el = el.parentElement) ancestors.push(el); const hud = document.createElement('div'); hud.className = 'fullscreen-hud'; hud.hidden = true; hud.innerHTML = ''; stage.append(hud); const [play, orientation, exit] = hud.querySelectorAll('button'), range = hud.querySelector('input'); function revealControls() { if (!active) return; hud.classList.remove('hud-hidden'); clearTimeout(hideTimer); hideTimer = setTimeout(() => { if (!hud.contains(document.activeElement)) hud.classList.add('hud-hidden'); }, 3200); } stage.addEventListener('pointerdown', revealControls, { passive: true }); stage.addEventListener('pointermove', event => { if (event.pointerType === 'mouse') revealControls(); }, { passive: true }); hud.addEventListener('focusin', () => { clearTimeout(hideTimer); hud.classList.remove('hud-hidden'); }); hud.addEventListener('focusout', () => setTimeout(revealControls, 0)); hud.addEventListener('click', revealControls); play.onclick = toggle; exit.onclick = leave; function layout() { if (!active) return; angle = custom ? rotation(settings().fullscreenOrientation, innerWidth, innerHeight) : 0; stage.dataset.fullscreenRotation = String(angle); orientation.setAttribute('aria-label', `Change fullscreen orientation (currently ${normalize(settings().fullscreenOrientation)})`); orientation.title = `Orientation: ${normalize(settings().fullscreenOrientation)}`; } async function orient() { if (locked) { screen.orientation?.unlock?.(); locked = false; } const value = normalize(settings().fullscreenOrientation); if (!custom && value !== 'auto') { try { await screen.orientation.lock(value); locked = true; if (!active) { screen.orientation.unlock(); locked = false; return; } } catch { if (!active) return; custom = true; stage.classList.add('orientation-overlay'); } } layout(); } orientation.onclick = () => { settings().fullscreenOrientation = choices[(choices.indexOf(normalize(settings().fullscreenOrientation)) + 1) % choices.length]; persist(); orient(); }; range.oninput = () => seek(Number(range.value) / 1000 * (state().duration || 0)); // Explicit inverse coordinates also work for rotated range controls on WebKit. const rangeSeek = event => { const point = coordinates(event.clientX, event.clientY, range.getBoundingClientRect(), angle); seek(Math.max(0, Math.min(1, point.x / point.width)) * (state().duration || 0)); }; range.onpointerdown = event => { event.preventDefault(); range.setPointerCapture(event.pointerId); rangeSeek(event); }; range.onpointermove = event => { if (range.hasPointerCapture(event.pointerId)) rangeSeek(event); }; hud.addEventListener('pointerdown', event => event.stopPropagation()); hud.addEventListener('pointerup', event => event.stopPropagation()); function cleanup() { active = false; custom = false; native = false; angle = 0; clearInterval(timer); clearTimeout(hideTimer); ancestors.forEach(el => el.classList.remove('orientation-ancestor')); hud.hidden = true; hud.classList.remove('hud-hidden'); stage.classList.remove('orientation-overlay', 'orientation-fullscreen'); delete stage.dataset.fullscreenRotation; document.body.classList.remove('landscape-fs'); if (locked) { screen.orientation?.unlock?.(); locked = false; } } function leave() { if (document.fullscreenElement === stage) document.exitFullscreen().catch(() => {}); cleanup(); } async function enter() { if (active) { leave(); return; } active = true; ancestors.forEach(el => el.classList.add('orientation-ancestor')); stage.classList.add('orientation-fullscreen'); hud.hidden = false; revealControls(); document.body.classList.add('landscape-fs'); try { if (!stage.requestFullscreen) throw new Error('Custom fullscreen'); await stage.requestFullscreen(); native = true; } catch { custom = true; stage.classList.add('orientation-overlay'); } if (!active) return; await orient(); if (!active) return; timer = setInterval(() => { const s = state(); range.value = String(s.duration ? s.time / s.duration * 1000 : 0); play.setAttribute('aria-label', s.paused ? 'Play video' : 'Pause video'); play.title = s.paused ? 'Play' : 'Pause'; play.innerHTML = s.paused ? '' : ''; }, 250); } document.addEventListener('fullscreenchange', () => { if (active && !document.fullscreenElement && native) cleanup(); }); document.addEventListener('keydown', event => { if (active && event.key === 'Escape') leave(); }); window.addEventListener('resize', layout); return { enter, leave, point: event => coordinates(event.clientX, event.clientY, stage.getBoundingClientRect(), angle) }; } const api = { normalize, rotation, coordinates, create }; if (typeof module !== 'undefined' && module.exports) module.exports = api; else root.FullscreenOrientation = api; })(globalThis);