jadefin/mods/InputEater.js

374 lines
12 KiB
JavaScript

//@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";
import { rd, rdom, rd$, RDOMListHelper } from "../utils/rdom.js";
export default JadefinIntegrity("InputEater", import.meta.url, () => new (class InputEater extends JadefinMod {
hasShownPopupThisSession = false;
canBeEating = false;
_isEnabled = true;
_isEating = false;
/** @type {Element | undefined} */
_lastOsdPage;
_btnRemote = rd$()`
<button is="paper-icon-button-light" class="btnRemote autoSize paper-icon-button-light" title="Grab remote">
<span class="xlargePaperIconButton material-icons settings_remote" aria-hidden="true"></span>
</button>
`;
constructor() {
super();
this._btnRemote.addEventListener("click", () => this.isEnabled = false);
this.initHookDocumentAddEventListener();
this.initHookDocumentRemoveEventListener();
}
get isEnabled() {
return this._isEnabled;
}
set isEnabled(value) {
this._isEnabled = value;
this.updateIsEating();
}
get shouldShowPopup() {
return this.storage.get("shouldShowPopup", true);
}
set shouldShowPopup(value) {
this.storage.set("shouldShowPopup", value);
}
get isEating() {
return this._isEating;
}
set isEating(value) {
if (this._isEating == value) {
return;
}
this.log.i(`isEating = ${value}`);
this._isEating = value;
this.update();
}
async init(name, url) {
await super.init(name, url);
await JadefinUtils.waitUntil(() => JadefinModules.syncPlay);
this.initStyle();
this.initHookSyncPlayEnabled();
this.initHookSyncPlayDisabled();
this.initHookMediaSessionHandlers();
this.initHookInputManagerHandler();
document.addEventListener("viewshow", () => {
this.updateIsEating();
});
const ExtrasMenu = /** @type {import("./ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu"));
ExtrasMenu.items.push({
name: "Grab remote",
secondaryText: "Take control over the party",
icon: "settings_remote",
in: ExtrasMenu.IN_CUSTOM,
inCustom: (current, item) => {
if (this.isEnabled) {
item.name = "Grab remote";
item.secondaryText = "Take control over the party";
} else {
item.name = "Disable controls";
item.secondaryText = "Give up control over the party";
}
return this.canBeEating;
},
cb: () => {
this.isEnabled = !this.isEnabled;
}
});
this.log.i("Ready");
}
initHookSyncPlayEnabled() {
const orig = this._enableSyncPlay = JadefinModules.syncPlay.Manager.enableSyncPlay.bind(JadefinModules.syncPlay.Manager);
JadefinModules.syncPlay.Manager.enableSyncPlay = (apiClient, groupInfo, showMessage) => {
const rv = orig(apiClient, groupInfo, showMessage);
this.updateIsEating();
return rv;
};
}
initHookSyncPlayDisabled() {
const orig = this._disableSyncPlay = JadefinModules.syncPlay.Manager.disableSyncPlay.bind(JadefinModules.syncPlay.Manager);
JadefinModules.syncPlay.Manager.disableSyncPlay = (showMessage) => {
const rv = orig(showMessage);
this.updateIsEating();
return rv;
};
}
initHookDocumentAddEventListener() {
const orig = this._addEventListener = document.addEventListener.bind(document);
document.addEventListener = (type, listener, options) => {
if (type == "keydown") {
const listenerStr = listener.toString();
// Anonymous function in playback-video
if (listenerStr.indexOf(".btnPause") != -1 &&
listenerStr.indexOf("32") != -1 &&
listenerStr.indexOf("playPause") != -1) {
this.log.i("Wrapping playback-video keydown listener");
this.log.dir(listener);
const origListener = this._playbackKeyDown = listener;
listener = this._playbackKeyDownWrap = (e) => {
if (this.isEating) {
if (e.keyCode == 32) {
return;
}
switch (e.key) {
case "ArrowLeft":
case "ArrowRight":
case "Enter":
case "Escape":
case "Back":
case "k":
case "l":
case "ArrowRight":
case "Right":
case "j":
case "ArrowLeft":
case "Left":
case "p":
case "P":
case "n":
case "N":
case "NavigationLeft":
case "GamepadDPadLeft":
case "GamepadLeftThumbstickLeft":
case "NavigationRight":
case "GamepadDPadRight":
case "GamepadLeftThumbstickRight":
case "Home":
case "End":
case "0":
case "1":
case "2":
case "3":
case "4":
case "5":
case "6":
case "7":
case "8":
case "9":
case ">":
case "<":
case "PageUp":
case "PageDown":
return;
}
}
return origListener(e);
};
}
}
return orig(type, listener, options);
};
}
initHookDocumentRemoveEventListener() {
const orig = this._removeEventListener = document.removeEventListener.bind(document);
document.removeEventListener = (type, listener, options) => {
if (listener == this._playbackKeyDown) {
listener = this._playbackKeyDownWrap;
}
return orig(type, listener, options);
};
}
initHookMediaSessionHandlers() {
const owner = Jadefin.findWebpackRawLoad(e => e?.f?.nextTrack && e?.f?.fastForward)[0].f;
if (!owner || !("mediaSession" in navigator)) {
this.log.e("Couldn't hook media session handlers");
return;
}
const basic = (name, e) => {
if (this.isEating) {
return;
}
owner[name](owner.getCurrentPlayer());
}
const handlerTuples = [
["previoustrack", (e) => basic("previousTrack", e)],
["nexttrack", (e) => basic("nextTrack", e)],
["play", (e) => basic("unpause", e)],
["pause", (e) => basic("pause", e)],
["seekbackward", (e) => basic("rewind", e)],
["seekforward", (e) => basic("fastForward", e)],
["seekto", (e) => {
if (this.isEating) {
return;
}
const player = owner.getCurrentPlayer();
const item = owner.getPlayerState(player).NowPlayingItem;
const currentTime = Math.floor(item.RunTimeTicks ? item.RunTimeTicks / 10000 : 0);
const seekTime = 1000 * e.seekTime;
owner.seekPercent(seekTime / currentTime * 100, player);
}]
];
const handlers = this._handlers = {};
for (let handlerTuple of handlerTuples) {
this.log.i(`Replacing media session action handler ${handlerTuple[0]}`);
// @ts-ignore
navigator.mediaSession.setActionHandler(handlerTuple[0], handlers[handlerTuple[0]] = handlerTuple[1].bind(this));
}
}
initHookInputManagerHandler() {
const orig = this._handleCommand = JadefinModules.inputManager.handleCommand.bind(JadefinModules.inputManager);
JadefinModules.inputManager.handleCommand = (commandName, options) => {
if (this.isEating) {
switch (commandName) {
case "nextchapter":
case "next":
case "nexttrack":
case "previous":
case "previoustrack":
case "previouschapter":
case "play":
case "pause":
case "playpause":
case "stop":
case "increaseplaybackrate":
case "decreaseplaybackrate":
case "fastforward":
case "rewind":
case "seek":
case "repeatnone":
case "repeatall":
case "repeatone":
return;
}
}
return orig(commandName, options);
};
}
/**
* @param {boolean} [force]
*/
showPopup(force) {
if (!force && (!this.shouldShowPopup || this.hasShownPopupThisSession)) {
return false;
}
this.hasShownPopupThisSession = true;
const ExtrasMenu = /** @type {import("./ExtrasMenu.js").default} */ (Jadefin.getMod("ExtrasMenu"));
JadefinModules.actionSheet.show({
dialogClass: "inputEaterInfo",
positionTo: ExtrasMenu.headerExtrasEl,
title: "Controls disabled.",
text: "You can toggle it in this corner.",
items: [
{name: "Okay", icon: "tune"}
]
});
return true;
}
async updateIsEating() {
let isSyncPlayEnabled = JadefinModules.syncPlay.Manager.isSyncPlayEnabled();
let isSyncPlayOwner = true;
if (isSyncPlayEnabled) {
const groupInfo = JadefinModules.syncPlay.Manager.groupInfo;
const currentUser = await JadefinModules.ApiClient.getCurrentUser();
isSyncPlayOwner = groupInfo.GroupName.indexOf(currentUser.Name) != -1;
}
this.canBeEating = isSyncPlayEnabled && !isSyncPlayOwner && JadefinUtils.routePathIsVideo;
this.isEating = this.isEnabled && this.canBeEating;
}
update() {
document.body.setAttribute("input-eaten", this.isEating ? "true" : "false");
if (this.isEating) {
this.canBeEating = true;
if (!this.showPopup()) {
JadefinModules.toast("Controls disabled.");
}
}
this._btnRemote.classList.toggle("hide", !this.isEating);
const videoOsdPage = document.querySelector("div#videoOsdPage:not(.hide)");
if (!videoOsdPage) {
return;
}
if (this._lastOsdPage == videoOsdPage) {
return;
}
this._lastOsdPage = videoOsdPage;
this.log.i("Adding event listener to videoOsdPage");
this.log.dir(videoOsdPage);
videoOsdPage.addEventListener(window.PointerEvent ? "pointerdown" : "click", e => {
if (this.isEating) {
e.stopPropagation();
}
}, true);
const buttons = videoOsdPage.querySelector(".osdControls > .buttons");
if (this._btnRemote.parentElement != buttons && buttons) {
this.log.i("Adding remote button to osd buttons");
this.log.dir(buttons);
buttons.insertBefore(this._btnRemote, buttons.firstChild);
}
}
})());