Relocate injected code into an object

This commit is contained in:
ConfusedPolarBear 2022-11-25 01:37:27 -06:00
parent 50e1d15cb9
commit 39d235006f

View File

@ -1,44 +1,46 @@
let nowPlayingItemSkipSegments = {}; let introSkipper = {
let videoPlayer = {}; skipSegments: {},
let originalFetch = window.fetch; videoPlayer: {},
function d(msg) { // .bind() is used here to prevent illegal invocation errors
originalFetch: window.fetch.bind(window),
};
introSkipper.d = function (msg) {
console.debug("[intro skipper]", msg); console.debug("[intro skipper]", msg);
} }
/** Setup event listeners */ /** Setup event listeners */
function setup() { introSkipper.setup = function () {
document.addEventListener("viewshow", viewshow); document.addEventListener("viewshow", introSkipper.viewShow);
window.fetch = fetchWrapper; window.fetch = introSkipper.fetchWrapper;
d("Registered hooks"); introSkipper.d("Registered hooks");
} }
/** Wrapper around fetch() that retrieves skip segments for the currently playing item. */ /** Wrapper around fetch() that retrieves skip segments for the currently playing item. */
async function fetchWrapper(...args) { introSkipper.fetchWrapper = async function (...args) {
// Based on JellyScrub's trickplay.js // Based on JellyScrub's trickplay.js
let [resource, options] = args; let [resource, options] = args;
let response = await originalFetch(resource, options); let response = await introSkipper.originalFetch(resource, options);
// Bail early if this isn't a playback info URL // Bail early if this isn't a playback info URL
try {
let path = new URL(resource).pathname; let path = new URL(resource).pathname;
if (!path.includes("/PlaybackInfo")) { if (!path.includes("/PlaybackInfo")) {
return response; return response;
} }
try introSkipper.d("retrieving skip segments from URL");
{ introSkipper.d(path);
d("retrieving skip segments from URL");
d(path);
let id = path.split("/")[2]; let id = path.split("/")[2];
nowPlayingItemSkipSegments = await authenticatedFetch(`Episode/${id}/IntroTimestamps/v1`); introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroTimestamps/v1`);
d("successfully retrieved skip segments"); introSkipper.d("successfully retrieved skip segments");
d(nowPlayingItemSkipSegments); introSkipper.d(introSkipper.skipSegments);
} }
catch (e) catch (e) {
{ console.error("unable to get skip segments from", resource, e);
console.error("unable to get skip segments from", path, e);
} }
return response; return response;
@ -48,36 +50,35 @@ async function fetchWrapper(...args) {
* Event handler that runs whenever the current view changes. * Event handler that runs whenever the current view changes.
* Used to detect the start of video playback. * Used to detect the start of video playback.
*/ */
function viewshow() { introSkipper.viewShow = function () {
const location = window.location.hash; const location = window.location.hash;
d("Location changed to " + location); introSkipper.d("Location changed to " + location);
if (location !== "#!/video") { if (location !== "#!/video") {
d("Ignoring location change"); introSkipper.d("Ignoring location change");
return; return;
} }
d("Adding button CSS and element"); introSkipper.d("Adding button CSS and element");
injectSkipButtonCss(); introSkipper.injectCss();
injectSkipButtonElement(); introSkipper.injectButton();
d("Hooking video timeupdate"); introSkipper.d("Hooking video timeupdate");
videoPlayer = document.querySelector("video"); introSkipper.videoPlayer = document.querySelector("video");
videoPlayer.addEventListener("timeupdate", videoPositionChanged); introSkipper.videoPlayer.addEventListener("timeupdate", introSkipper.videoPositionChanged);
} }
/** /**
* Injects the CSS used by the skip intro button. * Injects the CSS used by the skip intro button.
* Calling this function is a no-op if the CSS has already been injected. * Calling this function is a no-op if the CSS has already been injected.
*/ */
function injectSkipButtonCss() { introSkipper.injectCss = function () {
if (testElement("style#introSkipperCss")) if (introSkipper.testElement("style#introSkipperCss")) {
{ introSkipper.d("CSS already added");
d("CSS already added");
return; return;
} }
d("Adding CSS"); introSkipper.d("Adding CSS");
let styleElement = document.createElement("style"); let styleElement = document.createElement("style");
styleElement.id = "introSkipperCss"; styleElement.id = "introSkipperCss";
@ -129,17 +130,17 @@ function injectSkipButtonCss() {
* Inject the skip intro button into the video player. * Inject the skip intro button into the video player.
* Calling this function is a no-op if the CSS has already been injected. * Calling this function is a no-op if the CSS has already been injected.
*/ */
async function injectSkipButtonElement() { introSkipper.injectButton = async function () {
if (testElement(".btnSkipIntro")) { if (introSkipper.testElement(".btnSkipIntro")) {
d("Button already added"); introSkipper.d("Button already added");
return; return;
} }
d("Adding button"); introSkipper.d("Adding button");
let config = await authenticatedFetch("Intros/UserInterfaceConfiguration"); let config = await introSkipper.secureFetch("Intros/UserInterfaceConfiguration");
if (!config.SkipButtonVisible) { if (!config.SkipButtonVisible) {
d("Not adding button: not visible"); introSkipper.d("Not adding button: not visible");
return; return;
} }
@ -147,7 +148,7 @@ async function injectSkipButtonElement() {
const button = document.createElement("div"); const button = document.createElement("div");
button.id = "skipIntro" button.id = "skipIntro"
button.classList.add("hide"); button.classList.add("hide");
button.addEventListener("click", skipIntro); button.addEventListener("click", introSkipper.doSkip);
button.innerHTML = ` button.innerHTML = `
<button is="paper-icon-button-light" class="btnSkipIntro paper-icon-button-light"> <button is="paper-icon-button-light" class="btnSkipIntro paper-icon-button-light">
<span id="btnSkipIntroText"></span> <span id="btnSkipIntroText"></span>
@ -170,9 +171,9 @@ async function injectSkipButtonElement() {
} }
/** Playback position changed, check if the skip button needs to be displayed. */ /** Playback position changed, check if the skip button needs to be displayed. */
function videoPositionChanged() { introSkipper.videoPositionChanged = function () {
// Ensure a skip segment was found. // Ensure a skip segment was found.
if (!nowPlayingItemSkipSegments?.Valid) { if (!introSkipper.skipSegments?.Valid) {
return; return;
} }
@ -181,9 +182,9 @@ function videoPositionChanged() {
return; return;
} }
const position = videoPlayer.currentTime; const position = introSkipper.videoPlayer.currentTime;
if (position >= nowPlayingItemSkipSegments.ShowSkipPromptAt && if (position >= introSkipper.skipSegments.ShowSkipPromptAt &&
position < nowPlayingItemSkipSegments.HideSkipPromptAt) { position < introSkipper.skipSegments.HideSkipPromptAt) {
skipButton.classList.remove("hide"); skipButton.classList.remove("hide");
return; return;
} }
@ -192,17 +193,17 @@ function videoPositionChanged() {
} }
/** Seeks to the end of the intro. */ /** Seeks to the end of the intro. */
function skipIntro(e) { introSkipper.doSkip = function (e) {
d("Skipping intro"); introSkipper.d("Skipping intro");
d(nowPlayingItemSkipSegments); introSkipper.d(introSkipper.skipSegments);
videoPlayer.currentTime = nowPlayingItemSkipSegments.IntroEnd; introSkipper.videoPlayer.currentTime = introSkipper.skipSegments.IntroEnd;
} }
/** Tests if an element with the provided selector exists. */ /** Tests if an element with the provided selector exists. */
function testElement(selector) { return document.querySelector(selector); } introSkipper.testElement = function (selector) { return document.querySelector(selector); }
/** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */ /** Make an authenticated fetch to the Jellyfin server and parse the response body as JSON. */
async function authenticatedFetch(url) { introSkipper.secureFetch = async function (url) {
url = ApiClient.serverAddress() + "/" + url; url = ApiClient.serverAddress() + "/" + url;
const reqInit = { const reqInit = {
@ -220,4 +221,4 @@ async function authenticatedFetch(url) {
return await res.json(); return await res.json();
} }
setup(); introSkipper.setup();