From d6b84ce2cc19849d5b144465c200c50c78227b35 Mon Sep 17 00:00:00 2001 From: Jade Macho Date: Tue, 14 May 2024 18:14:49 +0200 Subject: [PATCH] Even more 10.9 fixes, including a patch for broken syncplay --- Jadefin.js | 8 +-- JadefinModules.js | 20 ++++---- JadefinUtils.js | 62 ++++++++++++++++++++++++ mods.json | 4 +- mods/InputEater.js | 2 +- mods/PatchFixSyncPlay.js | 78 ++++++++++++++++++++++++++++++ mods/{jade => }/PatchForceHLSJS.js | 25 +++++++--- mods/Transcript.js | 2 + mods_jade.json | 5 +- 9 files changed, 181 insertions(+), 25 deletions(-) create mode 100644 mods/PatchFixSyncPlay.js rename mods/{jade => }/PatchForceHLSJS.js (66%) diff --git a/Jadefin.js b/Jadefin.js index 59eb7e0..929aa10 100644 --- a/Jadefin.js +++ b/Jadefin.js @@ -103,7 +103,7 @@ export default JadefinIntegrity("Jadefin", import.meta.url, () => window["Jadefi try { return this.webpackLoad?.(id); } catch (e) { - this.log.w(`Failed to load webpack module ${id}`); + this.log.e(`Failed to load webpack module ${id}`); this.log.dir(e); return null; } @@ -242,21 +242,21 @@ export default JadefinIntegrity("Jadefin", import.meta.url, () => window["Jadefi * @param {(e: any) => any} cb */ findWebpackRawLoad(cb) { - return Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)).filter(e => e && cb(e)); + return JadefinUtils.filterMap(Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)), e => e && cb(e)); } /** * @param {(e: any) => any} cb */ findWebpackModules(cb) { - return Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)?.default).filter(e => e && cb(e)); + return JadefinUtils.filterMap(Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)?.default), e => e && cb(e)); } /** * @param {(e: any) => any} cb */ findWebpackFunctions(cb) { - return Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)).filter(e => e && e instanceof Function && cb(e)); + return JadefinUtils.filterMap(Object.keys(this.webpackModuleFuncs).map(id => this.webpackTryLoad?.(id)), e => e && e instanceof Function && cb(e)); } /** diff --git a/JadefinModules.js b/JadefinModules.js index a2b2540..c393390 100644 --- a/JadefinModules.js +++ b/JadefinModules.js @@ -2,6 +2,8 @@ import JadefinIntegrity from "./JadefinIntegrity.js"; +import JadefinUtils from "./JadefinUtils.js"; + export default JadefinIntegrity("JadefinModules", import.meta.url, () => window["JadefinModules"] = new (class JadefinModules { /** @type {{[id: string]: any}} */ _ = {}; @@ -95,7 +97,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ // toast /** @return {(text: string) => void} */ get toast() { - return this._.toast ??= this.Jadefin.findWebpackRawLoad(e => (e.A?.toString().indexOf(`toast"),t.textContent=`) || -1) != -1)[0]?.A; + return this._.toast ??= this.Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => o.toString().indexOf(`toast"),t.textContent=`) != -1))[0]; } // plugins/syncPlay/core/index @@ -108,7 +110,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ }} */ get syncPlay() { - return this._.syncPlay ??= this.Jadefin.findWebpackModules(e => e.Manager?.isSyncPlayEnabled)[0]; + return this._.syncPlay ??= this.Jadefin.findWebpackModules(e => e.Manager?.isSyncPlayEnabled && 1)[0]; } // inputManager @@ -123,7 +125,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ }} */ get inputManager() { - return this._.inputManager ??= this.Jadefin.findWebpackModules(e => e.handleCommand && e.notify && e.idleTime)[0]; + return this._.inputManager ??= this.Jadefin.findWebpackModules(e => e.handleCommand && e.notify && e.idleTime && 1)[0]; } // playbackManager @@ -131,7 +133,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ @return {any} */ get playbackManager() { - return this._.playbackManager ??= this.Jadefin.findWebpackRawLoad(e => e.f?.canHandleOffsetOnCurrentSubtitle)[0]?.f; + return this._.playbackManager ??= this.Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => o.canHandleOffsetOnCurrentSubtitle))[0]; } // plugins/htmlVideoPlayer/plugin.js @@ -139,7 +141,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ @return {any} */ get htmlVideoPlayer() { - return this._.htmlVideoPlayer ??= this.Jadefin.findWebpackModules(e => e.getDeviceProfileInternal)[0]; + return this._.htmlVideoPlayer ??= this.Jadefin.findWebpackModules(e => e.getDeviceProfileInternal && 1)[0]; } // taskbutton @@ -156,7 +158,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ } */ get taskButton() { - return this._.taskButton ??= this.Jadefin.findWebpackRawLoad(e => (e.A?.toString().indexOf(`sendMessage("ScheduledTasksInfoStart","1000,1000")`) || -1) != -1)[0]?.A; + return this._.taskButton ??= this.Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => o.toString().indexOf(`sendMessage("ScheduledTasksInfoStart","1000,1000")`) != -1))[0]; } // browser @@ -202,7 +204,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ }} */ get browser() { - return this._.browser ??= this.Jadefin.findWebpackRawLoad(e => typeof(e.A) == "object" && "supportsCssAnimation" in e.A && "version" in e.A)[0]?.A; + return this._.browser ??= this.Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => "supportsCssAnimation" in o && "version" in o))[0]; } // datetime @@ -220,7 +222,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ }} */ get datetime() { - return this._.datetime ??= this.Jadefin.findWebpackRawLoad(e => e.Ay?.getDisplayRunningTime)[0]?.Ay; + return this._.datetime ??= this.Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => o.getDisplayRunningTime))[0]; } // events @@ -232,7 +234,7 @@ export default JadefinIntegrity("JadefinModules", import.meta.url, () => window[ }} */ get Events() { - return this._.events ??= this.Jadefin.findWebpackRawLoad(e => e.Events?.on && e.Events?.off && e.Events?.trigger)[0]?.Events; + return this._.events ??= this.Jadefin.findWebpackRawLoad(e => e.Events?.on && e.Events?.off && e.Events?.trigger && 1)[0]?.Events; } })()); diff --git a/JadefinUtils.js b/JadefinUtils.js index 4ca7c4e..1de55f6 100644 --- a/JadefinUtils.js +++ b/JadefinUtils.js @@ -90,4 +90,66 @@ export default JadefinIntegrity("JadefinUtils", import.meta.url, () => window["J return false; } + /** + * @param {{ [x: string]: any; }} obj + * @param {number} levels + * @param {(level: number, v: any, k: string, obj: any) => any} cond + * @param {(level: number, v: any, k: string, obj: any) => void} [cb] + */ + findDeep(obj, levels, cond, cb) { + if (levels <= 0) { + return null; + } + + if (typeof(parent) == "object") { + for (const k in obj) { + let v; + try { + v = obj[k]; + } catch { + continue; + } + + let rv; + try { + rv = cond(levels, v, k, obj); + } catch { + continue; + } + + if (cond(levels, v, k, obj)) { + cb?.(levels, v, k, obj); + + if (levels > 1) { + return this.findDeep(obj, levels - 1, cond, cb); + } + + return v; + } + } + } + + return null; + } + + /** + * @param {any[]} arr + * @param {(value: any, [index]: any, [array]: any) => any} cb + */ + filterMap(arr, cb) { + return arr.reduce((res, value, index, array) => { + const rv = cb(value, index, array); + + if (rv) { + if (typeof(rv) == "object" || typeof(rv) == "function") { + res.push(rv); + } else { + res.push(value); + } + } + + return res; + }, []); + } + })()); diff --git a/mods.json b/mods.json index 1da8c56..a96840e 100644 --- a/mods.json +++ b/mods.json @@ -5,5 +5,7 @@ "VersionCheck.js", "Screenshot.js", "InputEater.js", - "Transcript.js" + "Transcript.js", + "PatchForceHLSJS.js", + "PatchFixSyncPlay.js" ] \ No newline at end of file diff --git a/mods/InputEater.js b/mods/InputEater.js index b9f0200..1de9493 100644 --- a/mods/InputEater.js +++ b/mods/InputEater.js @@ -214,7 +214,7 @@ export default JadefinIntegrity("InputEater", import.meta.url, () => new (class } initHookMediaSessionHandlers() { - const owner = Jadefin.findWebpackRawLoad(e => e?.f?.nextTrack && e?.f?.fastForward)[0].f; + const owner = Jadefin.findWebpackRawLoad(e => JadefinUtils.findDeep(e, 1, (_, o) => o.nextTrack && o.fastForward)); if (!owner || !("mediaSession" in navigator)) { this.log.e("Couldn't hook media session handlers"); diff --git a/mods/PatchFixSyncPlay.js b/mods/PatchFixSyncPlay.js new file mode 100644 index 0000000..b83d4a0 --- /dev/null +++ b/mods/PatchFixSyncPlay.js @@ -0,0 +1,78 @@ +//@ts-check + +import JadefinIntegrity from '../JadefinIntegrity.js'; + +import Jadefin from "../Jadefin.js"; +import JadefinMod from "../JadefinMod.js"; +import JadefinModules from "../JadefinModules.js"; +import JadefinUtils from "../JadefinUtils.js"; + +// Mainly spawned from https://github.com/jellyfin/jellyfin-web/issues/5485 +export default JadefinIntegrity("PatchFixSyncPlay", import.meta.url, () => new (class PatchFixSyncPlay extends JadefinMod { + constructor() { + super(); + + this._scheduleMightSkip = 0; + this._scheduleSkip = 0; + } + + async init(name, url) { + const self = this; + + await super.init(name, url); + + await JadefinUtils.waitUntil(() => JadefinModules.syncPlay); + + const queueCore = JadefinModules.syncPlay.Manager.queueCore; + + const startPlayback = this._startPlayback = queueCore.startPlayback.bind(queueCore); + queueCore.startPlayback = function(apiClient) { + // scheduleReadyRequestOnPlaybackStart must occur BEFORE playerWrapper.localPlay + if (this.manager.isFollowingGroupPlayback() && !this.isPlaylistEmpty()) { + this.scheduleReadyRequestOnPlaybackStart(apiClient, "startPlayback"); + self._scheduleMightSkip++; + self.hookPlayerWrapper(this.manager.getPlayerWrapper()); + } + + return startPlayback(apiClient); + }; + + const scheduleReadyRequestOnPlaybackStart = this._scheduleReadyRequestOnPlaybackStart = queueCore.scheduleReadyRequestOnPlaybackStart.bind(queueCore); + queueCore.scheduleReadyRequestOnPlaybackStart = function(apiClient, origin) { + if (origin == "startPlayback" && self._scheduleSkip > 0) { + self._scheduleSkip--; + return; + } + + return scheduleReadyRequestOnPlaybackStart(apiClient, origin); + }; + } + + /** + * @param {any} playerWrapper + */ + hookPlayerWrapper(playerWrapper) { + if (playerWrapper._PatchFixSyncPlay_localPlay) { + return; + } + + const localPlay = playerWrapper._PatchFixSyncPlay_localPlay = playerWrapper.localPlay.bind(playerWrapper); + playerWrapper.localPlay = (options) => { + const skip = this._scheduleMightSkip > 0; + if (skip) { + this._scheduleMightSkip--; + this._scheduleSkip++; + } + + const rv = localPlay(options); + + rv.catch(() => { + if (skip) { + this._scheduleSkip--; + } + }); + + return rv; + }; + } +})()); diff --git a/mods/jade/PatchForceHLSJS.js b/mods/PatchForceHLSJS.js similarity index 66% rename from mods/jade/PatchForceHLSJS.js rename to mods/PatchForceHLSJS.js index 2944caf..3d7bd0a 100644 --- a/mods/jade/PatchForceHLSJS.js +++ b/mods/PatchForceHLSJS.js @@ -1,10 +1,11 @@ //@ts-check -import JadefinIntegrity from '../../JadefinIntegrity.js'; +import JadefinIntegrity from '../JadefinIntegrity.js'; -import Jadefin from "../../Jadefin.js"; -import JadefinMod from "../../JadefinMod.js"; -import JadefinModules from "../../JadefinModules.js"; +import Jadefin from "../Jadefin.js"; +import JadefinMod from "../JadefinMod.js"; +import JadefinModules from "../JadefinModules.js"; +import JadefinUtils from "../JadefinUtils.js"; // Mainly spawned from https://github.com/jellyfin/jellyfin-android/issues/1031 export default JadefinIntegrity("PatchAndroidHLSJS", import.meta.url, () => new (class PatchAndroidHLSJS extends JadefinMod { @@ -25,12 +26,20 @@ export default JadefinIntegrity("PatchAndroidHLSJS", import.meta.url, () => new async init(name, url) { await super.init(name, url); - const htmlMediaHelper = this.htmlMediaPlayer = Jadefin.findWebpackRawLoad(e => (e.JQ?.toString().indexOf("x-mpegURL") || -1) != -1)[0]; - this._enableHlsJsPlayer = htmlMediaHelper.JQ.bind(htmlMediaHelper); + let enableHlsJsPlayerKey = []; + const htmlMediaHelper = this.htmlMediaPlayer = Jadefin.findWebpackRawLoad( + e => JadefinUtils.findDeep( + e, + 1, + (i, o) => o.toString().indexOf(`.canPlayType("application/vnd.apple.mpegURL").replace(/no/,"")`) != -1, + (i, v, k) => enableHlsJsPlayerKey.push(k) + ) && 1 + )[0]; + this._enableHlsJsPlayer = htmlMediaHelper[enableHlsJsPlayerKey[0]].bind(htmlMediaHelper); - Object.defineProperty(htmlMediaHelper, "JQ", { get: () => this.enableHlsJsPlayer }); + Object.defineProperty(htmlMediaHelper, enableHlsJsPlayerKey[0], { get: () => this.enableHlsJsPlayer }); - const ExtrasMenu = /** @type {import("../ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu")); + const ExtrasMenu = /** @type {import("./ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu")); ExtrasMenu.items.push({ name: "Enable HLS.js", diff --git a/mods/Transcript.js b/mods/Transcript.js index 6ddf581..c74de3b 100644 --- a/mods/Transcript.js +++ b/mods/Transcript.js @@ -620,6 +620,8 @@ export default JadefinIntegrity("Transcript", import.meta.url, () => new (class return el; }); + line.el?.classList.toggle("active", false); + line.el["_data_subLine"] = line; } diff --git a/mods_jade.json b/mods_jade.json index 22460f0..ea377a1 100644 --- a/mods_jade.json +++ b/mods_jade.json @@ -6,7 +6,8 @@ "Screenshot.js", "InputEater.js", "Transcript.js", + "PatchForceHLSJS.js", + "PatchFixSyncPlay.js", - "jade/Shortcuts.js", - "jade/PatchForceHLSJS.js" + "jade/Shortcuts.js" ] \ No newline at end of file