@editframe/elements 0.6.0-beta.9 → 0.7.0-beta.1
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/lib/av/EncodedAsset.cjs +570 -0
- package/dist/lib/av/EncodedAsset.js +553 -0
- package/dist/lib/av/MP4File.cjs +182 -0
- package/dist/lib/av/MP4File.js +165 -0
- package/dist/lib/av/msToTimeCode.cjs +15 -0
- package/dist/lib/av/msToTimeCode.js +15 -0
- package/dist/lib/util/awaitMicrotask.cjs +4 -0
- package/dist/lib/util/awaitMicrotask.js +4 -0
- package/dist/lib/util/memoize.cjs +14 -0
- package/dist/lib/util/memoize.js +14 -0
- package/dist/packages/elements/src/EF_FRAMEGEN.cjs +200 -0
- package/dist/packages/elements/src/EF_FRAMEGEN.d.ts +45 -0
- package/dist/packages/elements/src/EF_FRAMEGEN.js +200 -0
- package/dist/packages/elements/src/EF_INTERACTIVE.cjs +4 -0
- package/dist/packages/elements/src/EF_INTERACTIVE.d.ts +1 -0
- package/dist/packages/elements/src/EF_INTERACTIVE.js +4 -0
- package/dist/packages/elements/src/elements/CrossUpdateController.cjs +16 -0
- package/dist/packages/elements/src/elements/CrossUpdateController.d.ts +9 -0
- package/dist/packages/elements/src/elements/CrossUpdateController.js +16 -0
- package/dist/packages/elements/src/elements/EFAudio.cjs +53 -0
- package/dist/packages/elements/src/elements/EFAudio.d.ts +10 -0
- package/dist/packages/elements/src/elements/EFAudio.js +54 -0
- package/dist/packages/elements/src/elements/EFCaptions.cjs +164 -0
- package/dist/packages/elements/src/elements/EFCaptions.d.ts +38 -0
- package/dist/packages/elements/src/elements/EFCaptions.js +166 -0
- package/dist/packages/elements/src/elements/EFImage.cjs +79 -0
- package/dist/packages/elements/src/elements/EFImage.d.ts +14 -0
- package/dist/packages/elements/src/elements/EFImage.js +80 -0
- package/dist/packages/elements/src/elements/EFMedia.cjs +334 -0
- package/dist/packages/elements/src/elements/EFMedia.d.ts +61 -0
- package/dist/packages/elements/src/elements/EFMedia.js +334 -0
- package/dist/packages/elements/src/elements/EFSourceMixin.cjs +55 -0
- package/dist/packages/elements/src/elements/EFSourceMixin.d.ts +12 -0
- package/dist/packages/elements/src/elements/EFSourceMixin.js +55 -0
- package/dist/packages/elements/src/elements/EFTemporal.cjs +198 -0
- package/dist/packages/elements/src/elements/EFTemporal.d.ts +36 -0
- package/dist/packages/elements/src/elements/EFTemporal.js +198 -0
- package/dist/packages/elements/src/elements/EFTimegroup.browsertest.d.ts +12 -0
- package/dist/packages/elements/src/elements/EFTimegroup.cjs +350 -0
- package/dist/packages/elements/src/elements/EFTimegroup.d.ts +39 -0
- package/dist/packages/elements/src/elements/EFTimegroup.js +351 -0
- package/dist/packages/elements/src/elements/EFTimeline.cjs +15 -0
- package/dist/packages/elements/src/elements/EFTimeline.d.ts +3 -0
- package/dist/packages/elements/src/elements/EFTimeline.js +15 -0
- package/dist/packages/elements/src/elements/EFVideo.cjs +109 -0
- package/dist/packages/elements/src/elements/EFVideo.d.ts +14 -0
- package/dist/packages/elements/src/elements/EFVideo.js +110 -0
- package/dist/packages/elements/src/elements/EFWaveform.cjs +242 -0
- package/dist/packages/elements/src/elements/EFWaveform.d.ts +30 -0
- package/dist/packages/elements/src/elements/EFWaveform.js +226 -0
- package/dist/packages/elements/src/elements/FetchMixin.cjs +28 -0
- package/dist/packages/elements/src/elements/FetchMixin.d.ts +8 -0
- package/dist/packages/elements/src/elements/FetchMixin.js +28 -0
- package/dist/packages/elements/src/elements/TimegroupController.cjs +20 -0
- package/dist/packages/elements/src/elements/TimegroupController.d.ts +14 -0
- package/dist/packages/elements/src/elements/TimegroupController.js +20 -0
- package/dist/packages/elements/src/elements/durationConverter.cjs +8 -0
- package/dist/packages/elements/src/elements/durationConverter.d.ts +4 -0
- package/dist/packages/elements/src/elements/durationConverter.js +8 -0
- package/dist/packages/elements/src/elements/parseTimeToMs.cjs +12 -0
- package/dist/packages/elements/src/elements/parseTimeToMs.d.ts +1 -0
- package/dist/packages/elements/src/elements/parseTimeToMs.js +12 -0
- package/dist/packages/elements/src/elements/util.cjs +11 -0
- package/dist/packages/elements/src/elements/util.d.ts +4 -0
- package/dist/packages/elements/src/elements/util.js +11 -0
- package/dist/packages/elements/src/gui/EFFilmstrip.cjs +820 -0
- package/dist/packages/elements/src/gui/EFFilmstrip.d.ts +147 -0
- package/dist/packages/elements/src/gui/EFFilmstrip.js +828 -0
- package/dist/packages/elements/src/gui/EFWorkbench.cjs +213 -0
- package/dist/packages/elements/src/gui/EFWorkbench.d.ts +45 -0
- package/dist/packages/elements/src/gui/EFWorkbench.js +214 -0
- package/dist/packages/elements/src/gui/TWMixin.cjs +28 -0
- package/dist/packages/elements/src/gui/TWMixin.css.cjs +3 -0
- package/dist/packages/elements/src/gui/TWMixin.css.js +4 -0
- package/dist/packages/elements/src/gui/TWMixin.d.ts +3 -0
- package/dist/packages/elements/src/gui/TWMixin.js +28 -0
- package/dist/packages/elements/src/index.cjs +51 -0
- package/dist/packages/elements/src/index.d.ts +10 -0
- package/dist/packages/elements/src/index.js +24 -0
- package/dist/style.css +787 -0
- package/package.json +2 -2
- package/src/elements/CrossUpdateController.ts +22 -0
- package/src/elements/EFAudio.ts +40 -0
- package/src/elements/EFCaptions.ts +188 -0
- package/src/elements/EFImage.ts +68 -0
- package/src/elements/EFMedia.ts +384 -0
- package/src/elements/EFSourceMixin.ts +57 -0
- package/src/elements/EFTemporal.ts +231 -0
- package/src/elements/EFTimegroup.browsertest.ts +333 -0
- package/src/elements/EFTimegroup.ts +389 -0
- package/src/elements/EFTimeline.ts +13 -0
- package/src/elements/EFVideo.ts +103 -0
- package/src/elements/EFWaveform.ts +417 -0
- package/src/elements/FetchMixin.ts +19 -0
- package/src/elements/TimegroupController.ts +25 -0
- package/src/elements/durationConverter.ts +6 -0
- package/src/elements/parseTimeToMs.ts +9 -0
- package/src/elements/util.ts +24 -0
- package/src/gui/EFFilmstrip.ts +880 -0
- package/src/gui/EFWorkbench.ts +231 -0
- package/src/gui/TWMixin.css +3 -0
- package/src/gui/TWMixin.ts +30 -0
|
@@ -0,0 +1,880 @@
|
|
|
1
|
+
import { EFTimegroup } from "../elements/EFTimegroup";
|
|
2
|
+
import {
|
|
3
|
+
LitElement,
|
|
4
|
+
html,
|
|
5
|
+
css,
|
|
6
|
+
nothing,
|
|
7
|
+
type TemplateResult,
|
|
8
|
+
type ReactiveController,
|
|
9
|
+
type PropertyValueMap,
|
|
10
|
+
} from "lit";
|
|
11
|
+
import {
|
|
12
|
+
customElement,
|
|
13
|
+
property,
|
|
14
|
+
eventOptions,
|
|
15
|
+
state,
|
|
16
|
+
} from "lit/decorators.js";
|
|
17
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
18
|
+
import { ref, createRef } from "lit/directives/ref.js";
|
|
19
|
+
import { EFImage } from "../elements/EFImage";
|
|
20
|
+
import { EFAudio } from "../elements/EFAudio";
|
|
21
|
+
import { EFVideo } from "../elements/EFVideo";
|
|
22
|
+
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions";
|
|
23
|
+
import { EFWaveform } from "../elements/EFWaveform";
|
|
24
|
+
import type { TemporalMixinInterface } from "../elements/EFTemporal";
|
|
25
|
+
import { TimegroupController } from "../elements/TimegroupController";
|
|
26
|
+
import { consume } from "@lit/context";
|
|
27
|
+
import { type FocusContext, focusContext, focusedElement } from "./EFWorkbench";
|
|
28
|
+
import { TWMixin } from "./TWMixin";
|
|
29
|
+
import { msToTimeCode } from "@/av/msToTimeCode";
|
|
30
|
+
|
|
31
|
+
class ElementFilmstripController implements ReactiveController {
|
|
32
|
+
constructor(
|
|
33
|
+
private host: LitElement,
|
|
34
|
+
private filmstrip: FilmstripItem,
|
|
35
|
+
) {
|
|
36
|
+
this.host.addController(this);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
remove() {
|
|
40
|
+
this.host.removeController(this);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
hostDisconnected() {
|
|
44
|
+
this.host.removeController(this);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
hostUpdated(): void {
|
|
48
|
+
this.filmstrip.requestUpdate();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const CommonEffectKeys = new Set([
|
|
53
|
+
"offset",
|
|
54
|
+
"easing",
|
|
55
|
+
"composite",
|
|
56
|
+
"computedOffset",
|
|
57
|
+
]);
|
|
58
|
+
|
|
59
|
+
class FilmstripItem extends TWMixin(LitElement) {
|
|
60
|
+
static styles = [
|
|
61
|
+
css`
|
|
62
|
+
:host {
|
|
63
|
+
display: block;
|
|
64
|
+
}
|
|
65
|
+
`,
|
|
66
|
+
];
|
|
67
|
+
|
|
68
|
+
@consume({ context: focusContext })
|
|
69
|
+
focusContext?: FocusContext;
|
|
70
|
+
|
|
71
|
+
@consume({ context: focusedElement, subscribe: true })
|
|
72
|
+
focusedElement?: HTMLElement | null;
|
|
73
|
+
|
|
74
|
+
get isFocused() {
|
|
75
|
+
return this.element && this.focusedElement === this.element;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@property({ type: HTMLElement, attribute: false })
|
|
79
|
+
element: TemporalMixinInterface & LitElement = new EFTimegroup();
|
|
80
|
+
|
|
81
|
+
@property({ type: Number })
|
|
82
|
+
pixelsPerMs = 0.04;
|
|
83
|
+
|
|
84
|
+
get styles() {
|
|
85
|
+
return {
|
|
86
|
+
position: "relative",
|
|
87
|
+
left: `${this.pixelsPerMs * this.element.startTimeWithinParentMs}px`,
|
|
88
|
+
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
render() {
|
|
93
|
+
return html` <div class="" style=${styleMap(this.styles)}>
|
|
94
|
+
<div
|
|
95
|
+
@mouseenter=${() => {
|
|
96
|
+
if (this.focusContext) {
|
|
97
|
+
this.focusContext.focusedElement = this.element;
|
|
98
|
+
}
|
|
99
|
+
}}
|
|
100
|
+
@mouseleave=${() => {
|
|
101
|
+
if (this.focusContext) {
|
|
102
|
+
this.focusContext.focusedElement = null;
|
|
103
|
+
}
|
|
104
|
+
}}
|
|
105
|
+
?data-focused=${this.isFocused}
|
|
106
|
+
class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
|
|
107
|
+
>
|
|
108
|
+
${this.animations()}
|
|
109
|
+
</div>
|
|
110
|
+
${this.renderChildren()}
|
|
111
|
+
</div>`;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
renderChildren(): Array<TemplateResult<1> | typeof nothing> | typeof nothing {
|
|
115
|
+
return renderFilmstripChildren(
|
|
116
|
+
Array.from(this.element.children),
|
|
117
|
+
this.pixelsPerMs,
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
contents() {
|
|
122
|
+
return html``;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
animations() {
|
|
126
|
+
const animations = this.element.getAnimations();
|
|
127
|
+
return animations.map((animation) => {
|
|
128
|
+
const effect = animation.effect;
|
|
129
|
+
if (!(effect instanceof KeyframeEffect)) {
|
|
130
|
+
return nothing;
|
|
131
|
+
}
|
|
132
|
+
const start = effect.getTiming().delay ?? 0;
|
|
133
|
+
const duration = effect.getTiming().duration;
|
|
134
|
+
if (duration === null) {
|
|
135
|
+
return nothing;
|
|
136
|
+
}
|
|
137
|
+
const keyframes = effect.getKeyframes();
|
|
138
|
+
const firstKeyframe = keyframes[0];
|
|
139
|
+
if (!firstKeyframe) {
|
|
140
|
+
return nothing;
|
|
141
|
+
}
|
|
142
|
+
const properties = new Set(Object.keys(firstKeyframe));
|
|
143
|
+
for (const key of CommonEffectKeys) {
|
|
144
|
+
properties.delete(key);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return html`<div
|
|
148
|
+
class="relative h-[5px] bg-blue-500 opacity-50"
|
|
149
|
+
label="animation"
|
|
150
|
+
style=${styleMap({
|
|
151
|
+
left: `${this.pixelsPerMs * start}px`,
|
|
152
|
+
width: `${this.pixelsPerMs * Number(duration)}px`,
|
|
153
|
+
})}
|
|
154
|
+
>
|
|
155
|
+
<!-- <div class="text-nowrap">${Array.from(properties).join(" ")}</div> -->
|
|
156
|
+
${effect.getKeyframes().map((keyframe) => {
|
|
157
|
+
return html`<div
|
|
158
|
+
class="absolute top-0 h-full w-1 bg-red-500"
|
|
159
|
+
style=${styleMap({
|
|
160
|
+
left: `${
|
|
161
|
+
this.pixelsPerMs * keyframe.computedOffset * Number(duration)
|
|
162
|
+
}px`,
|
|
163
|
+
})}
|
|
164
|
+
></div>`;
|
|
165
|
+
})}
|
|
166
|
+
</div>`;
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
protected filmstripController?: ElementFilmstripController;
|
|
171
|
+
|
|
172
|
+
update(changedProperties: Map<string | number | symbol, unknown>) {
|
|
173
|
+
if (
|
|
174
|
+
changedProperties.has("element") &&
|
|
175
|
+
this.element instanceof LitElement
|
|
176
|
+
) {
|
|
177
|
+
this.filmstripController?.remove();
|
|
178
|
+
this.filmstripController = new ElementFilmstripController(
|
|
179
|
+
this.element,
|
|
180
|
+
this,
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
super.update(changedProperties);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
@customElement("ef-audio-filmstrip")
|
|
188
|
+
export class EFAudioFilmstrip extends FilmstripItem {
|
|
189
|
+
contents() {
|
|
190
|
+
return html``;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
@customElement("ef-video-filmstrip")
|
|
195
|
+
export class EFVideoFilmstrip extends FilmstripItem {
|
|
196
|
+
contents() {
|
|
197
|
+
return html` 📼 `;
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
@customElement("ef-captions-filmstrip")
|
|
202
|
+
export class EFCaptionsFilmstrip extends FilmstripItem {
|
|
203
|
+
contents() {
|
|
204
|
+
return html` 📝 `;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
@customElement("ef-waveform-filmstrip")
|
|
209
|
+
export class EFWaveformFilmstrip extends FilmstripItem {
|
|
210
|
+
contents() {
|
|
211
|
+
return html` 🌊 `;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
renderChildren(): typeof nothing {
|
|
215
|
+
return nothing;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
@customElement("ef-image-filmstrip")
|
|
220
|
+
export class EFImageFilmstrip extends FilmstripItem {
|
|
221
|
+
contents() {
|
|
222
|
+
return html` 🖼️ `;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
@customElement("ef-timegroup-filmstrip")
|
|
227
|
+
export class EFTimegroupFilmstrip extends FilmstripItem {
|
|
228
|
+
contents() {
|
|
229
|
+
return html`
|
|
230
|
+
<span>TIME GROUP</span>
|
|
231
|
+
${renderFilmstripChildren(
|
|
232
|
+
Array.from(this.element.children || []),
|
|
233
|
+
this.pixelsPerMs,
|
|
234
|
+
)}
|
|
235
|
+
</div>
|
|
236
|
+
`;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
@customElement("ef-html-filmstrip")
|
|
241
|
+
export class EFHTMLFilmstrip extends FilmstripItem {
|
|
242
|
+
contents() {
|
|
243
|
+
return html`
|
|
244
|
+
<span>${this.element.tagName}</span>
|
|
245
|
+
${renderFilmstripChildren(
|
|
246
|
+
Array.from(this.element.children || []),
|
|
247
|
+
this.pixelsPerMs,
|
|
248
|
+
)}
|
|
249
|
+
`;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
@customElement("ef-hierarchy-item")
|
|
254
|
+
class EFHierarchyItem<
|
|
255
|
+
ElementType extends HTMLElement = HTMLElement,
|
|
256
|
+
> extends TWMixin(LitElement) {
|
|
257
|
+
@property({ type: HTMLElement, attribute: false })
|
|
258
|
+
// @ts-expect-error This could be initialzed with any HTMLElement
|
|
259
|
+
element: ElementType = new EFTimegroup();
|
|
260
|
+
|
|
261
|
+
@consume({ context: focusContext })
|
|
262
|
+
focusContext?: FocusContext;
|
|
263
|
+
|
|
264
|
+
@consume({ context: focusedElement, subscribe: true })
|
|
265
|
+
focusedElement?: HTMLElement | null;
|
|
266
|
+
|
|
267
|
+
get icon(): TemplateResult<1> | string {
|
|
268
|
+
return "📼";
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
get isFocused() {
|
|
272
|
+
return this.element && this.focusedElement === this.element;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
displayLabel(): TemplateResult<1> | string | typeof nothing {
|
|
276
|
+
return nothing;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
render() {
|
|
280
|
+
return html`
|
|
281
|
+
<div>
|
|
282
|
+
<div
|
|
283
|
+
?data-focused=${this.isFocused}
|
|
284
|
+
class="peer
|
|
285
|
+
flex h-[1.1rem] items-center overflow-hidden text-nowrap border border-slate-500
|
|
286
|
+
bg-slate-200 pl-2 text-xs font-mono hover:bg-slate-400 data-[focused]:bg-slate-400"
|
|
287
|
+
@mouseenter=${() => {
|
|
288
|
+
if (this.focusContext) {
|
|
289
|
+
this.focusContext.focusedElement = this.element;
|
|
290
|
+
}
|
|
291
|
+
}}
|
|
292
|
+
@mouseleave=${() => {
|
|
293
|
+
if (this.focusContext) {
|
|
294
|
+
this.focusContext.focusedElement = null;
|
|
295
|
+
}
|
|
296
|
+
}}
|
|
297
|
+
>
|
|
298
|
+
${this.icon} ${this.displayLabel()}
|
|
299
|
+
</div>
|
|
300
|
+
<div
|
|
301
|
+
class="p-[1px] pb-0 pl-2 pr-0 peer-hover:bg-slate-300 peer-data-[focused]:bg-slate-300 peer-hover:border-slate-400 peer-data-[focused]:border-slate-400""
|
|
302
|
+
>
|
|
303
|
+
${this.renderChildren()}
|
|
304
|
+
</div>
|
|
305
|
+
</div>`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
renderChildren(): Array<TemplateResult<1>> | typeof nothing {
|
|
309
|
+
return renderHierarchyChildren(Array.from(this.element.children));
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
@customElement("ef-timegroup-hierarchy-item")
|
|
314
|
+
class EFTimegroupHierarchyItem extends EFHierarchyItem<EFTimegroup> {
|
|
315
|
+
get icon() {
|
|
316
|
+
return "🕒";
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
displayLabel(): string | TemplateResult<1> | typeof nothing {
|
|
320
|
+
return this.element.mode ?? "(no mode)";
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
@customElement("ef-audio-hierarchy-item")
|
|
325
|
+
class EFAudioHierarchyItem extends EFHierarchyItem<EFAudio> {
|
|
326
|
+
get icon() {
|
|
327
|
+
return "🔊";
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
displayLabel() {
|
|
331
|
+
return this.element.src ?? "(no src)";
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
@customElement("ef-video-hierarchy-item")
|
|
336
|
+
class EFVideoHierarchyItem extends EFHierarchyItem<EFVideo> {
|
|
337
|
+
get icon() {
|
|
338
|
+
return "📼";
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
displayLabel() {
|
|
342
|
+
return this.element.src ?? "(no src)";
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
@customElement("ef-captions-hierarchy-item")
|
|
347
|
+
class EFCaptionsHierarchyItem extends EFHierarchyItem {
|
|
348
|
+
get icon() {
|
|
349
|
+
return "📝 Captions";
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
@customElement("ef-captions-active-word-hierarchy-item")
|
|
354
|
+
class EFCaptionsActiveWordHierarchyItem extends EFHierarchyItem {
|
|
355
|
+
get icon() {
|
|
356
|
+
return "🗣️ Active Word";
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
@customElement("ef-waveform-hierarchy-item")
|
|
361
|
+
class EFWaveformHierarchyItem extends EFHierarchyItem {
|
|
362
|
+
get icon() {
|
|
363
|
+
return "🌊";
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
renderChildren(): typeof nothing {
|
|
367
|
+
return nothing;
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
@customElement("ef-image-hierarchy-item")
|
|
372
|
+
class EFImageHierarchyItem extends EFHierarchyItem<EFImage> {
|
|
373
|
+
get icon() {
|
|
374
|
+
return "🖼️";
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
displayLabel() {
|
|
378
|
+
return this.element.src ?? "(no src)";
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
@customElement("ef-html-hierarchy-item")
|
|
383
|
+
class EFHTMLHierarchyItem extends EFHierarchyItem {
|
|
384
|
+
get icon() {
|
|
385
|
+
return html`<code>${`<${this.element.tagName.toLowerCase()}>`}</code>`;
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const renderHierarchyChildren = (
|
|
390
|
+
children: Element[],
|
|
391
|
+
): Array<TemplateResult<1>> => {
|
|
392
|
+
return children.map((child) => {
|
|
393
|
+
if (child instanceof EFTimegroup) {
|
|
394
|
+
return html`<ef-timegroup-hierarchy-item
|
|
395
|
+
.element=${child}
|
|
396
|
+
></ef-timegroup-hierarchy-item>`;
|
|
397
|
+
}
|
|
398
|
+
if (child instanceof EFImage) {
|
|
399
|
+
return html`<ef-image-hierarchy-item
|
|
400
|
+
.element=${child}
|
|
401
|
+
></ef-image-hierarchy-item>`;
|
|
402
|
+
}
|
|
403
|
+
if (child instanceof EFAudio) {
|
|
404
|
+
return html`<ef-audio-hierarchy-item
|
|
405
|
+
.element=${child}
|
|
406
|
+
></ef-audio-hierarchy-item>`;
|
|
407
|
+
}
|
|
408
|
+
if (child instanceof EFVideo) {
|
|
409
|
+
return html`<ef-video-hierarchy-item
|
|
410
|
+
.element=${child}
|
|
411
|
+
></ef-video-hierarchy-item>`;
|
|
412
|
+
}
|
|
413
|
+
if (child instanceof EFCaptions) {
|
|
414
|
+
return html`<ef-captions-hierarchy-item
|
|
415
|
+
.element=${child}
|
|
416
|
+
></ef-captions-hierarchy-item>`;
|
|
417
|
+
}
|
|
418
|
+
if (child instanceof EFCaptionsActiveWord) {
|
|
419
|
+
return html`<ef-captions-active-word-hierarchy-item
|
|
420
|
+
.element=${child}
|
|
421
|
+
></ef-captions-active-word-hierarchy-item>`;
|
|
422
|
+
}
|
|
423
|
+
if (child instanceof EFWaveform) {
|
|
424
|
+
return html`<ef-waveform-hierarchy-item
|
|
425
|
+
.element=${child}
|
|
426
|
+
></ef-waveform-hierarchy-item>`;
|
|
427
|
+
}
|
|
428
|
+
return html`<ef-html-hierarchy-item
|
|
429
|
+
.element=${child}
|
|
430
|
+
></ef-html-hierarchy-item>`;
|
|
431
|
+
});
|
|
432
|
+
};
|
|
433
|
+
|
|
434
|
+
const renderFilmstripChildren = (
|
|
435
|
+
children: Element[],
|
|
436
|
+
pixelsPerMs: number,
|
|
437
|
+
): Array<TemplateResult<1> | typeof nothing> => {
|
|
438
|
+
return children.map((child) => {
|
|
439
|
+
if (child instanceof EFTimegroup) {
|
|
440
|
+
return html`<ef-timegroup-filmstrip
|
|
441
|
+
.element=${child}
|
|
442
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
443
|
+
>
|
|
444
|
+
</ef-timegroup-filmstrip>`;
|
|
445
|
+
}
|
|
446
|
+
if (child instanceof EFImage) {
|
|
447
|
+
return html`<ef-image-filmstrip
|
|
448
|
+
.element=${child}
|
|
449
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
450
|
+
></ef-image-filmstrip>`;
|
|
451
|
+
}
|
|
452
|
+
if (child instanceof EFAudio) {
|
|
453
|
+
return html`<ef-audio-filmstrip
|
|
454
|
+
.element=${child}
|
|
455
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
456
|
+
></ef-audio-filmstrip>`;
|
|
457
|
+
}
|
|
458
|
+
if (child instanceof EFVideo) {
|
|
459
|
+
return html`<ef-video-filmstrip
|
|
460
|
+
.element=${child}
|
|
461
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
462
|
+
></ef-video-filmstrip>`;
|
|
463
|
+
}
|
|
464
|
+
if (child instanceof EFCaptions) {
|
|
465
|
+
return html`<ef-captions-filmstrip
|
|
466
|
+
.element=${child}
|
|
467
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
468
|
+
></ef-captions-filmstrip>`;
|
|
469
|
+
}
|
|
470
|
+
if (child instanceof EFWaveform) {
|
|
471
|
+
return html`<ef-waveform-filmstrip
|
|
472
|
+
.element=${child}
|
|
473
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
474
|
+
></ef-waveform-filmstrip>`;
|
|
475
|
+
}
|
|
476
|
+
return html`<ef-html-filmstrip
|
|
477
|
+
.element=${child}
|
|
478
|
+
.pixelsPerMs=${pixelsPerMs}
|
|
479
|
+
></ef-html-filmstrip>`;
|
|
480
|
+
});
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
@customElement("ef-filmstrip")
|
|
484
|
+
export class EFFilmstrip extends TWMixin(LitElement) {
|
|
485
|
+
@property({ type: Number })
|
|
486
|
+
pixelsPerMs = 0.04;
|
|
487
|
+
|
|
488
|
+
@property({ type: Number })
|
|
489
|
+
currentTimeMs = 0;
|
|
490
|
+
|
|
491
|
+
@property({ type: String, attribute: "target", reflect: true })
|
|
492
|
+
targetSelector = "";
|
|
493
|
+
|
|
494
|
+
@state()
|
|
495
|
+
scrubbing = false;
|
|
496
|
+
|
|
497
|
+
@state()
|
|
498
|
+
playing = false;
|
|
499
|
+
|
|
500
|
+
@state()
|
|
501
|
+
timelineScrolltop = 0;
|
|
502
|
+
|
|
503
|
+
timegroupController?: TimegroupController;
|
|
504
|
+
|
|
505
|
+
connectedCallback(): void {
|
|
506
|
+
super.connectedCallback();
|
|
507
|
+
const target = this.targetTimegroup;
|
|
508
|
+
if (target) {
|
|
509
|
+
this.timegroupController = new TimegroupController(target, this);
|
|
510
|
+
// Set the current time to the last saved time to avoid a cycle
|
|
511
|
+
// where the filmstrip clobbers the time loaded from localStorage
|
|
512
|
+
this.currentTimeMs = target.currentTimeMs;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
window.addEventListener("keypress", this.#handleKeyPress);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
disconnectedCallback(): void {
|
|
519
|
+
super.disconnectedCallback();
|
|
520
|
+
window.removeEventListener("keypress", this.#handleKeyPress);
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
#handleKeyPress = (event: KeyboardEvent) => {
|
|
524
|
+
// On spacebar, toggle playback
|
|
525
|
+
if (event.key === " ") {
|
|
526
|
+
// CSS selector to match all interactive elements
|
|
527
|
+
const interactiveSelector =
|
|
528
|
+
"input, textarea, button, select, a, [contenteditable]";
|
|
529
|
+
|
|
530
|
+
// Check if the event target or its ancestor matches an interactive element
|
|
531
|
+
const closestInteractive = (event.target as HTMLElement | null)?.closest(
|
|
532
|
+
interactiveSelector,
|
|
533
|
+
);
|
|
534
|
+
if (closestInteractive) {
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
event.preventDefault();
|
|
538
|
+
this.playing = !this.playing;
|
|
539
|
+
}
|
|
540
|
+
};
|
|
541
|
+
|
|
542
|
+
@eventOptions({ passive: false })
|
|
543
|
+
syncGutterScroll() {
|
|
544
|
+
if (this.gutter && this.hierarchyRef.value) {
|
|
545
|
+
this.hierarchyRef.value.scrollTop = this.gutter.scrollTop;
|
|
546
|
+
this.timelineScrolltop = this.gutter.scrollTop;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
@eventOptions({ passive: false })
|
|
551
|
+
syncHierarchyScroll() {
|
|
552
|
+
if (this.gutter && this.hierarchyRef.value) {
|
|
553
|
+
this.gutter.scrollTop = this.hierarchyRef.value.scrollTop;
|
|
554
|
+
this.timelineScrolltop = this.hierarchyRef.value.scrollTop;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
#lastTick?: DOMHighResTimeStamp;
|
|
559
|
+
|
|
560
|
+
#playbackAudioContext: AudioContext | null = null;
|
|
561
|
+
#playbackAnimationFrameRequest: number | null = null;
|
|
562
|
+
#AUDIO_PLAYBACK_SLICE_MS = 1000;
|
|
563
|
+
|
|
564
|
+
#syncPlayheadToAudioContext(target: EFTimegroup, startMs: number) {
|
|
565
|
+
target.currentTimeMs =
|
|
566
|
+
startMs + (this.#playbackAudioContext?.currentTime ?? 0) * 1000;
|
|
567
|
+
this.#playbackAnimationFrameRequest = requestAnimationFrame(() => {
|
|
568
|
+
this.#syncPlayheadToAudioContext(target, startMs);
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
async #stopPlayback() {
|
|
573
|
+
if (this.#playbackAudioContext) {
|
|
574
|
+
if (this.#playbackAudioContext.state !== "closed") {
|
|
575
|
+
await this.#playbackAudioContext.close();
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
579
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
580
|
+
}
|
|
581
|
+
this.#playbackAudioContext = null;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
async #startPlayback() {
|
|
585
|
+
await this.#stopPlayback();
|
|
586
|
+
const timegroup = this.targetTimegroup;
|
|
587
|
+
if (!timegroup) {
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let currentMs = timegroup.currentTimeMs;
|
|
592
|
+
let bufferCount = 0;
|
|
593
|
+
this.#playbackAudioContext = new AudioContext({
|
|
594
|
+
latencyHint: "playback",
|
|
595
|
+
});
|
|
596
|
+
if (this.#playbackAnimationFrameRequest) {
|
|
597
|
+
cancelAnimationFrame(this.#playbackAnimationFrameRequest);
|
|
598
|
+
}
|
|
599
|
+
this.#syncPlayheadToAudioContext(timegroup, currentMs);
|
|
600
|
+
const playbackContext = this.#playbackAudioContext;
|
|
601
|
+
await playbackContext.suspend();
|
|
602
|
+
|
|
603
|
+
const fillBuffer = async () => {
|
|
604
|
+
if (bufferCount > 1) {
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const canFillBuffer = await queueBufferSource();
|
|
608
|
+
if (canFillBuffer) {
|
|
609
|
+
fillBuffer();
|
|
610
|
+
}
|
|
611
|
+
};
|
|
612
|
+
|
|
613
|
+
const fromMs = currentMs;
|
|
614
|
+
const toMs = timegroup.endTimeMs;
|
|
615
|
+
|
|
616
|
+
const queueBufferSource = async () => {
|
|
617
|
+
if (currentMs >= toMs) {
|
|
618
|
+
return false;
|
|
619
|
+
}
|
|
620
|
+
const startMs = currentMs;
|
|
621
|
+
const endMs = currentMs + this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
622
|
+
currentMs += this.#AUDIO_PLAYBACK_SLICE_MS;
|
|
623
|
+
const audioBuffer = await timegroup.renderAudio(startMs, endMs);
|
|
624
|
+
bufferCount++;
|
|
625
|
+
const source = playbackContext.createBufferSource();
|
|
626
|
+
source.buffer = audioBuffer;
|
|
627
|
+
source.connect(playbackContext.destination);
|
|
628
|
+
source.start((startMs - fromMs) / 1000);
|
|
629
|
+
source.onended = () => {
|
|
630
|
+
bufferCount--;
|
|
631
|
+
if (endMs >= toMs) {
|
|
632
|
+
this.playing = false;
|
|
633
|
+
} else {
|
|
634
|
+
fillBuffer();
|
|
635
|
+
}
|
|
636
|
+
};
|
|
637
|
+
return true;
|
|
638
|
+
};
|
|
639
|
+
|
|
640
|
+
await fillBuffer();
|
|
641
|
+
await playbackContext.resume();
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
advancePlayhead = (tick?: DOMHighResTimeStamp) => {
|
|
645
|
+
if (this.#lastTick && tick && this.targetTimegroup) {
|
|
646
|
+
this.targetTimegroup.currentTimeMs += tick - this.#lastTick;
|
|
647
|
+
if (
|
|
648
|
+
this.targetTimegroup.currentTimeMs >= this.targetTimegroup.durationMs
|
|
649
|
+
) {
|
|
650
|
+
this.playing = false;
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
this.#lastTick = tick;
|
|
654
|
+
|
|
655
|
+
if (this.playing) {
|
|
656
|
+
requestAnimationFrame(this.advancePlayhead);
|
|
657
|
+
}
|
|
658
|
+
};
|
|
659
|
+
|
|
660
|
+
@eventOptions({ capture: false })
|
|
661
|
+
scrub(e: MouseEvent) {
|
|
662
|
+
if (this.playing) {
|
|
663
|
+
return;
|
|
664
|
+
}
|
|
665
|
+
if (!this.scrubbing) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const gutter = this.shadowRoot?.querySelector("#gutter");
|
|
669
|
+
if (!gutter) {
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
const rect = gutter.getBoundingClientRect();
|
|
673
|
+
if (this.targetTimegroup) {
|
|
674
|
+
const layerX = e.pageX - rect.left + gutter.scrollLeft;
|
|
675
|
+
this.targetTimegroup.currentTimeMs = layerX / this.pixelsPerMs;
|
|
676
|
+
}
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
@eventOptions({ capture: false })
|
|
680
|
+
startScrub(e: MouseEvent) {
|
|
681
|
+
e.preventDefault();
|
|
682
|
+
this.scrubbing = true;
|
|
683
|
+
// Running scrub in the current microtask doesn't
|
|
684
|
+
// result in an actual update. Not sure why.
|
|
685
|
+
queueMicrotask(() => {
|
|
686
|
+
const gutter = this.shadowRoot?.querySelector("#gutter");
|
|
687
|
+
if (!gutter) {
|
|
688
|
+
return;
|
|
689
|
+
}
|
|
690
|
+
const rect = gutter.getBoundingClientRect();
|
|
691
|
+
if (this.targetTimegroup) {
|
|
692
|
+
const layerX = e.pageX - rect.left + gutter.scrollLeft;
|
|
693
|
+
this.targetTimegroup.currentTimeMs = layerX / this.pixelsPerMs;
|
|
694
|
+
}
|
|
695
|
+
});
|
|
696
|
+
addEventListener(
|
|
697
|
+
"mouseup",
|
|
698
|
+
() => {
|
|
699
|
+
this.scrubbing = false;
|
|
700
|
+
},
|
|
701
|
+
{ once: true },
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
@eventOptions({ passive: false })
|
|
706
|
+
scrollScrub(e: WheelEvent) {
|
|
707
|
+
if (this.targetTimegroup && this.gutter && !this.playing) {
|
|
708
|
+
e.preventDefault();
|
|
709
|
+
// Avoid over-scrolling to the left
|
|
710
|
+
if (
|
|
711
|
+
this.gutterRef.value &&
|
|
712
|
+
this.gutterRef.value.scrollLeft === 0 &&
|
|
713
|
+
e.deltaX < 0
|
|
714
|
+
) {
|
|
715
|
+
this.gutter.scrollBy(0, e.deltaY);
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Avoid over-scrolling to the right
|
|
720
|
+
if (
|
|
721
|
+
this.gutter.scrollWidth - this.gutter.scrollLeft ===
|
|
722
|
+
this.gutter.clientWidth &&
|
|
723
|
+
e.deltaX > 0
|
|
724
|
+
) {
|
|
725
|
+
this.gutter.scrollBy(0, e.deltaY);
|
|
726
|
+
return;
|
|
727
|
+
}
|
|
728
|
+
|
|
729
|
+
if (this) {
|
|
730
|
+
this.gutter.scrollBy(e.deltaX, e.deltaY);
|
|
731
|
+
this.targetTimegroup.currentTimeMs += e.deltaX / this.pixelsPerMs;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
gutterRef = createRef<HTMLDivElement>();
|
|
737
|
+
hierarchyRef = createRef<HTMLDivElement>();
|
|
738
|
+
playheadRef = createRef<HTMLDivElement>();
|
|
739
|
+
|
|
740
|
+
get gutter() {
|
|
741
|
+
return this.gutterRef.value;
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
render() {
|
|
745
|
+
const target = this.targetTimegroup;
|
|
746
|
+
|
|
747
|
+
return html` <div
|
|
748
|
+
class="grid h-full bg-slate-100"
|
|
749
|
+
style=${styleMap({
|
|
750
|
+
gridTemplateColumns: "200px 1fr",
|
|
751
|
+
gridTemplateRows: "1.5rem 1fr",
|
|
752
|
+
})}
|
|
753
|
+
>
|
|
754
|
+
<div
|
|
755
|
+
class="z-20 col-span-2 border-b-slate-600 bg-slate-100 shadow shadow-slate-300"
|
|
756
|
+
>
|
|
757
|
+
<input
|
|
758
|
+
type="range"
|
|
759
|
+
.value=${this.pixelsPerMs}
|
|
760
|
+
min="0.01"
|
|
761
|
+
max="0.1"
|
|
762
|
+
step="0.001"
|
|
763
|
+
@input=${(e: Event) => {
|
|
764
|
+
const target = e.target as HTMLInputElement;
|
|
765
|
+
this.pixelsPerMs = Number.parseFloat(target.value);
|
|
766
|
+
}}
|
|
767
|
+
/>
|
|
768
|
+
<code>${msToTimeCode(this.currentTimeMs, true)} </code> /
|
|
769
|
+
<code>${msToTimeCode(target?.durationMs ?? 0, true)}</code>
|
|
770
|
+
${
|
|
771
|
+
this.playing
|
|
772
|
+
? html`<button
|
|
773
|
+
@click=${() => {
|
|
774
|
+
this.playing = false;
|
|
775
|
+
}}
|
|
776
|
+
>
|
|
777
|
+
⏸️
|
|
778
|
+
</button>`
|
|
779
|
+
: html`<button
|
|
780
|
+
@click=${() => {
|
|
781
|
+
this.playing = true;
|
|
782
|
+
}}
|
|
783
|
+
>
|
|
784
|
+
▶️
|
|
785
|
+
</button>`
|
|
786
|
+
}
|
|
787
|
+
</div>
|
|
788
|
+
<div
|
|
789
|
+
class="z-10 pl-1 pr-1 pt-2 shadow shadow-slate-600 overflow-auto"
|
|
790
|
+
${ref(this.hierarchyRef)}
|
|
791
|
+
@scroll=${this.syncHierarchyScroll}
|
|
792
|
+
>
|
|
793
|
+
${renderHierarchyChildren(Array.from(target?.children ?? []))}
|
|
794
|
+
</div>
|
|
795
|
+
<div
|
|
796
|
+
class="h-full w-full cursor-crosshair overflow-auto bg-slate-200 pt-2"
|
|
797
|
+
id="gutter"
|
|
798
|
+
${ref(this.gutterRef)}
|
|
799
|
+
@scroll=${this.syncGutterScroll}
|
|
800
|
+
@wheel=${this.scrollScrub}
|
|
801
|
+
>
|
|
802
|
+
<div
|
|
803
|
+
class="relative h-full w-full"
|
|
804
|
+
style="width: ${this.pixelsPerMs * (target?.durationMs ?? 0)}px;"
|
|
805
|
+
@mousemove=${this.scrub}
|
|
806
|
+
@mousedown=${this.startScrub}
|
|
807
|
+
>
|
|
808
|
+
<div
|
|
809
|
+
class="border-red pointer-events-none absolute z-10 h-full w-[2px] border-r-2 border-red-700"
|
|
810
|
+
style=${styleMap({
|
|
811
|
+
left: `${this.pixelsPerMs * this.currentTimeMs}px`,
|
|
812
|
+
top: `${this.timelineScrolltop}px`,
|
|
813
|
+
})}
|
|
814
|
+
${ref(this.playheadRef)}
|
|
815
|
+
></div>
|
|
816
|
+
|
|
817
|
+
${renderFilmstripChildren(
|
|
818
|
+
Array.from(target?.children || []),
|
|
819
|
+
this.pixelsPerMs,
|
|
820
|
+
)}
|
|
821
|
+
</div>
|
|
822
|
+
</div>
|
|
823
|
+
</div>`;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
update(changedProperties: Map<string | number | symbol, unknown>) {
|
|
827
|
+
if (changedProperties.has("playing")) {
|
|
828
|
+
if (this.playing) {
|
|
829
|
+
this.#startPlayback();
|
|
830
|
+
} else {
|
|
831
|
+
this.#stopPlayback();
|
|
832
|
+
}
|
|
833
|
+
}
|
|
834
|
+
super.update(changedProperties);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
updated(changes: PropertyValueMap<any> | Map<PropertyKey, unknown>) {
|
|
838
|
+
if (!this.targetTimegroup) {
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (changes.has("currentTimeMs")) {
|
|
842
|
+
if (this.targetTimegroup.currentTimeMs !== this.currentTimeMs) {
|
|
843
|
+
this.targetTimegroup.currentTimeMs = this.currentTimeMs;
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
get targetTimegroup() {
|
|
849
|
+
if (this.getAttribute("target")) {
|
|
850
|
+
const target = document.getElementById(this.getAttribute("target") ?? "");
|
|
851
|
+
if (target instanceof EFTimegroup) {
|
|
852
|
+
return target;
|
|
853
|
+
}
|
|
854
|
+
|
|
855
|
+
throw new Error("Invalid target, must be an EFTimegroup element");
|
|
856
|
+
}
|
|
857
|
+
return undefined;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
declare global {
|
|
862
|
+
interface HTMLElementTagNameMap {
|
|
863
|
+
"ef-filmstrip": EFFilmstrip;
|
|
864
|
+
"ef-timegroup-hierarchy-item": EFTimegroupHierarchyItem;
|
|
865
|
+
"ef-audio-hierarchy-item": EFAudioHierarchyItem;
|
|
866
|
+
"ef-video-hierarchy-item": EFVideoHierarchyItem;
|
|
867
|
+
"ef-captions-hierarchy-item": EFCaptionsHierarchyItem;
|
|
868
|
+
"ef-captions-active-word-hierarchy-item": EFCaptionsActiveWordHierarchyItem;
|
|
869
|
+
"ef-waveform-hierarchy-item": EFWaveformHierarchyItem;
|
|
870
|
+
"ef-image-hierarchy-item": EFImageHierarchyItem;
|
|
871
|
+
"ef-html-hierarchy-item": EFHTMLHierarchyItem;
|
|
872
|
+
"ef-timegroup-filmstrip": EFTimegroupFilmstrip;
|
|
873
|
+
"ef-audio-filmstrip": EFAudioFilmstrip;
|
|
874
|
+
"ef-video-filmstrip": EFVideoFilmstrip;
|
|
875
|
+
"ef-captions-filmstrip": EFCaptionsFilmstrip;
|
|
876
|
+
"ef-waveform-filmstrip": EFWaveformFilmstrip;
|
|
877
|
+
"ef-image-filmstrip": EFImageFilmstrip;
|
|
878
|
+
"ef-html-filmstrip": EFHTMLFilmstrip;
|
|
879
|
+
}
|
|
880
|
+
}
|