first mvp extension

This commit is contained in:
Jorrin 2024-01-08 17:06:27 +01:00
parent fbb5ef7115
commit ef85c217f7
7 changed files with 134 additions and 56 deletions

View File

@ -31,6 +31,7 @@
"@ladjs/country-language": "^1.0.3", "@ladjs/country-language": "^1.0.3",
"@movie-web/providers": "^2.0.5", "@movie-web/providers": "^2.0.5",
"@noble/hashes": "^1.3.3", "@noble/hashes": "^1.3.3",
"@plasmohq/messaging": "^0.6.1",
"@react-spring/web": "^9.7.3", "@react-spring/web": "^9.7.3",
"@scure/bip39": "^1.2.2", "@scure/bip39": "^1.2.2",
"@sozialhelden/ietf-language-tags": "^5.4.2", "@sozialhelden/ietf-language-tags": "^5.4.2",

21
pnpm-lock.yaml generated
View File

@ -27,6 +27,9 @@ dependencies:
'@noble/hashes': '@noble/hashes':
specifier: ^1.3.3 specifier: ^1.3.3
version: 1.3.3 version: 1.3.3
'@plasmohq/messaging':
specifier: ^0.6.1
version: 0.6.1(react@18.2.0)
'@react-spring/web': '@react-spring/web':
specifier: ^9.7.3 specifier: ^9.7.3
version: 9.7.3(react-dom@18.2.0)(react@18.2.0) version: 9.7.3(react-dom@18.2.0)(react@18.2.0)
@ -1980,6 +1983,18 @@ packages:
tslib: 2.6.2 tslib: 2.6.2
dev: true dev: true
/@plasmohq/messaging@0.6.1(react@18.2.0):
resolution: {integrity: sha512-/nn1k8SG5z++o/NnZu+byHWcC9MhPLxfmvj+AP3buqMn7uwfYDcYWURLuMW2Knw08HBg+wku2v1Ltt4evN0nzA==}
peerDependencies:
react: ^16.8.6 || ^17 || ^18
peerDependenciesMeta:
react:
optional: true
dependencies:
nanoid: 5.0.3
react: 18.2.0
dev: false
/@react-spring/animated@9.7.3(react@18.2.0): /@react-spring/animated@9.7.3(react@18.2.0):
resolution: {integrity: sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==} resolution: {integrity: sha512-5CWeNJt9pNgyvuSzQH+uy2pvTg8Y4/OisoscZIR8/ZNLIOI+CatFBhGZpDGTF/OzdNFsAoGk3wiUYTwoJ0YIvw==}
peerDependencies: peerDependencies:
@ -5156,6 +5171,12 @@ packages:
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
/nanoid@5.0.3:
resolution: {integrity: sha512-I7X2b22cxA4LIHXPSqbBCEQSL+1wv8TuoefejsX4HFWyC6jc5JG7CEaxOltiKjc1M+YCS2YkrZZcj4+dytw9GA==}
engines: {node: ^18 || >=20}
hasBin: true
dev: false
/nanoid@5.0.4: /nanoid@5.0.4:
resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==} resolution: {integrity: sha512-vAjmBf13gsmhXSgBrtIclinISzFFy22WwCYoyilZlsrRXNIHSwgFQ1bEdjRwMT3aoadeIF6HMuDRlOxzfXV8ig==}
engines: {node: ^18 || >=20} engines: {node: ^18 || >=20}

37
src/@types/plasmo.d.ts vendored Normal file
View File

@ -0,0 +1,37 @@
/* eslint-disable @typescript-eslint/ban-types */
import "@plasmohq/messaging";
export interface PlasmoRequestBody {
ruleId: number;
domain: string;
requestHeaders?: Record<string, string>;
responseHeaders?: Record<string, string>;
}
export type PlasmoResponseBody =
| {
success: true;
ruleId: number;
}
| {
success: false;
error: string;
};
interface MmMetadata {
"declarative-net-request": {
req: PlasmoRequestBody;
res: PlasmoResponseBody;
};
"proxy-request": {
req: PlasmoRequestBody;
res: PlasmoResponseBody;
};
}
interface MpMetadata {}
declare module "@plasmohq/messaging" {
interface MessagesMetadata extends MmMetadata {}
interface PortsMetadata extends MpMetadata {}
}

View File

@ -1,6 +1,8 @@
import { sendToBackgroundViaRelay } from "@plasmohq/messaging";
import fscreen from "fscreen"; import fscreen from "fscreen";
import Hls, { Level } from "hls.js"; import Hls, { Level } from "hls.js";
import { PlasmoRequestBody, PlasmoResponseBody } from "@/@types/plasmo";
import { import {
DisplayInterface, DisplayInterface,
DisplayInterfaceEvents, DisplayInterfaceEvents,
@ -100,65 +102,75 @@ export function makeVideoElementDisplayInterface(): DisplayInterface {
} }
function setupSource(vid: HTMLVideoElement, src: LoadableSource) { function setupSource(vid: HTMLVideoElement, src: LoadableSource) {
if (src.type === "hls") { // TODO: Add check whether the extension is installed
if (canPlayHlsNatively(vid)) { sendToBackgroundViaRelay<PlasmoRequestBody, PlasmoResponseBody>({
vid.src = processCdnLink(src.url); name: "declarative-net-request",
body: {
ruleId: 1,
domain: src.type === "hls" ? new URL(src.url).hostname : src.url,
requestHeaders: src.preferredHeaders,
},
}).then(() => {
if (src.type === "hls") {
if (canPlayHlsNatively(vid)) {
vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
return;
}
if (!Hls.isSupported()) throw new Error("HLS not supported");
if (!hls) {
hls = new Hls({
maxBufferSize: 500 * 1000 * 1000, // 500 mb of buffering, should load more fragments at once
fragLoadPolicy: {
default: {
maxLoadTimeMs: 30 * 1000, // allow it load extra long, fragments are slow if requested for the first time on an origin
maxTimeToFirstByteMs: 30 * 1000,
errorRetry: {
maxNumRetry: 2,
retryDelayMs: 1000,
maxRetryDelayMs: 8000,
},
timeoutRetry: {
maxNumRetry: 3,
maxRetryDelayMs: 0,
retryDelayMs: 0,
},
},
},
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error("HLS error", data);
if (data.fatal) {
emit("error", {
message: data.error.message,
stackTrace: data.error.stack,
errorName: data.error.name,
type: "hls",
});
}
});
hls.on(Hls.Events.MANIFEST_LOADED, () => {
if (!hls) return;
reportLevels();
setupQualityForHls();
});
hls.on(Hls.Events.LEVEL_SWITCHED, () => {
if (!hls) return;
const quality = hlsLevelToQuality(hls.levels[hls.currentLevel]);
emit("changedquality", quality);
});
}
hls.attachMedia(vid);
hls.loadSource(processCdnLink(src.url));
vid.currentTime = startAt; vid.currentTime = startAt;
return; return;
} }
if (!Hls.isSupported()) throw new Error("HLS not supported"); vid.src = processCdnLink(src.url);
if (!hls) {
hls = new Hls({
maxBufferSize: 500 * 1000 * 1000, // 500 mb of buffering, should load more fragments at once
fragLoadPolicy: {
default: {
maxLoadTimeMs: 30 * 1000, // allow it load extra long, fragments are slow if requested for the first time on an origin
maxTimeToFirstByteMs: 30 * 1000,
errorRetry: {
maxNumRetry: 2,
retryDelayMs: 1000,
maxRetryDelayMs: 8000,
},
timeoutRetry: {
maxNumRetry: 3,
maxRetryDelayMs: 0,
retryDelayMs: 0,
},
},
},
});
hls.on(Hls.Events.ERROR, (event, data) => {
console.error("HLS error", data);
if (data.fatal) {
emit("error", {
message: data.error.message,
stackTrace: data.error.stack,
errorName: data.error.name,
type: "hls",
});
}
});
hls.on(Hls.Events.MANIFEST_LOADED, () => {
if (!hls) return;
reportLevels();
setupQualityForHls();
});
hls.on(Hls.Events.LEVEL_SWITCHED, () => {
if (!hls) return;
const quality = hlsLevelToQuality(hls.levels[hls.currentLevel]);
emit("changedquality", quality);
});
}
hls.attachMedia(vid);
hls.loadSource(processCdnLink(src.url));
vid.currentTime = startAt; vid.currentTime = startAt;
return; });
}
vid.src = processCdnLink(src.url);
vid.currentTime = startAt;
} }
function setSource() { function setSource() {

View File

@ -28,6 +28,7 @@ export function convertRunoutputToSource(out: {
return { return {
type: "hls", type: "hls",
url: out.stream.playlist, url: out.stream.playlist,
preferredHeaders: out.stream.preferredHeaders,
}; };
} }
if (out.stream.type === "file") { if (out.stream.type === "file") {
@ -49,6 +50,7 @@ export function convertRunoutputToSource(out: {
return { return {
type: "file", type: "file",
qualities, qualities,
preferredHeaders: out.stream.preferredHeaders,
}; };
} }
throw new Error("unrecognized type"); throw new Error("unrecognized type");

View File

@ -1,4 +1,4 @@
import { Qualities } from "@movie-web/providers"; import { Qualities, Stream } from "@movie-web/providers";
import { QualityStore } from "@/stores/quality"; import { QualityStore } from "@/stores/quality";
@ -14,16 +14,19 @@ export type SourceFileStream = {
export type LoadableSource = { export type LoadableSource = {
type: StreamType; type: StreamType;
url: string; url: string;
preferredHeaders?: Stream["preferredHeaders"];
}; };
export type SourceSliceSource = export type SourceSliceSource =
| { | {
type: "file"; type: "file";
qualities: Partial<Record<SourceQuality, SourceFileStream>>; qualities: Partial<Record<SourceQuality, SourceFileStream>>;
preferredHeaders?: Stream["preferredHeaders"];
} }
| { | {
type: "hls"; type: "hls";
url: string; url: string;
preferredHeaders?: Stream["preferredHeaders"];
}; };
const qualitySorting: Record<SourceQuality, number> = { const qualitySorting: Record<SourceQuality, number> = {

View File

@ -62,5 +62,7 @@ function makeLoadBalancedSimpleProxyFetcher() {
export const providers = makeProviders({ export const providers = makeProviders({
fetcher: makeStandardFetcher(fetch), fetcher: makeStandardFetcher(fetch),
proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(), proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(),
target: targets.BROWSER, // TODO: Add check whether the extension is installed
// target: targets.BROWSER,
target: targets.BROWSER_EXTENSION,
}) as any as ProviderControls; }) as any as ProviderControls;