@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.
Files changed (67) hide show
  1. package/dist/Meframe.d.ts.map +1 -1
  2. package/dist/Meframe.js +1 -1
  3. package/dist/Meframe.js.map +1 -1
  4. package/dist/cache/CacheManager.d.ts +1 -4
  5. package/dist/cache/CacheManager.d.ts.map +1 -1
  6. package/dist/cache/CacheManager.js +3 -13
  7. package/dist/cache/CacheManager.js.map +1 -1
  8. package/dist/cache/index.d.ts +1 -1
  9. package/dist/cache/index.d.ts.map +1 -1
  10. package/dist/cache/l1/VideoL1Cache.d.ts +7 -40
  11. package/dist/cache/l1/VideoL1Cache.d.ts.map +1 -1
  12. package/dist/cache/l1/VideoL1Cache.js +63 -251
  13. package/dist/cache/l1/VideoL1Cache.js.map +1 -1
  14. package/dist/cache/l1/types.d.ts +0 -2
  15. package/dist/cache/l1/types.d.ts.map +1 -1
  16. package/dist/cache/types.d.ts +0 -15
  17. package/dist/cache/types.d.ts.map +1 -1
  18. package/dist/model/CompositionModel.d.ts +7 -3
  19. package/dist/model/CompositionModel.d.ts.map +1 -1
  20. package/dist/model/CompositionModel.js +13 -6
  21. package/dist/model/CompositionModel.js.map +1 -1
  22. package/dist/model/RcFrame.d.ts +0 -4
  23. package/dist/model/RcFrame.d.ts.map +1 -1
  24. package/dist/model/RcFrame.js +0 -6
  25. package/dist/model/RcFrame.js.map +1 -1
  26. package/dist/model/patch.js +22 -59
  27. package/dist/model/patch.js.map +1 -1
  28. package/dist/orchestrator/Orchestrator.d.ts.map +1 -1
  29. package/dist/orchestrator/Orchestrator.js +29 -39
  30. package/dist/orchestrator/Orchestrator.js.map +1 -1
  31. package/dist/orchestrator/VideoClipSession.d.ts +4 -5
  32. package/dist/orchestrator/VideoClipSession.d.ts.map +1 -1
  33. package/dist/orchestrator/VideoClipSession.js +32 -35
  34. package/dist/orchestrator/VideoClipSession.js.map +1 -1
  35. package/dist/stages/compose/LayerRenderer.d.ts.map +1 -1
  36. package/dist/stages/compose/VideoComposer.d.ts +1 -7
  37. package/dist/stages/compose/VideoComposer.d.ts.map +1 -1
  38. package/dist/stages/compose/types.d.ts +1 -9
  39. package/dist/stages/compose/types.d.ts.map +1 -1
  40. package/dist/stages/decode/BaseDecoder.d.ts.map +1 -1
  41. package/dist/stages/decode/BaseDecoder.js +5 -0
  42. package/dist/stages/decode/BaseDecoder.js.map +1 -1
  43. package/dist/stages/decode/VideoChunkDecoder.d.ts +0 -4
  44. package/dist/stages/decode/VideoChunkDecoder.d.ts.map +1 -1
  45. package/dist/stages/decode/VideoChunkDecoder.js +3 -27
  46. package/dist/stages/decode/VideoChunkDecoder.js.map +1 -1
  47. package/dist/stages/decode/types.d.ts +1 -0
  48. package/dist/stages/decode/types.d.ts.map +1 -1
  49. package/dist/utils/time-utils.js +0 -16
  50. package/dist/utils/time-utils.js.map +1 -1
  51. package/dist/workers/{BaseDecoder.Bk26nCBk.js → BaseDecoder.BWYu1W0B.js} +6 -1
  52. package/dist/workers/BaseDecoder.BWYu1W0B.js.map +1 -0
  53. package/dist/workers/stages/compose/{video-compose.worker.B7xEVmN3.js → video-compose.worker.M5uomNVr.js} +50 -64
  54. package/dist/workers/stages/compose/video-compose.worker.M5uomNVr.js.map +1 -0
  55. package/dist/workers/stages/decode/{audio-decode.worker.Czb1P66a.js → audio-decode.worker.DnS17GD9.js} +2 -2
  56. package/dist/workers/stages/decode/{audio-decode.worker.Czb1P66a.js.map → audio-decode.worker.DnS17GD9.js.map} +1 -1
  57. package/dist/workers/stages/decode/{video-decode.worker.DLX8FRVc.js → video-decode.worker.BEYsjOXp.js} +5 -29
  58. package/dist/workers/stages/decode/video-decode.worker.BEYsjOXp.js.map +1 -0
  59. package/dist/workers/worker-manifest.json +3 -3
  60. package/package.json +1 -1
  61. package/dist/cache/l1/gop-utils.d.ts +0 -10
  62. package/dist/cache/l1/gop-utils.d.ts.map +0 -1
  63. package/dist/cache/l1/gop-utils.js +0 -78
  64. package/dist/cache/l1/gop-utils.js.map +0 -1
  65. package/dist/workers/BaseDecoder.Bk26nCBk.js.map +0 -1
  66. package/dist/workers/stages/compose/video-compose.worker.B7xEVmN3.js.map +0 -1
  67. 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
- entriesByClip = /* @__PURE__ */ new Map();
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 clipEntries = this.entriesByClip.get(clipId);
16
- if (!clipEntries || clipEntries.length === 0) {
17
- return null;
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
- putGOP(gop, clipId, trackId) {
39
- const gopIndex = gop.index ?? gop.startUs;
40
- const existing = this.getEntryExact(clipId, gopIndex);
41
- const frames = this.wrapFrames(gop.frames, clipId, gop.durationUs, trackId);
42
- if (existing) {
43
- existing.gopStartUs = gop.startUs;
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.registerEntry(entry);
55
- this.currentBytes += entry.size;
56
- }
57
- addFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) {
58
- const timestamp = frame.timestamp ?? 0;
59
- const gopIndex = typeof gopSerial === "number" ? gopSerial : findGopIndex(timestamp, this.gopIntervalUs);
60
- const existing = this.getEntryExact(clipId, gopIndex);
61
- const rcFrame = this.wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe);
62
- if (existing) {
63
- this.mergeFrames(existing, [rcFrame], frameDuration);
64
- return rcFrame;
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
- const entry = this.createEntry(clipId, {
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 entries = this.entriesByClip.get(clipId);
82
- if (!entries) return;
83
- for (const entry of entries) {
84
- this.closeFrames(entry);
85
- this.currentBytes -= entry.size;
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.entriesByClip.delete(clipId);
48
+ this.framesByClip.delete(clipId);
88
49
  }
89
- /**
90
- * Check if a clip has any cached entries
91
- */
92
50
  hasClip(clipId) {
93
- const entries = this.entriesByClip.get(clipId);
94
- return !!entries && entries.length > 0;
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 entries = this.entriesByClip.get(clipId);
103
- if (!entries) return 0;
55
+ const frames = this.framesByClip.get(clipId);
56
+ if (!frames) return 0;
104
57
  if (startTimeUs === void 0) {
105
- return entries.reduce((sum, entry) => sum + entry.frames.length, 0);
58
+ return frames.length;
106
59
  }
107
60
  let count = 0;
108
- for (const entry of entries) {
109
- for (const frame of entry.frames) {
110
- const frameTime = frame.timestampUs ?? entry.gopStartUs;
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 entries of this.entriesByClip.values()) {
131
- for (const entry of entries) {
132
- this.closeFrames(entry);
69
+ for (const frames of this.framesByClip.values()) {
70
+ for (const frame of frames) {
71
+ frame?.close?.();
133
72
  }
134
73
  }
135
- this.entriesByClip.clear();
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: this.countEntries(),
143
- clipCount: this.entriesByClip.size
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
- wrapFrame(frame, clipId, frameDuration, trackId, gopSerial, isKeyframe) {
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 = entries.length - 1;
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 entry = entries[mid];
230
- if (!entry) break;
231
- if (timeUs < entry.gopStartUs) {
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 >= entry.gopStartUs + entry.durationUs) {
101
+ } else if (timeUs >= frameTime + frameDuration) {
234
102
  low = mid + 1;
235
103
  } else {
236
- return entry;
104
+ result = mid;
105
+ break;
237
106
  }
238
107
  }
239
- return void 0;
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
- findFrameInsertIndex(frames, timestamp) {
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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"}
@@ -2,8 +2,6 @@ import { TimeUs } from '../../model/types';
2
2
 
3
3
  export interface L1Config {
4
4
  maxMemoryMB: number;
5
- maxGOPs?: number;
6
- gopIntervalUs?: number;
7
5
  }
8
6
  export interface AudioSlot {
9
7
  timestampUs: TimeUs;
@@ -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;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;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"}
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"}
@@ -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,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,UAAU,CAAC;AACxC;;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,GAAG;IAClB,gBAAgB;IAChB,KAAK,EAAE,MAAM,CAAC;IAEd,iCAAiC;IACjC,OAAO,EAAE,MAAM,CAAC;IAEhB,+BAA+B;IAC/B,UAAU,EAAE,MAAM,CAAC;IAEnB,+BAA+B;IAC/B,MAAM,EAAE,OAAO,EAAE,CAAC;IAElB,8CAA8C;IAC9C,UAAU,EAAE,OAAO,CAAC;IAEpB,qCAAqC;IACrC,MAAM,EAAE,MAAM,CAAC;CAChB;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"}
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 2-Clip strategy
38
- * Returns array of unique clip IDs (Prev/Current/Next)
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":"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"}
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 2-Clip strategy
128
- * Returns array of unique clip IDs (Prev/Current/Next)
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.add(current);
134
- if (next) clipIds.add(next);
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) {