Skip to content

Commit

Permalink
feat(webpage): generic website support
Browse files Browse the repository at this point in the history
  • Loading branch information
aidenlx committed Feb 9, 2024
1 parent 00a97d2 commit 3e3b749
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 80 deletions.
89 changes: 88 additions & 1 deletion apps/app/src/lib/remote-player/lib/plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,41 @@ import { fluentTimeUpdate } from "../hook/time-update";
import { mountedEvent, type MsgCtrlRemote } from "../type";
import { waitForSelector } from "./wait-el";

const generalPlayerRules = [
".dplayer",
".video-js",
".jwplayer",
"[data-player]",
];

export default class MediaPlugin extends LifeCycle {
constructor(public controller: MsgCtrlRemote) {
super();
this.register(() => controller.unload());
}

getStyle(): string | null {
return css;
}

#media: HTMLMediaElement | null = null;
findMedia(): Promise<HTMLMediaElement> {
return waitForSelector<HTMLMediaElement>("video, audio");
}

async load(): Promise<void> {
const style = this.getStyle();
if (style) {
this.injectStyle(style);
}
await super.load();
this.untilMediaReady("canplay").then(() => {
this.register(
this.controller.on("mx-toggle-controls", ({ payload: showWebsite }) => {
document.body.classList.toggle("mx-show-controls", showWebsite);
}),
);
});
this.controller.send("mx-play-ready", void 0);
}

Expand All @@ -32,7 +54,26 @@ export default class MediaPlugin extends LifeCycle {

async onload() {
this.#media = await this.findMedia();
await this.hookMediaEl();
await Promise.all([this.enterWebFullscreen(), this.hookMediaEl()]);
}

enterWebFullscreen(): any {
document.body.classList.add("mx-fs-enable");
const container =
this.media.closest<HTMLElement>(generalPlayerRules.join(", ")) ??
this.media;
container.classList.add("mx-player");
this.assignParentClass(container);
window.dispatchEvent(new Event("resize"));
}

assignParentClass(target: HTMLElement) {
for (const parent of parents(target)) {
parent.classList.add("mx-parent");
if (getComputedStyle(parent).position == "fixed") {
parent.classList.add("mx-absolute");
}
}
}

injectStyle(css: string) {
Expand Down Expand Up @@ -69,9 +110,55 @@ export default class MediaPlugin extends LifeCycle {
}
}

function* parents(element: HTMLElement, includeSelf = false) {
if (includeSelf) yield element;
// break if element is document.body
while (element.parentElement && element.parentElement !== document.body) {
element = element.parentElement;
yield element;
}
}

const readyStateEventMap = {
loadedmetadata: HTMLMediaElement.HAVE_METADATA,
loadeddata: HTMLMediaElement.HAVE_CURRENT_DATA,
canplay: HTMLMediaElement.HAVE_FUTURE_DATA,
canplaythrough: HTMLMediaElement.HAVE_ENOUGH_DATA,
} as const;

const css = `
body.mx-fs-enable .mx-player {
position: fixed !important;
top: 0 !important;
left: 0 !important;
width: 100vw !important;
height: 100vh !important;
max-width: none !important;
max-height: none !important;
min-width: 0 !important;
min-height: 0 !important;
margin: 0 !important;
padding: 0 !important;
z-index: 2147483647 !important; /* Ensure it's on top of other elements */
background-color: #000 !important;
transform: none !important;
}
body.mx-fs-enable .mx-parent {
overflow: visible !important;
z-index: auto !important;
transform: none !important;
-webkit-transform-style: flat !important;
transition: none !important;
contain: none !important;
}
body.mx-fs-enable .mx-absolute {
position: absolute !important;
}
body.mx-fs-enable {
overflow: hidden !important;
zoom: 100% !important;
}
body.mx-fs-enable .mx-parent video {
object-fit: contain !important;
}
`.trim();
3 changes: 2 additions & 1 deletion apps/app/src/web/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import BilibiliPlugin from "inline:./userscript/bilibili";
import VimeoPlugin from "inline:./userscript/vimeo";
import YouTubePlugin from "inline:./userscript/youtube";
import type { SupportedMediaHost } from "./url-match/supported";

export const plugins = {
bilibili: BilibiliPlugin,
youtube: YouTubePlugin,
vimeo: undefined,
vimeo: VimeoPlugin,
generic: undefined,
} satisfies Record<SupportedMediaHost, string | undefined>;
33 changes: 7 additions & 26 deletions apps/app/src/web/userscript/bilibili.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const css = `
#bilibili-player .bpx-player-control-wrap {
opacity: 0 !important;
}
#bilibili-player.mx-show-controls .bpx-player-control-wrap {
.mx-show-controls #bilibili-player .bpx-player-control-wrap {
opacity: 100 !important;
}
`;
Expand All @@ -21,43 +21,26 @@ export default class BilibiliPlugin extends MediaPlugin {
findMedia(): Promise<HTMLMediaElement> {
return waitForSelector<HTMLMediaElement>("#bilibili-player video");
}
getStyle() {
return css;
}
async onload(): Promise<void> {
this.controller.handle("bili_getManifest", () => {
return { value: window.player.getManifest() };
});
this.injectStyle(css);
localStorage.setItem("recommend_auto_play", "close");
// disable autoplay
localStorage.setItem(
"bpx_player_profile",
JSON.stringify({ media: { autoplay: false } }),
);
await super.onload();
// this.untilMediaReady("loadeddata").then(() => {
// this.preventAutoplay();
// });
// this.preventAutoplay();
// disable auto play recommendation
const player = document.querySelector<HTMLDivElement>("#bilibili-player");
if (!player) {
throw new Error("Bind failed: #bilibili-player not found");
}
this.#player = player;
await this.untilMediaReady("canplay");
this.register(
this.controller.on("mx-toggle-controls", ({ payload: showWebsite }) => {
player.classList.toggle("mx-show-controls", showWebsite);
}),
);
await Promise.all([this.toggleDanmaku(false), this.enterWebFullscreen()]);
await Promise.all([this.toggleDanmaku(false), this.untilWebFullscreen()]);
}

#player: HTMLDivElement | null = null;
get player() {
if (!this.#player) {
throw new Error("Get player before load");
}
return this.#player;
return this.media.closest<HTMLDivElement>("#bilibili-player")!;
}

async toggleDanmaku(val?: boolean) {
Expand Down Expand Up @@ -118,13 +101,11 @@ export default class BilibiliPlugin extends MediaPlugin {
);
console.log("Clicking fullscreen button");
fsButton.click();
// wait for playerEl to have class mode-webscreen
await this.untilWebFullscreen();
console.log("Entered web fullscreen");
}

async untilWebFullscreen() {
const playerEl = this.player;
if (this.isWebFullscreen()) return;
await new Promise((resolve) => {
const observer = new MutationObserver((mutations) => {
const mutation = mutations.find(
Expand Down
31 changes: 31 additions & 0 deletions apps/app/src/web/userscript/vimeo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// hugely inspired by https://greasyfork.org/zh-CN/scripts/4870-maximize-video

const css = `
.vp-player-ui-overlays {
opacity: 0 !important;
}
.mx-show-controls .vp-player-ui-overlays {
opacity: 100 !important;
}
`;

/* eslint-disable @typescript-eslint/naming-convention */
import { requireMx } from "./_require";

const { waitForSelector, MediaPlugin } = requireMx();

export default class VimeoPlugin extends MediaPlugin {
findMedia(): Promise<HTMLMediaElement> {
return waitForSelector<HTMLMediaElement>(
"#main [data-player] .vp-video video",
);
}

getStyle() {
const base = super.getStyle();
return base + "\n" + css;
}
// async onload(): Promise<void> {
// await super.onload();
// }
}
67 changes: 15 additions & 52 deletions apps/app/src/web/userscript/youtube.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ ytd-app .html5-endscreen {
ytd-app .ytp-chrome-bottom {
opacity: 0 !important;
}
ytd-app.mx-show-controls .ytp-chrome-bottom {
.mx-show-controls ytd-app .ytp-chrome-bottom {
opacity: 100 !important;
}
`;
Expand All @@ -53,47 +53,29 @@ const { waitForSelector, MediaPlugin } = requireMx();

export default class BilibiliPlugin extends MediaPlugin {
findMedia(): Promise<HTMLMediaElement> {
return waitForSelector<HTMLMediaElement>("#ytd-player video");
return waitForSelector<HTMLMediaElement>("ytd-app #movie_player video");
}

getStyle() {
return css;
}
async onload(): Promise<void> {
this.injectStyle(css);
await super.onload();
const app = document.querySelector<HTMLElement>("ytd-app");
if (!app) {
throw new Error("Bind failed: #ytd-app not found");
}
this.#app = app;

await Promise.all([
this.untilMediaReady("canplay").then(() => {
this.register(
this.controller.on(
"mx-toggle-controls",
({ payload: showWebsite }) => {
this.app.classList.toggle("mx-show-controls", showWebsite);
},
),
);
}),
await this.disableAutoPlay(),
this.enterWebFullscreen(),
]);
await Promise.all([this.disableAutoPlay()]);
}

#app: HTMLElement | null = null;
get app() {
if (!this.#app) {
throw new Error("Get player before load");
}
return this.#app;
return this.media.closest<HTMLElement>("ytd-app")!;
}

async disableAutoPlay() {
console.log("Disabling autoplay...");
const autoPlayButtonSelector =
'button.ytp-button[data-tooltip-target-id="ytp-autonav-toggle-button"]';
const autoPlayButton = this.app.querySelector<HTMLButtonElement>(
const autoPlayButton = await waitForSelector<HTMLButtonElement>(
autoPlayButtonSelector,
this.app,
);

if (!autoPlayButton) {
Expand Down Expand Up @@ -125,25 +107,15 @@ export default class BilibiliPlugin extends MediaPlugin {
}

async enterWebFullscreen() {
const moviePlayer = await waitForSelector<HTMLDivElement>(
"#movie_player",
this.app,
);
for (const parent of parents(moviePlayer)) {
parent.classList.add("mx-parent");
if (getComputedStyle(parent).position == "fixed") {
parent.classList.add("mx-absolute");
}
}
console.log("added parent classes");
const moviePlayer = this.media.closest<HTMLElement>("#movie_player")!;
this.assignParentClass(moviePlayer);

const fsButton = await waitForSelector<HTMLButtonElement>(
"#movie_player .ytp-size-button",
);
const isCinematicsMode = () =>
!!this.app.querySelector("ytd-watch-flexy[theater]");
if (!isCinematicsMode()) {
const fsButton = await waitForSelector<HTMLButtonElement>(
"#movie_player .ytp-size-button",
this.app,
);
console.log("Entering cinema mode");
do {
fsButton.click();
Expand All @@ -155,15 +127,6 @@ export default class BilibiliPlugin extends MediaPlugin {
}
}

function* parents(element: HTMLElement, includeSelf = false) {
if (includeSelf) yield element;
// break if element is document.body
while (element.parentElement && element.parentElement !== document.body) {
element = element.parentElement;
yield element;
}
}

function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

0 comments on commit 3e3b749

Please sign in to comment.