@meframe/core 0.0.29 → 0.0.30-beta
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 -13
- package/dist/Meframe.d.ts.map +1 -1
- package/dist/Meframe.js +6 -100
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +35 -19
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +223 -134
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +15 -2
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +58 -38
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/cache/l2/L2Cache.d.ts.map +1 -1
- package/dist/cache/l2/L2Cache.js +5 -5
- package/dist/cache/l2/L2Cache.js.map +1 -1
- package/dist/cache/l2/L2OPFSStore.d.ts +37 -0
- package/dist/cache/l2/L2OPFSStore.d.ts.map +1 -0
- package/dist/cache/l2/L2OPFSStore.js +89 -0
- package/dist/cache/l2/L2OPFSStore.js.map +1 -0
- package/dist/cache/resource/AudioSampleCache.d.ts +52 -0
- package/dist/cache/resource/AudioSampleCache.d.ts.map +1 -0
- package/dist/cache/resource/AudioSampleCache.js +69 -0
- package/dist/cache/resource/AudioSampleCache.js.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts +65 -0
- package/dist/cache/resource/ImageBitmapCache.d.ts.map +1 -0
- package/dist/cache/resource/ImageBitmapCache.js +101 -0
- package/dist/cache/resource/ImageBitmapCache.js.map +1 -0
- package/dist/cache/resource/MP4IndexCache.d.ts +48 -0
- package/dist/cache/resource/MP4IndexCache.d.ts.map +1 -0
- package/dist/cache/resource/MP4IndexCache.js +104 -0
- package/dist/cache/resource/MP4IndexCache.js.map +1 -0
- package/dist/cache/resource/ResourceCache.d.ts +46 -0
- package/dist/cache/resource/ResourceCache.d.ts.map +1 -0
- package/dist/cache/resource/ResourceCache.js +92 -0
- package/dist/cache/resource/ResourceCache.js.map +1 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts +75 -0
- package/dist/cache/storage/indexeddb/ChunkRecordStore.d.ts.map +1 -0
- package/dist/cache/{l2/IndexedDBStore.js → storage/indexeddb/ChunkRecordStore.js} +3 -3
- package/dist/cache/storage/indexeddb/ChunkRecordStore.js.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts +54 -0
- package/dist/cache/storage/opfs/OPFSManager.d.ts.map +1 -0
- package/dist/cache/storage/opfs/OPFSManager.js +133 -0
- package/dist/cache/storage/opfs/OPFSManager.js.map +1 -0
- package/dist/cache/storage/opfs/types.d.ts +16 -0
- package/dist/cache/storage/opfs/types.d.ts.map +1 -0
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +21 -2
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/types.d.ts +28 -0
- package/dist/config/types.d.ts.map +1 -1
- package/dist/controllers/ExportController.d.ts +16 -0
- package/dist/controllers/ExportController.d.ts.map +1 -0
- package/dist/controllers/ExportController.js +44 -0
- package/dist/controllers/ExportController.js.map +1 -0
- package/dist/controllers/PlaybackController.d.ts +28 -4
- package/dist/controllers/PlaybackController.d.ts.map +1 -1
- package/dist/controllers/PlaybackController.js +116 -51
- package/dist/controllers/PlaybackController.js.map +1 -1
- package/dist/controllers/index.d.ts +2 -3
- package/dist/controllers/index.d.ts.map +1 -1
- package/dist/controllers/types.d.ts +0 -28
- package/dist/controllers/types.d.ts.map +1 -1
- package/dist/event/events.d.ts +8 -0
- package/dist/event/events.d.ts.map +1 -1
- package/dist/event/events.js +1 -0
- package/dist/event/events.js.map +1 -1
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +11 -6
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/RcFrame.d.ts +2 -0
- package/dist/model/RcFrame.d.ts.map +1 -1
- package/dist/model/RcFrame.js +3 -0
- package/dist/model/RcFrame.js.map +1 -1
- package/dist/orchestrator/ExportScheduler.d.ts +35 -0
- package/dist/orchestrator/ExportScheduler.d.ts.map +1 -0
- package/dist/orchestrator/ExportScheduler.js +241 -0
- package/dist/orchestrator/ExportScheduler.js.map +1 -0
- package/dist/orchestrator/GlobalAudioSession.d.ts +21 -7
- package/dist/orchestrator/GlobalAudioSession.d.ts.map +1 -1
- package/dist/orchestrator/GlobalAudioSession.js +132 -140
- package/dist/orchestrator/GlobalAudioSession.js.map +1 -1
- package/dist/orchestrator/OnDemandVideoSession.d.ts +73 -0
- package/dist/orchestrator/OnDemandVideoSession.d.ts.map +1 -0
- package/dist/orchestrator/OnDemandVideoSession.js +281 -0
- package/dist/orchestrator/OnDemandVideoSession.js.map +1 -0
- package/dist/orchestrator/Orchestrator.d.ts +22 -17
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +231 -297
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +3 -15
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/orchestrator/index.d.ts +0 -1
- package/dist/orchestrator/index.d.ts.map +1 -1
- package/dist/orchestrator/types.d.ts +4 -4
- package/dist/orchestrator/types.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts +1 -1
- package/dist/stages/compose/FilterProcessor.d.ts.map +1 -1
- package/dist/stages/compose/FilterProcessor.js +226 -0
- package/dist/stages/compose/FilterProcessor.js.map +1 -0
- package/dist/stages/compose/LayerRenderer.d.ts +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/LayerRenderer.js +270 -0
- package/dist/stages/compose/LayerRenderer.js.map +1 -0
- package/dist/stages/compose/TransitionProcessor.d.ts +1 -1
- package/dist/stages/compose/TransitionProcessor.d.ts.map +1 -1
- package/dist/stages/compose/TransitionProcessor.js +189 -0
- package/dist/stages/compose/TransitionProcessor.js.map +1 -0
- package/dist/stages/compose/VideoComposer.d.ts +4 -2
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/VideoComposer.js +229 -0
- package/dist/stages/compose/VideoComposer.js.map +1 -0
- package/dist/stages/compose/text-renderers/animation-utils.js +76 -0
- package/dist/stages/compose/text-renderers/animation-utils.js.map +1 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts +2 -2
- package/dist/stages/compose/text-renderers/basic-text-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/basic-text-renderer.js +93 -0
- package/dist/stages/compose/text-renderers/basic-text-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js +132 -0
- package/dist/stages/compose/text-renderers/character-ktv-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js +128 -0
- package/dist/stages/compose/text-renderers/word-by-word-renderer.js.map +1 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.d.ts.map +1 -1
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js +135 -0
- package/dist/stages/compose/text-renderers/word-fancy-renderer.js.map +1 -0
- package/dist/stages/compose/text-utils/locale-detector.js +16 -0
- package/dist/stages/compose/text-utils/locale-detector.js.map +1 -0
- package/dist/stages/compose/text-utils/text-metrics.js +21 -0
- package/dist/stages/compose/text-utils/text-metrics.js.map +1 -0
- package/dist/stages/compose/text-utils/text-wrapper.js +225 -0
- package/dist/stages/compose/text-utils/text-wrapper.js.map +1 -0
- package/dist/stages/compose/types.d.ts +2 -1
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.js +0 -3
- package/dist/stages/decode/BaseDecoder.js.map +1 -1
- package/dist/stages/demux/MP4Demuxer.d.ts +5 -0
- package/dist/stages/demux/MP4Demuxer.d.ts.map +1 -1
- package/dist/stages/demux/MP4Demuxer.js +281 -0
- package/dist/stages/demux/MP4Demuxer.js.map +1 -0
- package/dist/stages/demux/MP4IndexParser.d.ts +71 -0
- package/dist/stages/demux/MP4IndexParser.d.ts.map +1 -0
- package/dist/stages/demux/MP4IndexParser.js +416 -0
- package/dist/stages/demux/MP4IndexParser.js.map +1 -0
- package/dist/stages/demux/types.d.ts +48 -0
- package/dist/stages/demux/types.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.d.ts +44 -2
- package/dist/stages/load/ResourceLoader.d.ts.map +1 -1
- package/dist/stages/load/ResourceLoader.js +281 -37
- package/dist/stages/load/ResourceLoader.js.map +1 -1
- package/dist/stages/load/TaskManager.d.ts +6 -2
- package/dist/stages/load/TaskManager.d.ts.map +1 -1
- package/dist/stages/load/TaskManager.js +27 -4
- package/dist/stages/load/TaskManager.js.map +1 -1
- package/dist/stages/load/types.d.ts +7 -0
- package/dist/stages/load/types.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.d.ts +2 -2
- package/dist/stages/mux/MP4Muxer.d.ts.map +1 -1
- package/dist/stages/mux/MP4Muxer.js +24 -13
- package/dist/stages/mux/MP4Muxer.js.map +1 -1
- package/dist/stages/mux/MuxManager.d.ts +10 -21
- package/dist/stages/mux/MuxManager.d.ts.map +1 -1
- package/dist/stages/mux/MuxManager.js +21 -162
- package/dist/stages/mux/MuxManager.js.map +1 -1
- package/dist/stages/mux/index.d.ts +0 -1
- package/dist/stages/mux/index.d.ts.map +1 -1
- package/dist/utils/binary-search.d.ts +12 -4
- package/dist/utils/binary-search.d.ts.map +1 -1
- package/dist/utils/binary-search.js +52 -6
- package/dist/utils/binary-search.js.map +1 -1
- package/dist/workers/{BaseDecoder.BWYu1W0B.js → BaseDecoder.CTW-vr29.js} +1 -4
- package/dist/workers/BaseDecoder.CTW-vr29.js.map +1 -0
- package/dist/workers/{MP4Demuxer.lMOUMWFh.js → MP4Demuxer.BEa6PLJm.js} +9 -2
- package/dist/workers/{MP4Demuxer.lMOUMWFh.js.map → MP4Demuxer.BEa6PLJm.js.map} +1 -1
- package/dist/workers/stages/compose/{video-compose.worker.CIeEIJO7.js → video-compose.worker.DHQ8B105.js} +59 -31
- package/dist/workers/stages/compose/video-compose.worker.DHQ8B105.js.map +1 -0
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js → audio-decode.worker.CP8bXXa4.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.DnS17GD9.js.map → audio-decode.worker.CP8bXXa4.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js → video-decode.worker.BIspTxgV.js} +2 -2
- package/dist/workers/stages/decode/{video-decode.worker.BEYsjOXp.js.map → video-decode.worker.BIspTxgV.js.map} +1 -1
- package/dist/workers/stages/demux/{audio-demux.worker.DcurGC8i.js → audio-demux.worker._VRQdLdv.js} +2 -2
- package/dist/workers/stages/demux/{audio-demux.worker.DcurGC8i.js.map → audio-demux.worker._VRQdLdv.js.map} +1 -1
- package/dist/workers/stages/demux/{video-demux.worker.B1_wntU4.js → video-demux.worker.CSkxGtmx.js} +3 -19
- package/dist/workers/stages/demux/video-demux.worker.CSkxGtmx.js.map +1 -0
- package/dist/workers/worker-manifest.json +5 -5
- package/package.json +1 -1
- package/dist/cache/l2/IndexedDBStore.js.map +0 -1
- package/dist/cache/l2/OPFSStore.js +0 -131
- package/dist/cache/l2/OPFSStore.js.map +0 -1
- package/dist/controllers/PreRenderService.d.ts +0 -59
- package/dist/controllers/PreRenderService.d.ts.map +0 -1
- package/dist/controllers/PreRenderService.js +0 -185
- package/dist/controllers/PreRenderService.js.map +0 -1
- package/dist/controllers/PreRenderTaskQueue.d.ts +0 -21
- package/dist/controllers/PreRenderTaskQueue.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.d.ts +0 -70
- package/dist/orchestrator/ClipSessionManager.d.ts.map +0 -1
- package/dist/orchestrator/ClipSessionManager.js +0 -158
- package/dist/orchestrator/ClipSessionManager.js.map +0 -1
- package/dist/stages/decode/AudioChunkDecoder.js +0 -169
- package/dist/stages/decode/AudioChunkDecoder.js.map +0 -1
- package/dist/stages/mux/OPFSWriter.d.ts +0 -46
- package/dist/stages/mux/OPFSWriter.d.ts.map +0 -1
- package/dist/utils/BackpressureAdapter.d.ts +0 -26
- package/dist/utils/BackpressureAdapter.d.ts.map +0 -1
- package/dist/workers/BaseDecoder.BWYu1W0B.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.CIeEIJO7.js.map +0 -1
- package/dist/workers/stages/demux/video-demux.worker.B1_wntU4.js.map +0 -1
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import "../../node_modules/.pnpm/mp4box@0.5.4/node_modules/mp4box/dist/mp4box.all.js";
|
|
2
|
+
import { MP4Demuxer } from "./MP4Demuxer.js";
|
|
3
|
+
import { __exports as mp4box_all } from "../../_virtual/mp4box.all.js";
|
|
4
|
+
class MP4IndexParser {
|
|
5
|
+
/**
|
|
6
|
+
* Parse from streaming download
|
|
7
|
+
* Returns video index + all audio samples
|
|
8
|
+
* Can optionally extract first GOP for fast cover rendering
|
|
9
|
+
*/
|
|
10
|
+
async parseFromStream(stream, options) {
|
|
11
|
+
const mp4boxFile = mp4box_all.createFile();
|
|
12
|
+
let resolveResult = null;
|
|
13
|
+
let rejectResult = null;
|
|
14
|
+
const audioChunks = [];
|
|
15
|
+
let audioConfig;
|
|
16
|
+
let audioTrackId = null;
|
|
17
|
+
let expectedAudioSamples = 0;
|
|
18
|
+
let savedInfo = null;
|
|
19
|
+
const resultPromise = new Promise((resolve, reject) => {
|
|
20
|
+
resolveResult = resolve;
|
|
21
|
+
rejectResult = reject;
|
|
22
|
+
});
|
|
23
|
+
const firstGOPState = {
|
|
24
|
+
byteEnd: 0,
|
|
25
|
+
extracted: !options?.onFirstFrameReady,
|
|
26
|
+
index: null,
|
|
27
|
+
buffer: new Uint8Array(0)
|
|
28
|
+
};
|
|
29
|
+
const streamState = {
|
|
30
|
+
fileOffset: 0,
|
|
31
|
+
moovParsed: false,
|
|
32
|
+
audioComplete: false
|
|
33
|
+
};
|
|
34
|
+
mp4boxFile.onError = (error) => {
|
|
35
|
+
rejectResult?.(new Error(`MP4Box error: ${error}`));
|
|
36
|
+
};
|
|
37
|
+
mp4boxFile.onReady = (info) => {
|
|
38
|
+
try {
|
|
39
|
+
streamState.moovParsed = true;
|
|
40
|
+
savedInfo = info;
|
|
41
|
+
const index = this.buildIndex(mp4boxFile, info);
|
|
42
|
+
firstGOPState.index = index;
|
|
43
|
+
const isFastStart = info.isProgressive === true;
|
|
44
|
+
if (isFastStart && options?.onFirstFrameReady && index.tracks.video?.gopIndex[0]) {
|
|
45
|
+
const firstGOP = index.tracks.video.gopIndex[0];
|
|
46
|
+
const samples = index.tracks.video.samples;
|
|
47
|
+
const startIdx = firstGOP.keyframeSampleIndex;
|
|
48
|
+
const endIdx = startIdx + firstGOP.sampleCount;
|
|
49
|
+
const endSample = samples[endIdx - 1];
|
|
50
|
+
if (endSample) {
|
|
51
|
+
firstGOPState.byteEnd = endSample.byteOffset + endSample.byteLength;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
const { config, trackId, expectedSamples, isComplete } = this.setupAudioExtraction(
|
|
55
|
+
mp4boxFile,
|
|
56
|
+
info
|
|
57
|
+
);
|
|
58
|
+
audioConfig = config;
|
|
59
|
+
audioTrackId = trackId ?? null;
|
|
60
|
+
expectedAudioSamples = expectedSamples;
|
|
61
|
+
streamState.audioComplete = isComplete;
|
|
62
|
+
if (isComplete) {
|
|
63
|
+
setTimeout(() => {
|
|
64
|
+
if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {
|
|
65
|
+
resolveResult?.({ index });
|
|
66
|
+
}
|
|
67
|
+
}, 0);
|
|
68
|
+
}
|
|
69
|
+
} catch (error) {
|
|
70
|
+
rejectResult?.(error);
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
mp4boxFile.onSamples = (trackId, _user, samples) => {
|
|
74
|
+
if (trackId === audioTrackId && audioConfig) {
|
|
75
|
+
const timescale = audioConfig.sampleRate;
|
|
76
|
+
if (samples && samples.length > 0) {
|
|
77
|
+
for (const sample of samples) {
|
|
78
|
+
try {
|
|
79
|
+
const chunk = new EncodedAudioChunk({
|
|
80
|
+
type: sample.is_sync ? "key" : "delta",
|
|
81
|
+
timestamp: Math.round(sample.cts / timescale * 1e6),
|
|
82
|
+
duration: Math.round(sample.duration / timescale * 1e6),
|
|
83
|
+
data: sample.data
|
|
84
|
+
});
|
|
85
|
+
audioChunks.push(chunk);
|
|
86
|
+
} catch (error) {
|
|
87
|
+
console.warn("[MP4IndexParser] Failed to create audio chunk:", error);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (expectedAudioSamples !== Infinity && audioChunks.length >= expectedAudioSamples) {
|
|
92
|
+
streamState.audioComplete = true;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
await this.processStreamData(stream, mp4boxFile, options, firstGOPState, streamState);
|
|
97
|
+
mp4boxFile.flush();
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
99
|
+
if (streamState.moovParsed && streamState.audioComplete) {
|
|
100
|
+
const index = firstGOPState.index || this.buildIndex(mp4boxFile, savedInfo);
|
|
101
|
+
resolveResult({
|
|
102
|
+
index,
|
|
103
|
+
audioSamples: audioChunks,
|
|
104
|
+
audioMetadata: audioConfig
|
|
105
|
+
});
|
|
106
|
+
} else if (streamState.moovParsed && !streamState.audioComplete && savedInfo) {
|
|
107
|
+
streamState.audioComplete = true;
|
|
108
|
+
const index = this.buildIndex(mp4boxFile, savedInfo);
|
|
109
|
+
if (audioChunks.length > 0 && audioConfig) {
|
|
110
|
+
resolveResult({
|
|
111
|
+
index,
|
|
112
|
+
audioSamples: audioChunks,
|
|
113
|
+
audioMetadata: audioConfig
|
|
114
|
+
});
|
|
115
|
+
} else {
|
|
116
|
+
resolveResult({ index });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const parseTimeout = new Promise((_, reject) => {
|
|
120
|
+
setTimeout(() => {
|
|
121
|
+
reject(
|
|
122
|
+
new Error(
|
|
123
|
+
`MP4Box parsing timeout after reading ${streamState.fileOffset} bytes. moovParsed=${streamState.moovParsed}, audioExtractionComplete=${streamState.audioComplete}`
|
|
124
|
+
)
|
|
125
|
+
);
|
|
126
|
+
}, 5e3);
|
|
127
|
+
});
|
|
128
|
+
return await Promise.race([resultPromise, parseTimeout]);
|
|
129
|
+
}
|
|
130
|
+
async processStreamData(stream, mp4boxFile, options, firstGOPState, streamState) {
|
|
131
|
+
const reader = stream.getReader();
|
|
132
|
+
try {
|
|
133
|
+
let hasData = false;
|
|
134
|
+
while (true) {
|
|
135
|
+
const { done, value } = await reader.read();
|
|
136
|
+
if (done) {
|
|
137
|
+
break;
|
|
138
|
+
}
|
|
139
|
+
if (value) {
|
|
140
|
+
hasData = true;
|
|
141
|
+
const buffer = this.prepareBuffer(value, streamState.fileOffset);
|
|
142
|
+
this.handleFirstGOPExtraction(buffer, value, options, firstGOPState, streamState);
|
|
143
|
+
mp4boxFile.appendBuffer(buffer);
|
|
144
|
+
streamState.fileOffset += buffer.byteLength;
|
|
145
|
+
if (streamState.moovParsed && streamState.audioComplete && firstGOPState.extracted) {
|
|
146
|
+
break;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
if (!hasData) {
|
|
151
|
+
throw new Error("Empty stream received");
|
|
152
|
+
}
|
|
153
|
+
} finally {
|
|
154
|
+
reader.releaseLock();
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
prepareBuffer(value, fileOffset) {
|
|
158
|
+
let buffer;
|
|
159
|
+
if (value.byteOffset === 0 && value.byteLength === value.buffer.byteLength) {
|
|
160
|
+
buffer = value.buffer;
|
|
161
|
+
} else {
|
|
162
|
+
buffer = value.buffer.slice(
|
|
163
|
+
value.byteOffset,
|
|
164
|
+
value.byteOffset + value.byteLength
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
buffer.fileStart = fileOffset;
|
|
168
|
+
return buffer;
|
|
169
|
+
}
|
|
170
|
+
handleFirstGOPExtraction(buffer, value, options, firstGOPState, streamState) {
|
|
171
|
+
if (!firstGOPState.extracted && options?.onFirstFrameReady) {
|
|
172
|
+
if (firstGOPState.buffer.length + value.length < 10 * 1024 * 1024) {
|
|
173
|
+
const newBuffer = new Uint8Array(firstGOPState.buffer.length + value.length);
|
|
174
|
+
newBuffer.set(firstGOPState.buffer);
|
|
175
|
+
newBuffer.set(value, firstGOPState.buffer.length);
|
|
176
|
+
firstGOPState.buffer = newBuffer;
|
|
177
|
+
} else if (!streamState.moovParsed) {
|
|
178
|
+
firstGOPState.extracted = true;
|
|
179
|
+
firstGOPState.buffer = new Uint8Array(0);
|
|
180
|
+
}
|
|
181
|
+
if (streamState.moovParsed && firstGOPState.byteEnd > 0 && firstGOPState.index && streamState.fileOffset + buffer.byteLength >= firstGOPState.byteEnd) {
|
|
182
|
+
try {
|
|
183
|
+
const currentIndex = firstGOPState.index;
|
|
184
|
+
const videoTrack = currentIndex.tracks.video;
|
|
185
|
+
if (videoTrack) {
|
|
186
|
+
const chunks = this.extractFirstGOP(firstGOPState.buffer, videoTrack, 0);
|
|
187
|
+
options.onFirstFrameReady?.(currentIndex, chunks);
|
|
188
|
+
}
|
|
189
|
+
firstGOPState.extracted = true;
|
|
190
|
+
firstGOPState.buffer = new Uint8Array(0);
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.warn("[MP4IndexParser] Failed to extract first GOP:", error);
|
|
193
|
+
firstGOPState.extracted = true;
|
|
194
|
+
firstGOPState.buffer = new Uint8Array(0);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
setupAudioExtraction(mp4boxFile, info) {
|
|
200
|
+
const audioTrack = info.tracks.find((t) => t.type === "audio");
|
|
201
|
+
if (audioTrack) {
|
|
202
|
+
const trackId = audioTrack.id;
|
|
203
|
+
const config = {
|
|
204
|
+
codec: audioTrack.codec.startsWith("mp4a") ? "mp4a.40.2" : audioTrack.codec,
|
|
205
|
+
sampleRate: audioTrack.audio.sample_rate || audioTrack.timescale,
|
|
206
|
+
numberOfChannels: audioTrack.audio.channel_count
|
|
207
|
+
};
|
|
208
|
+
mp4boxFile.setExtractionOptions(trackId, null, {
|
|
209
|
+
nbSamples: Infinity
|
|
210
|
+
// Extract all samples
|
|
211
|
+
});
|
|
212
|
+
mp4boxFile.start();
|
|
213
|
+
return {
|
|
214
|
+
config,
|
|
215
|
+
trackId,
|
|
216
|
+
expectedSamples: audioTrack.nb_samples || Infinity,
|
|
217
|
+
isComplete: false
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
return { expectedSamples: 0, isComplete: true };
|
|
221
|
+
}
|
|
222
|
+
/**
|
|
223
|
+
* Parse from file/ArrayBuffer (for already cached resources)
|
|
224
|
+
*/
|
|
225
|
+
async parseFromFile(data) {
|
|
226
|
+
const mp4boxFile = mp4box_all.createFile();
|
|
227
|
+
const indexPromise = new Promise((resolve, reject) => {
|
|
228
|
+
mp4boxFile.onError = (error) => {
|
|
229
|
+
reject(new Error(`MP4Box error: ${error}`));
|
|
230
|
+
};
|
|
231
|
+
mp4boxFile.onReady = (info) => {
|
|
232
|
+
try {
|
|
233
|
+
const index = this.buildIndex(mp4boxFile, info);
|
|
234
|
+
resolve(index);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
reject(error);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
});
|
|
240
|
+
let buffer;
|
|
241
|
+
if (data instanceof File) {
|
|
242
|
+
buffer = await data.arrayBuffer();
|
|
243
|
+
} else {
|
|
244
|
+
buffer = data;
|
|
245
|
+
}
|
|
246
|
+
buffer.fileStart = 0;
|
|
247
|
+
mp4boxFile.appendBuffer(buffer);
|
|
248
|
+
mp4boxFile.flush();
|
|
249
|
+
return indexPromise;
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Build MP4Index from mp4box.js parsed data
|
|
253
|
+
*/
|
|
254
|
+
buildIndex(mp4boxFile, info) {
|
|
255
|
+
const index = {
|
|
256
|
+
resourceId: "",
|
|
257
|
+
// Will be set by caller
|
|
258
|
+
moovOffset: 0,
|
|
259
|
+
// mp4box doesn't expose this directly
|
|
260
|
+
moovSize: 0,
|
|
261
|
+
durationUs: info.duration / info.timescale * 1e6,
|
|
262
|
+
tracks: {}
|
|
263
|
+
};
|
|
264
|
+
for (const trackInfo of info.tracks) {
|
|
265
|
+
if (trackInfo.type === "video") {
|
|
266
|
+
index.tracks.video = this.buildVideoTrackIndex(mp4boxFile, trackInfo);
|
|
267
|
+
} else if (trackInfo.type === "audio") {
|
|
268
|
+
index.tracks.audio = this.buildAudioTrackIndex(mp4boxFile, trackInfo);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return index;
|
|
272
|
+
}
|
|
273
|
+
/**
|
|
274
|
+
* Build video track index with sample table and GOP boundaries
|
|
275
|
+
*/
|
|
276
|
+
buildVideoTrackIndex(mp4boxFile, trackInfo) {
|
|
277
|
+
const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);
|
|
278
|
+
const gopIndex = this.buildGOPIndex(samples);
|
|
279
|
+
const description = this.getVideoDescription(mp4boxFile, trackInfo);
|
|
280
|
+
return {
|
|
281
|
+
trackId: trackInfo.id,
|
|
282
|
+
codec: trackInfo.codec,
|
|
283
|
+
width: trackInfo.track_width || trackInfo.video?.width || 0,
|
|
284
|
+
height: trackInfo.track_height || trackInfo.video?.height || 0,
|
|
285
|
+
timescale: trackInfo.timescale,
|
|
286
|
+
description,
|
|
287
|
+
samples,
|
|
288
|
+
gopIndex
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
/**
|
|
292
|
+
* Build audio track index
|
|
293
|
+
*/
|
|
294
|
+
buildAudioTrackIndex(mp4boxFile, trackInfo) {
|
|
295
|
+
const samples = this.buildSampleTable(mp4boxFile, trackInfo.id, trackInfo.timescale);
|
|
296
|
+
return {
|
|
297
|
+
trackId: trackInfo.id,
|
|
298
|
+
codec: trackInfo.codec,
|
|
299
|
+
sampleRate: trackInfo.audio?.sample_rate || 48e3,
|
|
300
|
+
numberOfChannels: trackInfo.audio?.channel_count || 2,
|
|
301
|
+
timescale: trackInfo.timescale,
|
|
302
|
+
samples
|
|
303
|
+
};
|
|
304
|
+
}
|
|
305
|
+
/**
|
|
306
|
+
* Build sample table from mp4box track samples
|
|
307
|
+
*
|
|
308
|
+
* IMPORTANT: Keep samples in DTS (decode) order, not PTS (presentation) order!
|
|
309
|
+
* VideoDecoder requires chunks in decode order. It will output frames in PTS order.
|
|
310
|
+
*/
|
|
311
|
+
buildSampleTable(mp4boxFile, trackId, timescale) {
|
|
312
|
+
const samples = [];
|
|
313
|
+
const trak = mp4boxFile.getTrackById(trackId);
|
|
314
|
+
if (!trak) return samples;
|
|
315
|
+
const sampleTable = trak.samples || [];
|
|
316
|
+
let timestampOffset = null;
|
|
317
|
+
for (const sample of sampleTable) {
|
|
318
|
+
const durationUs = (sample.duration || 0) / timescale * 1e6;
|
|
319
|
+
const rawTimestampUs = (sample.cts || 0) / timescale * 1e6;
|
|
320
|
+
if (timestampOffset === null) {
|
|
321
|
+
timestampOffset = rawTimestampUs;
|
|
322
|
+
}
|
|
323
|
+
const timestampUs = rawTimestampUs - timestampOffset;
|
|
324
|
+
samples.push({
|
|
325
|
+
timestamp: timestampUs,
|
|
326
|
+
// Normalized PTS (display timestamp)
|
|
327
|
+
duration: durationUs,
|
|
328
|
+
byteOffset: sample.offset || 0,
|
|
329
|
+
byteLength: sample.size || 0,
|
|
330
|
+
isKeyframe: sample.is_sync || false
|
|
331
|
+
});
|
|
332
|
+
}
|
|
333
|
+
return samples;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Extract video description (avcC/hvcC/etc) for VideoDecoder
|
|
337
|
+
* Reuses MP4Demuxer.extractVideoDescription for consistency
|
|
338
|
+
*/
|
|
339
|
+
getVideoDescription(mp4boxFile, trackInfo) {
|
|
340
|
+
return MP4Demuxer.extractVideoDescription(mp4boxFile, trackInfo.id);
|
|
341
|
+
}
|
|
342
|
+
/**
|
|
343
|
+
* Build GOP index from samples
|
|
344
|
+
* GOP = Group of Pictures, starts with a keyframe
|
|
345
|
+
*/
|
|
346
|
+
buildGOPIndex(samples) {
|
|
347
|
+
const gopIndex = [];
|
|
348
|
+
let currentGOP = null;
|
|
349
|
+
for (let i = 0; i < samples.length; i++) {
|
|
350
|
+
const sample = samples[i];
|
|
351
|
+
if (!sample) continue;
|
|
352
|
+
if (sample.isKeyframe) {
|
|
353
|
+
if (currentGOP) {
|
|
354
|
+
gopIndex.push(currentGOP);
|
|
355
|
+
}
|
|
356
|
+
currentGOP = {
|
|
357
|
+
startTimeUs: sample.timestamp,
|
|
358
|
+
keyframeSampleIndex: i,
|
|
359
|
+
sampleCount: 1
|
|
360
|
+
};
|
|
361
|
+
} else if (currentGOP) {
|
|
362
|
+
currentGOP.sampleCount++;
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (currentGOP) {
|
|
366
|
+
gopIndex.push(currentGOP);
|
|
367
|
+
}
|
|
368
|
+
return gopIndex;
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Extract first GOP chunks from accumulated buffer
|
|
372
|
+
* Used for fast cover rendering during streaming download
|
|
373
|
+
*/
|
|
374
|
+
extractFirstGOP(buffer, videoTrack, byteStart) {
|
|
375
|
+
const chunks = [];
|
|
376
|
+
const firstGOP = videoTrack.gopIndex[0];
|
|
377
|
+
if (!firstGOP) {
|
|
378
|
+
return chunks;
|
|
379
|
+
}
|
|
380
|
+
const samples = videoTrack.samples;
|
|
381
|
+
for (let i = 0; i < firstGOP.sampleCount; i++) {
|
|
382
|
+
const sampleIdx = firstGOP.keyframeSampleIndex + i;
|
|
383
|
+
const sample = samples[sampleIdx];
|
|
384
|
+
if (!sample) continue;
|
|
385
|
+
const relativeOffset = sample.byteOffset - byteStart;
|
|
386
|
+
if (relativeOffset < 0 || relativeOffset + sample.byteLength > buffer.length) {
|
|
387
|
+
console.warn("[MP4IndexParser] Sample outside buffer:", {
|
|
388
|
+
sampleOffset: sample.byteOffset,
|
|
389
|
+
sampleLength: sample.byteLength,
|
|
390
|
+
byteStart,
|
|
391
|
+
relativeOffset,
|
|
392
|
+
bufferLength: buffer.length
|
|
393
|
+
});
|
|
394
|
+
continue;
|
|
395
|
+
}
|
|
396
|
+
const sampleData = buffer.slice(relativeOffset, relativeOffset + sample.byteLength);
|
|
397
|
+
try {
|
|
398
|
+
chunks.push(
|
|
399
|
+
new EncodedVideoChunk({
|
|
400
|
+
type: sample.isKeyframe ? "key" : "delta",
|
|
401
|
+
timestamp: sample.timestamp,
|
|
402
|
+
duration: sample.duration,
|
|
403
|
+
data: sampleData
|
|
404
|
+
})
|
|
405
|
+
);
|
|
406
|
+
} catch (error) {
|
|
407
|
+
console.warn("[MP4IndexParser] Failed to create EncodedVideoChunk:", error);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return chunks;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
export {
|
|
414
|
+
MP4IndexParser
|
|
415
|
+
};
|
|
416
|
+
//# sourceMappingURL=MP4IndexParser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MP4IndexParser.js","sources":["../../../src/stages/demux/MP4IndexParser.ts"],"sourcesContent":["import * as MP4Box from 'mp4box';\nimport type { MP4BoxFile } from 'mp4box';\nimport type { MP4Index, VideoTrackIndex, AudioTrackIndex, Sample, GOP } from './types';\nimport type { TimeUs } from '../../model/types';\nimport { MP4Demuxer } from './MP4Demuxer';\n\nexport interface MP4ParseResult {\n index: MP4Index;\n audioSamples?: EncodedAudioChunk[];\n audioMetadata?: AudioDecoderConfig;\n}\n\nexport interface ParseStreamOptions {\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 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 | null = null;\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, expectedSamples, isComplete } = this.setupAudioExtraction(\n mp4boxFile,\n info\n );\n audioConfig = config;\n audioTrackId = trackId ?? null;\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 const timescale = 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(stream, mp4boxFile, options, firstGOPState, streamState);\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 ): 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 Error('Empty stream received');\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) {\n const chunks = this.extractFirstGOP(firstGOPState.buffer, videoTrack, 0);\n options.onFirstFrameReady?.(currentIndex, chunks);\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 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 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: trackInfo.codec,\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 console.warn('[MP4IndexParser] Sample outside buffer:', {\n sampleOffset: sample.byteOffset,\n sampleLength: sample.byteLength,\n byteStart,\n relativeOffset,\n bufferLength: buffer.length,\n });\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 return chunks;\n }\n}\n"],"names":["MP4Box.createFile"],"mappings":"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"}
|
|
@@ -1,3 +1,51 @@
|
|
|
1
|
+
import { TimeUs } from '../../model/types';
|
|
2
|
+
|
|
3
|
+
export interface MP4Index {
|
|
4
|
+
resourceId: string;
|
|
5
|
+
moovOffset: number;
|
|
6
|
+
moovSize: number;
|
|
7
|
+
durationUs: TimeUs;
|
|
8
|
+
tracks: {
|
|
9
|
+
video?: VideoTrackIndex;
|
|
10
|
+
audio?: AudioTrackIndex;
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export interface VideoTrackIndex {
|
|
14
|
+
trackId: number;
|
|
15
|
+
codec: string;
|
|
16
|
+
width: number;
|
|
17
|
+
height: number;
|
|
18
|
+
timescale: number;
|
|
19
|
+
description?: ArrayBuffer;
|
|
20
|
+
samples: Sample[];
|
|
21
|
+
gopIndex: GOP[];
|
|
22
|
+
}
|
|
23
|
+
export interface AudioTrackIndex {
|
|
24
|
+
trackId: number;
|
|
25
|
+
codec: string;
|
|
26
|
+
sampleRate: number;
|
|
27
|
+
numberOfChannels: number;
|
|
28
|
+
timescale: number;
|
|
29
|
+
samples: Sample[];
|
|
30
|
+
}
|
|
31
|
+
export interface Sample {
|
|
32
|
+
timestamp: TimeUs;
|
|
33
|
+
duration: TimeUs;
|
|
34
|
+
byteOffset: number;
|
|
35
|
+
byteLength: number;
|
|
36
|
+
isKeyframe: boolean;
|
|
37
|
+
}
|
|
38
|
+
export interface GOP {
|
|
39
|
+
startTimeUs: TimeUs;
|
|
40
|
+
keyframeSampleIndex: number;
|
|
41
|
+
sampleCount: number;
|
|
42
|
+
}
|
|
43
|
+
export interface GOPRange {
|
|
44
|
+
byteStart: number;
|
|
45
|
+
byteEnd: number;
|
|
46
|
+
startTimeUs: TimeUs;
|
|
47
|
+
endTimeUs: TimeUs;
|
|
48
|
+
}
|
|
1
49
|
export interface DemuxConfig {
|
|
2
50
|
codec?: string;
|
|
3
51
|
trackId?: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/stages/demux/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -14,6 +14,11 @@ export declare class ResourceLoader {
|
|
|
14
14
|
private byteRangeResolver;
|
|
15
15
|
private blobCache;
|
|
16
16
|
private pendingTransfers;
|
|
17
|
+
private parsingIndexes;
|
|
18
|
+
private isPreloadingEnabled;
|
|
19
|
+
private preloadQueue;
|
|
20
|
+
private activePreloads;
|
|
21
|
+
private readonly IDLE_PRELOAD_CONCURRENCY;
|
|
17
22
|
constructor(options?: ResourceLoaderOptions);
|
|
18
23
|
/**
|
|
19
24
|
* Bind to Orchestrator event system
|
|
@@ -24,6 +29,9 @@ export declare class ResourceLoader {
|
|
|
24
29
|
*/
|
|
25
30
|
unbind(): void;
|
|
26
31
|
private handleModelSet;
|
|
32
|
+
setPreloadingEnabled(enabled: boolean): void;
|
|
33
|
+
startPreloading(): void;
|
|
34
|
+
private processPreloadQueue;
|
|
27
35
|
private enqueueLoad;
|
|
28
36
|
private registerPendingTransfer;
|
|
29
37
|
private processQueue;
|
|
@@ -32,14 +40,45 @@ export declare class ResourceLoader {
|
|
|
32
40
|
* Handles state management (loading → ready/error) for all resource types
|
|
33
41
|
*/
|
|
34
42
|
private startLoad;
|
|
43
|
+
/**
|
|
44
|
+
* Ensure MP4 index is parsed for a cached resource
|
|
45
|
+
*/
|
|
46
|
+
ensureIndexParsed(resourceId: string): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Create ReadableStream from OPFS file
|
|
49
|
+
* Reuses OPFSManager's underlying file access
|
|
50
|
+
*/
|
|
51
|
+
private createOPFSReadStream;
|
|
52
|
+
/**
|
|
53
|
+
* Load resource and cache to OPFS + parse moov + extract first frame
|
|
54
|
+
*
|
|
55
|
+
* Strategy: Parallel tee() approach for fast first frame
|
|
56
|
+
* - One stream writes to OPFS (background)
|
|
57
|
+
* - Another stream parses moov and extracts first GOP
|
|
58
|
+
* - First frame is decoded and cached immediately
|
|
59
|
+
*/
|
|
60
|
+
private loadWithOPFSCache;
|
|
61
|
+
/**
|
|
62
|
+
* Write resource stream to OPFS
|
|
63
|
+
*/
|
|
64
|
+
private writeToOPFS;
|
|
65
|
+
/**
|
|
66
|
+
* Parse moov from stream and cache index + audio samples + decode first frame
|
|
67
|
+
*/
|
|
68
|
+
private parseIndexFromStream;
|
|
35
69
|
/**
|
|
36
70
|
* Load text-based resources (json, text)
|
|
37
71
|
* Just download the content - state management is handled by startLoad()
|
|
38
72
|
*/
|
|
39
73
|
private loadTextResource;
|
|
40
74
|
/**
|
|
41
|
-
* Load image resource: fetch blob → create ImageBitmap →
|
|
75
|
+
* Load image resource: fetch blob → create ImageBitmap → cache in CacheManager
|
|
42
76
|
* Note: Images don't need streaming (typically < 5MB)
|
|
77
|
+
*
|
|
78
|
+
* Strategy for window cache architecture:
|
|
79
|
+
* - Store ImageBitmap in CacheManager.imageBitmapCache (main thread accessible)
|
|
80
|
+
* - OnDemandComposeSession retrieves from cache for composition
|
|
81
|
+
* - No longer transfer to Worker (composition is in main thread)
|
|
43
82
|
*/
|
|
44
83
|
private loadImageBitmap;
|
|
45
84
|
/**
|
|
@@ -48,13 +87,16 @@ export declare class ResourceLoader {
|
|
|
48
87
|
private fetchBlob;
|
|
49
88
|
/**
|
|
50
89
|
* Transfer ImageBitmap to VideoComposeWorker
|
|
90
|
+
* Legacy: Not used in window cache architecture (images accessed via CacheManager)
|
|
51
91
|
*/
|
|
52
|
-
private transferImageToWorker;
|
|
53
92
|
/**
|
|
54
93
|
* Transfer cached image to a session
|
|
55
94
|
* Creates new ImageBitmap from cached Blob and transfers to worker
|
|
56
95
|
*/
|
|
57
96
|
private transferCachedImage;
|
|
97
|
+
/**
|
|
98
|
+
* Transfer stream to demux worker (for audio files)
|
|
99
|
+
*/
|
|
58
100
|
private transferToDemuxWorker;
|
|
59
101
|
private updateResourceState;
|
|
60
102
|
fetch(resourceId?: string, options?: ResourceLoadOptions): Promise<void>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ResourceLoader.d.ts","sourceRoot":"","sources":["../../../src/stages/load/ResourceLoader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"ResourceLoader.d.ts","sourceRoot":"","sources":["../../../src/stages/load/ResourceLoader.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,YAAY,EAAE,mBAAmB,EAAE,QAAQ,EAAE,qBAAqB,EAAE,MAAM,SAAS,CAAC;AAUlG,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAC,CAAe;IACpC,OAAO,CAAC,KAAK,CAAC,CAAmB;IACjC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,aAAa,CAAgB;IACrC,OAAO,CAAC,aAAa,CAAC,CAAgB;IACtC,OAAO,CAAC,QAAQ,CAAC,CAA4B;IAC7C,OAAO,CAAC,aAAa,CAAC,CAAyD;IAC/E,OAAO,CAAC,iBAAiB,CAA0B;IACnD,OAAO,CAAC,SAAS,CAA2B;IAC5C,OAAO,CAAC,gBAAgB,CAA+B;IACvD,OAAO,CAAC,cAAc,CAAqB;IAG3C,OAAO,CAAC,mBAAmB,CAAQ;IACnC,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,cAAc,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,wBAAwB,CAAK;gBAElC,OAAO,CAAC,EAAE,qBAAqB;IAa3C;;OAEG;IACH,IAAI,CAAC,YAAY,EAAE,YAAY,GAAG,IAAI;IAWtC;;OAEG;IACH,MAAM,IAAI,IAAI;IAMd,OAAO,CAAC,cAAc;IAOtB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI;IAU5C,eAAe,IAAI,IAAI;IAmCvB,OAAO,CAAC,mBAAmB;IAqB3B,OAAO,CAAC,WAAW;IAqCnB,OAAO,CAAC,uBAAuB;IAU/B,OAAO,CAAC,YAAY;IAQpB;;;OAGG;YACW,SAAS;IAqDvB;;OAEG;IACG,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC1D;;;OAGG;YACW,oBAAoB;IAkBlC;;;;;;;OAOG;YACW,iBAAiB;IA6C/B;;OAEG;YACW,WAAW;IAMzB;;OAEG;YACW,oBAAoB;IAgElC;;;OAGG;YACW,gBAAgB;IAM9B;;;;;;;;OAQG;YACW,eAAe;IAiC7B;;OAEG;YACW,SAAS;IAUvB;;;OAGG;IAuBH;;;OAGG;YACW,mBAAmB;IAuBjC;;OAEG;YACW,qBAAqB;IAyBnC,OAAO,CAAC,mBAAmB;IAkBrB,KAAK,CAAC,UAAU,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoF9E,MAAM,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAKhC;;OAEG;IACH,iBAAiB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO;IAI9C,KAAK,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAOzB,MAAM,CAAC,UAAU,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAqB9E,IAAI,WAAW,IAAI,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAEvC;IAED,IAAI,SAAS,IAAI,QAAQ,EAAE,CAE1B;IAED,OAAO,IAAI,IAAI;CAOhB"}
|