mediabunny
Version:
Pure TypeScript media toolkit for reading, writing, and converting media files, directly in the browser.
144 lines (115 loc) • 3.38 kB
text/typescript
/*!
* Copyright (c) 2025-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 { Reader } from '../reader';
import { OGGS } from './ogg-misc';
export const MIN_PAGE_HEADER_SIZE = 27;
export const MAX_PAGE_HEADER_SIZE = 27 + 255;
export const MAX_PAGE_SIZE = MAX_PAGE_HEADER_SIZE + 255 * 255;
export type Page = {
headerStartPos: number;
totalSize: number;
dataStartPos: number;
dataSize: number;
headerType: number;
granulePosition: number;
serialNumber: number;
sequenceNumber: number;
checksum: number;
lacingValues: Uint8Array;
};
export class OggReader {
pos = 0;
constructor(public reader: Reader) {}
readBytes(length: number) {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
this.pos += length;
return new Uint8Array(view.buffer, offset, length);
}
readU8() {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 1);
this.pos += 1;
return view.getUint8(offset);
}
readU32() {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
this.pos += 4;
return view.getUint32(offset, true);
}
readI32() {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + 4);
this.pos += 4;
return view.getInt32(offset, true);
}
readI64() {
const low = this.readU32();
const high = this.readI32();
return high * 0x100000000 + low;
}
readAscii(length: number) {
const { view, offset } = this.reader.getViewAndOffset(this.pos, this.pos + length);
this.pos += length;
let str = '';
for (let i = 0; i < length; i++) {
str += String.fromCharCode(view.getUint8(offset + i));
}
return str;
}
readPageHeader(): Page | null {
const startPos = this.pos;
const capturePattern = this.readU32();
if (capturePattern !== OGGS) {
return null;
}
this.pos += 1; // Version
const headerType = this.readU8();
const granulePosition = this.readI64();
const serialNumber = this.readU32();
const sequenceNumber = this.readU32();
const checksum = this.readU32();
const numberPageSegments = this.readU8();
const lacingValues = new Uint8Array(numberPageSegments);
for (let i = 0; i < numberPageSegments; i++) {
lacingValues[i] = this.readU8();
}
const headerSize = 27 + numberPageSegments;
const dataSize = lacingValues.reduce((a, b) => a + b, 0);
const totalSize = headerSize + dataSize;
return {
headerStartPos: startPos,
totalSize,
dataStartPos: startPos + headerSize,
dataSize,
headerType,
granulePosition,
serialNumber,
sequenceNumber,
checksum,
lacingValues,
};
}
findNextPageHeader(until: number) {
while (this.pos < until - (4 - 1)) { // Size of word minus 1
const word = this.readU32();
const firstByte = word & 0xff;
const secondByte = (word >>> 8) & 0xff;
const thirdByte = (word >>> 16) & 0xff;
const fourthByte = (word >>> 24) & 0xff;
const O = 0x4f; // 'O'
if (firstByte !== O && secondByte !== O && thirdByte !== O && fourthByte !== O) {
continue;
}
this.pos -= 4;
if (word === OGGS) {
// We have found the capture pattern
return true;
}
this.pos += 1;
}
return false;
}
}