@editframe/elements 0.17.6-beta.0 → 0.18.3-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/EF_FRAMEGEN.js +1 -1
- package/dist/ScrubTrackManager.d.ts +2 -2
- package/dist/elements/EFAudio.d.ts +21 -2
- package/dist/elements/EFAudio.js +41 -11
- package/dist/elements/EFImage.d.ts +1 -0
- package/dist/elements/EFImage.js +11 -3
- package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +18 -0
- package/dist/elements/EFMedia/AssetIdMediaEngine.js +41 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +47 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +116 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +96 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.js +159 -0
- package/dist/elements/EFMedia/JitMediaEngine.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/JitMediaEngine.d.ts +31 -0
- package/dist/elements/EFMedia/JitMediaEngine.js +62 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.d.ts +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioBufferTask.js +48 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.js +138 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.js +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioInputTask.js +22 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +24 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +18 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +16 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +104 -0
- package/dist/elements/EFMedia/services/AudioElementFactory.d.ts +22 -0
- package/dist/elements/EFMedia/services/AudioElementFactory.js +72 -0
- package/dist/elements/EFMedia/services/MediaSourceService.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/services/MediaSourceService.d.ts +47 -0
- package/dist/elements/EFMedia/services/MediaSourceService.js +73 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.d.ts +7 -0
- package/dist/elements/EFMedia/shared/AudioSpanUtils.js +54 -0
- package/dist/elements/EFMedia/shared/BufferUtils.d.ts +70 -0
- package/dist/elements/EFMedia/shared/BufferUtils.js +89 -0
- package/dist/elements/EFMedia/shared/MediaTaskUtils.d.ts +23 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts +1 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.d.ts +16 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoBufferTask.js +46 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.js +16 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.d.ts +3 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoInputTask.js +27 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSeekTask.js +25 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.js +18 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.js +16 -0
- package/dist/elements/EFMedia.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia.d.ts +75 -111
- package/dist/elements/EFMedia.js +141 -1111
- package/dist/elements/EFTemporal.d.ts +1 -1
- package/dist/elements/EFTemporal.js +1 -1
- package/dist/elements/EFTimegroup.d.ts +11 -0
- package/dist/elements/EFTimegroup.js +88 -13
- package/dist/elements/EFVideo.d.ts +60 -29
- package/dist/elements/EFVideo.js +103 -203
- package/dist/elements/EFWaveform.js +2 -2
- package/dist/elements/SampleBuffer.d.ts +14 -0
- package/dist/elements/SampleBuffer.js +52 -0
- package/dist/getRenderInfo.d.ts +2 -2
- package/dist/getRenderInfo.js +2 -1
- package/dist/gui/ContextMixin.js +17 -70
- package/dist/gui/EFFilmstrip.d.ts +3 -3
- package/dist/gui/EFFilmstrip.js +1 -1
- package/dist/gui/EFFitScale.d.ts +2 -2
- package/dist/gui/TWMixin.js +1 -1
- package/dist/gui/services/ElementConnectionManager.browsertest.d.ts +1 -0
- package/dist/gui/services/ElementConnectionManager.d.ts +59 -0
- package/dist/gui/services/ElementConnectionManager.js +128 -0
- package/dist/gui/services/PlaybackController.browsertest.d.ts +1 -0
- package/dist/gui/services/PlaybackController.d.ts +103 -0
- package/dist/gui/services/PlaybackController.js +290 -0
- package/dist/services/MediaSourceManager.d.ts +62 -0
- package/dist/services/MediaSourceManager.js +211 -0
- package/dist/style.css +1 -1
- package/dist/transcoding/cache/CacheManager.d.ts +73 -0
- package/dist/transcoding/cache/RequestDeduplicator.d.ts +29 -0
- package/dist/transcoding/cache/RequestDeduplicator.js +53 -0
- package/dist/transcoding/cache/RequestDeduplicator.test.d.ts +1 -0
- package/dist/transcoding/types/index.d.ts +242 -0
- package/dist/transcoding/utils/MediaUtils.d.ts +9 -0
- package/dist/transcoding/utils/UrlGenerator.d.ts +26 -0
- package/dist/transcoding/utils/UrlGenerator.js +45 -0
- package/dist/transcoding/utils/constants.d.ts +27 -0
- package/dist/utils/LRUCache.d.ts +34 -0
- package/dist/utils/LRUCache.js +115 -0
- package/package.json +3 -2
- package/src/elements/EFAudio.browsertest.ts +183 -43
- package/src/elements/EFAudio.ts +59 -13
- package/src/elements/EFImage.browsertest.ts +42 -0
- package/src/elements/EFImage.ts +23 -3
- package/src/elements/EFMedia/AssetIdMediaEngine.test.ts +222 -0
- package/src/elements/EFMedia/AssetIdMediaEngine.ts +70 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +210 -0
- package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +170 -0
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +400 -0
- package/src/elements/EFMedia/BufferedSeekingInput.ts +267 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +110 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.browsertest.ts +554 -0
- package/src/elements/EFMedia/audioTasks/makeAudioBufferTask.ts +81 -0
- package/src/elements/EFMedia/audioTasks/makeAudioFrequencyAnalysisTask.ts +241 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.browsertest.ts +59 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInitSegmentFetchTask.ts +23 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.browsertest.ts +55 -0
- package/src/elements/EFMedia/audioTasks/makeAudioInputTask.ts +35 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +42 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +34 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +23 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +174 -0
- package/src/elements/EFMedia/services/AudioElementFactory.browsertest.ts +325 -0
- package/src/elements/EFMedia/services/AudioElementFactory.ts +119 -0
- package/src/elements/EFMedia/services/MediaSourceService.browsertest.ts +257 -0
- package/src/elements/EFMedia/services/MediaSourceService.ts +102 -0
- package/src/elements/EFMedia/shared/AudioSpanUtils.ts +128 -0
- package/src/elements/EFMedia/shared/BufferUtils.ts +310 -0
- package/src/elements/EFMedia/shared/MediaTaskUtils.ts +44 -0
- package/src/elements/EFMedia/shared/RenditionHelpers.browsertest.ts +247 -0
- package/src/elements/EFMedia/shared/RenditionHelpers.ts +79 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.browsertest.ts +128 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.test.ts +233 -0
- package/src/elements/EFMedia/tasks/makeMediaEngineTask.ts +89 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.browsertest.ts +555 -0
- package/src/elements/EFMedia/videoTasks/makeVideoBufferTask.ts +79 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.browsertest.ts +59 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts +23 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.browsertest.ts +55 -0
- package/src/elements/EFMedia/videoTasks/makeVideoInputTask.ts +45 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSeekTask.ts +44 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +32 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +23 -0
- package/src/elements/EFMedia.browsertest.ts +658 -265
- package/src/elements/EFMedia.ts +173 -1763
- package/src/elements/EFTemporal.ts +3 -4
- package/src/elements/EFTimegroup.browsertest.ts +6 -3
- package/src/elements/EFTimegroup.ts +152 -21
- package/src/elements/EFVideo.browsertest.ts +115 -37
- package/src/elements/EFVideo.ts +123 -452
- package/src/elements/EFWaveform.ts +1 -1
- package/src/elements/MediaController.ts +2 -12
- package/src/elements/SampleBuffer.ts +97 -0
- package/src/gui/ContextMixin.ts +23 -104
- package/src/gui/services/ElementConnectionManager.browsertest.ts +263 -0
- package/src/gui/services/ElementConnectionManager.ts +224 -0
- package/src/gui/services/PlaybackController.browsertest.ts +437 -0
- package/src/gui/services/PlaybackController.ts +521 -0
- package/src/services/MediaSourceManager.ts +333 -0
- package/src/transcoding/cache/CacheManager.ts +208 -0
- package/src/transcoding/cache/RequestDeduplicator.test.ts +170 -0
- package/src/transcoding/cache/RequestDeduplicator.ts +65 -0
- package/src/transcoding/types/index.ts +265 -0
- package/src/transcoding/utils/MediaUtils.ts +63 -0
- package/src/transcoding/utils/UrlGenerator.ts +68 -0
- package/src/transcoding/utils/constants.ts +36 -0
- package/src/utils/LRUCache.ts +153 -0
- package/test/EFVideo.framegen.browsertest.ts +38 -29
- package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__32da3954ba60c96ad732020c65a08ebc/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__b0b2b07efcf607de8ee0f650328c32f7/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a75c2252b542e0c152c780e9a8d7b154/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a64ff1cfb1b52cae14df4b5dfa1e222b/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__91e8a522f950809b9f09f4173113b4b0/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__e66d2c831d951e74ad0aeaa6489795d0/metadata.json +21 -0
- 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
- package/test/__cache__/GET__api_v1_transcode_high_1_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__26197f6f7c46cacb0a71134131c3f775/metadata.json +21 -0
- 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
- package/test/__cache__/GET__api_v1_transcode_high_2_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__4cb6774cd3650ccf59c8f8dc6678c0b9/metadata.json +21 -0
- 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
- package/test/__cache__/GET__api_v1_transcode_high_3_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0b3b2b1c8933f7fcf8a9ecaa88d58b41/metadata.json +21 -0
- 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
- package/test/__cache__/GET__api_v1_transcode_high_init_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__0798c479b44aaeef850609a430f6e613/metadata.json +21 -0
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/data.bin +1 -0
- package/test/__cache__/GET__api_v1_transcode_manifest_json_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__3be92a0437de726b431ed5af2369158a/metadata.json +19 -0
- package/test/createJitTestClips.ts +320 -188
- package/test/recordReplayProxyPlugin.js +302 -0
- package/test/useAssetMSW.ts +1 -1
- package/test/useMSW.ts +35 -22
- package/types.json +1 -1
- package/dist/JitTranscodingClient.d.ts +0 -167
- package/dist/JitTranscodingClient.js +0 -373
- package/dist/ScrubTrackManager.js +0 -216
- package/dist/elements/printTaskStatus.js +0 -11
- package/src/elements/__screenshots__/EFMedia.browsertest.ts/EFMedia-JIT-audio-playback-audioBufferTask-should-work-in-JIT-mode-without-URL-errors-1.png +0 -0
- package/test/EFVideo.frame-tasks.browsertest.ts +0 -524
- /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
- /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
- /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/services/AudioElementFactory.browsertest.d.ts} +0 -0
package/src/elements/EFVideo.ts
CHANGED
|
@@ -1,14 +1,19 @@
|
|
|
1
|
-
import { VideoAsset } from "@editframe/assets/EncodedAsset.js";
|
|
2
1
|
import { Task } from "@lit/task";
|
|
3
2
|
import debug from "debug";
|
|
4
3
|
import { css, html, type PropertyValueMap } from "lit";
|
|
5
|
-
import { customElement, state } from "lit/decorators.js";
|
|
4
|
+
import { customElement, property, state } from "lit/decorators.js";
|
|
6
5
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
6
|
+
|
|
7
7
|
import { DelayedLoadingState } from "../DelayedLoadingState.js";
|
|
8
8
|
import { TWMixin } from "../gui/TWMixin.js";
|
|
9
|
-
import {
|
|
9
|
+
import type { CacheStats, ScrubTrackManager } from "../ScrubTrackManager.js";
|
|
10
|
+
import { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
|
|
11
|
+
import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts";
|
|
12
|
+
import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.ts";
|
|
13
|
+
import { makeVideoSeekTask } from "./EFMedia/videoTasks/makeVideoSeekTask.ts";
|
|
14
|
+
import { makeVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoSegmentFetchTask.ts";
|
|
15
|
+
import { makeVideoSegmentIdTask } from "./EFMedia/videoTasks/makeVideoSegmentIdTask.ts";
|
|
10
16
|
import { EFMedia } from "./EFMedia.js";
|
|
11
|
-
import { printTaskStatus } from "./printTaskStatus.ts";
|
|
12
17
|
|
|
13
18
|
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
14
19
|
declare global {
|
|
@@ -25,6 +30,16 @@ interface LoadingState {
|
|
|
25
30
|
|
|
26
31
|
@customElement("ef-video")
|
|
27
32
|
export class EFVideo extends TWMixin(EFMedia) {
|
|
33
|
+
static get observedAttributes() {
|
|
34
|
+
const parentAttributes = EFMedia.observedAttributes || [];
|
|
35
|
+
return [
|
|
36
|
+
...parentAttributes,
|
|
37
|
+
"video-buffer-duration",
|
|
38
|
+
"max-video-buffer-fetches",
|
|
39
|
+
"enable-video-buffering",
|
|
40
|
+
];
|
|
41
|
+
}
|
|
42
|
+
|
|
28
43
|
static styles = [
|
|
29
44
|
/**
|
|
30
45
|
*
|
|
@@ -92,14 +107,38 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
92
107
|
canvasRef = createRef<HTMLCanvasElement>();
|
|
93
108
|
|
|
94
109
|
/**
|
|
95
|
-
*
|
|
110
|
+
* Duration in milliseconds for video buffering ahead of current time
|
|
111
|
+
* @domAttribute "video-buffer-duration"
|
|
96
112
|
*/
|
|
97
|
-
|
|
113
|
+
@property({ type: Number, attribute: "video-buffer-duration" })
|
|
114
|
+
videoBufferDurationMs = 60000; // 60 seconds
|
|
98
115
|
|
|
99
116
|
/**
|
|
100
|
-
*
|
|
117
|
+
* Maximum number of concurrent video segment fetches for buffering
|
|
118
|
+
* @domAttribute "max-video-buffer-fetches"
|
|
101
119
|
*/
|
|
102
|
-
|
|
120
|
+
@property({ type: Number, attribute: "max-video-buffer-fetches" })
|
|
121
|
+
maxVideoBufferFetches = 2;
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Enable/disable video buffering system
|
|
125
|
+
* @domAttribute "enable-video-buffering"
|
|
126
|
+
*/
|
|
127
|
+
@property({ type: Boolean, attribute: "enable-video-buffering" })
|
|
128
|
+
enableVideoBuffering = true;
|
|
129
|
+
|
|
130
|
+
// Video-specific tasks
|
|
131
|
+
videoSegmentIdTask = makeVideoSegmentIdTask(this);
|
|
132
|
+
videoInitSegmentFetchTask = makeVideoInitSegmentFetchTask(this);
|
|
133
|
+
videoSegmentFetchTask = makeVideoSegmentFetchTask(this);
|
|
134
|
+
videoInputTask = makeVideoInputTask(this);
|
|
135
|
+
videoSeekTask = makeVideoSeekTask(this);
|
|
136
|
+
videoBufferTask = makeVideoBufferTask(this);
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Scrub track manager for fast timeline navigation
|
|
140
|
+
*/
|
|
141
|
+
scrubTrackManager?: ScrubTrackManager;
|
|
103
142
|
|
|
104
143
|
/**
|
|
105
144
|
* Delayed loading state manager for user feedback
|
|
@@ -153,35 +192,14 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
153
192
|
return this.canvasRef.value;
|
|
154
193
|
}
|
|
155
194
|
|
|
156
|
-
// The underlying video decoder MUST NOT be used concurrently.
|
|
157
|
-
// If frames are fed in out of order, the decoder may crash.
|
|
158
|
-
#decoderLock = false;
|
|
159
|
-
|
|
160
|
-
// Track if decoder needs reset due to errors
|
|
161
|
-
#decoderNeedsReset = false;
|
|
162
|
-
|
|
163
195
|
frameTask = new Task(this, {
|
|
164
196
|
args: () => [this.desiredSeekTimeMs] as const,
|
|
165
197
|
onError: (error) => {
|
|
166
198
|
console.error("frameTask error", error);
|
|
167
199
|
},
|
|
200
|
+
onComplete: () => {},
|
|
168
201
|
task: async ([_desiredSeekTimeMs], { signal }) => {
|
|
169
|
-
await this.
|
|
170
|
-
if (signal.aborted) {
|
|
171
|
-
return;
|
|
172
|
-
}
|
|
173
|
-
await this.fragmentIndexTask.taskComplete;
|
|
174
|
-
if (signal.aborted) {
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
await this.mediaSegmentsTask.taskComplete;
|
|
178
|
-
if (signal.aborted) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
await this.videoAssetTask.taskComplete;
|
|
182
|
-
if (signal.aborted) {
|
|
183
|
-
return;
|
|
184
|
-
}
|
|
202
|
+
await this.videoSeekTask.taskComplete;
|
|
185
203
|
await this.paintTask.taskComplete;
|
|
186
204
|
if (signal.aborted) {
|
|
187
205
|
return;
|
|
@@ -189,70 +207,10 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
189
207
|
},
|
|
190
208
|
});
|
|
191
209
|
|
|
192
|
-
get frameTaskStatus() {
|
|
193
|
-
return {
|
|
194
|
-
desiredSeekTimeMs: this.desiredSeekTimeMs,
|
|
195
|
-
fragmentIndexTask: printTaskStatus(this.fragmentIndexTask.status),
|
|
196
|
-
seekTask: printTaskStatus(this.seekTask.status),
|
|
197
|
-
mediaSegmentsTask: printTaskStatus(this.mediaSegmentsTask.status),
|
|
198
|
-
assetSegmentLoader: printTaskStatus(this.assetSegmentLoader.status),
|
|
199
|
-
assetSegmentKeysTask: printTaskStatus(this.assetSegmentKeysTask.status),
|
|
200
|
-
assetInitSegmentsTask: printTaskStatus(this.assetInitSegmentsTask.status),
|
|
201
|
-
videoAssetTask: printTaskStatus(this.videoAssetTask.status),
|
|
202
|
-
paintTask: printTaskStatus(this.paintTask.status),
|
|
203
|
-
frameTask: printTaskStatus(this.frameTask.status),
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
#lastVideoAsset: any = null;
|
|
208
|
-
|
|
209
210
|
protected updated(
|
|
210
211
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
211
212
|
): void {
|
|
212
213
|
super.updated(changedProperties);
|
|
213
|
-
|
|
214
|
-
const currentVideoAsset = this.videoAssetTask.value;
|
|
215
|
-
if (currentVideoAsset !== this.#lastVideoAsset) {
|
|
216
|
-
// Track video asset changes for reference, but don't reset decoder
|
|
217
|
-
// Decoder resets should only happen due to actual decoder errors, not normal asset transitions
|
|
218
|
-
this.#lastVideoAsset = currentVideoAsset;
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
// Initialize scrub track manager for JIT transcode mode
|
|
222
|
-
this.initializeScrubTrackManager();
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
/**
|
|
226
|
-
* Initialize scrub track manager if needed
|
|
227
|
-
*/
|
|
228
|
-
private async initializeScrubTrackManager(): Promise<void> {
|
|
229
|
-
const mode = this.effectiveMode;
|
|
230
|
-
|
|
231
|
-
// Only initialize for JIT transcode mode with valid src
|
|
232
|
-
if (mode === "jit-transcode" && this.src && !this.scrubTrackManager) {
|
|
233
|
-
const jitClient = this.jitClientTask.value;
|
|
234
|
-
if (jitClient) {
|
|
235
|
-
try {
|
|
236
|
-
this.scrubTrackManager = new ScrubTrackManager(this.src, jitClient, {
|
|
237
|
-
onLoadingStateChange: (isLoading: boolean, message?: string) => {
|
|
238
|
-
if (isLoading) {
|
|
239
|
-
// Only show loading for user-visible operations (non-background)
|
|
240
|
-
this.startDelayedLoading(
|
|
241
|
-
"scrub-segment",
|
|
242
|
-
message || "Loading scrub track...",
|
|
243
|
-
);
|
|
244
|
-
} else {
|
|
245
|
-
this.clearDelayedLoading("scrub-segment");
|
|
246
|
-
}
|
|
247
|
-
},
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
await this.scrubTrackManager.initialize();
|
|
251
|
-
} catch (error) {
|
|
252
|
-
console.warn("Failed to initialize scrub track manager:", error);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
214
|
}
|
|
257
215
|
|
|
258
216
|
/**
|
|
@@ -288,107 +246,24 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
288
246
|
};
|
|
289
247
|
}
|
|
290
248
|
|
|
291
|
-
videoAssetTask = new Task(this, {
|
|
292
|
-
autoRun: true,
|
|
293
|
-
args: () => [this.effectiveMode, this.mediaSegmentsTask.value] as const,
|
|
294
|
-
onError: (error) => {
|
|
295
|
-
console.error("videoAsset task error", error);
|
|
296
|
-
},
|
|
297
|
-
task: async ([mode, _files], { signal: _signal }) => {
|
|
298
|
-
await this.mediaSegmentsTask.taskComplete;
|
|
299
|
-
if (_signal.aborted) {
|
|
300
|
-
return undefined;
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
await this.fragmentIndexTask.taskComplete;
|
|
304
|
-
if (_signal.aborted) {
|
|
305
|
-
return undefined;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// Get fresh values
|
|
309
|
-
const files = this.mediaSegmentsTask.value;
|
|
310
|
-
const fragmentIndex = this.fragmentIndexTask.value;
|
|
311
|
-
|
|
312
|
-
if (!files) {
|
|
313
|
-
log("trace: videoAsset task aborted - no files");
|
|
314
|
-
throw new Error(
|
|
315
|
-
`Video asset creation failed: No media segment files available. This indicates a problem with media segment loading for source: "${this.src}"`,
|
|
316
|
-
);
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
const computedVideoTrackId = Object.values(fragmentIndex ?? {}).find(
|
|
320
|
-
(track) => track.type === "video",
|
|
321
|
-
)?.track;
|
|
322
|
-
|
|
323
|
-
if (computedVideoTrackId === undefined) {
|
|
324
|
-
log("trace: videoAsset task aborted - no video track");
|
|
325
|
-
throw new Error(
|
|
326
|
-
`Video asset creation failed: No video track found in media segments. Source may not contain video content: "${this.src}"`,
|
|
327
|
-
);
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
const videoFile = files[computedVideoTrackId];
|
|
331
|
-
if (!videoFile) {
|
|
332
|
-
log("trace: videoAsset task aborted - no video file");
|
|
333
|
-
throw new Error(
|
|
334
|
-
`Video asset creation failed: Video file not available for track ${computedVideoTrackId}. Media segment loading may have failed for source: "${this.src}"`,
|
|
335
|
-
);
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
// Cleanup existing asset
|
|
339
|
-
const existingAsset = this.videoAssetTask.value;
|
|
340
|
-
if (existingAsset) {
|
|
341
|
-
for (const frame of existingAsset?.decodedFrames || []) {
|
|
342
|
-
frame.close();
|
|
343
|
-
}
|
|
344
|
-
const decoder = existingAsset?.videoDecoder;
|
|
345
|
-
if (decoder && decoder.state !== "closed") {
|
|
346
|
-
decoder.close();
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
if (_signal.aborted) {
|
|
351
|
-
return undefined;
|
|
352
|
-
}
|
|
353
|
-
|
|
354
|
-
log("trace: creating video asset", { mode });
|
|
355
|
-
|
|
356
|
-
// Get start time offset from fragment index (timing correction for FFmpeg processing)
|
|
357
|
-
const videoTrackFragmentIndex = Object.values(fragmentIndex ?? {}).find(
|
|
358
|
-
(track) => track.type === "video",
|
|
359
|
-
);
|
|
360
|
-
const startTimeOffsetMs = Number(
|
|
361
|
-
(videoTrackFragmentIndex?.startTimeOffsetMs ?? 0).toFixed(5),
|
|
362
|
-
);
|
|
363
|
-
|
|
364
|
-
// Single branching point for creation method
|
|
365
|
-
if (mode === "jit-transcode") {
|
|
366
|
-
const result = await VideoAsset.createFromCompleteMP4(
|
|
367
|
-
`jit-segment-${computedVideoTrackId}`,
|
|
368
|
-
videoFile,
|
|
369
|
-
{ startTimeOffsetMs },
|
|
370
|
-
);
|
|
371
|
-
return result;
|
|
372
|
-
}
|
|
373
|
-
const result = await VideoAsset.createFromReadableStream(
|
|
374
|
-
"video.mp4",
|
|
375
|
-
videoFile.stream(),
|
|
376
|
-
videoFile,
|
|
377
|
-
{ startTimeOffsetMs },
|
|
378
|
-
);
|
|
379
|
-
return result;
|
|
380
|
-
},
|
|
381
|
-
});
|
|
382
|
-
|
|
383
249
|
paintTask = new Task(this, {
|
|
384
250
|
args: () => [this.desiredSeekTimeMs] as const,
|
|
385
251
|
onError: (error) => {
|
|
386
252
|
console.error("paintTask error", error);
|
|
387
253
|
},
|
|
254
|
+
onComplete: () => {},
|
|
388
255
|
task: async ([_seekToMs], { signal }) => {
|
|
256
|
+
await this.videoSeekTask.taskComplete;
|
|
389
257
|
// Check if we're in production rendering mode vs preview mode
|
|
390
258
|
const isProductionRendering = this.isInProductionRenderingMode();
|
|
391
259
|
|
|
260
|
+
const sample = this.videoSeekTask.value;
|
|
261
|
+
if (sample) {
|
|
262
|
+
const videoFrame = sample.toVideoFrame();
|
|
263
|
+
this.displayFrame(videoFrame, _seekToMs);
|
|
264
|
+
videoFrame.close();
|
|
265
|
+
}
|
|
266
|
+
|
|
392
267
|
// EF_FRAMEGEN-aware rendering mode detection
|
|
393
268
|
if (!isProductionRendering) {
|
|
394
269
|
// Preview mode: skip rendering during initialization to prevent artifacts
|
|
@@ -417,277 +292,9 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
417
292
|
if (signal.aborted) {
|
|
418
293
|
return;
|
|
419
294
|
}
|
|
420
|
-
|
|
421
|
-
// CRITICAL: For segment transitions, ensure we wait for the correct mediaSegmentsTask
|
|
422
|
-
// This prevents using stale VideoAssets from previous segments
|
|
423
|
-
|
|
424
|
-
await this.mediaSegmentsTask.taskComplete;
|
|
425
|
-
if (signal.aborted) {
|
|
426
|
-
return;
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
// CRITICAL: Always await fresh videoAsset just before using it
|
|
430
|
-
// This prevents race conditions where old VideoAssets with closed decoders are used
|
|
431
|
-
await this.videoAssetTask.taskComplete;
|
|
432
|
-
if (signal.aborted) {
|
|
433
|
-
return;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
// Get fresh values after await - ensures we use current VideoAsset
|
|
437
|
-
const videoAsset = this.videoAssetTask.value;
|
|
438
|
-
const currentSeekToMs = this.desiredSeekTimeMs; // Use current seek time, not captured
|
|
439
|
-
|
|
440
|
-
if (!videoAsset) {
|
|
441
|
-
log("trace: paintTask aborted - no video asset");
|
|
442
|
-
throw new Error(
|
|
443
|
-
`Frame rendering failed: No video asset available. This may indicate a problem with video loading or an invalid source: "${this.src}"`,
|
|
444
|
-
);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
// Check if decoder needs reset due to previous errors
|
|
448
|
-
if (this.#decoderNeedsReset) {
|
|
449
|
-
try {
|
|
450
|
-
// Reset the video decoder
|
|
451
|
-
if (videoAsset?.videoDecoder) {
|
|
452
|
-
videoAsset.configureDecoder();
|
|
453
|
-
} else {
|
|
454
|
-
console.warn("No video decoder available for reset");
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
// Clear the flag after successful reset
|
|
458
|
-
this.#decoderNeedsReset = false;
|
|
459
|
-
} catch (resetError) {
|
|
460
|
-
console.error("reset error", resetError);
|
|
461
|
-
// Keep the flag set if reset fails
|
|
462
|
-
throw new Error(
|
|
463
|
-
`Frame rendering failed: Unable to reset video decoder after previous error. Decoder state: ${resetError instanceof Error ? resetError.message : "Unknown error"}. Try refreshing the page or reloading the video.`,
|
|
464
|
-
);
|
|
465
|
-
}
|
|
466
|
-
}
|
|
467
|
-
if (signal.aborted) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
if (this.#decoderLock) {
|
|
472
|
-
return;
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
try {
|
|
476
|
-
this.#decoderLock = true;
|
|
477
|
-
|
|
478
|
-
// Validate VideoAsset is still current and decoder is in valid state
|
|
479
|
-
const currentVideoAsset = this.videoAssetTask.value;
|
|
480
|
-
if (videoAsset !== currentVideoAsset) {
|
|
481
|
-
return; // Skip render with stale videoAsset
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
// Check decoder state before using it
|
|
485
|
-
const decoderState = videoAsset?.videoDecoder?.state;
|
|
486
|
-
if (decoderState === "closed") {
|
|
487
|
-
return; // Skip render with closed decoder
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
// Try scrub track first for JIT transcode mode
|
|
491
|
-
if (this.effectiveMode === "jit-transcode" && this.scrubTrackManager) {
|
|
492
|
-
const shouldUseScrub =
|
|
493
|
-
this.scrubTrackManager.shouldUseScrubTrack(currentSeekToMs);
|
|
494
|
-
const isFastSeeking = this.scrubTrackManager.isFastSeeking(
|
|
495
|
-
this.lastSeekTimeMs,
|
|
496
|
-
currentSeekToMs,
|
|
497
|
-
);
|
|
498
|
-
|
|
499
|
-
if (false || shouldUseScrub || isFastSeeking) {
|
|
500
|
-
try {
|
|
501
|
-
// Use delayed loading instead of immediate loading
|
|
502
|
-
this.startDelayedLoading(
|
|
503
|
-
"scrub-segment-load",
|
|
504
|
-
"Loading scrub segment...",
|
|
505
|
-
);
|
|
506
|
-
|
|
507
|
-
const scrubFrame =
|
|
508
|
-
await this.scrubTrackManager.getScrubFrame(currentSeekToMs);
|
|
509
|
-
|
|
510
|
-
if (scrubFrame && this.canvasElement) {
|
|
511
|
-
this.scrubTrackManager.recordCacheMiss();
|
|
512
|
-
this.lastSeekTimeMs = currentSeekToMs;
|
|
513
|
-
|
|
514
|
-
// Clear loading and display scrub frame
|
|
515
|
-
this.clearDelayedLoading("scrub-segment-load");
|
|
516
|
-
return this.displayFrame(scrubFrame, currentSeekToMs);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Scrub frame was null/failed - fall back to normal video
|
|
520
|
-
console.warn(
|
|
521
|
-
"Scrub track returned null frame, falling back to normal video",
|
|
522
|
-
);
|
|
523
|
-
this.clearDelayedLoading("scrub-segment-load");
|
|
524
|
-
this.startDelayedLoading(
|
|
525
|
-
"video-segment-fallback",
|
|
526
|
-
"Loading high quality video...",
|
|
527
|
-
);
|
|
528
|
-
} catch (error) {
|
|
529
|
-
this.clearDelayedLoading("scrub-segment-load");
|
|
530
|
-
console.warn(
|
|
531
|
-
"Scrub track failed, falling back to normal video:",
|
|
532
|
-
error,
|
|
533
|
-
);
|
|
534
|
-
|
|
535
|
-
// Show loading for normal video fallback
|
|
536
|
-
this.startDelayedLoading(
|
|
537
|
-
"video-segment-fallback",
|
|
538
|
-
"Loading high quality video...",
|
|
539
|
-
);
|
|
540
|
-
}
|
|
541
|
-
} else {
|
|
542
|
-
// Cache hit for normal video - scrub track manager exists, record the hit
|
|
543
|
-
this.scrubTrackManager?.recordCacheHit();
|
|
544
|
-
}
|
|
545
|
-
}
|
|
546
|
-
|
|
547
|
-
// Normal video rendering path (for all cases where scrub track isn't used)
|
|
548
|
-
// Check if we need to show loading for normal video operations
|
|
549
|
-
const shouldShowLoading =
|
|
550
|
-
!this.delayedLoadingState.isLoading &&
|
|
551
|
-
(this.effectiveMode !== "asset" || !videoAsset);
|
|
552
|
-
|
|
553
|
-
if (shouldShowLoading) {
|
|
554
|
-
this.startDelayedLoading("video-segment", "Loading video segment...");
|
|
555
|
-
}
|
|
556
|
-
|
|
557
|
-
// Render normal video - pass fresh VideoAsset reference
|
|
558
|
-
this.lastSeekTimeMs = currentSeekToMs;
|
|
559
|
-
const result = await this.renderNormalVideo(
|
|
560
|
-
videoAsset,
|
|
561
|
-
currentSeekToMs,
|
|
562
|
-
);
|
|
563
|
-
|
|
564
|
-
// Clear loading state after normal video renders
|
|
565
|
-
this.clearDelayedLoading("video-segment");
|
|
566
|
-
this.clearDelayedLoading("video-segment-fallback");
|
|
567
|
-
|
|
568
|
-
return result;
|
|
569
|
-
} catch (error) {
|
|
570
|
-
// Clear all loading states on error
|
|
571
|
-
this.clearDelayedLoading("scrub-segment-load");
|
|
572
|
-
this.clearDelayedLoading("video-segment");
|
|
573
|
-
this.clearDelayedLoading("video-segment-fallback");
|
|
574
|
-
|
|
575
|
-
// Handle errors with proper error propagation
|
|
576
|
-
if (error instanceof Error) {
|
|
577
|
-
if (
|
|
578
|
-
error.name === "DataError" &&
|
|
579
|
-
error.message.includes("key frame is required")
|
|
580
|
-
) {
|
|
581
|
-
console.warn(
|
|
582
|
-
"Decoder reset during VideoAsset due to key frame requirement",
|
|
583
|
-
);
|
|
584
|
-
this.#decoderNeedsReset = true;
|
|
585
|
-
|
|
586
|
-
if (this.effectiveMode === "jit-transcode") {
|
|
587
|
-
this.requestUpdate();
|
|
588
|
-
}
|
|
589
|
-
throw error;
|
|
590
|
-
}
|
|
591
|
-
if (error.name === "AbortError") {
|
|
592
|
-
// AbortError is expected behavior when tasks are cancelled
|
|
593
|
-
throw new Error(
|
|
594
|
-
"Frame rendering cancelled: Operation was aborted, likely due to a new seek request or component unmounting.",
|
|
595
|
-
);
|
|
596
|
-
}
|
|
597
|
-
if (
|
|
598
|
-
error.message.includes("VideoAsset decoder closed") ||
|
|
599
|
-
error.message.includes("recreation in progress")
|
|
600
|
-
) {
|
|
601
|
-
// This is now expected behavior during VideoAsset transitions - don't treat as error
|
|
602
|
-
return; // Gracefully abort instead of throwing
|
|
603
|
-
}
|
|
604
|
-
if (
|
|
605
|
-
error.name === "InvalidStateError" &&
|
|
606
|
-
error.message.includes("closed codec")
|
|
607
|
-
) {
|
|
608
|
-
// Expected during VideoAsset recreation - gracefully abort
|
|
609
|
-
return; // Gracefully abort instead of throwing
|
|
610
|
-
}
|
|
611
|
-
console.warn("Decoder reset during VideoAsset recreation", error);
|
|
612
|
-
this.#decoderNeedsReset = true;
|
|
613
|
-
throw error;
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
// For non-Error objects, still provide descriptive error
|
|
617
|
-
throw new Error(
|
|
618
|
-
`Frame rendering failed: Unknown error during video rendering at ${currentSeekToMs}ms. Error: ${String(error)}`,
|
|
619
|
-
);
|
|
620
|
-
} finally {
|
|
621
|
-
this.#decoderLock = false;
|
|
622
|
-
}
|
|
623
295
|
},
|
|
624
296
|
});
|
|
625
297
|
|
|
626
|
-
/**
|
|
627
|
-
* Render normal video using existing logic
|
|
628
|
-
*/
|
|
629
|
-
private async renderNormalVideo(
|
|
630
|
-
videoAsset: VideoAsset,
|
|
631
|
-
seekToMs: number,
|
|
632
|
-
): Promise<number> {
|
|
633
|
-
let targetSeekTimeSeconds = seekToMs / 1000;
|
|
634
|
-
|
|
635
|
-
try {
|
|
636
|
-
// Validate VideoAsset is still current before seeking
|
|
637
|
-
const currentVideoAsset = this.videoAssetTask.value;
|
|
638
|
-
if (videoAsset !== currentVideoAsset) {
|
|
639
|
-
throw new Error(
|
|
640
|
-
"VideoAsset decoder closed during seek - recreation in progress",
|
|
641
|
-
);
|
|
642
|
-
}
|
|
643
|
-
|
|
644
|
-
// Check decoder state immediately before seeking
|
|
645
|
-
const decoderState = videoAsset?.videoDecoder?.state;
|
|
646
|
-
if (decoderState === "closed") {
|
|
647
|
-
throw new Error(
|
|
648
|
-
"VideoAsset decoder closed during seek - recreation in progress",
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
|
-
if (this.effectiveMode === "jit-transcode") {
|
|
652
|
-
targetSeekTimeSeconds %= 2;
|
|
653
|
-
}
|
|
654
|
-
|
|
655
|
-
const frame = await videoAsset.seekToTime(targetSeekTimeSeconds);
|
|
656
|
-
|
|
657
|
-
if (frame) {
|
|
658
|
-
// Final validation that VideoAsset is still current before displaying
|
|
659
|
-
const finalVideoAsset = this.videoAssetTask.value;
|
|
660
|
-
if (videoAsset !== finalVideoAsset) {
|
|
661
|
-
frame.close(); // Clean up the frame
|
|
662
|
-
throw new Error(
|
|
663
|
-
"VideoAsset decoder closed during seek - recreation in progress",
|
|
664
|
-
);
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
// Read fresh time right before displaying - final safeguard against stale values
|
|
668
|
-
const finalSeekToMs = this.desiredSeekTimeMs;
|
|
669
|
-
return this.displayFrame(frame, finalSeekToMs);
|
|
670
|
-
}
|
|
671
|
-
|
|
672
|
-
log("trace: no frame returned from seekToTime");
|
|
673
|
-
throw new Error(
|
|
674
|
-
`Frame rendering failed: No frame available at time ${seekToMs}ms (${targetSeekTimeSeconds}s). This may indicate seeking beyond video duration, corrupted video data, or an incompatible video format.`,
|
|
675
|
-
);
|
|
676
|
-
} catch (error) {
|
|
677
|
-
if (
|
|
678
|
-
error instanceof Error &&
|
|
679
|
-
(error.message.includes("VideoAsset decoder closed") ||
|
|
680
|
-
error.message.includes("recreation in progress"))
|
|
681
|
-
) {
|
|
682
|
-
// This is the expected narrow timing window race condition during VideoAsset transitions
|
|
683
|
-
throw error; // Re-throw to let paintTask handle it gracefully
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
// Re-throw other unexpected errors
|
|
687
|
-
throw error;
|
|
688
|
-
}
|
|
689
|
-
}
|
|
690
|
-
|
|
691
298
|
/**
|
|
692
299
|
* Display a video frame on the canvas
|
|
693
300
|
*/
|
|
@@ -729,7 +336,7 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
729
336
|
);
|
|
730
337
|
}
|
|
731
338
|
|
|
732
|
-
log("trace: drawing frame to canvas");
|
|
339
|
+
log("trace: drawing frame to canvas", frame.timestamp / 1000);
|
|
733
340
|
ctx.drawImage(
|
|
734
341
|
frame,
|
|
735
342
|
0,
|
|
@@ -792,6 +399,70 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
792
399
|
return this.scrubTrackManager?.getCacheStats() || null;
|
|
793
400
|
}
|
|
794
401
|
|
|
402
|
+
// Getter properties for backward compatibility with tests
|
|
403
|
+
/**
|
|
404
|
+
* Effective mode - always returns "asset" for EFVideo
|
|
405
|
+
*/
|
|
406
|
+
get effectiveMode(): string {
|
|
407
|
+
return "asset";
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* Legacy getter for asset index loader (maps to mediaEngine task)
|
|
412
|
+
*/
|
|
413
|
+
get assetIndexLoader() {
|
|
414
|
+
return this.mediaEngineTask;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
/**
|
|
418
|
+
* Legacy getter for fragment index task (maps to videoSegmentIdTask)
|
|
419
|
+
*/
|
|
420
|
+
get fragmentIndexTask() {
|
|
421
|
+
return this.videoSegmentIdTask;
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
/**
|
|
425
|
+
* Legacy getter for seek task (maps to videoSeekTask)
|
|
426
|
+
*/
|
|
427
|
+
get seekTask() {
|
|
428
|
+
return this.videoSeekTask;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
/**
|
|
432
|
+
* Legacy getter for media segments task (maps to videoSegmentFetchTask)
|
|
433
|
+
*/
|
|
434
|
+
get mediaSegmentsTask() {
|
|
435
|
+
return this.videoSegmentFetchTask;
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
/**
|
|
439
|
+
* Legacy getter for asset segment keys task (maps to videoSegmentIdTask)
|
|
440
|
+
*/
|
|
441
|
+
get assetSegmentKeysTask() {
|
|
442
|
+
return this.videoSegmentIdTask;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/**
|
|
446
|
+
* Legacy getter for asset init segments task (maps to videoInitSegmentFetchTask)
|
|
447
|
+
*/
|
|
448
|
+
get assetInitSegmentsTask() {
|
|
449
|
+
return this.videoInitSegmentFetchTask;
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
/**
|
|
453
|
+
* Legacy getter for asset segment loader (maps to videoSegmentFetchTask)
|
|
454
|
+
*/
|
|
455
|
+
get assetSegmentLoader() {
|
|
456
|
+
return this.videoSegmentFetchTask;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
/**
|
|
460
|
+
* Legacy getter for video asset task (maps to videoBufferTask)
|
|
461
|
+
*/
|
|
462
|
+
get videoAssetTask() {
|
|
463
|
+
return this.videoBufferTask;
|
|
464
|
+
}
|
|
465
|
+
|
|
795
466
|
/**
|
|
796
467
|
* Clean up resources when component is disconnected
|
|
797
468
|
*/
|