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>() // 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({}) 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) }