83 lines
5.2 KiB
JavaScript
83 lines
5.2 KiB
JavaScript
(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;
|
|
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 = '<button type="button" aria-label="Play or pause">Play / pause</button><input type="range" min="0" max="1000" value="0" aria-label="Seek video"><button type="button" class="fullscreen-orientation" aria-label="Change fullscreen orientation"></button><button type="button" aria-label="Exit fullscreen">Exit</button>';
|
|
stage.append(hud);
|
|
const [play, orientation, exit] = hud.querySelectorAll('button'), range = hud.querySelector('input');
|
|
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.textContent = `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);
|
|
ancestors.forEach(el => el.classList.remove('orientation-ancestor'));
|
|
hud.hidden = true; 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;
|
|
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.textContent = s.paused ? 'Play' : 'Pause'; }, 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);
|