'use strict'; import("./HTMLMediaElement.mjs"); async function xmr_media_lazyload_perform(element) { let content = element.querySelector("noscript"); if (content) { element.innerHTML = content.innerText; } } async function xmr_media_lazyload_initialize() { // Figure out what to load. let elements = document.querySelectorAll(".block-this._media > [data-lazyload]"); let stack = Array.from(elements); console.debug("Lazily loading " + stack.length + " elements..."); // Add a lazyloading handler to all entries. for (let el of stack) { xmr_media_lazyload_perform(el); } } class XMediaPlayer { constructor(element) { this._media = element; // Replace the original video element with a player. this._container = document.createElement("player"); this._media.parentElement.replaceChild(this._container, this._media); // Insert the original video element. This must be done here, else we have Z-fighting on mobile! this._container.appendChild(this._media); // Insert the container for our overlays. this._overlay = document.createElement("overlay"); this._container.appendChild(this._overlay); // Build the variant menu. this._variants = document.createElement("select"); this._variants.size = 2; this._variants.classList.add("variants", "hide"); this._overlay.appendChild(this._variants); console.log(this._media.currentSrc); for (let source of this._media.querySelectorAll("source")) { let variant = document.createElement("option") if (source.src == this._media.currentSrc) { variant.selected = true; } variant.value = source.src; if (source.title) { variant.innerText = source.title; } else { variant.innerText = (new URL(source.src)).pathname; } this._variants.appendChild(variant); } // Build the control overlay. this._controls = document.createElement("controls"); this._overlay.appendChild(this._controls); // - Play Button this._controlPlay = document.createElement("div"); this._controlPlay.classList.add("play", "symbol") this._controlPlay.tabIndex = 0; this._controlPlay.dataset.symbol = "⏵"; this._controlPlay.ariaLabel = "Play"; this._controlPlay.update = () => { if (this._media.paused) { this._controlPlay.classList.remove("playing"); this._controlPlay.dataset.symbol = "⏵"; this._controlPlay.ariaLabel = "Play"; } else if (this._media.error || this._media.ended) { this._controlPlay.classList.remove("playing"); this._controlPlay.dataset.symbol = "⏹"; this._controlPlay.ariaLabel = "Stopped"; } else { this._controlPlay.classList.add("playing"); this._controlPlay.dataset.symbol = "⏸"; this._controlPlay.ariaLabel = "Pause"; } this._controlPlay.innerText = this._controlPlay.dataset.symbol; } this._controls.appendChild(this._controlPlay); // - Time Display this._controlTime = document.createElement("div"); this._controlTime.classList.add("time"); this._controlTime.innerHTML = "00:00 / 00:00"; this._controlTime.update = () => { function formatSeconds(seconds, with_ms, with_minutes, with_hour) { let result = []; if (isFinite(seconds) && !isNaN(seconds)) { let tainted_seconds = seconds % 60; let pure_seconds = Math.floor(seconds % 60); let tainted_minutes = ((seconds - pure_seconds) / 60); let pure_minutes = Math.floor(tainted_minutes % 60); let pure_hours = Math.floor((tainted_minutes - pure_minutes) / 60); result.unshift(`${pure_seconds.toString(10).padStart(2, '0')}`); if (with_ms) { result[0] = `${result[0]}.${Math.floor((tainted_seconds % 1) * 100).toString(10).padStart(2, '0')}` } if ((with_minutes === true) || ((with_minutes === undefined) && (pure_minutes > 0))) { result[0] = result[0].padStart(2, '0'); result.unshift(`${pure_minutes.toString(10)}`); } if ((with_hour === true) || ((with_hour === undefined) && (pure_hours > 0))) { result[0] = result[0].padStart(2, '0'); result.unshift(`${pure_hours.toString(10)}`); } } return result; } let duration = formatSeconds(this._media.duration, true); let current = formatSeconds(this._media.currentTime, true, duration.length >= 2, duration.length >= 3); for (let idx in duration) { current[idx].padStart(duration[idx].length, '0'); } if (duration.length > 0) { this._controlTime.innerText = `${current.join(':')} / ${duration.join(':')}`; } else { this._controlTime.innerText = `${current.join(':')}`; } } this._controls.appendChild(this._controlTime); // - Progress Bar this._controlProgress = document.createElement("input"); this._controlProgress.classList.add("progress"); this._controlProgress.type = "range"; this._controlProgress.tabIndex = 0; this._controlProgress.min = -2147483648; this._controlProgress.max = 2147483647; this._controlProgress.update = () => { let min = parseInt(this._controlVolume.min, 10); let max = parseInt(this._controlVolume.max, 10); let dlt = max - min; this._controlProgress.disabled = !this._media.seekable; this._controlProgress.value = (this._media.currentTime / this._media.duration) * dlt + min; } this._controls.appendChild(this._controlProgress); // - Mute Button this._controlMute = document.createElement("div"); this._controlMute.classList.add("mute", "symbol"); this._controlMute.tabIndex = 0; this._controlMute.ariaLabel = "Mute"; this._controlMute.dataset.symbol = "🔊"; this._controlMute.update = () => { if (this._media.muted) { this._controlMute.classList.add("muted"); this._controlMute.dataset.symbol = "🔇"; this._controlMute.ariaLabel = "Unmute"; } else { this._controlMute.classList.remove("muted"); if (this._media.volume > 0.5) { this._controlMute.dataset.symbol = "🔊"; } else if (this._media.volume > 0.25) { this._controlMute.dataset.symbol = "🔉"; } else { this._controlMute.dataset.symbol = "🔈"; } this._controlMute.ariaLabel = `Volume: ${(this._media.volume * 100).toString(10)}%`; } this._controlMute.innerText = this._controlMute.dataset.symbol; } this._controls.appendChild(this._controlMute); // - Volume Controls this._controlVolume = document.createElement("input"); this._controlVolume.classList.add("volume"); this._controlVolume.type = "range"; this._controlVolume.tabIndex = 0; this._controlVolume.min = -2147483648; this._controlVolume.max = 2147483647; this._controlVolume.value = this._controlVolume.max; this._controlVolume.update = () => { let min = parseInt(this._controlVolume.min, 10); let max = parseInt(this._controlVolume.max, 10); let dlt = max - min; this._controlVolume.value = (this._media.volume * dlt) + min; this._controlVolume.ariaLabel = `Volume: ${(this._media.volume * 100).toString(10)}%`; if (this._media.muted) { this._controlVolume.classList.add("muted"); } else { this._controlVolume.classList.remove("muted"); } } this._controls.appendChild(this._controlVolume); // - Variant Button this._controlVariant = document.createElement("div"); this._controlVariant.classList.add("variant", "symbol"); this._controlVariant.tabIndex = 0; this._controlVariant.ariaLabel = "Change Variant"; this._controlVariant.dataset.symbol = "⚙"; this._controlVariant.update = () => { this._controlVariant.value = this._media.currentSrc; }; this._controls.appendChild(this._controlVariant); // - Fullscreen Button this._controlFullscreen = document.createElement("div"); this._controlFullscreen.classList.add("fullscreen", "symbol"); this._controlFullscreen.tabIndex = 0; this._controlFullscreen.ariaLabel = "Toggle Fullscreen"; this._controlFullscreen.dataset.symbol = "⛶"; this._controlFullscreen.update = () => { if (document.fullscreenElement == this._container) { this._controlFullscreen.dataset.symbol = "\u200F⬚"; } else { this._controlFullscreen.dataset.symbol = "⛶"; } this._controlFullscreen.innerText = this._controlFullscreen.dataset.symbol; }; this._controls.appendChild(this._controlFullscreen); // Now begin listening to all events that are relevant. for (let event of ["Abort", "CanPlay", "CanPlayThrough", "DurationChange", "Emptied", "Encrypted", "Ended", "Error", "LoadedData", "LoadedMetaData", "LoadStart", "Pause", "Play", "Playing", "Progress", "RateChange", "Seeked", "Seeking", "Stalled", "Suspend", "TimeUpdate", "VolumeChange", "Waiting", "DblClick", "Click", "KeyDown"]) { this._media.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["FullscreenChange", "FullscreenError", "KeyDown", "MouseEnter", "MouseLeave", "MouseMove", "Wheel"]) { this._container.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Click", "KeyDown"]) { this._controlPlay.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Input", "KeyDown", "Wheel"]) { this._controlProgress.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Click", "KeyDown"]) { this._controlMute.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Input", "KeyDown", "Wheel"]) { this._controlVolume.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Input"]) { this._variants.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Click", "KeyDown"]) { this._controlVariant.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } for (let event of ["Click", "KeyDown"]) { this._controlFullscreen.addEventListener(event.toLowerCase(), (ev) => { this[`_on${event}`](ev); }); } // And update all controls once. this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); this._controlMute.update(); this._controlVolume.update(); this._controlVariant.update(); this._controlFullscreen.update(); // And tell the media element to load. this._media.load(); } _hideAudioControlWithoutAudio() { let hasAudio = Boolean(this._media.mozHasAudio) || Boolean(this._media.webkitAudioDecodedByteCount) || Boolean(this._media.audioTracks && this._media.audioTracks.length); this._controlMute.style.display = hasAudio ? "" : "none"; this._controlVolume.style.display = hasAudio ? "" : "none"; } // Media Playback Status _onAbort(ev) { this.showOverlay(); this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onEnded(ev) { this.showOverlay(); this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onError(ev) { this.showOverlay(); this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onPlay(ev) { this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onPlaying(ev) { this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onPause(ev) { this.showOverlay(); this._controlPlay.update(); this._controlTime.update(); this._controlProgress.update(); } _onProgress(ev) { this._controlTime.update(); this._controlProgress.update(); } _onRateChange(ev) { this._controlPlay.update(); } _onTimeUpdate(ev) { this._controlTime.update(); this._controlProgress.update(); } _onVolumeChange(ev) { this._controlMute.update(); this._controlVolume.update(); } // Media Stream Status _onCanPlay(ev) { } _onCanPlayThrough(ev) { } _onDurationChange(ev) { this._controlTime.update(); this._controlProgress.update(); } _onEmptied(ev) { } _onEncrypted(ev) { } _onLoadedData(ev) { this._hideAudioControlWithoutAudio(); this._controlTime.update(); this._controlProgress.update(); } _onLoadedMetaData(ev) { this._hideAudioControlWithoutAudio(); this._controlTime.update(); this._controlProgress.update(); } _onLoadStart(ev) { } _onSeeked(ev) { this._controlTime.update(); this._controlProgress.update(); } _onSeeking(ev) { } _onSuspend(ev) { } _onWaiting(ev) { } // User Input _onClick(ev) { if ((ev.target == this._controlPlay) || (ev.target == this._media)) { this.togglePlayPause(); } else if (ev.target == this._controlMute) { this.toggleMute(); } else if (ev.target == this._controlFullscreen) { this.toggleFullscreen(); } else if (ev.target == this._controlVariant) { this.toggleVariants(); } else { // If we can't handle this, let it fall to the next handler. return } // Prevent default behavior and stop propagation. this.showOverlay(); ev.preventDefault(); ev.stopPropagation(); } _onDblClick(ev) { this._controlFullscreen.click(); this.showOverlay(); ev.preventDefault(); ev.stopPropagation(); } _onMouseEnter(ev) { } _onMouseMove(ev) { this.showOverlay(); } _onMouseLeave(ev) { } _onKeyDown(ev) { // Ignore IME compositing. if (ev.isComposing || ev.keyCode === 229) { return; } if (["Space", "Enter"].includes(ev.code)) { // Most likely an attempt to simulate a click. ev.target.click(); } else if (["Escape"].includes(ev.key)) { if (document.fullscreenElement) { this.toggleFullscreen(false); } else { this.toggleTheater(false); } } else if (["f", "F"].includes(ev.key)) { this._controlFullscreen.click(); } else if (["m", "M"].includes(ev.key)) { this._controlMute.click(); } else if (ev.code === "Home") { this._media.currentTime = 0; } else if (ev.code === "ArrowLeft") { this._media.currentTime -= 5; } else if (ev.code === "Comma") { this._media.currentTime -= .1; } else if (ev.code === "Period") { this._media.currentTime += .1; } else if (ev.code === "ArrowRight") { this._media.currentTime += 5; } else if (ev.code === "End") { this._media.currentTime = this._media.duration; } else if (ev.code === "ArrowUp") { this._media.volume = Math.max(0, Math.min(this._media.volume + 0.05, 1.0)); } else if (ev.code === "ArrowDown") { this._media.volume = Math.max(0, Math.min(this._media.volume - 0.05, 1.0)); } else if (["p", "P"].includes(ev.key)) { this._media.preservesPitch = !this._media.preservesPitch; } else if (ev.code === "PageDown") { this._media.playbackRate = Math.max(0.25, Math.min(this._media.playbackRate - 0.05, 4.0)); } else if (ev.code === "PageUp") { this._media.playbackRate = Math.max(0.25, Math.min(this._media.playbackRate + 0.05, 4.0)); } else if (["r", "R"].includes(ev.key)) { this._media.playbackRate = 1.0; } else { // If we can't handle this, let it fall to the next handler. return; } // Prevent default behavior and stop propagation. this.showOverlay(); ev.preventDefault(); ev.stopPropagation(); } _onInput(ev) { this.toggleVariants(false); if (ev.target == this._controlProgress) { ev.preventDefault(); if (isFinite(this._media.duration)) { let min = parseInt(this._controlProgress.min, 10); let max = parseInt(this._controlProgress.max, 10); let val = parseInt(this._controlProgress.value, 10); let dlt = max - min; let time = (val - min) / dlt; this._media.currentTime = time * this._media.duration; } } else if (ev.target == this._controlVolume) { let min = parseInt(this._controlVolume.min, 10); let max = parseInt(this._controlVolume.max, 10); let val = parseInt(this._controlVolume.value, 10); let dlt = max - min; let vol = (val - min) / dlt; this._media.volume = vol; this._media.muted = (this._media.volume < 0.01); } else if (ev.target == this._variants) { // Switch out the video file. // Store current state and pause. let paused = this._media.paused; let muted = this._media.muted; let volume = this._media.volume; let time = this._media.currentTime; this._media.pause(); this._controlPlay.update(); // Tell the this._media source to begin loading. this._media.src = this._variants.value; this._media.load(); this._media.currentTime = time; this._media.addEventListener("loadeddata", () => { this._media.currentTime = time; this._media.volume = volume; this._media.muted = muted; ev.preventDefault(); ev.stopPropagation(); }, { "once": true, "capture": true }) this._media.addEventListener("seeked", () => { if (paused) { this._media.pause(); } else { this._media.play(); } this._controlPlay.update(); ev.preventDefault(); ev.stopPropagation(); }, { "once": true, "capture": true }); } } _onChange(ev) {} // Visibility _onFullscreenChange(ev) { this._controlFullscreen.update(); } // Control Functions togglePlayPause() { if (this._media.paused) { this._media.play(); } else { this._media.pause(); } } toggleMute() { this._media.muted = !this._media.muted; } toggleVariants(state) { if ((state == false) || (!this._variants.classList.contains("hide"))) { this._variants.classList.add("hide"); } else { this._variants.classList.remove("hide"); } } toggleFullscreen(state) { if ((state == false) || (document.fullscreenElement == this._container)) { if (document.fullscreenElement) { document.exitFullscreen(); } } else { this._container.requestFullscreen(); } } showOverlay() { if (this._overlayTimeout) { clearTimeout(this._overlayTimeout); } this._container.classList.remove("hide"); this._overlayTimeout = setTimeout(() => { this.hideOverlay(); }, 2500); } hideOverlay() { if (this._overlayTimeout) { clearTimeout(this._overlayTimeout); } if (!this._media.paused && !this._media.ended && !this._media.error) { this._container.classList.add("hide"); } } } /* async function xmr_initialize_player(el) { let variant = el.querySelector(".variant"); { // Variants variant.update = function () { // Store current state and pause. let paused = this._media.paused; let muted = this._media.muted; let volume = this._media.volume; let time = this._media.currentTime; this._media.pause(); play_pause.update(); // Tell the this._media source to begin loading. this._media.src = variant.value; this._media.load(); this._media.currentTime = time; this._media.addEventListener("canplay", () => { this._media.currentTime = time; this._media.volume = volume; this._media.muted = muted; if (paused) { this._media.pause(); } else { this._media.play(); } play_pause.update(); }, { "once": true }) } variant.addEventListener("input", () => { variant.update(); }); variant.addEventListener("value", () => { variant.update(); }); // Variants - Add options from available sources. let sources = el.querySelectorAll("source"); for (let k of sources) { let option = document.createElement("option"); option.value = k.src; option.textContent = k.title; variant.appendChild(option); } variant.value = this._media.currentSrc; } } */ (function () { 'use strict'; // Support for Mobile Devices document.querySelector("#navigation-toggle").addEventListener("click", (ev) => { document.querySelector("#header #navigation").classList.toggle("open"); }); // Lazily load this._media xmr_media_lazyload_initialize(); // Initialize this._media player. for (let el of document.querySelectorAll("video")) { el.player = new XMediaPlayer(el); } })();