feat(spf): add mp4 box parser and decode-time origin extraction

Minimal ISO-BMFF box walker (media/mp4/box.ts) plus decode-time origin
extraction (timestamp-origin.ts) for the non-zero-PTS timestampOffset
relocation spike. Relocating a source by tfdt.baseMediaDecodeTime (a DTS)
over mdhd.timescale keeps the earliest DTS >= 0, so a Chromium
negative-DTS append failure is impossible by construction.

Two variants share the box walker and leaf field-readers:

- Presumptive (readFirstMediaTimescale / readFirstBaseMediaDecodeTime):
  the first mdhd timescale + first tfdt baseMediaDecodeTime, no track_id,
  no iteration. For single-media-track sources.
- Track-selected (findMediaTrack by hdlr handler / readBaseMediaDecodeTime
  by tfhd track_id): the track_id joins one track's timescale to the same
  track's baseMediaDecodeTime -- required when a source muxes CEA-608/708
  captions into the same moov/moof (each track carries its own timescale
  and baseMediaDecodeTime).

Split into separate exports so caption-free platforms tree-shake the
matching machinery away (~37% smaller minified than the track-selected
pair).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Christian Pillsbury
2026-07-07 09:34:24 -07:00
co-authored by Claude Opus 4.8
parent 51caeb7d95
commit cf8aaca45b
5 changed files with 536 additions and 0 deletions
+88
View File
@@ -0,0 +1,88 @@
/**
* Minimal ISO-BMFF (MP4/CMAF) box walker.
*
* Just enough to locate boxes by nested path — the framework needs a couple of
* leaf fields (`mdhd` timescale, `tfdt` baseMediaDecodeTime) to derive a
* segment's decode-time origin, not a full demuxer. DOM-free: operates on an
* `ArrayBuffer` / `Uint8Array` via `DataView`.
*
* Box layout: `[u32 size][u32 type][payload]`. `size === 1` means a `u64
* largesize` follows the type (payload after it); `size === 0` means the box
* runs to the end of its container.
*/
/** A located box: its 4-char type and byte offsets within the buffer. */
export interface Box {
type: string;
/** Offset of the box's first byte (its size field). */
start: number;
/** Offset of the box's payload — after `size` + `type` (+ `largesize`). */
dataStart: number;
/** Offset one past the box's last byte. */
end: number;
}
export function toDataView(data: ArrayBuffer | Uint8Array): DataView {
return data instanceof Uint8Array ? new DataView(data.buffer, data.byteOffset, data.byteLength) : new DataView(data);
}
/** Read a 4-character box type / FourCC at `offset`. */
export function readFourCC(view: DataView, offset: number): string {
return String.fromCharCode(
view.getUint8(offset),
view.getUint8(offset + 1),
view.getUint8(offset + 2),
view.getUint8(offset + 3)
);
}
/** Iterate the boxes directly contained in `[start, end)`. */
export function* iterateBoxes(view: DataView, start = 0, end = view.byteLength): Generator<Box> {
let offset = start;
while (offset + 8 <= end) {
let size = view.getUint32(offset);
const type = readFourCC(view, offset + 4);
let dataStart = offset + 8;
if (size === 1) {
size = Number(view.getBigUint64(offset + 8));
dataStart = offset + 16;
} else if (size === 0) {
size = end - offset;
}
// A size smaller than its own header is malformed — stop rather than loop.
if (size < dataStart - offset) return;
yield { type, start: offset, dataStart, end: offset + size };
offset += size;
}
}
/** Iterate the direct child boxes of a given `type` within `[start, end)`. */
export function* iterateBoxesOfType(view: DataView, type: string, start = 0, end = view.byteLength): Generator<Box> {
for (const box of iterateBoxes(view, start, end)) {
if (box.type === type) yield box;
}
}
/**
* Depth-first descent to the first box matching a nested path, e.g.
* `['moov', 'trak', 'mdia', 'mdhd']`. Returns `undefined` if any level is
* absent.
*/
export function findBox(view: DataView, path: readonly string[], start = 0, end = view.byteLength): Box | undefined {
const [head, ...rest] = path;
for (const box of iterateBoxes(view, start, end)) {
if (box.type !== head) continue;
if (rest.length === 0) return box;
const found = findBox(view, rest, box.dataStart, box.end);
if (found) return found;
}
return undefined;
}
/**
* Read the version byte of a FullBox — the `version(1) + flags(3)` header at the
* start of the payload of `mdhd` / `tkhd` / `tfdt` / `hdlr` / `elst` / etc.
*/
export function readFullBoxVersion(view: DataView, dataStart: number): number {
return view.getUint8(dataStart);
}
@@ -0,0 +1,66 @@
import { describe, expect, it } from 'vitest';
import { findBox, iterateBoxes, iterateBoxesOfType, readFullBoxVersion, toDataView } from '../box';
import { box, concat, largeBox, u32 } from './synthetic-boxes';
const view = (data: Uint8Array) => toDataView(data);
describe('iterateBoxes', () => {
it('yields top-level boxes with correct type and offsets', () => {
const ftyp = box('ftyp', u32(0));
const moov = box('moov', u32(0));
const boxes = [...iterateBoxes(view(concat(ftyp, moov)))];
expect(boxes.map((b) => b.type)).toEqual(['ftyp', 'moov']);
expect(boxes[0]).toMatchObject({ start: 0, dataStart: 8, end: ftyp.length });
expect(boxes[1]).toMatchObject({ start: ftyp.length, dataStart: ftyp.length + 8 });
});
it('reads a 64-bit largesize box (size field === 1)', () => {
const big = largeBox('free', u32(0));
const [b] = [...iterateBoxes(view(big))];
expect(b).toMatchObject({ type: 'free', start: 0, dataStart: 16, end: big.length });
});
it('stops on a malformed size smaller than the header', () => {
const bad = new Uint8Array(8);
new DataView(bad.buffer).setUint32(0, 2); // size 2 < 8-byte header
expect([...iterateBoxes(view(bad))]).toEqual([]);
});
});
describe('iterateBoxesOfType', () => {
it('yields only direct children of the given type', () => {
const data = concat(box('trak', u32(1)), box('free', u32(0)), box('trak', u32(2)));
const traks = [...iterateBoxesOfType(view(data), 'trak')];
expect(traks).toHaveLength(2);
expect(traks.every((b) => b.type === 'trak')).toBe(true);
});
});
describe('readFullBoxVersion', () => {
it('reads the version byte at the payload start', () => {
for (const version of [0, 1] as const) {
const dv = view(box('mdhd', new Uint8Array([version, 0, 0, 0])));
const [b] = [...iterateBoxes(dv)];
expect(readFullBoxVersion(dv, b!.dataStart)).toBe(version);
}
});
});
describe('findBox', () => {
it('descends a nested path', () => {
const data = box('moov', box('trak', box('mdia', box('mdhd', u32(90000)))));
const found = findBox(view(data), ['moov', 'trak', 'mdia', 'mdhd']);
expect(found?.type).toBe('mdhd');
});
it('skips non-matching siblings at each level', () => {
const data = concat(box('free', u32(0)), box('moov', box('mvhd', u32(0)), box('trak', box('tkhd', u32(0)))));
expect(findBox(view(data), ['moov', 'trak'])?.type).toBe('trak');
});
it('returns undefined when any path level is absent', () => {
const data = box('moov', box('trak'));
expect(findBox(view(data), ['moov', 'trak', 'mdia'])).toBeUndefined();
expect(findBox(view(data), ['moof', 'traf'])).toBeUndefined();
});
});
@@ -0,0 +1,124 @@
/**
* Hand-built ISO-BMFF boxes for unit tests — precise control over
* version/field layout and multi-track muxing without committing binary
* segment fixtures. Real-stream validation of the parsers lives in the
* non-zero-PTS spike probes, not here.
*/
export function concat(...parts: Uint8Array[]): Uint8Array {
const total = parts.reduce((n, p) => n + p.length, 0);
const out = new Uint8Array(total);
let offset = 0;
for (const part of parts) {
out.set(part, offset);
offset += part.length;
}
return out;
}
export function u32(value: number): Uint8Array {
const out = new Uint8Array(4);
new DataView(out.buffer).setUint32(0, value);
return out;
}
export function u64(value: number | bigint): Uint8Array {
const out = new Uint8Array(8);
new DataView(out.buffer).setBigUint64(0, BigInt(value));
return out;
}
function fourcc(type: string): Uint8Array {
return new Uint8Array([type.charCodeAt(0), type.charCodeAt(1), type.charCodeAt(2), type.charCodeAt(3)]);
}
/** A standard box: `[u32 size][4-char type][payload]`. */
export function box(type: string, ...children: Uint8Array[]): Uint8Array {
const payload = concat(...children);
const out = new Uint8Array(8 + payload.length);
new DataView(out.buffer).setUint32(0, out.length);
out.set(fourcc(type), 4);
out.set(payload, 8);
return out;
}
/** A 64-bit-`largesize` box (`size` field === 1). */
export function largeBox(type: string, ...children: Uint8Array[]): Uint8Array {
const payload = concat(...children);
const out = new Uint8Array(16 + payload.length);
const view = new DataView(out.buffer);
view.setUint32(0, 1);
out.set(fourcc(type), 4);
view.setBigUint64(8, BigInt(out.length));
out.set(payload, 16);
return out;
}
const flags = new Uint8Array(3);
const versionFlags = (version: 0 | 1) => new Uint8Array([version, 0, 0, 0]);
/** `mdhd` FullBox carrying `timescale`. v0 = 32-bit dates, v1 = 64-bit dates. */
export function mdhd(timescale: number, version: 0 | 1 = 0): Uint8Array {
const dates = version === 1 ? concat(u64(0), u64(0)) : concat(u32(0), u32(0));
const duration = version === 1 ? u64(0) : u32(0);
return box('mdhd', new Uint8Array([version]), flags, dates, u32(timescale), duration);
}
/** `tkhd` FullBox carrying `track_id`. */
export function tkhd(trackId: number, version: 0 | 1 = 0): Uint8Array {
const dates = version === 1 ? concat(u64(0), u64(0)) : concat(u32(0), u32(0));
return box('tkhd', versionFlags(version), dates, u32(trackId), u32(0) /* reserved */);
}
/** `hdlr` FullBox: version+flags, pre_defined, `handler_type`, reserved, name. */
export function hdlr(handlerType: string): Uint8Array {
return box(
'hdlr',
versionFlags(0),
u32(0) /* pre_defined */,
fourcc(handlerType),
new Uint8Array(12),
new Uint8Array([0])
);
}
/** `tfdt` FullBox carrying `baseMediaDecodeTime`. v0 = 32-bit, v1 = 64-bit. */
export function tfdt(baseMediaDecodeTime: number | bigint, version: 0 | 1 = 0): Uint8Array {
const value = version === 1 ? u64(baseMediaDecodeTime) : u32(Number(baseMediaDecodeTime));
return box('tfdt', versionFlags(version), value);
}
/** `tfhd` FullBox carrying `track_id` (right after version+flags). */
export function tfhd(trackId: number): Uint8Array {
return box('tfhd', versionFlags(0), u32(trackId));
}
export interface TrakSpec {
handler: string;
trackId: number;
timescale: number;
mdhdVersion?: 0 | 1;
tkhdVersion?: 0 | 1;
}
/** A `trak`: `tkhd` + `mdia > (hdlr, mdhd)`. */
export function trak({ handler, trackId, timescale, mdhdVersion = 0, tkhdVersion = 0 }: TrakSpec): Uint8Array {
return box('trak', tkhd(trackId, tkhdVersion), box('mdia', hdlr(handler), mdhd(timescale, mdhdVersion)));
}
/** An init segment: `ftyp` + `moov > (mvhd, ...traks)`. */
export function initSegment(...traks: Uint8Array[]): Uint8Array {
return concat(box('ftyp', u32(0)), box('moov', box('mvhd', u32(0)), ...traks));
}
export interface TrafSpec {
trackId: number;
baseMediaDecodeTime: number | bigint;
version?: 0 | 1;
}
/** A media segment: `styp` + `moof > (mfhd, ...traf)`, each `traf` = `tfhd` + `tfdt`. */
export function mediaSegment(...trafs: TrafSpec[]): Uint8Array {
const trafBoxes = trafs.map((t) => box('traf', tfhd(t.trackId), tfdt(t.baseMediaDecodeTime, t.version ?? 0)));
return concat(box('styp', u32(0)), box('moof', box('mfhd', u32(0)), ...trafBoxes));
}
@@ -0,0 +1,103 @@
import { describe, expect, it } from 'vitest';
import {
findMediaTrack,
readBaseMediaDecodeTime,
readFirstBaseMediaDecodeTime,
readFirstMediaTimescale,
} from '../timestamp-origin';
import { box, initSegment, mediaSegment, trak } from './synthetic-boxes';
// Mirrors the Apple bipbop advanced example: a video init/segment that muxes a
// closed-caption (`clcp`) track alongside the `vide` track, each with its own
// timescale and baseMediaDecodeTime.
const muxedVideoInit = initSegment(
trak({ handler: 'vide', trackId: 1, timescale: 6000 }),
trak({ handler: 'clcp', trackId: 2, timescale: 30000 })
);
const muxedVideoSegment = mediaSegment(
{ trackId: 1, baseMediaDecodeTime: 60000 }, // video → 60000/6000 = 10.0s
{ trackId: 2, baseMediaDecodeTime: 300000 } // captions → 300000/30000 = 10.0s
);
describe('readFirstMediaTimescale', () => {
it('reads the first mdhd timescale (single-track init)', () => {
const audioInit = initSegment(trak({ handler: 'soun', trackId: 1, timescale: 48000 }));
expect(readFirstMediaTimescale(audioInit)).toBe(48000);
});
it('reads the first trak on a muxed init — correct only by ordering', () => {
// Video happens to be first here; a clcp-first muxing would mis-read. That
// ordering fragility is exactly why the muxed case needs findMediaTrack.
expect(readFirstMediaTimescale(muxedVideoInit)).toBe(6000);
});
it('reads a v1 mdhd timescale (wider date fields)', () => {
const init = initSegment(trak({ handler: 'vide', trackId: 1, timescale: 90000, mdhdVersion: 1 }));
expect(readFirstMediaTimescale(init)).toBe(90000);
});
it('returns undefined when no mdhd exists', () => {
expect(readFirstMediaTimescale(box('moov', box('mvhd')))).toBeUndefined();
});
});
describe('findMediaTrack', () => {
it('selects the media track by handler, ignoring a muxed caption track', () => {
expect(findMediaTrack(muxedVideoInit, 'vide')).toEqual({ trackId: 1, timescale: 6000 });
});
it('selects an audio track by handler', () => {
const audioInit = initSegment(trak({ handler: 'soun', trackId: 1, timescale: 48000 }));
expect(findMediaTrack(audioInit, 'soun')).toEqual({ trackId: 1, timescale: 48000 });
});
it('handles v1 tkhd/mdhd (wider date fields)', () => {
const init = initSegment(trak({ handler: 'vide', trackId: 7, timescale: 90000, mdhdVersion: 1, tkhdVersion: 1 }));
expect(findMediaTrack(init, 'vide')).toEqual({ trackId: 7, timescale: 90000 });
});
it('returns undefined when no track matches the handler', () => {
expect(findMediaTrack(muxedVideoInit, 'soun')).toBeUndefined();
});
});
describe('readFirstBaseMediaDecodeTime', () => {
it('reads the first traf', () => {
expect(readFirstBaseMediaDecodeTime(muxedVideoSegment)).toBe(60000);
});
it('reads a 64-bit v1 baseMediaDecodeTime beyond the 32-bit range', () => {
const large = 2 ** 33 + 12345;
expect(readFirstBaseMediaDecodeTime(mediaSegment({ trackId: 1, baseMediaDecodeTime: large, version: 1 }))).toBe(
large
);
});
it('returns undefined when the traf or tfdt is absent', () => {
expect(readFirstBaseMediaDecodeTime(box('moof', box('traf')))).toBeUndefined();
});
});
describe('readBaseMediaDecodeTime', () => {
it('selects the traf matching track_id in a muxed segment', () => {
expect(readBaseMediaDecodeTime(muxedVideoSegment, 1)).toBe(60000);
expect(readBaseMediaDecodeTime(muxedVideoSegment, 2)).toBe(300000);
});
it('returns undefined when no traf matches track_id', () => {
expect(readBaseMediaDecodeTime(muxedVideoSegment, 99)).toBeUndefined();
});
});
describe('track-tied origin (cross-track mismatch guard)', () => {
it('pairs timescale and baseMediaDecodeTime from the same track', () => {
const track = findMediaTrack(muxedVideoInit, 'vide')!;
const bmdt = readBaseMediaDecodeTime(muxedVideoSegment, track.trackId)!;
// Correct, track-tied origin.
expect(bmdt / track.timescale).toBe(10);
// The bug track_id matching prevents: video timescale paired with the
// caption track's baseMediaDecodeTime would read 50s, not 10s.
const captionBmdt = readBaseMediaDecodeTime(muxedVideoSegment, 2)!;
expect(captionBmdt / track.timescale).toBe(50);
});
});
@@ -0,0 +1,155 @@
/**
* Decode-time origin extraction from fMP4/CMAF segments.
*
* Non-zero-PTS sources encode media at a non-zero start time (an instant clip
* starting at asset second 60, an Apple bipbop asset starting at 10s, …). To
* relocate such a source onto a 0-based presentation timeline via
* `SourceBuffer.timestampOffset`, the engine needs that start time — the
* **decode-time origin** — read straight from the container:
*
* - `tfdt.baseMediaDecodeTime` (from a media segment's `moof`) — the decode
* time of the segment's first sample, in the track's media timescale. This is
* a DTS: relocating by `(baseMediaDecodeTime / timescale)` lands the earliest
* DTS at exactly 0, so a negative-DTS append failure on Chromium is impossible
* by construction.
* - `mdhd.timescale` (from the init segment's `moov`) — ticks per second for
* that track, needed to convert the raw tick count to seconds.
*
* ## Presumptive vs. track-selected reads
*
* The two variants share only the leaf field-readers (`mdhd` timescale, `tfdt`
* baseMediaDecodeTime) and the box walker. Everything else differs, and the
* split is deliberate so the presumptive pair is *proportionally* smaller under
* tree-shaking:
*
* - **Presumptive** — {@link readFirstMediaTimescale} / {@link readFirstBaseMediaDecodeTime}
* read the first `mdhd` timescale and first `tfdt` baseMediaDecodeTime. There's
* no `track_id` (nothing to match against) and no `trak`/`traf` iteration — a
* direct `findBox` to the first leaf. Correct when the init/segment holds a
* single media track (the common CMAF case). A caption-free platform imports
* only this pair and tree-shakes away all the matching machinery below.
* - **Track-selected** — {@link findMediaTrack} / {@link readBaseMediaDecodeTime}.
* `findMediaTrack` returns `{ trackId, timescale }` for the `trak` whose `hdlr`
* handler matches; `readBaseMediaDecodeTime` takes that `trackId` and reads the
* `traf` whose `tfhd.track_id` matches. The `track_id` is the join that ties one
* track's timescale to the *same* track's baseMediaDecodeTime — required when a
* source muxes CEA-608/708 captions (a `clcp` track shares the same `moov` and
* `moof`, each track with its own timescale + baseMediaDecodeTime, so a
* presumptive read there risks `300000 / 6000 = 50s` instead of
* `60000 / 6000 = 10s`). This pair adds `trak`/`traf` iteration plus the handler
* and `track_id` reads. We only ever ask for buffered media handlers
* (`vide` / `soun`); the caption track is never selected — we read the origin,
* not the captions (caption *rendering* is out of scope).
*
* Both raw values are returned un-divided to leave room for an edit-list (`elst`)
* presentation-time correction term if a source ever carries one — the validated
* streams do not.
*/
import { type Box, findBox, iterateBoxesOfType, readFourCC, readFullBoxVersion, toDataView } from './box';
/** MSE-buffered media handler types (`mdhd`/`hdlr`). Captions/subtitles excluded. */
export type MediaHandlerType = 'vide' | 'soun';
export interface MediaTrackInfo {
/** `tkhd.track_id` — used to match the corresponding `traf` in media segments. */
trackId: number;
/** `mdhd.timescale` — ticks per second for this track. */
timescale: number;
}
// --- presumptive: first leaf, no track_id, no iteration -----------------------
/**
* Presumptive: the `timescale` of the **first** `mdhd` in an init segment.
* Correct only for single-media-track inits — for muxed captions use
* {@link findMediaTrack}. `undefined` if no `mdhd` exists.
*/
export function readFirstMediaTimescale(initSegment: ArrayBuffer | Uint8Array): number | undefined {
const view = toDataView(initSegment);
const mdhd = findBox(view, ['moov', 'trak', 'mdia', 'mdhd']);
return mdhd ? readMdhdTimescale(view, mdhd) : undefined;
}
/**
* Presumptive: `baseMediaDecodeTime` from the **first** `tfdt` of a media
* segment. Correct only for single-`traf` segments — for muxed captions use
* {@link readBaseMediaDecodeTime}. `undefined` if no `tfdt` exists.
*/
export function readFirstBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array): number | undefined {
const view = toDataView(mediaSegment);
const tfdt = findBox(view, ['moof', 'traf', 'tfdt']);
return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;
}
// --- track-selected: iterate + match by handler / track_id --------------------
/**
* The `track_ID` + timescale of the `trak` whose `mdhd` handler matches
* `handlerType`, skipping a muxed `clcp` caption track. `undefined` if no
* matching track exists.
*/
export function findMediaTrack(
initSegment: ArrayBuffer | Uint8Array,
handlerType: MediaHandlerType
): MediaTrackInfo | undefined {
const view = toDataView(initSegment);
const moov = findBox(view, ['moov']);
if (!moov) return undefined;
for (const trak of iterateBoxesOfType(view, 'trak', moov.dataStart, moov.end)) {
if (readTrakHandler(view, trak) !== handlerType) continue;
const tkhd = findBox(view, ['tkhd'], trak.dataStart, trak.end);
const mdhd = findBox(view, ['mdia', 'mdhd'], trak.dataStart, trak.end);
if (!tkhd || !mdhd) return undefined;
// tkhd FullBox: version(1)+flags(3), creation/modification dates (v0: 4+4, v1:
// 8+8), then track_id.
const trackId = view.getUint32(tkhd.dataStart + 4 + (readFullBoxVersion(view, tkhd.dataStart) === 1 ? 16 : 8));
return { trackId, timescale: readMdhdTimescale(view, mdhd) };
}
return undefined;
}
/**
* `baseMediaDecodeTime` (in the track's media timescale) from the `traf` matching
* `trackId` (via `tfhd.track_id`) — required for muxed multi-`traf` segments.
* `undefined` if no matching `tfdt` exists.
*/
export function readBaseMediaDecodeTime(mediaSegment: ArrayBuffer | Uint8Array, trackId: number): number | undefined {
const view = toDataView(mediaSegment);
const moof = findBox(view, ['moof']);
if (!moof) return undefined;
for (const traf of iterateBoxesOfType(view, 'traf', moof.dataStart, moof.end)) {
if (readTrafTrackId(view, traf) !== trackId) continue;
const tfdt = findBox(view, ['tfdt'], traf.dataStart, traf.end);
return tfdt ? readTfdtBaseMediaDecodeTime(view, tfdt) : undefined;
}
return undefined;
}
// --- shared leaf field-readers ------------------------------------------------
/** `mdhd.timescale`: FullBox version(1)+flags(3) + dates (v0: 4+4, v1: 8+8) + timescale. */
function readMdhdTimescale(view: DataView, mdhd: Box): number {
return view.getUint32(mdhd.dataStart + 4 + (readFullBoxVersion(view, mdhd.dataStart) === 1 ? 16 : 8));
}
/** `tfdt.baseMediaDecodeTime`: FullBox, then the value — v0: (4), v1: (8). */
function readTfdtBaseMediaDecodeTime(view: DataView, tfdt: Box): number {
const at = tfdt.dataStart + 4;
return readFullBoxVersion(view, tfdt.dataStart) === 1 ? Number(view.getBigUint64(at)) : view.getUint32(at);
}
// --- track-selection readers (referenced only by the track-selected variants) -
/** `hdlr.handler_type` for a `trak`: FullBox version(1)+flags(3) + pre_defined(4) + handler_type(4). */
function readTrakHandler(view: DataView, trak: Box): string | undefined {
const hdlr = findBox(view, ['mdia', 'hdlr'], trak.dataStart, trak.end);
return hdlr ? readFourCC(view, hdlr.dataStart + 8) : undefined;
}
/** `tfhd.track_id` for a `traf`: FullBox version(1)+flags(3) + track_id(4). */
function readTrafTrackId(view: DataView, traf: Box): number | undefined {
const tfhd = findBox(view, ['tfhd'], traf.dataStart, traf.end);
return tfhd ? view.getUint32(tfhd.dataStart + 4) : undefined;
}