From 50529c4a0b43d9cfc86607bd9f4c8e3c1cbd1833 Mon Sep 17 00:00:00 2001 From: rlauuzo <46294892+rlauuzo@users.noreply.github.com> Date: Sat, 1 Jun 2024 14:15:30 +0200 Subject: [PATCH] Allow the intro skip button to be confirmed with the Enter key (#189) * allow the intro skip button to be confirmed with the Enter key tested with LG webOS * add some comments * Update inject.js (#173) * Update inject.js * Update inject.js --------- Co-authored-by: Kilian von Pflugk Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com> --- .../Configuration/inject.js | 86 ++++++++++++++++--- .../Controllers/SkipIntroController.cs | 4 +- 2 files changed, 77 insertions(+), 13 deletions(-) diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js index 8269578..d794c3c 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Configuration/inject.js @@ -1,4 +1,5 @@ let introSkipper = { + allowEnter: true, skipSegments: {}, videoPlayer: {}, // .bind() is used here to prevent illegal invocation errors @@ -60,6 +61,7 @@ introSkipper.d = function (msg) { if (introSkipper.videoPlayer != null) { introSkipper.d("Hooking video timeupdate"); introSkipper.videoPlayer.addEventListener("timeupdate", introSkipper.videoPositionChanged); + document.body.addEventListener('keydown', introSkipper.eventHandler, true); } } /** @@ -169,13 +171,28 @@ introSkipper.getCurrentSegment = function (position) { } return { "SegmentType": "None" }; } +introSkipper.overrideBlur = function(embyButton) { + if (!embyButton.originalBlur) { + embyButton.originalBlur = embyButton.blur; + } + embyButton.blur = function () { + if (!introSkipper.osdVisible() || !embyButton.contains(document.activeElement)) { + embyButton.originalBlur.call(this); + } + }; +}; +introSkipper.restoreBlur = function(embyButton) { + if (embyButton.originalBlur) { + embyButton.blur = embyButton.originalBlur; + delete embyButton.originalBlur; + } +}; /** Playback position changed, check if the skip button needs to be displayed. */ introSkipper.videoPositionChanged = function () { const skipButton = document.querySelector("#skipIntro"); - if (!skipButton) { - return; - } + if (introSkipper.videoPlayer.currentTime === 0 || !skipButton || !introSkipper.allowEnter) return; const embyButton = skipButton.querySelector(".emby-button"); + const tvLayout = document.documentElement.classList.contains("layout-tv"); const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime); switch (segment.SegmentType) { case "None": @@ -184,6 +201,10 @@ introSkipper.videoPositionChanged = function () { embyButton.style.opacity = '0'; embyButton.addEventListener("transitionend", () => { skipButton.classList.add("hide"); + if (tvLayout) { + introSkipper.restoreBlur(embyButton); + embyButton.blur(); + } }, { once: true }); return; case "Introduction": @@ -196,22 +217,44 @@ introSkipper.videoPositionChanged = function () { if (!skipButton.classList.contains("hide")) return; skipButton.classList.remove("hide"); - embyButton.offsetWidth; // Force reflow - requestAnimationFrame(() => { - embyButton.style.opacity = '1'; - }); + embyButton.style.opacity = '1'; + + if (tvLayout) { + introSkipper.overrideBlur(embyButton); + embyButton.focus({ focusVisible: true }); + } +} +introSkipper.throttle = function (func, limit) { + let inThrottle; + return function(...args) { + const context = this; + if (!inThrottle) { + func.apply(context, args); + inThrottle = true; + setTimeout(() => inThrottle = false, limit); + } + }; } /** Seeks to the end of the intro. */ -introSkipper.doSkip = function (e) { +introSkipper.doSkip = introSkipper.throttle(function (e) { introSkipper.d("Skipping intro"); introSkipper.d(introSkipper.skipSegments); const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime); - if (segment["SegmentType"] === "None") { + if (segment.SegmentType === "None") { console.warn("[intro skipper] doSkip() called without an active segment"); return; } - introSkipper.videoPlayer.currentTime = segment["IntroEnd"]; -} + // Disable keydown events + introSkipper.allowEnter = false; + introSkipper.videoPlayer.currentTime = segment.IntroEnd; + // Listen for the seeked event to re-enable keydown events + const onSeeked = async () => { + await new Promise(resolve => setTimeout(resolve, 50)); // Wait 50ms + introSkipper.allowEnter = true; + introSkipper.videoPlayer.removeEventListener('seeked', onSeeked); + }; + introSkipper.videoPlayer.addEventListener('seeked', onSeeked); +}, 3000); /** Tests if an element with the provided selector exists. */ introSkipper.testElement = function (selector) { return document.querySelector(selector); } /** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */ @@ -222,4 +265,25 @@ introSkipper.secureFetch = async function (url) { if (res.status !== 200) { throw new Error(`Expected status 200 from ${url}, but got ${res.status}`); } return await res.json(); } +/** Handle keydown events. */ +introSkipper.eventHandler = function (e) { + const skipButton = document.querySelector("#skipIntro"); + if (!skipButton || skipButton.classList.contains("hide")) return; + // Ignore all keydown events + if (!introSkipper.allowEnter) { + e.preventDefault(); + return; + } + if (e.key !== "Enter") return; + const embyButton = skipButton.querySelector(".emby-button"); + if (document.documentElement.classList.contains("layout-tv") && embyButton.contains(document.activeElement)) { + e.stopPropagation(); + return; + } + if (document.documentElement.classList.contains("layout-desktop")) { + e.preventDefault(); + e.stopPropagation(); + introSkipper.doSkip(); + } +} introSkipper.setup(); diff --git a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs index f6125a5..4e65f22 100644 --- a/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs +++ b/ConfusedPolarBear.Plugin.IntroSkipper/Controllers/SkipIntroController.cs @@ -92,14 +92,14 @@ public class SkipIntroController : ControllerBase if (config.PersistSkipButton) { segment.ShowSkipPromptAt = segment.IntroStart; - segment.HideSkipPromptAt = segment.IntroEnd - 1; + segment.HideSkipPromptAt = segment.IntroEnd; } else { segment.ShowSkipPromptAt = Math.Max(0, segment.IntroStart - config.ShowPromptAdjustment); segment.HideSkipPromptAt = Math.Min( segment.IntroStart + config.HidePromptAdjustment, - segment.IntroEnd - 1); + segment.IntroEnd); } return segment;