@editframe/elements 0.19.2-beta.0 → 0.20.0-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 (96) 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 +24 -0
  14. package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +39 -0
  15. package/dist/elements/EFMedia/shared/GlobalInputCache.js +57 -0
  16. package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +27 -0
  17. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +106 -0
  18. package/dist/elements/EFMedia.js +25 -1
  19. package/dist/elements/EFSurface.browsertest.d.ts +0 -0
  20. package/dist/elements/EFSurface.d.ts +30 -0
  21. package/dist/elements/EFSurface.js +96 -0
  22. package/dist/elements/EFTemporal.js +7 -6
  23. package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
  24. package/dist/elements/EFThumbnailStrip.d.ts +86 -0
  25. package/dist/elements/EFThumbnailStrip.js +490 -0
  26. package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
  27. package/dist/elements/EFTimegroup.d.ts +7 -7
  28. package/dist/elements/EFTimegroup.js +59 -16
  29. package/dist/elements/updateAnimations.browsertest.d.ts +13 -0
  30. package/dist/elements/updateAnimations.d.ts +5 -0
  31. package/dist/elements/updateAnimations.js +37 -13
  32. package/dist/getRenderInfo.js +1 -1
  33. package/dist/gui/ContextMixin.js +27 -14
  34. package/dist/gui/EFControls.browsertest.d.ts +0 -0
  35. package/dist/gui/EFControls.d.ts +38 -0
  36. package/dist/gui/EFControls.js +51 -0
  37. package/dist/gui/EFFilmstrip.d.ts +40 -1
  38. package/dist/gui/EFFilmstrip.js +240 -3
  39. package/dist/gui/EFPreview.js +2 -1
  40. package/dist/gui/EFScrubber.d.ts +6 -5
  41. package/dist/gui/EFScrubber.js +31 -21
  42. package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
  43. package/dist/gui/EFTimeDisplay.d.ts +2 -6
  44. package/dist/gui/EFTimeDisplay.js +13 -23
  45. package/dist/gui/TWMixin.js +1 -1
  46. package/dist/gui/currentTimeContext.d.ts +3 -0
  47. package/dist/gui/currentTimeContext.js +3 -0
  48. package/dist/gui/durationContext.d.ts +3 -0
  49. package/dist/gui/durationContext.js +3 -0
  50. package/dist/index.d.ts +3 -0
  51. package/dist/index.js +4 -1
  52. package/dist/style.css +1 -1
  53. package/dist/transcoding/types/index.d.ts +11 -0
  54. package/dist/utils/LRUCache.d.ts +46 -0
  55. package/dist/utils/LRUCache.js +382 -1
  56. package/dist/utils/LRUCache.test.d.ts +1 -0
  57. package/package.json +2 -2
  58. package/src/elements/ContextProxiesController.ts +123 -0
  59. package/src/elements/EFCaptions.browsertest.ts +1820 -0
  60. package/src/elements/EFCaptions.ts +373 -36
  61. package/src/elements/EFImage.ts +4 -1
  62. package/src/elements/EFMedia/AssetIdMediaEngine.ts +30 -1
  63. package/src/elements/EFMedia/AssetMediaEngine.ts +33 -0
  64. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +3 -8
  65. package/src/elements/EFMedia/BaseMediaEngine.ts +35 -0
  66. package/src/elements/EFMedia/JitMediaEngine.ts +48 -0
  67. package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
  68. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
  69. package/src/elements/EFMedia.ts +38 -1
  70. package/src/elements/EFSurface.browsertest.ts +155 -0
  71. package/src/elements/EFSurface.ts +141 -0
  72. package/src/elements/EFTemporal.ts +14 -8
  73. package/src/elements/EFThumbnailStrip.browsertest.ts +591 -0
  74. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +713 -0
  75. package/src/elements/EFThumbnailStrip.ts +905 -0
  76. package/src/elements/EFTimegroup.browsertest.ts +56 -7
  77. package/src/elements/EFTimegroup.ts +88 -18
  78. package/src/elements/updateAnimations.browsertest.ts +361 -12
  79. package/src/elements/updateAnimations.ts +68 -19
  80. package/src/gui/ContextMixin.browsertest.ts +0 -25
  81. package/src/gui/ContextMixin.ts +44 -20
  82. package/src/gui/EFControls.browsertest.ts +175 -0
  83. package/src/gui/EFControls.ts +84 -0
  84. package/src/gui/EFFilmstrip.ts +323 -4
  85. package/src/gui/EFPreview.ts +2 -1
  86. package/src/gui/EFScrubber.ts +29 -25
  87. package/src/gui/EFTimeDisplay.browsertest.ts +237 -0
  88. package/src/gui/EFTimeDisplay.ts +12 -40
  89. package/src/gui/currentTimeContext.ts +5 -0
  90. package/src/gui/durationContext.ts +3 -0
  91. package/src/transcoding/types/index.ts +13 -0
  92. package/src/utils/LRUCache.test.ts +272 -0
  93. package/src/utils/LRUCache.ts +543 -0
  94. package/types.json +1 -1
  95. package/dist/transcoding/cache/CacheManager.d.ts +0 -73
  96. package/src/transcoding/cache/CacheManager.ts +0 -208
@@ -18,7 +18,11 @@ import { createRef, ref } from "lit/directives/ref.js";
18
18
  import { styleMap } from "lit/directives/style-map.js";
19
19
 
20
20
  import { EFAudio } from "../elements/EFAudio.js";
21
- import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
21
+ import {
22
+ type Caption,
23
+ EFCaptions,
24
+ EFCaptionsActiveWord,
25
+ } from "../elements/EFCaptions.js";
22
26
  import { EFImage } from "../elements/EFImage.js";
23
27
  import type { TemporalMixinInterface } from "../elements/EFTemporal.js";
24
28
  import { EFTimegroup } from "../elements/EFTimegroup.js";
@@ -223,8 +227,295 @@ export class EFVideoFilmstrip extends FilmstripItem {
223
227
 
224
228
  @customElement("ef-captions-filmstrip")
225
229
  export class EFCaptionsFilmstrip extends FilmstripItem {
226
- contents() {
227
- return html` 📝 `;
230
+ render() {
231
+ const captions = this.element as EFCaptions;
232
+ const captionsData = captions.unifiedCaptionsDataTask.value;
233
+
234
+ return html`<div style=${styleMap(this.gutterStyles)}>
235
+ <div
236
+ class="bg-slate-300 relative"
237
+ ?data-focused=${this.isFocused}
238
+ @mouseenter=${() => {
239
+ if (this.focusContext) {
240
+ this.focusContext.focusedElement = this.element;
241
+ }
242
+ }}
243
+ @mouseleave=${() => {
244
+ if (this.focusContext) {
245
+ this.focusContext.focusedElement = null;
246
+ }
247
+ }}
248
+ >
249
+ <div
250
+ ?data-focused=${this.isFocused}
251
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400 overflow-hidden"
252
+ style=${styleMap(this.trimPortionStyles)}
253
+ >
254
+ 📝 ${this.renderCaptionsData(captionsData)}
255
+ </div>
256
+ </div>
257
+ ${this.renderChildren()}
258
+ </div>`;
259
+ }
260
+
261
+ renderCaptionsData(captionsData: Caption | null | undefined) {
262
+ if (!captionsData) {
263
+ return html``;
264
+ }
265
+
266
+ // Get current time for highlighting active elements
267
+ const captions = this.element as EFCaptions;
268
+ const rootTimegroup = captions.rootTimegroup;
269
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
270
+ const captionsLocalTimeMs = currentTimeMs - captions.startTimeMs;
271
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1000;
272
+
273
+ // Show all segments with text content, let them clip naturally
274
+ const segmentElements = captionsData.segments.map((segment) => {
275
+ const isActive =
276
+ captionsLocalTimeSec >= segment.start &&
277
+ captionsLocalTimeSec < segment.end;
278
+
279
+ return html`<div
280
+ class="absolute border border-slate-600 text-xs overflow-hidden flex items-center ${isActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-slate-100"}"
281
+ style=${styleMap({
282
+ left: `${this.pixelsPerMs * segment.start * 1000}px`,
283
+ width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,
284
+ height: "100%",
285
+ top: "0px",
286
+ })}
287
+ title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
288
+ >
289
+ <span class="px-0.5 text-[8px] ${isActive ? "font-bold" : ""}">${segment.text}</span>
290
+ </div>`;
291
+ });
292
+
293
+ return html`${segmentElements}`;
294
+ }
295
+
296
+ renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
297
+ // Also render normal DOM children (like ef-captions-active-word elements)
298
+ return super.renderChildren();
299
+ }
300
+ }
301
+
302
+ @customElement("ef-captions-active-word-filmstrip")
303
+ export class EFCaptionsActiveWordFilmstrip extends FilmstripItem {
304
+ get captionsTrackStyles() {
305
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
306
+ return {
307
+ position: "relative",
308
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
309
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,
310
+ };
311
+ }
312
+
313
+ render() {
314
+ // Get parent captions element and its data
315
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
316
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
317
+
318
+ if (!captionsData) {
319
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
320
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
321
+ 🗣️ Active Word
322
+ </div>
323
+ </div>`;
324
+ }
325
+
326
+ // Get current time for highlighting
327
+ const rootTimegroup = parentCaptions.rootTimegroup;
328
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
329
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
330
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1000;
331
+
332
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
333
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
334
+ ${captionsData.word_segments.map((word) => {
335
+ const isCurrentlyActive =
336
+ captionsLocalTimeSec >= word.start &&
337
+ captionsLocalTimeSec < word.end;
338
+
339
+ return html`<div
340
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-blue-50 border-blue-200"}"
341
+ style=${styleMap({
342
+ left: `${this.pixelsPerMs * word.start * 1000}px`,
343
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,
344
+ height: "100%",
345
+ top: "0px",
346
+ })}
347
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
348
+ >
349
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-yellow-200">${word.text.trim()}</span>` : ""}
350
+ </div>`;
351
+ })}
352
+ </div>
353
+ </div>`;
354
+ }
355
+ }
356
+
357
+ @customElement("ef-captions-segment-filmstrip")
358
+ export class EFCaptionsSegmentFilmstrip extends FilmstripItem {
359
+ get captionsTrackStyles() {
360
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
361
+ return {
362
+ position: "relative",
363
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
364
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,
365
+ };
366
+ }
367
+
368
+ render() {
369
+ // Get parent captions element and its data
370
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
371
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
372
+
373
+ if (!captionsData) {
374
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
375
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
376
+ 📄 Segment
377
+ </div>
378
+ </div>`;
379
+ }
380
+
381
+ // Get current time for highlighting
382
+ const rootTimegroup = parentCaptions.rootTimegroup;
383
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
384
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
385
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1000;
386
+
387
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
388
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
389
+ ${captionsData.segments.map((segment) => {
390
+ const isCurrentlyActive =
391
+ captionsLocalTimeSec >= segment.start &&
392
+ captionsLocalTimeSec < segment.end;
393
+
394
+ return html`<div
395
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-green-200 border-green-500 font-bold z-[5]" : "bg-green-50 border-green-200"}"
396
+ style=${styleMap({
397
+ left: `${this.pixelsPerMs * segment.start * 1000}px`,
398
+ width: `${this.pixelsPerMs * (segment.end - segment.start) * 1000}px`,
399
+ height: "100%",
400
+ top: "0px",
401
+ })}
402
+ title="Segment: '${segment.text}' (${segment.start}s - ${segment.end}s)"
403
+ >
404
+ ${isCurrentlyActive ? html`<span class="px-0.5 text-[8px] font-bold whitespace-nowrap bg-green-200">${segment.text}</span>` : ""}
405
+ </div>`;
406
+ })}
407
+ </div>
408
+ </div>`;
409
+ }
410
+ }
411
+
412
+ @customElement("ef-captions-before-word-filmstrip")
413
+ export class EFCaptionsBeforeWordFilmstrip extends FilmstripItem {
414
+ get captionsTrackStyles() {
415
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
416
+ return {
417
+ position: "relative",
418
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
419
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,
420
+ };
421
+ }
422
+
423
+ render() {
424
+ // Get parent captions element and its data
425
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
426
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
427
+
428
+ if (!captionsData) {
429
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
430
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
431
+ ⬅️ Before
432
+ </div>
433
+ </div>`;
434
+ }
435
+
436
+ // Get current time for highlighting
437
+ const rootTimegroup = parentCaptions.rootTimegroup;
438
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
439
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
440
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1000;
441
+
442
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
443
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
444
+ ${captionsData.word_segments.map((word) => {
445
+ const isCurrentlyActive =
446
+ captionsLocalTimeSec >= word.start &&
447
+ captionsLocalTimeSec < word.end;
448
+
449
+ return html`<div
450
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
451
+ style=${styleMap({
452
+ left: `${this.pixelsPerMs * word.start * 1000}px`,
453
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,
454
+ height: "100%",
455
+ top: "0px",
456
+ })}
457
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
458
+ >
459
+ <!-- No text for before tracks - they're redundant -->
460
+ </div>`;
461
+ })}
462
+ </div>
463
+ </div>`;
464
+ }
465
+ }
466
+
467
+ @customElement("ef-captions-after-word-filmstrip")
468
+ export class EFCaptionsAfterWordFilmstrip extends FilmstripItem {
469
+ get captionsTrackStyles() {
470
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
471
+ return {
472
+ position: "relative",
473
+ left: `${this.pixelsPerMs * (parentCaptions?.startTimeWithinParentMs || 0)}px`,
474
+ width: `${this.pixelsPerMs * (parentCaptions?.durationMs || 0)}px`,
475
+ };
476
+ }
477
+
478
+ render() {
479
+ // Get parent captions element and its data
480
+ const parentCaptions = this.element.closest("ef-captions") as EFCaptions;
481
+ const captionsData = parentCaptions?.unifiedCaptionsDataTask.value;
482
+
483
+ if (!captionsData) {
484
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
485
+ <div class="bg-slate-300 border border-slate-500 h-[1.1rem] mb-[1px] text-xs">
486
+ ➡️ After
487
+ </div>
488
+ </div>`;
489
+ }
490
+
491
+ // Get current time for highlighting
492
+ const rootTimegroup = parentCaptions.rootTimegroup;
493
+ const currentTimeMs = rootTimegroup?.currentTimeMs || 0;
494
+ const captionsLocalTimeMs = currentTimeMs - parentCaptions.startTimeMs;
495
+ const captionsLocalTimeSec = captionsLocalTimeMs / 1000;
496
+
497
+ return html`<div style=${styleMap(this.captionsTrackStyles)}>
498
+ <div class="bg-slate-300 relative border border-slate-500 h-[1.1rem] mb-[1px] w-full">
499
+ ${captionsData.word_segments.map((word) => {
500
+ const isCurrentlyActive =
501
+ captionsLocalTimeSec >= word.start &&
502
+ captionsLocalTimeSec < word.end;
503
+
504
+ return html`<div
505
+ class="absolute border text-xs overflow-visible flex items-center ${isCurrentlyActive ? "bg-yellow-200 border-yellow-500 font-bold z-[5]" : "bg-purple-50 border-purple-200"}"
506
+ style=${styleMap({
507
+ left: `${this.pixelsPerMs * word.start * 1000}px`,
508
+ width: `${this.pixelsPerMs * (word.end - word.start) * 1000}px`,
509
+ height: "100%",
510
+ top: "0px",
511
+ })}
512
+ title="Word: '${word.text}' (${word.start}s - ${word.end}s)"
513
+ >
514
+ <!-- No text for after tracks - they're redundant -->
515
+ </div>`;
516
+ })}
517
+ </div>
518
+ </div>`;
228
519
  }
229
520
  }
230
521
 
@@ -496,6 +787,30 @@ const renderFilmstripChildren = (
496
787
  .pixelsPerMs=${pixelsPerMs}
497
788
  ></ef-captions-filmstrip>`;
498
789
  }
790
+ if (child instanceof EFCaptionsActiveWord) {
791
+ return html`<ef-captions-active-word-filmstrip
792
+ .element=${child}
793
+ .pixelsPerMs=${pixelsPerMs}
794
+ ></ef-captions-active-word-filmstrip>`;
795
+ }
796
+ if (child.tagName === "EF-CAPTIONS-SEGMENT") {
797
+ return html`<ef-captions-segment-filmstrip
798
+ .element=${child}
799
+ .pixelsPerMs=${pixelsPerMs}
800
+ ></ef-captions-segment-filmstrip>`;
801
+ }
802
+ if (child.tagName === "EF-CAPTIONS-BEFORE-ACTIVE-WORD") {
803
+ return html`<ef-captions-before-word-filmstrip
804
+ .element=${child}
805
+ .pixelsPerMs=${pixelsPerMs}
806
+ ></ef-captions-before-word-filmstrip>`;
807
+ }
808
+ if (child.tagName === "EF-CAPTIONS-AFTER-ACTIVE-WORD") {
809
+ return html`<ef-captions-after-word-filmstrip
810
+ .element=${child}
811
+ .pixelsPerMs=${pixelsPerMs}
812
+ ></ef-captions-after-word-filmstrip>`;
813
+ }
499
814
  if (child instanceof EFWaveform) {
500
815
  return html`<ef-waveform-filmstrip
501
816
  .element=${child}
@@ -769,7 +1084,7 @@ export class EFFilmstrip extends TWMixin(LitElement) {
769
1084
  @mousedown=${this.startScrub}
770
1085
  >
771
1086
  <div
772
- class="border-red pointer-events-none absolute z-10 h-full w-[2px] border-r-2 border-red-700"
1087
+ class="border-red pointer-events-none absolute z-[20] h-full w-[2px] border-r-2 border-red-700"
773
1088
  style=${styleMap({
774
1089
  left: `${this.pixelsPerMs * this.currentTimeMs}px`,
775
1090
  top: `${this.timelineScrolltop}px`,
@@ -833,6 +1148,10 @@ declare global {
833
1148
  "ef-audio-filmstrip": EFAudioFilmstrip;
834
1149
  "ef-video-filmstrip": EFVideoFilmstrip;
835
1150
  "ef-captions-filmstrip": EFCaptionsFilmstrip;
1151
+ "ef-captions-active-word-filmstrip": EFCaptionsActiveWordFilmstrip;
1152
+ "ef-captions-segment-filmstrip": EFCaptionsSegmentFilmstrip;
1153
+ "ef-captions-before-word-filmstrip": EFCaptionsBeforeWordFilmstrip;
1154
+ "ef-captions-after-word-filmstrip": EFCaptionsAfterWordFilmstrip;
836
1155
  "ef-waveform-filmstrip": EFWaveformFilmstrip;
837
1156
  "ef-image-filmstrip": EFImageFilmstrip;
838
1157
  "ef-html-filmstrip": EFHTMLFilmstrip;
@@ -1,12 +1,13 @@
1
1
  import { provide } from "@lit/context";
2
2
  import { css, html, LitElement } from "lit";
3
3
  import { customElement } from "lit/decorators.js";
4
+ import { EFTargetable } from "../elements/TargetController.js";
4
5
  import { ContextMixin } from "./ContextMixin.js";
5
6
  import { focusedElementContext } from "./focusedElementContext.js";
6
7
  import { TWMixin } from "./TWMixin.js";
7
8
 
8
9
  @customElement("ef-preview")
9
- export class EFPreview extends ContextMixin(TWMixin(LitElement)) {
10
+ export class EFPreview extends EFTargetable(ContextMixin(TWMixin(LitElement))) {
10
11
  static styles = [
11
12
  css`
12
13
  :host {
@@ -4,6 +4,8 @@ import { customElement, state } from "lit/decorators.js";
4
4
 
5
5
  import { ref } from "lit/directives/ref.js";
6
6
  import type { ContextMixinInterface } from "./ContextMixin.js";
7
+ import { currentTimeContext } from "./currentTimeContext.js";
8
+ import { durationContext } from "./durationContext.js";
7
9
  import { efContext } from "./efContext.js";
8
10
  import { playingContext } from "./playingContext.js";
9
11
 
@@ -16,8 +18,10 @@ export class EFScrubber extends LitElement {
16
18
  --ef-scrubber-background: rgb(209 213 219);
17
19
  --ef-scrubber-progress-color: rgb(37 99 235);
18
20
  --ef-scrubber-handle-size: 12px;
19
- display: block;
20
21
  width: 100%;
22
+ display: flex;
23
+ align-items: center;
24
+ justify-content: center;
21
25
  }
22
26
 
23
27
  .scrubber {
@@ -60,14 +64,17 @@ export class EFScrubber extends LitElement {
60
64
  @consume({ context: playingContext, subscribe: true })
61
65
  playing = false;
62
66
 
63
- @state()
64
- private lastTimeUpdateProgress = 0;
67
+ @consume({ context: currentTimeContext, subscribe: true })
68
+ currentTimeMs = Number.NaN;
69
+
70
+ @consume({ context: durationContext, subscribe: true })
71
+ durationMs = 0;
65
72
 
66
73
  @state()
67
74
  private scrubProgress = 0;
68
75
 
69
76
  @state()
70
- private isDragging = false;
77
+ private isMoving = false;
71
78
 
72
79
  private scrubberRef?: HTMLElement;
73
80
 
@@ -79,30 +86,33 @@ export class EFScrubber extends LitElement {
79
86
  const progress = Math.max(0, Math.min(1, x / rect.width));
80
87
 
81
88
  this.scrubProgress = progress;
82
- this.context.currentTimeMs =
83
- progress * (this.context.targetTimegroup?.durationMs ?? 0);
89
+ this.context.currentTimeMs = progress * this.durationMs;
84
90
  }
85
91
 
86
- private boundHandleMouseDown = (e: MouseEvent) => {
87
- this.isDragging = true;
92
+ private boundHandlePointerDown = (e: MouseEvent) => {
93
+ this.isMoving = true;
88
94
  e.preventDefault();
89
95
  this.updateProgress(e);
90
96
  };
91
97
 
92
- private boundHandleMouseMove = (e: MouseEvent) => {
93
- if (this.isDragging) {
98
+ private boundHandlePointerMove = (e: MouseEvent) => {
99
+ if (this.isMoving) {
94
100
  this.updateProgress(e);
95
101
  }
96
102
  };
97
103
 
98
- private boundHandleMouseUp = () => {
99
- this.isDragging = false;
104
+ private boundHandlePointerUp = () => {
105
+ this.isMoving = false;
100
106
  };
101
107
 
102
108
  render() {
103
- const displayProgress = this.isDragging
109
+ // Calculate progress from currentTimeMs and duration
110
+ const currentProgress =
111
+ this.durationMs > 0 ? (this.currentTimeMs ?? 0) / this.durationMs : 0;
112
+
113
+ const displayProgress = this.isMoving
104
114
  ? this.scrubProgress
105
- : this.lastTimeUpdateProgress;
115
+ : currentProgress;
106
116
 
107
117
  return html`
108
118
  <div
@@ -111,7 +121,7 @@ export class EFScrubber extends LitElement {
111
121
  })}
112
122
  part="scrubber"
113
123
  class="scrubber"
114
- @mousedown=${this.boundHandleMouseDown}
124
+ @mousedown=${this.boundHandlePointerDown}
115
125
  >
116
126
  <div class="progress" style="width: ${displayProgress * 100}%"></div>
117
127
  <div class="handle" style="left: ${displayProgress * 100}%"></div>
@@ -121,20 +131,14 @@ export class EFScrubber extends LitElement {
121
131
 
122
132
  connectedCallback() {
123
133
  super.connectedCallback();
124
- window.addEventListener("mouseup", this.boundHandleMouseUp);
125
- window.addEventListener("mousemove", this.boundHandleMouseMove);
126
-
127
- if (this.context) {
128
- this.context.addEventListener("timeupdate", (e: Event) => {
129
- this.lastTimeUpdateProgress = (e as CustomEvent).detail.progress;
130
- });
131
- }
134
+ window.addEventListener("pointerup", this.boundHandlePointerUp);
135
+ window.addEventListener("pointermove", this.boundHandlePointerMove);
132
136
  }
133
137
 
134
138
  disconnectedCallback() {
135
139
  super.disconnectedCallback();
136
- window.removeEventListener("mouseup", this.boundHandleMouseUp);
137
- window.removeEventListener("mousemove", this.boundHandleMouseMove);
140
+ window.removeEventListener("pointerup", this.boundHandlePointerUp);
141
+ window.removeEventListener("pointermove", this.boundHandlePointerMove);
138
142
  }
139
143
  }
140
144