diff --git a/assets/site.js b/assets/site.js index 9397e65..e441e4e 100644 --- a/assets/site.js +++ b/assets/site.js @@ -1,3 +1,7 @@ +'use strict'; + +import("./HTMLMediaElement.mjs"); + async function xmr_media_lazyload_perform(element) { let content = element.querySelector("noscript"); if (content) { @@ -22,262 +26,374 @@ async function xmr_initialize_player(el) { if (!media) el.querySelector("audio"); - // Play/Pause let play_pause = el.querySelector(".play"); - play_pause.update = function() { - if (media.paused) { - play_pause.classList.remove("playing"); - play_pause.innerText = "⏵"; - } else if (media.error || media.ended) { - play_pause.classList.remove("playing"); - play_pause.innerText = "⏹"; - } else { - play_pause.classList.add("playing"); - play_pause.innerText = "⏸"; - } - } - play_pause.addEventListener("click", () => { - if (media.paused) { - media.play(); - } else { - media.pause(); - } - }); - media.addEventListener("play", () => { play_pause.update(); }); - media.addEventListener("pause", () => { play_pause.update(); }); - media.addEventListener("ended", () => { play_pause.update(); }); - media.addEventListener("error", () => { play_pause.update(); }); - play_pause.update(); - - // Mute - let audio_mute = el.querySelector(".mute"); - audio_mute.update = function() { - if (media.muted) { - audio_mute.classList.add("muted"); - audio_mute.innerText = "🔇"; - } else { - audio_mute.classList.remove("muted"); - if (media.volume > 0.5) { - audio_mute.innerText = "🔊"; - } else if (media.volume > 0.125) { - audio_mute.innerText = "🔉"; + { // Play/Pause + play_pause.update = function() { + if (media.paused) { + play_pause.classList.remove("playing"); + play_pause.dataset.symbol = "⏵"; + play_pause.ariaLabel = "Play"; + } else if (media.error || media.ended) { + play_pause.classList.remove("playing"); + play_pause.dataset.symbol = "⏹"; + play_pause.ariaLabel = "Stopped"; } else { - audio_mute.innerText = "🔈"; + play_pause.classList.add("playing"); + play_pause.dataset.symbol = "⏸"; + play_pause.ariaLabel = "Pause"; } } - } - audio_mute.addEventListener("click", () => { - media.muted = !media.muted; - }); - media.addEventListener("volumechange", () => { - audio_mute.update(); - }); - audio_mute.update(); - - // Volume - let audio_volume = el.querySelector(".volume"); - audio_volume.min = 0; - audio_volume.max = 2147483647; - audio_volume.update = function() { - let min = parseInt(audio_volume.min, 10); - let max = parseInt(audio_volume.max, 10); - let dlt = max - min; - audio_volume.value = (media.volume * dlt) + min; - if (media.muted) { - audio_volume.classList.add("muted"); - } else { - audio_volume.classList.remove("muted"); - } - } - audio_volume.addEventListener("input", () => { - let min = parseInt(audio_volume.min, 10); - let max = parseInt(audio_volume.max, 10); - let val = parseInt(audio_volume.value, 10); - let dlt = max - min; - let vol = (val + min) / dlt; - media.volume = vol; - media.muted = (media.volume < 0.01); - }); - media.addEventListener("volumechange", () => { audio_volume.update(); }); - audio_volume.update(); - - // Time - let playback_time = el.querySelector(".time"); - playback_time.update = function() { - 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(media.duration, true); - let current = formatSeconds(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) { - playback_time.innerText = `${current.join(':')} / ${duration.join(':')}`; - } else { - playback_time.innerText = `${current.join(':')}`; - } - } - media.addEventListener("timeupdate", () => { playback_time.update(); }); - media.addEventListener("durationupdate", () => { playback_progress.update(); }); - media.addEventListener("loadedmetadata", () => { playback_time.update(); }); - playback_time.update(); - - // Progress - let playback_progress = el.querySelector(".progress"); - playback_progress.update = function() { - playback_progress.disabled = !media.seekable; - playback_progress.min = 0; - playback_progress.max = media.duration * 100; - playback_progress.value = media.currentTime * 100; - } - playback_progress.addEventListener("input", () => { - if (isFinite(media.duration)) { - media.currentTime = parseInt(playback_progress.value, 10) / 100; - } - }); - media.addEventListener("timeupdate", () => { playback_progress.update(); }); - media.addEventListener("durationupdate", () => { playback_progress.update(); }); - media.addEventListener("loadeddata", () => { playback_progress.update(); }); - media.addEventListener("loadedmetadata", () => { playback_progress.update(); }); - playback_progress.update(); - - // Fullscreen - let fullscreen = el.querySelector(".fullscreen"); - fullscreen.update = function() { - if (document.fullscreenElement) { - fullscreen.innerText = "⬚"; - } else { - fullscreen.innerText = "⛶"; - } - }; - fullscreen.addEventListener("click", () => { - if (document.fullscreenElement) { - document.exitFullscreen(); - } else { - el.requestFullscreen(); - } - }); - el.addEventListener("fullscreenchange", (ev) => { - fullscreen.update(); - }); - fullscreen.update(); - - // Media Play/Pause by click - media.addEventListener("click", () => { - play_pause.click(); - }) - - // Media Fullscreen by dblclick - media.addEventListener("dblclick", () => { - fullscreen.click(); - }) - - // Hide/Show controls - el.showOverlay = function() { - el.cancelHideOverlay(); - el.classList.remove("hide"); - } - el.hideOverlay = function() { - if (!media.paused && !media.ended && !media.error) { - el.classList.add("hide"); - } - } - el.delayHideOverlay = function() { - el.cancelHideOverlay(); - el.timer = setTimeout(() => { el.hideOverlay(); }, 2500); - } - el.cancelHideOverlay = function() { - if (el.timer) { - clearTimeout(el.timer); - } - } - el.addEventListener("mousemove", () => { - el.showOverlay(); - if (!media.paused && !media.ended && !media.error) { - el.delayHideOverlay(); - } - }); - el.hideOverlay(); - - // Show/Hide Audio controls without audio. - function checkAudioPresence() { - let hasAudio = Boolean(media.mozHasAudio) - || Boolean(media.webkitAudioDecodedByteCount) - || Boolean(media.audioTracks && media.audioTracks.length); - audio_mute.style.display = hasAudio ? "" : "none"; - audio_volume.style.display = hasAudio ? "" : "none"; - } - media.addEventListener("loadedmetadata", () => { checkAudioPresence() }); - media.addEventListener("loadeddata", () => { checkAudioPresence() }); - checkAudioPresence(); - - // Variants - let variant = el.querySelector(".variant"); - variant.update = function() { - // Store current state and pause. - let paused = media.paused; - let muted = media.muted; - let volume = media.volume; - let time = media.currentTime; - media.pause(); - play_pause.update(); - - // Tell the media source to begin loading. - media.src = variant.value; - media.load(); - media.currentTime = time; - - media.addEventListener("canplay", () => { - media.currentTime = time; - media.volume = volume; - media.muted = muted; - if (paused) { - media.pause(); - } else { + play_pause.addEventListener("click", () => { + if (media.paused) { media.play(); + } else { + media.pause(); + } + }); + play_pause.addEventListener("keydown", (ev) => { + if (ev.isComposing || ev.keyCode === 229) { + // Ignore IME compositing. + return; + } + if (["Space", "Enter"].includes(ev.code)) { + play_pause.click(); + ev.preventDefault(); + ev.stopPropagation(); + } + }); + media.addEventListener("play", () => { play_pause.update(); }); + media.addEventListener("pause", () => { play_pause.update(); }); + media.addEventListener("ended", () => { play_pause.update(); }); + media.addEventListener("error", () => { play_pause.update(); }); + play_pause.update(); + } + + let playback_time = el.querySelector(".time"); + { // Time + playback_time.update = function() { + 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(media.duration, true); + let current = formatSeconds(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) { + playback_time.innerText = `${current.join(':')} / ${duration.join(':')}`; + } else { + playback_time.innerText = `${current.join(':')}`; + } + } + media.addEventListener("timeupdate", () => { playback_time.update(); }); + media.addEventListener("durationupdate", () => { playback_progress.update(); }); + media.addEventListener("loadedmetadata", () => { playback_time.update(); }); + playback_time.update(); + } + + let playback_progress = el.querySelector(".progress"); + { // Progress + playback_progress.update = function() { + playback_progress.disabled = !media.seekable; + playback_progress.min = 0; + playback_progress.max = media.duration * 100; + playback_progress.value = media.currentTime * 100; + } + playback_progress.addEventListener("input", () => { + if (isFinite(media.duration)) { + media.currentTime = parseInt(playback_progress.value, 10) / 100; + } + }); + media.addEventListener("timeupdate", () => { playback_progress.update(); }); + media.addEventListener("durationupdate", () => { playback_progress.update(); }); + media.addEventListener("loadeddata", () => { playback_progress.update(); }); + media.addEventListener("loadedmetadata", () => { playback_progress.update(); }); + playback_progress.update(); + } + + let audio_mute = el.querySelector(".mute"); + { // Mute + audio_mute.update = function() { + if (media.muted) { + audio_mute.classList.add("muted"); + audio_mute.dataset.symbol = "🔇"; + audio_mute.ariaLabel = "Unmute"; + } else { + audio_mute.classList.remove("muted"); + if (media.volume > 0.5) { + audio_mute.dataset.symbol = "🔊"; + } else if (media.volume > 0.25) { + audio_mute.dataset.symbol = "🔉"; + } else { + audio_mute.dataset.symbol = "🔈"; + } + audio_mute.ariaLabel = `Volume: ${(media.volume * 100).toString(10)}%`; + } + } + audio_mute.addEventListener("click", () => { + media.muted = !media.muted; + }); + audio_mute.addEventListener("keydown", (ev) => { + if (ev.isComposing || ev.keyCode === 229) { + // Ignore IME compositing. + return; + } + if (["Space", "Enter"].includes(ev.code)) { + audio_mute.click(); + ev.preventDefault(); + ev.stopPropagation(); + } + }); + media.addEventListener("volumechange", () => { + audio_mute.update(); + }); + audio_mute.update(); + } + + let audio_volume = el.querySelector(".volume"); + { // Volume + audio_volume.min = 0; + audio_volume.max = 2147483647; + audio_volume.update = function() { + let min = parseInt(audio_volume.min, 10); + let max = parseInt(audio_volume.max, 10); + let dlt = max - min; + audio_volume.value = (media.logVolume * dlt) + min; + audio_volume.ariaLabel = `Volume: ${(media.volume * 100).toString(10)}%`; + if (media.muted) { + audio_volume.classList.add("muted"); + } else { + audio_volume.classList.remove("muted"); + } + } + audio_volume.addEventListener("input", () => { + let min = parseInt(audio_volume.min, 10); + let max = parseInt(audio_volume.max, 10); + let val = parseInt(audio_volume.value, 10); + let dlt = max - min; + let vol = (val + min) / dlt; + media.logVolume = vol; + media.muted = (media.volume < 0.01); + }); + media.addEventListener("volumechange", () => { audio_volume.update(); }); + audio_volume.update(); + } + + let fullscreen = el.querySelector(".fullscreen"); + { // Fullscreen + fullscreen.update = function() { + if (document.fullscreenElement) { + fullscreen.dataset.symbol = "⬚"; + } else { + fullscreen.dataset.symbol = "⛶"; + } + }; + fullscreen.addEventListener("click", () => { + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + el.requestFullscreen(); + } + }); + fullscreen.addEventListener("keydown", (ev) => { + if (ev.isComposing || ev.keyCode === 229) { + // Ignore IME compositing. + return; + } + if (["Space", "Enter"].includes(ev.code)) { + fullscreen.click(); + ev.preventDefault(); + } + }) + el.addEventListener("fullscreenchange", () => { + fullscreen.update(); + }); + fullscreen.update(); + } + + let variant = el.querySelector(".variant"); + { // Variants + variant.update = function() { + // Store current state and pause. + let paused = media.paused; + let muted = media.muted; + let volume = media.volume; + let time = media.currentTime; + media.pause(); play_pause.update(); - }, { - "once": true + + // Tell the media source to begin loading. + media.src = variant.value; + media.load(); + media.currentTime = time; + + media.addEventListener("canplay", () => { + media.currentTime = time; + media.volume = volume; + media.muted = muted; + if (paused) { + media.pause(); + } else { + 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 = media.currentSrc; + } + + { // Keyboard/Mouse Controls + el.addEventListener("keydown", (ev) => { + if (ev.isComposing || ev.keyCode === 229) { + // Ignore IME compositing. + return; + } + + if (["f", "F"].includes(ev.key)) { + fullscreen.click(); + ev.preventDefault(); + ev.stopPropagation(); + } else if (["m", "M"].includes(ev.key)) { + audio_mute.click(); + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "Space") { + play_pause.click(); + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "Home") { + media.currentTime = 0; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "ArrowLeft") { + media.currentTime -= 5; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "Comma") { + media.currentTime -= .1; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "Period") { + media.currentTime += .1; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "ArrowRight") { + media.currentTime += 5; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "End") { + media.currentTime = media.duration; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "ArrowUp") { + media.volume = Math.max(0, Math.min(media.logVolume + 0.05, 1.0)); + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "ArrowDown") { + media.volume = Math.max(0, Math.min(media.logVolume - 0.05, 1.0)); + ev.preventDefault(); + ev.stopPropagation(); + } else if (["p", "P"].includes(ev.key)) { + media.preservesPitch = !media.preservesPitch; + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "PageDown") { + media.playbackRate = Math.max(0.25, Math.min(media.playbackRate - 0.05, 4.0)); + ev.preventDefault(); + ev.stopPropagation(); + } else if (ev.code === "PageUp") { + media.playbackRate = Math.max(0.25, Math.min(media.playbackRate + 0.05, 4.0)); + ev.preventDefault(); + ev.stopPropagation(); + } + }); + + media.addEventListener("click", () => { + play_pause.click(); + }); + + media.addEventListener("dblclick", () => { + fullscreen.click(); }) } - 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); + { // Hide/Show controls + el.showOverlay = function() { + el.cancelHideOverlay(); + el.classList.remove("hide"); + } + el.hideOverlay = function() { + if (!media.paused && !media.ended && !media.error) { + el.classList.add("hide"); + } + } + el.delayHideOverlay = function() { + el.cancelHideOverlay(); + el.timer = setTimeout(() => { el.hideOverlay(); }, 2500); + } + el.cancelHideOverlay = function() { + if (el.timer) { + clearTimeout(el.timer); + } + } + el.addEventListener("mousemove", () => { + el.showOverlay(); + if (!media.paused && !media.ended && !media.error) { + el.delayHideOverlay(); + } + }); + el.hideOverlay(); + } + + { // Show/Hide Audio controls without audio. + function checkAudioPresence() { + let hasAudio = Boolean(media.mozHasAudio) + || Boolean(media.webkitAudioDecodedByteCount) + || Boolean(media.audioTracks && media.audioTracks.length); + audio_mute.style.display = hasAudio ? "" : "none"; + audio_volume.style.display = hasAudio ? "" : "none"; + } + media.addEventListener("loadedmetadata", () => { checkAudioPresence() }); + media.addEventListener("loadeddata", () => { checkAudioPresence() }); + checkAudioPresence(); } - variant.value = media.currentSrc; // Signal the browser to try and load some information. media.load();