import * as Catalog from "../karp/catalog" import * as Transfork from "../transfork" import * as Audio from "./audio" import * as Video from "./video" import { isAudioTrackSettings, isVideoTrackSettings } from "../common/settings" export interface BroadcastConfig { path: string[] media: MediaStream id?: number audio?: AudioEncoderConfig video?: VideoEncoderConfig } export interface BroadcastConfigTrack { codec: string bitrate: number } export class Broadcast { #config: BroadcastConfig #path: string[] constructor(config: BroadcastConfig) { const id = config.id || new Date().getTime() / 1000 this.#config = config this.#path = config.path.concat(id.toString()) } async publish(connection: Transfork.Connection) { const broadcast: Catalog.Broadcast = { path: this.#config.path, audio: [], video: [], } for (const media of this.#config.media.getTracks()) { const settings = media.getSettings() const info = { name: media.id, // TODO way too verbose priority: media.kind === "video" ? 1 : 2, } const track = new Transfork.Track(this.#config.path.concat(info.name), info.priority) if (isVideoTrackSettings(settings)) { if (!this.#config.video) { throw new Error("no video configuration provided") } const encoder = new Video.Encoder(this.#config.video) const packer = new Video.Packer(media as MediaStreamVideoTrack, encoder, track) // TODO handle error packer.run().catch((err) => console.error("failed to run video packer: ", err)) const decoder = await encoder.decoderConfig() const description = decoder.description ? new Uint8Array(decoder.description as ArrayBuffer) : undefined const video: Catalog.Video = { track: info, codec: decoder.codec, description: description, resolution: { width: settings.width, height: settings.height }, frame_rate: settings.frameRate, bitrate: this.#config.video.bitrate, } broadcast.video.push(video) } else if (isAudioTrackSettings(settings)) { if (!this.#config.audio) { throw new Error("no audio configuration provided") } const encoder = new Audio.Encoder(this.#config.audio) const packer = new Audio.Packer(media as MediaStreamAudioTrack, encoder, track) packer.run().catch((err) => console.error("failed to run audio packer: ", err)) // TODO handle error const decoder = await encoder.decoderConfig() const audio: Catalog.Audio = { track: info, codec: decoder.codec, sample_rate: settings.sampleRate, channel_count: settings.channelCount, bitrate: this.#config.audio.bitrate, } broadcast.audio.push(audio) } else { throw new Error(`unknown track type: ${media.kind}`) } connection.publish(track.reader()) } const track = new Transfork.Track(this.#config.path.concat("catalog.json"), 0) track.appendGroup().writeFrames(Catalog.encode(broadcast)) connection.publish(track.reader()) } close() {} }