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 <github@jumoog.io>
Co-authored-by: rlauu <46294892+rlauu@users.noreply.github.com>
This commit is contained in:
rlauuzo 2024-06-01 14:15:30 +02:00 committed by GitHub
parent 87272075ed
commit 50529c4a0b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 77 additions and 13 deletions

View File

@ -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();

View File

@ -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;