Reduce script overhead for injection
This commit is contained in:
parent
a90175dd66
commit
1c04ce80c3
@ -1,51 +1,39 @@
|
|||||||
let introSkipper = {
|
let introSkipper = {
|
||||||
skipSegments: {},
|
skipSegments: {},
|
||||||
videoPlayer: {},
|
videoPlayer: {},
|
||||||
|
|
||||||
// .bind() is used here to prevent illegal invocation errors
|
// .bind() is used here to prevent illegal invocation errors
|
||||||
originalFetch: window.fetch.bind(window),
|
originalFetch: window.fetch.bind(window),
|
||||||
};
|
};
|
||||||
|
|
||||||
introSkipper.d = function (msg) {
|
introSkipper.d = function (msg) {
|
||||||
console.debug("[intro skipper] ", msg);
|
console.debug("[intro skipper] ", msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Setup event listeners */
|
/** Setup event listeners */
|
||||||
introSkipper.setup = function () {
|
introSkipper.setup = function () {
|
||||||
document.addEventListener("viewshow", introSkipper.viewShow);
|
document.addEventListener("viewshow", introSkipper.viewShow);
|
||||||
window.fetch = introSkipper.fetchWrapper;
|
window.fetch = introSkipper.fetchWrapper;
|
||||||
introSkipper.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. */
|
||||||
introSkipper.fetchWrapper = async function (...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 introSkipper.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 {
|
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;
|
introSkipper.d("Retrieving skip segments from URL");
|
||||||
}
|
|
||||||
|
|
||||||
introSkipper.d("retrieving skip segments from URL");
|
|
||||||
introSkipper.d(path);
|
introSkipper.d(path);
|
||||||
|
|
||||||
let id = path.split("/")[2];
|
let id = path.split("/")[2];
|
||||||
introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroSkipperSegments`);
|
introSkipper.skipSegments = await introSkipper.secureFetch(`Episode/${id}/IntroSkipperSegments`);
|
||||||
|
introSkipper.d("Successfully retrieved skip segments");
|
||||||
introSkipper.d("successfully retrieved skip segments");
|
|
||||||
introSkipper.d(introSkipper.skipSegments);
|
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", resource, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -53,21 +41,17 @@ introSkipper.fetchWrapper = async function (...args) {
|
|||||||
introSkipper.viewShow = function () {
|
introSkipper.viewShow = function () {
|
||||||
const location = window.location.hash;
|
const location = window.location.hash;
|
||||||
introSkipper.d("Location changed to " + location);
|
introSkipper.d("Location changed to " + location);
|
||||||
|
|
||||||
if (location !== "#!/video") {
|
if (location !== "#!/video") {
|
||||||
introSkipper.d("Ignoring location change");
|
introSkipper.d("Ignoring location change");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
introSkipper.d("Adding button CSS and element");
|
introSkipper.d("Adding button CSS and element");
|
||||||
introSkipper.injectCss();
|
introSkipper.injectCss();
|
||||||
introSkipper.injectButton();
|
introSkipper.injectButton();
|
||||||
|
|
||||||
introSkipper.d("Hooking video timeupdate");
|
introSkipper.d("Hooking video timeupdate");
|
||||||
introSkipper.videoPlayer = document.querySelector("video");
|
introSkipper.videoPlayer = document.querySelector("video");
|
||||||
introSkipper.videoPlayer.addEventListener("timeupdate", introSkipper.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.
|
||||||
@ -77,9 +61,7 @@ introSkipper.injectCss = function () {
|
|||||||
introSkipper.d("CSS already added");
|
introSkipper.d("CSS already added");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
introSkipper.d("Adding CSS");
|
introSkipper.d("Adding CSS");
|
||||||
|
|
||||||
let styleElement = document.createElement("style");
|
let styleElement = document.createElement("style");
|
||||||
styleElement.id = "introSkipperCss";
|
styleElement.id = "introSkipperCss";
|
||||||
styleElement.innerText = `
|
styleElement.innerText = `
|
||||||
@ -130,7 +112,6 @@ introSkipper.injectCss = function () {
|
|||||||
`;
|
`;
|
||||||
document.querySelector("head").appendChild(styleElement);
|
document.querySelector("head").appendChild(styleElement);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
@ -141,20 +122,16 @@ introSkipper.injectButton = async function () {
|
|||||||
if (preExistingButton) {
|
if (preExistingButton) {
|
||||||
preExistingButton.style.display = "none";
|
preExistingButton.style.display = "none";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (introSkipper.testElement(".btnSkipIntro.injected")) {
|
if (introSkipper.testElement(".btnSkipIntro.injected")) {
|
||||||
introSkipper.d("Button already added");
|
introSkipper.d("Button already added");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
introSkipper.d("Adding button");
|
introSkipper.d("Adding button");
|
||||||
|
|
||||||
let config = await introSkipper.secureFetch("Intros/UserInterfaceConfiguration");
|
let config = await introSkipper.secureFetch("Intros/UserInterfaceConfiguration");
|
||||||
if (!config.SkipButtonVisible) {
|
if (!config.SkipButtonVisible) {
|
||||||
introSkipper.d("Not adding button: not visible");
|
introSkipper.d("Not adding button: not visible");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Construct the skip button div
|
// Construct the skip button div
|
||||||
const button = document.createElement("div");
|
const button = document.createElement("div");
|
||||||
button.id = "skipIntro"
|
button.id = "skipIntro"
|
||||||
@ -168,25 +145,21 @@ introSkipper.injectButton = async function () {
|
|||||||
`;
|
`;
|
||||||
button.dataset["intro_text"] = config.SkipButtonIntroText;
|
button.dataset["intro_text"] = config.SkipButtonIntroText;
|
||||||
button.dataset["credits_text"] = config.SkipButtonEndCreditsText;
|
button.dataset["credits_text"] = config.SkipButtonEndCreditsText;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Alternative workaround for #44. Jellyfin's video component registers a global click handler
|
* Alternative workaround for #44. Jellyfin's video component registers a global click handler
|
||||||
* (located at src/controllers/playback/video/index.js:1492) that pauses video playback unless
|
* (located at src/controllers/playback/video/index.js:1492) that pauses video playback unless
|
||||||
* the clicked element has a parent with the class "videoOsdBottom" or "upNextContainer".
|
* the clicked element has a parent with the class "videoOsdBottom" or "upNextContainer".
|
||||||
*/
|
*/
|
||||||
button.classList.add("upNextContainer");
|
button.classList.add("upNextContainer");
|
||||||
|
|
||||||
// Append the button to the video OSD
|
// Append the button to the video OSD
|
||||||
let controls = document.querySelector("div#videoOsdPage");
|
let controls = document.querySelector("div#videoOsdPage");
|
||||||
controls.appendChild(button);
|
controls.appendChild(button);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tests if the OSD controls are visible. */
|
/** Tests if the OSD controls are visible. */
|
||||||
introSkipper.osdVisible = function () {
|
introSkipper.osdVisible = function () {
|
||||||
const osd = document.querySelector("div.videoOsdBottom");
|
const osd = document.querySelector("div.videoOsdBottom");
|
||||||
return osd ? !osd.classList.contains("hide") : false;
|
return osd ? !osd.classList.contains("hide") : false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Get the currently playing skippable segment. */
|
/** Get the currently playing skippable segment. */
|
||||||
introSkipper.getCurrentSegment = function (position) {
|
introSkipper.getCurrentSegment = function (position) {
|
||||||
for (let key in introSkipper.skipSegments) {
|
for (let key in introSkipper.skipSegments) {
|
||||||
@ -196,71 +169,49 @@ introSkipper.getCurrentSegment = function (position) {
|
|||||||
return segment;
|
return segment;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { "SegmentType": "None" };
|
return { "SegmentType": "None" };
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Playback position changed, check if the skip button needs to be displayed. */
|
/** Playback position changed, check if the skip button needs to be displayed. */
|
||||||
introSkipper.videoPositionChanged = function () {
|
introSkipper.videoPositionChanged = function () {
|
||||||
const skipButton = document.querySelector("#skipIntro");
|
const skipButton = document.querySelector("#skipIntro");
|
||||||
if (!skipButton) {
|
if (!skipButton) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime);
|
const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime);
|
||||||
switch (segment["SegmentType"]) {
|
switch (segment["SegmentType"]) {
|
||||||
case "None":
|
case "None":
|
||||||
skipButton.classList.add("hide");
|
skipButton.classList.add("hide");
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case "Introduction":
|
case "Introduction":
|
||||||
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
||||||
skipButton.dataset["intro_text"];
|
skipButton.dataset["intro_text"];
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case "Credits":
|
case "Credits":
|
||||||
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
skipButton.querySelector("#btnSkipSegmentText").textContent =
|
||||||
skipButton.dataset["credits_text"];
|
skipButton.dataset["credits_text"];
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
skipButton.classList.remove("hide");
|
skipButton.classList.remove("hide");
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Seeks to the end of the intro. */
|
/** Seeks to the end of the intro. */
|
||||||
introSkipper.doSkip = function (e) {
|
introSkipper.doSkip = function (e) {
|
||||||
introSkipper.d("Skipping intro");
|
introSkipper.d("Skipping intro");
|
||||||
introSkipper.d(introSkipper.skipSegments);
|
introSkipper.d(introSkipper.skipSegments);
|
||||||
|
|
||||||
const segment = introSkipper.getCurrentSegment(introSkipper.videoPlayer.currentTime);
|
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");
|
console.warn("[intro skipper] doSkip() called without an active segment");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
introSkipper.videoPlayer.currentTime = segment["IntroEnd"];
|
introSkipper.videoPlayer.currentTime = segment["IntroEnd"];
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Tests if an element with the provided selector exists. */
|
/** Tests if an element with the provided selector exists. */
|
||||||
introSkipper.testElement = function (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. */
|
||||||
introSkipper.secureFetch = async function (url) {
|
introSkipper.secureFetch = async function (url) {
|
||||||
url = ApiClient.serverAddress() + "/" + url;
|
url = ApiClient.serverAddress() + "/" + url;
|
||||||
|
const reqInit = { headers: { "Authorization": "MediaBrowser Token=" + ApiClient.accessToken() } };
|
||||||
const reqInit = {
|
|
||||||
headers: {
|
|
||||||
"Authorization": "MediaBrowser Token=" + ApiClient.accessToken()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const res = await fetch(url, reqInit);
|
const res = await fetch(url, reqInit);
|
||||||
|
if (res.status !== 200) { throw new Error(`Expected status 200 from ${url}, but got ${res.status}`); }
|
||||||
if (res.status !== 200) {
|
|
||||||
throw new Error(`Expected status 200 from ${url}, but got ${res.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return await res.json();
|
return await res.json();
|
||||||
}
|
}
|
||||||
|
|
||||||
introSkipper.setup();
|
introSkipper.setup();
|
@ -2,8 +2,8 @@
|
|||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net6.0</TargetFramework>
|
<TargetFramework>net6.0</TargetFramework>
|
||||||
<RootNamespace>ConfusedPolarBear.Plugin.IntroSkipper</RootNamespace>
|
<RootNamespace>ConfusedPolarBear.Plugin.IntroSkipper</RootNamespace>
|
||||||
<AssemblyVersion>0.1.15.0</AssemblyVersion>
|
<AssemblyVersion>0.1.16.0</AssemblyVersion>
|
||||||
<FileVersion>0.1.15.0</FileVersion>
|
<FileVersion>0.1.16.0</FileVersion>
|
||||||
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
<GenerateDocumentationFile>true</GenerateDocumentationFile>
|
||||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user