diff --git a/src/backend/extension/messaging.ts b/src/backend/extension/messaging.ts new file mode 100644 index 00000000..ec0b4bf6 --- /dev/null +++ b/src/backend/extension/messaging.ts @@ -0,0 +1,51 @@ +import { + MessagesMetadata, + sendToBackgroundViaRelay, +} from "@plasmohq/messaging"; + +let activeExtension = false; + +export interface ExtensionHello { + version: string; +} + +function sendMessage( + message: keyof MessagesMetadata, + payload: any, + timeout: number = -1, +) { + return new Promise((resolve) => { + if (timeout >= 0) setTimeout(() => resolve(null), timeout); + sendToBackgroundViaRelay({ + name: message, + body: payload, + }) + .then((res) => { + activeExtension = true; + resolve(res); + }) + .catch(() => { + activeExtension = false; + resolve(null); + }); + }); +} + +export async function sendExtensionRequest( + url: string, + ops: any, +): Promise { + return sendMessage("proxy-request", { url, ...ops }); +} + +export async function extensionInfo(): Promise { + return sendMessage("hello", null, 300); +} + +export function isExtensionActiveCached(): boolean { + return activeExtension; +} + +export async function isExtensionActive(): Promise { + return !!(await extensionInfo()); +} diff --git a/src/@types/plasmo.d.ts b/src/backend/extension/plasmo.ts similarity index 72% rename from src/@types/plasmo.d.ts rename to src/backend/extension/plasmo.ts index 4570cae4..2e9cacbf 100644 --- a/src/@types/plasmo.d.ts +++ b/src/backend/extension/plasmo.ts @@ -1,6 +1,3 @@ -/* eslint-disable @typescript-eslint/ban-types */ -import "@plasmohq/messaging"; - export interface PlasmoRequestBody { ruleId: number; domain: string; @@ -8,7 +5,11 @@ export interface PlasmoRequestBody { responseHeaders?: Record; } -export type PlasmoResponseBody = +export interface ExtensionHelloReply { + version: string; +} + +export type ExtensionRequestReply = | { success: true; ruleId: number; @@ -21,11 +22,15 @@ export type PlasmoResponseBody = interface MmMetadata { "declarative-net-request": { req: PlasmoRequestBody; - res: PlasmoResponseBody; + res: ExtensionRequestReply; }; "proxy-request": { req: PlasmoRequestBody; - res: PlasmoResponseBody; + res: ExtensionRequestReply; + }; + hello: { + req: null; + res: ExtensionHelloReply; }; } diff --git a/src/backend/helpers/fetch.ts b/src/backend/helpers/fetch.ts index cc3e735e..f9aa145a 100644 --- a/src/backend/helpers/fetch.ts +++ b/src/backend/helpers/fetch.ts @@ -1,7 +1,7 @@ import { ofetch } from "ofetch"; import { getApiToken, setApiToken } from "@/backend/helpers/providerApi"; -import { getLoadbalancedProxyUrl } from "@/utils/providers"; +import { getLoadbalancedProxyUrl } from "@/backend/providers/fetchers"; type P = Parameters>; type R = ReturnType>; diff --git a/src/utils/providers.ts b/src/backend/providers/fetchers.ts similarity index 74% rename from src/utils/providers.ts rename to src/backend/providers/fetchers.ts index 73c95662..596e8376 100644 --- a/src/utils/providers.ts +++ b/src/backend/providers/fetchers.ts @@ -1,12 +1,6 @@ -import { - Fetcher, - ProviderControls, - makeProviders, - makeSimpleProxyFetcher, - makeStandardFetcher, - targets, -} from "@movie-web/providers"; +import { Fetcher, makeSimpleProxyFetcher } from "@movie-web/providers"; +import { sendExtensionRequest } from "@/backend/extension/messaging"; import { getApiToken, setApiToken } from "@/backend/helpers/providerApi"; import { getProviderApiUrls, getProxyUrls } from "@/utils/proxyUrls"; @@ -48,7 +42,7 @@ async function fetchButWithApiTokens( return response; } -function makeLoadBalancedSimpleProxyFetcher() { +export function makeLoadBalancedSimpleProxyFetcher() { const fetcher: Fetcher = async (a, b) => { const currentFetcher = makeSimpleProxyFetcher( getLoadbalancedProxyUrl(), @@ -59,10 +53,9 @@ function makeLoadBalancedSimpleProxyFetcher() { return fetcher; } -export const providers = makeProviders({ - fetcher: makeStandardFetcher(fetch), - proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(), - // TODO: Add check whether the extension is installed - // target: targets.BROWSER, - target: targets.BROWSER_EXTENSION, -}) as any as ProviderControls; +export function makeExtensionFetcher() { + const fetcher: Fetcher = async (a, b) => { + return sendExtensionRequest(a, b) as any; + }; + return fetcher; +} diff --git a/src/backend/providers/providers.ts b/src/backend/providers/providers.ts new file mode 100644 index 00000000..1a7b484a --- /dev/null +++ b/src/backend/providers/providers.ts @@ -0,0 +1,26 @@ +import { + makeProviders, + makeStandardFetcher, + targets, +} from "@movie-web/providers"; + +import { isExtensionActiveCached } from "@/backend/extension/messaging"; +import { + makeExtensionFetcher, + makeLoadBalancedSimpleProxyFetcher, +} from "@/backend/providers/fetchers"; + +export function getProviders() { + if (isExtensionActiveCached()) { + return makeProviders({ + fetcher: makeExtensionFetcher(), + target: targets.BROWSER_EXTENSION, + }); + } + + return makeProviders({ + fetcher: makeStandardFetcher(fetch), + proxiedFetcher: makeLoadBalancedSimpleProxyFetcher(), + target: targets.BROWSER, + }); +} diff --git a/src/components/player/display/base.ts b/src/components/player/display/base.ts index 11a0799c..91f36722 100644 --- a/src/components/player/display/base.ts +++ b/src/components/player/display/base.ts @@ -1,8 +1,6 @@ -import { sendToBackgroundViaRelay } from "@plasmohq/messaging"; import fscreen from "fscreen"; import Hls, { Level } from "hls.js"; -import { PlasmoRequestBody, PlasmoResponseBody } from "@/@types/plasmo"; import { DisplayInterface, DisplayInterfaceEvents, @@ -43,6 +41,7 @@ function qualityToHlsLevel(quality: SourceQuality): number | null { ); return found ? +found[0] : null; } + function hlsLevelsToQualities(levels: Level[]): SourceQuality[] { return levels .map((v) => hlsLevelToQuality(v)) @@ -103,74 +102,65 @@ export function makeVideoElementDisplayInterface(): DisplayInterface { function setupSource(vid: HTMLVideoElement, src: LoadableSource) { // TODO: Add check whether the extension is installed - sendToBackgroundViaRelay({ - 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)); + if (src.type === "hls") { + if (canPlayHlsNatively(vid)) { + vid.src = processCdnLink(src.url); vid.currentTime = startAt; return; } - vid.src = processCdnLink(src.url); + 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; - }); + return; + } + + vid.src = processCdnLink(src.url); + vid.currentTime = startAt; } function setSource() { diff --git a/src/components/player/hooks/useSourceSelection.ts b/src/components/player/hooks/useSourceSelection.ts index e28507cf..bca884f7 100644 --- a/src/components/player/hooks/useSourceSelection.ts +++ b/src/components/player/hooks/useSourceSelection.ts @@ -13,12 +13,13 @@ import { scrapeSourceOutputToProviderMetric, useReportProviders, } from "@/backend/helpers/report"; +import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers"; +import { getProviders } from "@/backend/providers/providers"; import { convertProviderCaption } from "@/components/player/utils/captions"; import { convertRunoutputToSource } from "@/components/player/utils/convertRunoutputToSource"; import { useOverlayRouter } from "@/hooks/useOverlayRouter"; import { metaToScrapeMedia } from "@/stores/player/slices/source"; import { usePlayerStore } from "@/stores/player/store"; -import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers"; export function useEmbedScraping( routerId: string, @@ -47,7 +48,7 @@ export function useEmbedScraping( ); result = await conn.promise(); } else { - result = await providers.runEmbedScraper({ + result = await getProviders().runEmbedScraper({ id: embedId, url, }); @@ -111,7 +112,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) { ); result = await conn.promise(); } else { - result = await providers.runSourceScraper({ + result = await getProviders().runSourceScraper({ id: sourceId, media: scrapeMedia, }); @@ -155,7 +156,7 @@ export function useSourceScraping(sourceId: string | null, routerId: string) { ); embedResult = await conn.promise(); } else { - embedResult = await providers.runEmbedScraper({ + embedResult = await getProviders().runEmbedScraper({ id: result.embeds[0].embedId, url: result.embeds[0].url, }); diff --git a/src/hooks/useProviderScrape.tsx b/src/hooks/useProviderScrape.tsx index 43328184..21cb985e 100644 --- a/src/hooks/useProviderScrape.tsx +++ b/src/hooks/useProviderScrape.tsx @@ -10,7 +10,8 @@ import { getCachedMetadata, makeProviderUrl, } from "@/backend/helpers/providerApi"; -import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers"; +import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers"; +import { getProviders } from "@/backend/providers/providers"; export interface ScrapingItems { id: string; @@ -172,8 +173,8 @@ export function useScrape() { return getResult(sseOutput === "" ? null : sseOutput); } - if (!providers) return null; startScrape(); + const providers = getProviders(); const output = await providers.runAll({ media, events: { diff --git a/src/pages/parts/player/MetaPart.tsx b/src/pages/parts/player/MetaPart.tsx index 4930fffb..6d5b64ef 100644 --- a/src/pages/parts/player/MetaPart.tsx +++ b/src/pages/parts/player/MetaPart.tsx @@ -3,6 +3,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useAsync } from "react-use"; import type { AsyncReturnType } from "type-fest"; +import { isExtensionActive } from "@/backend/extension/messaging"; import { fetchMetadata, setCachedMetadata, @@ -10,6 +11,8 @@ import { import { DetailedMeta, getMetaFromId } from "@/backend/metadata/getmeta"; import { decodeTMDBId } from "@/backend/metadata/tmdb"; import { MWMediaType } from "@/backend/metadata/types/mw"; +import { getLoadbalancedProviderApiUrl } from "@/backend/providers/fetchers"; +import { getProviders } from "@/backend/providers/providers"; import { Button } from "@/components/buttons/Button"; import { Icons } from "@/components/Icon"; import { IconPill } from "@/components/layout/IconPill"; @@ -18,7 +21,6 @@ import { Paragraph } from "@/components/text/Paragraph"; import { Title } from "@/components/text/Title"; import { ErrorContainer, ErrorLayout } from "@/pages/layouts/ErrorLayout"; import { conf } from "@/setup/config"; -import { getLoadbalancedProviderApiUrl, providers } from "@/utils/providers"; export interface MetaPartProps { onGetMeta?: (meta: DetailedMeta, episodeId?: string) => void; @@ -41,8 +43,12 @@ export function MetaPart(props: MetaPartProps) { const navigate = useNavigate(); const { error, value, loading } = useAsync(async () => { + // check extension + const isActive = await isExtensionActive(); + + // use api metadata or providers metadata const providerApiUrl = getLoadbalancedProviderApiUrl(); - if (providerApiUrl) { + if (providerApiUrl && !isActive) { try { await fetchMetadata(providerApiUrl); } catch (err) { @@ -50,11 +56,12 @@ export function MetaPart(props: MetaPartProps) { } } else { setCachedMetadata([ - ...providers.listSources(), - ...providers.listEmbeds(), + ...getProviders().listSources(), + ...getProviders().listEmbeds(), ]); } + // get media meta data let data: ReturnType = null; try { if (!params.media) throw new Error("no media params");