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) {
function innerPlayer() {
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 (
<MediaController

View File

@ -1,158 +1,165 @@
/* eslint-disable @typescript-eslint/ban-ts-comment */
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 { SnortContext } from "@snort/system-react";
import { findTag } from "@/utils";
interface MediaSegment {
created: number,
sha256: string,
url: string,
duration: number,
loaded: boolean
created: number;
sha256: string;
url: string;
duration: number;
loaded: boolean;
}
enum LoaderStatus {
kIdle = 0,
kConnecting = 1,
kBuffering = 2,
kError = 3,
kComplete = 4
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;
#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"
}
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");
}
// @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;
const rb = new RequestBuilder(`n94-stream-${link.encode()}`);
rb.withOptions({
leaveOpen: true,
});
rb.withFilter().replyToLink([link]).kinds([EventKind.FileHeader]);
get _status() { return this.#status }
get status() { return this.#status }
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();
});
}
destroy(): void {
throw new Error("Method not implemented.");
abort() {
this.#query?.cancel();
}
#addSegment(seg: MediaSegment) {
if (!this.#mediaChunks.has(seg.sha256)) {
this.#mediaChunks.set(seg.sha256, seg);
}
}
isWorking(): boolean {
throw new Error("Method not implemented.");
async #loadNext() {
if (this.#status === LoaderStatus.kConnecting || this.#status === LoaderStatus.kIdle) {
this.#status = LoaderStatus.kBuffering;
}
type = "nip94"
get needStashBuffer() {
return false;
if (this.#status !== LoaderStatus.kBuffering) {
return;
}
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) {
const link = parseNostrLink(dataSource.url);
if (!link) {
throw new Error("Datasource.url is invalid")
}
this.onDataArrival(buf, this.#bytes, buf.byteLength);
console.debug("pushing bytes", this.#bytes, buf.byteLength);
this.#bytes += buf.byteLength;
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;
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);
const ref = useRef(null);
const system = useContext(SnortContext);
useEffect(() => {
if (ref.current) {
const player = ref.current as HTMLVideoElement & {
__streamer?: mpegts.Player
};
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);
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]);
player.__streamer.attachMediaElement(player);
player.__streamer.load();
player.__streamer.play();
}
}
}, [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
.filter(a => {
try {
return (
findTag(a, "status") === StreamState.Live && canPlayEvent(a)
);
return findTag(a, "status") === StreamState.Live && canPlayEvent(a);
} catch {
return false;
}

View File

@ -56,7 +56,7 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
</Helmet>
<div className="flex flex-col gap-2 xl:overflow-y-auto scrollbar-hidden">
<Suspense>
{ev?.kind === LIVE_STREAM &&
{ev?.kind === LIVE_STREAM && (
<LiveVideoPlayer
title={title}
stream={status === StreamState.Live ? stream : recording}
@ -64,7 +64,8 @@ export function StreamPage({ link, evPreload }: { evPreload?: TaggedNostrEvent;
status={status}
link={evLink}
className="max-xl:max-h-[30vh] xl:w-full xl:max-h-[85dvh] mx-auto"
/>}
/>
)}
</Suspense>
<div className="lg:px-5 max-lg:px-2">
<StreamInfo ev={ev as TaggedNostrEvent} goal={goal} />

View File

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