You're Invited:Meet the Socket Team at RSAC and BSidesSF 2026, March 23–26.RSVP
Socket
Book a DemoSign in
Socket

@meframe/core

Package Overview
Dependencies
Maintainers
1
Versions
86
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@meframe/core - npm Package Compare versions

Comparing version
0.2.6
to
0.2.7
+1
-1
dist/stages/demux/MP4IndexParser.d.ts.map

@@ -1,1 +0,1 @@

{"version":3,"file":"MP4IndexParser.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/MP4IndexParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAiD,MAAM,SAAS,CAAC;AAKvF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,QAAQ,CAAC;IAChB,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,KAAK,IAAI,CAAC;CACpF;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB;;;;OAIG;IACG,eAAe,CACnB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,cAAc,CAAC;YAiLZ,iBAAiB;IA2C/B,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,oBAAoB;IAqC5B;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IAiChE;;OAEG;IACH,OAAO,CAAC,UAAU;IAoBlB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAsCrB;;;OAGG;IACH,OAAO,CAAC,eAAe;CAqExB"}
{"version":3,"file":"MP4IndexParser.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/MP4IndexParser.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAiD,MAAM,SAAS,CAAC;AAKvF,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,QAAQ,CAAC;IAChB,YAAY,CAAC,EAAE,iBAAiB,EAAE,CAAC;IACnC,aAAa,CAAC,EAAE,kBAAkB,CAAC;CACpC;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iBAAiB,CAAC,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,cAAc,EAAE,iBAAiB,EAAE,KAAK,IAAI,CAAC;CACpF;AAED;;;;;;;;;GASG;AACH,qBAAa,cAAc;IACzB;;;;OAIG;IACG,eAAe,CACnB,MAAM,EAAE,cAAc,CAAC,UAAU,CAAC,EAClC,OAAO,CAAC,EAAE,kBAAkB,GAC3B,OAAO,CAAC,cAAc,CAAC;YAwLZ,iBAAiB;IA0C/B,OAAO,CAAC,aAAa;IAcrB,OAAO,CAAC,wBAAwB;IAmDhC,OAAO,CAAC,oBAAoB;IAsC5B;;OAEG;IACG,aAAa,CAAC,IAAI,EAAE,IAAI,GAAG,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC;IAiChE;;OAEG;IACH,OAAO,CAAC,UAAU;IAoBlB;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAiB5B;;OAEG;IACH,OAAO,CAAC,oBAAoB;IAa5B;;;;;OAKG;IACH,OAAO,CAAC,gBAAgB;IAwCxB;;;OAGG;IACH,OAAO,CAAC,mBAAmB;IAI3B;;;OAGG;IACH,OAAO,CAAC,aAAa;IAsCrB;;;OAGG;IACH,OAAO,CAAC,eAAe;CAqExB"}

@@ -62,2 +62,5 @@ import { MP4Box } from "../../utils/mp4box.js";

streamState.audioComplete = isComplete;
if (!isComplete && audioTrackId && audioConfig) {
mp4boxFile.start();
}
if (isComplete) {

@@ -218,7 +221,6 @@ setTimeout(() => {

};
mp4boxFile.setExtractionOptions(trackId, null, {
mp4boxFile.setExtractionOptions(trackId, audioTrack, {
nbSamples: Infinity
// Extract all samples
});
mp4boxFile.start();
return {

@@ -225,0 +227,0 @@ config,

@@ -1,1 +0,1 @@

{"version":3,"file":"MP4IndexParser.js","sources":["../../../src/stages/demux/MP4IndexParser.ts"],"sourcesContent":["import { MP4Box } from '../../utils/mp4box';\nimport type { MP4BoxFile } from 'mp4box';\nimport type { MP4Index, VideoTrackIndex, AudioTrackIndex, Sample, GOP } from './types';\nimport type { TimeUs } from '../../model/types';\nimport { MP4Demuxer, normalizeVideoCodec } from './MP4Demuxer';\nimport { EmptyStreamError } from '../../utils/errors';\n\nexport interface MP4ParseResult {\n index: MP4Index;\n audioSamples?: EncodedAudioChunk[];\n audioMetadata?: AudioDecoderConfig;\n}\n\nexport interface ParseStreamOptions {\n resourceId?: string;\n onFirstFrameReady?: (index: MP4Index, firstGOPChunks: EncodedVideoChunk[]) => void;\n}\n\n/**\n * MP4IndexParser - Parse MP4 moov box and build time→byte index\n *\n * Features:\n * - Stream-based moov parsing (stop after moov found)\n * - Build sample table with byte offsets\n * - Build GOP index for keyframe positions\n * - Extract all audio samples (encoded) for memory caching\n * - Extract first GOP for fast cover rendering\n */\nexport class MP4IndexParser {\n /**\n * Parse from streaming download\n * Returns video index + all audio samples\n * Can optionally extract first GOP for fast cover rendering\n */\n async parseFromStream(\n stream: ReadableStream<Uint8Array>,\n options?: ParseStreamOptions\n ): Promise<MP4ParseResult> {\n const resourceId = options?.resourceId || 'unknown';\n const mp4boxFile = MP4Box.createFile();\n let resolveResult: ((result: MP4ParseResult) => void) | null = null;\n let rejectResult: ((error: Error) => void) | null = null;\n\n const audioChunks: EncodedAudioChunk[] = [];\n let audioConfig: AudioDecoderConfig | undefined;\n let audioTrackId: number | undefined;\n let audioTimescale: number | undefined;\n let expectedAudioSamples = 0;\n let savedInfo: any = null;\n\n const resultPromise = new Promise<MP4ParseResult>((resolve, reject) => {\n resolveResult = resolve;\n rejectResult = reject;\n });\n\n // First GOP extraction state\n const firstGOPState = {\n byteEnd: 0,\n extracted: !options?.onFirstFrameReady,\n index: null as MP4Index | null,\n buffer: new Uint8Array(0),\n };\n\n const streamState = {\n fileOffset: 0,\n moovParsed: false,\n audioComplete: false,\n };\n\n mp4boxFile.onError = (error: string) => {\n rejectResult?.(new Error(`MP4Box error: ${error}`));\n };\n\n mp4boxFile.onReady = (info: any) => {\n try {\n // Set moovParsed immediately\n streamState.moovParsed = true;\n\n // Save info for later use in audio extraction\n savedInfo = info;\n\n // Build video index\n const index = this.buildIndex(mp4boxFile, info);\n firstGOPState.index = index;\n\n // Check if this is a fast-start format (moov at beginning)\n const isFastStart = info.isProgressive === true;\n\n // Only extract first GOP for fast-start videos (moov-at-end videos will read from OPFS)\n if (isFastStart && options?.onFirstFrameReady && index.tracks.video?.gopIndex[0]) {\n const firstGOP = index.tracks.video.gopIndex[0];\n const samples = index.tracks.video.samples;\n const startIdx = firstGOP.keyframeSampleIndex;\n const endIdx = startIdx + firstGOP.sampleCount;\n\n const endSample = samples[endIdx - 1];\n if (endSample) {\n firstGOPState.byteEnd = endSample.byteOffset + endSample.byteLength;\n }\n }\n\n // Setup audio extraction if audio track exists\n const { config, trackId, timescale, expectedSamples, isComplete } =\n this.setupAudioExtraction(mp4boxFile, info);\n audioConfig = config;\n audioTrackId = trackId;\n audioTimescale = timescale;\n expectedAudioSamples = expectedSamples;\n streamState.audioComplete = isComplete;\n\n if (isComplete) {\n // No audio track, resolve immediately if firstGOP extracted or not needed\n // Use setTimeout to allow stack to unwind and ensure consistency\n setTimeout(() => {\n if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {\n resolveResult?.({ index });\n }\n }, 0);\n }\n } catch (error) {\n rejectResult?.(error as Error);\n }\n };\n\n // Handle audio sample extraction\n mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n if (trackId === audioTrackId && audioConfig) {\n // Use track timescale, fallback to sampleRate if timescale not available\n // MP4 sample.cts and sample.duration are in track timescale units\n const timescale = audioTimescale || audioConfig.sampleRate;\n\n // Process samples if any\n if (samples && samples.length > 0) {\n for (const sample of samples) {\n try {\n const chunk = new EncodedAudioChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp: Math.round((sample.cts / timescale) * 1_000_000),\n duration: Math.round((sample.duration / timescale) * 1_000_000),\n data: sample.data,\n });\n audioChunks.push(chunk);\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to create audio chunk:', error);\n }\n }\n }\n\n // Check if we have all samples\n if (expectedAudioSamples !== Infinity && audioChunks.length >= expectedAudioSamples) {\n streamState.audioComplete = true;\n }\n }\n };\n\n await this.processStreamData(\n stream,\n mp4boxFile,\n options,\n firstGOPState,\n streamState,\n resourceId\n );\n\n // Flush remaining data\n mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onReady/onSamples are async)\n // Reference: MP4Demuxer.ts line 302\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // If audio extraction finished normally (or there was no audio), resolve now\n if (streamState.moovParsed && streamState.audioComplete) {\n const index = firstGOPState.index || this.buildIndex(mp4boxFile, savedInfo);\n resolveResult!({\n index,\n audioSamples: audioChunks,\n audioMetadata: audioConfig,\n });\n }\n // If moov is parsed but audio extraction hasn't completed yet, force complete\n else if (streamState.moovParsed && !streamState.audioComplete && savedInfo) {\n streamState.audioComplete = true;\n const index = this.buildIndex(mp4boxFile, savedInfo);\n\n // Check if we have audio chunks\n if (audioChunks.length > 0 && audioConfig) {\n resolveResult!({\n index,\n audioSamples: audioChunks,\n audioMetadata: audioConfig,\n });\n } else {\n // No audio or extraction failed, just return index\n resolveResult!({ index });\n }\n }\n\n // Wait for result with timeout (5s max)\n const parseTimeout = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `MP4Box parsing timeout after reading ${streamState.fileOffset} bytes. ` +\n `moovParsed=${streamState.moovParsed}, audioExtractionComplete=${streamState.audioComplete}`\n )\n );\n }, 5000);\n });\n\n // Wait for either resultPromise or timeout\n return await Promise.race([resultPromise, parseTimeout]);\n }\n\n private async processStreamData(\n stream: ReadableStream<Uint8Array>,\n mp4boxFile: MP4BoxFile,\n options: ParseStreamOptions | undefined,\n firstGOPState: any,\n streamState: any,\n resourceId: string\n ): Promise<void> {\n const reader = stream.getReader();\n\n try {\n let hasData = false;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n if (value) {\n hasData = true;\n const buffer = this.prepareBuffer(value, streamState.fileOffset);\n\n this.handleFirstGOPExtraction(buffer, value, options, firstGOPState, streamState);\n\n mp4boxFile.appendBuffer(buffer);\n streamState.fileOffset += buffer.byteLength;\n\n if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {\n break;\n }\n }\n }\n\n if (!hasData) {\n throw new EmptyStreamError(resourceId, streamState.fileOffset);\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private prepareBuffer(value: Uint8Array, fileOffset: number): ArrayBuffer {\n let buffer: ArrayBuffer;\n if (value.byteOffset === 0 && value.byteLength === value.buffer.byteLength) {\n buffer = value.buffer as ArrayBuffer;\n } else {\n buffer = value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength\n ) as ArrayBuffer;\n }\n (buffer as any).fileStart = fileOffset;\n return buffer;\n }\n\n private handleFirstGOPExtraction(\n buffer: ArrayBuffer,\n value: Uint8Array,\n options: ParseStreamOptions | undefined,\n firstGOPState: any,\n streamState: any\n ): void {\n if (!firstGOPState.extracted && options?.onFirstFrameReady) {\n // Safety: Cap accumulation to 10MB\n if (firstGOPState.buffer.length + value.length < 10 * 1024 * 1024) {\n const newBuffer = new Uint8Array(firstGOPState.buffer.length + value.length);\n newBuffer.set(firstGOPState.buffer);\n newBuffer.set(value, firstGOPState.buffer.length);\n firstGOPState.buffer = newBuffer;\n } else if (!streamState.moovParsed) {\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n }\n\n if (\n streamState.moovParsed &&\n firstGOPState.byteEnd > 0 &&\n firstGOPState.index &&\n streamState.fileOffset + buffer.byteLength >= firstGOPState.byteEnd\n ) {\n try {\n const currentIndex = firstGOPState.index as MP4Index;\n const videoTrack = currentIndex.tracks.video;\n\n if (videoTrack && videoTrack.gopIndex[0]) {\n // buffer accumulated from file start (position 0)\n // sample.byteOffset is absolute offset from file start\n // So byteStart should be 0 to match buffer's starting position\n const chunks = this.extractFirstGOP(firstGOPState.buffer, videoTrack, 0);\n\n if (chunks.length > 0) {\n options.onFirstFrameReady?.(currentIndex, chunks);\n }\n }\n\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to extract first GOP:', error);\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n }\n }\n }\n }\n\n private setupAudioExtraction(\n mp4boxFile: MP4BoxFile,\n info: any\n ): {\n config?: AudioDecoderConfig;\n trackId?: number;\n timescale?: number;\n expectedSamples: number;\n isComplete: boolean;\n } {\n const audioTrack = info.tracks.find((t: any) => t.type === 'audio');\n if (audioTrack) {\n const trackId: number = audioTrack.id;\n const config = {\n codec: audioTrack.codec.startsWith('mp4a') ? 'mp4a.40.2' : audioTrack.codec,\n sampleRate: audioTrack.audio.sample_rate || audioTrack.timescale,\n numberOfChannels: audioTrack.audio.channel_count,\n };\n\n // Extract all audio samples\n mp4boxFile.setExtractionOptions(trackId, null as any, {\n nbSamples: Infinity, // Extract all samples\n });\n mp4boxFile.start();\n\n return {\n config,\n trackId,\n timescale: audioTrack.timescale, // Use track timescale for time calculations\n expectedSamples: audioTrack.nb_samples || Infinity,\n isComplete: false,\n };\n }\n\n return { expectedSamples: 0, isComplete: true };\n }\n\n /**\n * Parse from file/ArrayBuffer (for already cached resources)\n */\n async parseFromFile(data: File | ArrayBuffer): Promise<MP4Index> {\n const mp4boxFile = MP4Box.createFile();\n\n const indexPromise = new Promise<MP4Index>((resolve, reject) => {\n mp4boxFile.onError = (error: string) => {\n reject(new Error(`MP4Box error: ${error}`));\n };\n\n mp4boxFile.onReady = (info: any) => {\n try {\n const index = this.buildIndex(mp4boxFile, info);\n resolve(index);\n } catch (error) {\n reject(error);\n }\n };\n });\n\n // Read file data\n let buffer: ArrayBuffer;\n if (data instanceof File) {\n buffer = await data.arrayBuffer();\n } else {\n buffer = data;\n }\n\n (buffer as any).fileStart = 0;\n mp4boxFile.appendBuffer(buffer);\n mp4boxFile.flush();\n\n return indexPromise;\n }\n\n /**\n * Build MP4Index from mp4box.js parsed data\n */\n private buildIndex(mp4boxFile: MP4BoxFile, info: any): MP4Index {\n const index: MP4Index = {\n resourceId: '', // Will be set by caller\n moovOffset: 0, // mp4box doesn't expose this directly\n moovSize: 0,\n durationUs: (info.duration / info.timescale) * 1_000_000,\n tracks: {},\n };\n\n for (const trackInfo of info.tracks) {\n if (trackInfo.type === 'video') {\n index.tracks.video = this.buildVideoTrackIndex(mp4boxFile, trackInfo);\n } else if (trackInfo.type === 'audio') {\n index.tracks.audio = this.buildAudioTrackIndex(mp4boxFile, trackInfo);\n }\n }\n\n return index;\n }\n\n /**\n * Build video track index with sample table and GOP boundaries\n */\n private buildVideoTrackIndex(mp4boxFile: MP4BoxFile, trackInfo: any): VideoTrackIndex {\n const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);\n const gopIndex = this.buildGOPIndex(samples);\n const description = this.getVideoDescription(mp4boxFile, trackInfo);\n\n return {\n trackId: trackInfo.id,\n codec: normalizeVideoCodec(trackInfo.codec, description),\n width: trackInfo.track_width || trackInfo.video?.width || 0,\n height: trackInfo.track_height || trackInfo.video?.height || 0,\n timescale: trackInfo.timescale,\n description,\n samples,\n gopIndex,\n };\n }\n\n /**\n * Build audio track index\n */\n private buildAudioTrackIndex(mp4boxFile: MP4BoxFile, trackInfo: any): AudioTrackIndex {\n const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);\n\n return {\n trackId: trackInfo.id,\n codec: trackInfo.codec,\n sampleRate: trackInfo.audio?.sample_rate || 48000,\n numberOfChannels: trackInfo.audio?.channel_count || 2,\n timescale: trackInfo.timescale,\n samples,\n };\n }\n\n /**\n * Build sample table from mp4box track samples\n *\n * IMPORTANT: Keep samples in DTS (decode) order, not PTS (presentation) order!\n * VideoDecoder requires chunks in decode order. It will output frames in PTS order.\n */\n private buildSampleTable(mp4boxFile: MP4BoxFile, trackId: number, timescale: number): Sample[] {\n const samples: Sample[] = [];\n\n // Get track box\n const trak = mp4boxFile.getTrackById(trackId);\n if (!trak) return samples;\n\n // Access sample table (already in DTS order)\n const sampleTable = trak.samples || [];\n\n // Calculate PTS from CTS and normalize to start at 0\n let timestampOffset: number | null = null;\n\n for (const sample of sampleTable) {\n const durationUs = ((sample.duration || 0) / timescale) * 1_000_000;\n\n // Use CTS (Composition Time Stamp = PTS) for display timestamp\n const rawTimestampUs = ((sample.cts || 0) / timescale) * 1_000_000;\n\n // Normalize: first frame starts at 0 (like MP4Demuxer does)\n if (timestampOffset === null) {\n timestampOffset = rawTimestampUs;\n }\n const timestampUs = rawTimestampUs - timestampOffset;\n\n samples.push({\n timestamp: timestampUs, // Normalized PTS (display timestamp)\n duration: durationUs,\n byteOffset: sample.offset || 0,\n byteLength: sample.size || 0,\n isKeyframe: sample.is_sync || false,\n });\n }\n\n // DO NOT SORT! Samples must stay in DTS (decode) order for VideoDecoder\n // Decoder will output frames in PTS order automatically\n\n return samples;\n }\n\n /**\n * Extract video description (avcC/hvcC/etc) for VideoDecoder\n * Reuses MP4Demuxer.extractVideoDescription for consistency\n */\n private getVideoDescription(mp4boxFile: MP4BoxFile, trackInfo: any): ArrayBuffer | undefined {\n return MP4Demuxer.extractVideoDescription(mp4boxFile, trackInfo.id);\n }\n\n /**\n * Build GOP index from samples\n * GOP = Group of Pictures, starts with a keyframe\n */\n private buildGOPIndex(samples: Sample[]): GOP[] {\n const gopIndex: GOP[] = [];\n let currentGOP: {\n startTimeUs: TimeUs;\n keyframeSampleIndex: number;\n sampleCount: number;\n } | null = null;\n\n for (let i = 0; i < samples.length; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n if (sample.isKeyframe) {\n // Save previous GOP if exists\n if (currentGOP) {\n gopIndex.push(currentGOP);\n }\n\n // Start new GOP\n currentGOP = {\n startTimeUs: sample.timestamp,\n keyframeSampleIndex: i,\n sampleCount: 1,\n };\n } else if (currentGOP) {\n // Add sample to current GOP\n currentGOP.sampleCount++;\n }\n }\n\n // Save last GOP\n if (currentGOP) {\n gopIndex.push(currentGOP);\n }\n\n return gopIndex;\n }\n\n /**\n * Extract first GOP chunks from accumulated buffer\n * Used for fast cover rendering during streaming download\n */\n private extractFirstGOP(\n buffer: Uint8Array,\n videoTrack: VideoTrackIndex,\n byteStart: number\n ): EncodedVideoChunk[] {\n const chunks: EncodedVideoChunk[] = [];\n const firstGOP = videoTrack.gopIndex[0];\n if (!firstGOP) {\n return chunks;\n }\n\n const samples = videoTrack.samples;\n\n for (let i = 0; i < firstGOP.sampleCount; i++) {\n const sampleIdx = firstGOP.keyframeSampleIndex + i;\n const sample = samples[sampleIdx];\n if (!sample) continue;\n\n const relativeOffset = sample.byteOffset - byteStart;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > buffer.length) {\n // Critical: If first sample (keyframe) is not fully downloaded, return empty array\n // to avoid decoding error \"A key frame is required after configure()\"\n if (i === 0) {\n console.warn(\n '[MP4IndexParser] First GOP keyframe not fully downloaded, skipping cover decode'\n );\n return []; // Return empty array to avoid sending non-keyframe as first chunk\n }\n\n console.warn('[MP4IndexParser] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n byteStart,\n relativeOffset,\n bufferLength: buffer.length,\n isKeyframe: sample.isKeyframe,\n sampleIndex: i,\n });\n\n // If not first sample, safe to skip (keyframe already present)\n continue;\n }\n\n const sampleData = buffer.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n try {\n chunks.push(\n new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n })\n );\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to create EncodedVideoChunk:', error);\n }\n }\n\n // Additional safety check: ensure first chunk is keyframe\n if (chunks.length > 0 && chunks[0]?.type !== 'key') {\n console.error('[MP4IndexParser] First chunk is not a keyframe, discarding all chunks');\n return [];\n }\n\n return chunks;\n }\n}\n"],"names":[],"mappings":"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"}
{"version":3,"file":"MP4IndexParser.js","sources":["../../../src/stages/demux/MP4IndexParser.ts"],"sourcesContent":["import { MP4Box } from '../../utils/mp4box';\nimport type { MP4BoxFile } from 'mp4box';\nimport type { MP4Index, VideoTrackIndex, AudioTrackIndex, Sample, GOP } from './types';\nimport type { TimeUs } from '../../model/types';\nimport { MP4Demuxer, normalizeVideoCodec } from './MP4Demuxer';\nimport { EmptyStreamError } from '../../utils/errors';\n\nexport interface MP4ParseResult {\n index: MP4Index;\n audioSamples?: EncodedAudioChunk[];\n audioMetadata?: AudioDecoderConfig;\n}\n\nexport interface ParseStreamOptions {\n resourceId?: string;\n onFirstFrameReady?: (index: MP4Index, firstGOPChunks: EncodedVideoChunk[]) => void;\n}\n\n/**\n * MP4IndexParser - Parse MP4 moov box and build time→byte index\n *\n * Features:\n * - Stream-based moov parsing (stop after moov found)\n * - Build sample table with byte offsets\n * - Build GOP index for keyframe positions\n * - Extract all audio samples (encoded) for memory caching\n * - Extract first GOP for fast cover rendering\n */\nexport class MP4IndexParser {\n /**\n * Parse from streaming download\n * Returns video index + all audio samples\n * Can optionally extract first GOP for fast cover rendering\n */\n async parseFromStream(\n stream: ReadableStream<Uint8Array>,\n options?: ParseStreamOptions\n ): Promise<MP4ParseResult> {\n const resourceId = options?.resourceId || 'unknown';\n const mp4boxFile = MP4Box.createFile();\n let resolveResult: ((result: MP4ParseResult) => void) | null = null;\n let rejectResult: ((error: Error) => void) | null = null;\n\n const audioChunks: EncodedAudioChunk[] = [];\n let audioConfig: AudioDecoderConfig | undefined;\n let audioTrackId: number | undefined;\n let audioTimescale: number | undefined;\n let expectedAudioSamples = 0;\n let savedInfo: any = null;\n\n const resultPromise = new Promise<MP4ParseResult>((resolve, reject) => {\n resolveResult = resolve;\n rejectResult = reject;\n });\n\n // First GOP extraction state\n const firstGOPState = {\n byteEnd: 0,\n extracted: !options?.onFirstFrameReady,\n index: null as MP4Index | null,\n buffer: new Uint8Array(0),\n };\n\n const streamState = {\n fileOffset: 0,\n moovParsed: false,\n audioComplete: false,\n };\n\n mp4boxFile.onError = (error: string) => {\n rejectResult?.(new Error(`MP4Box error: ${error}`));\n };\n\n mp4boxFile.onReady = (info: any) => {\n try {\n // Set moovParsed immediately\n streamState.moovParsed = true;\n\n // Save info for later use in audio extraction\n savedInfo = info;\n\n // Build video index\n const index = this.buildIndex(mp4boxFile, info);\n firstGOPState.index = index;\n\n // Check if this is a fast-start format (moov at beginning)\n const isFastStart = info.isProgressive === true;\n\n // Only extract first GOP for fast-start videos (moov-at-end videos will read from OPFS)\n if (isFastStart && options?.onFirstFrameReady && index.tracks.video?.gopIndex[0]) {\n const firstGOP = index.tracks.video.gopIndex[0];\n const samples = index.tracks.video.samples;\n const startIdx = firstGOP.keyframeSampleIndex;\n const endIdx = startIdx + firstGOP.sampleCount;\n\n const endSample = samples[endIdx - 1];\n if (endSample) {\n firstGOPState.byteEnd = endSample.byteOffset + endSample.byteLength;\n }\n }\n\n // Setup audio extraction if audio track exists.\n // IMPORTANT: mp4boxFile.start() may synchronously trigger onSamples.\n // We must assign audioTrackId/audioConfig first, otherwise the onSamples handler\n // (which guards by audioTrackId && audioConfig) may drop all samples.\n const { config, trackId, timescale, expectedSamples, isComplete } =\n this.setupAudioExtraction(mp4boxFile, info);\n audioConfig = config;\n audioTrackId = trackId;\n audioTimescale = timescale;\n expectedAudioSamples = expectedSamples;\n streamState.audioComplete = isComplete;\n\n if (!isComplete && audioTrackId && audioConfig) {\n mp4boxFile.start();\n }\n\n if (isComplete) {\n // No audio track, resolve immediately if firstGOP extracted or not needed\n // Use setTimeout to allow stack to unwind and ensure consistency\n setTimeout(() => {\n if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {\n resolveResult?.({ index });\n }\n }, 0);\n }\n } catch (error) {\n rejectResult?.(error as Error);\n }\n };\n\n // Handle audio sample extraction\n mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n if (trackId === audioTrackId && audioConfig) {\n // Use track timescale, fallback to sampleRate if timescale not available\n // MP4 sample.cts and sample.duration are in track timescale units\n const timescale = audioTimescale || audioConfig.sampleRate;\n\n // Process samples if any\n if (samples && samples.length > 0) {\n for (const sample of samples) {\n try {\n const chunk = new EncodedAudioChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp: Math.round((sample.cts / timescale) * 1_000_000),\n duration: Math.round((sample.duration / timescale) * 1_000_000),\n data: sample.data,\n });\n audioChunks.push(chunk);\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to create audio chunk:', error);\n }\n }\n }\n\n // Check if we have all samples\n if (expectedAudioSamples !== Infinity && audioChunks.length >= expectedAudioSamples) {\n streamState.audioComplete = true;\n }\n }\n };\n\n await this.processStreamData(\n stream,\n mp4boxFile,\n options,\n firstGOPState,\n streamState,\n resourceId\n );\n\n // Flush remaining data\n mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onReady/onSamples are async)\n // Reference: MP4Demuxer.ts line 302\n await new Promise((resolve) => setTimeout(resolve, 100));\n\n // If audio extraction finished normally (or there was no audio), resolve now\n if (streamState.moovParsed && streamState.audioComplete) {\n const index = firstGOPState.index || this.buildIndex(mp4boxFile, savedInfo);\n resolveResult!({\n index,\n audioSamples: audioChunks,\n audioMetadata: audioConfig,\n });\n }\n // If moov is parsed but audio extraction hasn't completed yet, force complete\n else if (streamState.moovParsed && !streamState.audioComplete && savedInfo) {\n streamState.audioComplete = true;\n const index = this.buildIndex(mp4boxFile, savedInfo);\n\n // Check if we have audio chunks\n if (audioChunks.length > 0 && audioConfig) {\n resolveResult!({\n index,\n audioSamples: audioChunks,\n audioMetadata: audioConfig,\n });\n } else {\n // No audio or extraction failed, just return index\n resolveResult!({ index });\n }\n }\n\n // Wait for result with timeout (5s max)\n const parseTimeout = new Promise<never>((_, reject) => {\n setTimeout(() => {\n reject(\n new Error(\n `MP4Box parsing timeout after reading ${streamState.fileOffset} bytes. ` +\n `moovParsed=${streamState.moovParsed}, audioExtractionComplete=${streamState.audioComplete}`\n )\n );\n }, 5000);\n });\n\n // Wait for either resultPromise or timeout\n return await Promise.race([resultPromise, parseTimeout]);\n }\n\n private async processStreamData(\n stream: ReadableStream<Uint8Array>,\n mp4boxFile: MP4BoxFile,\n options: ParseStreamOptions | undefined,\n firstGOPState: any,\n streamState: any,\n resourceId: string\n ): Promise<void> {\n const reader = stream.getReader();\n\n try {\n let hasData = false;\n\n while (true) {\n const { done, value } = await reader.read();\n\n if (done) {\n break;\n }\n\n if (value) {\n hasData = true;\n const buffer = this.prepareBuffer(value, streamState.fileOffset);\n\n this.handleFirstGOPExtraction(buffer, value, options, firstGOPState, streamState);\n\n mp4boxFile.appendBuffer(buffer);\n streamState.fileOffset += buffer.byteLength;\n if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {\n break;\n }\n }\n }\n\n if (!hasData) {\n throw new EmptyStreamError(resourceId, streamState.fileOffset);\n }\n } finally {\n reader.releaseLock();\n }\n }\n\n private prepareBuffer(value: Uint8Array, fileOffset: number): ArrayBuffer {\n let buffer: ArrayBuffer;\n if (value.byteOffset === 0 && value.byteLength === value.buffer.byteLength) {\n buffer = value.buffer as ArrayBuffer;\n } else {\n buffer = value.buffer.slice(\n value.byteOffset,\n value.byteOffset + value.byteLength\n ) as ArrayBuffer;\n }\n (buffer as any).fileStart = fileOffset;\n return buffer;\n }\n\n private handleFirstGOPExtraction(\n buffer: ArrayBuffer,\n value: Uint8Array,\n options: ParseStreamOptions | undefined,\n firstGOPState: any,\n streamState: any\n ): void {\n if (!firstGOPState.extracted && options?.onFirstFrameReady) {\n // Safety: Cap accumulation to 10MB\n if (firstGOPState.buffer.length + value.length < 10 * 1024 * 1024) {\n const newBuffer = new Uint8Array(firstGOPState.buffer.length + value.length);\n newBuffer.set(firstGOPState.buffer);\n newBuffer.set(value, firstGOPState.buffer.length);\n firstGOPState.buffer = newBuffer;\n } else if (!streamState.moovParsed) {\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n }\n\n if (\n streamState.moovParsed &&\n firstGOPState.byteEnd > 0 &&\n firstGOPState.index &&\n streamState.fileOffset + buffer.byteLength >= firstGOPState.byteEnd\n ) {\n try {\n const currentIndex = firstGOPState.index as MP4Index;\n const videoTrack = currentIndex.tracks.video;\n\n if (videoTrack && videoTrack.gopIndex[0]) {\n // buffer accumulated from file start (position 0)\n // sample.byteOffset is absolute offset from file start\n // So byteStart should be 0 to match buffer's starting position\n const chunks = this.extractFirstGOP(firstGOPState.buffer, videoTrack, 0);\n\n if (chunks.length > 0) {\n options.onFirstFrameReady?.(currentIndex, chunks);\n }\n }\n\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to extract first GOP:', error);\n firstGOPState.extracted = true;\n firstGOPState.buffer = new Uint8Array(0);\n }\n }\n }\n }\n\n private setupAudioExtraction(\n mp4boxFile: MP4BoxFile,\n info: any\n ): {\n config?: AudioDecoderConfig;\n trackId?: number;\n timescale?: number;\n expectedSamples: number;\n isComplete: boolean;\n } {\n const audioTrack = info.tracks.find((t: any) => t.type === 'audio');\n if (audioTrack) {\n const trackId: number = audioTrack.id;\n const config = {\n codec: audioTrack.codec.startsWith('mp4a') ? 'mp4a.40.2' : audioTrack.codec,\n sampleRate: audioTrack.audio.sample_rate || audioTrack.timescale,\n numberOfChannels: audioTrack.audio.channel_count,\n };\n\n // Extract all audio samples.\n // mp4box.js expects the track info object as the \"user\" parameter for reliable extraction.\n // Passing null can result in missing onSamples callbacks for some files.\n mp4boxFile.setExtractionOptions(trackId, audioTrack, {\n nbSamples: Infinity, // Extract all samples\n });\n\n return {\n config,\n trackId,\n timescale: audioTrack.timescale, // Use track timescale for time calculations\n expectedSamples: audioTrack.nb_samples || Infinity,\n isComplete: false,\n };\n }\n\n return { expectedSamples: 0, isComplete: true };\n }\n\n /**\n * Parse from file/ArrayBuffer (for already cached resources)\n */\n async parseFromFile(data: File | ArrayBuffer): Promise<MP4Index> {\n const mp4boxFile = MP4Box.createFile();\n\n const indexPromise = new Promise<MP4Index>((resolve, reject) => {\n mp4boxFile.onError = (error: string) => {\n reject(new Error(`MP4Box error: ${error}`));\n };\n\n mp4boxFile.onReady = (info: any) => {\n try {\n const index = this.buildIndex(mp4boxFile, info);\n resolve(index);\n } catch (error) {\n reject(error);\n }\n };\n });\n\n // Read file data\n let buffer: ArrayBuffer;\n if (data instanceof File) {\n buffer = await data.arrayBuffer();\n } else {\n buffer = data;\n }\n\n (buffer as any).fileStart = 0;\n mp4boxFile.appendBuffer(buffer);\n mp4boxFile.flush();\n\n return indexPromise;\n }\n\n /**\n * Build MP4Index from mp4box.js parsed data\n */\n private buildIndex(mp4boxFile: MP4BoxFile, info: any): MP4Index {\n const index: MP4Index = {\n resourceId: '', // Will be set by caller\n moovOffset: 0, // mp4box doesn't expose this directly\n moovSize: 0,\n durationUs: (info.duration / info.timescale) * 1_000_000,\n tracks: {},\n };\n\n for (const trackInfo of info.tracks) {\n if (trackInfo.type === 'video') {\n index.tracks.video = this.buildVideoTrackIndex(mp4boxFile, trackInfo);\n } else if (trackInfo.type === 'audio') {\n index.tracks.audio = this.buildAudioTrackIndex(mp4boxFile, trackInfo);\n }\n }\n\n return index;\n }\n\n /**\n * Build video track index with sample table and GOP boundaries\n */\n private buildVideoTrackIndex(mp4boxFile: MP4BoxFile, trackInfo: any): VideoTrackIndex {\n const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);\n const gopIndex = this.buildGOPIndex(samples);\n const description = this.getVideoDescription(mp4boxFile, trackInfo);\n\n return {\n trackId: trackInfo.id,\n codec: normalizeVideoCodec(trackInfo.codec, description),\n width: trackInfo.track_width || trackInfo.video?.width || 0,\n height: trackInfo.track_height || trackInfo.video?.height || 0,\n timescale: trackInfo.timescale,\n description,\n samples,\n gopIndex,\n };\n }\n\n /**\n * Build audio track index\n */\n private buildAudioTrackIndex(mp4boxFile: MP4BoxFile, trackInfo: any): AudioTrackIndex {\n const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);\n\n return {\n trackId: trackInfo.id,\n codec: trackInfo.codec,\n sampleRate: trackInfo.audio?.sample_rate || 48000,\n numberOfChannels: trackInfo.audio?.channel_count || 2,\n timescale: trackInfo.timescale,\n samples,\n };\n }\n\n /**\n * Build sample table from mp4box track samples\n *\n * IMPORTANT: Keep samples in DTS (decode) order, not PTS (presentation) order!\n * VideoDecoder requires chunks in decode order. It will output frames in PTS order.\n */\n private buildSampleTable(mp4boxFile: MP4BoxFile, trackId: number, timescale: number): Sample[] {\n const samples: Sample[] = [];\n\n // Get track box\n const trak = mp4boxFile.getTrackById(trackId);\n if (!trak) return samples;\n\n // Access sample table (already in DTS order)\n const sampleTable = trak.samples || [];\n\n // Calculate PTS from CTS and normalize to start at 0\n let timestampOffset: number | null = null;\n\n for (const sample of sampleTable) {\n const durationUs = ((sample.duration || 0) / timescale) * 1_000_000;\n\n // Use CTS (Composition Time Stamp = PTS) for display timestamp\n const rawTimestampUs = ((sample.cts || 0) / timescale) * 1_000_000;\n\n // Normalize: first frame starts at 0 (like MP4Demuxer does)\n if (timestampOffset === null) {\n timestampOffset = rawTimestampUs;\n }\n const timestampUs = rawTimestampUs - timestampOffset;\n\n samples.push({\n timestamp: timestampUs, // Normalized PTS (display timestamp)\n duration: durationUs,\n byteOffset: sample.offset || 0,\n byteLength: sample.size || 0,\n isKeyframe: sample.is_sync || false,\n });\n }\n\n // DO NOT SORT! Samples must stay in DTS (decode) order for VideoDecoder\n // Decoder will output frames in PTS order automatically\n\n return samples;\n }\n\n /**\n * Extract video description (avcC/hvcC/etc) for VideoDecoder\n * Reuses MP4Demuxer.extractVideoDescription for consistency\n */\n private getVideoDescription(mp4boxFile: MP4BoxFile, trackInfo: any): ArrayBuffer | undefined {\n return MP4Demuxer.extractVideoDescription(mp4boxFile, trackInfo.id);\n }\n\n /**\n * Build GOP index from samples\n * GOP = Group of Pictures, starts with a keyframe\n */\n private buildGOPIndex(samples: Sample[]): GOP[] {\n const gopIndex: GOP[] = [];\n let currentGOP: {\n startTimeUs: TimeUs;\n keyframeSampleIndex: number;\n sampleCount: number;\n } | null = null;\n\n for (let i = 0; i < samples.length; i++) {\n const sample = samples[i];\n if (!sample) continue;\n\n if (sample.isKeyframe) {\n // Save previous GOP if exists\n if (currentGOP) {\n gopIndex.push(currentGOP);\n }\n\n // Start new GOP\n currentGOP = {\n startTimeUs: sample.timestamp,\n keyframeSampleIndex: i,\n sampleCount: 1,\n };\n } else if (currentGOP) {\n // Add sample to current GOP\n currentGOP.sampleCount++;\n }\n }\n\n // Save last GOP\n if (currentGOP) {\n gopIndex.push(currentGOP);\n }\n\n return gopIndex;\n }\n\n /**\n * Extract first GOP chunks from accumulated buffer\n * Used for fast cover rendering during streaming download\n */\n private extractFirstGOP(\n buffer: Uint8Array,\n videoTrack: VideoTrackIndex,\n byteStart: number\n ): EncodedVideoChunk[] {\n const chunks: EncodedVideoChunk[] = [];\n const firstGOP = videoTrack.gopIndex[0];\n if (!firstGOP) {\n return chunks;\n }\n\n const samples = videoTrack.samples;\n\n for (let i = 0; i < firstGOP.sampleCount; i++) {\n const sampleIdx = firstGOP.keyframeSampleIndex + i;\n const sample = samples[sampleIdx];\n if (!sample) continue;\n\n const relativeOffset = sample.byteOffset - byteStart;\n\n // Validate offset is within buffer\n if (relativeOffset < 0 || relativeOffset + sample.byteLength > buffer.length) {\n // Critical: If first sample (keyframe) is not fully downloaded, return empty array\n // to avoid decoding error \"A key frame is required after configure()\"\n if (i === 0) {\n console.warn(\n '[MP4IndexParser] First GOP keyframe not fully downloaded, skipping cover decode'\n );\n return []; // Return empty array to avoid sending non-keyframe as first chunk\n }\n\n console.warn('[MP4IndexParser] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n byteStart,\n relativeOffset,\n bufferLength: buffer.length,\n isKeyframe: sample.isKeyframe,\n sampleIndex: i,\n });\n\n // If not first sample, safe to skip (keyframe already present)\n continue;\n }\n\n const sampleData = buffer.slice(relativeOffset, relativeOffset + sample.byteLength);\n\n try {\n chunks.push(\n new EncodedVideoChunk({\n type: sample.isKeyframe ? 'key' : 'delta',\n timestamp: sample.timestamp,\n duration: sample.duration,\n data: sampleData,\n })\n );\n } catch (error) {\n console.warn('[MP4IndexParser] Failed to create EncodedVideoChunk:', error);\n }\n }\n\n // Additional safety check: ensure first chunk is keyframe\n if (chunks.length > 0 && chunks[0]?.type !== 'key') {\n console.error('[MP4IndexParser] First chunk is not a keyframe, discarding all chunks');\n return [];\n }\n\n return chunks;\n }\n}\n"],"names":[],"mappings":"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"}
{
"name": "@meframe/core",
"version": "0.2.6",
"version": "0.2.7",
"description": "Next generation media processing framework based on WebCodecs",

@@ -5,0 +5,0 @@ "type": "module",