mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 16:55:37 +02:00
✨ feat: Host a relay on Hetzner (#114)
We are hosting a [MoQ](https://quic.video) relay on a remote (bare metal) server on Hetzner With a lot of help from @victorpahuus
This commit is contained in:
119
packages/moq/playback/worker/index.ts
Normal file
119
packages/moq/playback/worker/index.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
import { Timeline } from "./timeline"
|
||||
|
||||
import * as Audio from "./audio"
|
||||
import * as Video from "./video"
|
||||
|
||||
import * as MP4 from "../../media/mp4"
|
||||
import * as Message from "./message"
|
||||
import { asError } from "../../common/error"
|
||||
import { Deferred } from "../../common/async"
|
||||
import { GroupReader, Reader } from "../../transport/objects"
|
||||
|
||||
class Worker {
|
||||
// Timeline receives samples, buffering them and choosing the timestamp to render.
|
||||
#timeline = new Timeline()
|
||||
|
||||
// A map of init tracks.
|
||||
#inits = new Map<string, Deferred<Uint8Array>>()
|
||||
|
||||
// Renderer requests samples, rendering video frames and emitting audio frames.
|
||||
#audio?: Audio.Renderer
|
||||
#video?: Video.Renderer
|
||||
|
||||
on(e: MessageEvent) {
|
||||
const msg = e.data as Message.ToWorker
|
||||
|
||||
if (msg.config) {
|
||||
this.#onConfig(msg.config)
|
||||
} else if (msg.init) {
|
||||
// TODO buffer the init segmnet so we don't hold the stream open.
|
||||
this.#onInit(msg.init)
|
||||
} else if (msg.segment) {
|
||||
this.#onSegment(msg.segment).catch(console.warn)
|
||||
} else {
|
||||
throw new Error(`unknown message: + ${JSON.stringify(msg)}`)
|
||||
}
|
||||
}
|
||||
|
||||
#onConfig(msg: Message.Config) {
|
||||
if (msg.audio) {
|
||||
this.#audio = new Audio.Renderer(msg.audio, this.#timeline.audio)
|
||||
}
|
||||
|
||||
if (msg.video) {
|
||||
this.#video = new Video.Renderer(msg.video, this.#timeline.video)
|
||||
}
|
||||
}
|
||||
|
||||
#onInit(msg: Message.Init) {
|
||||
let init = this.#inits.get(msg.name)
|
||||
if (!init) {
|
||||
init = new Deferred()
|
||||
this.#inits.set(msg.name, init)
|
||||
}
|
||||
|
||||
init.resolve(msg.data)
|
||||
}
|
||||
|
||||
async #onSegment(msg: Message.Segment) {
|
||||
let init = this.#inits.get(msg.init)
|
||||
if (!init) {
|
||||
init = new Deferred()
|
||||
this.#inits.set(msg.init, init)
|
||||
}
|
||||
|
||||
// Create a new stream that we will use to decode.
|
||||
const container = new MP4.Parser(await init.promise)
|
||||
|
||||
const timeline = msg.kind === "audio" ? this.#timeline.audio : this.#timeline.video
|
||||
const reader = new GroupReader(msg.header, new Reader(msg.buffer, msg.stream))
|
||||
|
||||
// Create a queue that will contain each MP4 frame.
|
||||
const queue = new TransformStream<MP4.Frame>({})
|
||||
const segment = queue.writable.getWriter()
|
||||
|
||||
// Add the segment to the timeline
|
||||
const segments = timeline.segments.getWriter()
|
||||
await segments.write({
|
||||
sequence: msg.header.group,
|
||||
frames: queue.readable,
|
||||
})
|
||||
segments.releaseLock()
|
||||
|
||||
// Read each chunk, decoding the MP4 frames and adding them to the queue.
|
||||
for (;;) {
|
||||
const chunk = await reader.read()
|
||||
if (!chunk) {
|
||||
break
|
||||
}
|
||||
|
||||
if (!(chunk.payload instanceof Uint8Array)) {
|
||||
throw new Error(`invalid payload: ${chunk.payload}`)
|
||||
}
|
||||
|
||||
const frames = container.decode(chunk.payload)
|
||||
for (const frame of frames) {
|
||||
await segment.write(frame)
|
||||
}
|
||||
}
|
||||
|
||||
// We done.
|
||||
await segment.close()
|
||||
}
|
||||
}
|
||||
|
||||
// Pass all events to the worker
|
||||
const worker = new Worker()
|
||||
self.addEventListener("message", (msg) => {
|
||||
try {
|
||||
worker.on(msg)
|
||||
} catch (e) {
|
||||
const err = asError(e)
|
||||
console.warn("worker error:", err)
|
||||
}
|
||||
})
|
||||
|
||||
// Validates this is an expected message
|
||||
function _send(msg: Message.FromWorker) {
|
||||
postMessage(msg)
|
||||
}
|
||||
Reference in New Issue
Block a user