@editframe/elements 0.19.4-beta.0 → 0.20.1-beta.0

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 (132) hide show
  1. package/dist/elements/ContextProxiesController.d.ts +40 -0
  2. package/dist/elements/ContextProxiesController.js +69 -0
  3. package/dist/elements/EFCaptions.d.ts +45 -6
  4. package/dist/elements/EFCaptions.js +220 -26
  5. package/dist/elements/EFImage.js +4 -1
  6. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +2 -1
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.js +9 -0
  8. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.js +11 -0
  10. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +13 -1
  11. package/dist/elements/EFMedia/BaseMediaEngine.js +9 -0
  12. package/dist/elements/EFMedia/JitMediaEngine.d.ts +7 -1
  13. package/dist/elements/EFMedia/JitMediaEngine.js +15 -0
  14. package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +2 -1
  15. package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +2 -0
  16. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +1 -1
  17. package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +3 -1
  18. package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +1 -1
  19. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +1 -1
  20. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +6 -5
  21. package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +3 -1
  22. package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +2 -0
  23. package/dist/elements/EFMedia/shared/AudioSpanUtils.js +2 -2
  24. package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +39 -0
  25. package/dist/elements/EFMedia/shared/GlobalInputCache.js +57 -0
  26. package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +27 -0
  27. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +106 -0
  28. package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -1
  29. package/dist/elements/EFMedia.d.ts +2 -2
  30. package/dist/elements/EFMedia.js +25 -1
  31. package/dist/elements/EFSurface.browsertest.d.ts +0 -0
  32. package/dist/elements/EFSurface.d.ts +30 -0
  33. package/dist/elements/EFSurface.js +96 -0
  34. package/dist/elements/EFTemporal.js +7 -6
  35. package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
  36. package/dist/elements/EFThumbnailStrip.d.ts +86 -0
  37. package/dist/elements/EFThumbnailStrip.js +490 -0
  38. package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
  39. package/dist/elements/EFTimegroup.d.ts +6 -1
  40. package/dist/elements/EFTimegroup.js +53 -11
  41. package/dist/elements/updateAnimations.browsertest.d.ts +13 -0
  42. package/dist/elements/updateAnimations.d.ts +5 -0
  43. package/dist/elements/updateAnimations.js +37 -13
  44. package/dist/getRenderInfo.js +1 -1
  45. package/dist/gui/ContextMixin.js +27 -14
  46. package/dist/gui/EFControls.browsertest.d.ts +0 -0
  47. package/dist/gui/EFControls.d.ts +38 -0
  48. package/dist/gui/EFControls.js +51 -0
  49. package/dist/gui/EFFilmstrip.d.ts +40 -1
  50. package/dist/gui/EFFilmstrip.js +240 -3
  51. package/dist/gui/EFPreview.js +2 -1
  52. package/dist/gui/EFScrubber.d.ts +6 -5
  53. package/dist/gui/EFScrubber.js +31 -21
  54. package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
  55. package/dist/gui/EFTimeDisplay.d.ts +2 -6
  56. package/dist/gui/EFTimeDisplay.js +13 -23
  57. package/dist/gui/TWMixin.js +1 -1
  58. package/dist/gui/currentTimeContext.d.ts +3 -0
  59. package/dist/gui/currentTimeContext.js +3 -0
  60. package/dist/gui/durationContext.d.ts +3 -0
  61. package/dist/gui/durationContext.js +3 -0
  62. package/dist/index.d.ts +3 -0
  63. package/dist/index.js +4 -1
  64. package/dist/style.css +1 -1
  65. package/dist/transcoding/types/index.d.ts +11 -0
  66. package/dist/utils/LRUCache.d.ts +46 -0
  67. package/dist/utils/LRUCache.js +382 -1
  68. package/dist/utils/LRUCache.test.d.ts +1 -0
  69. package/package.json +2 -2
  70. package/src/elements/ContextProxiesController.ts +124 -0
  71. package/src/elements/EFCaptions.browsertest.ts +1820 -0
  72. package/src/elements/EFCaptions.ts +373 -36
  73. package/src/elements/EFImage.ts +4 -1
  74. package/src/elements/EFMedia/AssetIdMediaEngine.ts +30 -1
  75. package/src/elements/EFMedia/AssetMediaEngine.ts +33 -0
  76. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +3 -8
  77. package/src/elements/EFMedia/BaseMediaEngine.ts +35 -0
  78. package/src/elements/EFMedia/JitMediaEngine.ts +34 -0
  79. package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +6 -5
  80. package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +5 -0
  81. package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +8 -5
  82. package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +5 -5
  83. package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +11 -12
  84. package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +7 -4
  85. package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +5 -0
  86. package/src/elements/EFMedia/shared/AudioSpanUtils.ts +2 -2
  87. package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
  88. package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +2 -2
  89. package/src/elements/EFMedia/shared/RenditionHelpers.ts +2 -2
  90. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
  91. package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +1 -1
  92. package/src/elements/EFMedia.ts +38 -1
  93. package/src/elements/EFSurface.browsertest.ts +155 -0
  94. package/src/elements/EFSurface.ts +141 -0
  95. package/src/elements/EFTemporal.ts +14 -8
  96. package/src/elements/EFThumbnailStrip.browsertest.ts +591 -0
  97. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +713 -0
  98. package/src/elements/EFThumbnailStrip.ts +905 -0
  99. package/src/elements/EFTimegroup.browsertest.ts +56 -7
  100. package/src/elements/EFTimegroup.ts +88 -16
  101. package/src/elements/updateAnimations.browsertest.ts +333 -11
  102. package/src/elements/updateAnimations.ts +68 -19
  103. package/src/gui/ContextMixin.browsertest.ts +0 -25
  104. package/src/gui/ContextMixin.ts +44 -20
  105. package/src/gui/EFControls.browsertest.ts +175 -0
  106. package/src/gui/EFControls.ts +84 -0
  107. package/src/gui/EFFilmstrip.ts +323 -4
  108. package/src/gui/EFPreview.ts +2 -1
  109. package/src/gui/EFScrubber.ts +29 -25
  110. package/src/gui/EFTimeDisplay.browsertest.ts +237 -0
  111. package/src/gui/EFTimeDisplay.ts +12 -40
  112. package/src/gui/currentTimeContext.ts +5 -0
  113. package/src/gui/durationContext.ts +3 -0
  114. package/src/transcoding/types/index.ts +13 -0
  115. package/src/utils/LRUCache.test.ts +272 -0
  116. package/src/utils/LRUCache.ts +543 -0
  117. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/data.bin +0 -0
  118. package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +1 -1
  119. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/data.bin +0 -0
  120. package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +1 -1
  121. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/data.bin +0 -0
  122. package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +1 -1
  123. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
  124. package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +1 -1
  125. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/data.bin +0 -0
  126. package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/metadata.json +1 -1
  127. package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/data.bin +0 -0
  128. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -1
  129. package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +1 -1
  130. package/types.json +1 -1
  131. package/dist/transcoding/cache/CacheManager.d.ts +0 -73
  132. package/src/transcoding/cache/CacheManager.ts +0 -208
@@ -151,3 +151,546 @@ export class SizeAwareLRUCache<K> {
151
151
  return this.maxSizeBytes;
152
152
  }
153
153
  }
154
+
155
+ /**
156
+ * Red-Black Tree node colors
157
+ */
158
+ enum Color {
159
+ RED = "RED",
160
+ BLACK = "BLACK",
161
+ }
162
+
163
+ /**
164
+ * Red-Black Tree node for ordered key storage
165
+ */
166
+ class RBTreeNode<K> {
167
+ constructor(
168
+ public key: K,
169
+ public color: Color = Color.RED,
170
+ public left: RBTreeNode<K> | null = null,
171
+ public right: RBTreeNode<K> | null = null,
172
+ public parent: RBTreeNode<K> | null = null,
173
+ ) {}
174
+ }
175
+
176
+ /**
177
+ * Red-Black Tree implementation for O(log n) operations
178
+ * Supports insert, delete, search, range queries, and nearest neighbor
179
+ */
180
+ class RedBlackTree<K> {
181
+ private root: RBTreeNode<K> | null = null;
182
+ private readonly compareFn: (a: K, b: K) => number;
183
+
184
+ constructor(compareFn: (a: K, b: K) => number) {
185
+ this.compareFn = compareFn;
186
+ }
187
+
188
+ insert(key: K): void {
189
+ const node = new RBTreeNode(key);
190
+
191
+ if (!this.root) {
192
+ this.root = node;
193
+ node.color = Color.BLACK;
194
+ return;
195
+ }
196
+
197
+ this.insertNode(node);
198
+ this.fixInsert(node);
199
+ }
200
+
201
+ delete(key: K): boolean {
202
+ const node = this.findNode(key);
203
+ if (!node) return false;
204
+
205
+ this.deleteNode(node);
206
+ return true;
207
+ }
208
+
209
+ find(key: K): K | null {
210
+ const node = this.findNode(key);
211
+ return node ? node.key : null;
212
+ }
213
+
214
+ findNearestInRange(center: K, distance: K): K[] {
215
+ // Calculate the range bounds
216
+ const start = this.subtractDistance(center, distance);
217
+ const end = this.addDistance(center, distance);
218
+
219
+ // Use existing range search (O(log n + k))
220
+ return this.findRange(start, end);
221
+ }
222
+
223
+ private subtractDistance(center: K, distance: K): K {
224
+ if (typeof center === "number" && typeof distance === "number") {
225
+ return (center - distance) as K;
226
+ }
227
+
228
+ // For strings, we can't easily subtract distance, so just return center
229
+ // This means string searches will be exact matches only
230
+ return center;
231
+ }
232
+
233
+ private addDistance(center: K, distance: K): K {
234
+ if (typeof center === "number" && typeof distance === "number") {
235
+ return (center + distance) as K;
236
+ }
237
+
238
+ // For strings, we can't easily add distance, so just return center
239
+ // This means string searches will be exact matches only
240
+ return center;
241
+ }
242
+
243
+ findRange(start: K, end: K): K[] {
244
+ const result: K[] = [];
245
+ this.inorderRange(this.root, start, end, result);
246
+ return result;
247
+ }
248
+
249
+ getAllSorted(): K[] {
250
+ const result: K[] = [];
251
+ this.inorder(this.root, result);
252
+ return result;
253
+ }
254
+
255
+ private findNode(key: K): RBTreeNode<K> | null {
256
+ let current = this.root;
257
+
258
+ while (current) {
259
+ const cmp = this.compareFn(key, current.key);
260
+ if (cmp === 0) return current;
261
+ current = cmp < 0 ? current.left : current.right;
262
+ }
263
+
264
+ return null;
265
+ }
266
+
267
+ private insertNode(node: RBTreeNode<K>): void {
268
+ let parent = null;
269
+ let current = this.root;
270
+
271
+ while (current) {
272
+ parent = current;
273
+ const cmp = this.compareFn(node.key, current.key);
274
+ current = cmp < 0 ? current.left : current.right;
275
+ }
276
+
277
+ node.parent = parent;
278
+ if (!parent) {
279
+ this.root = node;
280
+ } else {
281
+ const cmp = this.compareFn(node.key, parent.key);
282
+ if (cmp < 0) {
283
+ parent.left = node;
284
+ } else {
285
+ parent.right = node;
286
+ }
287
+ }
288
+ }
289
+
290
+ private fixInsert(node: RBTreeNode<K>): void {
291
+ while (node.parent && node.parent.color === Color.RED) {
292
+ if (node.parent === node.parent.parent?.left) {
293
+ const uncle = node.parent.parent.right;
294
+
295
+ if (uncle?.color === Color.RED) {
296
+ node.parent.color = Color.BLACK;
297
+ uncle.color = Color.BLACK;
298
+ node.parent.parent.color = Color.RED;
299
+ node = node.parent.parent;
300
+ } else {
301
+ if (node === node.parent.right) {
302
+ node = node.parent;
303
+ this.rotateLeft(node);
304
+ }
305
+
306
+ if (node.parent) {
307
+ node.parent.color = Color.BLACK;
308
+ if (node.parent.parent) {
309
+ node.parent.parent.color = Color.RED;
310
+ this.rotateRight(node.parent.parent);
311
+ }
312
+ }
313
+ }
314
+ } else {
315
+ const uncle = node.parent.parent?.left;
316
+
317
+ if (uncle?.color === Color.RED) {
318
+ node.parent.color = Color.BLACK;
319
+ uncle.color = Color.BLACK;
320
+ if (node.parent.parent) {
321
+ node.parent.parent.color = Color.RED;
322
+ node = node.parent.parent;
323
+ }
324
+ } else {
325
+ if (node === node.parent.left) {
326
+ node = node.parent;
327
+ this.rotateRight(node);
328
+ }
329
+
330
+ if (node.parent) {
331
+ node.parent.color = Color.BLACK;
332
+ if (node.parent.parent) {
333
+ node.parent.parent.color = Color.RED;
334
+ this.rotateLeft(node.parent.parent);
335
+ }
336
+ }
337
+ }
338
+ }
339
+ }
340
+
341
+ if (this.root) {
342
+ this.root.color = Color.BLACK;
343
+ }
344
+ }
345
+
346
+ private deleteNode(node: RBTreeNode<K>): void {
347
+ let y = node;
348
+ let yOriginalColor = y.color;
349
+ let x: RBTreeNode<K> | null;
350
+
351
+ if (!node.left) {
352
+ x = node.right;
353
+ this.transplant(node, node.right);
354
+ } else if (!node.right) {
355
+ x = node.left;
356
+ this.transplant(node, node.left);
357
+ } else {
358
+ y = this.minimum(node.right);
359
+ yOriginalColor = y.color;
360
+ x = y.right;
361
+
362
+ if (y.parent === node) {
363
+ if (x) x.parent = y;
364
+ } else {
365
+ this.transplant(y, y.right);
366
+ y.right = node.right;
367
+ if (y.right) y.right.parent = y;
368
+ }
369
+
370
+ this.transplant(node, y);
371
+ y.left = node.left;
372
+ if (y.left) y.left.parent = y;
373
+ y.color = node.color;
374
+ }
375
+
376
+ if (yOriginalColor === Color.BLACK && x) {
377
+ this.fixDelete(x);
378
+ }
379
+ }
380
+
381
+ private fixDelete(node: RBTreeNode<K>): void {
382
+ while (node !== this.root && node.color === Color.BLACK) {
383
+ if (node === node.parent?.left) {
384
+ let sibling = node.parent.right;
385
+
386
+ if (sibling?.color === Color.RED) {
387
+ sibling.color = Color.BLACK;
388
+ node.parent.color = Color.RED;
389
+ this.rotateLeft(node.parent);
390
+ sibling = node.parent.right;
391
+ }
392
+
393
+ if (
394
+ sibling?.left?.color !== Color.RED &&
395
+ sibling?.right?.color !== Color.RED
396
+ ) {
397
+ if (sibling) {
398
+ sibling.color = Color.RED;
399
+ }
400
+ node = node.parent;
401
+ } else {
402
+ if (sibling?.right?.color !== Color.RED) {
403
+ if (sibling.left) sibling.left.color = Color.BLACK;
404
+ sibling.color = Color.RED;
405
+ this.rotateRight(sibling);
406
+ sibling = node.parent.right;
407
+ }
408
+
409
+ if (sibling) {
410
+ sibling.color = node.parent.color;
411
+ node.parent.color = Color.BLACK;
412
+ if (sibling.right) sibling.right.color = Color.BLACK;
413
+ this.rotateLeft(node.parent);
414
+ }
415
+ if (!this.root) {
416
+ throw new Error("Root is null");
417
+ }
418
+ node = this.root;
419
+ }
420
+ } else {
421
+ let sibling = node.parent?.left;
422
+
423
+ if (sibling?.color === Color.RED) {
424
+ sibling.color = Color.BLACK;
425
+ if (node.parent) node.parent.color = Color.RED;
426
+ if (node.parent) this.rotateRight(node.parent);
427
+ sibling = node.parent?.left;
428
+ }
429
+
430
+ if (
431
+ sibling?.right?.color !== Color.RED &&
432
+ sibling?.left?.color !== Color.RED
433
+ ) {
434
+ if (sibling) {
435
+ sibling.color = Color.RED;
436
+ }
437
+ if (node.parent === null) {
438
+ throw new Error("Node parent is null");
439
+ }
440
+ node = node.parent;
441
+ } else {
442
+ if (sibling?.left?.color !== Color.RED) {
443
+ if (sibling.right) sibling.right.color = Color.BLACK;
444
+ sibling.color = Color.RED;
445
+ this.rotateLeft(sibling);
446
+ sibling = node.parent?.left;
447
+ }
448
+
449
+ if (sibling) {
450
+ sibling.color = node.parent?.color || Color.BLACK;
451
+ if (node.parent) node.parent.color = Color.BLACK;
452
+ if (sibling.left) sibling.left.color = Color.BLACK;
453
+ if (node.parent) this.rotateRight(node.parent);
454
+ }
455
+ if (!this.root) {
456
+ throw new Error("Root is null");
457
+ }
458
+ node = this.root;
459
+ }
460
+ }
461
+ }
462
+
463
+ node.color = Color.BLACK;
464
+ }
465
+
466
+ private rotateLeft(node: RBTreeNode<K>): void {
467
+ const rightChild = node.right;
468
+ if (!rightChild) {
469
+ throw new Error("Right child is null");
470
+ }
471
+ node.right = rightChild.left;
472
+
473
+ if (rightChild.left) {
474
+ rightChild.left.parent = node;
475
+ }
476
+
477
+ rightChild.parent = node.parent;
478
+
479
+ if (!node.parent) {
480
+ this.root = rightChild;
481
+ } else if (node === node.parent.left) {
482
+ node.parent.left = rightChild;
483
+ } else {
484
+ node.parent.right = rightChild;
485
+ }
486
+
487
+ rightChild.left = node;
488
+ node.parent = rightChild;
489
+ }
490
+
491
+ private rotateRight(node: RBTreeNode<K>): void {
492
+ const leftChild = node.left;
493
+ if (!leftChild) {
494
+ throw new Error("Left child is null");
495
+ }
496
+ node.left = leftChild.right;
497
+
498
+ if (leftChild.right) {
499
+ leftChild.right.parent = node;
500
+ }
501
+
502
+ leftChild.parent = node.parent;
503
+
504
+ if (!node.parent) {
505
+ this.root = leftChild;
506
+ } else if (node === node.parent.right) {
507
+ node.parent.right = leftChild;
508
+ } else {
509
+ node.parent.left = leftChild;
510
+ }
511
+
512
+ leftChild.right = node;
513
+ node.parent = leftChild;
514
+ }
515
+
516
+ private transplant(u: RBTreeNode<K>, v: RBTreeNode<K> | null): void {
517
+ if (!u.parent) {
518
+ this.root = v;
519
+ } else if (u === u.parent.left) {
520
+ u.parent.left = v;
521
+ } else {
522
+ u.parent.right = v;
523
+ }
524
+
525
+ if (v) {
526
+ v.parent = u.parent;
527
+ }
528
+ }
529
+
530
+ private minimum(node: RBTreeNode<K>): RBTreeNode<K> {
531
+ while (node.left) {
532
+ node = node.left;
533
+ }
534
+ return node;
535
+ }
536
+
537
+ private inorder(node: RBTreeNode<K> | null, result: K[]): void {
538
+ if (node) {
539
+ this.inorder(node.left, result);
540
+ result.push(node.key);
541
+ this.inorder(node.right, result);
542
+ }
543
+ }
544
+
545
+ private inorderRange(
546
+ node: RBTreeNode<K> | null,
547
+ start: K,
548
+ end: K,
549
+ result: K[],
550
+ ): void {
551
+ if (!node) return;
552
+
553
+ const startCmp = this.compareFn(node.key, start);
554
+ const endCmp = this.compareFn(node.key, end);
555
+
556
+ if (startCmp > 0) {
557
+ this.inorderRange(node.left, start, end, result);
558
+ }
559
+
560
+ if (startCmp >= 0 && endCmp <= 0) {
561
+ result.push(node.key);
562
+ }
563
+
564
+ if (endCmp < 0) {
565
+ this.inorderRange(node.right, start, end, result);
566
+ }
567
+ }
568
+ }
569
+
570
+ /**
571
+ * LRU cache with binary search capabilities using Red-Black tree
572
+ * All operations are O(log n) for ordered queries and O(1) for LRU operations
573
+ */
574
+ export class OrderedLRUCache<K extends number | string, V> {
575
+ private cache = new Map<K, V>();
576
+ private tree: RedBlackTree<K>;
577
+ private readonly maxSize: number;
578
+ private readonly compareFn: (a: K, b: K) => number;
579
+
580
+ constructor(maxSize: number, compareFn?: (a: K, b: K) => number) {
581
+ this.maxSize = maxSize;
582
+ this.compareFn = compareFn || ((a, b) => (a < b ? -1 : a > b ? 1 : 0));
583
+ this.tree = new RedBlackTree(this.compareFn);
584
+ }
585
+
586
+ /**
587
+ * Get value by exact key (O(1))
588
+ */
589
+ get(key: K): V | undefined {
590
+ const value = this.cache.get(key);
591
+ if (value) {
592
+ // Refresh position by removing and re-adding
593
+ this.cache.delete(key);
594
+ this.cache.set(key, value);
595
+ }
596
+ return value;
597
+ }
598
+
599
+ /**
600
+ * Set key-value pair (O(log n) for tree operations, O(1) for cache)
601
+ */
602
+ set(key: K, value: V): void {
603
+ const isUpdate = this.cache.has(key);
604
+
605
+ if (isUpdate) {
606
+ this.cache.delete(key);
607
+ } else {
608
+ if (this.cache.size >= this.maxSize) {
609
+ // Remove oldest entry (first item in map)
610
+ const firstKey = this.cache.keys().next().value;
611
+ if (firstKey) {
612
+ this.cache.delete(firstKey);
613
+ this.tree.delete(firstKey);
614
+ }
615
+ }
616
+ // Add to tree index for new keys
617
+ this.tree.insert(key);
618
+ }
619
+
620
+ this.cache.set(key, value);
621
+ }
622
+
623
+ /**
624
+ * Find exact key using tree search (O(log n))
625
+ */
626
+ findExact(key: K): V | undefined {
627
+ const foundKey = this.tree.find(key);
628
+ if (foundKey !== null) {
629
+ return this.get(key);
630
+ }
631
+ return undefined;
632
+ }
633
+
634
+ /**
635
+ * Find keys within distance of center point (O(log n + k) where k is result count)
636
+ * Returns empty array if no keys found in range
637
+ */
638
+ findNearestInRange(center: K, distance: K): Array<{ key: K; value: V }> {
639
+ const nearestKeys = this.tree.findNearestInRange(center, distance);
640
+ const result: Array<{ key: K; value: V }> = [];
641
+
642
+ for (const key of nearestKeys) {
643
+ const value = this.get(key);
644
+ if (value !== undefined) {
645
+ result.push({ key, value });
646
+ }
647
+ }
648
+
649
+ return result;
650
+ }
651
+
652
+ /**
653
+ * Find all key-value pairs in range [start, end] (O(log n + k) where k is result count)
654
+ */
655
+ findRange(start: K, end: K): Array<{ key: K; value: V }> {
656
+ const keys = this.tree.findRange(start, end);
657
+ const result: Array<{ key: K; value: V }> = [];
658
+
659
+ for (const key of keys) {
660
+ const value = this.get(key);
661
+ if (value !== undefined) {
662
+ result.push({ key, value });
663
+ }
664
+ }
665
+
666
+ return result;
667
+ }
668
+
669
+ /**
670
+ * Get all keys in sorted order (O(n))
671
+ */
672
+ getSortedKeys(): ReadonlyArray<K> {
673
+ return this.tree.getAllSorted();
674
+ }
675
+
676
+ has(key: K): boolean {
677
+ return this.cache.has(key);
678
+ }
679
+
680
+ delete(key: K): boolean {
681
+ const deleted = this.cache.delete(key);
682
+ if (deleted) {
683
+ this.tree.delete(key);
684
+ }
685
+ return deleted;
686
+ }
687
+
688
+ clear(): void {
689
+ this.cache.clear();
690
+ this.tree = new RedBlackTree(this.compareFn);
691
+ }
692
+
693
+ get size(): number {
694
+ return this.cache.size;
695
+ }
696
+ }
@@ -6,7 +6,7 @@
6
6
  "access-control-allow-origin": "*",
7
7
  "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "2057283",
9
+ "content-length": "2055451",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
6
6
  "access-control-allow-origin": "*",
7
7
  "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "2185975",
9
+ "content-length": "2192280",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
6
6
  "access-control-allow-origin": "*",
7
7
  "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "2120135",
9
+ "content-length": "2115680",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
6
6
  "access-control-allow-origin": "*",
7
7
  "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "2221511",
9
+ "content-length": "2217276",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -6,7 +6,7 @@
6
6
  "access-control-allow-origin": "*",
7
7
  "access-control-expose-headers": "Content-Length, Content-Range, X-Cache, X-Actual-Start-Time, X-Actual-Duration, X-Transcode-Time-Ms, X-Total-Server-Time-Ms",
8
8
  "cache-control": "public, max-age=3600",
9
- "content-length": "2037521",
9
+ "content-length": "2025221",
10
10
  "content-type": "video/iso.segment",
11
11
  "x-powered-by": "Express"
12
12
  },
@@ -1 +1 @@
1
- {"version":"1.0","type":"com.editframe/manifest","sourceUrl":"http://web:3000/head-moov-480p.mp4","duration":10,"durationMs":10000,"baseUrl":"http://localhost:63315","videoRenditions":[{"id":"high","width":1920,"height":1080,"bitrate":5000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"medium","width":1280,"height":720,"bitrate":2500000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"low","width":854,"height":480,"bitrate":1000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"scrub","width":320,"height":180,"bitrate":100000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029\"","segmentDuration":30,"segmentDurationMs":30000,"segmentDurationsMs":[30000],"frameRate":15,"profile":"High","level":"4.1"}],"audioRenditions":[{"id":"audio","channels":1,"sampleRate":48000,"bitrate":128000,"codec":"mp4a.40.2","container":"audio/mp4","mimeType":"audio/mp4; codecs=\"mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2026.66,2005.33,1984,2005.33,2026.66],"language":"en"}],"endpoints":{"initSegment":"http://localhost:63315/api/v1/transcode/{rendition}/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4","mediaSegment":"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4"},"jitInfo":{"parallelTranscodingSupported":true,"expectedTranscodeLatency":500,"segmentCount":5,"scrubSegmentCount":1}}
1
+ {"version":"1.0","type":"com.editframe/manifest","sourceUrl":"http://web:3000/head-moov-480p.mp4","duration":10,"durationMs":10000,"baseUrl":"http://localhost:63315","videoRenditions":[{"id":"high","width":1920,"height":1078,"bitrate":5000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"medium","width":1280,"height":718,"bitrate":2500000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"low","width":854,"height":480,"bitrate":1000000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029,mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2000,2000,2000,2000,2000],"frameRate":30,"profile":"High","level":"4.1"},{"id":"scrub","width":320,"height":180,"bitrate":100000,"codec":"avc1.640029","container":"video/mp4","mimeType":"video/mp4; codecs=\"avc1.640029\"","segmentDuration":30,"segmentDurationMs":30000,"segmentDurationsMs":[30000],"frameRate":15,"profile":"High","level":"4.1"}],"audioRenditions":[{"id":"audio","channels":1,"sampleRate":48000,"bitrate":128000,"codec":"mp4a.40.2","container":"audio/mp4","mimeType":"audio/mp4; codecs=\"mp4a.40.2\"","segmentDuration":2,"segmentDurationMs":2000,"segmentDurationsMs":[2026.66,2005.33,1984,2005.33,2026.66],"language":"en"}],"endpoints":{"initSegment":"http://localhost:63315/api/v1/transcode/{rendition}/init.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4","mediaSegment":"http://localhost:63315/api/v1/transcode/{rendition}/{segmentId}.m4s?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4"},"jitInfo":{"parallelTranscodingSupported":true,"expectedTranscodeLatency":500,"segmentCount":5,"scrubSegmentCount":1}}
@@ -8,7 +8,7 @@
8
8
  "cache-control": "public, max-age=300",
9
9
  "content-length": "2045",
10
10
  "content-type": "application/json; charset=utf-8",
11
- "etag": "W/\"81b-wi6z588RhWTgs57jivyDs3lEkkA\"",
11
+ "etag": "W/\"81b-HUWtw7SxFRSpD9Ic54e9i9HWJnc\"",
12
12
  "x-powered-by": "Express"
13
13
  },
14
14
  "url": "/api/v1/transcode/manifest.json?url=http%3A%2F%2Fweb%3A3000%2Fhead-moov-480p.mp4",