mediabunny
Version:
Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.
62 lines (53 loc) • 1.97 kB
text/typescript
/*!
* Copyright (c) 2026-present, Vanilagy and contributors
*
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
*/
import { MP3_FRAME_HEADER_SIZE, getMp3ChannelCount, Mp3FrameHeader, readMp3FrameHeader } from '../../shared/mp3-misc';
import { Reader, readU32Be } from '../reader';
export const readNextMp3FrameHeader = async (
reader: Reader,
startPos: number,
until: number | null,
ref: Mp3FrameHeader | null = null,
): Promise<{
header: Mp3FrameHeader;
startPos: number;
} | null> => {
const CHUNK_SIZE = 2 ** 16; // So we don't need to grab thousands of slices
let currentPos = startPos;
while (until === null || currentPos < until) {
const maxLength = until !== null
? Math.min(CHUNK_SIZE, until - currentPos)
: CHUNK_SIZE;
let slice = reader.requestSliceRange(currentPos, MP3_FRAME_HEADER_SIZE, maxLength);
if (slice instanceof Promise) slice = await slice;
if (!slice || slice.length < MP3_FRAME_HEADER_SIZE) break;
while (slice.remainingLength >= MP3_FRAME_HEADER_SIZE) {
const posBeforeRead = slice.filePos;
const word = readU32Be(slice);
const remainingBytes = reader.fileSize !== null
? reader.fileSize - currentPos
: null;
const result = readMp3FrameHeader(word, remainingBytes);
if (
result.header
&& (!ref || (
// This condition helps us recover malformed streams
// https://stackoverflow.com/a/20884944
result.header.sampleRate === ref.sampleRate
&& result.header.mpegVersionId === ref.mpegVersionId
&& result.header.layer === ref.layer
&& getMp3ChannelCount(result.header.channel) === getMp3ChannelCount(ref.channel)
))
) {
return { header: result.header, startPos: currentPos };
}
slice.filePos = posBeforeRead + result.bytesAdvanced;
currentPos = slice.filePos;
}
}
return null;
};