refactor(audio): extract IFormatDecoder/WavFormatDecoder and wire Content-Type to JS format selection
StreamDecoder is now format-agnostic; WavFormatDecoder delegates to WavUtils; contentType flows C# to JS.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
/**
|
||||
* 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).
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
getAlignedSegmentSize(
|
||||
info: FormatInfo,
|
||||
availableBytes: number,
|
||||
requestedSize: number,
|
||||
streamComplete: boolean
|
||||
): 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;
|
||||
}
|
||||
Reference in New Issue
Block a user