chore: Update translations

This commit is contained in:
kieran 2024-11-19 17:15:36 +00:00
parent 03a5820140
commit aa7aca8823
5 changed files with 144 additions and 136 deletions

View File

@ -35,10 +35,12 @@ type VideoPlayerProps = {
export default function LiveVideoPlayer({ title, stream, status, poster, link, ...props }: VideoPlayerProps) { export default function LiveVideoPlayer({ title, stream, status, poster, link, ...props }: VideoPlayerProps) {
function innerPlayer() { function innerPlayer() {
if (stream === "nip94") { if (stream === "nip94") {
return <Nip94Player link={link} /> return <Nip94Player link={link} />;
} }
{/* @ts-ignore Web Componenet */ } {
return <hls-video {...props} slot="media" src={stream} playsInline={true} autoPlay={true} /> /* @ts-ignore Web Componenet */
}
return <hls-video {...props} slot="media" src={stream} playsInline={true} autoPlay={true} />;
} }
return ( return (
<MediaController <MediaController

View File

@ -1,158 +1,165 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */ /* eslint-disable @typescript-eslint/ban-ts-comment */
import { EventKind, NostrLink, QueryLike, RequestBuilder, SystemInterface, parseNostrLink } from "@snort/system"; import { EventKind, NostrLink, QueryLike, RequestBuilder, SystemInterface, parseNostrLink } from "@snort/system";
import { useContext, useEffect, useRef } from "react" import { useContext, useEffect, useRef } from "react";
import mpegts from "mpegts.js"; import mpegts from "mpegts.js";
import { SnortContext } from "@snort/system-react"; import { SnortContext } from "@snort/system-react";
import { findTag } from "@/utils"; import { findTag } from "@/utils";
interface MediaSegment { interface MediaSegment {
created: number, created: number;
sha256: string, sha256: string;
url: string, url: string;
duration: number, duration: number;
loaded: boolean loaded: boolean;
} }
enum LoaderStatus { enum LoaderStatus {
kIdle = 0, kIdle = 0,
kConnecting = 1, kConnecting = 1,
kBuffering = 2, kBuffering = 2,
kError = 3, kError = 3,
kComplete = 4 kComplete = 4,
} }
class Nip94Loader implements mpegts.BaseLoader { class Nip94Loader implements mpegts.BaseLoader {
#system!: SystemInterface #system!: SystemInterface;
#query?: QueryLike #query?: QueryLike;
#mediaChunks: Map<string, MediaSegment> = new Map(); #mediaChunks: Map<string, MediaSegment> = new Map();
#status: LoaderStatus = LoaderStatus.kIdle; #status: LoaderStatus = LoaderStatus.kIdle;
#bytes = 0; #bytes = 0;
constructor(_seekHandler: mpegts.SeekHandler, config: mpegts.Config) { constructor(_seekHandler: mpegts.SeekHandler, config: mpegts.Config) {
if ("system" in config) { if ("system" in config) {
this.#system = config.system as SystemInterface; this.#system = config.system as SystemInterface;
} else { } else {
throw "Invalid config, missing system" 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");
} }
// @ts-expect-error const rb = new RequestBuilder(`n94-stream-${link.encode()}`);
onContentLengthKnown: (contentLength: number) => void; rb.withOptions({
// @ts-expect-error leaveOpen: true,
onURLRedirect: (redirectedURL: string) => void; });
// @ts-expect-error rb.withFilter().replyToLink([link]).kinds([EventKind.FileHeader]);
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 } this.#status = LoaderStatus.kConnecting;
get status() { return this.#status } 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();
});
}
destroy(): void { abort() {
throw new Error("Method not implemented."); this.#query?.cancel();
}
#addSegment(seg: MediaSegment) {
if (!this.#mediaChunks.has(seg.sha256)) {
this.#mediaChunks.set(seg.sha256, seg);
} }
}
isWorking(): boolean { async #loadNext() {
throw new Error("Method not implemented."); if (this.#status === LoaderStatus.kConnecting || this.#status === LoaderStatus.kIdle) {
this.#status = LoaderStatus.kBuffering;
} }
if (this.#status !== LoaderStatus.kBuffering) {
type = "nip94" return;
get needStashBuffer() {
return false;
} }
get _needStash() { return this.needStashBuffer } 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();
open(dataSource: mpegts.MediaSegment, range: mpegts.Range) { this.onDataArrival(buf, this.#bytes, buf.byteLength);
const link = parseNostrLink(dataSource.url); console.debug("pushing bytes", this.#bytes, buf.byteLength);
if (!link) { this.#bytes += buf.byteLength;
throw new Error("Datasource.url is invalid")
}
const rb = new RequestBuilder(`n94-stream-${link.encode()}`); this.#mediaChunks.set(s.sha256, {
rb.withOptions({ ...s,
leaveOpen: true, loaded: 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;
} }
this.#status = LoaderStatus.kIdle;
}
} }
export default function Nip94Player({ link }: { link: NostrLink }) { export default function Nip94Player({ link }: { link: NostrLink }) {
const ref = useRef(null); const ref = useRef(null);
const system = useContext(SnortContext); const system = useContext(SnortContext);
useEffect(() => { useEffect(() => {
if (ref.current) { if (ref.current) {
const player = ref.current as HTMLVideoElement & { const player = ref.current as HTMLVideoElement & {
__streamer?: mpegts.Player __streamer?: mpegts.Player;
}; };
if (!player.__streamer) { if (!player.__streamer) {
player.__streamer = new mpegts.MSEPlayer({ player.__streamer = new mpegts.MSEPlayer(
type: "mse", {
isLive: true, type: "mse",
url: link.encode() isLive: true,
}, { url: link.encode(),
system, },
customLoader: Nip94Loader {
} as unknown as mpegts.Config); system,
customLoader: Nip94Loader,
} as unknown as mpegts.Config,
);
player.__streamer.attachMediaElement(player); player.__streamer.attachMediaElement(player);
player.__streamer.load(); player.__streamer.load();
player.__streamer.play(); player.__streamer.play();
} }
} }
}, [ref]); }, [ref]);
return <video slot="media" ref={ref}></video> return <video slot="media" ref={ref}></video>;
} }

View File

@ -36,9 +36,7 @@ export function useSortedStreams(feed: Array<TaggedNostrEvent>, oldest?: number)
const live = feedSorted const live = feedSorted
.filter(a => { .filter(a => {
try { try {
return ( return findTag(a, "status") === StreamState.Live && canPlayEvent(a);
findTag(a, "status") === StreamState.Live && canPlayEvent(a)
);
} catch { } catch {
return false; return false;
} }

View File

@ -56,7 +56,7 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
</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>
{ev?.kind === LIVE_STREAM && {ev?.kind === LIVE_STREAM && (
<LiveVideoPlayer <LiveVideoPlayer
title={title} title={title}
stream={status === StreamState.Live ? stream : recording} stream={status === StreamState.Live ? stream : recording}
@ -64,7 +64,8 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
status={status} status={status}
link={evLink} link={evLink}
className="max-xl:max-h-[30vh] xl:w-full xl:max-h-[85dvh] mx-auto" 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} />

View File

@ -115,7 +115,7 @@ export interface StreamInfo {
host?: string; host?: string;
gameId?: string; gameId?: string;
gameInfo?: GameInfo; gameInfo?: GameInfo;
streams: Array<string> streams: Array<string>;
} }
const gameTagFormat = /^[a-z-]+:[a-z0-9-]+$/i; const gameTagFormat = /^[a-z-]+:[a-z0-9-]+$/i;