Add .gitignore to exclude all node packages and lock files

This commit is contained in:
Adolfo Reyna
2026-02-23 21:56:04 -05:00
parent faae96c9ed
commit dcc5c6c044
9747 changed files with 1555105 additions and 2 deletions
@@ -0,0 +1,6 @@
import type { SeekResolution } from '../../work-on-seek-request';
import type { AacSeekingHints } from './seeking-hints';
export declare const getSeekingByteForAac: ({ time, seekingHints, }: {
time: number;
seekingHints: AacSeekingHints;
}) => SeekResolution;
@@ -0,0 +1,34 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteForAac = void 0;
const getSeekingByteForAac = ({ time, seekingHints, }) => {
let bestAudioSample;
for (const hint of seekingHints.audioSampleMap) {
if (hint.timeInSeconds > time) {
continue;
}
// Everything is a keyframe in flac, so if this sample does not cover the time, it's not a good candidate.
// Let's go to the next one. Exception: If we already saw the last sample, we use it so we find can at least
// find the closest one.
if (hint.timeInSeconds + hint.durationInSeconds < time &&
!seekingHints.lastSampleObserved) {
continue;
}
if (!bestAudioSample) {
bestAudioSample = hint;
continue;
}
if (bestAudioSample.timeInSeconds < hint.timeInSeconds) {
bestAudioSample = hint;
}
}
if (bestAudioSample) {
return {
type: 'do-seek',
byte: bestAudioSample.offset,
timeInSeconds: bestAudioSample.timeInSeconds,
};
}
return { type: 'valid-but-must-wait' };
};
exports.getSeekingByteForAac = getSeekingByteForAac;
@@ -0,0 +1,3 @@
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseAac: (state: ParserState) => Promise<ParseResult>;
@@ -0,0 +1,103 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAac = void 0;
const aac_codecprivate_1 = require("../../aac-codecprivate");
const convert_audio_or_video_sample_1 = require("../../convert-audio-or-video-sample");
const register_track_1 = require("../../register-track");
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const parseAac = async (state) => {
const { iterator } = state;
const startOffset = iterator.counter.getOffset();
iterator.startReadingBits();
const syncWord = iterator.getBits(12);
if (syncWord !== 0xfff) {
throw new Error('Invalid syncword: ' + syncWord);
}
const id = iterator.getBits(1);
if (id !== 0) {
throw new Error('Only supporting MPEG-4 for .aac');
}
const layer = iterator.getBits(2);
if (layer !== 0) {
throw new Error('Only supporting layer 0 for .aac');
}
const protectionAbsent = iterator.getBits(1); // protection absent
const audioObjectType = iterator.getBits(2); // 1 = 'AAC-LC'
const samplingFrequencyIndex = iterator.getBits(4);
const sampleRate = (0, aac_codecprivate_1.getSampleRateFromSampleFrequencyIndex)(samplingFrequencyIndex);
iterator.getBits(1); // private bit
const channelConfiguration = iterator.getBits(3);
const codecPrivate = (0, aac_codecprivate_1.createAacCodecPrivate)({
audioObjectType,
sampleRate,
channelConfiguration,
codecPrivate: null,
});
iterator.getBits(1); // originality
iterator.getBits(1); // home
iterator.getBits(1); // copyright bit
iterator.getBits(1); // copy start
const frameLength = iterator.getBits(13); // frame length
iterator.getBits(11); // buffer fullness
iterator.getBits(2); // number of AAC frames minus 1
if (!protectionAbsent) {
iterator.getBits(16); // crc
}
iterator.stopReadingBits();
iterator.counter.decrement(iterator.counter.getOffset() - startOffset);
const data = iterator.getSlice(frameLength);
if (state.callbacks.tracks.getTracks().length === 0) {
state.mediaSection.addMediaSection({
start: startOffset,
size: state.contentLength - startOffset,
});
await (0, register_track_1.registerAudioTrack)({
container: 'aac',
track: {
codec: (0, aac_codecprivate_1.mapAudioObjectTypeToCodecString)(audioObjectType),
codecEnum: 'aac',
codecData: { type: 'aac-config', data: codecPrivate },
description: codecPrivate,
numberOfChannels: channelConfiguration,
sampleRate,
originalTimescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackId: 0,
type: 'audio',
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackMediaTimeOffsetInTrackTimescale: 0,
},
registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
tracks: state.callbacks.tracks,
logLevel: state.logLevel,
onAudioTrack: state.onAudioTrack,
});
state.callbacks.tracks.setIsDone(state.logLevel);
}
const duration = 1024 / sampleRate;
const { index } = state.aac.addSample({ offset: startOffset, size: frameLength });
const timestamp = (1024 / sampleRate) * index;
state.aac.audioSamples.addSample({
timeInSeconds: timestamp,
offset: startOffset,
durationInSeconds: duration,
});
// One ADTS frame contains 1024 samples
const audioSample = (0, convert_audio_or_video_sample_1.convertAudioOrVideoSampleToWebCodecsTimestamps)({
sample: {
duration,
type: 'key',
data,
offset: startOffset,
decodingTimestamp: timestamp,
timestamp,
},
timescale: 1,
});
await state.callbacks.onAudioSample({
audioSample,
trackId: 0,
});
return Promise.resolve(null);
};
exports.parseAac = parseAac;
@@ -0,0 +1,13 @@
import type { AacState } from '../../state/aac-state';
import type { AudioSampleOffset } from '../../state/audio-sample-map';
import type { SamplesObservedState } from '../../state/samples-observed/slow-duration-fps';
export type AacSeekingHints = {
type: 'aac-seeking-hints';
audioSampleMap: AudioSampleOffset[];
lastSampleObserved: boolean;
};
export declare const getSeekingHintsForAac: ({ aacState, samplesObserved, }: {
aacState: AacState;
samplesObserved: SamplesObservedState;
}) => AacSeekingHints;
export declare const setSeekingHintsForAac: () => void;
@@ -0,0 +1,14 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSeekingHintsForAac = exports.getSeekingHintsForAac = void 0;
const getSeekingHintsForAac = ({ aacState, samplesObserved, }) => {
return {
type: 'aac-seeking-hints',
audioSampleMap: aacState.audioSamples.getSamples(),
lastSampleObserved: samplesObserved.getLastSampleObserved(),
};
};
exports.getSeekingHintsForAac = getSeekingHintsForAac;
// TODO: Implement this and maintain index
const setSeekingHintsForAac = () => { };
exports.setSeekingHintsForAac = setSeekingHintsForAac;
@@ -0,0 +1,4 @@
export type AacStructure = {
type: 'aac';
boxes: never[];
};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,2 @@
import type { AvcProfileInfo } from './parse-avc';
export declare const getCodecStringFromSpsAndPps: (sps: AvcProfileInfo) => string;
@@ -0,0 +1,7 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getCodecStringFromSpsAndPps = void 0;
const getCodecStringFromSpsAndPps = (sps) => {
return `avc1.${sps.spsData.profile.toString(16).padStart(2, '0')}${sps.spsData.compatibility.toString(16).padStart(2, '0')}${sps.spsData.level.toString(16).padStart(2, '0')}`;
};
exports.getCodecStringFromSpsAndPps = getCodecStringFromSpsAndPps;
@@ -0,0 +1,6 @@
export type MediaParserMatrixCoefficients = 'rgb' | 'bt709' | 'bt470bg' | 'smpte170m' | 'bt2020-ncl';
export declare const getMatrixCoefficientsFromIndex: (index: number) => MediaParserMatrixCoefficients | null;
export type MediaParserTransferCharacteristics = 'bt709' | 'smpte170m' | 'iec61966-2-1' | 'linear' | 'pq' | 'hlg';
export declare const getTransferCharacteristicsFromIndex: (index: number) => MediaParserTransferCharacteristics | null;
export type MediaParserPrimaries = 'bt709' | 'bt470bg' | 'smpte170m' | 'bt2020' | 'smpte432' | null;
export declare const getPrimariesFromIndex: (index: number) => MediaParserPrimaries | null;
@@ -0,0 +1,65 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getPrimariesFromIndex = exports.getTransferCharacteristicsFromIndex = exports.getMatrixCoefficientsFromIndex = void 0;
const getMatrixCoefficientsFromIndex = (index) => {
if (index === 0) {
return 'rgb';
}
if (index === 1) {
return 'bt709';
}
if (index === 5) {
return 'bt470bg';
}
if (index === 6) {
return 'smpte170m';
}
if (index === 9) {
return 'bt2020-ncl';
}
return null;
};
exports.getMatrixCoefficientsFromIndex = getMatrixCoefficientsFromIndex;
// https://w3c.github.io/webcodecs/#videotransfercharacteristics
// But we may support more than that
const getTransferCharacteristicsFromIndex = (index) => {
if (index === 1) {
return 'bt709';
}
if (index === 6) {
return 'smpte170m';
}
if (index === 8) {
return 'linear';
}
if (index === 13) {
return 'iec61966-2-1';
}
if (index === 16) {
return 'pq';
}
if (index === 18) {
return 'hlg';
}
return null;
};
exports.getTransferCharacteristicsFromIndex = getTransferCharacteristicsFromIndex;
const getPrimariesFromIndex = (index) => {
if (index === 1) {
return 'bt709';
}
if (index === 5) {
return 'bt470bg';
}
if (index === 6) {
return 'smpte170m';
}
if (index === 9) {
return 'bt2020';
}
if (index === 12) {
return 'smpte432';
}
return null;
};
exports.getPrimariesFromIndex = getPrimariesFromIndex;
@@ -0,0 +1,2 @@
import type { SpsAndPps } from '../../state/parser-state';
export declare const createSpsPpsData: (avc1Profile: SpsAndPps) => Uint8Array<ArrayBufferLike>;
@@ -0,0 +1,48 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createSpsPpsData = void 0;
const combine_uint8_arrays_1 = require("../../combine-uint8-arrays");
const truthy_1 = require("../../truthy");
function serializeUint16(value) {
const buffer = new ArrayBuffer(2);
const view = new DataView(buffer);
view.setUint16(0, value);
return new Uint8Array(buffer);
}
const createSpsPpsData = (avc1Profile) => {
return (0, combine_uint8_arrays_1.combineUint8Arrays)([
new Uint8Array([
// https://gist.github.com/uupaa/8493378ec15f644a3d2b
1, // version
avc1Profile.sps.spsData.profile,
avc1Profile.sps.spsData.compatibility,
avc1Profile.sps.spsData.level,
0xff,
0xe1,
]),
// sequence parameter set length
serializeUint16(avc1Profile.sps.sps.length),
// sequence parameter set
avc1Profile.sps.sps,
// num of PPS
new Uint8Array([0x01]),
// picture parameter set length
serializeUint16(avc1Profile.pps.pps.length),
// PPS
avc1Profile.pps.pps,
// if AVCProfileIndication != 66 && AVCProfileIndication != 77 && AVCProfileIndication != 88
[66, 77, 88].some((b) => avc1Profile.sps.spsData.profile === b)
? null
: /**
* reserved 63 (0x3F)
chroma_format 1, '4:2:0'
reserved 31 (0x1F)
bit_depth_luma_minus8 0
reserved 31 (0x1F)
bit_depth_chroma_minus8 0
numOfSequenceParameterSetExt 0
*/
new Uint8Array([0xfd, 0xf8, 0xf8, 0]),
].filter(truthy_1.truthy));
};
exports.createSpsPpsData = createSpsPpsData;
@@ -0,0 +1,11 @@
import type { MediaParserAdvancedColor } from '../../get-tracks';
import type { SpsInfo } from './parse-avc';
export declare const getDimensionsFromSps: (sps: SpsInfo) => {
height: number;
width: number;
};
export declare const getSampleAspectRatioFromSps: (sps: SpsInfo) => {
width: number;
height: number;
};
export declare const getVideoColorFromSps: (sps: SpsInfo) => MediaParserAdvancedColor;
@@ -0,0 +1,50 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVideoColorFromSps = exports.getSampleAspectRatioFromSps = exports.getDimensionsFromSps = void 0;
const color_1 = require("./color");
const getDimensionsFromSps = (sps) => {
var _a, _b, _c, _d;
const height = sps.pic_height_in_map_units_minus1;
const width = sps.pic_width_in_mbs_minus1;
// https://stackoverflow.com/questions/12018535/get-the-width-height-of-the-video-from-h-264-nalu
return {
height: (height + 1) * 16 -
((_a = sps.frame_crop_bottom_offset) !== null && _a !== void 0 ? _a : 0) * 2 -
((_b = sps.frame_crop_top_offset) !== null && _b !== void 0 ? _b : 0) * 2,
width: (width + 1) * 16 -
((_c = sps.frame_crop_right_offset) !== null && _c !== void 0 ? _c : 0) * 2 -
((_d = sps.frame_crop_left_offset) !== null && _d !== void 0 ? _d : 0) * 2,
};
};
exports.getDimensionsFromSps = getDimensionsFromSps;
const getSampleAspectRatioFromSps = (sps) => {
var _a;
if (((_a = sps.vui_parameters) === null || _a === void 0 ? void 0 : _a.sar_height) && sps.vui_parameters.sar_width) {
return {
width: sps.vui_parameters.sar_width,
height: sps.vui_parameters.sar_height,
};
}
return {
width: 1,
height: 1,
};
};
exports.getSampleAspectRatioFromSps = getSampleAspectRatioFromSps;
const getVideoColorFromSps = (sps) => {
var _a, _b, _c, _d, _e;
const matrixCoefficients = (_a = sps.vui_parameters) === null || _a === void 0 ? void 0 : _a.matrix_coefficients;
const transferCharacteristics = (_b = sps.vui_parameters) === null || _b === void 0 ? void 0 : _b.transfer_characteristics;
const colorPrimaries = (_c = sps.vui_parameters) === null || _c === void 0 ? void 0 : _c.colour_primaries;
return {
matrix: matrixCoefficients
? (0, color_1.getMatrixCoefficientsFromIndex)(matrixCoefficients)
: null,
transfer: transferCharacteristics
? (0, color_1.getTransferCharacteristicsFromIndex)(transferCharacteristics)
: null,
primaries: colorPrimaries ? (0, color_1.getPrimariesFromIndex)(colorPrimaries) : null,
fullRange: (_e = (_d = sps.vui_parameters) === null || _d === void 0 ? void 0 : _d.video_full_range_flag) !== null && _e !== void 0 ? _e : null,
};
};
exports.getVideoColorFromSps = getVideoColorFromSps;
@@ -0,0 +1,2 @@
import type { AvcInfo } from './parse-avc';
export declare const getKeyFrameOrDeltaFromAvcInfo: (infos: AvcInfo[]) => "key" | "delta" | "bidirectional";
@@ -0,0 +1,15 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getKeyFrameOrDeltaFromAvcInfo = void 0;
const getKeyFrameOrDeltaFromAvcInfo = (infos) => {
const keyOrDelta = infos.find((i) => i.type === 'keyframe' || i.type === 'delta-frame');
if (!keyOrDelta) {
throw new Error('expected avc to contain info about key or delta');
}
return keyOrDelta.type === 'keyframe'
? 'key'
: keyOrDelta.isBidirectionalFrame
? 'bidirectional'
: 'delta';
};
exports.getKeyFrameOrDeltaFromAvcInfo = getKeyFrameOrDeltaFromAvcInfo;
@@ -0,0 +1,3 @@
import type { SpsInfo } from './parse-avc';
export declare const macroBlocksPerFrame: (sps: SpsInfo) => number;
export declare const maxMacroblockBufferSize: (sps: SpsInfo) => number;
@@ -0,0 +1,40 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.maxMacroblockBufferSize = exports.macroBlocksPerFrame = void 0;
// https://www.itu.int/rec/T-REC-H.264-202408-I
// Table A-1 Level limits
const maxMacroblocksByLevel = {
10: 396, // Level 1.0
11: 900, // Level 1.1
12: 2376, // Level 1.2
13: 2376, // Level 1.3
20: 2376, // Level 2.0
21: 4752, // Level 2.1
22: 8100, // Level 2.2
30: 8100, // Level 3.0
31: 18000, // Level 3.1
32: 20480, // Level 3.2
40: 32768, // Level 4.0
41: 32768, // Level 4.1
42: 34816, // Level 4.2
50: 110400, // Level 5.0
51: 184320, // Level 5.1
52: 184320, // Level 5.2
60: 696320, // Level 6.0
61: 696320, // Level 6.1
62: 696320, // Level 6.2
};
const macroBlocksPerFrame = (sps) => {
const { pic_width_in_mbs_minus1, pic_height_in_map_units_minus1 } = sps;
return (pic_width_in_mbs_minus1 + 1) * (pic_height_in_map_units_minus1 + 1);
};
exports.macroBlocksPerFrame = macroBlocksPerFrame;
const maxMacroblockBufferSize = (sps) => {
const { level } = sps;
const maxMacroblocks = maxMacroblocksByLevel[level];
if (maxMacroblocks === undefined) {
throw new Error(`Unsupported level: ${level.toString(16)}`);
}
return maxMacroblocks;
};
exports.maxMacroblockBufferSize = maxMacroblockBufferSize;
@@ -0,0 +1,50 @@
import type { AvcState } from '../../state/avc/avc-state';
import type { MediaParserAvcDeltaFrameInfo, MediaParserAvcKeyframeInfo } from '../../webcodec-sample-types';
type VuiParameters = {
sar_width: number | null;
sar_height: number | null;
overscan_appropriate_flag: number | null;
video_format: number | null;
video_full_range_flag: boolean | null;
colour_primaries: number | null;
transfer_characteristics: number | null;
matrix_coefficients: number | null;
chroma_sample_loc_type_top_field: number | null;
chroma_sample_loc_type_bottom_field: number | null;
};
export type SpsInfo = {
profile: number;
compatibility: number;
level: number;
seq_parameter_set_id: number;
separate_colour_plane_flag: number | null;
bit_depth_luma_minus8: number | null;
bit_depth_chroma_minus8: number | null;
qpprime_y_zero_transform_bypass_flag: number | null;
log2_max_frame_num_minus4: number;
log2_max_pic_order_cnt_lsb_minus4: number | null;
max_num_ref_frames: number | null;
gaps_in_frame_num_value_allowed_flag: number | null;
pic_width_in_mbs_minus1: number;
pic_height_in_map_units_minus1: number;
mb_adaptive_frame_field_flag: number | null;
direct_8x8_inference_flag: number | null;
frame_crop_left_offset: number | null;
frame_crop_right_offset: number | null;
frame_crop_top_offset: number | null;
frame_crop_bottom_offset: number | null;
vui_parameters: VuiParameters | null;
pic_order_cnt_type: number;
};
export type AvcProfileInfo = {
spsData: SpsInfo;
sps: Uint8Array;
type: 'avc-profile';
};
export type AvcPPs = {
type: 'avc-pps';
pps: Uint8Array;
};
export type AvcInfo = AvcProfileInfo | AvcPPs | MediaParserAvcKeyframeInfo | MediaParserAvcDeltaFrameInfo;
export declare const parseAvc: (buffer: Uint8Array, avcState: AvcState) => AvcInfo[];
export {};
@@ -0,0 +1,332 @@
"use strict";
// https://www.itu.int/rec/T-REC-H.264-202408-I/en
// Page 455
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseAvc = void 0;
const buffer_iterator_1 = require("../../iterator/buffer-iterator");
const Extended_SAR = 255;
const getPoc = (iterator, sps, avcState, isReferencePicture) => {
const { pic_order_cnt_type, log2_max_pic_order_cnt_lsb_minus4 } = sps;
if (pic_order_cnt_type !== 0) {
return null;
}
const prevPicOrderCntLsb = avcState.getPrevPicOrderCntLsb();
const prevPicOrderCntMsb = avcState.getPrevPicOrderCntMsb();
if (log2_max_pic_order_cnt_lsb_minus4 === null) {
throw new Error('log2_max_pic_order_cnt_lsb_minus4 is null');
}
const max_pic_order_cnt_lsb = 2 ** (log2_max_pic_order_cnt_lsb_minus4 + 4);
const pic_order_cnt_lsb = iterator.getBits(log2_max_pic_order_cnt_lsb_minus4 + 4);
let picOrderCntMsb;
if (pic_order_cnt_lsb < prevPicOrderCntLsb &&
prevPicOrderCntLsb - pic_order_cnt_lsb >= max_pic_order_cnt_lsb / 2) {
picOrderCntMsb = prevPicOrderCntMsb + max_pic_order_cnt_lsb;
}
else if (pic_order_cnt_lsb > prevPicOrderCntLsb &&
pic_order_cnt_lsb - prevPicOrderCntLsb > max_pic_order_cnt_lsb / 2) {
picOrderCntMsb = prevPicOrderCntMsb - max_pic_order_cnt_lsb;
}
else {
picOrderCntMsb = prevPicOrderCntMsb;
}
const poc = picOrderCntMsb + pic_order_cnt_lsb;
if (isReferencePicture) {
avcState.setPrevPicOrderCntLsb(pic_order_cnt_lsb);
avcState.setPrevPicOrderCntMsb(picOrderCntMsb);
}
return poc;
};
const readVuiParameters = (iterator) => {
let sar_width = null;
let sar_height = null;
let overscan_appropriate_flag = null;
let video_format = null;
let video_full_range_flag = null;
let colour_primaries = null;
let transfer_characteristics = null;
let matrix_coefficients = null;
let chroma_sample_loc_type_top_field = null;
let chroma_sample_loc_type_bottom_field = null;
const aspect_ratio_info_present_flag = iterator.getBits(1);
if (aspect_ratio_info_present_flag) {
const aspect_ratio_idc = iterator.getBits(8);
if (aspect_ratio_idc === Extended_SAR) {
sar_width = iterator.getBits(16);
sar_height = iterator.getBits(16);
}
}
const overscan_info_present_flag = iterator.getBits(1);
if (overscan_info_present_flag) {
overscan_appropriate_flag = iterator.getBits(1);
}
const video_signal_type_present_flag = iterator.getBits(1);
if (video_signal_type_present_flag) {
video_format = iterator.getBits(3);
video_full_range_flag = Boolean(iterator.getBits(1));
const colour_description_present_flag = iterator.getBits(1);
if (colour_description_present_flag) {
colour_primaries = iterator.getBits(8);
transfer_characteristics = iterator.getBits(8);
matrix_coefficients = iterator.getBits(8);
}
}
const chroma_loc_info_present_flag = iterator.getBits(1);
if (chroma_loc_info_present_flag) {
chroma_sample_loc_type_top_field = iterator.readExpGolomb();
chroma_sample_loc_type_bottom_field = iterator.readExpGolomb();
}
return {
sar_width,
sar_height,
overscan_appropriate_flag,
chroma_sample_loc_type_bottom_field,
chroma_sample_loc_type_top_field,
colour_primaries,
matrix_coefficients,
transfer_characteristics,
video_format,
video_full_range_flag,
};
};
const readSps = (iterator) => {
const profile = iterator.getUint8();
const compatibility = iterator.getUint8();
const level = iterator.getUint8();
iterator.startReadingBits();
const seq_parameter_set_id = iterator.readExpGolomb();
let separate_colour_plane_flag = null;
let bit_depth_luma_minus8 = null;
let bit_depth_chroma_minus8 = null;
let qpprime_y_zero_transform_bypass_flag = null;
let log2_max_frame_num_minus4 = null;
let log2_max_pic_order_cnt_lsb_minus4 = null;
let max_num_ref_frames = null;
let gaps_in_frame_num_value_allowed_flag = null;
let mb_adaptive_frame_field_flag = null;
let direct_8x8_inference_flag = null;
let frame_crop_left_offset = null;
let frame_crop_right_offset = null;
let frame_crop_top_offset = null;
let frame_crop_bottom_offset = null;
let vui_parameters = null;
// Page 71
if (profile === 100 ||
profile === 110 ||
profile === 122 ||
profile === 244 ||
profile === 44 ||
profile === 83 ||
profile === 86 ||
profile === 118 ||
profile === 128 ||
profile === 138 ||
profile === 139 ||
profile === 134 ||
profile === 135) {
const chromaFormat = iterator.readExpGolomb();
if (chromaFormat === 3) {
separate_colour_plane_flag = iterator.getBits(1);
}
bit_depth_luma_minus8 = iterator.readExpGolomb();
bit_depth_chroma_minus8 = iterator.readExpGolomb();
qpprime_y_zero_transform_bypass_flag = iterator.getBits(1);
const seq_scaling_matrix_present_flag = iterator.getBits(1);
const seq_scaling_list_present_flag = [];
if (seq_scaling_matrix_present_flag) {
for (let i = 0; i < (chromaFormat !== 3 ? 8 : 12); i++) {
seq_scaling_list_present_flag[i] = iterator.getBits(1);
if (seq_scaling_list_present_flag[i]) {
if (i < 6) {
// scaling_list not implemented
throw new Error('Not implemented');
}
else {
// scaling_list not implemented
throw new Error('Not implemented');
}
}
}
}
}
log2_max_frame_num_minus4 = iterator.readExpGolomb();
const pic_order_cnt_type = iterator.readExpGolomb();
if (pic_order_cnt_type === 0) {
log2_max_pic_order_cnt_lsb_minus4 = iterator.readExpGolomb();
}
else if (pic_order_cnt_type === 1) {
throw new Error('pic_order_cnt_type = 1 not implemented');
}
max_num_ref_frames = iterator.readExpGolomb();
gaps_in_frame_num_value_allowed_flag = iterator.getBits(1);
const pic_width_in_mbs_minus1 = iterator.readExpGolomb();
const pic_height_in_map_units_minus1 = iterator.readExpGolomb();
const frame_mbs_only_flag = iterator.getBits(1);
if (!frame_mbs_only_flag) {
mb_adaptive_frame_field_flag = iterator.getBits(1);
}
direct_8x8_inference_flag = iterator.getBits(1);
const frame_cropping_flag = iterator.getBits(1);
if (frame_cropping_flag) {
frame_crop_left_offset = iterator.readExpGolomb();
frame_crop_right_offset = iterator.readExpGolomb();
frame_crop_top_offset = iterator.readExpGolomb();
frame_crop_bottom_offset = iterator.readExpGolomb();
}
const vui_parameters_present_flag = iterator.getBits(1);
if (vui_parameters_present_flag) {
vui_parameters = readVuiParameters(iterator);
}
iterator.stopReadingBits();
return {
profile,
compatibility,
level,
bit_depth_chroma_minus8,
bit_depth_luma_minus8,
gaps_in_frame_num_value_allowed_flag,
log2_max_frame_num_minus4,
log2_max_pic_order_cnt_lsb_minus4,
max_num_ref_frames,
pic_height_in_map_units_minus1,
pic_width_in_mbs_minus1,
qpprime_y_zero_transform_bypass_flag,
separate_colour_plane_flag,
seq_parameter_set_id,
direct_8x8_inference_flag,
frame_crop_bottom_offset,
frame_crop_left_offset,
frame_crop_right_offset,
frame_crop_top_offset,
mb_adaptive_frame_field_flag,
vui_parameters,
pic_order_cnt_type,
};
};
const findEnd = (buffer) => {
let zeroesInARow = 0;
for (let i = 0; i < buffer.length; i++) {
const val = buffer[i];
if (val === 0) {
zeroesInARow++;
continue;
}
if (zeroesInARow >= 2 && val === 1) {
return i - zeroesInARow;
}
zeroesInARow = 0;
}
return null;
};
const inspect = (buffer, avcState) => {
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)({
initialData: buffer,
maxBytes: buffer.byteLength,
logLevel: 'error',
});
iterator.startReadingBits();
iterator.getBits(1); // forbidden_zero_bit
const nal_ref_idc = iterator.getBits(2); // nal_ref_idc
const isReferencePicture = nal_ref_idc !== 0;
const type = iterator.getBits(5); // nal_unit_type
if (type === 7) {
iterator.stopReadingBits();
const end = findEnd(buffer);
const data = readSps(iterator);
const sps = buffer.slice(0, end === null ? Infinity : end);
avcState.setSps(data);
if (isReferencePicture) {
avcState.setPrevPicOrderCntLsb(0);
avcState.setPrevPicOrderCntMsb(0);
}
return {
spsData: data,
sps,
type: 'avc-profile',
};
}
if (type === 5) {
avcState.setPrevPicOrderCntLsb(0);
avcState.setPrevPicOrderCntMsb(0);
iterator.readExpGolomb(); // ignore first_mb_in_slice
iterator.readExpGolomb(); // slice_type
iterator.readExpGolomb(); // pic_parameter_set_id
const sps = avcState.getSps();
if (!sps) {
throw new Error('SPS not found');
}
const numberOfBitsForFrameNum = sps.log2_max_frame_num_minus4 + 4;
iterator.getBits(numberOfBitsForFrameNum); // frame_num
iterator.readExpGolomb(); // idr_pic_id
const { pic_order_cnt_type } = sps;
let poc = null;
if (pic_order_cnt_type === 0) {
poc = getPoc(iterator, sps, avcState, isReferencePicture);
}
iterator.stopReadingBits();
return {
type: 'keyframe',
poc,
};
}
if (type === 8) {
iterator.stopReadingBits();
const end = findEnd(buffer);
const pps = buffer.slice(0, end === null ? Infinity : end);
return {
type: 'avc-pps',
pps,
};
}
if (type === 1) {
iterator.readExpGolomb(); // ignore first_mb_in_slice
const slice_type = iterator.readExpGolomb();
const isBidirectionalFrame = slice_type === 6;
iterator.readExpGolomb(); // pic_parameter_set_id
const sps = avcState.getSps();
if (!sps) {
throw new Error('SPS not found');
}
const numberOfBitsForFrameNum = sps.log2_max_frame_num_minus4 + 4;
iterator.getBits(numberOfBitsForFrameNum); // frame_num
const { pic_order_cnt_type } = sps;
let poc = null;
if (pic_order_cnt_type === 0) {
poc = getPoc(iterator, sps, avcState, isReferencePicture);
}
iterator.stopReadingBits();
return {
type: 'delta-frame',
isBidirectionalFrame,
poc,
};
}
iterator.destroy();
return null;
};
// https://stackoverflow.com/questions/24884827/possible-locations-for-sequence-picture-parameter-sets-for-h-264-stream
const parseAvc = (buffer, avcState) => {
let zeroesInARow = 0;
const infos = [];
for (let i = 0; i < buffer.length; i++) {
const val = buffer[i];
if (val === 0) {
zeroesInARow++;
continue;
}
if (zeroesInARow >= 2 && val === 1) {
zeroesInARow = 0;
const info = inspect(buffer.slice(i + 1, i + 100), avcState);
if (info) {
infos.push(info);
if (info.type === 'keyframe' || info.type === 'delta-frame') {
break;
}
}
}
if (val !== 1) {
zeroesInARow = 0;
}
}
return infos;
};
exports.parseAvc = parseAvc;
@@ -0,0 +1,3 @@
import type { SpsAndPps } from '../../state/parser-state';
import type { AvcInfo } from './parse-avc';
export declare const getSpsAndPps: (infos: AvcInfo[]) => SpsAndPps;
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSpsAndPps = void 0;
const getSpsAndPps = (infos) => {
const avcProfile = infos.find((i) => i.type === 'avc-profile');
const ppsProfile = infos.find((i) => i.type === 'avc-pps');
if (!avcProfile || !ppsProfile) {
throw new Error('Expected avcProfile and ppsProfile');
}
return { pps: ppsProfile, sps: avcProfile };
};
exports.getSpsAndPps = getSpsAndPps;
@@ -0,0 +1,2 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
export declare const getBlockSize: (iterator: BufferIterator) => number | "uncommon-u16" | "uncommon-u8" | null;
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getBlockSize = void 0;
const getBlockSize = (iterator) => {
const bits = iterator.getBits(4);
if (bits === 0b0000) {
// Probably we are in the wrong spot overall, and just landed on a spot that incidentially hit the syncword.
// Don't throw an error, in the parent function just keep reading.
// Internal message with repro: https://discord.com/channels/@me/1314232261008162876/1410312296709881988
return null;
}
if (bits === 0b0001) {
return 192;
}
if (bits >= 0b0010 && bits <= 0b0101) {
return 144 * 2 ** bits;
}
if (bits === 0b0110) {
return 'uncommon-u8';
}
if (bits === 0b0111) {
return 'uncommon-u16';
}
if (bits >= 0b1000 && bits <= 0b1111) {
return 2 ** bits;
}
throw new Error('Invalid block size');
};
exports.getBlockSize = getBlockSize;
@@ -0,0 +1,2 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
export declare const getChannelCount: (iterator: BufferIterator) => 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8;
@@ -0,0 +1,42 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getChannelCount = void 0;
// https://www.rfc-editor.org/rfc/rfc9639.html#name-channels-bits
const getChannelCount = (iterator) => {
const bits = iterator.getBits(4);
if (bits === 0b0000) {
return 1;
}
if (bits === 0b0001) {
return 2;
}
if (bits === 0b0010) {
return 3;
}
if (bits === 0b0011) {
return 4;
}
if (bits === 0b0100) {
return 5;
}
if (bits === 0b0101) {
return 6;
}
if (bits === 0b0110) {
return 7;
}
if (bits === 0b0111) {
return 8;
}
if (bits === 0b1000 || bits === 0b1001 || bits === 0b1010) {
return 2;
}
// 0b1011..0b1111 are reserved per RFC 9639 §9.1.3 (Channels Bits).
// Some encoders/files in the wild may nonetheless use these values.
// Be lenient and treat them as stereo (2 channels) to keep parsing robust.
if (bits >= 0b1011 && bits <= 0b1111) {
return 2;
}
throw new Error(`Invalid channel count: ${bits.toString(2)}`);
};
exports.getChannelCount = getChannelCount;
@@ -0,0 +1,2 @@
import type { ParserState } from '../../state/parser-state';
export declare const getDurationFromFlac: (parserState: ParserState) => number;
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getDurationFromFlac = void 0;
const getDurationFromFlac = (parserState) => {
const structure = parserState.structure.getFlacStructure();
const streaminfo = structure.boxes.find((b) => b.type === 'flac-streaminfo');
if (!streaminfo) {
throw new Error('Streaminfo not found');
}
return streaminfo.totalSamples / streaminfo.sampleRate;
};
exports.getDurationFromFlac = getDurationFromFlac;
@@ -0,0 +1,2 @@
import type { FlacStructure } from './types';
export declare const getMetadataFromFlac: (structure: FlacStructure) => import("../..").MediaParserMetadataEntry[] | null;
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMetadataFromFlac = void 0;
const getMetadataFromFlac = (structure) => {
const box = structure.boxes.find((b) => b.type === 'flac-vorbis-comment');
if (!box) {
return null;
}
return box.fields;
};
exports.getMetadataFromFlac = getMetadataFromFlac;
@@ -0,0 +1,3 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParserState } from '../../state/parser-state';
export declare const getSampleRate: (iterator: BufferIterator, state: ParserState) => number | "uncommon-u8" | "uncommon-u16" | "uncommon-u16-10";
@@ -0,0 +1,60 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSampleRate = void 0;
// https://www.rfc-editor.org/rfc/rfc9639.html#name-sample-rate-bits
const getSampleRate = (iterator, state) => {
var _a, _b;
const mode = iterator.getBits(4);
if (mode === 0b0000 || mode === 0b1111) {
const structure = state.structure.getFlacStructure();
const sampleRate = (_b = (_a = structure.boxes.find((box) => box.type === 'flac-streaminfo')) === null || _a === void 0 ? void 0 : _a.sampleRate) !== null && _b !== void 0 ? _b : null;
if (sampleRate === null) {
throw new Error('Sample rate not found');
}
return sampleRate;
}
if (mode === 0b0001) {
return 88200;
}
if (mode === 0b0010) {
return 176400;
}
if (mode === 0b0011) {
return 192000;
}
if (mode === 0b0100) {
return 8000;
}
if (mode === 0b0101) {
return 16000;
}
if (mode === 0b0110) {
return 22050;
}
if (mode === 0b0111) {
return 24000;
}
if (mode === 0b1000) {
return 32000;
}
if (mode === 0b1001) {
return 44100;
}
if (mode === 0b1010) {
return 48000;
}
if (mode === 0b1011) {
return 96000;
}
if (mode === 0b1100) {
return 'uncommon-u8';
}
if (mode === 0b1101) {
return 'uncommon-u16';
}
if (mode === 0b1110) {
return 'uncommon-u16-10';
}
throw new Error(`Invalid sample rate mode: ${mode.toString(2)}`);
};
exports.getSampleRate = getSampleRate;
@@ -0,0 +1,6 @@
import type { AudioSampleOffset } from '../../state/audio-sample-map';
import type { FlacSeekingHints } from './seeking-hints';
export declare const getSeekingByteForFlac: ({ time, seekingHints, }: {
time: number;
seekingHints: FlacSeekingHints;
}) => AudioSampleOffset | null;
@@ -0,0 +1,30 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteForFlac = void 0;
const getSeekingByteForFlac = ({ time, seekingHints, }) => {
let bestAudioSample;
for (const hint of seekingHints.audioSampleMap) {
if (hint.timeInSeconds > time) {
continue;
}
// Everything is a keyframe in flac, so if this sample does not cover the time, it's not a good candidate.
// Let's go to the next one. Exception: If we already saw the last sample, we use it so we find can at least
// find the closest one.
if (hint.timeInSeconds + hint.durationInSeconds < time &&
!seekingHints.lastSampleObserved) {
continue;
}
if (!bestAudioSample) {
bestAudioSample = hint;
continue;
}
if (bestAudioSample.timeInSeconds < hint.timeInSeconds) {
bestAudioSample = hint;
}
}
if (bestAudioSample) {
return bestAudioSample;
}
return null;
};
exports.getSeekingByteForFlac = getSeekingByteForFlac;
@@ -0,0 +1,15 @@
import { type BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseFrameHeader: ({ iterator, state, }: {
iterator: BufferIterator;
state: ParserState;
}) => {
num: number;
blockSize: number;
sampleRate: number;
} | null;
export declare const parseFlacFrame: ({ state, iterator, }: {
state: ParserState;
iterator: BufferIterator;
}) => Promise<ParseResult>;
@@ -0,0 +1,185 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFlacFrame = exports.parseFrameHeader = void 0;
const convert_audio_or_video_sample_1 = require("../../convert-audio-or-video-sample");
const buffer_iterator_1 = require("../../iterator/buffer-iterator");
const get_block_size_1 = require("./get-block-size");
const get_channel_count_1 = require("./get-channel-count");
const get_sample_rate_1 = require("./get-sample-rate");
// https://www.rfc-editor.org/rfc/rfc9639.html#section-9.1.1
function calculateCRC8(data) {
const polynomial = 0x07; // x^8 + x^2 + x^1 + x^0
let crc = 0x00; // Initialize CRC to 0
for (const byte of data) {
crc ^= byte; // XOR byte into least significant byte of crc
for (let i = 0; i < 8; i++) {
// For each bit in the byte
if ((crc & 0x80) !== 0) {
// If the leftmost bit (MSB) is set
crc = (crc << 1) ^ polynomial; // Shift left and XOR with polynomial
}
else {
crc <<= 1; // Just shift left
}
crc &= 0xff; // Ensure CRC remains 8-bit
}
}
return crc;
}
const parseFrameHeader = ({ iterator, state, }) => {
if (iterator.bytesRemaining() < 10) {
return null;
}
const startOffset = iterator.counter.getOffset();
iterator.discard(2); // sync code
iterator.startReadingBits();
const blockSizeBits = (0, get_block_size_1.getBlockSize)(iterator);
if (blockSizeBits === null) {
return null;
}
const sampleRateBits = (0, get_sample_rate_1.getSampleRate)(iterator, state);
(0, get_channel_count_1.getChannelCount)(iterator); // channel count
iterator.getBits(3); // bit depth
iterator.getBits(1);
const num = iterator.getFlacCodecNumber();
const blockSize = blockSizeBits === 'uncommon-u16'
? iterator.getBits(16) + 1
: blockSizeBits === 'uncommon-u8'
? iterator.getBits(8) + 1
: blockSizeBits;
const sampleRate = sampleRateBits === 'uncommon-u16'
? iterator.getBits(16)
: sampleRateBits === 'uncommon-u16-10'
? iterator.getBits(16) * 10
: sampleRateBits === 'uncommon-u8'
? iterator.getBits(8)
: sampleRateBits;
iterator.stopReadingBits();
const size = iterator.counter.getOffset() - startOffset;
const crc = iterator.getUint8();
iterator.counter.decrement(size + 1);
const crcCalculated = calculateCRC8(iterator.getSlice(size));
iterator.counter.decrement(size);
if (crcCalculated !== crc) {
return null;
}
return { num, blockSize, sampleRate };
};
exports.parseFrameHeader = parseFrameHeader;
const emitSample = async ({ state, data, offset, }) => {
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)({
initialData: data,
maxBytes: data.length,
logLevel: 'error',
});
const parsed = (0, exports.parseFrameHeader)({ iterator, state });
if (!parsed) {
throw new Error('Invalid CRC');
}
const { blockSize, num, sampleRate } = parsed;
const duration = blockSize / sampleRate;
const structure = state.structure.getFlacStructure();
const streamInfo = structure.boxes.find((box) => box.type === 'flac-streaminfo');
if (!streamInfo) {
throw new Error('Stream info not found');
}
if (streamInfo.minimumBlockSize !== streamInfo.maximumBlockSize) {
throw new Error('Cannot determine timestamp');
}
const timestamp = (num * streamInfo.maximumBlockSize) / streamInfo.sampleRate;
state.flac.audioSamples.addSample({
timeInSeconds: timestamp,
offset,
durationInSeconds: duration,
});
const audioSample = (0, convert_audio_or_video_sample_1.convertAudioOrVideoSampleToWebCodecsTimestamps)({
sample: {
data,
duration,
decodingTimestamp: timestamp,
timestamp,
type: 'key',
offset,
},
timescale: 1,
});
await state.callbacks.onAudioSample({
audioSample,
trackId: 0,
});
iterator.destroy();
};
const parseFlacFrame = async ({ state, iterator, }) => {
var _a, _b;
const blockingBit = state.flac.getBlockingBitStrategy();
const offset = iterator.counter.getOffset();
const { returnToCheckpoint } = iterator.startCheckpoint();
iterator.startReadingBits();
if (blockingBit === undefined) {
const bits = iterator.getBits(15);
if (bits !== 0b111111111111100) {
throw new Error('Invalid sync code');
}
state.flac.setBlockingBitStrategy(iterator.getBits(1));
}
else if (blockingBit === 1) {
const bits = iterator.getBits(16);
if (bits !== 0b1111111111111001) {
throw new Error('Blocking bit changed, it should not');
}
}
else if (blockingBit === 0) {
const bits = iterator.getBits(16);
if (bits !== 0b1111111111111000) {
throw new Error('Blocking bit changed, it should not');
}
}
const setBlockingBit = state.flac.getBlockingBitStrategy();
if (setBlockingBit === undefined) {
throw new Error('Blocking bit should be set');
}
iterator.stopReadingBits();
const structure = state.structure.getFlacStructure();
const minimumFrameSize = (_b = (_a = structure.boxes.find((b) => b.type === 'flac-streaminfo')) === null || _a === void 0 ? void 0 : _a.minimumFrameSize) !== null && _b !== void 0 ? _b : null;
if (minimumFrameSize === null) {
throw new Error('Expected flac-streaminfo');
}
if (minimumFrameSize !== 0) {
iterator.getSlice(minimumFrameSize - 2);
}
while (true) {
if (iterator.counter.getOffset() === state.contentLength) {
const size = iterator.counter.getOffset() - offset;
returnToCheckpoint();
const slice = iterator.getSlice(size);
await emitSample({ state, data: slice, offset });
break;
}
if (iterator.bytesRemaining() === 0) {
returnToCheckpoint();
break;
}
const nextByte = iterator.getUint8();
if (nextByte === 0xff) {
const nextBits = iterator.getUint8();
const expected = setBlockingBit === 1 ? 249 : 248;
if (nextBits !== expected) {
iterator.counter.decrement(1);
continue;
}
iterator.counter.decrement(2);
const nextIsLegit = (0, exports.parseFrameHeader)({ iterator, state });
if (!nextIsLegit) {
iterator.discard(1);
continue;
}
const size = iterator.counter.getOffset() - offset;
returnToCheckpoint();
const data = iterator.getSlice(size);
await emitSample({ state, data, offset });
break;
}
}
return null;
};
exports.parseFlacFrame = parseFlacFrame;
@@ -0,0 +1,7 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseFlac: ({ iterator, state, }: {
iterator: BufferIterator;
state: ParserState;
}) => Promise<ParseResult>;
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFlac = void 0;
const skip_1 = require("../../skip");
const may_skip_video_data_1 = require("../../state/may-skip-video-data");
const parse_flac_frame_1 = require("./parse-flac-frame");
const parse_header_1 = require("./parse-header");
const parse_meta_1 = require("./parse-meta");
const parseFlac = ({ iterator, state, }) => {
const mediaSectionState = state.mediaSection.isCurrentByteInMediaSection(iterator);
if (mediaSectionState === 'in-section') {
if ((0, may_skip_video_data_1.maySkipVideoData)({ state })) {
return Promise.resolve((0, skip_1.makeSkip)(state.contentLength));
}
return (0, parse_flac_frame_1.parseFlacFrame)({ state, iterator });
}
const bytes = iterator.getByteString(4, true);
if (bytes === 'fLaC') {
return (0, parse_header_1.parseFlacHeader)({ state, iterator });
}
iterator.counter.decrement(4);
// https://www.rfc-editor.org/rfc/rfc9639.html#name-streaminfo
// section 8.1
return (0, parse_meta_1.parseMetaBlock)({
iterator,
state,
});
};
exports.parseFlac = parseFlac;
@@ -0,0 +1,7 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseFlacHeader: ({ state, }: {
state: ParserState;
iterator: BufferIterator;
}) => Promise<ParseResult>;
@@ -0,0 +1,10 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFlacHeader = void 0;
const parseFlacHeader = ({ state, }) => {
state.structure.getFlacStructure().boxes.push({
type: 'flac-header',
});
return Promise.resolve(null);
};
exports.parseFlacHeader = parseFlacHeader;
@@ -0,0 +1,6 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParserState } from '../../state/parser-state';
export declare const parseMetaBlock: ({ iterator, state, }: {
iterator: BufferIterator;
state: ParserState;
}) => Promise<import("../../parse-result").ParseResult>;
@@ -0,0 +1,31 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMetaBlock = void 0;
const parse_metadata_1 = require("./parse-metadata");
const parse_streaminfo_1 = require("./parse-streaminfo");
const parse_unknown_block_1 = require("./parse-unknown-block");
const flacTypes = {
streaminfo: 0,
vorbisComment: 4,
};
const parseMetaBlock = ({ iterator, state, }) => {
iterator.startReadingBits();
const isLastMetadata = iterator.getBits(1);
const metaBlockType = iterator.getBits(7);
iterator.stopReadingBits();
const size = iterator.getUint24();
if (isLastMetadata) {
state.mediaSection.addMediaSection({
start: iterator.counter.getOffset() + size,
size: state.contentLength - iterator.counter.getOffset() - size,
});
}
if (metaBlockType === flacTypes.streaminfo) {
return (0, parse_streaminfo_1.parseStreamInfo)({ iterator, state });
}
if (metaBlockType === flacTypes.vorbisComment) {
return (0, parse_metadata_1.parseVorbisComment)({ iterator, state, size });
}
return (0, parse_unknown_block_1.parseFlacUnkownBlock)({ iterator, state, size });
};
exports.parseMetaBlock = parseMetaBlock;
@@ -0,0 +1,8 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseVorbisComment: ({ state, iterator, size, }: {
state: ParserState;
iterator: BufferIterator;
size: number;
}) => Promise<ParseResult>;
@@ -0,0 +1,24 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseVorbisComment = void 0;
const parseVorbisComment = ({ state, iterator, size, }) => {
const { expectNoMoreBytes } = iterator.startBox(size);
const box = {
type: 'flac-vorbis-comment',
fields: [],
};
const vendorLength = iterator.getUint32Le();
const vendorString = iterator.getByteString(vendorLength, true);
const numberOfFields = iterator.getUint32Le();
box.fields.push({ key: 'vendor', value: vendorString, trackId: null });
for (let i = 0; i < numberOfFields; i++) {
const fieldLength = iterator.getUint32Le();
const field = iterator.getByteString(fieldLength, true);
const [key, value] = field.split('=');
box.fields.push({ key: key.toLowerCase(), value, trackId: null });
}
state.structure.getFlacStructure().boxes.push(box);
expectNoMoreBytes();
return Promise.resolve(null);
};
exports.parseVorbisComment = parseVorbisComment;
@@ -0,0 +1,7 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseStreamInfo: ({ iterator, state, }: {
iterator: BufferIterator;
state: ParserState;
}) => Promise<ParseResult>;
@@ -0,0 +1,59 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseStreamInfo = void 0;
const register_track_1 = require("../../register-track");
const webcodecs_timescale_1 = require("../../webcodecs-timescale");
const parseStreamInfo = async ({ iterator, state, }) => {
const counter = iterator.counter.getOffset();
const minimumBlockSize = iterator.getUint16();
const maximumBlockSize = iterator.getUint16();
const minimumFrameSize = iterator.getUint24();
const maximumFrameSize = iterator.getUint24();
iterator.startReadingBits();
const sampleRate = iterator.getBits(20);
const channels = iterator.getBits(3) + 1;
const bitsPerSample = iterator.getBits(5);
const totalSamples = iterator.getBits(36);
iterator.getBits(128); // md5
iterator.stopReadingBits();
const counterNow = iterator.counter.getOffset();
const size = counterNow - counter;
iterator.counter.decrement(size);
const asUint8Array = iterator.getSlice(size);
const flacStreamInfo = {
type: 'flac-streaminfo',
bitsPerSample,
channels,
maximumBlockSize,
maximumFrameSize,
minimumBlockSize,
minimumFrameSize,
sampleRate,
totalSamples,
};
state.structure.getFlacStructure().boxes.push(flacStreamInfo);
await (0, register_track_1.registerAudioTrack)({
container: 'flac',
track: {
codec: 'flac',
type: 'audio',
description: asUint8Array,
codecData: { type: 'flac-description', data: asUint8Array },
codecEnum: 'flac',
numberOfChannels: channels,
sampleRate,
originalTimescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackId: 0,
startInSeconds: 0,
timescale: webcodecs_timescale_1.WEBCODECS_TIMESCALE,
trackMediaTimeOffsetInTrackTimescale: 0,
},
registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
tracks: state.callbacks.tracks,
logLevel: state.logLevel,
onAudioTrack: state.onAudioTrack,
});
state.callbacks.tracks.setIsDone(state.logLevel);
return Promise.resolve(null);
};
exports.parseStreamInfo = parseStreamInfo;
@@ -0,0 +1,8 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { ParseResult } from '../../parse-result';
import type { ParserState } from '../../state/parser-state';
export declare const parseFlacUnkownBlock: ({ iterator, state, size, }: {
iterator: BufferIterator;
state: ParserState;
size: number;
}) => Promise<ParseResult>;
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFlacUnkownBlock = void 0;
const parseFlacUnkownBlock = ({ iterator, state, size, }) => {
iterator.discard(size);
state.structure.getFlacStructure().boxes.push({
type: 'flac-header',
});
return Promise.resolve(null);
};
exports.parseFlacUnkownBlock = parseFlacUnkownBlock;
@@ -0,0 +1,18 @@
import type { AudioSampleOffset } from '../../state/audio-sample-map';
import type { FlacState } from '../../state/flac-state';
import type { ParserState } from '../../state/parser-state';
import type { SamplesObservedState } from '../../state/samples-observed/slow-duration-fps';
export type FlacSeekingHints = {
type: 'flac-seeking-hints';
audioSampleMap: AudioSampleOffset[];
blockingBitStrategy: number | null;
lastSampleObserved: boolean;
};
export declare const getSeekingHintsForFlac: ({ flacState, samplesObserved, }: {
flacState: FlacState;
samplesObserved: SamplesObservedState;
}) => FlacSeekingHints;
export declare const setSeekingHintsForFlac: ({ hints, state, }: {
hints: FlacSeekingHints;
state: ParserState;
}) => void;
@@ -0,0 +1,20 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.setSeekingHintsForFlac = exports.getSeekingHintsForFlac = void 0;
const getSeekingHintsForFlac = ({ flacState, samplesObserved, }) => {
var _a;
return {
type: 'flac-seeking-hints',
audioSampleMap: flacState.audioSamples.getSamples(),
blockingBitStrategy: (_a = flacState.getBlockingBitStrategy()) !== null && _a !== void 0 ? _a : null,
lastSampleObserved: samplesObserved.getLastSampleObserved(),
};
};
exports.getSeekingHintsForFlac = getSeekingHintsForFlac;
const setSeekingHintsForFlac = ({ hints, state, }) => {
if (hints.blockingBitStrategy !== null) {
state.flac.setBlockingBitStrategy(hints.blockingBitStrategy);
}
state.flac.audioSamples.setFromSeekingHints(hints.audioSampleMap);
};
exports.setSeekingHintsForFlac = setSeekingHintsForFlac;
@@ -0,0 +1,26 @@
import type { MediaParserMetadataEntry } from '../../metadata/get-metadata';
export type FlacHeader = {
type: 'flac-header';
};
export type FlacStreamInfo = {
type: 'flac-streaminfo';
minimumBlockSize: number;
maximumBlockSize: number;
minimumFrameSize: number;
maximumFrameSize: number;
sampleRate: number;
channels: number;
bitsPerSample: number;
totalSamples: number;
};
export type FlacVorbisComment = {
type: 'flac-vorbis-comment';
fields: MediaParserMetadataEntry[];
};
export type FlacUnknownBlock = {
type: 'flac-unknown-block';
};
export type FlacStructure = {
type: 'flac';
boxes: (FlacHeader | FlacStreamInfo | FlacUnknownBlock | FlacVorbisComment)[];
};
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,6 @@
import type { MoofBox } from '../../state/iso-base-media/precomputed-moof';
import type { TfraBox } from './mfra/tfra';
export declare const areSamplesComplete: ({ moofBoxes, tfraBoxes, }: {
moofBoxes: MoofBox[];
tfraBoxes: TfraBox[];
}) => boolean;
@@ -0,0 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.areSamplesComplete = void 0;
const areSamplesComplete = ({ moofBoxes, tfraBoxes, }) => {
if (moofBoxes.length === 0) {
return true;
}
return (tfraBoxes.length > 0 &&
tfraBoxes.every((t) => t.entries.length === moofBoxes.length));
};
exports.areSamplesComplete = areSamplesComplete;
@@ -0,0 +1,40 @@
import type { BaseBox } from './base-type';
import type { ElstBox } from './elst';
import type { EsdsBox } from './esds/esds';
import type { FtypBox } from './ftyp';
import type { MdhdBox } from './mdhd';
import type { HdlrBox } from './meta/hdlr';
import type { IlstBox } from './meta/ilst';
import type { TfraBox } from './mfra/tfra';
import type { MoovBox } from './moov/moov';
import type { MvhdBox } from './moov/mvhd';
import type { TrexBox } from './moov/trex';
import type { Av1CBox } from './stsd/av1c';
import type { AvccBox } from './stsd/avcc';
import type { ColorParameterBox } from './stsd/colr';
import type { CttsBox } from './stsd/ctts';
import type { HvccBox } from './stsd/hvcc';
import type { KeysBox } from './stsd/keys';
import type { MebxBox } from './stsd/mebx';
import type { PaspBox } from './stsd/pasp';
import type { StcoBox } from './stsd/stco';
import type { StscBox } from './stsd/stsc';
import type { StsdBox } from './stsd/stsd';
import type { StssBox } from './stsd/stss';
import type { StszBox } from './stsd/stsz';
import type { SttsBox } from './stsd/stts';
import type { VpccBox } from './stsd/vpcc';
import type { TfdtBox } from './tfdt';
import type { TfhdBox } from './tfhd';
import type { TkhdBox } from './tkhd';
import type { TrakBox } from './trak/trak';
import type { TrunBox } from './trun';
import type { VoidBox } from './void-box';
export interface RegularBox extends BaseBox {
boxType: string;
boxSize: number;
children: IsoBaseMediaBox[];
offset: number;
type: 'regular-box';
}
export type IsoBaseMediaBox = RegularBox | FtypBox | MvhdBox | TkhdBox | StsdBox | ElstBox | MebxBox | KeysBox | MoovBox | TrakBox | SttsBox | MdhdBox | IlstBox | EsdsBox | StszBox | StcoBox | StscBox | AvccBox | HvccBox | VpccBox | VoidBox | StssBox | PaspBox | CttsBox | Av1CBox | TrunBox | HdlrBox | ColorParameterBox | TfdtBox | TfhdBox | TfraBox | TrexBox;
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,4 @@
export interface BaseBox {
boxSize: number;
offset: number;
}
@@ -0,0 +1,2 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,15 @@
import type { MoofBox } from '../../state/iso-base-media/precomputed-moof';
import type { TrexBox } from './moov/trex';
import type { TkhdBox } from './tkhd';
export declare const collectSamplePositionsFromMoofBoxes: ({ moofBoxes, tkhdBox, isComplete, trexBoxes, }: {
moofBoxes: MoofBox[];
tkhdBox: TkhdBox;
isComplete: boolean;
trexBoxes: TrexBox[];
}) => {
samplePositions: {
isLastFragment: boolean;
samples: import("../../get-sample-positions").SamplePosition[];
}[];
isComplete: boolean;
};
@@ -0,0 +1,19 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectSamplePositionsFromMoofBoxes = void 0;
const samples_from_moof_1 = require("../../samples-from-moof");
const collectSamplePositionsFromMoofBoxes = ({ moofBoxes, tkhdBox, isComplete, trexBoxes, }) => {
const samplePositions = moofBoxes.map((m, index) => {
const isLastFragment = index === moofBoxes.length - 1 && isComplete;
return {
isLastFragment,
samples: (0, samples_from_moof_1.getSamplesFromMoof)({
moofBox: m,
trackId: tkhdBox.trackId,
trexBoxes,
}),
};
});
return { samplePositions, isComplete };
};
exports.collectSamplePositionsFromMoofBoxes = collectSamplePositionsFromMoofBoxes;
@@ -0,0 +1,2 @@
import type { TrakBox } from './trak/trak';
export declare const collectSamplePositionsFromTrak: (trakBox: TrakBox) => import("../../get-sample-positions").SamplePosition[];
@@ -0,0 +1,49 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.collectSamplePositionsFromTrak = void 0;
const get_fps_1 = require("../../get-fps");
const get_sample_positions_1 = require("../../get-sample-positions");
const get_sample_positions_from_mp4_1 = require("../../get-sample-positions-from-mp4");
const should_group_audio_samples_1 = require("./should-group-audio-samples");
const traversal_1 = require("./traversal");
const collectSamplePositionsFromTrak = (trakBox) => {
const shouldGroupSamples = (0, should_group_audio_samples_1.shouldGroupAudioSamples)(trakBox);
const timescaleAndDuration = (0, get_fps_1.getTimescaleAndDuration)(trakBox);
if (shouldGroupSamples) {
return (0, get_sample_positions_from_mp4_1.getGroupedSamplesPositionsFromMp4)({
trakBox,
bigEndian: shouldGroupSamples.bigEndian,
});
}
const stszBox = (0, traversal_1.getStszBox)(trakBox);
const stcoBox = (0, traversal_1.getStcoBox)(trakBox);
const stscBox = (0, traversal_1.getStscBox)(trakBox);
const stssBox = (0, traversal_1.getStssBox)(trakBox);
const sttsBox = (0, traversal_1.getSttsBox)(trakBox);
const cttsBox = (0, traversal_1.getCttsBox)(trakBox);
if (!stszBox) {
throw new Error('Expected stsz box in trak box');
}
if (!stcoBox) {
throw new Error('Expected stco box in trak box');
}
if (!stscBox) {
throw new Error('Expected stsc box in trak box');
}
if (!sttsBox) {
throw new Error('Expected stts box in trak box');
}
if (!timescaleAndDuration) {
throw new Error('Expected timescale and duration in trak box');
}
const samplePositions = (0, get_sample_positions_1.getSamplePositions)({
stcoBox,
stscBox,
stszBox,
stssBox,
sttsBox,
cttsBox,
});
return samplePositions;
};
exports.collectSamplePositionsFromTrak = collectSamplePositionsFromTrak;
@@ -0,0 +1,2 @@
import type { MediaParserAdvancedColor } from '../../get-tracks';
export declare const mediaParserAdvancedColorToWebCodecsColor: (color: MediaParserAdvancedColor) => VideoColorSpaceInit;
@@ -0,0 +1,12 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.mediaParserAdvancedColorToWebCodecsColor = void 0;
const mediaParserAdvancedColorToWebCodecsColor = (color) => {
return {
transfer: color.transfer,
matrix: color.matrix,
primaries: color.primaries,
fullRange: color.fullRange,
};
};
exports.mediaParserAdvancedColorToWebCodecsColor = mediaParserAdvancedColorToWebCodecsColor;
@@ -0,0 +1,19 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { BaseBox } from './base-type';
export interface ElstBox extends BaseBox {
type: 'elst-box';
version: number;
flags: number;
entries: ElstEntry[];
}
export type ElstEntry = {
editDuration: number;
mediaTime: number;
mediaRateInteger: number;
mediaRateFraction: number;
};
export declare const parseElst: ({ iterator, size, offset, }: {
iterator: BufferIterator;
size: number;
offset: number;
}) => ElstBox;
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseElst = void 0;
const parseElst = ({ iterator, size, offset, }) => {
const { discardRest } = iterator.startBox(size - 8);
const version = iterator.getUint8();
const flags = iterator.getUint24();
const entryCount = iterator.getUint32();
const entries = [];
for (let i = 0; i < entryCount; i++) {
const editDuration = Number(version === 1 ? iterator.getUint64() : iterator.getUint32());
const mediaTime = Number(version === 1 ? iterator.getUint64() : iterator.getInt32());
const mediaRateInteger = iterator.getUint16();
const mediaRateFraction = iterator.getUint16();
entries.push({
editDuration,
mediaTime,
mediaRateInteger,
mediaRateFraction,
});
}
discardRest();
const result = {
type: 'elst-box',
version,
flags,
entries,
boxSize: size,
offset,
};
return result;
};
exports.parseElst = parseElst;
@@ -0,0 +1,14 @@
import type { BufferIterator } from '../../../iterator/buffer-iterator';
type UnknownDecoderSpecificConfig = {
type: 'unknown-decoder-specific-config';
};
type AudioSpecificConfig = {
type: 'mp4a-specific-config';
audioObjectType: number;
samplingFrequencyIndex: number;
channelConfiguration: number;
asBytes: Uint8Array;
};
export type DecoderSpecificConfig = UnknownDecoderSpecificConfig | AudioSpecificConfig;
export declare const parseDecoderSpecificConfig: (iterator: BufferIterator) => DecoderSpecificConfig;
export {};
@@ -0,0 +1,38 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseDecoderSpecificConfig = void 0;
const parseDecoderSpecificConfig = (iterator) => {
const layerTag = iterator.getUint8();
const layerSize = iterator.getPaddedFourByteNumber();
const start = iterator.counter.getOffset();
if (layerTag !== 5) {
iterator.discard(layerSize);
return {
type: 'unknown-decoder-specific-config',
};
}
// https://csclub.uwaterloo.ca/~pbarfuss/ISO14496-3-2009.pdf
// 1.6.2.1 AudioSpecificConfig
const bytes = iterator.getSlice(layerSize);
iterator.counter.decrement(layerSize);
iterator.startReadingBits();
const audioObjectType = iterator.getBits(5);
const samplingFrequencyIndex = iterator.getBits(4);
if (samplingFrequencyIndex === 0xf) {
iterator.getBits(24);
}
const channelConfiguration = iterator.getBits(4);
iterator.stopReadingBits();
const read = iterator.counter.getOffset() - start;
if (read < layerSize) {
iterator.discard(layerSize - read);
}
return {
type: 'mp4a-specific-config',
audioObjectType,
samplingFrequencyIndex,
channelConfiguration,
asBytes: bytes,
};
};
exports.parseDecoderSpecificConfig = parseDecoderSpecificConfig;
@@ -0,0 +1,29 @@
import type { BufferIterator } from '../../../iterator/buffer-iterator';
import type { DecoderSpecificConfig } from './decoder-specific-config';
type AudioObjectType = 'aac' | 'mp3' | 'unknown';
type DecoderConfigDescriptor = {
type: 'decoder-config-descriptor';
objectTypeIndication: AudioObjectType;
asNumber: number;
streamType: number;
upStream: number;
bufferSizeDB: number;
maxBitrate: number;
avgBitrate: number;
decoderSpecificConfigs: DecoderSpecificConfig[];
};
type SlConfigDescriptor = {
type: 'sl-config-descriptor';
};
type UnknownDescriptor = {
type: 'unknown-descriptor';
};
export type Descriptor = DecoderConfigDescriptor | SlConfigDescriptor | UnknownDescriptor;
type DescriptorAndNext = {
descriptor: Descriptor | null;
};
export declare const processDescriptor: ({ iterator, }: {
iterator: BufferIterator;
}) => DescriptorAndNext;
export declare const parseDescriptors: (iterator: BufferIterator, maxBytes: number) => Descriptor[];
export {};
@@ -0,0 +1,82 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseDescriptors = exports.processDescriptor = void 0;
const decoder_specific_config_1 = require("./decoder-specific-config");
const mapToObjectAudioIndicator = (num) => {
// https://chromium.googlesource.com/chromium/src/media/+/master/formats/mp4/es_descriptor.h
// http://netmedia.zju.edu.cn/multimedia2013/mpeg-4/ISO%20IEC%2014496-1%20MPEG-4%20System%20Standard.pdf
// Page 42, table 8
if (num === 0x40) {
return 'aac';
}
if (num === 0x6b) {
return 'mp3';
}
return 'unknown';
};
const processDescriptor = ({ iterator, }) => {
const tag = iterator.getUint8();
if (tag === 4) {
const size = iterator.getPaddedFourByteNumber();
const initialOffset = iterator.counter.getOffset();
const objectTypeIndication = iterator.getUint8();
iterator.startReadingBits();
const streamType = iterator.getBits(6);
const upStream = iterator.getBits(1);
// reserved
iterator.getBits(1);
const bufferSizeDB = iterator.getBits(24);
iterator.stopReadingBits();
const maxBitrate = iterator.getUint32();
const avgBitrate = iterator.getUint32();
const decoderSpecificConfigs = [];
while (size - (iterator.counter.getOffset() - initialOffset) > 0) {
const decoderSpecificConfig = (0, decoder_specific_config_1.parseDecoderSpecificConfig)(iterator);
decoderSpecificConfigs.push(decoderSpecificConfig);
}
return {
descriptor: {
type: 'decoder-config-descriptor',
objectTypeIndication: mapToObjectAudioIndicator(objectTypeIndication),
asNumber: objectTypeIndication,
bufferSizeDB,
streamType,
upStream,
avgBitrate,
maxBitrate,
decoderSpecificConfigs,
},
};
}
if (tag === 6) {
const size = iterator.getPaddedFourByteNumber();
iterator.discard(size);
return {
descriptor: {
type: 'sl-config-descriptor',
},
};
}
return {
descriptor: null,
};
};
exports.processDescriptor = processDescriptor;
const parseDescriptors = (iterator, maxBytes) => {
const descriptors = [];
const initialOffset = iterator.counter.getOffset();
while (iterator.bytesRemaining() > 0 &&
iterator.counter.getOffset() - initialOffset < maxBytes) {
const { descriptor } = (0, exports.processDescriptor)({
iterator,
});
if (descriptor) {
descriptors.push(descriptor);
}
else {
break;
}
}
return descriptors;
};
exports.parseDescriptors = parseDescriptors;
@@ -0,0 +1,15 @@
import type { BufferIterator } from '../../../iterator/buffer-iterator';
import type { Descriptor } from './esds-descriptors';
export interface EsdsBox {
type: 'esds-box';
version: number;
tag: number;
sizeOfInstance: number;
esId: number;
descriptors: Descriptor[];
}
export declare const parseEsds: ({ data, size, fileOffset, }: {
data: BufferIterator;
size: number;
fileOffset: number;
}) => EsdsBox;
@@ -0,0 +1,27 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseEsds = void 0;
const esds_descriptors_1 = require("./esds-descriptors");
const parseEsds = ({ data, size, fileOffset, }) => {
const version = data.getUint8();
// Flags, we discard them
data.discard(3);
const tag = data.getUint8();
const sizeOfInstance = data.getPaddedFourByteNumber();
const esId = data.getUint16();
// disard 1 byte, currently unknown
data.discard(1);
const remaining = size - (data.counter.getOffset() - fileOffset);
const descriptors = (0, esds_descriptors_1.parseDescriptors)(data, remaining);
const remainingNow = size - (data.counter.getOffset() - fileOffset);
data.discard(remainingNow);
return {
type: 'esds-box',
version,
tag,
sizeOfInstance,
esId,
descriptors,
};
};
exports.parseEsds = parseEsds;
@@ -0,0 +1,11 @@
import type { SamplePosition } from '../../get-sample-positions';
import type { MediaParserLogLevel } from '../../log';
import type { MediaSection } from '../../state/video-section';
export declare const findKeyframeBeforeTime: ({ samplePositions, time, timescale, mediaSections, logLevel, startInSeconds, }: {
samplePositions: SamplePosition[];
time: number;
timescale: number;
mediaSections: MediaSection[];
logLevel: MediaParserLogLevel;
startInSeconds: number;
}) => SamplePosition | null;
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findKeyframeBeforeTime = void 0;
const log_1 = require("../../log");
const findKeyframeBeforeTime = ({ samplePositions, time, timescale, mediaSections, logLevel, startInSeconds, }) => {
let videoByte = 0;
let videoSample = null;
for (const sample of samplePositions) {
const ctsInSeconds = sample.timestamp / timescale + startInSeconds;
const dtsInSeconds = sample.decodingTimestamp / timescale + startInSeconds;
if (!sample.isKeyframe) {
continue;
}
if (!(ctsInSeconds <= time || dtsInSeconds <= time)) {
continue;
}
if (videoByte <= sample.offset) {
videoByte = sample.offset;
videoSample = sample;
}
}
if (!videoSample) {
throw new Error('No sample found');
}
const mediaSection = mediaSections.find((section) => videoSample.offset >= section.start &&
videoSample.offset < section.start + section.size);
if (!mediaSection) {
log_1.Log.trace(logLevel, 'Found a sample, but the offset has not yet been marked as a video section yet. Not yet able to seek, but probably once we have started reading the next box.', videoSample);
return null;
}
return videoSample;
};
exports.findKeyframeBeforeTime = findKeyframeBeforeTime;
@@ -0,0 +1,14 @@
import type { SamplePosition } from '../../get-sample-positions';
import type { MediaParserAudioTrack, MediaParserOtherTrack, MediaParserVideoTrack } from '../../get-tracks';
import type { IsoBaseMediaStructure } from '../../parse-result';
import type { StructureState } from '../../state/structure';
export declare const findAnyTrackWithSamplePositions: (allTracks: (MediaParserVideoTrack | MediaParserAudioTrack | MediaParserOtherTrack)[], struc: IsoBaseMediaStructure) => {
track: MediaParserVideoTrack | MediaParserAudioTrack;
samplePositions: SamplePosition[];
} | null;
type TrackWithSamplePositions = {
track: MediaParserVideoTrack | MediaParserAudioTrack;
samplePositions: SamplePosition[];
};
export declare const findTrackToSeek: (allTracks: (MediaParserVideoTrack | MediaParserAudioTrack | MediaParserOtherTrack)[], structure: StructureState) => TrackWithSamplePositions | null;
export {};
@@ -0,0 +1,64 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.findTrackToSeek = exports.findAnyTrackWithSamplePositions = void 0;
const are_samples_complete_1 = require("./are-samples-complete");
const get_sample_positions_from_track_1 = require("./get-sample-positions-from-track");
const traversal_1 = require("./traversal");
const findAnyTrackWithSamplePositions = (allTracks, struc) => {
const moov = (0, traversal_1.getMoovFromFromIsoStructure)(struc);
if (!moov) {
return null;
}
for (const track of allTracks) {
if (track.type === 'video' || track.type === 'audio') {
const trakBox = (0, traversal_1.getTrakBoxByTrackId)(moov, track.trackId);
if (!trakBox) {
continue;
}
const { samplePositions } = (0, get_sample_positions_from_track_1.getSamplePositionsFromTrack)({
trakBox,
moofBoxes: (0, traversal_1.getMoofBoxes)(struc.boxes),
moofComplete: (0, are_samples_complete_1.areSamplesComplete)({
moofBoxes: (0, traversal_1.getMoofBoxes)(struc.boxes),
tfraBoxes: (0, traversal_1.getTfraBoxes)(struc.boxes),
}),
trexBoxes: (0, traversal_1.getTrexBoxes)(moov),
});
if (samplePositions.length === 0) {
continue;
}
return { track, samplePositions };
}
}
return null;
};
exports.findAnyTrackWithSamplePositions = findAnyTrackWithSamplePositions;
const findTrackToSeek = (allTracks, structure) => {
const firstVideoTrack = allTracks.find((t) => t.type === 'video');
const struc = structure.getIsoStructure();
if (!firstVideoTrack) {
return (0, exports.findAnyTrackWithSamplePositions)(allTracks, struc);
}
const moov = (0, traversal_1.getMoovFromFromIsoStructure)(struc);
if (!moov) {
return null;
}
const trakBox = (0, traversal_1.getTrakBoxByTrackId)(moov, firstVideoTrack.trackId);
if (!trakBox) {
return null;
}
const { samplePositions } = (0, get_sample_positions_from_track_1.getSamplePositionsFromTrack)({
trakBox,
moofBoxes: (0, traversal_1.getMoofBoxes)(struc.boxes),
moofComplete: (0, are_samples_complete_1.areSamplesComplete)({
moofBoxes: (0, traversal_1.getMoofBoxes)(struc.boxes),
tfraBoxes: (0, traversal_1.getTfraBoxes)(struc.boxes),
}),
trexBoxes: (0, traversal_1.getTrexBoxes)(moov),
});
if (samplePositions.length === 0) {
return (0, exports.findAnyTrackWithSamplePositions)(allTracks, struc);
}
return { track: firstVideoTrack, samplePositions };
};
exports.findTrackToSeek = findTrackToSeek;
@@ -0,0 +1,13 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { BaseBox } from './base-type';
export interface FtypBox extends BaseBox {
type: 'ftyp-box';
majorBrand: string;
minorVersion: number;
compatibleBrands: string[];
}
export declare const parseFtyp: ({ iterator, size, offset, }: {
iterator: BufferIterator;
size: number;
offset: number;
}) => FtypBox;
@@ -0,0 +1,22 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFtyp = void 0;
const parseFtyp = ({ iterator, size, offset, }) => {
const majorBrand = iterator.getByteString(4, false);
const minorVersion = iterator.getFourByteNumber();
const types = (size - iterator.counter.getOffset()) / 4;
const compatibleBrands = [];
for (let i = 0; i < types; i++) {
compatibleBrands.push(iterator.getByteString(4, false).trim());
}
const offsetAtEnd = iterator.counter.getOffset();
return {
type: 'ftyp-box',
majorBrand,
minorVersion,
compatibleBrands,
offset,
boxSize: offsetAtEnd - offset,
};
};
exports.parseFtyp = parseFtyp;
@@ -0,0 +1,14 @@
import type { MediaParserCodecData } from '../../codec-data';
import type { MediaParserAudioCodec } from '../../get-tracks';
type AudioDecoderConfig = {
numberOfChannels: number;
sampleRate: number;
codecPrivate: MediaParserCodecData | null;
};
export declare const getActualDecoderParameters: ({ audioCodec, codecPrivate, numberOfChannels, sampleRate, }: {
audioCodec: MediaParserAudioCodec;
codecPrivate: MediaParserCodecData | null;
numberOfChannels: number;
sampleRate: number;
}) => AudioDecoderConfig;
export {};
@@ -0,0 +1,33 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getActualDecoderParameters = void 0;
const aac_codecprivate_1 = require("../../aac-codecprivate");
// Example video: 'https://pub-646d808d9cb240cea53bedc76dd3cd0c.r2.dev/riverside.mp4';
// This video has `numberOfChannels = 2`, but the actual number of channels is `1` according to Codec Private.
// Therefore, prioritizing Codec Private over `numberOfChannels`.
const getActualDecoderParameters = ({ audioCodec, codecPrivate, numberOfChannels, sampleRate, }) => {
if (audioCodec !== 'aac') {
return {
numberOfChannels,
sampleRate,
codecPrivate,
};
}
if (codecPrivate === null) {
return { numberOfChannels, sampleRate, codecPrivate };
}
if (codecPrivate.type !== 'aac-config') {
throw new Error('Expected AAC codec private data');
}
const parsed = (0, aac_codecprivate_1.parseAacCodecPrivate)(codecPrivate.data);
const actual = (0, aac_codecprivate_1.createAacCodecPrivate)({
...parsed,
codecPrivate: codecPrivate.data,
});
return {
numberOfChannels: parsed.channelConfiguration,
sampleRate: parsed.sampleRate,
codecPrivate: { type: 'aac-config', data: actual },
};
};
exports.getActualDecoderParameters = getActualDecoderParameters;
@@ -0,0 +1,11 @@
import type { BufferIterator } from '../../iterator/buffer-iterator';
import type { MediaParserLogLevel } from '../../log';
import type { IsoBaseMediaBox } from './base-media-box';
import type { OnlyIfMoovAtomExpected } from './process-box';
export declare const getIsoBaseMediaChildren: ({ size, iterator, logLevel, onlyIfMoovAtomExpected, contentLength, }: {
size: number;
iterator: BufferIterator;
logLevel: MediaParserLogLevel;
onlyIfMoovAtomExpected: OnlyIfMoovAtomExpected | null;
contentLength: number;
}) => Promise<IsoBaseMediaBox[]>;
@@ -0,0 +1,26 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getIsoBaseMediaChildren = void 0;
const process_box_1 = require("./process-box");
const getIsoBaseMediaChildren = async ({ size, iterator, logLevel, onlyIfMoovAtomExpected, contentLength, }) => {
const boxes = [];
const initial = iterator.counter.getOffset();
while (iterator.counter.getOffset() < size + initial) {
const parsed = await (0, process_box_1.processBox)({
iterator,
logLevel,
onlyIfMoovAtomExpected,
onlyIfMdatAtomExpected: null,
contentLength,
});
if (parsed.type !== 'box') {
throw new Error('Expected box');
}
boxes.push(parsed.box);
}
if (iterator.counter.getOffset() > size + initial) {
throw new Error(`read too many bytes - size: ${size}, read: ${iterator.counter.getOffset() - initial}. initial offset: ${initial}`);
}
return boxes;
};
exports.getIsoBaseMediaChildren = getIsoBaseMediaChildren;
@@ -0,0 +1,3 @@
import type { MediaParserKeyframe } from '../../options';
import type { ParserState } from '../../state/parser-state';
export declare const getKeyframesFromIsoBaseMedia: (state: ParserState) => MediaParserKeyframe[];
@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getKeyframesFromIsoBaseMedia = void 0;
const get_tracks_1 = require("../../get-tracks");
const are_samples_complete_1 = require("./are-samples-complete");
const get_sample_positions_from_track_1 = require("./get-sample-positions-from-track");
const traversal_1 = require("./traversal");
const getKeyframesFromIsoBaseMedia = (state) => {
const tracks = (0, get_tracks_1.getTracksFromIsoBaseMedia)({
isoState: state.iso,
m3uPlaylistContext: state.m3uPlaylistContext,
structure: state.structure,
mayUsePrecomputed: true,
});
const videoTracks = tracks.filter((t) => t.type === 'video');
const structure = state.structure.getIsoStructure();
const moofBoxes = (0, traversal_1.getMoofBoxes)(structure.boxes);
const tfraBoxes = (0, traversal_1.getTfraBoxes)(structure.boxes);
const moov = (0, traversal_1.getMoovFromFromIsoStructure)(structure);
if (!moov) {
return [];
}
const allSamples = videoTracks.map((t) => {
const { originalTimescale: ts } = t;
const trakBox = (0, traversal_1.getTrakBoxByTrackId)(moov, t.trackId);
if (!trakBox) {
return [];
}
const { samplePositions, isComplete } = (0, get_sample_positions_from_track_1.getSamplePositionsFromTrack)({
trakBox,
moofBoxes,
moofComplete: (0, are_samples_complete_1.areSamplesComplete)({
moofBoxes,
tfraBoxes,
}),
trexBoxes: (0, traversal_1.getTrexBoxes)(moov),
});
if (!isComplete) {
return [];
}
const keyframes = samplePositions
.filter((k) => {
return k.isKeyframe;
})
.map((k) => {
return {
trackId: t.trackId,
presentationTimeInSeconds: k.timestamp / ts,
decodingTimeInSeconds: k.decodingTimestamp / ts,
positionInBytes: k.offset,
sizeInBytes: k.size,
};
});
return keyframes;
});
return allSamples.flat();
};
exports.getKeyframesFromIsoBaseMedia = getKeyframesFromIsoBaseMedia;
@@ -0,0 +1,15 @@
import type { MediaParserController } from '../../controller/media-parser-controller';
import type { PrefetchCache } from '../../fetch';
import type { MediaParserLogLevel } from '../../log';
import type { ParseMediaSrc } from '../../options';
import type { MediaParserReaderInterface } from '../../readers/reader';
import type { IsoBaseMediaBox } from './base-media-box';
export type MfraSeekingBoxOptions = {
contentLength: number;
controller: MediaParserController;
readerInterface: MediaParserReaderInterface;
src: ParseMediaSrc;
logLevel: MediaParserLogLevel;
prefetchCache: PrefetchCache;
};
export declare const getMfraSeekingBox: ({ contentLength, controller, readerInterface, src, logLevel, prefetchCache, }: MfraSeekingBoxOptions) => Promise<IsoBaseMediaBox[] | null>;
@@ -0,0 +1,37 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMfraSeekingBox = void 0;
const get_children_1 = require("./get-children");
const get_mfra_atom_1 = require("./mfra/get-mfra-atom");
const get_mfro_atom_1 = require("./mfra/get-mfro-atom");
const getMfraSeekingBox = async ({ contentLength, controller, readerInterface, src, logLevel, prefetchCache, }) => {
const parentSize = await (0, get_mfro_atom_1.getMfroAtom)({
contentLength,
controller,
readerInterface,
src,
logLevel,
prefetchCache,
});
if (!parentSize) {
return null;
}
const mfraAtom = await (0, get_mfra_atom_1.getMfraAtom)({
contentLength,
controller,
readerInterface,
src,
parentSize,
logLevel,
prefetchCache,
});
mfraAtom.discard(8);
return (0, get_children_1.getIsoBaseMediaChildren)({
iterator: mfraAtom,
logLevel,
size: parentSize - 8,
onlyIfMoovAtomExpected: null,
contentLength,
});
};
exports.getMfraSeekingBox = getMfraSeekingBox;
@@ -0,0 +1,6 @@
import type { ParserState } from '../../state/parser-state';
import type { MoovBox } from './moov/moov';
export declare const getMoovAtom: ({ endOfMdat, state, }: {
state: ParserState;
endOfMdat: number;
}) => Promise<MoovBox>;
@@ -0,0 +1,112 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getMoovAtom = void 0;
const buffer_iterator_1 = require("../../iterator/buffer-iterator");
const log_1 = require("../../log");
const register_track_1 = require("../../register-track");
const can_skip_tracks_1 = require("../../state/can-skip-tracks");
const has_tracks_section_1 = require("../../state/has-tracks-section");
const structure_1 = require("../../state/structure");
const process_box_1 = require("./process-box");
const traversal_1 = require("./traversal");
const getMoovAtom = async ({ endOfMdat, state, }) => {
var _a;
const headerSegment = (_a = state.m3uPlaylistContext) === null || _a === void 0 ? void 0 : _a.mp4HeaderSegment;
if (headerSegment) {
const segment = (0, traversal_1.getMoovFromFromIsoStructure)(headerSegment);
if (!segment) {
throw new Error('No moov box found in header segment');
}
return segment;
}
const start = Date.now();
log_1.Log.verbose(state.logLevel, 'Starting second fetch to get moov atom');
const { reader } = await state.readerInterface.read({
src: state.src,
range: endOfMdat,
controller: state.controller,
logLevel: state.logLevel,
prefetchCache: state.prefetchCache,
});
const onAudioTrack = state.onAudioTrack
? async ({ track, container }) => {
await (0, register_track_1.registerAudioTrack)({
track,
container,
logLevel: state.logLevel,
onAudioTrack: state.onAudioTrack,
registerAudioSampleCallback: state.callbacks.registerAudioSampleCallback,
tracks: state.callbacks.tracks,
});
return null;
}
: null;
const onVideoTrack = state.onVideoTrack
? async ({ track, container }) => {
await (0, register_track_1.registerVideoTrack)({
track,
container,
logLevel: state.logLevel,
onVideoTrack: state.onVideoTrack,
registerVideoSampleCallback: state.callbacks.registerVideoSampleCallback,
tracks: state.callbacks.tracks,
});
return null;
}
: null;
const iterator = (0, buffer_iterator_1.getArrayBufferIterator)({
initialData: new Uint8Array([]),
maxBytes: state.contentLength - endOfMdat,
logLevel: 'error',
});
while (true) {
const result = await reader.reader.read();
if (result.value) {
iterator.addData(result.value);
}
if (result.done) {
break;
}
}
const boxes = [];
const canSkipTracksState = (0, can_skip_tracks_1.makeCanSkipTracksState)({
hasAudioTrackHandlers: false,
fields: { slowStructure: true },
hasVideoTrackHandlers: false,
structure: (0, structure_1.structureState)(),
});
const tracksState = (0, has_tracks_section_1.makeTracksSectionState)(canSkipTracksState, state.src);
while (true) {
const box = await (0, process_box_1.processBox)({
iterator,
logLevel: state.logLevel,
onlyIfMoovAtomExpected: {
tracks: tracksState,
isoState: null,
movieTimeScaleState: state.iso.movieTimeScale,
onAudioTrack,
onVideoTrack,
registerVideoSampleCallback: () => Promise.resolve(),
registerAudioSampleCallback: () => Promise.resolve(),
},
onlyIfMdatAtomExpected: null,
contentLength: state.contentLength - endOfMdat,
});
if (box.type === 'box') {
boxes.push(box.box);
}
if (iterator.counter.getOffset() + endOfMdat > state.contentLength) {
throw new Error('Read past end of file');
}
if (iterator.counter.getOffset() + endOfMdat === state.contentLength) {
break;
}
}
const moov = boxes.find((b) => b.type === 'moov-box');
if (!moov) {
throw new Error('No moov box found');
}
log_1.Log.verbose(state.logLevel, `Finished fetching moov atom in ${Date.now() - start}ms`);
return moov;
};
exports.getMoovAtom = getMoovAtom;
@@ -0,0 +1,5 @@
import type { SamplePosition } from '../../get-sample-positions';
export declare const getSamplePositionBounds: (samplePositions: SamplePosition[], timescale: number) => {
min: number;
max: number;
};
@@ -0,0 +1,21 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSamplePositionBounds = void 0;
const getSamplePositionBounds = (samplePositions, timescale) => {
var _a;
let min = Infinity;
let max = -Infinity;
for (const samplePosition of samplePositions) {
const timestampMin = Math.min(samplePosition.timestamp, samplePosition.decodingTimestamp);
const timestampMax = Math.max(samplePosition.timestamp, samplePosition.decodingTimestamp) +
((_a = samplePosition.duration) !== null && _a !== void 0 ? _a : 0);
if (timestampMin < min) {
min = timestampMin;
}
if (timestampMax > max) {
max = timestampMax;
}
}
return { min: min / timescale, max: max / timescale };
};
exports.getSamplePositionBounds = getSamplePositionBounds;
@@ -0,0 +1,13 @@
import type { SamplePosition } from '../../get-sample-positions';
import type { MoofBox } from '../../state/iso-base-media/precomputed-moof';
import type { TrexBox } from './moov/trex';
import type { TrakBox } from './trak/trak';
export declare const getSamplePositionsFromTrack: ({ trakBox, moofBoxes, moofComplete, trexBoxes, }: {
trakBox: TrakBox;
moofBoxes: MoofBox[];
moofComplete: boolean;
trexBoxes: TrexBox[];
}) => {
samplePositions: SamplePosition[];
isComplete: boolean;
};
@@ -0,0 +1,29 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSamplePositionsFromTrack = void 0;
const collect_sample_positions_from_moof_boxes_1 = require("./collect-sample-positions-from-moof-boxes");
const collect_sample_positions_from_trak_1 = require("./collect-sample-positions-from-trak");
const traversal_1 = require("./traversal");
const getSamplePositionsFromTrack = ({ trakBox, moofBoxes, moofComplete, trexBoxes, }) => {
const tkhdBox = (0, traversal_1.getTkhdBox)(trakBox);
if (!tkhdBox) {
throw new Error('Expected tkhd box in trak box');
}
if (moofBoxes.length > 0) {
const { samplePositions } = (0, collect_sample_positions_from_moof_boxes_1.collectSamplePositionsFromMoofBoxes)({
moofBoxes,
tkhdBox,
isComplete: moofComplete,
trexBoxes,
});
return {
samplePositions: samplePositions.map((s) => s.samples).flat(1),
isComplete: moofComplete,
};
}
return {
samplePositions: (0, collect_sample_positions_from_trak_1.collectSamplePositionsFromTrak)(trakBox),
isComplete: true,
};
};
exports.getSamplePositionsFromTrack = getSamplePositionsFromTrack;
@@ -0,0 +1,18 @@
import type { MediaParserTrack } from '../../get-tracks';
import type { MediaParserLogLevel } from '../../log';
import type { IsoBaseMediaStructure } from '../../parse-result';
import type { IsoBaseMediaSeekingHints } from '../../seeking-hints';
import type { IsoBaseMediaState } from '../../state/iso-base-media/iso-state';
import type { StructureState } from '../../state/structure';
import type { SeekResolution } from '../../work-on-seek-request';
export declare const getSeekingByteFromFragmentedMp4: ({ info, time, logLevel, currentPosition, isoState, tracks, isLastChunkInPlaylist, structure, mp4HeaderSegment, }: {
info: IsoBaseMediaSeekingHints;
time: number;
logLevel: MediaParserLogLevel;
currentPosition: number;
isoState: IsoBaseMediaState;
structure: StructureState;
tracks: MediaParserTrack[];
isLastChunkInPlaylist: boolean;
mp4HeaderSegment: IsoBaseMediaStructure | null;
}) => Promise<SeekResolution>;
@@ -0,0 +1,111 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteFromFragmentedMp4 = void 0;
const log_1 = require("../../log");
const video_section_1 = require("../../state/video-section");
const are_samples_complete_1 = require("./are-samples-complete");
const collect_sample_positions_from_moof_boxes_1 = require("./collect-sample-positions-from-moof-boxes");
const find_keyframe_before_time_1 = require("./find-keyframe-before-time");
const get_sample_position_bounds_1 = require("./get-sample-position-bounds");
const find_best_segment_from_tfra_1 = require("./mfra/find-best-segment-from-tfra");
const traversal_1 = require("./traversal");
const getSeekingByteFromFragmentedMp4 = async ({ info, time, logLevel, currentPosition, isoState, tracks, isLastChunkInPlaylist, structure, mp4HeaderSegment, }) => {
const firstVideoTrack = tracks.find((t) => t.type === 'video');
// If there is both video and audio, seek based on video, but if not then audio is also okay
const firstTrack = firstVideoTrack !== null && firstVideoTrack !== void 0 ? firstVideoTrack : tracks.find((t) => t.type === 'audio');
if (!firstTrack) {
throw new Error('no video and no audio tracks');
}
const moov = (0, traversal_1.getMoovBoxFromState)({
structureState: structure,
isoState,
mp4HeaderSegment,
mayUsePrecomputed: true,
});
if (!moov) {
throw new Error('No moov atom found');
}
const trakBox = (0, traversal_1.getTrakBoxByTrackId)(moov, firstTrack.trackId);
if (!trakBox) {
throw new Error('No trak box found');
}
const tkhdBox = (0, traversal_1.getTkhdBox)(trakBox);
if (!tkhdBox) {
throw new Error('Expected tkhd box in trak box');
}
const isComplete = (0, are_samples_complete_1.areSamplesComplete)({
moofBoxes: info.moofBoxes,
tfraBoxes: info.tfraBoxes,
});
const { samplePositions: samplePositionsArray } = (0, collect_sample_positions_from_moof_boxes_1.collectSamplePositionsFromMoofBoxes)({
moofBoxes: info.moofBoxes,
tkhdBox,
isComplete,
trexBoxes: (0, traversal_1.getTrexBoxes)(moov),
});
log_1.Log.trace(logLevel, 'Fragmented MP4 - Checking if we have seeking info for this time range');
for (const positions of samplePositionsArray) {
const { min, max } = (0, get_sample_position_bounds_1.getSamplePositionBounds)(positions.samples, firstTrack.originalTimescale);
if (min <= time &&
(positions.isLastFragment || isLastChunkInPlaylist || time <= max)) {
log_1.Log.trace(logLevel, `Fragmented MP4 - Found that we have seeking info for this time range: ${min} <= ${time} <= ${max}`);
const kf = (0, find_keyframe_before_time_1.findKeyframeBeforeTime)({
samplePositions: positions.samples,
time,
timescale: firstTrack.originalTimescale,
logLevel,
mediaSections: info.mediaSections,
startInSeconds: firstTrack.startInSeconds,
});
if (kf) {
return {
type: 'do-seek',
byte: kf.offset,
timeInSeconds: Math.min(kf.decodingTimestamp, kf.timestamp) /
firstTrack.originalTimescale,
};
}
}
}
const atom = await (info.mfraAlreadyLoaded
? Promise.resolve(info.mfraAlreadyLoaded)
: isoState.mfra.triggerLoad());
if (atom) {
const moofOffset = (0, find_best_segment_from_tfra_1.findBestSegmentFromTfra)({
mfra: atom,
time,
firstTrack,
timescale: firstTrack.originalTimescale,
});
if (moofOffset !== null &&
!(moofOffset.start <= currentPosition && currentPosition < moofOffset.end)) {
log_1.Log.verbose(logLevel, `Fragmented MP4 - Found based on mfra information that we should seek to: ${moofOffset.start} ${moofOffset.end}`);
return {
type: 'intermediary-seek',
byte: moofOffset.start,
};
}
}
log_1.Log.trace(logLevel, 'Fragmented MP4 - No seeking info found for this time range.');
if ((0, video_section_1.isByteInMediaSection)({
position: currentPosition,
mediaSections: info.mediaSections,
}) !== 'in-section') {
return {
type: 'valid-but-must-wait',
};
}
log_1.Log.trace(logLevel, 'Fragmented MP4 - Inside the wrong video section, skipping to the end of the section');
const mediaSection = (0, video_section_1.getCurrentMediaSection)({
offset: currentPosition,
mediaSections: info.mediaSections,
});
if (!mediaSection) {
throw new Error('No video section defined');
}
return {
type: 'intermediary-seek',
byte: mediaSection.start + mediaSection.size,
};
};
exports.getSeekingByteFromFragmentedMp4 = getSeekingByteFromFragmentedMp4;
@@ -0,0 +1,15 @@
import type { MediaParserLogLevel } from '../../log';
import type { M3uPlaylistContext } from '../../options';
import type { IsoBaseMediaSeekingHints } from '../../seeking-hints';
import type { IsoBaseMediaState } from '../../state/iso-base-media/iso-state';
import type { StructureState } from '../../state/structure';
import type { SeekResolution } from '../../work-on-seek-request';
export declare const getSeekingByteFromIsoBaseMedia: ({ info, time, logLevel, currentPosition, isoState, m3uPlaylistContext, structure, }: {
info: IsoBaseMediaSeekingHints;
time: number;
logLevel: MediaParserLogLevel;
currentPosition: number;
isoState: IsoBaseMediaState;
m3uPlaylistContext: M3uPlaylistContext | null;
structure: StructureState;
}) => Promise<SeekResolution>;
@@ -0,0 +1,70 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getSeekingByteFromIsoBaseMedia = void 0;
const get_tracks_1 = require("../../get-tracks");
const log_1 = require("../../log");
const find_keyframe_before_time_1 = require("./find-keyframe-before-time");
const find_track_to_seek_1 = require("./find-track-to-seek");
const get_seeking_byte_from_fragmented_mp4_1 = require("./get-seeking-byte-from-fragmented-mp4");
const traversal_1 = require("./traversal");
const getSeekingByteFromIsoBaseMedia = ({ info, time, logLevel, currentPosition, isoState, m3uPlaylistContext, structure, }) => {
var _a, _b, _c;
const tracks = (0, get_tracks_1.getTracksFromIsoBaseMedia)({
isoState,
m3uPlaylistContext,
structure,
mayUsePrecomputed: false,
});
const hasMoov = Boolean((0, traversal_1.getMoovBoxFromState)({
structureState: structure,
isoState,
mayUsePrecomputed: false,
mp4HeaderSegment: (_a = m3uPlaylistContext === null || m3uPlaylistContext === void 0 ? void 0 : m3uPlaylistContext.mp4HeaderSegment) !== null && _a !== void 0 ? _a : null,
}));
if (!hasMoov) {
log_1.Log.trace(logLevel, 'No moov box found, must wait');
return Promise.resolve({
type: 'valid-but-must-wait',
});
}
if (info.moofBoxes.length > 0) {
return (0, get_seeking_byte_from_fragmented_mp4_1.getSeekingByteFromFragmentedMp4)({
info,
time,
logLevel,
currentPosition,
isoState,
tracks,
isLastChunkInPlaylist: (_b = m3uPlaylistContext === null || m3uPlaylistContext === void 0 ? void 0 : m3uPlaylistContext.isLastChunkInPlaylist) !== null && _b !== void 0 ? _b : false,
structure,
mp4HeaderSegment: (_c = m3uPlaylistContext === null || m3uPlaylistContext === void 0 ? void 0 : m3uPlaylistContext.mp4HeaderSegment) !== null && _c !== void 0 ? _c : null,
});
}
const trackWithSamplePositions = (0, find_track_to_seek_1.findTrackToSeek)(tracks, structure);
if (!trackWithSamplePositions) {
return Promise.resolve({
type: 'valid-but-must-wait',
});
}
const { track, samplePositions } = trackWithSamplePositions;
const keyframe = (0, find_keyframe_before_time_1.findKeyframeBeforeTime)({
samplePositions,
time,
timescale: track.originalTimescale,
logLevel,
mediaSections: info.mediaSections,
startInSeconds: track.startInSeconds,
});
if (keyframe) {
return Promise.resolve({
type: 'do-seek',
byte: keyframe.offset,
timeInSeconds: Math.min(keyframe.decodingTimestamp, keyframe.timestamp) /
track.originalTimescale,
});
}
return Promise.resolve({
type: 'invalid',
});
};
exports.getSeekingByteFromIsoBaseMedia = getSeekingByteFromIsoBaseMedia;
@@ -0,0 +1,2 @@
import type { TrakBox } from './trak/trak';
export declare const getVideoCodecFromIsoTrak: (trakBox: TrakBox) => "h264" | "vp9" | "av1" | "h265" | "prores";
@@ -0,0 +1,58 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getVideoCodecFromIsoTrak = void 0;
const traversal_1 = require("./traversal");
const getVideoCodecFromIsoTrak = (trakBox) => {
const stsdBox = (0, traversal_1.getStsdBox)(trakBox);
if (stsdBox && stsdBox.type === 'stsd-box') {
const videoSample = stsdBox.samples.find((s) => s.type === 'video');
if (videoSample && videoSample.type === 'video') {
if (videoSample.format === 'hvc1' || videoSample.format === 'hev1') {
return 'h265';
}
if (videoSample.format === 'avc1') {
return 'h264';
}
if (videoSample.format === 'av01') {
return 'av1';
}
if (videoSample.format === 'vp09') {
return 'vp9';
}
// ap4h: ProRes 4444
if (videoSample.format === 'ap4h') {
return 'prores';
}
// ap4x: ap4x: ProRes 4444 XQ
if (videoSample.format === 'ap4x') {
return 'prores';
}
// apch: ProRes 422 High Quality
if (videoSample.format === 'apch') {
return 'prores';
}
// apcn: ProRes 422 Standard Definition
if (videoSample.format === 'apcn') {
return 'prores';
}
// apcs: ProRes 422 LT
if (videoSample.format === 'apcs') {
return 'prores';
}
// apco: ProRes 422 Proxy
if (videoSample.format === 'apco') {
return 'prores';
}
// aprh: ProRes RAW High Quality
if (videoSample.format === 'aprh') {
return 'prores';
}
// aprn: ProRes RAW Standard Definition
if (videoSample.format === 'aprn') {
return 'prores';
}
}
}
throw new Error('Could not find video codec');
};
exports.getVideoCodecFromIsoTrak = getVideoCodecFromIsoTrak;

Some files were not shown because too many files have changed in this diff Show More