diff --git a/AGENTS.md b/AGENTS.md
index 3837d41..0dc3cb8 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -2,13 +2,13 @@
## Project Overview
-`@plutotcool/fsv` is a TypeScript library that defines and implements the **FSV (Fast Scrubbing Video)** format — a custom binary video format optimized for frame-accurate scrubbing on the web, with optional alpha-channel support.
+`@plutotcool/fsv` is a TypeScript library that defines and implements the **FSV (Fast Scrubbing Video)** format — a video container format optimized for frame-accurate scrubbing on the web, using H.264 and H.265 codecs with optional alpha-channel support.
The library ships two distinct APIs:
| API | Environment | Purpose |
|-----|-------------|---------|
-| **Conversion** | Node.js | Converts any video file to `.fsv` using ffmpeg (`node-av`) for encoding and config extraction |
+| **Conversion** | Node.js | Converts any video file to `.fsv` (H.264/H.265 container) using ffmpeg (`node-av`) for encoding and config extraction |
| **Decoding / Rendering** | Browser | Loads, demuxes, decodes, and WebGL2-renders `.fsv` files using native browser WebCodecs and WebGL2 APIs |
The package is published to the GitHub Packages registry (`https://npm.pkg.github.com`) under the `@plutotcool` scope.
@@ -185,8 +185,8 @@ The manifest is serialized as a compact flat number array: each frame is 4 conse
- Uses `node-av` (ffmpeg bindings) for decoding and encoding
- Uses a `FilterComplexAPI` for pixel format conversion to `yuv420p`
- For alpha videos, uses ffmpeg's `alphaextract` filter to produce separate color + alpha streams
-- Supported output codecs: `libx264`, `libx265`, `libvpx-vp8`, `libvpx-vp9`
-- Default output codec: `libx264` → mp4 container
+- Supported output codecs: `libx264` (H.264), `libx265` (H.265)
+- Default output codec: `libx264`
- Default encoder settings: `crf=20`, `gopSize=5`, `maxBFrames=0`, `threadType=FF_THREAD_FRAME`
- Sets `AV_CODEC_FLAG_GLOBAL_HEADER` on each encoder so SPS/PPS go into extradata (not inlined in keyframes)
- Collects raw packets in memory as `Packet[]` — no temp files or intermediate container
@@ -200,7 +200,7 @@ The manifest is serialized as a compact flat number array: each frame is 4 conse
### Muxer (Node.js only — `src/core/Muxer.ts`)
- Accepts `Packet[]` arrays from Converter (no container demuxing)
-- Converts Annex B → AVCC bitstream for H.264/H.265 packets (VP8/VP9 pass through unchanged)
+- Converts Annex B → AVCC bitstream for H.264/H.265 packets
- Packs packets and manifest directly into FSV binary format
- Reads width/height from `config.codedWidth`/`config.codedHeight` for manifest serialization
diff --git a/package.json b/package.json
index 0c8eb86..91c6abe 100644
--- a/package.json
+++ b/package.json
@@ -66,6 +66,10 @@
"import": "./dist/cli/main.mjs",
"require": "./dist/cli/main.cjs"
},
+ "./core/Codec": {
+ "import": "./dist/core/Codec.mjs",
+ "require": "./dist/core/Codec.cjs"
+ },
"./core/Converter": {
"import": "./dist/core/Converter.mjs",
"require": "./dist/core/Converter.cjs"
diff --git a/readme.md b/readme.md
index db18c25..a552dd3 100644
--- a/readme.md
+++ b/readme.md
@@ -4,6 +4,11 @@ FSV (short for "Fast Scrubbing Video") is a video format optimized for fast
scrubbing on the web, supporting alpha channel. It has been inspired by the
[ActiveFrame](https://github.com/activetheory/activeframe) project.
+More specifically, FSV is a container for h264 and h265. It is designed to be
+easily demuxable on the browser, and contains additional metadata to allow for
+frame-accurate scrubbing and efficient seeking. See the [format](#format)
+section for more details.
+
[See the demo](https://plutotcool-fsv.vercel.app)
@@ -78,8 +83,8 @@ await Converter.convert('input.webm', 'output.fsv', {
The input file codec is automatically detected if not provided. Yet the
conversion may fail (especially for alpha videos) if the input codec is not
-compatible with the conversion process. For VP8/VP9 videos, you may need to
-explicitly set the input codec to `libvpx-vp8` or `libvpx-vp9`.
+compatible with the conversion process. For VP8/VP9 input videos, you may
+need to explicitly set the input codec to `libvpx-vp8` or `libvpx-vp9`.
## Rendering
diff --git a/src/cli/convert.ts b/src/cli/convert.ts
index f6da83e..046233d 100644
--- a/src/cli/convert.ts
+++ b/src/cli/convert.ts
@@ -1,5 +1,6 @@
import { defineCommand } from 'citty'
-import { Converter, FF_ENCODER_LIBX264, type ConvertOptions } from '~/core/Converter'
+import { Converter, type ConvertOptions } from '~/core/Converter'
+import { resolveCodec } from '~/core/Codec'
import { Logger } from '~/core/Logger'
export default defineCommand({
@@ -26,7 +27,7 @@ export default defineCommand({
},
'output-codec': {
type: 'string',
- default: FF_ENCODER_LIBX264,
+ default: 'libx264',
valueHint: 'codec',
description: 'Codec to use for encoding the video tracks.'
},
@@ -60,7 +61,7 @@ export default defineCommand({
}),
alpha: args.alpha,
- outputCodec: args['output-codec'],
+ outputCodec: resolveCodec(args['output-codec']),
inputCodec: args['input-codec'] === 'auto'
? undefined
: args['input-codec'] as ConvertOptions['inputCodec'],
diff --git a/src/core/Codec.ts b/src/core/Codec.ts
new file mode 100644
index 0000000..0929efa
--- /dev/null
+++ b/src/core/Codec.ts
@@ -0,0 +1,38 @@
+import {
+ FF_ENCODER_LIBX264 as H264,
+ FF_ENCODER_LIBX265 as H265
+} from 'node-av/constants'
+
+export const CODECS = [
+ H264,
+ H265
+]
+
+export const DEFAULT_CODEC = H264
+
+export {
+ H264,
+ H265
+}
+
+export type Codec = typeof CODECS[number]
+
+export function isCodec(codec: unknown): codec is Codec {
+ return CODECS.includes(codec as Codec)
+}
+
+export function assertCodec(codec: unknown): asserts codec is Codec {
+ if (!isCodec(codec)) {
+ throw new Error(`Unsupported codec: ${codec}. Supported codecs: ${CODECS}`)
+ }
+}
+
+export function resolveCodec(codec: unknown): Codec {
+ if (!codec) {
+ return DEFAULT_CODEC
+ }
+
+ assertCodec(codec)
+
+ return codec
+}
diff --git a/src/core/Converter.ts b/src/core/Converter.ts
index 5a9a105..20ab6a8 100644
--- a/src/core/Converter.ts
+++ b/src/core/Converter.ts
@@ -5,7 +5,6 @@ import type {
FFDecoderCodec,
FFEncoderCodec,
AVCodecID,
- Codec,
Stream
} from 'node-av'
@@ -22,10 +21,6 @@ import {
import {
AV_PIX_FMT_YUV420P,
AV_CODEC_FLAG_GLOBAL_HEADER,
- FF_ENCODER_LIBX264,
- FF_ENCODER_LIBX265,
- FF_ENCODER_LIBVPX_VP8,
- FF_ENCODER_LIBVPX_VP9,
FF_THREAD_FRAME,
AVCOL_PRI_BT709,
AVCOL_PRI_BT470BG,
@@ -48,29 +43,13 @@ import {
} from 'node-av/constants'
import type { Packet } from './Packet'
-import { Muxer as FSVMuxer, type MuxOptions } from './Muxer'
+import { Muxer as FSVMuxer } from './Muxer'
+import { assertCodec, DEFAULT_CODEC, type Codec } from './Codec'
-const DEFAULT_OUTPUT_CODEC = FF_ENCODER_LIBX264
const ALPHA_SPLIT_FILTER = (
'[0:v]split[v1][v2];[v1]format=yuv420p[color];[v2]alphaextract,format=yuv420p[alpha]'
)
-export const OUTPUT_CODECS = [
- FF_ENCODER_LIBX264,
- FF_ENCODER_LIBX265,
- FF_ENCODER_LIBVPX_VP8,
- FF_ENCODER_LIBVPX_VP9,
- 'libvpx-vp9',
- 'libvpx-vp8'
-]
-
-export {
- FF_ENCODER_LIBX264,
- FF_ENCODER_LIBX265,
- FF_ENCODER_LIBVPX_VP8,
- FF_ENCODER_LIBVPX_VP9
-}
-
export interface ConvertOptions {
/**
* Whether to include an alpha track in the output.
@@ -95,7 +74,7 @@ export interface ConvertOptions {
/**
* The output video codec.
*/
- outputCodec?: typeof OUTPUT_CODECS[number]
+ outputCodec?: Codec
/**
* Optional encoder settings to override the defaults.
@@ -165,7 +144,6 @@ async function convert(
const encoded = await transcode(input, options)
const muxed = FSVMuxer.mux(encoded.color, encoded.alpha, {
config: encoded.config,
- codec: encoded.codec,
duration: encoded.duration
})
@@ -189,7 +167,6 @@ interface TranscodeOutput {
color: Packet[]
alpha?: Packet[]
config: VideoDecoderConfig
- codec: MuxOptions['codec']
duration: number
}
@@ -204,7 +181,7 @@ async function transcode(source: string | Buffer, {
const {
inputFormat,
inputCodec,
- outputCodec = DEFAULT_OUTPUT_CODEC,
+ outputCodec = DEFAULT_CODEC,
encoder: encoderOptions,
logger
} = options
@@ -212,10 +189,7 @@ async function transcode(source: string | Buffer, {
logger?.start(`Starting encoding`)
try {
- if (!OUTPUT_CODECS.includes(outputCodec)) {
- logger?.error(`Supported output codecs: ${OUTPUT_CODECS}`)
- throw new Error(`Unsupported output codec "${outputCodec}"`)
- }
+ assertCodec(outputCodec)
logger?.info(`Opening input with format ${inputFormat || '[auto]'}`)
await using input = await Demuxer.open(source, {
@@ -315,13 +289,12 @@ async function transcode(source: string | Buffer, {
logger?.success(`Encoding completed`)
- return {
- color: packets,
- alpha: undefined,
- config,
- codec: outputCodecToMuxCodec(outputCodec),
- duration: streamDurationToMicroseconds(videoStream)
- }
+ return {
+ color: packets,
+ alpha: undefined,
+ config,
+ duration: streamDurationToMicroseconds(videoStream)
+ }
} catch (error) {
logger?.error('Encoding failed')
throw error
@@ -331,17 +304,14 @@ async function transcode(source: string | Buffer, {
async function transcodeAlpha(source: string | Buffer, {
inputFormat,
inputCodec,
- outputCodec = DEFAULT_OUTPUT_CODEC,
+ outputCodec = DEFAULT_CODEC,
encoder: encoderOptions,
logger
}: Exclude = {}): Promise {
logger?.start(`Starting encoding`)
try {
- if (!OUTPUT_CODECS.includes(outputCodec)) {
- logger?.error(`Supported output codecs: ${OUTPUT_CODECS}`)
- throw new Error(`Unsupported output codec "${outputCodec}"`)
- }
+ assertCodec(outputCodec)
logger?.info(`Opening input with format ${inputFormat || '[auto]'}`)
await using input = await Demuxer.open(source, {
@@ -482,13 +452,12 @@ async function transcodeAlpha(source: string | Buffer, {
logger?.success(`Encoding completed`)
- return {
- color: colorPackets,
- alpha: alphaPackets,
- config,
- codec: outputCodecToMuxCodec(outputCodec),
- duration: streamDurationToMicroseconds(videoStream)
- }
+ return {
+ color: colorPackets,
+ alpha: alphaPackets,
+ config,
+ duration: streamDurationToMicroseconds(videoStream)
+ }
} catch (error) {
logger?.error('Encoding failed')
throw error
@@ -619,7 +588,8 @@ function mapTransferCharacteristic(value: number): string | undefined {
}
/**
- * Maps an ffmpeg color space (matrix coefficients) value to a WebCodecs matrix string.
+ * Maps an ffmpeg color space (matrix coefficients) value to a WebCodecs matrix
+ * string.
*/
function mapColorMatrix(value: number): string | undefined {
switch (value) {
@@ -683,17 +653,15 @@ function streamDurationToMicroseconds(stream: Stream): number {
return 0
}
- return Number(duration * BigInt(timeBase.num) * 1_000_000n / BigInt(timeBase.den))
+ return Number(
+ duration * BigInt(timeBase.num) * 1_000_000n / BigInt(timeBase.den)
+ )
}
function resolveEncoderOptions(
stream: Stream,
encoderOptions?: Partial
): EncoderOptions {
- const format = resolveOutputFormat(
- (encoderOptions as ConvertOptions | undefined)?.outputCodec
- )
-
return {
threadCount: 0,
threadType: FF_THREAD_FRAME,
@@ -703,77 +671,19 @@ function resolveEncoderOptions(
frameRate: stream.avgFrameRate,
...encoderOptions,
options: {
- ...(
- format === 'mp4' ? {
- crf: 20,
- preset: 'slower',
- tune: 'fastdecode',
- profile: 'baseline',
- level: '5.1',
- sc_threshold: '0',
- movflags: '+faststart',
- refs: '1',
- pixel_format: AV_PIX_FMT_YUV420P
- } :
-
- format === 'webm' ? {
- crf: 20,
- b: '0',
- deadline: 'good',
- 'cpu-used': '2',
- 'row-mt': '1',
- 'tile-columns': '2',
- 'tile-rows': '1',
- 'frame-parallel': '1',
- pixel_format: AV_PIX_FMT_YUV420P
- } :
-
- null
- ),
+ ...{
+ crf: 20,
+ preset: 'slower',
+ tune: 'fastdecode',
+ profile: 'baseline',
+ level: '5.1',
+ sc_threshold: '0',
+ movflags: '+faststart',
+ refs: '1',
+ pixel_format: AV_PIX_FMT_YUV420P
+ },
...encoderOptions?.options
}
} as EncoderOptions
}
-
-function resolveOutputFormat(
- codec: ConvertOptions['outputCodec'] = DEFAULT_OUTPUT_CODEC
-) {
- switch (codec) {
- case FF_ENCODER_LIBX264:
- case FF_ENCODER_LIBX265:
- return 'mp4'
-
- case FF_ENCODER_LIBVPX_VP8:
- case FF_ENCODER_LIBVPX_VP9:
- case 'libvpx-vp8':
- case 'libvpx-vp9':
- return 'webm'
-
- default:
- throw new Error(`Unsupported output codec "${codec}"`)
- }
-}
-
-function outputCodecToMuxCodec(
- codec: ConvertOptions['outputCodec'] = DEFAULT_OUTPUT_CODEC
-): MuxOptions['codec'] {
- switch (codec) {
- case FF_ENCODER_LIBX264:
- return 'h264'
-
- case FF_ENCODER_LIBX265:
- return 'h265'
-
- case FF_ENCODER_LIBVPX_VP8:
- case 'libvpx-vp8':
- return 'vp8'
-
- case FF_ENCODER_LIBVPX_VP9:
- case 'libvpx-vp9':
- return 'vp9'
-
- default:
- throw new Error(`Unsupported output codec "${codec}"`)
- }
-}
diff --git a/src/core/Muxer.ts b/src/core/Muxer.ts
index 220ec5c..bcf8e15 100644
--- a/src/core/Muxer.ts
+++ b/src/core/Muxer.ts
@@ -15,14 +15,6 @@ export interface MuxOptions {
*/
config: VideoDecoderConfig
- /**
- * The output codec identifier.
- *
- * Used to decide whether Annex B → AVCC conversion is required.
- * H.264 and H.265 packets require conversion; VP8 and VP9 do not.
- */
- codec: 'h264' | 'h265' | 'vp8' | 'vp9'
-
/**
* Total duration of the video in microseconds.
*/
@@ -39,9 +31,8 @@ export const Muxer = {
/**
* Muxes raw encoded video packets into fsv format.
*
- * For H.264 and H.265, each packet's Annex B bitstream is converted to AVCC
- * format (4-byte big-endian length prefixes) before being packed. VP8 and VP9
- * packets are stored as-is.
+ * Each packet's Annex B bitstream is converted to AVCC format
+ * (4-byte big-endian length prefixes) before being packed.
*
* @param packets The encoded color (or only) track packets.
* @param alphaPackets Optional encoded alpha track packets.
@@ -74,18 +65,13 @@ function mux(
function muxTrack(packets: Packet[], {
config,
- codec,
duration
}: MuxOptions): Buffer {
- const needsConversion = codec === 'h264' || codec === 'h265'
-
const chunks: Buffer[] = []
const frames: ManifestFrame[] = []
for (const packet of packets) {
- const data = needsConversion
- ? Buffer.from(annexBToAVCC(packet.data))
- : packet.data
+ const data = Buffer.from(annexBToAVCC(packet.data))
const previous = frames[frames.length - 1]
@@ -122,11 +108,7 @@ function muxTrack(packets: Packet[], {
*
* Annex B uses start codes (0x00 0x00 0x00 0x01 or 0x00 0x00 0x01) as NAL
* unit delimiters. AVCC replaces each start code with a 4-byte big-endian NAL
- * unit length prefix, which is the format expected by WebCodecs for H.264 and
- * H.265.
- *
- * VP8 and VP9 bitstreams do not use start codes and should be passed through
- * unchanged — do not call this function for those codecs.
+ * unit length prefix, which is the format expected by WebCodecs.
*
* @param data The Annex B bitstream buffer.
* @returns A new buffer containing the equivalent AVCC-formatted data.
diff --git a/src/core/Packet.ts b/src/core/Packet.ts
index 395619e..ebad97d 100644
--- a/src/core/Packet.ts
+++ b/src/core/Packet.ts
@@ -4,9 +4,6 @@
export interface Packet {
/**
* The raw encoded packet data.
- *
- * For H.264 and H.265 this is an Annex B bitstream (start-code delimited).
- * For VP8 and VP9 this is the raw codec bitstream (no conversion needed).
*/
data: Buffer
diff --git a/tests/Converter.test.ts b/tests/Converter.test.ts
index a7a8dd6..3a8f524 100644
--- a/tests/Converter.test.ts
+++ b/tests/Converter.test.ts
@@ -3,6 +3,7 @@ import { describe, it, expect } from 'vitest'
import { Converter } from '../src/core/Converter'
import { Demuxer } from '../src/core/Demuxer'
+import { H264, H265 } from '../src/core/Codec'
describe('Converter', () => {
describe('mp4/H.264 input (non-alpha)', () => {
@@ -32,39 +33,39 @@ describe('Converter', () => {
expect(fsv.frames[0].chunk.type).toBe('key')
})
- it('converts with libx265 output codec', async () => {
+ it('converts with libx264 output codec', async () => {
const fsv = demux(await Converter.convert(H264_MP4_FIXTURE, {
- outputCodec: 'libx265',
- encoder: {
- options: {
- // libx265 does not support H.264-specific profile/tune values;
- // override them so the encoder initialises successfully.
- profile: undefined,
- tune: undefined,
- preset: 'ultrafast'
- }
- }
+ outputCodec: H264
}))
expect(fsv.width).toBe(320)
expect(fsv.frames.length).toBeGreaterThan(0)
+
+ const nalUnitType = getH264NalUnitType(fsv.frames[0].chunk)
+ expect(nalUnitType).toBe(5) // IDR slice
})
- it('converts with libvpx-vp9 output codec', async () => {
+ it('converts with libx265 output codec', async () => {
const fsv = demux(await Converter.convert(H264_MP4_FIXTURE, {
- outputCodec: 'libvpx-vp9',
+ outputCodec: H265,
encoder: {
options: {
- // VP9 does not support tune:fastdecode or H.264 profile values;
+ // @todo: Adjust default encoder options depending on the chosen output codec
+ // See issue #22
+ // libx265 does not support H.264-specific profile/tune values;
// override them so the encoder initialises successfully.
profile: undefined,
- tune: undefined
+ tune: undefined,
+ preset: 'ultrafast'
}
}
}))
expect(fsv.width).toBe(320)
expect(fsv.frames.length).toBeGreaterThan(0)
+
+ const nalUnitType = getH265NalUnitType(fsv.frames[0].chunk)
+ expect([19, 20]).toContain(nalUnitType) // IDR_W_RADL (19) or IDR_N_LP (20)
})
it('throws for a non-existent input path', async () => {
@@ -77,7 +78,7 @@ describe('Converter', () => {
await expect(
// eslint-disable-next-line @typescript-eslint/no-explicit-any
Converter.convert(H264_MP4_FIXTURE, { outputCodec: 'libfoo' as any })
- ).rejects.toThrow(/[Uu]nsupported/)
+ ).rejects.toThrow(/Unsupported/)
})
})
@@ -164,3 +165,47 @@ const FRAME_TOLERANCE = 2
function demux(buf: Buffer) {
return Demuxer.demux(buf.buffer.slice(buf.byteOffset, buf.byteOffset + buf.byteLength) as ArrayBuffer)
}
+
+function getH264NalUnitType(chunk: EncodedVideoChunk): number {
+ const data = new Uint8Array(chunk.byteLength)
+ chunk.copyTo(data)
+ // AVCC format: each NAL unit is preceded by a 4-byte big-endian length.
+ // H.264 NAL unit header is 1 byte: forbidden_zero_bit(1) | nal_ref_idc(2) | nal_unit_type(5)
+ // Iterate all NAL units; prefer returning an IDR slice (type 5) over other types.
+ let firstType = -1
+ let offset = 0
+ while (offset + 4 < data.length) {
+ const nalLen = ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]) >>> 0
+ if (nalLen === 0 || offset + 4 + nalLen > data.length) break
+ const nalType = data[offset + 4] & 0x1F
+ if (firstType === -1) firstType = nalType
+ if (nalType === 5) return 5 // IDR slice — return immediately
+ offset += 4 + nalLen
+ }
+ return firstType
+}
+
+function getH265NalUnitType(chunk: EncodedVideoChunk): number {
+ const data = new Uint8Array(chunk.byteLength)
+ chunk.copyTo(data)
+ // AVCC format: each NAL unit is preceded by a 4-byte big-endian length.
+ // H.265 NAL unit header is 2 bytes (big-endian 16-bit value):
+ // bit 15 : forbidden_zero_bit
+ // bits 14–9 : nal_unit_type (6 bits) → (header >> 9) & 0x3F
+ // bits 8–3 : nuh_layer_id
+ // bits 2–0 : nuh_temporal_id_plus1
+ // IDR_W_RADL = 19, IDR_N_LP = 20
+ // Iterate all NAL units; prefer returning an IDR type over other types.
+ let firstType = -1
+ let offset = 0
+ while (offset + 5 < data.length) {
+ const nalLen = ((data[offset] << 24) | (data[offset + 1] << 16) | (data[offset + 2] << 8) | data[offset + 3]) >>> 0
+ if (nalLen === 0 || offset + 4 + nalLen > data.length) break
+ const header = (data[offset + 4] << 8) | data[offset + 5]
+ const nalType = (header >> 9) & 0x3F
+ if (firstType === -1) firstType = nalType
+ if (nalType === 19 || nalType === 20) return nalType // IDR_W_RADL or IDR_N_LP
+ offset += 4 + nalLen
+ }
+ return firstType
+}
diff --git a/tests/Demuxer.test.ts b/tests/Demuxer.test.ts
index ff019ec..edb8f00 100644
--- a/tests/Demuxer.test.ts
+++ b/tests/Demuxer.test.ts
@@ -149,7 +149,7 @@ describe('Demuxer', () => {
})
const config: VideoDecoderConfig = {
- codec: 'vp09.00.10.08',
+ codec: 'avc1.42001f',
codedWidth: 320,
codedHeight: 240,
optimizeForLatency: true
@@ -166,7 +166,6 @@ function makePackets(count: number, payloadSize = 32): Packet[] {
function makeNonAlphaFSV(packetCount = 5): Buffer {
return Muxer.mux(makePackets(packetCount), undefined, {
config,
- codec: 'vp9',
duration: packetCount * 100000
})
}
@@ -174,7 +173,6 @@ function makeNonAlphaFSV(packetCount = 5): Buffer {
function makeAlphaFSV(packetCount = 5): Buffer {
return Muxer.mux(makePackets(packetCount), makePackets(packetCount, 16), {
config,
- codec: 'vp9',
duration: packetCount * 100000
})
}
diff --git a/tests/Muxer.test.ts b/tests/Muxer.test.ts
index 2b0458c..a292819 100644
--- a/tests/Muxer.test.ts
+++ b/tests/Muxer.test.ts
@@ -7,7 +7,10 @@ import type { Packet } from '../src/core/Packet'
describe('Muxer', () => {
describe('non-alpha mux (H.264)', () => {
const packets = makePackets(5, () => makeAnnexBPacket(0x65, 20))
- const buffer = Muxer.mux(packets, undefined, { config, codec: 'h264', duration: 500000 })
+ const buffer = Muxer.mux(packets, undefined, {
+ config,
+ duration: 500000
+ })
it('returns a Buffer', () => {
expect(Buffer.isBuffer(buffer)).toBe(true)
@@ -67,7 +70,10 @@ describe('Muxer', () => {
describe('alpha mux (H.264)', () => {
const colorPackets = makePackets(5, () => makeAnnexBPacket(0x65, 20))
const alphaPackets = makePackets(5, () => makeAnnexBPacket(0x65, 15))
- const buffer = Muxer.mux(colorPackets, alphaPackets, { config, codec: 'h264', duration: 500000 })
+ const buffer = Muxer.mux(colorPackets, alphaPackets, {
+ config,
+ duration: 500000
+ })
it('first 4 bytes = color track buffer offset (non-zero)', () => {
const alphaOffset = buffer.readUInt32LE(0)
@@ -92,44 +98,6 @@ describe('Muxer', () => {
expect(alphaManifest.width).toBe(320)
})
})
-
- describe('VP8/VP9 pass-through (no Annex B conversion)', () => {
- it('VP8 packet data is stored unchanged', () => {
- const raw = makeVpxPacket(32)
- const packets: Packet[] = [{
- data: raw,
- timestamp: 0,
- isKeyFrame: true
- }]
- const buf = Muxer.mux(packets, undefined, { config, codec: 'vp8', duration: 100000 })
-
- const manifestLength = buf.readUInt32LE(4)
- const manifest = parseManifest(buf.subarray(8, 8 + manifestLength).toString('utf8'))
- const frame = manifest.frames[0]
- const dataStart = 8 + manifestLength
- const stored = buf.subarray(dataStart + frame.offset, dataStart + frame.offset + frame.byteLength)
-
- expect(Buffer.from(stored)).toEqual(raw)
- })
-
- it('VP9 packet data is stored unchanged', () => {
- const raw = makeVpxPacket(64)
- const packets: Packet[] = [{
- data: raw,
- timestamp: 0,
- isKeyFrame: true
- }]
- const buf = Muxer.mux(packets, undefined, { config, codec: 'vp9', duration: 100000 })
-
- const manifestLength = buf.readUInt32LE(4)
- const manifest = parseManifest(buf.subarray(8, 8 + manifestLength).toString('utf8'))
- const frame = manifest.frames[0]
- const dataStart = 8 + manifestLength
- const stored = buf.subarray(dataStart + frame.offset, dataStart + frame.offset + frame.byteLength)
-
- expect(Buffer.from(stored)).toEqual(raw)
- })
- })
})
const config: VideoDecoderConfig = {
@@ -161,6 +129,3 @@ function makeAnnexBPacket(nalType: number, payloadSize: number): Buffer {
return buf
}
-function makeVpxPacket(size: number): Buffer {
- return Buffer.alloc(size, 0xcd)
-}
diff --git a/tsconfig.json b/tsconfig.json
index 670df90..71aa750 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,7 @@
},
"include": [
"src",
+ "tests",
"@types"
]
}