@editframe/elements 0.20.4-beta.0 → 0.21.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.
- package/dist/DelayedLoadingState.js +0 -27
- package/dist/EF_FRAMEGEN.d.ts +5 -3
- package/dist/EF_FRAMEGEN.js +50 -11
- package/dist/_virtual/_@oxc-project_runtime@0.93.0/helpers/decorate.js +7 -0
- package/dist/elements/ContextProxiesController.js +2 -22
- package/dist/elements/EFAudio.js +4 -8
- package/dist/elements/EFCaptions.js +59 -84
- package/dist/elements/EFImage.js +5 -6
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +2 -4
- package/dist/elements/EFMedia/AssetMediaEngine.js +35 -30
- package/dist/elements/EFMedia/BaseMediaEngine.js +57 -73
- package/dist/elements/EFMedia/BufferedSeekingInput.js +134 -76
- package/dist/elements/EFMedia/JitMediaEngine.js +9 -19
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +3 -6
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +6 -5
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +1 -3
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +1 -1
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +1 -1
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +4 -16
- package/dist/elements/EFMedia/shared/BufferUtils.js +2 -15
- package/dist/elements/EFMedia/shared/GlobalInputCache.js +0 -24
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +0 -21
- package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +0 -17
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +1 -10
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.d.ts +29 -0
- package/dist/elements/EFMedia/videoTasks/MainVideoInputCache.js +32 -0
- package/dist/elements/EFMedia/videoTasks/ScrubInputCache.js +1 -15
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoBufferTask.js +1 -7
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoInputTask.js +8 -5
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.js +12 -13
- package/dist/elements/EFMedia/videoTasks/makeScrubVideoSegmentIdTask.js +1 -1
- package/dist/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.js +134 -70
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +7 -11
- package/dist/elements/EFMedia.js +26 -24
- package/dist/elements/EFSourceMixin.js +5 -7
- package/dist/elements/EFSurface.js +6 -9
- package/dist/elements/EFTemporal.js +19 -37
- package/dist/elements/EFThumbnailStrip.js +16 -59
- package/dist/elements/EFTimegroup.js +95 -90
- package/dist/elements/EFVideo.d.ts +6 -2
- package/dist/elements/EFVideo.js +142 -107
- package/dist/elements/EFWaveform.js +18 -27
- package/dist/elements/SampleBuffer.js +2 -5
- package/dist/elements/TargetController.js +3 -3
- package/dist/elements/durationConverter.js +4 -4
- package/dist/elements/updateAnimations.js +14 -35
- package/dist/gui/ContextMixin.js +23 -52
- package/dist/gui/EFConfiguration.js +7 -7
- package/dist/gui/EFControls.js +5 -5
- package/dist/gui/EFFilmstrip.js +77 -98
- package/dist/gui/EFFitScale.js +5 -6
- package/dist/gui/EFFocusOverlay.js +4 -4
- package/dist/gui/EFPreview.js +4 -4
- package/dist/gui/EFScrubber.js +9 -9
- package/dist/gui/EFTimeDisplay.js +5 -5
- package/dist/gui/EFToggleLoop.js +4 -4
- package/dist/gui/EFTogglePlay.js +5 -5
- package/dist/gui/EFWorkbench.js +5 -5
- package/dist/gui/TWMixin2.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/otel/BridgeSpanExporter.d.ts +13 -0
- package/dist/otel/BridgeSpanExporter.js +87 -0
- package/dist/otel/setupBrowserTracing.d.ts +12 -0
- package/dist/otel/setupBrowserTracing.js +30 -0
- package/dist/otel/tracingHelpers.d.ts +34 -0
- package/dist/otel/tracingHelpers.js +113 -0
- package/dist/transcoding/cache/RequestDeduplicator.js +0 -21
- package/dist/transcoding/cache/URLTokenDeduplicator.js +1 -21
- package/dist/transcoding/utils/UrlGenerator.js +2 -19
- package/dist/utils/LRUCache.js +6 -53
- package/package.json +10 -2
- package/src/elements/EFCaptions.browsertest.ts +2 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +65 -37
- package/src/elements/EFMedia/BaseMediaEngine.ts +110 -52
- package/src/elements/EFMedia/BufferedSeekingInput.ts +218 -101
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +7 -3
- package/src/elements/EFMedia/videoTasks/MainVideoInputCache.ts +76 -0
- package/src/elements/EFMedia/videoTasks/makeScrubVideoInputTask.ts +16 -10
- package/src/elements/EFMedia/videoTasks/makeScrubVideoSeekTask.ts +7 -1
- package/src/elements/EFMedia/videoTasks/makeUnifiedVideoSeekTask.ts +222 -116
- package/src/elements/EFMedia.ts +16 -1
- package/src/elements/EFTimegroup.browsertest.ts +10 -8
- package/src/elements/EFTimegroup.ts +164 -76
- package/src/elements/EFVideo.browsertest.ts +19 -27
- package/src/elements/EFVideo.ts +203 -101
- package/src/otel/BridgeSpanExporter.ts +150 -0
- package/src/otel/setupBrowserTracing.ts +68 -0
- package/src/otel/tracingHelpers.ts +251 -0
- package/types.json +1 -1
|
@@ -6,6 +6,7 @@ import { customElement, property } from "lit/decorators.js";
|
|
|
6
6
|
|
|
7
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
8
8
|
import { isContextMixin } from "../gui/ContextMixin.js";
|
|
9
|
+
import { isTracingEnabled, withSpan } from "../otel/tracingHelpers.js";
|
|
9
10
|
import type { AudioSpan } from "../transcoding/types/index.ts";
|
|
10
11
|
import { durationConverter } from "./durationConverter.js";
|
|
11
12
|
import { deepGetMediaElements } from "./EFMedia.js";
|
|
@@ -422,18 +423,47 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
422
423
|
}
|
|
423
424
|
|
|
424
425
|
async waitForFrameTasks() {
|
|
425
|
-
const
|
|
426
|
+
const result = await withSpan(
|
|
427
|
+
"timegroup.waitForFrameTasks",
|
|
428
|
+
{
|
|
429
|
+
timegroupId: this.id || "unknown",
|
|
430
|
+
mode: this.mode,
|
|
431
|
+
},
|
|
432
|
+
undefined,
|
|
433
|
+
async (span) => {
|
|
434
|
+
const innerStart = performance.now();
|
|
435
|
+
|
|
436
|
+
const temporalElements = deepGetElementsWithFrameTasks(this);
|
|
437
|
+
if (isTracingEnabled()) {
|
|
438
|
+
span.setAttribute("temporalElementsCount", temporalElements.length);
|
|
439
|
+
}
|
|
426
440
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
441
|
+
// Filter to only include temporally visible elements for frame processing
|
|
442
|
+
// Use animation-friendly visibility to prevent animation jumps at exact boundaries
|
|
443
|
+
const visibleElements = temporalElements.filter((element) => {
|
|
444
|
+
const animationState = evaluateTemporalStateForAnimation(element);
|
|
445
|
+
return animationState.isVisible;
|
|
446
|
+
});
|
|
447
|
+
if (isTracingEnabled()) {
|
|
448
|
+
span.setAttribute("visibleElementsCount", visibleElements.length);
|
|
449
|
+
}
|
|
433
450
|
|
|
434
|
-
|
|
435
|
-
|
|
451
|
+
const promiseStart = performance.now();
|
|
452
|
+
|
|
453
|
+
await Promise.all(
|
|
454
|
+
visibleElements.map((element) => element.frameTask.run()),
|
|
455
|
+
);
|
|
456
|
+
const promiseEnd = performance.now();
|
|
457
|
+
|
|
458
|
+
const innerEnd = performance.now();
|
|
459
|
+
if (isTracingEnabled()) {
|
|
460
|
+
span.setAttribute("actualInnerMs", innerEnd - innerStart);
|
|
461
|
+
span.setAttribute("promiseAwaitMs", promiseEnd - promiseStart);
|
|
462
|
+
}
|
|
463
|
+
},
|
|
436
464
|
);
|
|
465
|
+
|
|
466
|
+
return result;
|
|
437
467
|
}
|
|
438
468
|
|
|
439
469
|
mediaDurationsPromise: Promise<void> | undefined = undefined;
|
|
@@ -452,37 +482,53 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
452
482
|
* in calculations and it was not clear why.
|
|
453
483
|
*/
|
|
454
484
|
async #waitForMediaDurations() {
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
485
|
+
return withSpan(
|
|
486
|
+
"timegroup.waitForMediaDurations",
|
|
487
|
+
{
|
|
488
|
+
timegroupId: this.id || "unknown",
|
|
489
|
+
mode: this.mode,
|
|
490
|
+
},
|
|
491
|
+
undefined,
|
|
492
|
+
async (span) => {
|
|
493
|
+
// We must await updateComplete to ensure all media elements inside this are connected
|
|
494
|
+
// and will match deepGetMediaElements
|
|
495
|
+
await this.updateComplete;
|
|
496
|
+
const mediaElements = deepGetMediaElements(this);
|
|
497
|
+
if (isTracingEnabled()) {
|
|
498
|
+
span.setAttribute("mediaElementsCount", mediaElements.length);
|
|
499
|
+
}
|
|
466
500
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
501
|
+
// Then, we must await the fragmentIndexTask to ensure all media elements have their
|
|
502
|
+
// fragment index loaded, which is where their duration is parsed from.
|
|
503
|
+
await Promise.all(
|
|
504
|
+
mediaElements.map((m) =>
|
|
505
|
+
m.mediaEngineTask.value
|
|
506
|
+
? Promise.resolve()
|
|
507
|
+
: m.mediaEngineTask.run(),
|
|
508
|
+
),
|
|
509
|
+
);
|
|
470
510
|
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
511
|
+
// After waiting for durations, we must force some updates to cascade and ensure all temporal elements
|
|
512
|
+
// have correct durations and start times. It is not ideal that we have to do this inside here,
|
|
513
|
+
// but it is the best current way to ensure that all temporal elements have correct durations and start times.
|
|
474
514
|
|
|
475
|
-
|
|
476
|
-
|
|
515
|
+
// Next, we must flush the startTimeMs cache to ensure all media elements have their
|
|
516
|
+
// startTimeMs parsed fresh, otherwise the startTimeMs is cached per animation frame.
|
|
517
|
+
flushStartTimeMsCache();
|
|
477
518
|
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
// This also makes the filmstrip update correctly.
|
|
481
|
-
this.requestUpdate("currentTime");
|
|
482
|
-
// Finally, we must await updateComplete to ensure all temporal elements have their
|
|
483
|
-
// currentTime updated and all animations have run.
|
|
519
|
+
// Flush duration cache since child durations may have changed
|
|
520
|
+
flushSequenceDurationCache();
|
|
484
521
|
|
|
485
|
-
|
|
522
|
+
// Request an update to the currentTime of this group, ensuring that time updates will cascade
|
|
523
|
+
// down to children, forcing sequence groups to arrange correctly.
|
|
524
|
+
// This also makes the filmstrip update correctly.
|
|
525
|
+
this.requestUpdate("currentTime");
|
|
526
|
+
// Finally, we must await updateComplete to ensure all temporal elements have their
|
|
527
|
+
// currentTime updated and all animations have run.
|
|
528
|
+
|
|
529
|
+
await this.updateComplete;
|
|
530
|
+
},
|
|
531
|
+
);
|
|
486
532
|
}
|
|
487
533
|
|
|
488
534
|
get childTemporals() {
|
|
@@ -645,35 +691,52 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
645
691
|
}
|
|
646
692
|
|
|
647
693
|
async renderAudio(fromMs: number, toMs: number) {
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
694
|
+
return withSpan(
|
|
695
|
+
"timegroup.renderAudio",
|
|
696
|
+
{
|
|
697
|
+
timegroupId: this.id || "unknown",
|
|
698
|
+
fromMs,
|
|
699
|
+
toMs,
|
|
700
|
+
durationMs: toMs - fromMs,
|
|
701
|
+
},
|
|
702
|
+
undefined,
|
|
703
|
+
async (span) => {
|
|
704
|
+
// Here we determine the number of samples we need to render rather than the duration.
|
|
705
|
+
// We cannot tolerate having more or fewer samples than fit exactlly into AAC frames.
|
|
706
|
+
const durationMs = toMs - fromMs;
|
|
707
|
+
const duration = durationMs / 1000;
|
|
708
|
+
const exactSamples = 48000 * duration;
|
|
709
|
+
const aacFrames = exactSamples / 1024;
|
|
710
|
+
const alignedFrames = Math.round(aacFrames);
|
|
711
|
+
const contextSize = alignedFrames * 1024; // AAC-aligned sample count
|
|
712
|
+
|
|
713
|
+
if (isTracingEnabled()) {
|
|
714
|
+
span.setAttribute("contextSize", contextSize);
|
|
715
|
+
span.setAttribute("alignedFrames", alignedFrames);
|
|
716
|
+
}
|
|
663
717
|
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
);
|
|
671
|
-
}
|
|
718
|
+
// Debug logging for audio duration calculations
|
|
719
|
+
if (contextSize <= 0) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
`Duration must be greater than 0 when rendering audio. ${contextSize}ms`,
|
|
722
|
+
);
|
|
723
|
+
}
|
|
672
724
|
|
|
673
|
-
|
|
674
|
-
|
|
725
|
+
let audioContext: OfflineAudioContext;
|
|
726
|
+
try {
|
|
727
|
+
audioContext = new OfflineAudioContext(2, contextSize, 48000);
|
|
728
|
+
} catch (error) {
|
|
729
|
+
throw new Error(
|
|
730
|
+
`[EFTimegroup.renderAudio] Failed to create OfflineAudioContext(2, ${contextSize}, 48000) for renderAudio(${fromMs}, ${toMs}) with contextSize=${contextSize}: ${error instanceof Error ? error.message : String(error)}. This typically happens when audio parameters are invalid (e.g., contextSize <= 0).`,
|
|
731
|
+
);
|
|
732
|
+
}
|
|
675
733
|
|
|
676
|
-
|
|
734
|
+
await this.#addAudioToContext(audioContext, fromMs, toMs);
|
|
735
|
+
const renderedBuffer = await audioContext.startRendering();
|
|
736
|
+
|
|
737
|
+
return renderedBuffer;
|
|
738
|
+
},
|
|
739
|
+
);
|
|
677
740
|
}
|
|
678
741
|
|
|
679
742
|
/**
|
|
@@ -728,10 +791,21 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
728
791
|
// autoRun: EF_INTERACTIVE,
|
|
729
792
|
autoRun: false,
|
|
730
793
|
args: () => [this.ownCurrentTimeMs, this.currentTimeMs] as const,
|
|
731
|
-
task: async ([]) => {
|
|
794
|
+
task: async ([ownCurrentTimeMs, currentTimeMs]) => {
|
|
732
795
|
if (this.isRootTimegroup) {
|
|
733
|
-
await
|
|
734
|
-
|
|
796
|
+
await withSpan(
|
|
797
|
+
"timegroup.frameTask",
|
|
798
|
+
{
|
|
799
|
+
timegroupId: this.id || "unknown",
|
|
800
|
+
ownCurrentTimeMs,
|
|
801
|
+
currentTimeMs,
|
|
802
|
+
},
|
|
803
|
+
undefined,
|
|
804
|
+
async () => {
|
|
805
|
+
await this.waitForFrameTasks();
|
|
806
|
+
updateAnimations(this);
|
|
807
|
+
},
|
|
808
|
+
);
|
|
735
809
|
}
|
|
736
810
|
},
|
|
737
811
|
});
|
|
@@ -744,19 +818,33 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
744
818
|
if (!this.isRootTimegroup) {
|
|
745
819
|
return;
|
|
746
820
|
}
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
821
|
+
return withSpan(
|
|
822
|
+
"timegroup.seekTask",
|
|
823
|
+
{
|
|
824
|
+
timegroupId: this.id || "unknown",
|
|
825
|
+
targetTime: targetTime ?? 0,
|
|
826
|
+
durationMs: this.durationMs,
|
|
827
|
+
},
|
|
828
|
+
undefined,
|
|
829
|
+
async (span) => {
|
|
830
|
+
await this.waitForMediaDurations();
|
|
831
|
+
const newTime = Math.max(
|
|
832
|
+
0,
|
|
833
|
+
Math.min(targetTime ?? 0, this.durationMs / 1000),
|
|
834
|
+
);
|
|
835
|
+
if (isTracingEnabled()) {
|
|
836
|
+
span.setAttribute("newTime", newTime);
|
|
837
|
+
}
|
|
838
|
+
// Apply the clamped time back to currentTime
|
|
839
|
+
this.#currentTime = newTime;
|
|
840
|
+
this.requestUpdate("currentTime");
|
|
841
|
+
await this.runThrottledFrameTask();
|
|
842
|
+
this.#saveTimeToLocalStorage(this.#currentTime);
|
|
843
|
+
// This has to be set false here so any following seeks are not treated as pending
|
|
844
|
+
this.#seekInProgress = false;
|
|
845
|
+
return newTime;
|
|
846
|
+
},
|
|
751
847
|
);
|
|
752
|
-
// Apply the clamped time back to currentTime
|
|
753
|
-
this.#currentTime = newTime;
|
|
754
|
-
this.requestUpdate("currentTime");
|
|
755
|
-
await this.runThrottledFrameTask();
|
|
756
|
-
this.#saveTimeToLocalStorage(this.#currentTime);
|
|
757
|
-
// This has to be set false here so any following seeks are not treated as pending
|
|
758
|
-
this.#seekInProgress = false;
|
|
759
|
-
return newTime;
|
|
760
848
|
},
|
|
761
849
|
});
|
|
762
850
|
}
|
|
@@ -238,7 +238,7 @@ describe("EFVideo", () => {
|
|
|
238
238
|
|
|
239
239
|
// Should not throw when video asset is missing
|
|
240
240
|
expect(() => {
|
|
241
|
-
video.
|
|
241
|
+
video.paint(0);
|
|
242
242
|
}).not.toThrow();
|
|
243
243
|
});
|
|
244
244
|
});
|
|
@@ -267,7 +267,7 @@ describe("EFVideo", () => {
|
|
|
267
267
|
close: vi.fn(),
|
|
268
268
|
} as unknown as VideoFrame;
|
|
269
269
|
|
|
270
|
-
// Simulate frame painting (this would normally happen through
|
|
270
|
+
// Simulate frame painting (this would normally happen through paint method)
|
|
271
271
|
const ctx = canvas.getContext("2d");
|
|
272
272
|
if (ctx && mockFrame.codedWidth && mockFrame.codedHeight) {
|
|
273
273
|
canvas.width = mockFrame.codedWidth;
|
|
@@ -355,20 +355,16 @@ describe("EFVideo", () => {
|
|
|
355
355
|
|
|
356
356
|
// Simulate the decoder being in use
|
|
357
357
|
if (decoderLockDescriptor) {
|
|
358
|
-
// We can
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
// All should complete without throwing
|
|
365
|
-
await expect(
|
|
366
|
-
Promise.allSettled([paintPromise1, paintPromise2, paintPromise3]),
|
|
367
|
-
).resolves.toBeDefined();
|
|
358
|
+
// We can test that multiple paint calls don't cause issues
|
|
359
|
+
expect(() => {
|
|
360
|
+
video.paint(0);
|
|
361
|
+
video.paint(0);
|
|
362
|
+
video.paint(0);
|
|
363
|
+
}).not.toThrow();
|
|
368
364
|
}
|
|
369
365
|
});
|
|
370
366
|
|
|
371
|
-
test("
|
|
367
|
+
test("paint handles missing canvas gracefully", ({ expect }) => {
|
|
372
368
|
const container = document.createElement("div");
|
|
373
369
|
render(html`<ef-video></ef-video>`, container);
|
|
374
370
|
document.body.appendChild(container);
|
|
@@ -379,23 +375,19 @@ describe("EFVideo", () => {
|
|
|
379
375
|
const canvas = video.canvasElement;
|
|
380
376
|
canvas?.remove();
|
|
381
377
|
|
|
382
|
-
// Paint
|
|
383
|
-
expect(() =>
|
|
384
|
-
video.paintTask.run();
|
|
385
|
-
}).not.toThrow();
|
|
378
|
+
// Paint should handle missing canvas
|
|
379
|
+
expect(() => video.paint(0)).not.toThrow();
|
|
386
380
|
});
|
|
387
381
|
|
|
388
|
-
test("handles paint
|
|
382
|
+
test("handles paint with no video asset", ({ expect }) => {
|
|
389
383
|
const container = document.createElement("div");
|
|
390
384
|
render(html`<ef-video></ef-video>`, container);
|
|
391
385
|
document.body.appendChild(container);
|
|
392
386
|
|
|
393
387
|
const video = container.querySelector("ef-video") as EFVideo;
|
|
394
388
|
|
|
395
|
-
// Paint
|
|
396
|
-
expect(() =>
|
|
397
|
-
video.paintTask.run();
|
|
398
|
-
}).not.toThrow();
|
|
389
|
+
// Paint should handle missing video asset gracefully
|
|
390
|
+
expect(() => video.paint(0)).not.toThrow();
|
|
399
391
|
});
|
|
400
392
|
});
|
|
401
393
|
|
|
@@ -445,12 +437,12 @@ describe("EFVideo", () => {
|
|
|
445
437
|
// Should handle invalid seek times gracefully
|
|
446
438
|
expect(() => {
|
|
447
439
|
video.desiredSeekTimeMs = -1000; // Invalid negative time
|
|
448
|
-
video.
|
|
440
|
+
video.paint(-1000);
|
|
449
441
|
}).not.toThrow();
|
|
450
442
|
|
|
451
443
|
expect(() => {
|
|
452
444
|
video.desiredSeekTimeMs = Number.POSITIVE_INFINITY;
|
|
453
|
-
video.
|
|
445
|
+
video.paint(Number.POSITIVE_INFINITY);
|
|
454
446
|
}).not.toThrow();
|
|
455
447
|
});
|
|
456
448
|
|
|
@@ -462,14 +454,14 @@ describe("EFVideo", () => {
|
|
|
462
454
|
const video = container.querySelector("ef-video") as EFVideo;
|
|
463
455
|
|
|
464
456
|
// Start some operations
|
|
465
|
-
video.
|
|
457
|
+
video.paint(0);
|
|
466
458
|
|
|
467
459
|
// Remove element
|
|
468
460
|
video.remove();
|
|
469
461
|
|
|
470
462
|
// Should not cause errors
|
|
471
463
|
expect(() => {
|
|
472
|
-
video.
|
|
464
|
+
video.paint(0);
|
|
473
465
|
}).not.toThrow();
|
|
474
466
|
});
|
|
475
467
|
|
|
@@ -491,7 +483,7 @@ describe("EFVideo", () => {
|
|
|
491
483
|
|
|
492
484
|
// Should handle context loss gracefully
|
|
493
485
|
expect(() => {
|
|
494
|
-
video.
|
|
486
|
+
video.paint(0);
|
|
495
487
|
}).not.toThrow();
|
|
496
488
|
|
|
497
489
|
// Restore original method
|