@meframe/core 0.2.6 → 0.2.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
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"}
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;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"}
@@ -59,6 +59,9 @@ class MP4IndexParser {
59
59
  audioTimescale = timescale;
60
60
  expectedAudioSamples = expectedSamples;
61
61
  streamState.audioComplete = isComplete;
62
+ if (!isComplete && audioTrackId && audioConfig) {
63
+ mp4boxFile.start();
64
+ }
62
65
  if (isComplete) {
63
66
  setTimeout(() => {
64
67
  if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {
@@ -214,11 +217,10 @@ class MP4IndexParser {
214
217
  sampleRate: audioTrack.audio.sample_rate || audioTrack.timescale,
215
218
  numberOfChannels: audioTrack.audio.channel_count
216
219
  };
217
- mp4boxFile.setExtractionOptions(trackId, null, {
220
+ mp4boxFile.setExtractionOptions(trackId, audioTrack, {
218
221
  nbSamples: Infinity
219
222
  // Extract all samples
220
223
  });
221
- mp4boxFile.start();
222
224
  return {
223
225
  config,
224
226
  trackId,
@@ -1 +1 @@
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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"}
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 // 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meframe/core",
3
- "version": "0.2.6",
3
+ "version": "0.2.7",
4
4
  "description": "Next generation media processing framework based on WebCodecs",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",