@editframe/elements 0.17.6-beta.0 → 0.18.7-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/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.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/AssetMediaEngine.d.ts +45 -0
- package/dist/elements/EFMedia/AssetMediaEngine.js +135 -0
- package/dist/elements/EFMedia/BaseMediaEngine.d.ts +55 -0
- package/dist/elements/EFMedia/BaseMediaEngine.js +115 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.d.ts +43 -0
- package/dist/elements/EFMedia/BufferedSeekingInput.js +179 -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 +81 -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 +141 -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 +30 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.d.ts +0 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.d.ts +7 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSeekTask.js +32 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.js +28 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.d.ts +4 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.js +17 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.d.ts +3 -0
- package/dist/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.js +107 -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/PrecisionUtils.d.ts +28 -0
- package/dist/elements/EFMedia/shared/PrecisionUtils.js +29 -0
- package/dist/elements/EFMedia/shared/RenditionHelpers.d.ts +19 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.d.ts +18 -0
- package/dist/elements/EFMedia/tasks/makeMediaEngineTask.js +60 -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 +34 -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 +28 -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 +17 -0
- package/dist/elements/EFMedia.browsertest.d.ts +1 -0
- package/dist/elements/EFMedia.d.ts +63 -111
- package/dist/elements/EFMedia.js +117 -1113
- 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 +83 -13
- package/dist/elements/EFVideo.d.ts +54 -32
- package/dist/elements/EFVideo.js +100 -207
- 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.js +2 -1
- package/dist/gui/ContextMixin.js +3 -2
- 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/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 -3
- package/src/elements/EFAudio.browsertest.ts +189 -49
- 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.browsertest.ts +100 -0
- package/src/elements/EFMedia/AssetMediaEngine.ts +255 -0
- package/src/elements/EFMedia/BaseMediaEngine.test.ts +164 -0
- package/src/elements/EFMedia/BaseMediaEngine.ts +219 -0
- package/src/elements/EFMedia/BufferedSeekingInput.browsertest.ts +481 -0
- package/src/elements/EFMedia/BufferedSeekingInput.ts +324 -0
- package/src/elements/EFMedia/JitMediaEngine.browsertest.ts +165 -0
- package/src/elements/EFMedia/JitMediaEngine.ts +166 -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 +250 -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 +43 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.chunkboundary.regression.browsertest.ts +199 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSeekTask.ts +64 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentFetchTask.ts +45 -0
- package/src/elements/EFMedia/audioTasks/makeAudioSegmentIdTask.ts +24 -0
- package/src/elements/EFMedia/audioTasks/makeAudioTimeDomainAnalysisTask.ts +183 -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/PrecisionUtils.ts +46 -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 +68 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.browsertest.ts +57 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentFetchTask.ts +43 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.browsertest.ts +56 -0
- package/src/elements/EFMedia/videoTasks/makeVideoSegmentIdTask.ts +24 -0
- package/src/elements/EFMedia.browsertest.ts +706 -273
- package/src/elements/EFMedia.ts +136 -1769
- package/src/elements/EFTemporal.ts +3 -4
- package/src/elements/EFTimegroup.browsertest.ts +6 -3
- package/src/elements/EFTimegroup.ts +147 -21
- package/src/elements/EFVideo.browsertest.ts +980 -169
- package/src/elements/EFVideo.ts +113 -458
- package/src/elements/EFWaveform.ts +1 -1
- package/src/elements/MediaController.ts +2 -12
- package/src/elements/SampleBuffer.ts +95 -0
- package/src/gui/ContextMixin.ts +3 -6
- 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 +269 -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 +39 -30
- 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_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_1_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__9ed2d25c675aa6bb6ff5b3ae23887c71/metadata.json +22 -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_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_2_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__d5a3309a2bf756dd6e304807eb402f56/metadata.json +22 -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_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_audio_3_mp4_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4_bytes_0__773254bb671e3466fca8677139fb239e/metadata.json +22 -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_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/data.bin +0 -0
- package/test/__cache__/GET__api_v1_transcode_high_4_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a6fb05a22b18d850f7f2950bbcdbdeed/metadata.json +21 -0
- 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
- package/test/__cache__/GET__api_v1_transcode_high_5_m4s_url_http_3A_2F_2Fweb_3A3000_2Fhead_moov_480p_mp4__a50058c7c3602e90879fe3428ed891f4/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 +352 -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.d.ts +0 -96
- 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/{DecoderResetFrequency.test.d.ts → elements/EFMedia/AssetIdMediaEngine.test.d.ts} +0 -0
- /package/dist/{DecoderResetRecovery.test.d.ts → elements/EFMedia/BaseMediaEngine.test.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.browsertest.d.ts → elements/EFMedia/BufferedSeekingInput.browsertest.d.ts} +0 -0
- /package/dist/{JitTranscodingClient.test.d.ts → elements/EFMedia/shared/RenditionHelpers.browsertest.d.ts} +0 -0
- /package/dist/{ScrubTrackIntegration.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.browsertest.d.ts} +0 -0
- /package/dist/{SegmentSwitchLoading.test.d.ts → elements/EFMedia/tasks/makeMediaEngineTask.test.d.ts} +0 -0
package/src/elements/EFVideo.ts
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
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 { makeVideoBufferTask } from "./EFMedia/videoTasks/makeVideoBufferTask.ts";
|
|
10
|
+
import { makeVideoInitSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoInitSegmentFetchTask.ts";
|
|
11
|
+
import { makeVideoInputTask } from "./EFMedia/videoTasks/makeVideoInputTask.ts";
|
|
12
|
+
import { makeVideoSeekTask } from "./EFMedia/videoTasks/makeVideoSeekTask.ts";
|
|
13
|
+
import { makeVideoSegmentFetchTask } from "./EFMedia/videoTasks/makeVideoSegmentFetchTask.ts";
|
|
14
|
+
import { makeVideoSegmentIdTask } from "./EFMedia/videoTasks/makeVideoSegmentIdTask.ts";
|
|
10
15
|
import { EFMedia } from "./EFMedia.js";
|
|
11
|
-
import { printTaskStatus } from "./printTaskStatus.ts";
|
|
12
16
|
|
|
13
17
|
// EF_FRAMEGEN is a global instance created in EF_FRAMEGEN.ts
|
|
14
18
|
declare global {
|
|
@@ -25,6 +29,16 @@ interface LoadingState {
|
|
|
25
29
|
|
|
26
30
|
@customElement("ef-video")
|
|
27
31
|
export class EFVideo extends TWMixin(EFMedia) {
|
|
32
|
+
static get observedAttributes() {
|
|
33
|
+
const parentAttributes = EFMedia.observedAttributes || [];
|
|
34
|
+
return [
|
|
35
|
+
...parentAttributes,
|
|
36
|
+
"video-buffer-duration",
|
|
37
|
+
"max-video-buffer-fetches",
|
|
38
|
+
"enable-video-buffering",
|
|
39
|
+
];
|
|
40
|
+
}
|
|
41
|
+
|
|
28
42
|
static styles = [
|
|
29
43
|
/**
|
|
30
44
|
*
|
|
@@ -92,14 +106,33 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
92
106
|
canvasRef = createRef<HTMLCanvasElement>();
|
|
93
107
|
|
|
94
108
|
/**
|
|
95
|
-
*
|
|
109
|
+
* Duration in milliseconds for video buffering ahead of current time
|
|
110
|
+
* @domAttribute "video-buffer-duration"
|
|
111
|
+
*/
|
|
112
|
+
@property({ type: Number, attribute: "video-buffer-duration" })
|
|
113
|
+
videoBufferDurationMs = 60000; // 60 seconds
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Maximum number of concurrent video segment fetches for buffering
|
|
117
|
+
* @domAttribute "max-video-buffer-fetches"
|
|
96
118
|
*/
|
|
97
|
-
|
|
119
|
+
@property({ type: Number, attribute: "max-video-buffer-fetches" })
|
|
120
|
+
maxVideoBufferFetches = 2;
|
|
98
121
|
|
|
99
122
|
/**
|
|
100
|
-
*
|
|
123
|
+
* Enable/disable video buffering system
|
|
124
|
+
* @domAttribute "enable-video-buffering"
|
|
101
125
|
*/
|
|
102
|
-
|
|
126
|
+
@property({ type: Boolean, attribute: "enable-video-buffering" })
|
|
127
|
+
enableVideoBuffering = true;
|
|
128
|
+
|
|
129
|
+
// Video-specific tasks
|
|
130
|
+
videoSegmentIdTask = makeVideoSegmentIdTask(this);
|
|
131
|
+
videoInitSegmentFetchTask = makeVideoInitSegmentFetchTask(this);
|
|
132
|
+
videoSegmentFetchTask = makeVideoSegmentFetchTask(this);
|
|
133
|
+
videoInputTask = makeVideoInputTask(this);
|
|
134
|
+
videoSeekTask = makeVideoSeekTask(this);
|
|
135
|
+
videoBufferTask = makeVideoBufferTask(this);
|
|
103
136
|
|
|
104
137
|
/**
|
|
105
138
|
* Delayed loading state manager for user feedback
|
|
@@ -153,35 +186,14 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
153
186
|
return this.canvasRef.value;
|
|
154
187
|
}
|
|
155
188
|
|
|
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
189
|
frameTask = new Task(this, {
|
|
164
190
|
args: () => [this.desiredSeekTimeMs] as const,
|
|
165
191
|
onError: (error) => {
|
|
166
192
|
console.error("frameTask error", error);
|
|
167
193
|
},
|
|
194
|
+
onComplete: () => {},
|
|
168
195
|
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
|
-
}
|
|
196
|
+
await this.videoSeekTask.taskComplete;
|
|
185
197
|
await this.paintTask.taskComplete;
|
|
186
198
|
if (signal.aborted) {
|
|
187
199
|
return;
|
|
@@ -189,70 +201,10 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
189
201
|
},
|
|
190
202
|
});
|
|
191
203
|
|
|
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
204
|
protected updated(
|
|
210
205
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
211
206
|
): void {
|
|
212
207
|
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
208
|
}
|
|
257
209
|
|
|
258
210
|
/**
|
|
@@ -288,107 +240,24 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
288
240
|
};
|
|
289
241
|
}
|
|
290
242
|
|
|
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
243
|
paintTask = new Task(this, {
|
|
384
244
|
args: () => [this.desiredSeekTimeMs] as const,
|
|
385
245
|
onError: (error) => {
|
|
386
246
|
console.error("paintTask error", error);
|
|
387
247
|
},
|
|
248
|
+
onComplete: () => {},
|
|
388
249
|
task: async ([_seekToMs], { signal }) => {
|
|
250
|
+
await this.videoSeekTask.taskComplete;
|
|
389
251
|
// Check if we're in production rendering mode vs preview mode
|
|
390
252
|
const isProductionRendering = this.isInProductionRenderingMode();
|
|
391
253
|
|
|
254
|
+
const sample = this.videoSeekTask.value;
|
|
255
|
+
if (sample) {
|
|
256
|
+
const videoFrame = sample.toVideoFrame();
|
|
257
|
+
this.displayFrame(videoFrame, _seekToMs);
|
|
258
|
+
videoFrame.close();
|
|
259
|
+
}
|
|
260
|
+
|
|
392
261
|
// EF_FRAMEGEN-aware rendering mode detection
|
|
393
262
|
if (!isProductionRendering) {
|
|
394
263
|
// Preview mode: skip rendering during initialization to prevent artifacts
|
|
@@ -417,277 +286,9 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
417
286
|
if (signal.aborted) {
|
|
418
287
|
return;
|
|
419
288
|
}
|
|
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
289
|
},
|
|
624
290
|
});
|
|
625
291
|
|
|
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
292
|
/**
|
|
692
293
|
* Display a video frame on the canvas
|
|
693
294
|
*/
|
|
@@ -729,7 +330,7 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
729
330
|
);
|
|
730
331
|
}
|
|
731
332
|
|
|
732
|
-
log("trace: drawing frame to canvas");
|
|
333
|
+
log("trace: drawing frame to canvas", frame.timestamp / 1000);
|
|
733
334
|
ctx.drawImage(
|
|
734
335
|
frame,
|
|
735
336
|
0,
|
|
@@ -785,11 +386,68 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
785
386
|
return currentTime >= renderStartTime;
|
|
786
387
|
}
|
|
787
388
|
|
|
389
|
+
// Getter properties for backward compatibility with tests
|
|
390
|
+
/**
|
|
391
|
+
* Effective mode - always returns "asset" for EFVideo
|
|
392
|
+
*/
|
|
393
|
+
get effectiveMode(): string {
|
|
394
|
+
return "asset";
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Legacy getter for asset index loader (maps to mediaEngine task)
|
|
399
|
+
*/
|
|
400
|
+
get assetIndexLoader() {
|
|
401
|
+
return this.mediaEngineTask;
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Legacy getter for fragment index task (maps to videoSegmentIdTask)
|
|
406
|
+
*/
|
|
407
|
+
get fragmentIndexTask() {
|
|
408
|
+
return this.videoSegmentIdTask;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Legacy getter for seek task (maps to videoSeekTask)
|
|
413
|
+
*/
|
|
414
|
+
get seekTask() {
|
|
415
|
+
return this.videoSeekTask;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Legacy getter for media segments task (maps to videoSegmentFetchTask)
|
|
420
|
+
*/
|
|
421
|
+
get mediaSegmentsTask() {
|
|
422
|
+
return this.videoSegmentFetchTask;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Legacy getter for asset segment keys task (maps to videoSegmentIdTask)
|
|
427
|
+
*/
|
|
428
|
+
get assetSegmentKeysTask() {
|
|
429
|
+
return this.videoSegmentIdTask;
|
|
430
|
+
}
|
|
431
|
+
|
|
788
432
|
/**
|
|
789
|
-
*
|
|
433
|
+
* Legacy getter for asset init segments task (maps to videoInitSegmentFetchTask)
|
|
790
434
|
*/
|
|
791
|
-
|
|
792
|
-
return this.
|
|
435
|
+
get assetInitSegmentsTask() {
|
|
436
|
+
return this.videoInitSegmentFetchTask;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
/**
|
|
440
|
+
* Legacy getter for asset segment loader (maps to videoSegmentFetchTask)
|
|
441
|
+
*/
|
|
442
|
+
get assetSegmentLoader() {
|
|
443
|
+
return this.videoSegmentFetchTask;
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* Legacy getter for video asset task (maps to videoBufferTask)
|
|
448
|
+
*/
|
|
449
|
+
get videoAssetTask() {
|
|
450
|
+
return this.videoBufferTask;
|
|
793
451
|
}
|
|
794
452
|
|
|
795
453
|
/**
|
|
@@ -798,9 +456,6 @@ export class EFVideo extends TWMixin(EFMedia) {
|
|
|
798
456
|
disconnectedCallback(): void {
|
|
799
457
|
super.disconnectedCallback();
|
|
800
458
|
|
|
801
|
-
// Clean up scrub track manager
|
|
802
|
-
this.scrubTrackManager?.cleanup();
|
|
803
|
-
|
|
804
459
|
// Clean up delayed loading state
|
|
805
460
|
this.delayedLoadingState.clearAllLoading();
|
|
806
461
|
}
|