feat: Add streaming support (#125)

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>
This commit is contained in:
Wanjohi
2024-12-08 14:54:56 +03:00
committed by GitHub
parent 5eb21eeadb
commit 379db1c87b
137 changed files with 12737 additions and 5234 deletions

View File

@@ -0,0 +1,64 @@
import type { Group, GroupReader } from "../transfork/model"
import { setVint62 } from "../transfork/stream"
export type FrameType = "key" | "delta"
export class Frame {
type: FrameType
timestamp: number
data: Uint8Array
constructor(type: FrameType, timestamp: number, data: Uint8Array) {
this.type = type
this.timestamp = timestamp
this.data = data
}
static async decode(group: GroupReader): Promise<Frame | undefined> {
const kind = group.index === 0 ? "key" : "delta"
const payload = await group.readFrame()
if (!payload) {
return undefined
}
const [timestamp, data] = decode_timestamp(payload)
return new Frame(kind, timestamp, data)
}
encode(group: Group) {
if ((group.length === 0) !== (this.type === "key")) {
throw new Error(`invalid ${this.type} position`)
}
let frame = new Uint8Array(8 + this.data.byteLength)
const size = setVint62(frame, BigInt(this.timestamp)).byteLength
frame.set(this.data, size)
frame = new Uint8Array(frame.buffer, 0, this.data.byteLength + size)
group.writeFrame(frame)
}
}
// QUIC VarInt
function decode_timestamp(buf: Uint8Array): [number, Uint8Array] {
const size = 1 << ((buf[0] & 0xc0) >> 6)
const view = new DataView(buf.buffer, buf.byteOffset, size)
const remain = new Uint8Array(buf.buffer, buf.byteOffset + size, buf.byteLength - size)
let v: number
if (size === 1) {
v = buf[0] & 0x3f
} else if (size === 2) {
v = view.getInt16(0) & 0x3fff
} else if (size === 4) {
v = view.getUint32(0) & 0x3fffffff
} else if (size === 8) {
// NOTE: Precision loss above 2^52
v = Number(view.getBigUint64(0) & 0x3fffffffffffffffn)
} else {
throw new Error("impossible")
}
return [v, remain]
}