@meframe/core 0.0.30 → 0.0.32
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.
- package/dist/Meframe.d.ts +2 -2
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +3 -3
- package/dist/Meframe.js.map +1 -1
- package/dist/_virtual/_commonjsHelpers.js +7 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/cache/CacheManager.d.ts +12 -17
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +18 -281
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/AudioL1Cache.d.ts +36 -19
- package/dist/cache/l1/AudioL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/AudioL1Cache.js +182 -282
- package/dist/cache/l1/AudioL1Cache.js.map +1 -1
- package/dist/controllers/PlaybackController.d.ts +5 -2
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +60 -13
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/medeo-fe/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +1 -0
- package/dist/{node_modules → medeo-fe/node_modules}/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js +7 -2
- package/dist/medeo-fe/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +1 -0
- package/dist/model/types.d.ts +0 -4
- package/dist/model/types.d.ts.map +1 -1
- package/dist/model/types.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts +6 -0
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -1
- package/dist/orchestrator/ExportScheduler.js +45 -66
- package/dist/orchestrator/ExportScheduler.js.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.d.ts +35 -28
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +212 -421
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +3 -3
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.js +4 -4
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts +1 -2
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +34 -48
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +0 -2
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +0 -49
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.d.ts.map +1 -1
- package/dist/stages/compose/OfflineAudioMixer.js +13 -18
- package/dist/stages/compose/OfflineAudioMixer.js.map +1 -1
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/VideoComposer.js +4 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -1
- package/dist/stages/decode/AudioChunkDecoder.js +169 -0
- package/dist/stages/decode/AudioChunkDecoder.js.map +1 -0
- package/dist/stages/demux/MP3FrameParser.js +186 -0
- package/dist/stages/demux/MP3FrameParser.js.map +1 -0
- package/dist/stages/demux/MP4Demuxer.js +6 -7
- package/dist/stages/demux/MP4Demuxer.js.map +1 -1
- package/dist/stages/demux/MP4IndexParser.js +3 -4
- package/dist/stages/demux/MP4IndexParser.js.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +20 -10
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +92 -139
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/index.d.ts +0 -1
- package/dist/stages/load/index.d.ts.map +1 -1
- package/dist/stages/load/types.d.ts +1 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +1 -1
- package/dist/utils/audio-data.d.ts +16 -0
- package/dist/utils/audio-data.d.ts.map +1 -0
- package/dist/utils/audio-data.js +111 -0
- package/dist/utils/audio-data.js.map +1 -0
- package/dist/utils/mp4box.d.ts +4 -0
- package/dist/utils/mp4box.d.ts.map +1 -0
- package/dist/utils/mp4box.js +17 -0
- package/dist/utils/mp4box.js.map +1 -0
- package/dist/workers/{MP4Demuxer.BEa6PLJm.js → MP4Demuxer.DxMpB08B.js} +49 -11
- package/dist/workers/MP4Demuxer.DxMpB08B.js.map +1 -0
- package/dist/workers/stages/compose/{video-compose.worker.DHQ8B105.js → video-compose.worker.BhpN-lxf.js} +5 -1
- package/dist/workers/stages/compose/video-compose.worker.BhpN-lxf.js.map +1 -0
- package/dist/workers/stages/demux/{audio-demux.worker._VRQdLdv.js → audio-demux.worker.Fd8sRTYi.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker._VRQdLdv.js.map → audio-demux.worker.Fd8sRTYi.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.CSkxGtmx.js → video-demux.worker.DqFOe12v.js} +2 -2
- package/dist/workers/stages/demux/{video-demux.worker.CSkxGtmx.js.map → video-demux.worker.DqFOe12v.js.map} +1 -1
- package/dist/workers/worker-manifest.json +3 -3
- package/package.json +1 -1
- package/dist/cache/resource/ImageBitmapCache.d.ts +0 -65
- package/dist/cache/resource/ImageBitmapCache.d.ts.map +0 -1
- package/dist/cache/resource/ImageBitmapCache.js +0 -101
- package/dist/cache/resource/ImageBitmapCache.js.map +0 -1
- package/dist/node_modules/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js.map +0 -1
- package/dist/node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js.map +0 -1
- package/dist/stages/load/WindowByteRangeResolver.d.ts +0 -47
- package/dist/stages/load/WindowByteRangeResolver.d.ts.map +0 -1
- package/dist/stages/load/WindowByteRangeResolver.js +0 -270
- package/dist/stages/load/WindowByteRangeResolver.js.map +0 -1
- package/dist/workers/MP4Demuxer.BEa6PLJm.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +0 -1
- /package/dist/{node_modules → medeo-fe/node_modules}/.pnpm/mp4-muxer@5.2.2/node_modules/mp4-muxer/build/mp4-muxer.js +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { BaseDecoder } from "./BaseDecoder.js";
|
|
2
|
+
class AudioChunkDecoder extends BaseDecoder {
|
|
3
|
+
// Default values
|
|
4
|
+
static DEFAULT_HIGH_WATER_MARK = 20;
|
|
5
|
+
static DEFAULT_DECODE_QUEUE_THRESHOLD = 16;
|
|
6
|
+
// Exposed properties
|
|
7
|
+
trackId;
|
|
8
|
+
// Backpressure configuration
|
|
9
|
+
highWaterMark;
|
|
10
|
+
decodeQueueThreshold;
|
|
11
|
+
// Buffering support for delayed configuration
|
|
12
|
+
bufferedChunks = [];
|
|
13
|
+
isProcessingBuffer = false;
|
|
14
|
+
constructor(trackId, config) {
|
|
15
|
+
super(config);
|
|
16
|
+
this.trackId = trackId;
|
|
17
|
+
this.highWaterMark = config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;
|
|
18
|
+
this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;
|
|
19
|
+
}
|
|
20
|
+
// Computed properties
|
|
21
|
+
get isConfigured() {
|
|
22
|
+
return this.isReady;
|
|
23
|
+
}
|
|
24
|
+
get state() {
|
|
25
|
+
return this.decoder?.state || "unconfigured";
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Update configuration - can be called before or after initialization
|
|
29
|
+
*/
|
|
30
|
+
async updateConfig(config) {
|
|
31
|
+
if (!this.isReady && config.codec) {
|
|
32
|
+
await this.configure(config);
|
|
33
|
+
await this.processBufferedChunks();
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Override createStream to handle buffering
|
|
38
|
+
createStream() {
|
|
39
|
+
return new TransformStream(
|
|
40
|
+
{
|
|
41
|
+
start: async (controller) => {
|
|
42
|
+
this.controller = controller;
|
|
43
|
+
if (this.config?.codec && !this.isReady) {
|
|
44
|
+
await this.initialize();
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
transform: async (chunk) => {
|
|
48
|
+
if (!this.isReady) {
|
|
49
|
+
this.bufferedChunks.push(chunk);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (this.isProcessingBuffer) {
|
|
53
|
+
this.bufferedChunks.push(chunk);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
await this.processChunk(chunk);
|
|
57
|
+
},
|
|
58
|
+
flush: async () => {
|
|
59
|
+
if (this.isReady) {
|
|
60
|
+
await this.flush();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
highWaterMark: this.highWaterMark,
|
|
66
|
+
size: () => 1
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Process a single chunk (extracted from transform for reuse)
|
|
72
|
+
*/
|
|
73
|
+
async processChunk(chunk) {
|
|
74
|
+
if (!this.decoder) {
|
|
75
|
+
throw new Error("Decoder not initialized");
|
|
76
|
+
}
|
|
77
|
+
if (this.decoder.state !== "configured") {
|
|
78
|
+
console.error("[AudioChunkDecoder] Decoder in unexpected state:", this.decoder.state);
|
|
79
|
+
throw new Error(`Decoder not configured, state: ${this.decoder.state}`);
|
|
80
|
+
}
|
|
81
|
+
if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {
|
|
82
|
+
await new Promise((resolve) => {
|
|
83
|
+
const check = () => {
|
|
84
|
+
if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {
|
|
85
|
+
resolve();
|
|
86
|
+
} else {
|
|
87
|
+
setTimeout(check, 20);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
check();
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
try {
|
|
94
|
+
this.decode(chunk);
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error(`[AudioChunkDecoder] decode error:`, error);
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
// Implement abstract methods
|
|
101
|
+
async isConfigSupported(config) {
|
|
102
|
+
const result = await AudioDecoder.isConfigSupported({
|
|
103
|
+
codec: config.codec,
|
|
104
|
+
sampleRate: config.sampleRate,
|
|
105
|
+
numberOfChannels: config.numberOfChannels
|
|
106
|
+
});
|
|
107
|
+
return { supported: result.supported ?? false };
|
|
108
|
+
}
|
|
109
|
+
createDecoder(init) {
|
|
110
|
+
return new AudioDecoder(init);
|
|
111
|
+
}
|
|
112
|
+
getDecoderType() {
|
|
113
|
+
return "Audio";
|
|
114
|
+
}
|
|
115
|
+
async configureDecoder(config) {
|
|
116
|
+
if (!this.decoder) return;
|
|
117
|
+
await this.decoder.configure({
|
|
118
|
+
codec: config.codec,
|
|
119
|
+
sampleRate: config.sampleRate,
|
|
120
|
+
numberOfChannels: config.numberOfChannels,
|
|
121
|
+
...config.description && { description: config.description }
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
decode(chunk) {
|
|
125
|
+
this.decoder?.decode(chunk);
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Configure the decoder with codec info (can be called after creation)
|
|
129
|
+
*/
|
|
130
|
+
async configure(config) {
|
|
131
|
+
if (this.isReady) {
|
|
132
|
+
await this.reconfigure(config);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
this.config = config;
|
|
136
|
+
await this.initialize();
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Process any buffered chunks after configuration
|
|
140
|
+
*/
|
|
141
|
+
async processBufferedChunks() {
|
|
142
|
+
if (!this.isReady || this.bufferedChunks.length === 0) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
this.isProcessingBuffer = true;
|
|
146
|
+
const chunks = [...this.bufferedChunks];
|
|
147
|
+
this.bufferedChunks = [];
|
|
148
|
+
for (const chunk of chunks) {
|
|
149
|
+
try {
|
|
150
|
+
await this.processChunk(chunk);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
console.error("[AudioChunkDecoder] Error processing buffered chunk:", error);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
this.isProcessingBuffer = false;
|
|
156
|
+
if (this.bufferedChunks.length > 0) {
|
|
157
|
+
await this.processBufferedChunks();
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// Override close to clean up buffered chunks
|
|
161
|
+
async close() {
|
|
162
|
+
this.bufferedChunks = [];
|
|
163
|
+
await super.close();
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
export {
|
|
167
|
+
AudioChunkDecoder
|
|
168
|
+
};
|
|
169
|
+
//# sourceMappingURL=AudioChunkDecoder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AudioChunkDecoder.js","sources":["../../../src/stages/decode/AudioChunkDecoder.ts"],"sourcesContent":["import { AudioDecoderConfig } from './types';\nimport { BaseDecoder } from './BaseDecoder';\n\n/**\n * Audio decoder with streaming support\n * Extends BaseDecoder for common WebCodecs operations\n */\nexport class AudioChunkDecoder extends BaseDecoder<\n AudioDecoder,\n AudioDecoderConfig,\n EncodedAudioChunk,\n AudioData\n> {\n // Default values\n private static readonly DEFAULT_HIGH_WATER_MARK = 20;\n private static readonly DEFAULT_DECODE_QUEUE_THRESHOLD = 16;\n\n // Exposed properties\n readonly trackId: string;\n\n // Backpressure configuration\n protected readonly highWaterMark: number;\n protected readonly decodeQueueThreshold: number;\n\n // Buffering support for delayed configuration\n private bufferedChunks: EncodedAudioChunk[] = [];\n private isProcessingBuffer: boolean = false;\n\n constructor(trackId: string, config?: Partial<AudioDecoderConfig>) {\n // Initialize with empty config, will be configured later\n super(config as AudioDecoderConfig);\n\n this.trackId = trackId;\n\n // Set backpressure configuration\n this.highWaterMark =\n config?.backpressure?.highWaterMark ?? AudioChunkDecoder.DEFAULT_HIGH_WATER_MARK;\n this.decodeQueueThreshold = AudioChunkDecoder.DEFAULT_DECODE_QUEUE_THRESHOLD;\n }\n\n // Computed properties\n get isConfigured(): boolean {\n return this.isReady;\n }\n\n get state(): string {\n return this.decoder?.state || 'unconfigured';\n }\n\n /**\n * Update configuration - can be called before or after initialization\n */\n async updateConfig(config: Partial<AudioDecoderConfig>): Promise<void> {\n // If decoder is not ready and we have codec info, configure it\n if (!this.isReady && config.codec) {\n await this.configure(config as AudioDecoderConfig);\n await this.processBufferedChunks();\n return;\n }\n\n // Note: AudioDecoder doesn't have many runtime-configurable options\n // Backpressure settings are readonly in this implementation\n // if (config.backpressure) {\n // console.warn('Backpressure settings cannot be changed at runtime');\n // }\n }\n\n // Override createStream to handle buffering\n override createStream(): TransformStream<EncodedAudioChunk, AudioData> {\n return new TransformStream<EncodedAudioChunk, AudioData>(\n {\n start: async (controller) => {\n this.controller = controller;\n // Don't initialize if no codec config yet\n if (this.config?.codec && !this.isReady) {\n await this.initialize();\n }\n },\n\n transform: async (chunk) => {\n // If not configured yet, buffer the chunk\n if (!this.isReady) {\n this.bufferedChunks.push(chunk);\n return; // Don't process yet\n }\n\n // If we're processing buffered chunks, add to buffer to maintain order\n if (this.isProcessingBuffer) {\n this.bufferedChunks.push(chunk);\n return;\n }\n\n // Normal processing\n await this.processChunk(chunk);\n },\n\n flush: async () => {\n if (this.isReady) {\n await this.flush();\n }\n },\n },\n {\n highWaterMark: this.highWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Process a single chunk (extracted from transform for reuse)\n */\n private async processChunk(chunk: EncodedAudioChunk): Promise<void> {\n if (!this.decoder) {\n throw new Error('Decoder not initialized');\n }\n\n if (this.decoder.state !== 'configured') {\n console.error('[AudioChunkDecoder] Decoder in unexpected state:', this.decoder.state);\n throw new Error(`Decoder not configured, state: ${this.decoder.state}`);\n }\n\n // Check decoder queue pressure\n if (this.decoder.decodeQueueSize >= this.decodeQueueThreshold) {\n await new Promise<void>((resolve) => {\n const check = () => {\n if (!this.decoder || this.decoder.decodeQueueSize < this.decodeQueueThreshold - 1) {\n resolve();\n } else {\n setTimeout(check, 20);\n }\n };\n check();\n });\n }\n\n // Decode the chunk\n try {\n this.decode(chunk);\n } catch (error) {\n console.error(`[AudioChunkDecoder] decode error:`, error);\n throw error; // Re-throw to propagate\n }\n }\n\n // Implement abstract methods\n protected async isConfigSupported(config: AudioDecoderConfig): Promise<{ supported: boolean }> {\n const result = await AudioDecoder.isConfigSupported({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n });\n return { supported: result.supported ?? false };\n }\n\n protected createDecoder(init: {\n output: (data: AudioData) => void;\n error: (error: DOMException) => void;\n }): AudioDecoder {\n return new AudioDecoder(init);\n }\n\n protected getDecoderType(): string {\n return 'Audio';\n }\n\n protected async configureDecoder(config: AudioDecoderConfig): Promise<void> {\n if (!this.decoder) return;\n\n await this.decoder.configure({\n codec: config.codec,\n sampleRate: config.sampleRate,\n numberOfChannels: config.numberOfChannels,\n ...(config.description && { description: config.description }),\n });\n }\n\n protected decode(chunk: EncodedAudioChunk): void {\n this.decoder?.decode(chunk);\n }\n\n /**\n * Configure the decoder with codec info (can be called after creation)\n */\n async configure(config: AudioDecoderConfig): Promise<void> {\n if (this.isReady) {\n // If already configured, reconfigure\n await this.reconfigure(config);\n return;\n }\n\n this.config = config as any;\n\n // Initialize decoder with new config\n await this.initialize();\n }\n\n /**\n * Process any buffered chunks after configuration\n */\n async processBufferedChunks(): Promise<void> {\n if (!this.isReady || this.bufferedChunks.length === 0) {\n return;\n }\n\n this.isProcessingBuffer = true;\n\n // Process buffered chunks in order\n const chunks = [...this.bufferedChunks];\n this.bufferedChunks = [];\n\n for (const chunk of chunks) {\n try {\n await this.processChunk(chunk);\n } catch (error) {\n console.error('[AudioChunkDecoder] Error processing buffered chunk:', error);\n }\n }\n\n this.isProcessingBuffer = false;\n\n // Process any new chunks that arrived while processing buffer\n if (this.bufferedChunks.length > 0) {\n await this.processBufferedChunks();\n }\n }\n\n // Override close to clean up buffered chunks\n override async close(): Promise<void> {\n // Clear buffered chunks\n this.bufferedChunks = [];\n\n // Call parent close\n await super.close();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
const BITRATE_TABLE = {
|
|
2
|
+
"1-3": [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],
|
|
3
|
+
"1-2": [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],
|
|
4
|
+
"1-1": [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],
|
|
5
|
+
"2-3": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
6
|
+
"2-2": [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],
|
|
7
|
+
"2-1": [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0]
|
|
8
|
+
};
|
|
9
|
+
const SAMPLE_RATE_TABLE = {
|
|
10
|
+
1: [44100, 48e3, 32e3],
|
|
11
|
+
2: [22050, 24e3, 16e3],
|
|
12
|
+
2.5: [11025, 12e3, 8e3]
|
|
13
|
+
};
|
|
14
|
+
class MP3FrameParser {
|
|
15
|
+
buffer = new Uint8Array(0);
|
|
16
|
+
config = null;
|
|
17
|
+
timestampUs = 0;
|
|
18
|
+
push(chunk) {
|
|
19
|
+
this.appendToBuffer(chunk);
|
|
20
|
+
this.skipId3Headers();
|
|
21
|
+
const frames = [];
|
|
22
|
+
let configEmitted;
|
|
23
|
+
let offset = 0;
|
|
24
|
+
while (offset <= this.buffer.length - 4) {
|
|
25
|
+
const header = this.parseHeader(this.buffer, offset);
|
|
26
|
+
if (!header) {
|
|
27
|
+
offset += 1;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
if (offset + header.frameSize > this.buffer.length) {
|
|
31
|
+
break;
|
|
32
|
+
}
|
|
33
|
+
const frameBytes = this.buffer.slice(offset, offset + header.frameSize);
|
|
34
|
+
const durationUs = Math.round(header.samplesPerFrame * 1e6 / header.sampleRate);
|
|
35
|
+
if (!this.config) {
|
|
36
|
+
this.config = {
|
|
37
|
+
codec: "mp3",
|
|
38
|
+
sampleRate: header.sampleRate,
|
|
39
|
+
channels: header.channels,
|
|
40
|
+
bitrateKbps: header.bitrateKbps,
|
|
41
|
+
samplesPerFrame: header.samplesPerFrame
|
|
42
|
+
};
|
|
43
|
+
configEmitted = this.config;
|
|
44
|
+
}
|
|
45
|
+
frames.push({
|
|
46
|
+
data: frameBytes,
|
|
47
|
+
timestampUs: this.timestampUs,
|
|
48
|
+
durationUs
|
|
49
|
+
});
|
|
50
|
+
this.timestampUs += durationUs;
|
|
51
|
+
offset += header.frameSize;
|
|
52
|
+
}
|
|
53
|
+
if (offset > 0) {
|
|
54
|
+
this.buffer = this.buffer.slice(offset);
|
|
55
|
+
}
|
|
56
|
+
return { frames, config: configEmitted };
|
|
57
|
+
}
|
|
58
|
+
flush() {
|
|
59
|
+
const { frames } = this.push(new Uint8Array(0));
|
|
60
|
+
const remainder = this.buffer;
|
|
61
|
+
this.buffer = new Uint8Array(0);
|
|
62
|
+
if (remainder.length) {
|
|
63
|
+
console.warn("[MP3FrameParser] Remaining unparsed bytes:", remainder.length);
|
|
64
|
+
}
|
|
65
|
+
return frames;
|
|
66
|
+
}
|
|
67
|
+
appendToBuffer(chunk) {
|
|
68
|
+
if (chunk.length === 0) {
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const combined = new Uint8Array(this.buffer.length + chunk.length);
|
|
72
|
+
combined.set(this.buffer, 0);
|
|
73
|
+
combined.set(chunk, this.buffer.length);
|
|
74
|
+
this.buffer = combined;
|
|
75
|
+
}
|
|
76
|
+
skipId3Headers() {
|
|
77
|
+
let offset = 0;
|
|
78
|
+
while (this.buffer.length - offset >= 10) {
|
|
79
|
+
if (this.buffer[offset] === 73 && this.buffer[offset + 1] === 68 && this.buffer[offset + 2] === 51) {
|
|
80
|
+
const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));
|
|
81
|
+
const total = size + 10;
|
|
82
|
+
if (offset + total <= this.buffer.length) {
|
|
83
|
+
offset += total;
|
|
84
|
+
continue;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
break;
|
|
88
|
+
}
|
|
89
|
+
if (offset > 0) {
|
|
90
|
+
this.buffer = this.buffer.slice(offset);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
parseHeader(buffer, offset) {
|
|
94
|
+
if (offset + 3 >= buffer.length) {
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
if (buffer[offset] !== 255 || ((buffer[offset + 1] ?? 0) & 224) !== 224) {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
const versionBits = (buffer[offset + 1] ?? 0) >> 3 & 3;
|
|
101
|
+
const layerBits = (buffer[offset + 1] ?? 0) >> 1 & 3;
|
|
102
|
+
const bitrateIndex = (buffer[offset + 2] ?? 0) >> 4 & 15;
|
|
103
|
+
const sampleRateIndex = (buffer[offset + 2] ?? 0) >> 2 & 3;
|
|
104
|
+
const paddingBit = (buffer[offset + 2] ?? 0) >> 1 & 1;
|
|
105
|
+
const channelMode = (buffer[offset + 3] ?? 0) >> 6 & 3;
|
|
106
|
+
const version = this.getVersion(versionBits);
|
|
107
|
+
const layer = this.getLayer(layerBits);
|
|
108
|
+
if (!version || !layer) {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;
|
|
112
|
+
if (!sampleRate) {
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;
|
|
116
|
+
const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;
|
|
117
|
+
if (bitrateKbps === null) {
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
const samplesPerFrame = this.getSamplesPerFrame(version, layer);
|
|
121
|
+
const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);
|
|
122
|
+
if (!frameSize || frameSize < 24) {
|
|
123
|
+
return null;
|
|
124
|
+
}
|
|
125
|
+
const channels = channelMode === 3 ? 1 : 2;
|
|
126
|
+
return {
|
|
127
|
+
frameSize,
|
|
128
|
+
sampleRate,
|
|
129
|
+
channels,
|
|
130
|
+
bitrateKbps: bitrateKbps || null,
|
|
131
|
+
samplesPerFrame
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
getVersion(bits) {
|
|
135
|
+
switch (bits) {
|
|
136
|
+
case 3:
|
|
137
|
+
return 1;
|
|
138
|
+
case 2:
|
|
139
|
+
return 2;
|
|
140
|
+
case 0:
|
|
141
|
+
return 2.5;
|
|
142
|
+
default:
|
|
143
|
+
return null;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
getLayer(bits) {
|
|
147
|
+
switch (bits) {
|
|
148
|
+
case 3:
|
|
149
|
+
return 1;
|
|
150
|
+
case 2:
|
|
151
|
+
return 2;
|
|
152
|
+
case 1:
|
|
153
|
+
return 3;
|
|
154
|
+
default:
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
getSamplesPerFrame(version, layer) {
|
|
159
|
+
if (layer === 1) {
|
|
160
|
+
return 384;
|
|
161
|
+
}
|
|
162
|
+
if (layer === 2) {
|
|
163
|
+
return 1152;
|
|
164
|
+
}
|
|
165
|
+
return version === 1 ? 1152 : 576;
|
|
166
|
+
}
|
|
167
|
+
calculateFrameSize(layer, bitrateKbps, sampleRate, padding) {
|
|
168
|
+
if (bitrateKbps <= 0) {
|
|
169
|
+
return 0;
|
|
170
|
+
}
|
|
171
|
+
if (layer === 1) {
|
|
172
|
+
return (12 * bitrateKbps * 1e3 / sampleRate + padding) * 4;
|
|
173
|
+
}
|
|
174
|
+
return Math.floor(144 * bitrateKbps * 1e3 / sampleRate + padding);
|
|
175
|
+
}
|
|
176
|
+
readSynchsafeInteger(bytes) {
|
|
177
|
+
if (bytes.length !== 4) {
|
|
178
|
+
return 0;
|
|
179
|
+
}
|
|
180
|
+
return ((bytes[0] ?? 0) & 127) << 21 | ((bytes[1] ?? 0) & 127) << 14 | ((bytes[2] ?? 0) & 127) << 7 | (bytes[3] ?? 0) & 127;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
export {
|
|
184
|
+
MP3FrameParser
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=MP3FrameParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP3FrameParser.js","sources":["../../../src/stages/demux/MP3FrameParser.ts"],"sourcesContent":["const BITRATE_TABLE: Record<string, number[]> = {\n '1-3': [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0],\n '1-2': [0, 32, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 384, 0],\n '1-1': [0, 32, 64, 96, 128, 160, 192, 224, 256, 288, 320, 352, 384, 416, 448, 0],\n '2-3': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-2': [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0],\n '2-1': [0, 32, 48, 56, 64, 80, 96, 112, 128, 144, 160, 176, 192, 224, 256, 0],\n};\n\nconst SAMPLE_RATE_TABLE: Record<number, number[]> = {\n 1: [44100, 48000, 32000],\n 2: [22050, 24000, 16000],\n 2.5: [11025, 12000, 8000],\n};\n\nexport interface MP3Config {\n codec: 'mp3';\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n}\n\nexport interface MP3Frame {\n data: Uint8Array;\n timestampUs: number;\n durationUs: number;\n}\n\ninterface ParseResult {\n frames: MP3Frame[];\n config?: MP3Config;\n}\n\nexport class MP3FrameParser {\n private buffer = new Uint8Array(0);\n private config: MP3Config | null = null;\n private timestampUs = 0;\n\n push(chunk: Uint8Array): ParseResult {\n this.appendToBuffer(chunk);\n this.skipId3Headers();\n\n const frames: MP3Frame[] = [];\n let configEmitted: MP3Config | undefined;\n\n let offset = 0;\n while (offset <= this.buffer.length - 4) {\n const header = this.parseHeader(this.buffer, offset);\n if (!header) {\n offset += 1;\n continue;\n }\n\n if (offset + header.frameSize > this.buffer.length) {\n break;\n }\n\n const frameBytes = this.buffer.slice(offset, offset + header.frameSize);\n const durationUs = Math.round((header.samplesPerFrame * 1_000_000) / header.sampleRate);\n\n if (!this.config) {\n this.config = {\n codec: 'mp3',\n sampleRate: header.sampleRate,\n channels: header.channels,\n bitrateKbps: header.bitrateKbps,\n samplesPerFrame: header.samplesPerFrame,\n };\n configEmitted = this.config;\n }\n\n frames.push({\n data: frameBytes,\n timestampUs: this.timestampUs,\n durationUs,\n });\n\n this.timestampUs += durationUs;\n offset += header.frameSize;\n }\n\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n\n return { frames, config: configEmitted };\n }\n\n flush(): MP3Frame[] {\n const { frames } = this.push(new Uint8Array(0));\n const remainder = this.buffer;\n this.buffer = new Uint8Array(0);\n if (remainder.length) {\n console.warn('[MP3FrameParser] Remaining unparsed bytes:', remainder.length);\n }\n return frames;\n }\n\n private appendToBuffer(chunk: Uint8Array): void {\n if (chunk.length === 0) {\n return;\n }\n const combined = new Uint8Array(this.buffer.length + chunk.length);\n combined.set(this.buffer, 0);\n combined.set(chunk, this.buffer.length);\n this.buffer = combined;\n }\n\n private skipId3Headers(): void {\n let offset = 0;\n while (this.buffer.length - offset >= 10) {\n if (\n this.buffer[offset] === 0x49 &&\n this.buffer[offset + 1] === 0x44 &&\n this.buffer[offset + 2] === 0x33\n ) {\n const size = this.readSynchsafeInteger(this.buffer.subarray(offset + 6, offset + 10));\n const total = size + 10;\n if (offset + total <= this.buffer.length) {\n offset += total;\n continue;\n }\n }\n break;\n }\n if (offset > 0) {\n this.buffer = this.buffer.slice(offset);\n }\n }\n\n private parseHeader(\n buffer: Uint8Array,\n offset: number\n ): {\n frameSize: number;\n sampleRate: number;\n channels: number;\n bitrateKbps: number | null;\n samplesPerFrame: number;\n } | null {\n if (offset + 3 >= buffer.length) {\n return null;\n }\n if (buffer[offset] !== 0xff || ((buffer[offset + 1] ?? 0) & 0xe0) !== 0xe0) {\n return null;\n }\n\n const versionBits = ((buffer[offset + 1] ?? 0) >> 3) & 0x03;\n const layerBits = ((buffer[offset + 1] ?? 0) >> 1) & 0x03;\n const bitrateIndex = ((buffer[offset + 2] ?? 0) >> 4) & 0x0f;\n const sampleRateIndex = ((buffer[offset + 2] ?? 0) >> 2) & 0x03;\n const paddingBit = ((buffer[offset + 2] ?? 0) >> 1) & 0x01;\n const channelMode = ((buffer[offset + 3] ?? 0) >> 6) & 0x03;\n\n const version = this.getVersion(versionBits);\n const layer = this.getLayer(layerBits);\n if (!version || !layer) {\n return null;\n }\n\n const sampleRate = SAMPLE_RATE_TABLE[version]?.[sampleRateIndex] ?? null;\n if (!sampleRate) {\n return null;\n }\n\n const bitrateKey = `${version === 1 ? 1 : 2}-${layer}`;\n const bitrateKbps = BITRATE_TABLE[bitrateKey]?.[bitrateIndex] ?? null;\n if (bitrateKbps === null) {\n return null;\n }\n\n const samplesPerFrame = this.getSamplesPerFrame(version, layer);\n const frameSize = this.calculateFrameSize(layer, bitrateKbps, sampleRate, paddingBit);\n if (!frameSize || frameSize < 24) {\n return null;\n }\n\n const channels = channelMode === 3 ? 1 : 2;\n\n return {\n frameSize,\n sampleRate,\n channels,\n bitrateKbps: bitrateKbps || null,\n samplesPerFrame,\n };\n }\n\n private getVersion(bits: number): 1 | 2 | 2.5 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b00:\n return 2.5;\n default:\n return null;\n }\n }\n\n private getLayer(bits: number): 1 | 2 | 3 | null {\n switch (bits) {\n case 0b11:\n return 1;\n case 0b10:\n return 2;\n case 0b01:\n return 3;\n default:\n return null;\n }\n }\n\n private getSamplesPerFrame(version: 1 | 2 | 2.5, layer: 1 | 2 | 3): number {\n if (layer === 1) {\n return 384;\n }\n if (layer === 2) {\n return 1152;\n }\n return version === 1 ? 1152 : 576;\n }\n\n private calculateFrameSize(\n layer: 1 | 2 | 3,\n bitrateKbps: number,\n sampleRate: number,\n padding: number\n ): number {\n if (bitrateKbps <= 0) {\n return 0;\n }\n\n if (layer === 1) {\n return ((12 * bitrateKbps * 1000) / sampleRate + padding) * 4;\n }\n\n return Math.floor((144 * bitrateKbps * 1000) / sampleRate + padding);\n }\n\n private readSynchsafeInteger(bytes: Uint8Array): number {\n if (bytes.length !== 4) {\n return 0;\n }\n return (\n (((bytes[0] ?? 0) & 0x7f) << 21) |\n (((bytes[1] ?? 0) & 0x7f) << 14) |\n (((bytes[2] ?? 0) & 0x7f) << 7) |\n ((bytes[3] ?? 0) & 0x7f)\n );\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import "../../
|
|
2
|
-
import { __exports as mp4box_all } from "../../_virtual/mp4box.all.js";
|
|
1
|
+
import { MP4Box } from "../../utils/mp4box.js";
|
|
3
2
|
function normalizeAudioCodec(codec) {
|
|
4
3
|
if (!codec) return "mp4a.40.2";
|
|
5
4
|
if (codec === "mp4a") {
|
|
@@ -20,7 +19,7 @@ class MP4Demuxer {
|
|
|
20
19
|
audioTimestampOffset = null;
|
|
21
20
|
skipAudio = false;
|
|
22
21
|
constructor(config = {}) {
|
|
23
|
-
this.mp4boxFile =
|
|
22
|
+
this.mp4boxFile = MP4Box.createFile();
|
|
24
23
|
this.onReadyCallback = config.onReady;
|
|
25
24
|
this.skipAudio = config.skipAudio ?? false;
|
|
26
25
|
const DEFAULT_HIGH_WATER_MARK = 10;
|
|
@@ -130,10 +129,10 @@ class MP4Demuxer {
|
|
|
130
129
|
for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {
|
|
131
130
|
const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;
|
|
132
131
|
if (box) {
|
|
133
|
-
const stream = new
|
|
132
|
+
const stream = new MP4Box.DataStream(
|
|
134
133
|
void 0,
|
|
135
134
|
0,
|
|
136
|
-
|
|
135
|
+
MP4Box.DataStream.BIG_ENDIAN
|
|
137
136
|
// IMPORTANT: must be BIG_ENDIAN
|
|
138
137
|
);
|
|
139
138
|
box.write(stream);
|
|
@@ -168,10 +167,10 @@ class MP4Demuxer {
|
|
|
168
167
|
console.warn("[MP4Demuxer] Could not extract AudioSpecificConfig from esds structure");
|
|
169
168
|
return void 0;
|
|
170
169
|
} else if (entry.dOps) {
|
|
171
|
-
const stream = new
|
|
170
|
+
const stream = new MP4Box.DataStream(
|
|
172
171
|
void 0,
|
|
173
172
|
0,
|
|
174
|
-
|
|
173
|
+
MP4Box.DataStream.BIG_ENDIAN
|
|
175
174
|
);
|
|
176
175
|
entry.dOps.write(stream);
|
|
177
176
|
return new Uint8Array(stream.buffer.slice(8)).buffer;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"MP4Demuxer.js","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"sourcesContent":["import * as MP4Box from 'mp4box';\nimport type { DemuxConfig, TrackInfo } from './types';\n\n/**\n * Normalize audio codec string for WebCodecs compatibility\n * Fixes incomplete AAC codec strings from mp4box.js\n */\nexport function normalizeAudioCodec(codec?: string): string {\n if (!codec) return 'mp4a.40.2'; // Default AAC-LC\n\n // Incomplete mp4a → complete as AAC-LC\n if (codec === 'mp4a') {\n return 'mp4a.40.2';\n }\n\n // Already complete or other format, return as-is\n return codec;\n}\n\n/**\n * MP4 Demuxer - Extract encoded chunks from MP4 container\n * Simplified implementation following Stream API pattern\n */\nexport class MP4Demuxer {\n private mp4boxFile: any;\n tracks = new Map<number, TrackInfo>();\n isReady = false;\n private videoController?: ReadableStreamDefaultController<EncodedVideoChunk>;\n private audioController?: ReadableStreamDefaultController<EncodedAudioChunk>;\n private demuxHighWaterMark: number;\n private onReadyCallback?: () => void;\n private fileOffset = 0;\n private videoTimestampOffset: number | null = null;\n private audioTimestampOffset: number | null = null;\n private skipAudio: boolean = false;\n\n constructor(config: DemuxConfig & { onReady?: () => void } = {}) {\n this.mp4boxFile = MP4Box.createFile();\n this.onReadyCallback = config.onReady;\n this.skipAudio = config.skipAudio ?? false;\n\n // Use provided config with local default as fallback\n const DEFAULT_HIGH_WATER_MARK = 10; // Default for video chunks\n this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;\n\n this.setupHandlers();\n }\n\n updateConfig(config: DemuxConfig): void {\n this.demuxHighWaterMark = config.highWaterMark ?? this.demuxHighWaterMark;\n }\n\n private setupHandlers(): void {\n this.mp4boxFile.onError = (error: string) => {\n console.error('[MP4Demuxer] MP4Box error:', error);\n const err = new Error(error);\n this.videoController?.error(err);\n this.audioController?.error(err);\n };\n\n this.mp4boxFile.onReady = (info: any) => {\n this.processTracks(info.tracks);\n this.isReady = true;\n\n // Call the ready callback before starting\n if (this.onReadyCallback) {\n this.onReadyCallback();\n }\n\n // Start processing samples after callback\n // Note: start() enables extraction, actual samples will be output\n // when flush() is called (by TransformStream's flush callback)\n this.mp4boxFile.start();\n };\n\n this.mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n this.processSamples(trackId, samples);\n };\n }\n\n private processTracks(tracks: any[]): void {\n for (const track of tracks) {\n const trackInfo: TrackInfo = {\n id: track.id,\n type: track.type === 'video' ? 'video' : 'audio',\n codec: track.type === 'audio' ? normalizeAudioCodec(track.codec) : track.codec,\n timescale: track.timescale,\n };\n\n if (track.type === 'video') {\n trackInfo.width = track.video?.width;\n trackInfo.height = track.video?.height;\n trackInfo.description = this.getVideoDescription(track);\n } else if (track.type === 'audio') {\n trackInfo.sampleRate = track.audio?.sample_rate || track.timescale;\n trackInfo.numberOfChannels = track.audio?.channel_count;\n trackInfo.description = this.getAudioDescription(track);\n }\n\n this.tracks.set(track.id, trackInfo);\n\n // Configure extraction - use Infinity to extract all samples\n // Note: onSamples will be called multiple times as data is appended,\n // but we don't need to manually request more batches\n this.mp4boxFile.setExtractionOptions(track.id, track, {\n nbSamples: Infinity,\n });\n }\n }\n\n private processSamples(trackId: number, samples: any[]): void {\n const track = this.tracks.get(trackId);\n if (!track) return;\n\n const timescale = track.timescale || 90000;\n for (const sample of samples) {\n const rawTimestamp = (sample.cts * 1000000) / timescale;\n const duration = (sample.duration * 1000000) / timescale;\n\n if (track.type === 'video') {\n if (!this.videoController) {\n console.error('[MP4Demuxer] videoController is null when trying to output chunk!');\n // Should not happen - stream should be created before appendBuffer\n return;\n }\n\n // Normalize timestamp: first frame starts at 0\n if (this.videoTimestampOffset === null) {\n this.videoTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.videoTimestampOffset;\n\n const chunk = new EncodedVideoChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp,\n duration,\n data: sample.data,\n });\n this.videoController.enqueue(chunk);\n } else if (track.type === 'audio') {\n // Skip audio processing if skipAudio enabled or no controller\n if (!this.audioController) {\n // Still release samples immediately to avoid memory accumulation in mp4box.js\n const last = samples[samples.length - 1].number;\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n return;\n }\n\n // Normalize timestamp: first frame starts at 0\n if (this.audioTimestampOffset === null) {\n this.audioTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.audioTimestampOffset;\n\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp,\n duration,\n data: sample.data,\n });\n this.audioController.enqueue(chunk);\n }\n }\n\n const last = samples[samples.length - 1].number;\n // Release memory immediately\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n }\n\n private getVideoDescription(track: any): ArrayBuffer | undefined {\n return MP4Demuxer.extractVideoDescription(this.mp4boxFile, track.id);\n }\n\n /**\n * Extract video description (avcC/hvcC/etc) for VideoDecoder configuration\n * Static method for reuse in other components\n */\n static extractVideoDescription(mp4boxFile: any, trackId: number): ArrayBuffer | undefined {\n try {\n const fullTrack = mp4boxFile.getTrackById(trackId);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;\n if (box) {\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN // IMPORTANT: must be BIG_ENDIAN\n );\n box.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get video description:', error);\n }\n return undefined;\n }\n\n // private getVideoDescription(track: any): ArrayBuffer | undefined {\n // if (!this.mp4boxFile) return undefined;\n // return getVideoDescription(this.mp4boxFile, track);\n // }\n\n private getAudioDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n if (entry.esds) {\n // Use mp4box.js parsed structure to get AudioSpecificConfig\n // esds.esd.descs[0] = DecoderConfigDescriptor\n // esds.esd.descs[0].descs[0].data = DecoderSpecificInfo (AudioSpecificConfig)\n const decConfigDesc = entry.esds.esd?.descs?.[0];\n const decSpecInfo = decConfigDesc?.descs?.[0];\n\n if (decSpecInfo?.data) {\n // Convert Uint8Array to ArrayBuffer\n const data = decSpecInfo.data;\n if (data instanceof Uint8Array) {\n const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n return buffer instanceof ArrayBuffer ? buffer : undefined;\n } else if (Array.isArray(data)) {\n // Some versions of mp4box.js return Array instead of Uint8Array\n return new Uint8Array(data).buffer;\n }\n }\n\n console.warn('[MP4Demuxer] Could not extract AudioSpecificConfig from esds structure');\n return undefined;\n } else if (entry.dOps) {\n // For Opus, use the full dOps box\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN\n );\n entry.dOps.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get audio description:', error);\n }\n return undefined;\n }\n\n /**\n * Create readable stream for video chunks (output only)\n */\n createVideoStream(): ReadableStream<EncodedVideoChunk> {\n return new ReadableStream<EncodedVideoChunk>(\n {\n start: (ctrl) => {\n this.videoController = ctrl as any;\n },\n cancel: () => {\n this.videoController = undefined;\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Create readable stream for audio chunks (output only)\n */\n createAudioStream(): ReadableStream<EncodedAudioChunk> | null {\n if (this.skipAudio) {\n return null;\n }\n\n return new ReadableStream<EncodedAudioChunk>(\n {\n start: (ctrl) => {\n this.audioController = ctrl as any;\n },\n cancel: () => {\n this.audioController = undefined;\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Create writable stream for input data (single source)\n * This is the only stream that should receive the input data\n */\n createInputStream(): WritableStream<Uint8Array | ArrayBuffer> {\n return new WritableStream<Uint8Array | ArrayBuffer>(\n {\n write: (chunk) => {\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n close: async () => {\n // Trigger final MP4Box flush\n this.mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onSamples callbacks)\n await new Promise((resolve) => setTimeout(resolve, 100));\n this.videoController?.close();\n this.audioController?.close();\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n }\n );\n }\n\n appendBuffer(chunk: Uint8Array): void {\n const buffer = chunk.buffer as ArrayBuffer & { fileStart: number };\n buffer.fileStart = this.fileOffset;\n this.mp4boxFile.appendBuffer(buffer);\n this.fileOffset += chunk.byteLength;\n }\n\n /**\n * Get video track info if available\n */\n get videoTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'video');\n }\n\n /**\n * Get audio track info if available\n */\n get audioTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'audio');\n }\n\n destroy(): void {\n this.mp4boxFile?.stop();\n this.mp4boxFile = null;\n this.tracks.clear();\n this.isReady = false;\n this.videoTimestampOffset = null;\n this.audioTimestampOffset = null;\n }\n}\n"],"names":["MP4Box.createFile","last","MP4Box.DataStream"],"mappings":";;AAOO,SAAS,oBAAoB,OAAwB;AAC1D,MAAI,CAAC,MAAO,QAAO;AAGnB,MAAI,UAAU,QAAQ;AACpB,WAAO;AAAA,EACT;AAGA,SAAO;AACT;AAMO,MAAM,WAAW;AAAA,EACd;AAAA,EACR,6BAAa,IAAA;AAAA,EACb,UAAU;AAAA,EACF;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,aAAa;AAAA,EACb,uBAAsC;AAAA,EACtC,uBAAsC;AAAA,EACtC,YAAqB;AAAA,EAE7B,YAAY,SAAiD,IAAI;AAC/D,SAAK,aAAaA,sBAAO;AACzB,SAAK,kBAAkB,OAAO;AAC9B,SAAK,YAAY,OAAO,aAAa;AAGrC,UAAM,0BAA0B;AAChC,SAAK,qBAAqB,OAAO,iBAAiB;AAElD,SAAK,cAAA;AAAA,EACP;AAAA,EAEA,aAAa,QAA2B;AACtC,SAAK,qBAAqB,OAAO,iBAAiB,KAAK;AAAA,EACzD;AAAA,EAEQ,gBAAsB;AAC5B,SAAK,WAAW,UAAU,CAAC,UAAkB;AAC3C,cAAQ,MAAM,8BAA8B,KAAK;AACjD,YAAM,MAAM,IAAI,MAAM,KAAK;AAC3B,WAAK,iBAAiB,MAAM,GAAG;AAC/B,WAAK,iBAAiB,MAAM,GAAG;AAAA,IACjC;AAEA,SAAK,WAAW,UAAU,CAAC,SAAc;AACvC,WAAK,cAAc,KAAK,MAAM;AAC9B,WAAK,UAAU;AAGf,UAAI,KAAK,iBAAiB;AACxB,aAAK,gBAAA;AAAA,MACP;AAKA,WAAK,WAAW,MAAA;AAAA,IAClB;AAEA,SAAK,WAAW,YAAY,CAAC,SAAiB,OAAY,YAAmB;AAC3E,WAAK,eAAe,SAAS,OAAO;AAAA,IACtC;AAAA,EACF;AAAA,EAEQ,cAAc,QAAqB;AACzC,eAAW,SAAS,QAAQ;AAC1B,YAAM,YAAuB;AAAA,QAC3B,IAAI,MAAM;AAAA,QACV,MAAM,MAAM,SAAS,UAAU,UAAU;AAAA,QACzC,OAAO,MAAM,SAAS,UAAU,oBAAoB,MAAM,KAAK,IAAI,MAAM;AAAA,QACzE,WAAW,MAAM;AAAA,MAAA;AAGnB,UAAI,MAAM,SAAS,SAAS;AAC1B,kBAAU,QAAQ,MAAM,OAAO;AAC/B,kBAAU,SAAS,MAAM,OAAO;AAChC,kBAAU,cAAc,KAAK,oBAAoB,KAAK;AAAA,MACxD,WAAW,MAAM,SAAS,SAAS;AACjC,kBAAU,aAAa,MAAM,OAAO,eAAe,MAAM;AACzD,kBAAU,mBAAmB,MAAM,OAAO;AAC1C,kBAAU,cAAc,KAAK,oBAAoB,KAAK;AAAA,MACxD;AAEA,WAAK,OAAO,IAAI,MAAM,IAAI,SAAS;AAKnC,WAAK,WAAW,qBAAqB,MAAM,IAAI,OAAO;AAAA,QACpD,WAAW;AAAA,MAAA,CACZ;AAAA,IACH;AAAA,EACF;AAAA,EAEQ,eAAe,SAAiB,SAAsB;AAC5D,UAAM,QAAQ,KAAK,OAAO,IAAI,OAAO;AACrC,QAAI,CAAC,MAAO;AAEZ,UAAM,YAAY,MAAM,aAAa;AACrC,eAAW,UAAU,SAAS;AAC5B,YAAM,eAAgB,OAAO,MAAM,MAAW;AAC9C,YAAM,WAAY,OAAO,WAAW,MAAW;AAE/C,UAAI,MAAM,SAAS,SAAS;AAC1B,YAAI,CAAC,KAAK,iBAAiB;AACzB,kBAAQ,MAAM,mEAAmE;AAEjF;AAAA,QACF;AAGA,YAAI,KAAK,yBAAyB,MAAM;AACtC,eAAK,uBAAuB;AAAA,QAC9B;AACA,cAAM,YAAY,eAAe,KAAK;AAEtC,cAAM,QAAQ,IAAI,kBAAkB;AAAA,UAClC,MAAM,OAAO,UAAU,QAAQ;AAAA,UAC/B;AAAA,UACA;AAAA,UACA,MAAM,OAAO;AAAA,QAAA,CACd;AACD,aAAK,gBAAgB,QAAQ,KAAK;AAAA,MACpC,WAAW,MAAM,SAAS,SAAS;AAEjC,YAAI,CAAC,KAAK,iBAAiB;AAEzB,gBAAMC,QAAO,QAAQ,QAAQ,SAAS,CAAC,EAAE;AACzC,eAAK,WAAW,mBAAmB,SAASA,QAAO,CAAC;AACpD;AAAA,QACF;AAGA,YAAI,KAAK,yBAAyB,MAAM;AACtC,eAAK,uBAAuB;AAAA,QAC9B;AACA,cAAM,YAAY,eAAe,KAAK;AAEtC,cAAM,QAAQ,IAAI,kBAAkB;AAAA,UAClC,MAAM;AAAA,UACN;AAAA,UACA;AAAA,UACA,MAAM,OAAO;AAAA,QAAA,CACd;AACD,aAAK,gBAAgB,QAAQ,KAAK;AAAA,MACpC;AAAA,IACF;AAEA,UAAM,OAAO,QAAQ,QAAQ,SAAS,CAAC,EAAE;AAEzC,SAAK,WAAW,mBAAmB,SAAS,OAAO,CAAC;AAAA,EACtD;AAAA,EAEQ,oBAAoB,OAAqC;AAC/D,WAAO,WAAW,wBAAwB,KAAK,YAAY,MAAM,EAAE;AAAA,EACrE;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,wBAAwB,YAAiB,SAA0C;AACxF,QAAI;AACF,YAAM,YAAY,WAAW,aAAa,OAAO;AACjD,iBAAW,SAAS,UAAU,KAAK,KAAK,KAAK,KAAK,SAAS;AACzD,cAAM,MAAM,MAAM,QAAQ,MAAM,QAAQ,MAAM,QAAQ,MAAM;AAC5D,YAAI,KAAK;AACP,gBAAM,SAAS,IAAKC,WAAAA;AAAAA,YAClB;AAAA,YACA;AAAA,YACCA,sBAA0B;AAAA;AAAA,UAAA;AAE7B,cAAI,MAAM,MAAM;AAChB,iBAAO,IAAI,WAAW,OAAO,OAAO,MAAM,CAAC,CAAC,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AAAA,IACzD;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,oBAAoB,OAAqC;AAC/D,QAAI;AACF,YAAM,YAAY,KAAK,WAAW,aAAa,MAAM,EAAE;AACvD,iBAAW,SAAS,UAAU,KAAK,KAAK,KAAK,KAAK,SAAS;AACzD,YAAI,MAAM,MAAM;AAId,gBAAM,gBAAgB,MAAM,KAAK,KAAK,QAAQ,CAAC;AAC/C,gBAAM,cAAc,eAAe,QAAQ,CAAC;AAE5C,cAAI,aAAa,MAAM;AAErB,kBAAM,OAAO,YAAY;AACzB,gBAAI,gBAAgB,YAAY;AAC9B,oBAAM,SAAS,KAAK,OAAO,MAAM,KAAK,YAAY,KAAK,aAAa,KAAK,UAAU;AACnF,qBAAO,kBAAkB,cAAc,SAAS;AAAA,YAClD,WAAW,MAAM,QAAQ,IAAI,GAAG;AAE9B,qBAAO,IAAI,WAAW,IAAI,EAAE;AAAA,YAC9B;AAAA,UACF;AAEA,kBAAQ,KAAK,wEAAwE;AACrF,iBAAO;AAAA,QACT,WAAW,MAAM,MAAM;AAErB,gBAAM,SAAS,IAAKA,WAAAA;AAAAA,YAClB;AAAA,YACA;AAAA,YACCA,sBAA0B;AAAA,UAAA;AAE7B,gBAAM,KAAK,MAAM,MAAM;AACvB,iBAAO,IAAI,WAAW,OAAO,OAAO,MAAM,CAAC,CAAC,EAAE;AAAA,QAChD;AAAA,MACF;AAAA,IACF,SAAS,OAAO;AACd,cAAQ,MAAM,oCAAoC,KAAK;AAAA,IACzD;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA,EAKA,oBAAuD;AACrD,WAAO,IAAI;AAAA,MACT;AAAA,QACE,OAAO,CAAC,SAAS;AACf,eAAK,kBAAkB;AAAA,QACzB;AAAA,QACA,QAAQ,MAAM;AACZ,eAAK,kBAAkB;AAAA,QACzB;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,eAAe,KAAK;AAAA,QACpB,MAAM,MAAM;AAAA,MAAA;AAAA,IACd;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA,EAKA,oBAA8D;AAC5D,QAAI,KAAK,WAAW;AAClB,aAAO;AAAA,IACT;AAEA,WAAO,IAAI;AAAA,MACT;AAAA,QACE,OAAO,CAAC,SAAS;AACf,eAAK,kBAAkB;AAAA,QACzB;AAAA,QACA,QAAQ,MAAM;AACZ,eAAK,kBAAkB;AAAA,QACzB;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,eAAe,KAAK;AAAA,QACpB,MAAM,MAAM;AAAA,MAAA;AAAA,IACd;AAAA,EAEJ;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,oBAA8D;AAC5D,WAAO,IAAI;AAAA,MACT;AAAA,QACE,OAAO,CAAC,UAAU;AAEhB,gBAAM,YAAY,IAAI,WAAW,KAAK;AACtC,eAAK,aAAa,SAAS;AAG3B,eAAK,WAAW,MAAA;AAAA,QAClB;AAAA,QACA,OAAO,YAAY;AAEjB,eAAK,WAAW,MAAA;AAGhB,gBAAM,IAAI,QAAQ,CAAC,YAAY,WAAW,SAAS,GAAG,CAAC;AACvD,eAAK,iBAAiB,MAAA;AACtB,eAAK,iBAAiB,MAAA;AAAA,QACxB;AAAA,MAAA;AAAA,MAEF;AAAA,QACE,eAAe,KAAK;AAAA,MAAA;AAAA,IACtB;AAAA,EAEJ;AAAA,EAEA,aAAa,OAAyB;AACpC,UAAM,SAAS,MAAM;AACrB,WAAO,YAAY,KAAK;AACxB,SAAK,WAAW,aAAa,MAAM;AACnC,SAAK,cAAc,MAAM;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,iBAAwC;AAC1C,WAAO,MAAM,KAAK,KAAK,OAAO,OAAA,CAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,SAAS,OAAO;AAAA,EAChF;AAAA;AAAA;AAAA;AAAA,EAKA,IAAI,iBAAwC;AAC1C,WAAO,MAAM,KAAK,KAAK,OAAO,OAAA,CAAQ,EAAE,KAAK,CAAC,UAAU,MAAM,SAAS,OAAO;AAAA,EAChF;AAAA,EAEA,UAAgB;AACd,SAAK,YAAY,KAAA;AACjB,SAAK,aAAa;AAClB,SAAK,OAAO,MAAA;AACZ,SAAK,UAAU;AACf,SAAK,uBAAuB;AAC5B,SAAK,uBAAuB;AAAA,EAC9B;AACF;"}
|
|
1
|
+
{"version":3,"file":"MP4Demuxer.js","sources":["../../../src/stages/demux/MP4Demuxer.ts"],"sourcesContent":["import { MP4Box } from '../../utils/mp4box';\nimport type { DemuxConfig, TrackInfo } from './types';\n\n/**\n * Normalize audio codec string for WebCodecs compatibility\n * Fixes incomplete AAC codec strings from mp4box.js\n */\nexport function normalizeAudioCodec(codec?: string): string {\n if (!codec) return 'mp4a.40.2'; // Default AAC-LC\n\n // Incomplete mp4a → complete as AAC-LC\n if (codec === 'mp4a') {\n return 'mp4a.40.2';\n }\n\n // Already complete or other format, return as-is\n return codec;\n}\n\n/**\n * MP4 Demuxer - Extract encoded chunks from MP4 container\n * Simplified implementation following Stream API pattern\n */\nexport class MP4Demuxer {\n private mp4boxFile: any;\n tracks = new Map<number, TrackInfo>();\n isReady = false;\n private videoController?: ReadableStreamDefaultController<EncodedVideoChunk>;\n private audioController?: ReadableStreamDefaultController<EncodedAudioChunk>;\n private demuxHighWaterMark: number;\n private onReadyCallback?: () => void;\n private fileOffset = 0;\n private videoTimestampOffset: number | null = null;\n private audioTimestampOffset: number | null = null;\n private skipAudio: boolean = false;\n\n constructor(config: DemuxConfig & { onReady?: () => void } = {}) {\n this.mp4boxFile = MP4Box.createFile();\n this.onReadyCallback = config.onReady;\n this.skipAudio = config.skipAudio ?? false;\n\n // Use provided config with local default as fallback\n const DEFAULT_HIGH_WATER_MARK = 10; // Default for video chunks\n this.demuxHighWaterMark = config.highWaterMark ?? DEFAULT_HIGH_WATER_MARK;\n\n this.setupHandlers();\n }\n\n updateConfig(config: DemuxConfig): void {\n this.demuxHighWaterMark = config.highWaterMark ?? this.demuxHighWaterMark;\n }\n\n private setupHandlers(): void {\n this.mp4boxFile.onError = (error: string) => {\n console.error('[MP4Demuxer] MP4Box error:', error);\n const err = new Error(error);\n this.videoController?.error(err);\n this.audioController?.error(err);\n };\n\n this.mp4boxFile.onReady = (info: any) => {\n this.processTracks(info.tracks);\n this.isReady = true;\n\n // Call the ready callback before starting\n if (this.onReadyCallback) {\n this.onReadyCallback();\n }\n\n // Start processing samples after callback\n // Note: start() enables extraction, actual samples will be output\n // when flush() is called (by TransformStream's flush callback)\n this.mp4boxFile.start();\n };\n\n this.mp4boxFile.onSamples = (trackId: number, _user: any, samples: any[]) => {\n this.processSamples(trackId, samples);\n };\n }\n\n private processTracks(tracks: any[]): void {\n for (const track of tracks) {\n const trackInfo: TrackInfo = {\n id: track.id,\n type: track.type === 'video' ? 'video' : 'audio',\n codec: track.type === 'audio' ? normalizeAudioCodec(track.codec) : track.codec,\n timescale: track.timescale,\n };\n\n if (track.type === 'video') {\n trackInfo.width = track.video?.width;\n trackInfo.height = track.video?.height;\n trackInfo.description = this.getVideoDescription(track);\n } else if (track.type === 'audio') {\n trackInfo.sampleRate = track.audio?.sample_rate || track.timescale;\n trackInfo.numberOfChannels = track.audio?.channel_count;\n trackInfo.description = this.getAudioDescription(track);\n }\n\n this.tracks.set(track.id, trackInfo);\n\n // Configure extraction - use Infinity to extract all samples\n // Note: onSamples will be called multiple times as data is appended,\n // but we don't need to manually request more batches\n this.mp4boxFile.setExtractionOptions(track.id, track, {\n nbSamples: Infinity,\n });\n }\n }\n\n private processSamples(trackId: number, samples: any[]): void {\n const track = this.tracks.get(trackId);\n if (!track) return;\n\n const timescale = track.timescale || 90000;\n for (const sample of samples) {\n const rawTimestamp = (sample.cts * 1000000) / timescale;\n const duration = (sample.duration * 1000000) / timescale;\n\n if (track.type === 'video') {\n if (!this.videoController) {\n console.error('[MP4Demuxer] videoController is null when trying to output chunk!');\n // Should not happen - stream should be created before appendBuffer\n return;\n }\n\n // Normalize timestamp: first frame starts at 0\n if (this.videoTimestampOffset === null) {\n this.videoTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.videoTimestampOffset;\n\n const chunk = new EncodedVideoChunk({\n type: sample.is_sync ? 'key' : 'delta',\n timestamp,\n duration,\n data: sample.data,\n });\n this.videoController.enqueue(chunk);\n } else if (track.type === 'audio') {\n // Skip audio processing if skipAudio enabled or no controller\n if (!this.audioController) {\n // Still release samples immediately to avoid memory accumulation in mp4box.js\n const last = samples[samples.length - 1].number;\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n return;\n }\n\n // Normalize timestamp: first frame starts at 0\n if (this.audioTimestampOffset === null) {\n this.audioTimestampOffset = rawTimestamp;\n }\n const timestamp = rawTimestamp - this.audioTimestampOffset;\n\n const chunk = new EncodedAudioChunk({\n type: 'key',\n timestamp,\n duration,\n data: sample.data,\n });\n this.audioController.enqueue(chunk);\n }\n }\n\n const last = samples[samples.length - 1].number;\n // Release memory immediately\n this.mp4boxFile.releaseUsedSamples(trackId, last + 1);\n }\n\n private getVideoDescription(track: any): ArrayBuffer | undefined {\n return MP4Demuxer.extractVideoDescription(this.mp4boxFile, track.id);\n }\n\n /**\n * Extract video description (avcC/hvcC/etc) for VideoDecoder configuration\n * Static method for reuse in other components\n */\n static extractVideoDescription(mp4boxFile: any, trackId: number): ArrayBuffer | undefined {\n try {\n const fullTrack = mp4boxFile.getTrackById(trackId);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n const box = entry.avcC ?? entry.hvcC ?? entry.av1C ?? entry.vpcC;\n if (box) {\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN // IMPORTANT: must be BIG_ENDIAN\n );\n box.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get video description:', error);\n }\n return undefined;\n }\n\n // private getVideoDescription(track: any): ArrayBuffer | undefined {\n // if (!this.mp4boxFile) return undefined;\n // return getVideoDescription(this.mp4boxFile, track);\n // }\n\n private getAudioDescription(track: any): ArrayBuffer | undefined {\n try {\n const fullTrack = this.mp4boxFile.getTrackById(track.id);\n for (const entry of fullTrack.mdia.minf.stbl.stsd.entries) {\n if (entry.esds) {\n // Use mp4box.js parsed structure to get AudioSpecificConfig\n // esds.esd.descs[0] = DecoderConfigDescriptor\n // esds.esd.descs[0].descs[0].data = DecoderSpecificInfo (AudioSpecificConfig)\n const decConfigDesc = entry.esds.esd?.descs?.[0];\n const decSpecInfo = decConfigDesc?.descs?.[0];\n\n if (decSpecInfo?.data) {\n // Convert Uint8Array to ArrayBuffer\n const data = decSpecInfo.data;\n if (data instanceof Uint8Array) {\n const buffer = data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength);\n return buffer instanceof ArrayBuffer ? buffer : undefined;\n } else if (Array.isArray(data)) {\n // Some versions of mp4box.js return Array instead of Uint8Array\n return new Uint8Array(data).buffer;\n }\n }\n\n console.warn('[MP4Demuxer] Could not extract AudioSpecificConfig from esds structure');\n return undefined;\n } else if (entry.dOps) {\n // For Opus, use the full dOps box\n const stream = new (MP4Box as any).DataStream(\n undefined,\n 0,\n (MP4Box as any).DataStream.BIG_ENDIAN\n );\n entry.dOps.write(stream);\n return new Uint8Array(stream.buffer.slice(8)).buffer;\n }\n }\n } catch (error) {\n console.error('Failed to get audio description:', error);\n }\n return undefined;\n }\n\n /**\n * Create readable stream for video chunks (output only)\n */\n createVideoStream(): ReadableStream<EncodedVideoChunk> {\n return new ReadableStream<EncodedVideoChunk>(\n {\n start: (ctrl) => {\n this.videoController = ctrl as any;\n },\n cancel: () => {\n this.videoController = undefined;\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Create readable stream for audio chunks (output only)\n */\n createAudioStream(): ReadableStream<EncodedAudioChunk> | null {\n if (this.skipAudio) {\n return null;\n }\n\n return new ReadableStream<EncodedAudioChunk>(\n {\n start: (ctrl) => {\n this.audioController = ctrl as any;\n },\n cancel: () => {\n this.audioController = undefined;\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n size: () => 1,\n }\n );\n }\n\n /**\n * Create writable stream for input data (single source)\n * This is the only stream that should receive the input data\n */\n createInputStream(): WritableStream<Uint8Array | ArrayBuffer> {\n return new WritableStream<Uint8Array | ArrayBuffer>(\n {\n write: (chunk) => {\n // Create a copy to avoid buffer reuse issues (required for mp4box 0.5.x)\n const chunkData = new Uint8Array(chunk);\n this.appendBuffer(chunkData);\n\n // Flush after each append to trigger sample extraction immediately\n this.mp4boxFile.flush();\n },\n close: async () => {\n // Trigger final MP4Box flush\n this.mp4boxFile.flush();\n\n // Wait for MP4Box to complete sample extraction (onSamples callbacks)\n await new Promise((resolve) => setTimeout(resolve, 100));\n this.videoController?.close();\n this.audioController?.close();\n },\n },\n {\n highWaterMark: this.demuxHighWaterMark,\n }\n );\n }\n\n appendBuffer(chunk: Uint8Array): void {\n const buffer = chunk.buffer as ArrayBuffer & { fileStart: number };\n buffer.fileStart = this.fileOffset;\n this.mp4boxFile.appendBuffer(buffer);\n this.fileOffset += chunk.byteLength;\n }\n\n /**\n * Get video track info if available\n */\n get videoTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'video');\n }\n\n /**\n * Get audio track info if available\n */\n get audioTrackInfo(): TrackInfo | undefined {\n return Array.from(this.tracks.values()).find((track) => track.type === 'audio');\n }\n\n destroy(): void {\n this.mp4boxFile?.stop();\n this.mp4boxFile = null;\n this.tracks.clear();\n this.isReady = false;\n this.videoTimestampOffset = null;\n this.audioTimestampOffset = null;\n }\n}\n"],"names":["last"],"mappings":"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"}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import "../../
|
|
1
|
+
import { MP4Box } from "../../utils/mp4box.js";
|
|
2
2
|
import { MP4Demuxer } from "./MP4Demuxer.js";
|
|
3
|
-
import { __exports as mp4box_all } from "../../_virtual/mp4box.all.js";
|
|
4
3
|
class MP4IndexParser {
|
|
5
4
|
/**
|
|
6
5
|
* Parse from streaming download
|
|
@@ -8,7 +7,7 @@ class MP4IndexParser {
|
|
|
8
7
|
* Can optionally extract first GOP for fast cover rendering
|
|
9
8
|
*/
|
|
10
9
|
async parseFromStream(stream, options) {
|
|
11
|
-
const mp4boxFile =
|
|
10
|
+
const mp4boxFile = MP4Box.createFile();
|
|
12
11
|
let resolveResult = null;
|
|
13
12
|
let rejectResult = null;
|
|
14
13
|
const audioChunks = [];
|
|
@@ -223,7 +222,7 @@ class MP4IndexParser {
|
|
|
223
222
|
* Parse from file/ArrayBuffer (for already cached resources)
|
|
224
223
|
*/
|
|
225
224
|
async parseFromFile(data) {
|
|
226
|
-
const mp4boxFile =
|
|
225
|
+
const mp4boxFile = MP4Box.createFile();
|
|
227
226
|
const indexPromise = new Promise((resolve, reject) => {
|
|
228
227
|
mp4boxFile.onError = (error) => {
|
|
229
228
|
reject(new Error(`MP4Box error: ${error}`));
|