import { sub, APP_VIDEO_CHANGED, SETTINGS_CHANGED, TRANSFORM_CHANGE, } from "event"; import { log } from "log"; import { opts, settings } from "settings"; const videoEl = document.getElementById("stream"); const mirrorEl = document.getElementById("mirror-stream"); const playEl = document.getElementById("play-stream"); const options = { volume: 0.5, poster: "/img/screen_loading.gif", mirrorMode: null, mirrorUpdateRate: 1 / 60, }; const state = { screen: videoEl, timerId: null, w: 0, h: 0, aspect: 4 / 3, fit: "contain", flip: false, ready: false, autoplayWait: false, }; const mute = (mute) => (videoEl.muted = mute); const applyTransform = () => { const rot = state.rot ? `rotate(${-state.rot}deg)` : ""; const flip = state.flip ? "scaleY(-1)" : ""; mirrorEl.style.transform = rot; videoEl.style.transform = [rot, flip].filter(Boolean).join(" "); }; const onPlay = () => { state.ready = true; videoEl.poster = ""; applyTransform(); resize(state.w, state.h, state.aspect, state.fit); useCustomScreen(options.mirrorMode === "mirror"); }; const play = () => videoEl .play() ?.then(onPlay) .catch((error) => { if (error.name === "NotAllowedError") { showPlayButton(); } else { log.error("Playback fail", error); } }); const toggle = (show) => state.screen.toggleAttribute("hidden", show === undefined ? show : !show); const resize = (w, h, aspect, fit) => { if (!state.ready) return; state.screen.setAttribute("width", "" + w); state.screen.setAttribute("height", "" + h); if (aspect !== undefined) { state.screen.style.aspectRatio = "" + aspect; } if (fit !== undefined) { state.screen.style["object-fit"] = fit; } // properly size the element for rotated view if (state.rot && Math.abs(state.rot % 180) > 1) { const fullscreen = document.fullscreenElement !== null; const ch = fullscreen ? window.innerHeight : state.screen.parentElement.clientHeight; if (ch && state.aspect) { const availableH = ch - (fullscreen ? 14 : 0); const fw = availableH; const fh = Math.round(availableH * state.aspect); const shift = (availableH - fh) / 2; const rot = `rotate(${-state.rot}deg)`; const flp = state.flip && state.screen === videoEl ? "scaleY(-1)" : ""; state.screen.style.width = fw + "px"; state.screen.style.height = fh + "px"; state.screen.style.objectFit = "fill"; state.screen.style.position = "relative"; state.screen.style.top = "0"; state.screen.style.transform = `translateY(${shift}px) ${rot} ${flp}`.trim(); } } else { state.screen.style.width = ""; state.screen.style.height = ""; state.screen.style.position = ""; state.screen.style.top = ""; // hack: cover 1px edge artifact state.screen.style.marginLeft = state.flip ? "1px" : ""; } }; const showPlayButton = () => { state.autoplayWait = true; toggle(); playEl.removeAttribute("hidden"); }; playEl.addEventListener("click", () => { playEl.setAttribute("hidden", ""); state.autoplayWait = false; play(); toggle(); }); // Track resize even when the underlying media stream changes its video size videoEl.addEventListener("resize", () => { recalculateSize(); if (state.screen === videoEl) return; resize(videoEl.videoWidth, videoEl.videoHeight); }); videoEl.addEventListener("loadstart", () => { videoEl.volume = options.volume / 100; videoEl.poster = options.poster; }); videoEl.onfocus = () => videoEl.blur(); videoEl.onerror = (e) => log.error("Playback error", e); const onFullscreen = (fullscreen) => { const el = document.fullscreenElement; state.screen.parentElement.style.overflow = fullscreen ? "visible" : ""; if (fullscreen) { // timeout is due to a chrome bug setTimeout(() => { // aspect ratio calc (skip for rotated - resize handles it) if (!(state.rot && Math.abs(state.rot % 180) > 1)) { const w = window.screen.width ?? window.innerWidth; const hh = el.innerHeight || el.clientHeight || 0; const dw = (w - hh * state.aspect) / 2; state.screen.style.padding = `0 ${dw}px`; } else { // clear any leftover padding for rotated content state.screen.style.padding = "0"; } state.screen.classList.toggle("with-footer"); // re-apply transform in case fullscreen transition reset it applyTransform(); resize(state.w, state.h, state.aspect, state.fit); }, 1); } else { state.screen.style.padding = "0"; state.screen.classList.toggle("with-footer"); applyTransform(); resize(state.w, state.h, state.aspect, state.fit); } if (el === videoEl) { videoEl.classList.toggle("no-media-controls", !fullscreen); videoEl.blur(); } }; const vs = { w: 1, h: 1 }; const recalculateSize = () => { const fullscreen = document.fullscreenElement !== null; const { aspect, screen } = state; let width, height; if (fullscreen) { // we can't get the real