fix(input-format): recognise CMAF segments that start with sidx

Sibling case to #308: when a fragmented MP4 segment begins with a sidx
box (no ftyp/styp/moof at the file root), Mp4InputFormat._canReadInput
returned false, causing Input.getTracks() to throw
UnsupportedInputFormatError.

This shape is standard in CMAF when the DASH on-demand profile is used
or when HLS playlists are derived from one (Vimeo's vod-adaptive-ak CDN,
for example). The segment is still a valid ISOBMFF fragment — the sidx
just sits before the moof.

Adds 'sidx' alongside the existing 'moof' check so the format probe
accepts these segments, and a focused regression test that fails on
the previous behaviour.
This commit is contained in:
Theredbt
2026-05-11 04:22:46 +01:00
parent 89ee88bc7c
commit b48eb75565
2 changed files with 24 additions and 2 deletions
+22 -1
View File
@@ -1,7 +1,7 @@
/* eslint-disable @stylistic/max-len */
import { ALL_FORMATS, BufferSource, EncodedPacketSink, Input, InputAudioTrack, InputVideoTrack, UrlSource } from 'mediabunny';
import { expect, test, vi } from 'vitest';
import { HLS, HLS_FORMATS, HlsInputFormat } from '../../src/input-format.js';
import { HLS, HLS_FORMATS, HlsInputFormat, MP4 } from '../../src/input-format.js';
import { assert, hexStringToBytes, rejectAfter } from '../../src/misc.js';
import { CustomPathedSource } from '../../src/source.js';
@@ -788,6 +788,27 @@ test.concurrent('Live HLS', { timeout: 30_000 }, async () => {
expect(consoleSpy).not.toHaveBeenCalled();
});
test.concurrent('Mp4InputFormat recognises a CMAF segment that starts with sidx', async () => {
// Sibling of the moof case fixed in #308: CMAF segments using the DASH
// on-demand profile (and HLS playlists derived from it) place a sidx
// before the moof. Without ftyp/styp at the file start the existing
// format probe used to bail out, producing UnsupportedInputFormatError.
//
// Repro shape (real Vimeo segment): 32 60 73 69 64 78 ...
// (size 0x32 60-byte sidx box header, four-cc 'sidx').
const bytes = new Uint8Array(12);
new DataView(bytes.buffer).setUint32(0, 60); // box size
bytes.set([0x73, 0x69, 0x64, 0x78], 4); // 'sidx'
using input = new Input({
source: new BufferSource(bytes),
formats: ALL_FORMATS,
});
expect(await input.canRead()).toBe(true);
expect(await input.getFormat()).toBe(MP4);
});
test.concurrent('#EXT-X-I-FRAME-STREAM-INF tags are parsed properly', async () => {
const text = `#EXTM3U
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=256000,CODECS="avc1.4D401E",RESOLUTION=480x270,URI="7f2459cb12854fdbbc7ec1e7279da179/f74fb10563564130a4702743d64112a3/index_11.m3u8"