feat: n94 poc
This commit is contained in:
parent
f9f0cf213d
commit
03a5820140
@ -24,6 +24,7 @@
|
|||||||
"hls-video-element": "^1.2.7",
|
"hls-video-element": "^1.2.7",
|
||||||
"marked": "^12.0.2",
|
"marked": "^12.0.2",
|
||||||
"media-chrome": "^3.2.4",
|
"media-chrome": "^3.2.4",
|
||||||
|
"mpegts.js": "^1.7.3",
|
||||||
"qr-code-styling": "^1.6.0-rc.1",
|
"qr-code-styling": "^1.6.0-rc.1",
|
||||||
"react": "^18.3.1",
|
"react": "^18.3.1",
|
||||||
"react-confetti": "^6.1.0",
|
"react-confetti": "^6.1.0",
|
||||||
|
@ -20,6 +20,8 @@ import {
|
|||||||
} from "media-chrome/react";
|
} from "media-chrome/react";
|
||||||
import "hls-video-element";
|
import "hls-video-element";
|
||||||
import { StreamState } from "@/const";
|
import { StreamState } from "@/const";
|
||||||
|
import Nip94Player from "./n94-player";
|
||||||
|
import { NostrLink } from "@snort/system";
|
||||||
|
|
||||||
type VideoPlayerProps = {
|
type VideoPlayerProps = {
|
||||||
title?: string;
|
title?: string;
|
||||||
@ -27,9 +29,17 @@ type VideoPlayerProps = {
|
|||||||
stream?: string;
|
stream?: string;
|
||||||
poster?: string;
|
poster?: string;
|
||||||
muted?: boolean;
|
muted?: boolean;
|
||||||
|
link: NostrLink;
|
||||||
} & HTMLProps<HTMLVideoElement>;
|
} & HTMLProps<HTMLVideoElement>;
|
||||||
|
|
||||||
export default function LiveVideoPlayer({ title, stream, status, poster, ...props }: VideoPlayerProps) {
|
export default function LiveVideoPlayer({ title, stream, status, poster, link, ...props }: VideoPlayerProps) {
|
||||||
|
function innerPlayer() {
|
||||||
|
if (stream === "nip94") {
|
||||||
|
return <Nip94Player link={link} />
|
||||||
|
}
|
||||||
|
{/* @ts-ignore Web Componenet */ }
|
||||||
|
return <hls-video {...props} slot="media" src={stream} playsInline={true} autoPlay={true} />
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<MediaController
|
<MediaController
|
||||||
className={classNames(props.className, "h-inherit aspect-video w-full")}
|
className={classNames(props.className, "h-inherit aspect-video w-full")}
|
||||||
@ -42,8 +52,7 @@ export default function LiveVideoPlayer({ title, stream, status, poster, ...prop
|
|||||||
<div slot="top-chrome" className="py-1 text-center w-full text-2xl bg-primary">
|
<div slot="top-chrome" className="py-1 text-center w-full text-2xl bg-primary">
|
||||||
{title}
|
{title}
|
||||||
</div>
|
</div>
|
||||||
{/* @ts-ignore Web Componenet */}
|
{innerPlayer()}
|
||||||
<hls-video {...props} slot="media" src={stream} playsInline={true} />
|
|
||||||
<MediaRenditionMenu hidden anchor="auto" />
|
<MediaRenditionMenu hidden anchor="auto" />
|
||||||
{poster && <MediaPosterImage slot="poster" src={poster} />}
|
{poster && <MediaPosterImage slot="poster" src={poster} />}
|
||||||
<MediaControlBar>
|
<MediaControlBar>
|
||||||
|
158
src/element/stream/n94-player.tsx
Normal file
158
src/element/stream/n94-player.tsx
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/ban-ts-comment */
|
||||||
|
import { EventKind, NostrLink, QueryLike, RequestBuilder, SystemInterface, parseNostrLink } from "@snort/system";
|
||||||
|
import { useContext, useEffect, useRef } from "react"
|
||||||
|
import mpegts from "mpegts.js";
|
||||||
|
import { SnortContext } from "@snort/system-react";
|
||||||
|
import { findTag } from "@/utils";
|
||||||
|
|
||||||
|
interface MediaSegment {
|
||||||
|
created: number,
|
||||||
|
sha256: string,
|
||||||
|
url: string,
|
||||||
|
duration: number,
|
||||||
|
loaded: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
enum LoaderStatus {
|
||||||
|
kIdle = 0,
|
||||||
|
kConnecting = 1,
|
||||||
|
kBuffering = 2,
|
||||||
|
kError = 3,
|
||||||
|
kComplete = 4
|
||||||
|
}
|
||||||
|
class Nip94Loader implements mpegts.BaseLoader {
|
||||||
|
#system!: SystemInterface
|
||||||
|
#query?: QueryLike
|
||||||
|
#mediaChunks: Map<string, MediaSegment> = new Map();
|
||||||
|
#status: LoaderStatus = LoaderStatus.kIdle;
|
||||||
|
#bytes = 0;
|
||||||
|
|
||||||
|
constructor(_seekHandler: mpegts.SeekHandler, config: mpegts.Config) {
|
||||||
|
if ("system" in config) {
|
||||||
|
this.#system = config.system as SystemInterface;
|
||||||
|
} else {
|
||||||
|
throw "Invalid config, missing system"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
onContentLengthKnown: (contentLength: number) => void;
|
||||||
|
// @ts-expect-error
|
||||||
|
onURLRedirect: (redirectedURL: string) => void;
|
||||||
|
// @ts-expect-error
|
||||||
|
onDataArrival: (chunk: ArrayBuffer, byteStart: number, receivedLength?: number | undefined) => void;
|
||||||
|
// @ts-expect-error
|
||||||
|
onError: (errorType: mpegts.LoaderErrors, errorInfo: mpegts.LoaderErrorMessage) => void;
|
||||||
|
// @ts-expect-error
|
||||||
|
onComplete: (rangeFrom: number, rangeTo: number) => void;
|
||||||
|
|
||||||
|
get _status() { return this.#status }
|
||||||
|
get status() { return this.#status }
|
||||||
|
|
||||||
|
destroy(): void {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
isWorking(): boolean {
|
||||||
|
throw new Error("Method not implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
type = "nip94"
|
||||||
|
get needStashBuffer() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
get _needStash() { return this.needStashBuffer }
|
||||||
|
|
||||||
|
open(dataSource: mpegts.MediaSegment, range: mpegts.Range) {
|
||||||
|
const link = parseNostrLink(dataSource.url);
|
||||||
|
if (!link) {
|
||||||
|
throw new Error("Datasource.url is invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
const rb = new RequestBuilder(`n94-stream-${link.encode()}`);
|
||||||
|
rb.withOptions({
|
||||||
|
leaveOpen: true,
|
||||||
|
});
|
||||||
|
rb.withFilter()
|
||||||
|
.replyToLink([link])
|
||||||
|
.kinds([EventKind.FileHeader]);
|
||||||
|
|
||||||
|
this.#status = LoaderStatus.kConnecting;
|
||||||
|
this.#query = this.#system.Query(rb);
|
||||||
|
this.#query.on("event", (evs) => {
|
||||||
|
for (const ev of evs) {
|
||||||
|
const seg = {
|
||||||
|
created: ev.created_at,
|
||||||
|
url: findTag(ev, "url")!,
|
||||||
|
sha256: findTag(ev, "x")!,
|
||||||
|
} as MediaSegment;
|
||||||
|
this.#addSegment(seg);
|
||||||
|
}
|
||||||
|
this.#loadNext();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
abort() {
|
||||||
|
this.#query?.cancel();
|
||||||
|
}
|
||||||
|
|
||||||
|
#addSegment(seg: MediaSegment) {
|
||||||
|
if (!this.#mediaChunks.has(seg.sha256)) {
|
||||||
|
this.#mediaChunks.set(seg.sha256, seg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async #loadNext() {
|
||||||
|
if (this.#status === LoaderStatus.kConnecting || this.#status === LoaderStatus.kIdle) {
|
||||||
|
this.#status = LoaderStatus.kBuffering;
|
||||||
|
}
|
||||||
|
if (this.#status !== LoaderStatus.kBuffering) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const orderedLoad = [...this.#mediaChunks.values()].sort((a, b) => a.created - b.created).filter(a => !a.loaded);
|
||||||
|
for (const s of orderedLoad) {
|
||||||
|
const result = await fetch(s.url);
|
||||||
|
const buf = await result.arrayBuffer();
|
||||||
|
|
||||||
|
this.onDataArrival(buf, this.#bytes, buf.byteLength);
|
||||||
|
console.debug("pushing bytes", this.#bytes, buf.byteLength);
|
||||||
|
this.#bytes += buf.byteLength;
|
||||||
|
|
||||||
|
this.#mediaChunks.set(s.sha256, {
|
||||||
|
...s,
|
||||||
|
loaded: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.#status = LoaderStatus.kIdle;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function Nip94Player({ link }: { link: NostrLink }) {
|
||||||
|
const ref = useRef(null);
|
||||||
|
const system = useContext(SnortContext);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (ref.current) {
|
||||||
|
const player = ref.current as HTMLVideoElement & {
|
||||||
|
__streamer?: mpegts.Player
|
||||||
|
};
|
||||||
|
|
||||||
|
if (!player.__streamer) {
|
||||||
|
player.__streamer = new mpegts.MSEPlayer({
|
||||||
|
type: "mse",
|
||||||
|
isLive: true,
|
||||||
|
url: link.encode()
|
||||||
|
}, {
|
||||||
|
system,
|
||||||
|
customLoader: Nip94Loader
|
||||||
|
} as unknown as mpegts.Config);
|
||||||
|
|
||||||
|
player.__streamer.attachMediaElement(player);
|
||||||
|
player.__streamer.load();
|
||||||
|
player.__streamer.play();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [ref]);
|
||||||
|
|
||||||
|
return <video slot="media" ref={ref}></video>
|
||||||
|
}
|
@ -1,8 +1,10 @@
|
|||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
import { EventKind, RequestBuilder } from "@snort/system";
|
import { RequestBuilder } from "@snort/system";
|
||||||
import { useRequestBuilder } from "@snort/system-react";
|
import { useRequestBuilder } from "@snort/system-react";
|
||||||
|
import { LIVE_STREAM } from "@/const";
|
||||||
|
|
||||||
export function useStreamsFeed(tag?: string) {
|
export function useStreamsFeed(tag?: string) {
|
||||||
|
const liveStreamKinds = [LIVE_STREAM];
|
||||||
const rb = useMemo(() => {
|
const rb = useMemo(() => {
|
||||||
const rb = new RequestBuilder(tag ? `streams:${tag}` : "streams");
|
const rb = new RequestBuilder(tag ? `streams:${tag}` : "streams");
|
||||||
rb.withOptions({
|
rb.withOptions({
|
||||||
@ -11,26 +13,26 @@ export function useStreamsFeed(tag?: string) {
|
|||||||
if (import.meta.env.VITE_SINGLE_PUBLISHER) {
|
if (import.meta.env.VITE_SINGLE_PUBLISHER) {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([EventKind.LiveEvent])
|
.kinds(liveStreamKinds)
|
||||||
.tag("t", [tag])
|
.tag("t", [tag])
|
||||||
.authors([import.meta.env.VITE_SINGLE_PUBLISHER]);
|
.authors([import.meta.env.VITE_SINGLE_PUBLISHER]);
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([EventKind.LiveEvent])
|
.kinds(liveStreamKinds)
|
||||||
.tag("t", [tag])
|
.tag("t", [tag])
|
||||||
.tag("p", [import.meta.env.VITE_SINGLE_PUBLISHER]);
|
.tag("p", [import.meta.env.VITE_SINGLE_PUBLISHER]);
|
||||||
} else {
|
} else {
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([EventKind.LiveEvent])
|
.kinds(liveStreamKinds)
|
||||||
.authors([import.meta.env.VITE_SINGLE_PUBLISHER]);
|
.authors([import.meta.env.VITE_SINGLE_PUBLISHER]);
|
||||||
rb.withFilter()
|
rb.withFilter()
|
||||||
.kinds([EventKind.LiveEvent])
|
.kinds(liveStreamKinds)
|
||||||
.tag("p", [import.meta.env.VITE_SINGLE_PUBLISHER]);
|
.tag("p", [import.meta.env.VITE_SINGLE_PUBLISHER]);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (tag) {
|
if (tag) {
|
||||||
rb.withFilter().kinds([EventKind.LiveEvent]).tag("t", [tag]);
|
rb.withFilter().kinds(liveStreamKinds).tag("t", [tag]);
|
||||||
} else {
|
} else {
|
||||||
rb.withFilter().kinds([EventKind.LiveEvent]);
|
rb.withFilter().kinds(liveStreamKinds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return rb;
|
return rb;
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { DAY, StreamState } from "@/const";
|
import { DAY, LIVE_STREAM, StreamState } from "@/const";
|
||||||
import { findTag, getHost } from "@/utils";
|
import { findTag, getHost } from "@/utils";
|
||||||
import { unixNow } from "@snort/shared";
|
import { unixNow } from "@snort/shared";
|
||||||
import { NostrEvent, TaggedNostrEvent } from "@snort/system";
|
import { NostrEvent, TaggedNostrEvent } from "@snort/system";
|
||||||
@ -24,12 +24,20 @@ export function useSortedStreams(feed: Array<TaggedNostrEvent>, oldest?: number)
|
|||||||
return [];
|
return [];
|
||||||
}, [feed]);
|
}, [feed]);
|
||||||
|
|
||||||
|
function canPlayEvent(ev: NostrEvent) {
|
||||||
|
if (ev.kind === LIVE_STREAM) {
|
||||||
|
const isHls = ev.tags.some(a => a[0] === "streaming" && a[1].includes(".m3u8"));
|
||||||
|
const isN94 = ev.tags.some(a => a[0] === "streaming" && a[1] == "nip94");
|
||||||
|
return isHls || isN94;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
const live = feedSorted
|
const live = feedSorted
|
||||||
.filter(a => {
|
.filter(a => {
|
||||||
try {
|
try {
|
||||||
return (
|
return (
|
||||||
findTag(a, "status") === StreamState.Live &&
|
findTag(a, "status") === StreamState.Live && canPlayEvent(a)
|
||||||
a.tags.some(a => a[0] === "streaming" && new URL(a[1]).pathname.includes(".m3u8"))
|
|
||||||
);
|
);
|
||||||
} catch {
|
} catch {
|
||||||
return false;
|
return false;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { SHORTS_KIND, VIDEO_KIND } from "@/const";
|
import { LIVE_STREAM, SHORTS_KIND, VIDEO_KIND } from "@/const";
|
||||||
import { useStreamLink } from "@/hooks/stream-link";
|
import { useStreamLink } from "@/hooks/stream-link";
|
||||||
import { getEventFromLocationState } from "@/utils";
|
import { getEventFromLocationState } from "@/utils";
|
||||||
import { NostrPrefix, EventKind } from "@snort/system";
|
import { NostrPrefix } from "@snort/system";
|
||||||
import { useLocation } from "react-router-dom";
|
import { useLocation } from "react-router-dom";
|
||||||
import { StreamPage } from "./stream-page";
|
import { StreamPage } from "./stream-page";
|
||||||
import { VideoPage } from "./video";
|
import { VideoPage } from "./video";
|
||||||
@ -25,7 +25,7 @@ export function LinkHandler() {
|
|||||||
<NostrEventElement link={link} />
|
<NostrEventElement link={link} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
} else if (link.kind === EventKind.LiveEvent || link.type === NostrPrefix.PublicKey) {
|
} else if (link.kind === LIVE_STREAM || link.type === NostrPrefix.PublicKey) {
|
||||||
return (
|
return (
|
||||||
<div className={classNames(layoutContext.showHeader ? "h-[calc(100dvh-44px)]" : "h-[calc(100dvh)]", "w-full")}>
|
<div className={classNames(layoutContext.showHeader ? "h-[calc(100dvh-44px)]" : "h-[calc(100dvh)]", "w-full")}>
|
||||||
<StreamPage link={link} evPreload={evPreload} />
|
<StreamPage link={link} evPreload={evPreload} />
|
||||||
|
@ -10,7 +10,7 @@ import { useZapGoal } from "@/hooks/goals";
|
|||||||
import { StreamCards } from "@/element/stream-cards";
|
import { StreamCards } from "@/element/stream-cards";
|
||||||
import { ContentWarningOverlay, useContentWarning } from "@/element/nsfw";
|
import { ContentWarningOverlay, useContentWarning } from "@/element/nsfw";
|
||||||
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
import { useCurrentStreamFeed } from "@/hooks/current-stream-feed";
|
||||||
import { StreamState } from "@/const";
|
import { LIVE_STREAM, StreamState } from "@/const";
|
||||||
import { StreamInfo } from "@/element/stream/stream-info";
|
import { StreamInfo } from "@/element/stream/stream-info";
|
||||||
import { StreamContextProvider } from "@/element/stream/stream-state";
|
import { StreamContextProvider } from "@/element/stream/stream-state";
|
||||||
|
|
||||||
@ -46,23 +46,25 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
|
|||||||
<StreamContextProvider link={link}>
|
<StreamContextProvider link={link}>
|
||||||
<div className="xl:grid xl:grid-cols-[auto_450px] 2xl:xl:grid-cols-[auto_500px] max-xl:flex max-xl:flex-col xl:gap-4 max-xl:gap-1 h-full">
|
<div className="xl:grid xl:grid-cols-[auto_450px] 2xl:xl:grid-cols-[auto_500px] max-xl:flex max-xl:flex-col xl:gap-4 max-xl:gap-1 h-full">
|
||||||
<Helmet>
|
<Helmet>
|
||||||
<title>{`${title} - zap.stream`}</title>
|
<title>{`${title ?? "Untitled"} - zap.stream`}</title>
|
||||||
<meta name="description" content={descriptionContent} />
|
<meta name="description" content={descriptionContent} />
|
||||||
<meta property="og:url" content={`https://${window.location.host}/${link.encode()}`} />
|
<meta property="og:url" content={`https://${window.location.host}/${link.encode()}`} />
|
||||||
<meta property="og:type" content="video" />
|
<meta property="og:type" content="video" />
|
||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title ?? "Untitled"} />
|
||||||
<meta property="og:description" content={descriptionContent} />
|
<meta property="og:description" content={descriptionContent} />
|
||||||
<meta property="og:image" content={image ?? ""} />
|
<meta property="og:image" content={image ?? ""} />
|
||||||
</Helmet>
|
</Helmet>
|
||||||
<div className="flex flex-col gap-2 xl:overflow-y-auto scrollbar-hidden">
|
<div className="flex flex-col gap-2 xl:overflow-y-auto scrollbar-hidden">
|
||||||
<Suspense>
|
<Suspense>
|
||||||
<LiveVideoPlayer
|
{ev?.kind === LIVE_STREAM &&
|
||||||
title={title}
|
<LiveVideoPlayer
|
||||||
stream={status === StreamState.Live ? stream : recording}
|
title={title}
|
||||||
poster={image}
|
stream={status === StreamState.Live ? stream : recording}
|
||||||
status={status}
|
poster={image}
|
||||||
className="max-xl:max-h-[30vh] xl:w-full xl:max-h-[85dvh] mx-auto"
|
status={status}
|
||||||
/>
|
link={evLink}
|
||||||
|
className="max-xl:max-h-[30vh] xl:w-full xl:max-h-[85dvh] mx-auto"
|
||||||
|
/>}
|
||||||
</Suspense>
|
</Suspense>
|
||||||
<div className="lg:px-5 max-lg:px-2">
|
<div className="lg:px-5 max-lg:px-2">
|
||||||
<StreamInfo ev={ev as TaggedNostrEvent} goal={goal} />
|
<StreamInfo ev={ev as TaggedNostrEvent} goal={goal} />
|
||||||
|
14
src/utils.ts
14
src/utils.ts
@ -115,6 +115,7 @@ export interface StreamInfo {
|
|||||||
host?: string;
|
host?: string;
|
||||||
gameId?: string;
|
gameId?: string;
|
||||||
gameInfo?: GameInfo;
|
gameInfo?: GameInfo;
|
||||||
|
streams: Array<string>
|
||||||
}
|
}
|
||||||
|
|
||||||
const gameTagFormat = /^[a-z-]+:[a-z0-9-]+$/i;
|
const gameTagFormat = /^[a-z-]+:[a-z0-9-]+$/i;
|
||||||
@ -135,8 +136,9 @@ export function extractStreamInfo(ev?: NostrEvent) {
|
|||||||
matchTag(t, "image", v => (ret.image = v));
|
matchTag(t, "image", v => (ret.image = v));
|
||||||
matchTag(t, "thumbnail", v => (ret.thumbnail = v));
|
matchTag(t, "thumbnail", v => (ret.thumbnail = v));
|
||||||
matchTag(t, "status", v => (ret.status = v as StreamState));
|
matchTag(t, "status", v => (ret.status = v as StreamState));
|
||||||
if (t[0] === "streaming" && t[1].startsWith("http")) {
|
if (t[0] === "streaming") {
|
||||||
matchTag(t, "streaming", v => (ret.stream = v));
|
ret.streams ??= [];
|
||||||
|
ret.streams.push(t[1]);
|
||||||
}
|
}
|
||||||
matchTag(t, "recording", v => (ret.recording = v));
|
matchTag(t, "recording", v => (ret.recording = v));
|
||||||
matchTag(t, "url", v => (ret.recording = v));
|
matchTag(t, "url", v => (ret.recording = v));
|
||||||
@ -154,6 +156,14 @@ export function extractStreamInfo(ev?: NostrEvent) {
|
|||||||
ret.gameId = gameId;
|
ret.gameId = gameId;
|
||||||
ret.gameInfo = gameInfo;
|
ret.gameInfo = gameInfo;
|
||||||
|
|
||||||
|
if (ret.streams) {
|
||||||
|
const isN94 = ret.streams.includes("nip94");
|
||||||
|
if (isN94) {
|
||||||
|
ret.stream = "nip94";
|
||||||
|
} else {
|
||||||
|
ret.stream = ret.streams.find(a => a.includes(".m3u8"));
|
||||||
|
}
|
||||||
|
}
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
25
yarn.lock
25
yarn.lock
@ -4435,6 +4435,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"es6-promise@npm:^4.2.5":
|
||||||
|
version: 4.2.8
|
||||||
|
resolution: "es6-promise@npm:4.2.8"
|
||||||
|
checksum: 10c0/2373d9c5e9a93bdd9f9ed32ff5cb6dd3dd785368d1c21e9bbbfd07d16345b3774ae260f2bd24c8f836a6903f432b4151e7816a7fa8891ccb4e1a55a028ec42c3
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3":
|
"es6-symbol@npm:^3.1.1, es6-symbol@npm:^3.1.3":
|
||||||
version: 3.1.4
|
version: 3.1.4
|
||||||
resolution: "es6-symbol@npm:3.1.4"
|
resolution: "es6-symbol@npm:3.1.4"
|
||||||
@ -6219,6 +6226,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"mpegts.js@npm:^1.7.3":
|
||||||
|
version: 1.7.3
|
||||||
|
resolution: "mpegts.js@npm:1.7.3"
|
||||||
|
dependencies:
|
||||||
|
es6-promise: "npm:^4.2.5"
|
||||||
|
webworkify-webpack: "npm:^2.1.5"
|
||||||
|
checksum: 10c0/6a0f5f5815114d1d32ea6ae4902f787bdb4e467e9537f8fd12152eeedc1f82fb9ca260c64891fabebf0af37229ba184f6e35136cbc77b670f9cf6e63720448d0
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"ms@npm:2.0.0":
|
"ms@npm:2.0.0":
|
||||||
version: 2.0.0
|
version: 2.0.0
|
||||||
resolution: "ms@npm:2.0.0"
|
resolution: "ms@npm:2.0.0"
|
||||||
@ -7661,6 +7678,7 @@ __metadata:
|
|||||||
hls-video-element: "npm:^1.2.7"
|
hls-video-element: "npm:^1.2.7"
|
||||||
marked: "npm:^12.0.2"
|
marked: "npm:^12.0.2"
|
||||||
media-chrome: "npm:^3.2.4"
|
media-chrome: "npm:^3.2.4"
|
||||||
|
mpegts.js: "npm:^1.7.3"
|
||||||
postcss: "npm:^8.4.38"
|
postcss: "npm:^8.4.38"
|
||||||
prettier: "npm:^3.2.5"
|
prettier: "npm:^3.2.5"
|
||||||
prop-types: "npm:^15.8.1"
|
prop-types: "npm:^15.8.1"
|
||||||
@ -8505,6 +8523,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"webworkify-webpack@npm:^2.1.5":
|
||||||
|
version: 2.1.5
|
||||||
|
resolution: "webworkify-webpack@npm:2.1.5"
|
||||||
|
checksum: 10c0/672108eef17df3d01d7a499509d722a09dd65dd6e7f9316c21cd559c106fb162a1304b5f326977748aa92ad2bab917a0891ce26d244da083a5e16b752ab91058
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"whatwg-url@npm:^7.0.0":
|
"whatwg-url@npm:^7.0.0":
|
||||||
version: 7.1.0
|
version: 7.1.0
|
||||||
resolution: "whatwg-url@npm:7.1.0"
|
resolution: "whatwg-url@npm:7.1.0"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user