@editframe/elements 0.16.8-beta.0 → 0.17.6-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/README.md +30 -0
- package/dist/DecoderResetFrequency.test.d.ts +1 -0
- package/dist/DecoderResetRecovery.test.d.ts +1 -0
- package/dist/DelayedLoadingState.d.ts +48 -0
- package/dist/DelayedLoadingState.integration.test.d.ts +1 -0
- package/dist/DelayedLoadingState.js +113 -0
- package/dist/DelayedLoadingState.test.d.ts +1 -0
- package/dist/EF_FRAMEGEN.d.ts +10 -1
- package/dist/EF_FRAMEGEN.js +199 -179
- package/dist/EF_INTERACTIVE.js +2 -6
- package/dist/EF_RENDERING.js +1 -3
- package/dist/JitTranscodingClient.browsertest.d.ts +1 -0
- package/dist/JitTranscodingClient.d.ts +167 -0
- package/dist/JitTranscodingClient.js +373 -0
- package/dist/JitTranscodingClient.test.d.ts +1 -0
- package/dist/LoadingDebounce.test.d.ts +1 -0
- package/dist/LoadingIndicator.browsertest.d.ts +0 -0
- package/dist/ManualScrubTest.test.d.ts +1 -0
- package/dist/ScrubResolvedFlashing.test.d.ts +1 -0
- package/dist/ScrubTrackIntegration.test.d.ts +1 -0
- package/dist/ScrubTrackManager.d.ts +96 -0
- package/dist/ScrubTrackManager.js +216 -0
- package/dist/ScrubTrackManager.test.d.ts +1 -0
- package/dist/SegmentSwitchLoading.test.d.ts +1 -0
- package/dist/VideoSeekFlashing.browsertest.d.ts +0 -0
- package/dist/VideoStuckDiagnostic.test.d.ts +1 -0
- package/dist/elements/CrossUpdateController.js +13 -15
- package/dist/elements/EFAudio.browsertest.d.ts +0 -0
- package/dist/elements/EFAudio.d.ts +1 -1
- package/dist/elements/EFAudio.js +30 -43
- package/dist/elements/EFCaptions.js +337 -373
- package/dist/elements/EFImage.js +64 -90
- package/dist/elements/EFMedia.d.ts +98 -33
- package/dist/elements/EFMedia.js +1169 -678
- package/dist/elements/EFSourceMixin.js +31 -48
- package/dist/elements/EFTemporal.d.ts +1 -0
- package/dist/elements/EFTemporal.js +266 -360
- package/dist/elements/EFTimegroup.d.ts +3 -1
- package/dist/elements/EFTimegroup.js +262 -323
- package/dist/elements/EFVideo.browsertest.d.ts +0 -0
- package/dist/elements/EFVideo.d.ts +90 -2
- package/dist/elements/EFVideo.js +408 -111
- package/dist/elements/EFWaveform.js +375 -411
- package/dist/elements/FetchMixin.js +14 -24
- package/dist/elements/MediaController.d.ts +30 -0
- package/dist/elements/TargetController.js +130 -156
- package/dist/elements/TimegroupController.js +17 -19
- package/dist/elements/durationConverter.js +15 -4
- package/dist/elements/parseTimeToMs.js +4 -10
- package/dist/elements/printTaskStatus.d.ts +2 -0
- package/dist/elements/printTaskStatus.js +11 -0
- package/dist/elements/updateAnimations.js +39 -59
- package/dist/getRenderInfo.js +58 -67
- package/dist/gui/ContextMixin.js +203 -288
- package/dist/gui/EFConfiguration.js +27 -43
- package/dist/gui/EFFilmstrip.js +440 -620
- package/dist/gui/EFFitScale.js +112 -135
- package/dist/gui/EFFocusOverlay.js +45 -61
- package/dist/gui/EFPreview.js +30 -49
- package/dist/gui/EFScrubber.js +78 -99
- package/dist/gui/EFTimeDisplay.js +49 -70
- package/dist/gui/EFToggleLoop.js +17 -34
- package/dist/gui/EFTogglePlay.js +37 -58
- package/dist/gui/EFWorkbench.js +66 -88
- package/dist/gui/TWMixin.js +2 -48
- package/dist/gui/TWMixin2.js +31 -0
- package/dist/gui/efContext.js +2 -6
- package/dist/gui/fetchContext.js +1 -3
- package/dist/gui/focusContext.js +1 -3
- package/dist/gui/focusedElementContext.js +2 -6
- package/dist/gui/playingContext.js +1 -4
- package/dist/index.js +5 -30
- package/dist/msToTimeCode.js +11 -13
- package/dist/style.css +2 -1
- package/package.json +3 -3
- package/src/elements/EFAudio.browsertest.ts +569 -0
- package/src/elements/EFAudio.ts +4 -6
- package/src/elements/EFCaptions.browsertest.ts +0 -1
- package/src/elements/EFImage.browsertest.ts +0 -1
- package/src/elements/EFMedia.browsertest.ts +147 -115
- package/src/elements/EFMedia.ts +1339 -307
- package/src/elements/EFTemporal.browsertest.ts +0 -1
- package/src/elements/EFTemporal.ts +11 -0
- package/src/elements/EFTimegroup.ts +73 -10
- package/src/elements/EFVideo.browsertest.ts +680 -0
- package/src/elements/EFVideo.ts +729 -50
- package/src/elements/EFWaveform.ts +4 -4
- package/src/elements/MediaController.ts +108 -0
- 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/src/elements/printTaskStatus.ts +16 -0
- package/src/elements/updateAnimations.ts +6 -0
- package/src/gui/TWMixin.ts +10 -3
- package/test/EFVideo.frame-tasks.browsertest.ts +524 -0
- package/test/EFVideo.framegen.browsertest.ts +118 -0
- package/test/createJitTestClips.ts +293 -0
- package/test/useAssetMSW.ts +49 -0
- package/test/useMSW.ts +31 -0
- package/types.json +1 -1
- package/dist/gui/TWMixin.css.js +0 -4
- /package/dist/elements/{TargetController.test.d.ts → TargetController.browsertest.d.ts} +0 -0
- /package/src/elements/{TargetController.test.ts → TargetController.browsertest.ts} +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
import debug from "debug";
|
|
2
|
+
import { VideoAsset } from "@editframe/assets/EncodedAsset.js";
|
|
3
|
+
const log = debug("ef:elements:ScrubTrackManager");
|
|
4
|
+
var ScrubTrackManager = class {
|
|
5
|
+
constructor(videoUrl, jitClient, config = {}) {
|
|
6
|
+
this.scrubCache = /* @__PURE__ */ new Map();
|
|
7
|
+
this.metadata = null;
|
|
8
|
+
this.cacheHits = 0;
|
|
9
|
+
this.cacheMisses = 0;
|
|
10
|
+
this.isInitialized = false;
|
|
11
|
+
this.preloadingSegments = /* @__PURE__ */ new Set();
|
|
12
|
+
this.videoUrl = videoUrl;
|
|
13
|
+
this.jitClient = jitClient;
|
|
14
|
+
this.config = {
|
|
15
|
+
maxScrubCacheSegments: 10,
|
|
16
|
+
prefetchCount: 3,
|
|
17
|
+
fastSeekThresholdMs: 1e4,
|
|
18
|
+
onLoadingStateChange: () => {},
|
|
19
|
+
...config
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Initialize scrub track manager and start preloading
|
|
24
|
+
*/
|
|
25
|
+
async initialize() {
|
|
26
|
+
if (this.isInitialized) return;
|
|
27
|
+
try {
|
|
28
|
+
this.metadata = await this.jitClient.getMetadata(this.videoUrl);
|
|
29
|
+
await this.preloadInitialSegments();
|
|
30
|
+
this.isInitialized = true;
|
|
31
|
+
log("ScrubTrackManager initialized for", this.videoUrl);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
log("Failed to initialize ScrubTrackManager:", error);
|
|
34
|
+
throw error;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Determine if scrub track should be used instead of normal video
|
|
39
|
+
*/
|
|
40
|
+
shouldUseScrubTrack(seekTimeMs) {
|
|
41
|
+
const currentQuality = this.jitClient.getCurrentQuality();
|
|
42
|
+
return !this.jitClient.hasSegmentInCache(this.videoUrl, currentQuality, seekTimeMs);
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Detect if this is a fast seeking operation
|
|
46
|
+
*/
|
|
47
|
+
isFastSeeking(currentTimeMs, seekTimeMs) {
|
|
48
|
+
const timeDiff = Math.abs(seekTimeMs - currentTimeMs);
|
|
49
|
+
return timeDiff > this.config.fastSeekThresholdMs;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Align seek time to 30s scrub segment boundary
|
|
53
|
+
*/
|
|
54
|
+
alignToScrubBoundary(timeMs) {
|
|
55
|
+
const segmentDurationMs = 3e4;
|
|
56
|
+
return Math.floor(timeMs / segmentDurationMs) * segmentDurationMs;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Get scrub frame for the given seek time
|
|
60
|
+
*/
|
|
61
|
+
async getScrubFrame(seekTimeMs) {
|
|
62
|
+
const alignedTimeMs = this.alignToScrubBoundary(seekTimeMs);
|
|
63
|
+
try {
|
|
64
|
+
let videoAsset = this.scrubCache.get(alignedTimeMs);
|
|
65
|
+
const wasCached = !!videoAsset;
|
|
66
|
+
if (!videoAsset) {
|
|
67
|
+
this.config.onLoadingStateChange?.(true, "Loading scrub segment...");
|
|
68
|
+
const segmentBuffer = await this.jitClient.loadSegment(this.videoUrl, "scrub", alignedTimeMs);
|
|
69
|
+
this.config.onLoadingStateChange?.(true, "Processing video data...");
|
|
70
|
+
const segmentBlob = new Blob([segmentBuffer], { type: "video/mp4" });
|
|
71
|
+
const segmentFile = new File([segmentBlob], `scrub-${alignedTimeMs}.mp4`, { type: "video/mp4" });
|
|
72
|
+
const segmentId = `scrub-${this.videoUrl}-${alignedTimeMs}`;
|
|
73
|
+
videoAsset = await VideoAsset.createFromCompleteMP4(segmentId, segmentFile);
|
|
74
|
+
this.cacheSegment(alignedTimeMs, videoAsset);
|
|
75
|
+
}
|
|
76
|
+
const relativeSeekTimeMs = seekTimeMs - alignedTimeMs;
|
|
77
|
+
const relativeSeekTimeSeconds = relativeSeekTimeMs / 1e3;
|
|
78
|
+
log(`Seeking to ${relativeSeekTimeSeconds}s within scrub segment starting at ${alignedTimeMs}ms`);
|
|
79
|
+
const frame = await videoAsset.seekToTime(relativeSeekTimeSeconds);
|
|
80
|
+
if (!wasCached) this.config.onLoadingStateChange?.(false, "");
|
|
81
|
+
if (!frame) {
|
|
82
|
+
log(`Warning: No frame returned for seek to ${relativeSeekTimeSeconds}s in scrub segment`);
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
85
|
+
this.preloadNearbySegments(alignedTimeMs);
|
|
86
|
+
return frame;
|
|
87
|
+
} catch (error) {
|
|
88
|
+
this.config.onLoadingStateChange?.(false, "");
|
|
89
|
+
log("Failed to get scrub frame:", error);
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Record cache hit for statistics
|
|
95
|
+
*/
|
|
96
|
+
recordCacheHit() {
|
|
97
|
+
this.cacheHits++;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Record cache miss for statistics
|
|
101
|
+
*/
|
|
102
|
+
recordCacheMiss() {
|
|
103
|
+
this.cacheMisses++;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Get cache performance statistics
|
|
107
|
+
*/
|
|
108
|
+
getCacheStats() {
|
|
109
|
+
const total = this.cacheHits + this.cacheMisses;
|
|
110
|
+
return {
|
|
111
|
+
hits: this.cacheHits,
|
|
112
|
+
misses: this.cacheMisses,
|
|
113
|
+
hitRate: total > 0 ? this.cacheHits / total : 0
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* Check if scrub segment is cached for the given time
|
|
118
|
+
*/
|
|
119
|
+
isScrubSegmentCached(seekTimeMs) {
|
|
120
|
+
const alignedTimeMs = this.alignToScrubBoundary(seekTimeMs);
|
|
121
|
+
return this.scrubCache.has(alignedTimeMs);
|
|
122
|
+
}
|
|
123
|
+
/**
|
|
124
|
+
* Get current scrub cache size
|
|
125
|
+
*/
|
|
126
|
+
getScrubCacheSize() {
|
|
127
|
+
return this.scrubCache.size;
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Get total number of scrub segments for video
|
|
131
|
+
*/
|
|
132
|
+
getTotalScrubSegments() {
|
|
133
|
+
if (!this.metadata?.durationMs) return 0;
|
|
134
|
+
return Math.ceil(this.metadata.durationMs / 3e4);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Update metadata (e.g., when video duration changes)
|
|
138
|
+
*/
|
|
139
|
+
async updateMetadata() {
|
|
140
|
+
this.metadata = await this.jitClient.getMetadata(this.videoUrl);
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Preload initial scrub segments
|
|
144
|
+
*/
|
|
145
|
+
async preloadInitialSegments() {
|
|
146
|
+
const segmentsToPreload = Math.min(this.config.prefetchCount, this.getTotalScrubSegments());
|
|
147
|
+
const preloadPromises = [];
|
|
148
|
+
for (let i = 0; i < segmentsToPreload; i++) {
|
|
149
|
+
const segmentStartMs = i * 3e4;
|
|
150
|
+
preloadPromises.push(this.preloadSegment(segmentStartMs));
|
|
151
|
+
}
|
|
152
|
+
await Promise.allSettled(preloadPromises);
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Preload nearby segments around a given time
|
|
156
|
+
*/
|
|
157
|
+
preloadNearbySegments(centerTimeMs) {
|
|
158
|
+
const segmentDurationMs = 3e4;
|
|
159
|
+
const preloadCount = this.config.prefetchCount;
|
|
160
|
+
for (let i = 1; i <= preloadCount; i++) {
|
|
161
|
+
const nextSegmentTime = centerTimeMs + i * segmentDurationMs;
|
|
162
|
+
const prevSegmentTime = centerTimeMs - i * segmentDurationMs;
|
|
163
|
+
if (nextSegmentTime < (this.metadata?.durationMs || 0)) this.preloadSegment(nextSegmentTime);
|
|
164
|
+
if (prevSegmentTime >= 0) this.preloadSegment(prevSegmentTime);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Preload a single scrub segment (background operation)
|
|
169
|
+
*/
|
|
170
|
+
async preloadSegment(startTimeMs) {
|
|
171
|
+
if (this.scrubCache.has(startTimeMs) || this.preloadingSegments.has(startTimeMs)) return;
|
|
172
|
+
this.preloadingSegments.add(startTimeMs);
|
|
173
|
+
try {
|
|
174
|
+
const segmentBuffer = await this.jitClient.loadSegment(this.videoUrl, "scrub", startTimeMs);
|
|
175
|
+
const segmentBlob = new Blob([segmentBuffer], { type: "video/mp4" });
|
|
176
|
+
const segmentFile = new File([segmentBlob], `scrub-${startTimeMs}.mp4`, { type: "video/mp4" });
|
|
177
|
+
const segmentId = `scrub-${this.videoUrl}-${startTimeMs}`;
|
|
178
|
+
const videoAsset = await VideoAsset.createFromCompleteMP4(segmentId, segmentFile);
|
|
179
|
+
this.cacheSegment(startTimeMs, videoAsset);
|
|
180
|
+
log(`Preloaded scrub segment at ${startTimeMs}ms`);
|
|
181
|
+
} catch (error) {
|
|
182
|
+
log(`Failed to preload scrub segment at ${startTimeMs}ms:`, error);
|
|
183
|
+
} finally {
|
|
184
|
+
this.preloadingSegments.delete(startTimeMs);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Cache a scrub segment with LRU eviction
|
|
189
|
+
*/
|
|
190
|
+
cacheSegment(startTimeMs, videoAsset) {
|
|
191
|
+
if (this.scrubCache.size >= this.config.maxScrubCacheSegments) {
|
|
192
|
+
const oldestKey = this.scrubCache.keys().next().value;
|
|
193
|
+
if (oldestKey !== void 0) this.scrubCache.delete(oldestKey);
|
|
194
|
+
}
|
|
195
|
+
this.scrubCache.set(startTimeMs, videoAsset);
|
|
196
|
+
}
|
|
197
|
+
/**
|
|
198
|
+
* Clear cache and reset state
|
|
199
|
+
*/
|
|
200
|
+
reset() {
|
|
201
|
+
this.scrubCache.clear();
|
|
202
|
+
this.cacheHits = 0;
|
|
203
|
+
this.cacheMisses = 0;
|
|
204
|
+
this.preloadingSegments.clear();
|
|
205
|
+
this.isInitialized = false;
|
|
206
|
+
this.metadata = null;
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Clean up resources and cancel any pending operations
|
|
210
|
+
*/
|
|
211
|
+
cleanup() {
|
|
212
|
+
this.config.onLoadingStateChange?.(false, "");
|
|
213
|
+
this.reset();
|
|
214
|
+
}
|
|
215
|
+
};
|
|
216
|
+
export { ScrubTrackManager };
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -1,16 +1,14 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
}
|
|
14
|
-
export {
|
|
15
|
-
CrossUpdateController
|
|
1
|
+
var CrossUpdateController = class {
|
|
2
|
+
constructor(host, target) {
|
|
3
|
+
this.host = host;
|
|
4
|
+
this.target = target;
|
|
5
|
+
this.host.addController(this);
|
|
6
|
+
}
|
|
7
|
+
hostUpdate() {
|
|
8
|
+
this.target.requestUpdate();
|
|
9
|
+
}
|
|
10
|
+
remove() {
|
|
11
|
+
this.host.removeController(this);
|
|
12
|
+
}
|
|
16
13
|
};
|
|
14
|
+
export { CrossUpdateController };
|
|
File without changes
|
|
@@ -4,7 +4,7 @@ export declare class EFAudio extends EFMedia {
|
|
|
4
4
|
audioElementRef: import('lit-html/directives/ref.js').Ref<HTMLAudioElement>;
|
|
5
5
|
render(): import('lit-html').TemplateResult<1>;
|
|
6
6
|
get audioElement(): HTMLAudioElement | undefined;
|
|
7
|
-
frameTask: Task<readonly [import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus
|
|
7
|
+
frameTask: Task<readonly [import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus], void>;
|
|
8
8
|
}
|
|
9
9
|
declare global {
|
|
10
10
|
interface HTMLElementTagNameMap {
|
package/dist/elements/EFAudio.js
CHANGED
|
@@ -1,48 +1,35 @@
|
|
|
1
|
+
import { EFMedia } from "./EFMedia.js";
|
|
1
2
|
import { Task } from "@lit/task";
|
|
2
3
|
import { html } from "lit";
|
|
3
4
|
import { customElement } from "lit/decorators.js";
|
|
5
|
+
import _decorate from "@oxc-project/runtime/helpers/decorate";
|
|
4
6
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
await this.videoAssetTask.taskComplete;
|
|
32
|
-
this.rootTimegroup?.requestUpdate();
|
|
33
|
-
}
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
render() {
|
|
37
|
-
return html`<audio ${ref(this.audioElementRef)}></audio>`;
|
|
38
|
-
}
|
|
39
|
-
get audioElement() {
|
|
40
|
-
return this.audioElementRef.value;
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
EFAudio = __decorateClass([
|
|
44
|
-
customElement("ef-audio")
|
|
45
|
-
], EFAudio);
|
|
46
|
-
export {
|
|
47
|
-
EFAudio
|
|
7
|
+
let EFAudio = class EFAudio$1 extends EFMedia {
|
|
8
|
+
constructor(..._args) {
|
|
9
|
+
super(..._args);
|
|
10
|
+
this.audioElementRef = createRef();
|
|
11
|
+
this.frameTask = new Task(this, {
|
|
12
|
+
args: () => [
|
|
13
|
+
this.fragmentIndexTask.status,
|
|
14
|
+
this.seekTask.status,
|
|
15
|
+
this.mediaSegmentsTask.status,
|
|
16
|
+
this.videoAssetTask.status
|
|
17
|
+
],
|
|
18
|
+
task: async () => {
|
|
19
|
+
await this.fragmentIndexTask.taskComplete;
|
|
20
|
+
await this.seekTask.taskComplete;
|
|
21
|
+
await this.mediaSegmentsTask.taskComplete;
|
|
22
|
+
await this.videoAssetTask.taskComplete;
|
|
23
|
+
this.rootTimegroup?.requestUpdate();
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
render() {
|
|
28
|
+
return html`<audio ${ref(this.audioElementRef)}></audio>`;
|
|
29
|
+
}
|
|
30
|
+
get audioElement() {
|
|
31
|
+
return this.audioElementRef.value;
|
|
32
|
+
}
|
|
48
33
|
};
|
|
34
|
+
EFAudio = _decorate([customElement("ef-audio")], EFAudio);
|
|
35
|
+
export { EFAudio };
|