moq-rs/player/src/video/decoder.ts
Luke Curley cc00a79881 Initial progress on WebCodecs.
I'm on a plane and it's $18 to get Wifi for an hour.
2023-03-26 16:36:40 -07:00

123 lines
3.5 KiB
TypeScript

import * as Message from "./message";
import { Track } from "./track";
import { Renderer } from "./renderer"
import { MP4New, MP4Sample, MP4ArrayBuffer } from "../mp4/mp4"
export class Decoder {
tracks: Map<string, Track>;
renderer: Renderer;
constructor(renderer: Renderer) {
this.tracks = new Map();
this.renderer = renderer;
}
async init(msg: Message.Init) {
let track = this.tracks.get(msg.track);
if (!track) {
track = new Track()
this.tracks.set(msg.track, track)
}
while (1) {
const data = await msg.stream.read()
if (!data) break
track.init(data)
}
// TODO this will hang on incomplete data
const init = await track.ready;
const info = init.info;
if (info.audioTracks.length + info.videoTracks.length != 1) {
throw new Error("expected a single track")
}
}
async decode(msg: Message.Segment) {
let track = this.tracks.get(msg.track);
if (!track) {
track = new Track()
this.tracks.set(msg.track, track)
}
// Wait for the init segment to be fully received and parsed
const init = await track.ready;
const info = init.info;
const video = info.videoTracks[0]
const decoder = new VideoDecoder({
output: (frame: VideoFrame) => {
this.renderer.push(frame)
},
error: (err: Error) => {
console.warn(err)
}
});
decoder.configure({
codec: info.mime,
codedHeight: video.track_height,
codedWidth: video.track_width,
// optimizeForLatency: true
})
const input = MP4New();
input.onSamples = (id: number, user: any, samples: MP4Sample[]) => {
for (let sample of samples) {
const timestamp = sample.dts / (1000 / info.timescale) // milliseconds
decoder.decode(new EncodedVideoChunk({
data: sample.data,
duration: sample.duration,
timestamp: timestamp,
type: sample.is_sync ? "key" : "delta",
}))
}
}
input.onReady = (info: any) => {
input.setExtractionOptions(info.tracks[0].id, {}, { nbSamples: 1 });
input.start();
}
let offset = 0
// MP4box requires us to reparse the init segment unfortunately
for (let raw of track.raw) {
offset = input.appendBuffer(raw)
}
/* TODO I'm not actually sure why this code doesn't work; something trips up the MP4 parser
while (1) {
const data = await stream.read()
if (!data) break
input.appendBuffer(data)
input.flush()
}
*/
// One day I'll figure it out; until then read one top-level atom at a time
while (!await msg.stream.done()) {
const raw = await msg.stream.peek(4)
const size = new DataView(raw.buffer, raw.byteOffset, raw.byteLength).getUint32(0)
const atom = await msg.stream.bytes(size)
// Make a copy of the atom because mp4box only accepts an ArrayBuffer unfortunately
let box = new Uint8Array(atom.byteLength);
box.set(atom)
// and for some reason we need to modify the underlying ArrayBuffer with offset
let buffer = box.buffer as MP4ArrayBuffer
buffer.fileStart = offset
// Parse the data
offset = input.appendBuffer(buffer)
input.flush()
}
}
}