109 lines
4.8 KiB
TypeScript
109 lines
4.8 KiB
TypeScript
/**
|
|
* FormatInfo: parsed header data needed to stream and seek an audio file.
|
|
* Populated by IFormatDecoder.tryParseHeader; used by StreamDecoder throughout playback.
|
|
*/
|
|
export interface FormatInfo {
|
|
/** Samples per second (e.g. 44100). */
|
|
sampleRate: number;
|
|
/** Number of audio channels. */
|
|
channels: number;
|
|
/**
|
|
* Nominal bit depth — 16 for MP3 (conventional), 16/24/32 for WAV/FLAC.
|
|
* Used for display; decoders handle the actual sample format internally.
|
|
*/
|
|
bitsPerSample: number;
|
|
/**
|
|
* Average bytes per second. Used for CBR byte-offset estimation.
|
|
* For WAV: exact (sampleRate * blockAlign).
|
|
* For MP3 CBR: bitrate_kbps * 125.
|
|
* For FLAC: approximate (fileSize / duration).
|
|
*/
|
|
byteRate: number;
|
|
/**
|
|
* For WAV: PCM frame size in bytes (channels * bitsPerSample / 8).
|
|
* For MP3: frame size in bytes (constant for CBR, 0 for VBR with TOC).
|
|
* For FLAC: 0 (frame sizes vary; use sync scan instead).
|
|
* Used by getAlignedSegmentSize to round to clean frame boundaries.
|
|
*/
|
|
blockAlign: number;
|
|
/** Total duration in seconds, from the header (null if unavailable). */
|
|
totalDuration: number | null;
|
|
/** Byte offset where audio frames begin in the original file. */
|
|
audioDataOffset: number;
|
|
/**
|
|
* Format-specific accelerator for seek-beyond-buffer byte calculation.
|
|
* WAV: null (uses byteRate/blockAlign directly).
|
|
* MP3 VBR: Xing/VBRI TOC (100-entry Uint8Array, values are file-percentage * 255).
|
|
* FLAC: SeekTable (array of {sampleNumber: number, streamOffset: number} — stream_offset
|
|
* is bytes from the start of audio frames, i.e. after all metadata blocks).
|
|
* Opus does NOT flow through this seam — it uses the WebCodecs IStreamingDecoder path and resolves
|
|
* seek offsets via OpusSidecar.resolveOpusByteOffset, not FormatInfo.seekData.
|
|
*/
|
|
seekData?: Mp3VbrSeekData | FlacSeekData | null;
|
|
}
|
|
|
|
export interface Mp3VbrSeekData {
|
|
kind: 'mp3-vbr';
|
|
toc: Uint8Array; // 100 entries; toc[i] = file-byte-fraction at i% of duration
|
|
totalBytes: number; // total audio bytes (from Xing header)
|
|
}
|
|
|
|
export interface FlacSeekData {
|
|
kind: 'flac-seektable';
|
|
points: Array<{ sampleNumber: number; streamOffset: number }>;
|
|
streamInfoBytes: Uint8Array; // raw STREAMINFO metadata block for segment wrapping
|
|
metadataBlocksSize: number; // total bytes of all metadata blocks (for stream_offset relative math)
|
|
}
|
|
|
|
/**
|
|
* IFormatDecoder: per-format strategy for header parsing, segment boundary detection,
|
|
* segment wrapping, and seek offset calculation.
|
|
*
|
|
* Implementations: WavFormatDecoder (Wave 1), Mp3FormatDecoder (Wave 2), FlacFormatDecoder (Wave 2).
|
|
*/
|
|
export interface IFormatDecoder {
|
|
/**
|
|
* Attempt to parse the header from accumulated bytes. Returns null if more bytes are needed.
|
|
* Called with growing chunks array until it succeeds or exceeds MAX_HEADER_SEARCH_BYTES.
|
|
*/
|
|
tryParseHeader(chunks: Uint8Array[], totalSize: number): FormatInfo | null;
|
|
|
|
/**
|
|
* Return the largest decodable byte count ≤ requestedSize that ends on a clean frame/block
|
|
* boundary in the audio data (post-header). Returns 0 if not enough data yet.
|
|
* @param info - the parsed FormatInfo
|
|
* @param availableBytes - bytes available starting at the current processedBytes position
|
|
* @param requestedSize - maximum desired segment size
|
|
* @param streamComplete - true when the stream has ended (allows draining the tail)
|
|
* @param rawData - optional; the first `Math.min(requestedSize, availableBytes)` raw audio
|
|
* bytes (starting at the current processedBytes position in the stream), made available
|
|
* for format-specific frame-sync scanning. WAV and MP3 decoders ignore this parameter;
|
|
* FLAC and similar variable-frame formats use it to find the last clean frame boundary
|
|
* within the candidate window.
|
|
*/
|
|
getAlignedSegmentSize(
|
|
info: FormatInfo,
|
|
availableBytes: number,
|
|
requestedSize: number,
|
|
streamComplete: boolean,
|
|
rawData?: Uint8Array
|
|
): number;
|
|
|
|
/**
|
|
* Wrap raw audio bytes in the minimal decodable container for decodeAudioData.
|
|
* WAV: prepend a 44-byte standard PCM header.
|
|
* MP3: pass through unchanged (raw frames are self-contained).
|
|
* FLAC: prepend fLaC marker + STREAMINFO metadata block.
|
|
*/
|
|
wrapSegment(info: FormatInfo, rawBytes: Uint8Array): Uint8Array;
|
|
|
|
/**
|
|
* Calculate the file-absolute byte offset for a seek-beyond-buffer Range request.
|
|
* The returned value includes the header offset (result ≥ info.audioDataOffset).
|
|
* WAV: exact PCM frame alignment.
|
|
* MP3 CBR: frame-aligned estimate; MP3 VBR: TOC interpolation.
|
|
* FLAC: SEEKTABLE lookup when available.
|
|
*/
|
|
calculateByteOffset(info: FormatInfo, positionSeconds: number): number;
|
|
}
|