mirror of
https://github.com/nestriness/nestri.git
synced 2025-12-12 08:45:38 +02:00
This adds: - [x] Keyboard and mouse handling on the frontend - [x] Video and audio streaming from the backend to the frontend - [x] Input server that works with Websockets Update - 17/11 - [ ] Master docker container to run this - [ ] Steam runtime - [ ] Entrypoint.sh --------- Co-authored-by: Kristian Ollikainen <14197772+DatCaptainHorse@users.noreply.github.com> Co-authored-by: Kristian Ollikainen <DatCaptainHorse@users.noreply.github.com>
109 lines
2.9 KiB
TypeScript
109 lines
2.9 KiB
TypeScript
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() {}
|
|
}
|