@meframe/core 0.0.25 → 0.0.27
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.map +1 -1
- package/dist/Meframe.js +1 -1
- package/dist/Meframe.js.map +1 -1
- package/dist/cache/CacheManager.d.ts +1 -4
- package/dist/cache/CacheManager.d.ts.map +1 -1
- package/dist/cache/CacheManager.js +3 -13
- package/dist/cache/CacheManager.js.map +1 -1
- package/dist/cache/index.d.ts +1 -1
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.d.ts +7 -40
- package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
- package/dist/cache/l1/VideoL1Cache.js +63 -251
- package/dist/cache/l1/VideoL1Cache.js.map +1 -1
- package/dist/cache/l1/types.d.ts +0 -2
- package/dist/cache/l1/types.d.ts.map +1 -1
- package/dist/cache/types.d.ts +0 -15
- package/dist/cache/types.d.ts.map +1 -1
- package/dist/model/CompositionModel.d.ts +7 -3
- package/dist/model/CompositionModel.d.ts.map +1 -1
- package/dist/model/CompositionModel.js +13 -6
- package/dist/model/CompositionModel.js.map +1 -1
- package/dist/model/RcFrame.d.ts +0 -4
- package/dist/model/RcFrame.d.ts.map +1 -1
- package/dist/model/RcFrame.js +0 -6
- package/dist/model/RcFrame.js.map +1 -1
- package/dist/model/patch.js +22 -59
- package/dist/model/patch.js.map +1 -1
- package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
- package/dist/orchestrator/Orchestrator.js +29 -39
- package/dist/orchestrator/Orchestrator.js.map +1 -1
- package/dist/orchestrator/VideoClipSession.d.ts +4 -5
- package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
- package/dist/orchestrator/VideoClipSession.js +32 -35
- package/dist/orchestrator/VideoClipSession.js.map +1 -1
- package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
- package/dist/stages/compose/VideoComposer.d.ts +1 -7
- package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
- package/dist/stages/compose/types.d.ts +1 -9
- package/dist/stages/compose/types.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.d.ts.map +1 -1
- package/dist/stages/decode/BaseDecoder.js +5 -0
- package/dist/stages/decode/BaseDecoder.js.map +1 -1
- package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -4
- package/dist/stages/decode/VideoChunkDecoder.d.ts.map +1 -1
- package/dist/stages/decode/VideoChunkDecoder.js +3 -27
- package/dist/stages/decode/VideoChunkDecoder.js.map +1 -1
- package/dist/stages/decode/types.d.ts +1 -0
- package/dist/stages/decode/types.d.ts.map +1 -1
- package/dist/utils/time-utils.js +0 -16
- package/dist/utils/time-utils.js.map +1 -1
- package/dist/workers/{BaseDecoder.Bk26nCBk.js → BaseDecoder.BWYu1W0B.js} +6 -1
- package/dist/workers/BaseDecoder.BWYu1W0B.js.map +1 -0
- package/dist/workers/stages/compose/{video-compose.worker.B7xEVmN3.js → video-compose.worker.M5uomNVr.js} +50 -64
- package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +1 -0
- package/dist/workers/stages/decode/{audio-decode.worker.Czb1P66a.js → audio-decode.worker.DnS17GD9.js} +2 -2
- package/dist/workers/stages/decode/{audio-decode.worker.Czb1P66a.js.map → audio-decode.worker.DnS17GD9.js.map} +1 -1
- package/dist/workers/stages/decode/{video-decode.worker.DLX8FRVc.js → video-decode.worker.BEYsjOXp.js} +5 -29
- package/dist/workers/stages/decode/video-decode.worker.BEYsjOXp.js.map +1 -0
- package/dist/workers/worker-manifest.json +3 -3
- package/package.json +1 -1
- package/dist/cache/l1/gop-utils.d.ts +0 -10
- package/dist/cache/l1/gop-utils.d.ts.map +0 -1
- package/dist/cache/l1/gop-utils.js +0 -78
- package/dist/cache/l1/gop-utils.js.map +0 -1
- package/dist/workers/BaseDecoder.Bk26nCBk.js.map +0 -1
- package/dist/workers/stages/compose/video-compose.worker.B7xEVmN3.js.map +0 -1
- package/dist/workers/stages/decode/video-decode.worker.DLX8FRVc.js.map +0 -1
|
@@ -1,273 +1,113 @@
|
|
|
1
1
|
import { RcFrame } from "../../model/RcFrame.js";
|
|
2
|
-
import { findFrameIndex, findGopIndex } from "./gop-utils.js";
|
|
3
|
-
const DEFAULT_GOP_INTERVAL_US = 2e6;
|
|
4
2
|
const BYTES_PER_MB = 1024 * 1024;
|
|
5
3
|
class VideoL1Cache {
|
|
6
|
-
|
|
4
|
+
framesByClip = /* @__PURE__ */ new Map();
|
|
7
5
|
maxMemoryBytes;
|
|
8
|
-
gopIntervalUs;
|
|
9
6
|
currentBytes = 0;
|
|
10
7
|
constructor(config) {
|
|
11
8
|
this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;
|
|
12
|
-
this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US;
|
|
13
9
|
}
|
|
14
10
|
get(timeUs, clipId) {
|
|
15
|
-
const
|
|
16
|
-
if (!
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const entry = this.findEntryByTime(clipEntries, timeUs);
|
|
20
|
-
if (!entry) {
|
|
21
|
-
return null;
|
|
22
|
-
}
|
|
23
|
-
const frameIndex = findFrameIndex(
|
|
24
|
-
{
|
|
25
|
-
index: entry.gopIndex,
|
|
26
|
-
startUs: entry.gopStartUs,
|
|
27
|
-
durationUs: entry.durationUs,
|
|
28
|
-
frames: entry.frames,
|
|
29
|
-
clipId: entry.clipId
|
|
30
|
-
},
|
|
31
|
-
timeUs
|
|
32
|
-
);
|
|
33
|
-
if (frameIndex === -1) {
|
|
34
|
-
return null;
|
|
35
|
-
}
|
|
36
|
-
return entry.frames[frameIndex] ?? null;
|
|
11
|
+
const frames = this.framesByClip.get(clipId);
|
|
12
|
+
if (!frames || frames.length === 0) return null;
|
|
13
|
+
const index = this.binarySearchFrame(frames, timeUs);
|
|
14
|
+
return index !== -1 ? frames[index] ?? null : null;
|
|
37
15
|
}
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
this.mergeFrames(existing, frames, gop.durationUs);
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
const entry = this.createEntry(clipId, {
|
|
48
|
-
gopIndex,
|
|
49
|
-
startUs: gop.startUs,
|
|
50
|
-
durationUs: gop.durationUs,
|
|
51
|
-
frames,
|
|
52
|
-
isKeyframe: gop.isKeyframe
|
|
16
|
+
addFrame(frame, clipId, frameDuration, trackId) {
|
|
17
|
+
const rcFrame = RcFrame.wrap(frame, {
|
|
18
|
+
trackId,
|
|
19
|
+
clipId,
|
|
20
|
+
timestampUs: frame.timestamp ?? 0,
|
|
21
|
+
durationUs: frame.duration ?? frameDuration
|
|
53
22
|
});
|
|
54
|
-
this.
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
23
|
+
let frames = this.framesByClip.get(clipId);
|
|
24
|
+
if (!frames) {
|
|
25
|
+
frames = [];
|
|
26
|
+
this.framesByClip.set(clipId, frames);
|
|
27
|
+
}
|
|
28
|
+
const timestamp = rcFrame.timestampUs ?? 0;
|
|
29
|
+
const insertIndex = this.findInsertIndex(frames, timestamp);
|
|
30
|
+
if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? 0) === timestamp) {
|
|
31
|
+
const oldFrame = frames[insertIndex];
|
|
32
|
+
frames[insertIndex] = rcFrame;
|
|
33
|
+
this.currentBytes -= oldFrame?.sizeEstimate ?? 0;
|
|
34
|
+
oldFrame?.close?.();
|
|
35
|
+
} else {
|
|
36
|
+
frames.splice(insertIndex, 0, rcFrame);
|
|
65
37
|
}
|
|
66
|
-
|
|
67
|
-
gopIndex,
|
|
68
|
-
startUs: timestamp,
|
|
69
|
-
durationUs: frameDuration,
|
|
70
|
-
frames: [rcFrame],
|
|
71
|
-
isKeyframe: isKeyframe ?? true
|
|
72
|
-
});
|
|
73
|
-
this.registerEntry(entry);
|
|
74
|
-
this.currentBytes += entry.size;
|
|
38
|
+
this.currentBytes += rcFrame.sizeEstimate;
|
|
75
39
|
return rcFrame;
|
|
76
40
|
}
|
|
77
|
-
/**
|
|
78
|
-
* Evict all cache entries for a specific clip
|
|
79
|
-
*/
|
|
80
41
|
invalidateClip(clipId) {
|
|
81
|
-
const
|
|
82
|
-
if (!
|
|
83
|
-
for (const
|
|
84
|
-
this.
|
|
85
|
-
|
|
42
|
+
const frames = this.framesByClip.get(clipId);
|
|
43
|
+
if (!frames) return;
|
|
44
|
+
for (const frame of frames) {
|
|
45
|
+
this.currentBytes -= frame.sizeEstimate;
|
|
46
|
+
frame?.close?.();
|
|
86
47
|
}
|
|
87
|
-
this.
|
|
48
|
+
this.framesByClip.delete(clipId);
|
|
88
49
|
}
|
|
89
|
-
/**
|
|
90
|
-
* Check if a clip has any cached entries
|
|
91
|
-
*/
|
|
92
50
|
hasClip(clipId) {
|
|
93
|
-
const
|
|
94
|
-
return !!
|
|
51
|
+
const frames = this.framesByClip.get(clipId);
|
|
52
|
+
return !!frames && frames.length > 0;
|
|
95
53
|
}
|
|
96
|
-
/**
|
|
97
|
-
* Get total frame count for a clip
|
|
98
|
-
* @param clipId - Clip identifier
|
|
99
|
-
* @param startTimeUs - Optional start time to count frames from (inclusive)
|
|
100
|
-
*/
|
|
101
54
|
getClipFrameCount(clipId, startTimeUs) {
|
|
102
|
-
const
|
|
103
|
-
if (!
|
|
55
|
+
const frames = this.framesByClip.get(clipId);
|
|
56
|
+
if (!frames) return 0;
|
|
104
57
|
if (startTimeUs === void 0) {
|
|
105
|
-
return
|
|
58
|
+
return frames.length;
|
|
106
59
|
}
|
|
107
60
|
let count = 0;
|
|
108
|
-
for (const
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
if (frameTime >= startTimeUs) {
|
|
112
|
-
count++;
|
|
113
|
-
}
|
|
61
|
+
for (const frame of frames) {
|
|
62
|
+
if ((frame.timestampUs ?? 0) >= startTimeUs) {
|
|
63
|
+
count++;
|
|
114
64
|
}
|
|
115
65
|
}
|
|
116
66
|
return count;
|
|
117
67
|
}
|
|
118
|
-
// invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void {
|
|
119
|
-
// for (const entry of this.iterateEntries()) {
|
|
120
|
-
// if (clipId && entry.clipId !== clipId) {
|
|
121
|
-
// continue;
|
|
122
|
-
// }
|
|
123
|
-
// const gopEnd = entry.gopStartUs + entry.durationUs;
|
|
124
|
-
// if (entry.gopStartUs < endUs && gopEnd > startUs) {
|
|
125
|
-
// this.removeEntry(entry);
|
|
126
|
-
// }
|
|
127
|
-
// }
|
|
128
|
-
// }
|
|
129
68
|
clear() {
|
|
130
|
-
for (const
|
|
131
|
-
for (const
|
|
132
|
-
|
|
69
|
+
for (const frames of this.framesByClip.values()) {
|
|
70
|
+
for (const frame of frames) {
|
|
71
|
+
frame?.close?.();
|
|
133
72
|
}
|
|
134
73
|
}
|
|
135
|
-
this.
|
|
74
|
+
this.framesByClip.clear();
|
|
136
75
|
this.currentBytes = 0;
|
|
137
76
|
}
|
|
138
77
|
getMetadata() {
|
|
78
|
+
let totalFrames = 0;
|
|
79
|
+
for (const frames of this.framesByClip.values()) {
|
|
80
|
+
totalFrames += frames.length;
|
|
81
|
+
}
|
|
139
82
|
return {
|
|
140
83
|
size: this.currentBytes,
|
|
141
84
|
maxSize: this.maxMemoryBytes,
|
|
142
|
-
entries:
|
|
143
|
-
clipCount: this.
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
registerEntry(entry) {
|
|
147
|
-
const entries = this.ensureClipEntries(entry.clipId);
|
|
148
|
-
const insertIndex = this.findInsertIndex(entries, entry.gopIndex);
|
|
149
|
-
entries.splice(insertIndex, 0, entry);
|
|
150
|
-
}
|
|
151
|
-
createEntry(clipId, gop) {
|
|
152
|
-
const frames = this.normalizeFrames(gop.frames);
|
|
153
|
-
const entry = {
|
|
154
|
-
key: this.composeKey(clipId, gop.gopIndex),
|
|
155
|
-
clipId,
|
|
156
|
-
gopIndex: gop.gopIndex,
|
|
157
|
-
gopStartUs: gop.startUs,
|
|
158
|
-
durationUs: gop.durationUs,
|
|
159
|
-
frames,
|
|
160
|
-
size: 0
|
|
85
|
+
entries: totalFrames,
|
|
86
|
+
clipCount: this.framesByClip.size
|
|
161
87
|
};
|
|
162
|
-
this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length));
|
|
163
|
-
return entry;
|
|
164
|
-
}
|
|
165
|
-
mergeFrames(entry, frames, fallbackDuration) {
|
|
166
|
-
const durationFallback = this.deriveFallbackDuration(
|
|
167
|
-
fallbackDuration,
|
|
168
|
-
entry.frames.length + frames.length
|
|
169
|
-
);
|
|
170
|
-
for (const rcFrame of frames) {
|
|
171
|
-
this.insertFrame(entry, rcFrame, durationFallback);
|
|
172
|
-
}
|
|
173
|
-
this.updateEntryStats(entry, durationFallback);
|
|
174
|
-
}
|
|
175
|
-
insertFrame(entry, frame, fallbackDuration) {
|
|
176
|
-
const timestamp = frame.timestampUs ?? entry.gopStartUs;
|
|
177
|
-
const frames = entry.frames;
|
|
178
|
-
const insertIndex = this.findFrameInsertIndex(frames, timestamp);
|
|
179
|
-
if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp) {
|
|
180
|
-
const oldFrame = frames[insertIndex];
|
|
181
|
-
frames[insertIndex] = frame;
|
|
182
|
-
oldFrame?.close?.();
|
|
183
|
-
} else {
|
|
184
|
-
frames.splice(insertIndex, 0, frame);
|
|
185
|
-
}
|
|
186
|
-
entry.size += frame.sizeEstimate;
|
|
187
|
-
const duration = frame.durationUs || fallbackDuration;
|
|
188
|
-
entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs);
|
|
189
|
-
}
|
|
190
|
-
closeFrames(entry) {
|
|
191
|
-
for (const frame of entry.frames) {
|
|
192
|
-
frame?.close?.();
|
|
193
|
-
}
|
|
194
|
-
}
|
|
195
|
-
composeKey(clipId, gopIndex) {
|
|
196
|
-
return `${clipId}:${gopIndex}`;
|
|
197
88
|
}
|
|
198
|
-
|
|
199
|
-
return RcFrame.wrap(frame, {
|
|
200
|
-
trackId,
|
|
201
|
-
clipId,
|
|
202
|
-
timestampUs: frame.timestamp ?? 0,
|
|
203
|
-
durationUs: frame.duration ?? frameDuration,
|
|
204
|
-
gopSerial,
|
|
205
|
-
isKeyframe
|
|
206
|
-
});
|
|
207
|
-
}
|
|
208
|
-
wrapFrames(frames, clipId, fallbackDuration, trackId) {
|
|
209
|
-
const wrapped = frames.map(
|
|
210
|
-
(frame) => frame instanceof RcFrame ? frame : this.wrapFrame(
|
|
211
|
-
frame,
|
|
212
|
-
clipId,
|
|
213
|
-
fallbackDuration / Math.max(frames.length, 1),
|
|
214
|
-
trackId
|
|
215
|
-
)
|
|
216
|
-
);
|
|
217
|
-
return this.normalizeFrames(wrapped);
|
|
218
|
-
}
|
|
219
|
-
getEntryExact(clipId, gopIndex) {
|
|
220
|
-
const entries = this.entriesByClip.get(clipId);
|
|
221
|
-
if (!entries) return void 0;
|
|
222
|
-
return entries.find((e) => e.gopIndex === gopIndex);
|
|
223
|
-
}
|
|
224
|
-
findEntryByTime(entries, timeUs) {
|
|
89
|
+
binarySearchFrame(frames, timeUs) {
|
|
225
90
|
let low = 0;
|
|
226
|
-
let high =
|
|
91
|
+
let high = frames.length - 1;
|
|
92
|
+
let result = -1;
|
|
227
93
|
while (low <= high) {
|
|
228
94
|
const mid = Math.floor((low + high) / 2);
|
|
229
|
-
const
|
|
230
|
-
if (!
|
|
231
|
-
|
|
95
|
+
const frame = frames[mid];
|
|
96
|
+
if (!frame) break;
|
|
97
|
+
const frameTime = frame.timestampUs ?? 0;
|
|
98
|
+
const frameDuration = frame.durationUs ?? 0;
|
|
99
|
+
if (timeUs < frameTime) {
|
|
232
100
|
high = mid - 1;
|
|
233
|
-
} else if (timeUs >=
|
|
101
|
+
} else if (timeUs >= frameTime + frameDuration) {
|
|
234
102
|
low = mid + 1;
|
|
235
103
|
} else {
|
|
236
|
-
|
|
104
|
+
result = mid;
|
|
105
|
+
break;
|
|
237
106
|
}
|
|
238
107
|
}
|
|
239
|
-
return
|
|
240
|
-
}
|
|
241
|
-
countEntries() {
|
|
242
|
-
let count = 0;
|
|
243
|
-
for (const clipEntries of this.entriesByClip.values()) {
|
|
244
|
-
count += clipEntries.length;
|
|
245
|
-
}
|
|
246
|
-
return count;
|
|
247
|
-
}
|
|
248
|
-
ensureClipEntries(clipId) {
|
|
249
|
-
let entries = this.entriesByClip.get(clipId);
|
|
250
|
-
if (!entries) {
|
|
251
|
-
entries = [];
|
|
252
|
-
this.entriesByClip.set(clipId, entries);
|
|
253
|
-
}
|
|
254
|
-
return entries;
|
|
255
|
-
}
|
|
256
|
-
findInsertIndex(entries, gopIndex) {
|
|
257
|
-
let low = 0;
|
|
258
|
-
let high = entries.length;
|
|
259
|
-
while (low < high) {
|
|
260
|
-
const mid = Math.floor((low + high) / 2);
|
|
261
|
-
const entry = entries[mid];
|
|
262
|
-
if (entry && entry.gopIndex < gopIndex) {
|
|
263
|
-
low = mid + 1;
|
|
264
|
-
} else {
|
|
265
|
-
high = mid;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
return low;
|
|
108
|
+
return result;
|
|
269
109
|
}
|
|
270
|
-
|
|
110
|
+
findInsertIndex(frames, timestamp) {
|
|
271
111
|
let low = 0;
|
|
272
112
|
let high = frames.length;
|
|
273
113
|
while (low < high) {
|
|
@@ -281,34 +121,6 @@ class VideoL1Cache {
|
|
|
281
121
|
}
|
|
282
122
|
return low;
|
|
283
123
|
}
|
|
284
|
-
normalizeFrames(frames) {
|
|
285
|
-
const seen = /* @__PURE__ */ new Set();
|
|
286
|
-
const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0));
|
|
287
|
-
const result = [];
|
|
288
|
-
for (const frame of sorted) {
|
|
289
|
-
const ts = frame.timestampUs ?? 0;
|
|
290
|
-
if (seen.has(ts)) {
|
|
291
|
-
frame?.close?.();
|
|
292
|
-
} else {
|
|
293
|
-
seen.add(ts);
|
|
294
|
-
result.push(frame);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
297
|
-
return result;
|
|
298
|
-
}
|
|
299
|
-
updateEntryStats(entry, fallbackDuration) {
|
|
300
|
-
entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0);
|
|
301
|
-
entry.durationUs = entry.frames.reduce((acc, frame) => {
|
|
302
|
-
const duration = frame.durationUs || fallbackDuration;
|
|
303
|
-
return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs);
|
|
304
|
-
}, entry.durationUs);
|
|
305
|
-
}
|
|
306
|
-
deriveFallbackDuration(durationUs, frameCount) {
|
|
307
|
-
if (frameCount <= 1) {
|
|
308
|
-
return durationUs || this.gopIntervalUs;
|
|
309
|
-
}
|
|
310
|
-
return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount);
|
|
311
|
-
}
|
|
312
124
|
}
|
|
313
125
|
export {
|
|
314
126
|
VideoL1Cache
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport type { GOP } from '../types';\nimport { RcFrame } from '../../model';\nimport { findFrameIndex, findGopIndex } from './gop-utils';\n\nconst DEFAULT_GOP_INTERVAL_US = 2_000_000;\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n maxGOPs?: number;\n gopIntervalUs?: number;\n}\n\ninterface L1CacheEntry {\n key: string;\n clipId: string;\n gopIndex: number;\n gopStartUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n size: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache for 2-Clip strategy\n *\n * Clip lifecycle is managed by ClipSessionManager:\n * - No LRU eviction (clips evicted explicitly)\n * - No capacity limits (fixed 3 clips)\n * - Simple GOP storage per clip\n */\nexport class VideoL1Cache {\n private readonly entriesByClip = new Map<string, L1CacheEntry[]>();\n private maxMemoryBytes: number;\n private gopIntervalUs: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n this.gopIntervalUs = config.gopIntervalUs ?? DEFAULT_GOP_INTERVAL_US;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const clipEntries = this.entriesByClip.get(clipId);\n if (!clipEntries || clipEntries.length === 0) {\n return null;\n }\n\n const entry = this.findEntryByTime(clipEntries, timeUs);\n if (!entry) {\n return null;\n }\n\n const frameIndex = findFrameIndex(\n {\n index: entry.gopIndex,\n startUs: entry.gopStartUs,\n durationUs: entry.durationUs,\n frames: entry.frames,\n isKeyframe: true,\n clipId: entry.clipId,\n },\n timeUs\n );\n if (frameIndex === -1) {\n return null;\n }\n\n return entry.frames[frameIndex] ?? null;\n }\n\n putGOP(gop: GOP, clipId: string, trackId: string): void {\n const gopIndex = gop.index ?? gop.startUs;\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId);\n\n if (existing) {\n existing.gopStartUs = gop.startUs;\n this.mergeFrames(existing, frames, gop.durationUs);\n return;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n isKeyframe: gop.isKeyframe,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n }\n\n addFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame | null {\n const timestamp = frame.timestamp ?? 0;\n const gopIndex =\n typeof gopSerial === 'number' ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);\n const existing = this.getEntryExact(clipId, gopIndex);\n\n const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe);\n\n if (existing) {\n this.mergeFrames(existing, [rcFrame], frameDuration);\n return rcFrame;\n }\n\n const entry = this.createEntry(clipId, {\n gopIndex,\n startUs: timestamp,\n durationUs: frameDuration,\n frames: [rcFrame],\n isKeyframe: isKeyframe ?? true,\n });\n\n this.registerEntry(entry);\n this.currentBytes += entry.size;\n return rcFrame;\n }\n\n /**\n * Evict all cache entries for a specific clip\n */\n invalidateClip(clipId: string): void {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return;\n\n for (const entry of entries) {\n this.closeFrames(entry);\n this.currentBytes -= entry.size;\n }\n\n this.entriesByClip.delete(clipId);\n }\n\n /**\n * Check if a clip has any cached entries\n */\n hasClip(clipId: string): boolean {\n const entries = this.entriesByClip.get(clipId);\n return !!entries && entries.length > 0;\n }\n\n /**\n * Get total frame count for a clip\n * @param clipId - Clip identifier\n * @param startTimeUs - Optional start time to count frames from (inclusive)\n */\n getClipFrameCount(clipId: string, startTimeUs?: TimeUs): number {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return 0;\n\n // If no startTimeUs specified, return total frame count (backward compatible)\n if (startTimeUs === undefined) {\n return entries.reduce((sum, entry) => sum + entry.frames.length, 0);\n }\n\n // Count only frames at or after startTimeUs\n // Always check individual frame timestamps since gopSerial might not split correctly\n let count = 0;\n for (const entry of entries) {\n for (const frame of entry.frames) {\n const frameTime = frame.timestampUs ?? entry.gopStartUs;\n if (frameTime >= startTimeUs) {\n count++;\n }\n }\n }\n\n return count;\n }\n\n // invalidateRange(startUs: TimeUs, endUs: TimeUs, clipId?: string): void {\n // for (const entry of this.iterateEntries()) {\n // if (clipId && entry.clipId !== clipId) {\n // continue;\n // }\n\n // const gopEnd = entry.gopStartUs + entry.durationUs;\n // if (entry.gopStartUs < endUs && gopEnd > startUs) {\n // this.removeEntry(entry);\n // }\n // }\n // }\n\n clear(): void {\n for (const entries of this.entriesByClip.values()) {\n for (const entry of entries) {\n this.closeFrames(entry);\n }\n }\n this.entriesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: this.countEntries(),\n clipCount: this.entriesByClip.size,\n };\n }\n\n private registerEntry(entry: L1CacheEntry): void {\n const entries = this.ensureClipEntries(entry.clipId);\n const insertIndex = this.findInsertIndex(entries, entry.gopIndex);\n entries.splice(insertIndex, 0, entry);\n }\n\n private createEntry(\n clipId: string,\n gop: {\n gopIndex: number;\n startUs: TimeUs;\n durationUs: TimeUs;\n frames: RcFrame[];\n isKeyframe: boolean;\n }\n ): L1CacheEntry {\n const frames = this.normalizeFrames(gop.frames);\n const entry: L1CacheEntry = {\n key: this.composeKey(clipId, gop.gopIndex),\n clipId,\n gopIndex: gop.gopIndex,\n gopStartUs: gop.startUs,\n durationUs: gop.durationUs,\n frames,\n size: 0,\n };\n\n this.updateEntryStats(entry, this.deriveFallbackDuration(gop.durationUs, frames.length));\n return entry;\n }\n\n private mergeFrames(entry: L1CacheEntry, frames: RcFrame[], fallbackDuration: TimeUs): void {\n const durationFallback = this.deriveFallbackDuration(\n fallbackDuration,\n entry.frames.length + frames.length\n );\n for (const rcFrame of frames) {\n this.insertFrame(entry, rcFrame, durationFallback);\n }\n this.updateEntryStats(entry, durationFallback);\n }\n\n private insertFrame(entry: L1CacheEntry, frame: RcFrame, fallbackDuration: TimeUs): void {\n const timestamp = frame.timestampUs ?? entry.gopStartUs;\n const frames = entry.frames;\n const insertIndex = this.findFrameInsertIndex(frames, timestamp);\n\n if (\n insertIndex < frames.length &&\n (frames[insertIndex]?.timestampUs ?? entry.gopStartUs) === timestamp\n ) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = frame;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, frame);\n }\n\n entry.size += frame.sizeEstimate;\n const duration = frame.durationUs || fallbackDuration;\n entry.durationUs = Math.max(entry.durationUs, timestamp + duration - entry.gopStartUs);\n }\n\n private closeFrames(entry: L1CacheEntry): void {\n for (const frame of entry.frames) {\n frame?.close?.();\n }\n }\n\n private composeKey(clipId: string, gopIndex: number): string {\n return `${clipId}:${gopIndex}`;\n }\n\n private wrapFrame(\n frame: VideoFrame,\n clipId: string,\n frameDuration: TimeUs,\n trackId: string,\n gopSerial?: number,\n isKeyframe?: boolean\n ): RcFrame {\n return RcFrame.wrap(frame, {\n trackId,\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n gopSerial,\n isKeyframe,\n });\n }\n\n private wrapFrames(\n frames: (RcFrame | VideoFrame)[],\n clipId: string,\n fallbackDuration: TimeUs,\n trackId: string\n ): RcFrame[] {\n const wrapped = frames.map((frame) =>\n frame instanceof RcFrame\n ? frame\n : this.wrapFrame(\n frame as VideoFrame,\n clipId,\n fallbackDuration / Math.max(frames.length, 1),\n trackId\n )\n );\n return this.normalizeFrames(wrapped);\n }\n\n private getEntryExact(clipId: string, gopIndex: number): L1CacheEntry | undefined {\n const entries = this.entriesByClip.get(clipId);\n if (!entries) return undefined;\n return entries.find((e) => e.gopIndex === gopIndex);\n }\n\n private findEntryByTime(entries: L1CacheEntry[], timeUs: TimeUs): L1CacheEntry | undefined {\n let low = 0;\n let high = entries.length - 1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (!entry) break;\n\n if (timeUs < entry.gopStartUs) {\n high = mid - 1;\n } else if (timeUs >= entry.gopStartUs + entry.durationUs) {\n low = mid + 1;\n } else {\n return entry;\n }\n }\n\n return undefined;\n }\n\n private countEntries(): number {\n let count = 0;\n for (const clipEntries of this.entriesByClip.values()) {\n count += clipEntries.length;\n }\n return count;\n }\n\n private ensureClipEntries(clipId: string): L1CacheEntry[] {\n let entries = this.entriesByClip.get(clipId);\n if (!entries) {\n entries = [];\n this.entriesByClip.set(clipId, entries);\n }\n return entries;\n }\n\n private findInsertIndex(entries: L1CacheEntry[], gopIndex: number): number {\n let low = 0;\n let high = entries.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const entry = entries[mid];\n if (entry && entry.gopIndex < gopIndex) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private findFrameInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n return low;\n }\n\n private normalizeFrames(frames: RcFrame[]): RcFrame[] {\n const seen = new Set<number>();\n const sorted = [...frames].sort((a, b) => (a.timestampUs ?? 0) - (b.timestampUs ?? 0));\n const result: RcFrame[] = [];\n for (const frame of sorted) {\n const ts = frame.timestampUs ?? 0;\n if (seen.has(ts)) {\n frame?.close?.();\n } else {\n seen.add(ts);\n result.push(frame);\n }\n }\n return result;\n }\n\n private updateEntryStats(entry: L1CacheEntry, fallbackDuration: TimeUs): void {\n entry.size = entry.frames.reduce((acc, frame) => acc + frame.sizeEstimate, 0);\n entry.durationUs = entry.frames.reduce((acc, frame) => {\n const duration = frame.durationUs || fallbackDuration;\n return Math.max(acc, (frame.timestampUs ?? entry.gopStartUs) + duration - entry.gopStartUs);\n }, entry.durationUs);\n }\n\n private deriveFallbackDuration(durationUs: TimeUs, frameCount: number): TimeUs {\n if (frameCount <= 1) {\n return durationUs || this.gopIntervalUs;\n }\n return Math.max(durationUs / frameCount, this.gopIntervalUs / frameCount);\n }\n}\n"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"VideoL1Cache.js","sources":["../../../src/cache/l1/VideoL1Cache.ts"],"sourcesContent":["import type { TimeUs } from '../../model/types';\nimport { RcFrame } from '../../model';\n\nconst BYTES_PER_MB = 1024 * 1024;\n\ninterface L1Config {\n maxMemoryMB: number;\n}\n\nexport interface L1CacheMetadata {\n size: number;\n maxSize: number;\n entries: number;\n clipCount: number;\n}\n\n/**\n * Simplified VideoL1Cache - Single-layer frame storage per clip\n *\n * Each clip stores frames in a sorted array by timestamp.\n * No GOP management - frames are directly stored and retrieved via binary search.\n */\nexport class VideoL1Cache {\n private readonly framesByClip = new Map<string, RcFrame[]>();\n private readonly maxMemoryBytes: number;\n private currentBytes = 0;\n\n constructor(config: L1Config) {\n this.maxMemoryBytes = config.maxMemoryMB * BYTES_PER_MB;\n }\n\n get(timeUs: TimeUs, clipId: string): RcFrame | null {\n const frames = this.framesByClip.get(clipId);\n if (!frames || frames.length === 0) return null;\n\n // Binary search for frame at timeUs\n const index = this.binarySearchFrame(frames, timeUs);\n return index !== -1 ? (frames[index] ?? null) : null;\n }\n\n addFrame(frame: VideoFrame, clipId: string, frameDuration: TimeUs, trackId: string): RcFrame {\n const rcFrame = RcFrame.wrap(frame, {\n trackId,\n clipId,\n timestampUs: frame.timestamp ?? 0,\n durationUs: frame.duration ?? frameDuration,\n });\n\n let frames = this.framesByClip.get(clipId);\n if (!frames) {\n frames = [];\n this.framesByClip.set(clipId, frames);\n }\n\n const timestamp = rcFrame.timestampUs ?? 0;\n const insertIndex = this.findInsertIndex(frames, timestamp);\n\n // Check for duplicate timestamp\n if (insertIndex < frames.length && (frames[insertIndex]?.timestampUs ?? 0) === timestamp) {\n const oldFrame = frames[insertIndex];\n frames[insertIndex] = rcFrame;\n this.currentBytes -= oldFrame?.sizeEstimate ?? 0;\n oldFrame?.close?.();\n } else {\n frames.splice(insertIndex, 0, rcFrame);\n }\n\n this.currentBytes += rcFrame.sizeEstimate;\n return rcFrame;\n }\n\n invalidateClip(clipId: string): void {\n const frames = this.framesByClip.get(clipId);\n if (!frames) return;\n\n for (const frame of frames) {\n this.currentBytes -= frame.sizeEstimate;\n frame?.close?.();\n }\n\n this.framesByClip.delete(clipId);\n }\n\n hasClip(clipId: string): boolean {\n const frames = this.framesByClip.get(clipId);\n return !!frames && frames.length > 0;\n }\n\n getClipFrameCount(clipId: string, startTimeUs?: TimeUs): number {\n const frames = this.framesByClip.get(clipId);\n if (!frames) return 0;\n\n if (startTimeUs === undefined) {\n return frames.length;\n }\n\n // Count frames >= startTimeUs\n let count = 0;\n for (const frame of frames) {\n if ((frame.timestampUs ?? 0) >= startTimeUs) {\n count++;\n }\n }\n return count;\n }\n\n clear(): void {\n for (const frames of this.framesByClip.values()) {\n for (const frame of frames) {\n frame?.close?.();\n }\n }\n this.framesByClip.clear();\n this.currentBytes = 0;\n }\n\n getMetadata(): L1CacheMetadata {\n let totalFrames = 0;\n for (const frames of this.framesByClip.values()) {\n totalFrames += frames.length;\n }\n\n return {\n size: this.currentBytes,\n maxSize: this.maxMemoryBytes,\n entries: totalFrames,\n clipCount: this.framesByClip.size,\n };\n }\n\n private binarySearchFrame(frames: RcFrame[], timeUs: TimeUs): number {\n let low = 0;\n let high = frames.length - 1;\n let result = -1;\n\n while (low <= high) {\n const mid = Math.floor((low + high) / 2);\n const frame = frames[mid];\n if (!frame) break;\n\n const frameTime = frame.timestampUs ?? 0;\n const frameDuration = frame.durationUs ?? 0;\n\n if (timeUs < frameTime) {\n high = mid - 1;\n } else if (timeUs >= frameTime + frameDuration) {\n low = mid + 1;\n } else {\n result = mid;\n break;\n }\n }\n\n return result;\n }\n\n private findInsertIndex(frames: RcFrame[], timestamp: TimeUs): number {\n let low = 0;\n let high = frames.length;\n\n while (low < high) {\n const mid = Math.floor((low + high) / 2);\n const midTs = frames[mid]?.timestampUs ?? 0;\n\n if (midTs < timestamp) {\n low = mid + 1;\n } else {\n high = mid;\n }\n }\n\n return low;\n }\n}\n"],"names":[],"mappings":"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"}
|
package/dist/cache/l1/types.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../src/cache/l1/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,mBAAmB,CAAC;AAEhD,MAAM,WAAW,QAAQ;IACvB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,MAAM,EAAE,YAAY,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B"}
|
package/dist/cache/types.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { RcFrame } from '../model';
|
|
2
1
|
import { TimeUs } from '../model/types';
|
|
3
2
|
|
|
4
3
|
export interface CacheConfig {
|
|
@@ -55,20 +54,6 @@ export interface CacheEntry<T> {
|
|
|
55
54
|
/** Pin tokens referencing this entry (window aware retention). @better-ai.mdc retain window */
|
|
56
55
|
pinnedBy?: Set<string>;
|
|
57
56
|
}
|
|
58
|
-
export interface GOP {
|
|
59
|
-
/** GOP index */
|
|
60
|
-
index: number;
|
|
61
|
-
/** Start time in microseconds */
|
|
62
|
-
startUs: TimeUs;
|
|
63
|
-
/** Duration in microseconds */
|
|
64
|
-
durationUs: TimeUs;
|
|
65
|
-
/** Video frames in this GOP */
|
|
66
|
-
frames: RcFrame[];
|
|
67
|
-
/** Whether this GOP starts with a keyframe */
|
|
68
|
-
isKeyframe: boolean;
|
|
69
|
-
/** Clip ID for clip-aware caching */
|
|
70
|
-
clipId: string;
|
|
71
|
-
}
|
|
72
57
|
export interface ClipMetadata {
|
|
73
58
|
/** Clip identifier */
|
|
74
59
|
id: string;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/cache/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAE7C,MAAM,WAAW,WAAW;IAC1B,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,qCAAqC;IACrC,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,KAAK,GAAG,KAAK,GAAG,MAAM,CAAC;IAEvC,sCAAsC;IACtC,gBAAgB,EAAE,MAAM,CAAC;IAEzB,uCAAuC;IACvC,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,UAAU;IACzB,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,0BAA0B;IAC1B,EAAE,EAAE;QACF,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,OAAO,EAAE,MAAM,CAAC;QAChB,QAAQ,EAAE,MAAM,CAAC;QACjB,SAAS,EAAE,MAAM,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;IAEF,yBAAyB;IACzB,KAAK,EAAE;QACL,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED,MAAM,WAAW,UAAU,CAAC,CAAC;IAC3B,gBAAgB;IAChB,GAAG,EAAE,MAAM,CAAC;IAEZ,kBAAkB;IAClB,IAAI,EAAE,CAAC,CAAC;IAER,oBAAoB;IACpB,IAAI,EAAE,MAAM,CAAC;IAEb,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAElB,4BAA4B;IAC5B,cAAc,EAAE,MAAM,CAAC;IAEvB,mBAAmB;IACnB,WAAW,EAAE,MAAM,CAAC;IAEpB,+FAA+F;IAC/F,QAAQ,CAAC,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;CACxB;AAED,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,EAAE,EAAE,MAAM,CAAC;IAEX,8BAA8B;IAC9B,IAAI,EAAE,MAAM,CAAC;IAEb,gCAAgC;IAChC,OAAO,EAAE,MAAM,CAAC;IAEhB,eAAe;IACf,UAAU,EAAE,MAAM,CAAC;IAEnB,uBAAuB;IACvB,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,UAAU,EAAE,MAAM,CAAC;IAEnB,0BAA0B;IAC1B,WAAW,CAAC,EAAE;QACZ,KAAK,EAAE,MAAM,CAAC;QACd,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,MAAM,CAAC;QAChB,OAAO,CAAC,EAAE,MAAM,CAAC;KAClB,CAAC;CACH"}
|
|
@@ -34,10 +34,13 @@ export declare class CompositionModel {
|
|
|
34
34
|
next?: string;
|
|
35
35
|
};
|
|
36
36
|
/**
|
|
37
|
-
* Get all clip IDs that should be cached using
|
|
38
|
-
*
|
|
37
|
+
* Get all clip IDs that should be cached using adaptive strategy
|
|
38
|
+
* - Short clips (≤ maxDuration): cache Current + Next (smooth transitions)
|
|
39
|
+
* - Long clips (> maxDuration): cache Current only (memory control)
|
|
40
|
+
* @param timeUs - Current playback time
|
|
41
|
+
* @param maxDuration - Max duration for 2-clip strategy (default 5s)
|
|
39
42
|
*/
|
|
40
|
-
getClipsToCacheAtTime(timeUs: TimeUs): Set<string>;
|
|
43
|
+
getClipsToCacheAtTime(timeUs: TimeUs, maxDuration?: number): Set<string>;
|
|
41
44
|
getResource(id: string): Resource | null;
|
|
42
45
|
updateResourceState(id: string, state: 'pending' | 'loading' | 'ready' | 'error'): void;
|
|
43
46
|
getUnusedResources(): Resource[];
|
|
@@ -47,6 +50,7 @@ export declare class CompositionModel {
|
|
|
47
50
|
incremental?: boolean;
|
|
48
51
|
trackId?: string;
|
|
49
52
|
clipId?: string;
|
|
53
|
+
clip?: Clip;
|
|
50
54
|
operation?: 'add' | 'update' | 'remove';
|
|
51
55
|
}): void;
|
|
52
56
|
private fullRebuildIndexes;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,EACpB,KAAK,EACL,IAAI,EACJ,QAAQ,EACR,MAAM,EAGP,MAAM,SAAS,CAAC;AAIjB,qBAAa,gBAAgB;IAC3B,SAAgB,OAAO,EAAG,KAAK,CAAU;IACzC,SAAgB,GAAG,EAAE,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC;IAChC,UAAU,EAAG,MAAM,CAAC;IAC3B,SAAgB,WAAW,EAAE,MAAM,CAAC;IAC7B,MAAM,EAAE,KAAK,EAAE,CAAC;IACvB,SAAgB,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAEjD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAqB;IAC9C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,gBAAgB,CAAsB;IAEvD,SAAgB,YAAY,CAAC,EAAE;QAC7B,KAAK,EAAE,MAAM,CAAC;QACd,MAAM,EAAE,MAAM,CAAC;QACf,eAAe,CAAC,EAAE,MAAM,CAAC;KAC1B,CAAC;IAEF,SAAgB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;gBAElC,IAAI,EAAE,oBAAoB;IA6BtC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,KAAK,GAAG,IAAI;IAInC,eAAe,CAAC,IAAI,EAAE,OAAO,GAAG,OAAO,GAAG,SAAS,GAAG,SAAS,GAAG,IAAI,GAAG,KAAK,EAAE;IAKhF,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI;IAIjC,cAAc,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,IAAI,EAAE;IAoBxD,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE;IAgBtD,sBAAsB,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,EAAE;IAKpD,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;IAcpE;;;OAGG;IACH,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAAC,IAAI,CAAC,EAAE,MAAM,CAAA;KAAE;IAsCvF
|
|
1
|
+
{"version":3,"file":"CompositionModel.d.ts","sourceRoot":"","sources":["../../src/model/CompositionModel.ts"],"names":[],"mappings":"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"}
|
|
@@ -124,14 +124,21 @@ ${errors.map((e) => `${e.path}: ${e.message}`).join("\n")}`
|
|
|
124
124
|
return result;
|
|
125
125
|
}
|
|
126
126
|
/**
|
|
127
|
-
* Get all clip IDs that should be cached using
|
|
128
|
-
*
|
|
127
|
+
* Get all clip IDs that should be cached using adaptive strategy
|
|
128
|
+
* - Short clips (≤ maxDuration): cache Current + Next (smooth transitions)
|
|
129
|
+
* - Long clips (> maxDuration): cache Current only (memory control)
|
|
130
|
+
* @param timeUs - Current playback time
|
|
131
|
+
* @param maxDuration - Max duration for 2-clip strategy (default 5s)
|
|
129
132
|
*/
|
|
130
|
-
getClipsToCacheAtTime(timeUs) {
|
|
133
|
+
getClipsToCacheAtTime(timeUs, maxDuration = 5e6) {
|
|
131
134
|
const { current, next } = this.getNeighboringClips(timeUs);
|
|
132
135
|
const clipIds = /* @__PURE__ */ new Set();
|
|
133
|
-
if (current) clipIds
|
|
134
|
-
|
|
136
|
+
if (!current) return clipIds;
|
|
137
|
+
clipIds.add(current);
|
|
138
|
+
const currentClip = this.findClip(current);
|
|
139
|
+
if (currentClip && currentClip.durationUs <= maxDuration && next) {
|
|
140
|
+
clipIds.add(next);
|
|
141
|
+
}
|
|
135
142
|
return clipIds;
|
|
136
143
|
}
|
|
137
144
|
// Resource operations
|
|
@@ -167,7 +174,7 @@ ${errors.map((e) => `${e.path}: ${e.message}`).join("\n")}`
|
|
|
167
174
|
const track = options?.trackId ? this.findTrack(options.trackId) : void 0;
|
|
168
175
|
const isAttachmentTrack = track && track.kind !== "video" && track.kind !== "audio";
|
|
169
176
|
if (options?.incremental && !isAttachmentTrack && options.clipId && options.operation) {
|
|
170
|
-
const clip = this.clipMap.get(options.clipId);
|
|
177
|
+
const clip = options.clip ?? this.clipMap.get(options.clipId);
|
|
171
178
|
if (options.operation === "add" && clip) {
|
|
172
179
|
this.addClipToIndexes(clip);
|
|
173
180
|
} else if (options.operation === "remove" && clip) {
|