@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
|
@@ -1,333 +1,233 @@
|
|
|
1
|
+
import { binarySearchOverlapping, binarySearchFirst } from "../../utils/binary-search.js";
|
|
2
|
+
import { extractPlanesFromAudioData } from "../../utils/audio-data.js";
|
|
1
3
|
class AudioL1Cache {
|
|
2
|
-
slots
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
reader.releaseLock();
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
this.addAudio(value);
|
|
18
|
-
await pump();
|
|
19
|
-
};
|
|
20
|
-
pump().catch((error) => {
|
|
21
|
-
console.error("[AudioL1Cache] stream error", error);
|
|
22
|
-
reader.releaseLock();
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
putClipAudioData(clipId, audioData, clipDurationUs) {
|
|
26
|
-
const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;
|
|
4
|
+
// Aligned with VideoL1Cache: array of discrete audio data slots per clip
|
|
5
|
+
audioDataByClip = /* @__PURE__ */ new Map();
|
|
6
|
+
// Unified window management (aligned with VideoL1Cache)
|
|
7
|
+
// All clips share the same global window center
|
|
8
|
+
windowCenter = 0;
|
|
9
|
+
// Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)
|
|
10
|
+
WINDOW_RADIUS = 5e6;
|
|
11
|
+
// ±5s
|
|
12
|
+
EVICT_THROTTLE_MS = 500;
|
|
13
|
+
lastEvictTime = 0;
|
|
14
|
+
putClipAudioData(clipId, audioData, _clipDurationUs, globalTimeUs) {
|
|
15
|
+
const numberOfChannels = audioData.numberOfChannels ?? 2;
|
|
27
16
|
const numberOfFrames = audioData.numberOfFrames ?? 0;
|
|
28
|
-
const sampleRate = audioData.sampleRate ??
|
|
17
|
+
const sampleRate = audioData.sampleRate ?? 48e3;
|
|
18
|
+
const audioTimestampUs = audioData.timestamp ?? 0;
|
|
19
|
+
const audioDurationUs = audioData.duration ?? Math.round(numberOfFrames / sampleRate * 1e6);
|
|
29
20
|
if (!numberOfChannels || !numberOfFrames) {
|
|
30
21
|
audioData.close();
|
|
31
22
|
return;
|
|
32
23
|
}
|
|
33
|
-
const planes =
|
|
24
|
+
const planes = extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);
|
|
34
25
|
audioData.close();
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
this.clipPCM.set(clipId, entry);
|
|
48
|
-
if (this.clipPCM.size > this.maxClips) {
|
|
49
|
-
this.evictLRU();
|
|
50
|
-
}
|
|
26
|
+
const slot = {
|
|
27
|
+
timestampUs: audioTimestampUs,
|
|
28
|
+
durationUs: audioDurationUs,
|
|
29
|
+
planes,
|
|
30
|
+
sampleRate,
|
|
31
|
+
numberOfChannels,
|
|
32
|
+
globalTimeUs
|
|
33
|
+
};
|
|
34
|
+
let slots = this.audioDataByClip.get(clipId);
|
|
35
|
+
if (!slots) {
|
|
36
|
+
slots = [];
|
|
37
|
+
this.audioDataByClip.set(clipId, slots);
|
|
51
38
|
}
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
39
|
+
const insertIndex = this.findInsertIndex(slots, audioTimestampUs);
|
|
40
|
+
if (insertIndex < slots.length && slots[insertIndex].timestampUs === audioTimestampUs) {
|
|
41
|
+
slots[insertIndex] = slot;
|
|
42
|
+
} else {
|
|
43
|
+
slots.splice(insertIndex, 0, slot);
|
|
56
44
|
}
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
45
|
+
}
|
|
46
|
+
getSlotsInWindow(clipId, startUs, endUs) {
|
|
47
|
+
const slots = this.audioDataByClip.get(clipId);
|
|
48
|
+
if (!slots || slots.length === 0) {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({
|
|
52
|
+
start: slot.timestampUs,
|
|
53
|
+
end: slot.timestampUs + slot.durationUs
|
|
54
|
+
}));
|
|
55
|
+
if (overlappingSlots.length === 0) {
|
|
56
|
+
return null;
|
|
69
57
|
}
|
|
70
|
-
|
|
58
|
+
return overlappingSlots;
|
|
71
59
|
}
|
|
72
60
|
getPCM(clipId, startUs, endUs) {
|
|
73
|
-
const
|
|
74
|
-
if (!
|
|
61
|
+
const slots = this.audioDataByClip.get(clipId);
|
|
62
|
+
if (!slots || slots.length === 0) {
|
|
75
63
|
return null;
|
|
76
64
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
const requestedDurationUs = endUs - startUs;
|
|
83
|
-
const durationUs = Math.min(requestedDurationUs, availableDurationUs);
|
|
84
|
-
if (durationUs <= 0) {
|
|
65
|
+
const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({
|
|
66
|
+
start: slot.timestampUs,
|
|
67
|
+
end: slot.timestampUs + slot.durationUs
|
|
68
|
+
}));
|
|
69
|
+
if (overlappingSlots.length === 0) {
|
|
85
70
|
return null;
|
|
86
71
|
}
|
|
87
|
-
const
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
72
|
+
const firstSlot = overlappingSlots[0];
|
|
73
|
+
const uniformSampleRate = firstSlot.sampleRate;
|
|
74
|
+
const uniformChannels = firstSlot.numberOfChannels;
|
|
75
|
+
const hasUniformRate = overlappingSlots.every(
|
|
76
|
+
(s) => s.sampleRate === uniformSampleRate && s.numberOfChannels === uniformChannels
|
|
77
|
+
);
|
|
78
|
+
if (!hasUniformRate) {
|
|
79
|
+
console.error(
|
|
80
|
+
`[AudioL1Cache] Inconsistent sample rates detected for clip ${clipId}:`,
|
|
81
|
+
overlappingSlots.map((s) => ({
|
|
82
|
+
timestamp: s.timestampUs,
|
|
83
|
+
sampleRate: s.sampleRate,
|
|
84
|
+
channels: s.numberOfChannels
|
|
85
|
+
}))
|
|
86
|
+
);
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
const requestedDurationUs = endUs - startUs;
|
|
90
|
+
const totalFrames = Math.ceil(requestedDurationUs / 1e6 * uniformSampleRate);
|
|
91
|
+
const result = Array.from(
|
|
92
|
+
{ length: uniformChannels },
|
|
93
|
+
() => new Float32Array(totalFrames)
|
|
94
|
+
);
|
|
95
|
+
for (const slot of overlappingSlots) {
|
|
96
|
+
const slotStartUs = slot.timestampUs;
|
|
97
|
+
const slotEndUs = slotStartUs + slot.durationUs;
|
|
98
|
+
const copyStartUs = Math.max(slotStartUs, startUs);
|
|
99
|
+
const copyEndUs = Math.min(slotEndUs, endUs);
|
|
100
|
+
if (copyStartUs >= copyEndUs) continue;
|
|
101
|
+
const srcOffsetFrames = Math.floor(
|
|
102
|
+
(copyStartUs - slotStartUs) / 1e6 * uniformSampleRate
|
|
103
|
+
);
|
|
104
|
+
const dstOffsetFrames = Math.floor((copyStartUs - startUs) / 1e6 * uniformSampleRate);
|
|
105
|
+
const copyFrameCount = Math.ceil((copyEndUs - copyStartUs) / 1e6 * uniformSampleRate);
|
|
106
|
+
for (let ch = 0; ch < uniformChannels; ch++) {
|
|
107
|
+
const srcPlane = slot.planes[ch];
|
|
108
|
+
const dstPlane = result[ch];
|
|
109
|
+
if (!srcPlane || !dstPlane) continue;
|
|
110
|
+
const actualCopyFrames = Math.min(
|
|
111
|
+
copyFrameCount,
|
|
112
|
+
srcPlane.length - srcOffsetFrames,
|
|
113
|
+
dstPlane.length - dstOffsetFrames
|
|
114
|
+
);
|
|
115
|
+
if (actualCopyFrames > 0) {
|
|
116
|
+
const srcSlice = srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames);
|
|
117
|
+
dstPlane.set(srcSlice, dstOffsetFrames);
|
|
118
|
+
}
|
|
100
119
|
}
|
|
101
|
-
result.push(channelData);
|
|
102
120
|
}
|
|
103
121
|
return result;
|
|
104
122
|
}
|
|
105
123
|
getPCMWithMetadata(clipId, startUs, endUs) {
|
|
106
|
-
const
|
|
107
|
-
if (!
|
|
124
|
+
const slots = this.audioDataByClip.get(clipId);
|
|
125
|
+
if (!slots || slots.length === 0) {
|
|
108
126
|
return null;
|
|
109
127
|
}
|
|
110
|
-
const
|
|
111
|
-
if (!
|
|
128
|
+
const planes = this.getPCM(clipId, startUs, endUs);
|
|
129
|
+
if (!planes) {
|
|
112
130
|
return null;
|
|
113
131
|
}
|
|
132
|
+
const firstSlot = slots[0];
|
|
114
133
|
return {
|
|
115
134
|
planes,
|
|
116
|
-
sampleRate:
|
|
117
|
-
numberOfChannels:
|
|
135
|
+
sampleRate: firstSlot.sampleRate,
|
|
136
|
+
numberOfChannels: firstSlot.numberOfChannels
|
|
118
137
|
};
|
|
119
138
|
}
|
|
120
139
|
hasClipPCM(clipId) {
|
|
121
|
-
return this.
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
140
|
+
return this.audioDataByClip.has(clipId);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Check if sufficient PCM data exists for the requested time window
|
|
144
|
+
* Returns true only if at least 80% of requested duration is available
|
|
145
|
+
*/
|
|
146
|
+
hasWindowData(clipId, startUs, endUs) {
|
|
147
|
+
const slots = this.audioDataByClip.get(clipId);
|
|
148
|
+
if (!slots || slots.length === 0) {
|
|
149
|
+
return false;
|
|
150
|
+
}
|
|
151
|
+
const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({
|
|
152
|
+
start: slot.timestampUs,
|
|
153
|
+
end: slot.timestampUs + slot.durationUs
|
|
154
|
+
}));
|
|
155
|
+
if (overlappingSlots.length === 0) {
|
|
156
|
+
return false;
|
|
157
|
+
}
|
|
158
|
+
let coveredDurationUs = 0;
|
|
159
|
+
const requestedDurationUs = endUs - startUs;
|
|
160
|
+
for (const slot of overlappingSlots) {
|
|
161
|
+
const slotEndUs = slot.timestampUs + slot.durationUs;
|
|
162
|
+
const overlapStart = Math.max(slot.timestampUs, startUs);
|
|
163
|
+
const overlapEnd = Math.min(slotEndUs, endUs);
|
|
164
|
+
if (overlapStart < overlapEnd) {
|
|
165
|
+
coveredDurationUs += overlapEnd - overlapStart;
|
|
133
166
|
}
|
|
134
167
|
}
|
|
135
|
-
|
|
136
|
-
this.clipPCM.delete(oldestClipId);
|
|
137
|
-
}
|
|
168
|
+
return coveredDurationUs >= requestedDurationUs * 0.8;
|
|
138
169
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
if (!plane) continue;
|
|
172
|
-
audioData.copyTo(plane, { planeIndex: channel, format: "f32-planar" });
|
|
173
|
-
}
|
|
174
|
-
return true;
|
|
170
|
+
clearClipPCM(clipId) {
|
|
171
|
+
this.audioDataByClip.delete(clipId);
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Update window center (unified global window)
|
|
175
|
+
* Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center
|
|
176
|
+
*/
|
|
177
|
+
setWindow(centerGlobalUs) {
|
|
178
|
+
this.windowCenter = centerGlobalUs;
|
|
179
|
+
this.checkEviction();
|
|
180
|
+
}
|
|
181
|
+
checkEviction() {
|
|
182
|
+
const now = Date.now();
|
|
183
|
+
if (now - this.lastEvictTime > this.EVICT_THROTTLE_MS) {
|
|
184
|
+
this.evictOutOfWindow();
|
|
185
|
+
this.lastEvictTime = now;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
/**
|
|
189
|
+
* Evict audio slots outside the global window (aligned with VideoL1Cache)
|
|
190
|
+
* Skip if eviction is disabled (e.g., during export)
|
|
191
|
+
*/
|
|
192
|
+
evictOutOfWindow() {
|
|
193
|
+
const windowStart = Math.max(0, this.windowCenter - this.WINDOW_RADIUS);
|
|
194
|
+
const windowEnd = this.windowCenter + this.WINDOW_RADIUS;
|
|
195
|
+
for (const [clipId, slots] of this.audioDataByClip) {
|
|
196
|
+
const toKeep = [];
|
|
197
|
+
for (const slot of slots) {
|
|
198
|
+
const globalTime = slot.globalTimeUs;
|
|
199
|
+
if (globalTime === void 0) {
|
|
200
|
+
toKeep.push(slot);
|
|
201
|
+
continue;
|
|
175
202
|
}
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
const plane = planes[channel];
|
|
179
|
-
if (!plane) continue;
|
|
180
|
-
audioData.copyTo(tmp, { planeIndex: channel, format: "s16-planar" });
|
|
181
|
-
for (let i = 0; i < numberOfFrames; i += 1) {
|
|
182
|
-
plane[i] = toFloat(tmp[i] ?? 0);
|
|
183
|
-
}
|
|
203
|
+
if (globalTime >= windowStart && globalTime <= windowEnd) {
|
|
204
|
+
toKeep.push(slot);
|
|
184
205
|
}
|
|
185
|
-
return true;
|
|
186
|
-
} catch {
|
|
187
|
-
return false;
|
|
188
206
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
const plane = planes[channel];
|
|
194
|
-
if (!plane) continue;
|
|
195
|
-
audioData.copyTo(plane, { planeIndex: channel });
|
|
196
|
-
}
|
|
197
|
-
return true;
|
|
198
|
-
} catch {
|
|
199
|
-
return false;
|
|
200
|
-
}
|
|
201
|
-
};
|
|
202
|
-
const reportedFormat = audioData.format;
|
|
203
|
-
const attempts = [];
|
|
204
|
-
const scheduled = /* @__PURE__ */ new Set();
|
|
205
|
-
const scheduleAttempt = (token, attempt) => {
|
|
206
|
-
if (!scheduled.has(token)) {
|
|
207
|
-
scheduled.add(token);
|
|
208
|
-
attempts.push(attempt);
|
|
209
|
-
}
|
|
210
|
-
};
|
|
211
|
-
if (reportedFormat) {
|
|
212
|
-
switch (reportedFormat) {
|
|
213
|
-
case "f32":
|
|
214
|
-
scheduleAttempt("f32", () => fillInterleaved("f32"));
|
|
215
|
-
break;
|
|
216
|
-
case "s16":
|
|
217
|
-
scheduleAttempt("s16", () => fillInterleaved("s16"));
|
|
218
|
-
break;
|
|
219
|
-
case "f32-planar":
|
|
220
|
-
scheduleAttempt("f32-planar", () => fillPlanar("f32-planar"));
|
|
221
|
-
break;
|
|
222
|
-
case "s16-planar":
|
|
223
|
-
scheduleAttempt("s16-planar", () => fillPlanar("s16-planar"));
|
|
224
|
-
break;
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
scheduleAttempt("f32", () => fillInterleaved("f32"));
|
|
228
|
-
scheduleAttempt("f32-planar", () => fillPlanar("f32-planar"));
|
|
229
|
-
scheduleAttempt("s16", () => fillInterleaved("s16"));
|
|
230
|
-
scheduleAttempt("s16-planar", () => fillPlanar("s16-planar"));
|
|
231
|
-
let filled = false;
|
|
232
|
-
for (const attempt of attempts) {
|
|
233
|
-
if (attempt()) {
|
|
234
|
-
filled = true;
|
|
235
|
-
break;
|
|
207
|
+
if (toKeep.length > 0) {
|
|
208
|
+
this.audioDataByClip.set(clipId, toKeep);
|
|
209
|
+
} else {
|
|
210
|
+
this.audioDataByClip.delete(clipId);
|
|
236
211
|
}
|
|
237
212
|
}
|
|
238
|
-
if (!filled) {
|
|
239
|
-
filled = fillFallback();
|
|
240
|
-
}
|
|
241
|
-
if (!filled) {
|
|
242
|
-
throw new Error("AudioL1Cache: unsupported AudioData format");
|
|
243
|
-
}
|
|
244
|
-
return planes;
|
|
245
213
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
return;
|
|
252
|
-
}
|
|
253
|
-
if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {
|
|
254
|
-
this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };
|
|
255
|
-
}
|
|
256
|
-
if (numberOfChannels !== this.metadata.numberOfChannels) {
|
|
257
|
-
this.metadata = { ...this.metadata, numberOfChannels };
|
|
258
|
-
}
|
|
259
|
-
const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);
|
|
260
|
-
this.slots.push({
|
|
261
|
-
timestampUs: audio.timestamp ?? 0,
|
|
262
|
-
durationUs: audio.duration ?? Math.round(numberOfFrames / this.metadata.sampleRate * 1e6),
|
|
263
|
-
planes
|
|
264
|
-
});
|
|
265
|
-
audio.close();
|
|
266
|
-
}
|
|
267
|
-
getClosest(timeUs) {
|
|
268
|
-
if (this.slots.length === 0) {
|
|
269
|
-
return null;
|
|
270
|
-
}
|
|
271
|
-
let closest = null;
|
|
272
|
-
let minDelta = Number.MAX_SAFE_INTEGER;
|
|
273
|
-
for (const slot of this.slots) {
|
|
274
|
-
const start = slot.timestampUs;
|
|
275
|
-
const end = start + slot.durationUs;
|
|
276
|
-
if (timeUs >= start && timeUs <= end) {
|
|
277
|
-
closest = slot;
|
|
278
|
-
break;
|
|
279
|
-
}
|
|
280
|
-
const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));
|
|
281
|
-
if (delta < minDelta) {
|
|
282
|
-
closest = slot;
|
|
283
|
-
minDelta = delta;
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
if (!closest) {
|
|
287
|
-
return null;
|
|
288
|
-
}
|
|
289
|
-
return {
|
|
290
|
-
...closest,
|
|
291
|
-
planes: this.applyGain(closest.planes),
|
|
292
|
-
metadata: this.metadata
|
|
293
|
-
};
|
|
214
|
+
/**
|
|
215
|
+
* Find insertion index for a new slot (aligned with VideoL1Cache)
|
|
216
|
+
*/
|
|
217
|
+
findInsertIndex(slots, timestamp) {
|
|
218
|
+
return binarySearchFirst(slots, (slot) => slot.timestampUs >= timestamp);
|
|
294
219
|
}
|
|
295
220
|
flush() {
|
|
296
|
-
this.
|
|
221
|
+
this.audioDataByClip.clear();
|
|
297
222
|
}
|
|
298
223
|
clear() {
|
|
299
224
|
this.flush();
|
|
300
|
-
this.
|
|
301
|
-
this.
|
|
302
|
-
this.volume = 1;
|
|
303
|
-
this.muted = false;
|
|
304
|
-
}
|
|
305
|
-
setPlaybackRate(_rate) {
|
|
306
|
-
}
|
|
307
|
-
setVolume(volume) {
|
|
308
|
-
this.volume = Math.max(0, Math.min(1, volume));
|
|
309
|
-
}
|
|
310
|
-
setMute(muted) {
|
|
311
|
-
this.muted = muted;
|
|
225
|
+
this.audioDataByClip.clear();
|
|
226
|
+
this.windowCenter = 0;
|
|
312
227
|
}
|
|
313
228
|
dispose() {
|
|
314
229
|
this.clear();
|
|
315
230
|
}
|
|
316
|
-
applyGain(planes) {
|
|
317
|
-
if (this.muted || this.volume === 0) {
|
|
318
|
-
return planes.map((plane) => new Float32Array(plane.length));
|
|
319
|
-
}
|
|
320
|
-
if (this.volume === 1) {
|
|
321
|
-
return planes.map((plane) => plane.slice());
|
|
322
|
-
}
|
|
323
|
-
return planes.map((plane) => {
|
|
324
|
-
const scaled = new Float32Array(plane.length);
|
|
325
|
-
for (let i = 0; i < plane.length; i += 1) {
|
|
326
|
-
scaled[i] = (plane[i] ?? 0) * this.volume;
|
|
327
|
-
}
|
|
328
|
-
return scaled;
|
|
329
|
-
});
|
|
330
|
-
}
|
|
331
231
|
}
|
|
332
232
|
export {
|
|
333
233
|
AudioL1Cache
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { AudioMetadata, AudioSlot } from './types';\nimport type { TimeUs } from '../../model/types';\n\ninterface PCMClipEntry {\n clipId: string;\n sampleRate: number;\n numberOfChannels: number;\n planes: Float32Array[];\n startUs: TimeUs;\n durationUs: TimeUs;\n lastAccessedAt: number;\n}\n\nexport class AudioL1Cache {\n private slots: AudioSlot[] = [];\n private clipPCM = new Map<string, PCMClipEntry>();\n private metadata: AudioMetadata = { sampleRate: 48_000, numberOfChannels: 2 };\n private volume = 1;\n private muted = false;\n private maxClips = 20;\n\n attachStream(stream: ReadableStream<AudioData>, metadata: AudioMetadata): void {\n this.metadata = metadata;\n const reader = stream.getReader();\n\n const pump = async (): Promise<void> => {\n const { done, value } = await reader.read();\n if (done) {\n reader.releaseLock();\n return;\n }\n\n this.addAudio(value);\n await pump();\n };\n\n pump().catch((error) => {\n console.error('[AudioL1Cache] stream error', error);\n reader.releaseLock();\n });\n }\n\n putClipAudioData(clipId: string, audioData: AudioData, clipDurationUs: TimeUs): void {\n const numberOfChannels = audioData.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? this.metadata.sampleRate;\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n const planes = this.extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n let entry = this.clipPCM.get(clipId);\n if (!entry) {\n entry = {\n clipId,\n sampleRate,\n numberOfChannels,\n planes: Array.from({ length: numberOfChannels }, () => new Float32Array(0)),\n startUs: 0, // Use clip-relative time (0-based), same as video cache\n durationUs: clipDurationUs,\n lastAccessedAt: Date.now(),\n };\n this.clipPCM.set(clipId, entry);\n\n if (this.clipPCM.size > this.maxClips) {\n this.evictLRU();\n }\n }\n\n // Calculate how many frames we should store based on clip duration\n const maxFrames = Math.ceil((clipDurationUs / 1_000_000) * sampleRate);\n const currentFrames = entry.planes[0]?.length ?? 0;\n\n // If we've already stored enough frames for the clip duration, skip\n if (currentFrames >= maxFrames) {\n return;\n }\n\n for (let channel = 0; channel < numberOfChannels; channel++) {\n const existingPlane = entry.planes[channel];\n const newPlane = planes[channel];\n if (!existingPlane || !newPlane) continue;\n\n // Calculate how many frames from newPlane we should actually append\n const framesToAppend = Math.min(newPlane.length, maxFrames - currentFrames);\n\n if (framesToAppend <= 0) {\n continue;\n }\n\n const combined = new Float32Array(existingPlane.length + framesToAppend);\n combined.set(existingPlane, 0);\n combined.set(newPlane.subarray(0, framesToAppend), existingPlane.length);\n entry.planes[channel] = combined;\n }\n\n entry.lastAccessedAt = Date.now();\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n entry.lastAccessedAt = Date.now();\n\n const offsetUs = Math.max(0, startUs - entry.startUs);\n\n // Calculate actual stored duration based on actual frames, not expected durationUs\n const actualStoredFrames = entry.planes[0]?.length ?? 0;\n const actualStoredUs = (actualStoredFrames / entry.sampleRate) * 1_000_000;\n const availableDurationUs = Math.max(0, actualStoredUs - offsetUs);\n\n const requestedDurationUs = endUs - startUs;\n const durationUs = Math.min(requestedDurationUs, availableDurationUs);\n\n if (durationUs <= 0) {\n return null;\n }\n\n const offsetFrames = Math.floor((offsetUs / 1_000_000) * entry.sampleRate);\n const frameCount = Math.ceil((durationUs / 1_000_000) * entry.sampleRate);\n\n const result: Float32Array[] = [];\n for (let channel = 0; channel < entry.numberOfChannels; channel++) {\n const plane = entry.planes[channel];\n if (!plane) {\n result.push(new Float32Array(frameCount));\n continue;\n }\n\n const channelData = new Float32Array(frameCount);\n const copyLength = Math.min(frameCount, plane.length - offsetFrames);\n for (let i = 0; i < copyLength; i++) {\n channelData[i] = plane[offsetFrames + i] ?? 0;\n }\n result.push(channelData);\n }\n\n return result;\n }\n\n getPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n const entry = this.clipPCM.get(clipId);\n if (!entry) {\n return null;\n }\n\n return {\n planes,\n sampleRate: entry.sampleRate,\n numberOfChannels: entry.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.clipPCM.has(clipId);\n }\n\n clearClipPCM(clipId: string): void {\n this.clipPCM.delete(clipId);\n }\n\n private evictLRU(): void {\n let oldestClipId: string | null = null;\n let oldestTime = Date.now();\n\n for (const [clipId, entry] of this.clipPCM) {\n if (entry.lastAccessedAt < oldestTime) {\n oldestTime = entry.lastAccessedAt;\n oldestClipId = clipId;\n }\n }\n\n if (oldestClipId) {\n this.clipPCM.delete(oldestClipId);\n }\n }\n\n private extractPlanesFromAudioData(\n audioData: AudioData,\n numberOfChannels: number,\n numberOfFrames: number\n ): Float32Array[] {\n const planes: Float32Array[] = Array.from(\n { length: numberOfChannels },\n () => new Float32Array(numberOfFrames)\n );\n\n const toFloat = (value: number): number => value / 32768;\n\n const fillInterleaved = (format: 'f32' | 's16'): boolean => {\n const samples =\n format === 'f32'\n ? new Float32Array(numberOfFrames * numberOfChannels)\n : new Int16Array(numberOfFrames * numberOfChannels);\n\n try {\n audioData.copyTo(samples, { format, planeIndex: 0 });\n } catch {\n return false;\n }\n\n for (let frame = 0; frame < numberOfFrames; frame += 1) {\n const offset = frame * numberOfChannels;\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n if (format === 'f32') {\n plane[frame] = (samples as Float32Array)[offset + channel] ?? 0;\n } else {\n plane[frame] = toFloat((samples as Int16Array)[offset + channel] ?? 0);\n }\n }\n }\n\n return true;\n };\n\n const fillPlanar = (format: 'f32-planar' | 's16-planar'): boolean => {\n try {\n if (format === 'f32-planar') {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel, format: 'f32-planar' });\n }\n return true;\n }\n\n const tmp = new Int16Array(numberOfFrames);\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(tmp, { planeIndex: channel, format: 's16-planar' as any });\n for (let i = 0; i < numberOfFrames; i += 1) {\n plane[i] = toFloat(tmp[i] ?? 0);\n }\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const fillFallback = (): boolean => {\n try {\n for (let channel = 0; channel < numberOfChannels; channel += 1) {\n const plane = planes[channel];\n if (!plane) continue;\n audioData.copyTo(plane, { planeIndex: channel });\n }\n return true;\n } catch {\n return false;\n }\n };\n\n const reportedFormat = (audioData as any).format as string | undefined;\n const attempts: Array<() => boolean> = [];\n const scheduled = new Set<string>();\n\n const scheduleAttempt = (token: string, attempt: () => boolean): void => {\n if (!scheduled.has(token)) {\n scheduled.add(token);\n attempts.push(attempt);\n }\n };\n\n if (reportedFormat) {\n switch (reportedFormat) {\n case 'f32':\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n break;\n case 's16':\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n break;\n case 'f32-planar':\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n break;\n case 's16-planar':\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n break;\n default:\n break;\n }\n }\n\n scheduleAttempt('f32', () => fillInterleaved('f32'));\n scheduleAttempt('f32-planar', () => fillPlanar('f32-planar'));\n scheduleAttempt('s16', () => fillInterleaved('s16'));\n scheduleAttempt('s16-planar', () => fillPlanar('s16-planar'));\n\n let filled = false;\n for (const attempt of attempts) {\n if (attempt()) {\n filled = true;\n break;\n }\n }\n\n if (!filled) {\n filled = fillFallback();\n }\n\n if (!filled) {\n throw new Error('AudioL1Cache: unsupported AudioData format');\n }\n\n return planes;\n }\n\n addAudio(audio: AudioData): void {\n const numberOfChannels = audio.numberOfChannels ?? this.metadata.numberOfChannels;\n const numberOfFrames = audio.numberOfFrames ?? 0;\n\n if (!numberOfChannels || !numberOfFrames) {\n audio.close();\n return;\n }\n\n if (audio.sampleRate && audio.sampleRate > 0 && audio.sampleRate !== this.metadata.sampleRate) {\n this.metadata = { ...this.metadata, sampleRate: audio.sampleRate };\n }\n\n if (numberOfChannels !== this.metadata.numberOfChannels) {\n this.metadata = { ...this.metadata, numberOfChannels };\n }\n\n const planes = this.extractPlanesFromAudioData(audio, numberOfChannels, numberOfFrames);\n\n this.slots.push({\n timestampUs: audio.timestamp ?? 0,\n durationUs:\n audio.duration ?? Math.round((numberOfFrames / this.metadata.sampleRate) * 1_000_000),\n planes,\n });\n\n audio.close();\n }\n\n getClosest(timeUs: number): (AudioSlot & { metadata: AudioMetadata }) | null {\n if (this.slots.length === 0) {\n return null;\n }\n\n let closest: AudioSlot | null = null;\n let minDelta = Number.MAX_SAFE_INTEGER;\n\n for (const slot of this.slots) {\n const start = slot.timestampUs;\n const end = start + slot.durationUs;\n if (timeUs >= start && timeUs <= end) {\n closest = slot;\n break;\n }\n\n const delta = Math.min(Math.abs(timeUs - start), Math.abs(timeUs - end));\n if (delta < minDelta) {\n closest = slot;\n minDelta = delta;\n }\n }\n\n if (!closest) {\n return null;\n }\n\n return {\n ...closest,\n planes: this.applyGain(closest.planes),\n metadata: this.metadata,\n };\n }\n\n flush(): void {\n this.slots = [];\n }\n\n clear(): void {\n this.flush();\n this.clipPCM.clear();\n this.metadata = { sampleRate: 48_000, numberOfChannels: 2 };\n this.volume = 1;\n this.muted = false;\n }\n\n setPlaybackRate(_rate: number): void {\n // Reserved for future use\n }\n\n setVolume(volume: number): void {\n this.volume = Math.max(0, Math.min(1, volume));\n }\n\n setMute(muted: boolean): void {\n this.muted = muted;\n }\n\n dispose(): void {\n this.clear();\n }\n\n private applyGain(planes: Float32Array[]): Float32Array[] {\n if (this.muted || this.volume === 0) {\n return planes.map((plane) => new Float32Array(plane.length));\n }\n\n if (this.volume === 1) {\n return planes.map((plane) => plane.slice());\n }\n\n return planes.map((plane) => {\n const scaled = new Float32Array(plane.length);\n for (let i = 0; i < plane.length; i += 1) {\n scaled[i] = (plane[i] ?? 0) * this.volume;\n }\n return scaled;\n });\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AudioL1Cache.js","sources":["../../../src/cache/l1/AudioL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { binarySearchFirst, binarySearchOverlapping } from '../../utils/binary-search';\nimport { extractPlanesFromAudioData } from '../../utils/audio-data';\n\ninterface AudioDataSlot {\n timestampUs: TimeUs; // Clip-relative timestamp\n durationUs: TimeUs;\n planes: Float32Array[]; // PCM data for this slot\n sampleRate: number;\n numberOfChannels: number;\n globalTimeUs?: TimeUs; // Global timeline time (for window management)\n}\n\nexport type { AudioDataSlot };\n\nexport class AudioL1Cache {\n // Aligned with VideoL1Cache: array of discrete audio data slots per clip\n private audioDataByClip = new Map<string, AudioDataSlot[]>();\n\n // Unified window management (aligned with VideoL1Cache)\n // All clips share the same global window center\n private windowCenter: TimeUs = 0;\n\n // Window radius aligned with video (±3.5s, but we use 5s for audio safety margin)\n private readonly WINDOW_RADIUS = 5_000_000; // ±5s\n private readonly EVICT_THROTTLE_MS = 500;\n private lastEvictTime = 0;\n\n putClipAudioData(\n clipId: string,\n audioData: AudioData,\n _clipDurationUs: TimeUs,\n globalTimeUs?: TimeUs\n ): void {\n const numberOfChannels = audioData.numberOfChannels ?? 2;\n const numberOfFrames = audioData.numberOfFrames ?? 0;\n const sampleRate = audioData.sampleRate ?? 48_000;\n const audioTimestampUs = audioData.timestamp ?? 0;\n const audioDurationUs =\n audioData.duration ?? Math.round((numberOfFrames / sampleRate) * 1_000_000);\n\n if (!numberOfChannels || !numberOfFrames) {\n audioData.close();\n return;\n }\n\n // Extract PCM data\n const planes = extractPlanesFromAudioData(audioData, numberOfChannels, numberOfFrames);\n audioData.close();\n\n // Create audio data slot (aligned with video architecture)\n const slot: AudioDataSlot = {\n timestampUs: audioTimestampUs,\n durationUs: audioDurationUs,\n planes,\n sampleRate,\n numberOfChannels,\n globalTimeUs,\n };\n // Get or create slots array for this clip\n let slots = this.audioDataByClip.get(clipId);\n if (!slots) {\n slots = [];\n this.audioDataByClip.set(clipId, slots);\n }\n\n // Insert slot in sorted order (aligned with VideoL1Cache.addFrame)\n const insertIndex = this.findInsertIndex(slots, audioTimestampUs);\n\n // Check for duplicate timestamp - replace if exists\n if (insertIndex < slots.length && slots[insertIndex]!.timestampUs === audioTimestampUs) {\n // Replace existing slot at same timestamp\n slots[insertIndex] = slot;\n } else {\n // Insert new slot\n slots.splice(insertIndex, 0, slot);\n }\n }\n\n getSlotsInWindow(clipId: string, startUs: TimeUs, endUs: TimeUs): AudioDataSlot[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n return overlappingSlots;\n }\n\n getPCM(clipId: string, startUs: TimeUs, endUs: TimeUs): Float32Array[] | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n // Use binary search to find overlapping slots (O(log n + k) vs O(n))\n // Aligned with video GOP/frame search\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return null;\n }\n\n // Validate sample rate consistency across all slots\n const firstSlot = overlappingSlots[0]!;\n const uniformSampleRate = firstSlot.sampleRate;\n const uniformChannels = firstSlot.numberOfChannels;\n\n // Check if all slots have the same sample rate and channel count\n const hasUniformRate = overlappingSlots.every(\n (s) => s.sampleRate === uniformSampleRate && s.numberOfChannels === uniformChannels\n );\n\n if (!hasUniformRate) {\n console.error(\n `[AudioL1Cache] Inconsistent sample rates detected for clip ${clipId}:`,\n overlappingSlots.map((s) => ({\n timestamp: s.timestampUs,\n sampleRate: s.sampleRate,\n channels: s.numberOfChannels,\n }))\n );\n // Return null to avoid corrupted audio data\n // This will trigger re-decode with correct sample rate\n return null;\n }\n\n // Calculate total frame count needed\n const requestedDurationUs = endUs - startUs;\n const totalFrames = Math.ceil((requestedDurationUs / 1_000_000) * uniformSampleRate);\n\n // Initialize result arrays\n const result: Float32Array[] = Array.from(\n { length: uniformChannels },\n () => new Float32Array(totalFrames)\n );\n\n // Copy data from each overlapping slot\n for (const slot of overlappingSlots) {\n const slotStartUs = slot.timestampUs;\n const slotEndUs = slotStartUs + slot.durationUs;\n\n // Calculate intersection with requested range\n const copyStartUs = Math.max(slotStartUs, startUs);\n const copyEndUs = Math.min(slotEndUs, endUs);\n\n if (copyStartUs >= copyEndUs) continue;\n\n // Convert time to frame indices (all slots have same sample rate after validation)\n const srcOffsetFrames = Math.floor(\n ((copyStartUs - slotStartUs) / 1_000_000) * uniformSampleRate\n );\n const dstOffsetFrames = Math.floor(((copyStartUs - startUs) / 1_000_000) * uniformSampleRate);\n const copyFrameCount = Math.ceil(((copyEndUs - copyStartUs) / 1_000_000) * uniformSampleRate);\n\n // Copy each channel\n for (let ch = 0; ch < uniformChannels; ch++) {\n const srcPlane = slot.planes[ch];\n const dstPlane = result[ch];\n if (!srcPlane || !dstPlane) continue;\n\n // Boundary check\n const actualCopyFrames = Math.min(\n copyFrameCount,\n srcPlane.length - srcOffsetFrames,\n dstPlane.length - dstOffsetFrames\n );\n\n if (actualCopyFrames > 0) {\n const srcSlice = srcPlane.subarray(srcOffsetFrames, srcOffsetFrames + actualCopyFrames);\n dstPlane.set(srcSlice, dstOffsetFrames);\n }\n }\n }\n\n return result;\n }\n\n getPCMWithMetadata(\n clipId: string,\n startUs: TimeUs,\n endUs: TimeUs\n ): { planes: Float32Array[]; sampleRate: number; numberOfChannels: number } | null {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return null;\n }\n\n const planes = this.getPCM(clipId, startUs, endUs);\n if (!planes) {\n return null;\n }\n\n // Use first slot's metadata\n const firstSlot = slots[0]!;\n return {\n planes,\n sampleRate: firstSlot.sampleRate,\n numberOfChannels: firstSlot.numberOfChannels,\n };\n }\n\n hasClipPCM(clipId: string): boolean {\n return this.audioDataByClip.has(clipId);\n }\n\n /**\n * Check if sufficient PCM data exists for the requested time window\n * Returns true only if at least 80% of requested duration is available\n */\n hasWindowData(clipId: string, startUs: TimeUs, endUs: TimeUs): boolean {\n const slots = this.audioDataByClip.get(clipId);\n if (!slots || slots.length === 0) {\n return false;\n }\n\n // Use binary search to find overlapping slots (performance optimization)\n const overlappingSlots = binarySearchOverlapping(slots, startUs, endUs, (slot) => ({\n start: slot.timestampUs,\n end: slot.timestampUs + slot.durationUs,\n }));\n\n if (overlappingSlots.length === 0) {\n return false;\n }\n\n // Calculate total duration covered\n let coveredDurationUs = 0;\n const requestedDurationUs = endUs - startUs;\n\n for (const slot of overlappingSlots) {\n const slotEndUs = slot.timestampUs + slot.durationUs;\n\n // Calculate overlap with requested range\n const overlapStart = Math.max(slot.timestampUs, startUs);\n const overlapEnd = Math.min(slotEndUs, endUs);\n\n if (overlapStart < overlapEnd) {\n coveredDurationUs += overlapEnd - overlapStart;\n }\n }\n\n // Consider window data sufficient if we have at least 80% coverage\n return coveredDurationUs >= requestedDurationUs * 0.8;\n }\n\n clearClipPCM(clipId: string): void {\n this.audioDataByClip.delete(clipId);\n }\n\n /**\n * Update window center (unified global window)\n * Aligned with VideoL1Cache strategy: maintains a window of ±RADIUS around center\n */\n setWindow(centerGlobalUs: TimeUs): void {\n this.windowCenter = centerGlobalUs;\n this.checkEviction();\n }\n\n private checkEviction(): void {\n const now = Date.now();\n if (now - this.lastEvictTime > this.EVICT_THROTTLE_MS) {\n this.evictOutOfWindow();\n this.lastEvictTime = now;\n }\n }\n\n /**\n * Evict audio slots outside the global window (aligned with VideoL1Cache)\n * Skip if eviction is disabled (e.g., during export)\n */\n private evictOutOfWindow(): void {\n const windowStart = Math.max(0, this.windowCenter - this.WINDOW_RADIUS);\n const windowEnd = this.windowCenter + this.WINDOW_RADIUS;\n\n for (const [clipId, slots] of this.audioDataByClip) {\n const toKeep: AudioDataSlot[] = [];\n\n for (const slot of slots) {\n const globalTime = slot.globalTimeUs;\n\n // Slots without globalTimeUs are kept (legacy)\n if (globalTime === undefined) {\n toKeep.push(slot);\n continue;\n }\n\n // Keep slots within window\n if (globalTime >= windowStart && globalTime <= windowEnd) {\n toKeep.push(slot);\n }\n // Slots outside window are discarded (no close needed for Float32Array)\n }\n\n if (toKeep.length > 0) {\n this.audioDataByClip.set(clipId, toKeep);\n } else {\n this.audioDataByClip.delete(clipId);\n }\n }\n }\n\n /**\n * Find insertion index for a new slot (aligned with VideoL1Cache)\n */\n private findInsertIndex(slots: AudioDataSlot[], timestamp: TimeUs): number {\n return binarySearchFirst(slots, (slot) => slot.timestampUs >= timestamp);\n }\n\n flush(): void {\n this.audioDataByClip.clear();\n }\n\n clear(): void {\n this.flush();\n this.audioDataByClip.clear();\n this.windowCenter = 0;\n }\n\n dispose(): void {\n this.clear();\n }\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -51,20 +51,22 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
|
|
|
51
51
|
private updateTime;
|
|
52
52
|
/**
|
|
53
53
|
* Initialize window at given time (called on play/seek)
|
|
54
|
+
* Sets unified window for both video and audio
|
|
54
55
|
*/
|
|
55
56
|
private initWindow;
|
|
56
57
|
/**
|
|
57
58
|
* Check if approaching window end and trigger preheat for next window
|
|
58
59
|
*
|
|
59
|
-
* Strategy:
|
|
60
|
+
* Strategy: Unified sliding window for both video and audio
|
|
60
61
|
* - Current window: [windowStart, windowEnd] (3s duration)
|
|
61
62
|
* - When playback reaches windowEnd - 1s, preheat next window
|
|
62
63
|
* - Next window: [windowEnd, windowEnd + 3s]
|
|
63
64
|
*/
|
|
64
65
|
private checkAndPreheatWindow;
|
|
65
66
|
/**
|
|
66
|
-
* Preheat next window by decoding from current
|
|
67
|
+
* Preheat next window by decoding from current playback time
|
|
67
68
|
* Updates windowStart and windowEnd after preheat completes
|
|
69
|
+
* Preheats both video and audio in parallel
|
|
68
70
|
*/
|
|
69
71
|
private preheatNextWindow;
|
|
70
72
|
renderCurrentFrame(timeUs: TimeUs): Promise<void>;
|
|
@@ -72,6 +74,7 @@ export declare class PlaybackController implements IPlaybackController, PreviewH
|
|
|
72
74
|
private clampTime;
|
|
73
75
|
dispose(): void;
|
|
74
76
|
private onCacheCover;
|
|
77
|
+
private onModelSet;
|
|
75
78
|
private setupEventListeners;
|
|
76
79
|
private ensureAudioContext;
|
|
77
80
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,aAAa,CAA8B;IAGnD,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAAa;IAGhC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,oBAAoB,CAAS;IAGrC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAa;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;
|
|
1
|
+
{"version":3,"file":"PlaybackController.d.ts","sourceRoot":"","sources":["../../src/controllers/PlaybackController.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,mBAAmB,EAEnB,eAAe,EACf,SAAS,EACT,aAAa,EACb,MAAM,EACP,MAAM,SAAS,CAAC;AAEjB,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oCAAoC,CAAC;AAC7E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAIpD;;;GAGG;AACH,qBAAa,kBAAmB,YAAW,mBAAmB,EAAE,aAAa;IAC3E,OAAO,CAAC,YAAY,CAAe;IACnC,OAAO,CAAC,QAAQ,CAAY;IAC5B,OAAO,CAAC,MAAM,CAAsC;IACpD,OAAO,CAAC,aAAa,CAA8B;IAGnD,aAAa,EAAE,MAAM,CAAK;IAC1B,OAAO,CAAC,KAAK,CAAyB;IACtC,OAAO,CAAC,YAAY,CAAO;IAC3B,OAAO,CAAC,MAAM,CAAO;IACrB,OAAO,CAAC,IAAI,CAAS;IAGrB,OAAO,CAAC,KAAK,CAAuB;IACpC,OAAO,CAAC,WAAW,CAAa;IAGhC,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,GAAG,CAAK;IAChB,OAAO,CAAC,YAAY,CAA6B;IACjD,OAAO,CAAC,YAAY,CAAmC;IAGvD,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,aAAa,CAAK;IAC1B,OAAO,CAAC,oBAAoB,CAAS;IAGrC,OAAO,CAAC,SAAS,CAAa;IAC9B,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAa;IAC7C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAa;IAC9C,OAAO,CAAC,iBAAiB,CAAS;gBAEtB,YAAY,EAAE,YAAY,EAAE,QAAQ,EAAE,SAAS,EAAE,OAAO,EAAE,eAAe;IA0C/E,WAAW,IAAI,OAAO,CAAC,IAAI,CAAC;IAKlC,IAAI,IAAI,IAAI;YAYE,aAAa;IAoC3B,KAAK,IAAI,IAAI;IAgBb,IAAI,IAAI,IAAI;IAuBN,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAyDzC,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI;IAW3B,SAAS,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI;IAO/B,OAAO,CAAC,KAAK,EAAE,OAAO,GAAG,IAAI;IAQ7B,OAAO,CAAC,IAAI,EAAE,OAAO,GAAG,IAAI;IAI5B,IAAI,QAAQ,IAAI,MAAM,CAOrB;IAED,IAAI,SAAS,IAAI,OAAO,CAEvB;IAED,eAAe,CAAC,OAAO,EAAE,kBAAkB,GAAG,IAAI;IAKlD,MAAM,IAAI,IAAI;IAId,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAIxD,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,GAAG,KAAK,IAAI,GAAG,IAAI;IAKzD,OAAO,CAAC,YAAY;IAqEpB,OAAO,CAAC,UAAU;IAiClB;;;OAGG;IACH,OAAO,CAAC,UAAU;IAQlB;;;;;;;OAOG;IACH,OAAO,CAAC,qBAAqB;IAuB7B;;;;OAIG;YACW,iBAAiB;IAsBzB,kBAAkB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;YAiCzC,uBAAuB;IA2DrC,OAAO,CAAC,SAAS;IAKjB,OAAO,IAAI,IAAI;IAUf,OAAO,CAAC,YAAY,CASlB;IAEF,OAAO,CAAC,UAAU,CAmBhB;IAEF,OAAO,CAAC,mBAAmB;YAKb,kBAAkB;CAOjC"}
|