mfx
Version:
In-browser video editing toolkit, with effects accelerated by WebGL
119 lines (109 loc) • 3.13 kB
text/typescript
import { Muxer as MP4Muxer, StreamTarget as MP4StreamTarget } from "mp4-muxer";
import type { MFXEncodedChunk } from "../../types";
import { MFXTransformStream } from "../../stream";
import { MFXBlob } from "../../blob";
import type { ContainerEncoderConfig } from "../encoderConfig";
/**
* @group Encode
*/
export class MP4ContainerEncoder extends MFXTransformStream<
MFXEncodedChunk,
MFXBlob
> {
get identifier() {
return "MP4ContainerEncoder";
}
ready: Promise<any>;
constructor(config: ContainerEncoderConfig) {
const videoCodecMapper = (
codec: ContainerEncoderConfig["video"]["codec"],
): "avc" | "hevc" | "vp9" | "av1" => {
const targets: ("avc" | "hevc" | "vp9" | "av1")[] = [
"avc",
"hevc",
"vp9",
"av1",
];
if (codec.startsWith("hvc")) {
return "hevc";
}
for (let i = 0; i < targets.length; i++) {
if (codec.startsWith(targets[i])) {
return targets[i];
}
}
throw new Error(`Unsupported MP4 video codec ${codec}`);
};
const audioCodecMapper = (
codec: ContainerEncoderConfig["audio"]["codec"],
) => {
const targets: ("opus" | "aac")[] = ["opus", "aac"];
for (let i = 0; i < targets.length; i++) {
if (codec.startsWith(targets[i])) {
return targets[i];
}
}
throw new Error(`Unsupported MP4 audio codec ${codec}`);
};
let muxer: MP4Muxer<MP4StreamTarget>;
super({
transform: async (chunk) => {
// TODO: support raw chunks such as addAudioChunkRaw
// to allow passthrough tracks
if (chunk.video) {
muxer.addVideoChunk(chunk.video.chunk, chunk.video.metadata);
}
if (chunk.audio) {
muxer.addAudioChunk(chunk.audio.chunk, chunk.audio.metadata);
}
},
flush: async () => {
muxer.finalize();
},
});
muxer = new MP4Muxer({
...(config.video
? {
video: {
frameRate: config.video.framerate,
height: config.video.height,
width: config.video.width,
codec: videoCodecMapper(config.video.codec),
},
}
: {}),
...(config.audio
? {
audio: {
codec: audioCodecMapper(config.audio.codec),
numberOfChannels: config.audio.numberOfChannels,
sampleRate: config.audio.sampleRate,
},
}
: {}),
firstTimestampBehavior: "offset",
fastStart: config.streaming
? "fragmented"
: config?.faststart
? "in-memory"
: false,
target: new MP4StreamTarget({
onData: (data, position) => {
this.queue(
new MFXBlob([data], {
type: "video/mp4",
position,
config,
}),
);
},
...(Number.isInteger(config.chunkSize)
? {
chunked: true,
chunkSize: config.chunkSize,
}
: {}),
}),
});
}
}