@editframe/elements 0.11.0-beta.8 → 0.12.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.
Files changed (49) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +8 -15
  2. package/dist/elements/EFCaptions.d.ts +50 -6
  3. package/dist/elements/EFMedia.d.ts +1 -1
  4. package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
  5. package/dist/elements/EFTimegroup.d.ts +23 -2
  6. package/dist/elements/EFWaveform.d.ts +17 -13
  7. package/dist/elements/src/EF_FRAMEGEN.js +24 -26
  8. package/dist/elements/src/elements/EFCaptions.js +295 -42
  9. package/dist/elements/src/elements/EFImage.js +3 -13
  10. package/dist/elements/src/elements/EFMedia.js +0 -5
  11. package/dist/elements/src/elements/EFTemporal.js +13 -10
  12. package/dist/elements/src/elements/EFTimegroup.js +37 -12
  13. package/dist/elements/src/elements/EFVideo.js +7 -7
  14. package/dist/elements/src/elements/EFWaveform.js +262 -149
  15. package/dist/elements/src/gui/ContextMixin.js +36 -7
  16. package/dist/elements/src/gui/EFFilmstrip.js +16 -3
  17. package/dist/elements/src/gui/EFScrubber.js +142 -0
  18. package/dist/elements/src/gui/EFTimeDisplay.js +81 -0
  19. package/dist/elements/src/gui/EFTogglePlay.js +14 -14
  20. package/dist/elements/src/gui/EFWorkbench.js +1 -24
  21. package/dist/elements/src/gui/TWMixin.css.js +1 -1
  22. package/dist/elements/src/index.js +8 -1
  23. package/dist/gui/ContextMixin.d.ts +2 -1
  24. package/dist/gui/EFScrubber.d.ts +23 -0
  25. package/dist/gui/EFTimeDisplay.d.ts +17 -0
  26. package/dist/gui/EFTogglePlay.d.ts +1 -1
  27. package/dist/gui/EFWorkbench.d.ts +0 -1
  28. package/dist/index.d.ts +3 -1
  29. package/dist/style.css +6 -801
  30. package/package.json +2 -2
  31. package/src/elements/EFCaptions.browsertest.ts +6 -6
  32. package/src/elements/EFCaptions.ts +325 -56
  33. package/src/elements/EFImage.browsertest.ts +4 -17
  34. package/src/elements/EFImage.ts +4 -14
  35. package/src/elements/EFMedia.browsertest.ts +8 -19
  36. package/src/elements/EFMedia.ts +1 -6
  37. package/src/elements/EFTemporal.browsertest.ts +14 -0
  38. package/src/elements/EFTemporal.ts +14 -0
  39. package/src/elements/EFTimegroup.browsertest.ts +37 -0
  40. package/src/elements/EFTimegroup.ts +42 -17
  41. package/src/elements/EFVideo.ts +7 -8
  42. package/src/elements/EFWaveform.ts +349 -319
  43. package/src/gui/ContextMixin.browsertest.ts +28 -2
  44. package/src/gui/ContextMixin.ts +41 -9
  45. package/src/gui/EFFilmstrip.ts +16 -3
  46. package/src/gui/EFScrubber.ts +145 -0
  47. package/src/gui/EFTimeDisplay.ts +81 -0
  48. package/src/gui/EFTogglePlay.ts +21 -21
  49. package/src/gui/EFWorkbench.ts +3 -36
@@ -1,14 +1,14 @@
1
- import { html, css, LitElement } from "lit";
2
1
  import { Task } from "@lit/task";
2
+ import { html, css, LitElement } from "lit";
3
3
  import { property, customElement } from "lit/decorators.js";
4
- import { EFVideo } from "./EFVideo.js";
4
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
5
+ import { EF_RENDERING } from "../EF_RENDERING.js";
6
+ import { CrossUpdateController } from "./CrossUpdateController.js";
5
7
  import { EFAudio } from "./EFAudio.js";
8
+ import { EFSourceMixin } from "./EFSourceMixin.js";
6
9
  import { EFTemporal } from "./EFTemporal.js";
7
- import { CrossUpdateController } from "./CrossUpdateController.js";
10
+ import { EFVideo } from "./EFVideo.js";
8
11
  import { FetchMixin } from "./FetchMixin.js";
9
- import { EFSourceMixin } from "./EFSourceMixin.js";
10
- import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
11
- import { EF_RENDERING } from "../EF_RENDERING.js";
12
12
  var __defProp = Object.defineProperty;
13
13
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
14
14
  var __decorateClass = (decorators, target, key, kind) => {
@@ -19,15 +19,22 @@ var __decorateClass = (decorators, target, key, kind) => {
19
19
  if (kind && result) __defProp(target, key, result);
20
20
  return result;
21
21
  };
22
+ const stopWords = /* @__PURE__ */ new Set(["", ".", "!", "?", ","]);
22
23
  let EFCaptionsActiveWord = class extends EFTemporal(LitElement) {
23
24
  constructor() {
24
25
  super(...arguments);
25
26
  this.wordStartMs = 0;
26
27
  this.wordEndMs = 0;
27
28
  this.wordText = "";
29
+ this.hidden = false;
28
30
  }
29
31
  render() {
30
- return html`${this.wordText}`;
32
+ if (stopWords.has(this.wordText)) {
33
+ this.hidden = true;
34
+ return void 0;
35
+ }
36
+ this.hidden = false;
37
+ return html` ${this.wordText.trim()} `;
31
38
  }
32
39
  get startTimeMs() {
33
40
  return this.wordStartMs || 0;
@@ -40,6 +47,10 @@ EFCaptionsActiveWord.styles = [
40
47
  css`
41
48
  :host {
42
49
  display: inline-block;
50
+ white-space: pre;
51
+ }
52
+ :host([hidden]) {
53
+ display: none;
43
54
  }
44
55
  `
45
56
  ];
@@ -52,18 +63,176 @@ __decorateClass([
52
63
  __decorateClass([
53
64
  property({ type: String, attribute: false })
54
65
  ], EFCaptionsActiveWord.prototype, "wordText", 2);
66
+ __decorateClass([
67
+ property({ type: Boolean, reflect: true })
68
+ ], EFCaptionsActiveWord.prototype, "hidden", 2);
55
69
  EFCaptionsActiveWord = __decorateClass([
56
70
  customElement("ef-captions-active-word")
57
71
  ], EFCaptionsActiveWord);
72
+ let EFCaptionsSegment = class extends EFTemporal(LitElement) {
73
+ constructor() {
74
+ super(...arguments);
75
+ this.segmentStartMs = 0;
76
+ this.segmentEndMs = 0;
77
+ this.segmentText = "";
78
+ this.hidden = false;
79
+ }
80
+ render() {
81
+ if (stopWords.has(this.segmentText)) {
82
+ this.hidden = true;
83
+ return void 0;
84
+ }
85
+ this.hidden = false;
86
+ return html`${this.segmentText}`;
87
+ }
88
+ get startTimeMs() {
89
+ return this.segmentStartMs || 0;
90
+ }
91
+ get durationMs() {
92
+ return this.segmentEndMs - this.segmentStartMs;
93
+ }
94
+ };
95
+ EFCaptionsSegment.styles = [
96
+ css`
97
+ :host {
98
+ display: block;
99
+ }
100
+ :host([hidden]) {
101
+ display: none;
102
+ }
103
+ `
104
+ ];
105
+ __decorateClass([
106
+ property({ type: Number, attribute: false })
107
+ ], EFCaptionsSegment.prototype, "segmentStartMs", 2);
108
+ __decorateClass([
109
+ property({ type: Number, attribute: false })
110
+ ], EFCaptionsSegment.prototype, "segmentEndMs", 2);
111
+ __decorateClass([
112
+ property({ type: String, attribute: false })
113
+ ], EFCaptionsSegment.prototype, "segmentText", 2);
114
+ __decorateClass([
115
+ property({ type: Boolean, reflect: true })
116
+ ], EFCaptionsSegment.prototype, "hidden", 2);
117
+ EFCaptionsSegment = __decorateClass([
118
+ customElement("ef-captions-segment")
119
+ ], EFCaptionsSegment);
120
+ let EFCaptionsBeforeActiveWord = class extends EFCaptionsSegment {
121
+ constructor() {
122
+ super(...arguments);
123
+ this.hidden = false;
124
+ this.segmentText = "";
125
+ this.segmentStartMs = 0;
126
+ this.segmentEndMs = 0;
127
+ }
128
+ render() {
129
+ if (stopWords.has(this.segmentText)) {
130
+ this.hidden = true;
131
+ return void 0;
132
+ }
133
+ this.hidden = false;
134
+ return html` ${this.segmentText}`;
135
+ }
136
+ get startTimeMs() {
137
+ return this.segmentStartMs || 0;
138
+ }
139
+ get durationMs() {
140
+ return this.segmentEndMs - this.segmentStartMs;
141
+ }
142
+ };
143
+ EFCaptionsBeforeActiveWord.styles = [
144
+ css`
145
+ :host {
146
+ display: inline-block;
147
+ white-space: pre;
148
+ }
149
+ :host([hidden]) {
150
+ display: none;
151
+ }
152
+ `
153
+ ];
154
+ __decorateClass([
155
+ property({ type: Boolean, reflect: true })
156
+ ], EFCaptionsBeforeActiveWord.prototype, "hidden", 2);
157
+ __decorateClass([
158
+ property({ type: String, attribute: false })
159
+ ], EFCaptionsBeforeActiveWord.prototype, "segmentText", 2);
160
+ __decorateClass([
161
+ property({ type: Number, attribute: false })
162
+ ], EFCaptionsBeforeActiveWord.prototype, "segmentStartMs", 2);
163
+ __decorateClass([
164
+ property({ type: Number, attribute: false })
165
+ ], EFCaptionsBeforeActiveWord.prototype, "segmentEndMs", 2);
166
+ EFCaptionsBeforeActiveWord = __decorateClass([
167
+ customElement("ef-captions-before-active-word")
168
+ ], EFCaptionsBeforeActiveWord);
169
+ let EFCaptionsAfterActiveWord = class extends EFCaptionsSegment {
170
+ constructor() {
171
+ super(...arguments);
172
+ this.hidden = false;
173
+ this.segmentText = "";
174
+ this.segmentStartMs = 0;
175
+ this.segmentEndMs = 0;
176
+ }
177
+ render() {
178
+ if (stopWords.has(this.segmentText)) {
179
+ this.hidden = true;
180
+ return void 0;
181
+ }
182
+ this.hidden = false;
183
+ return html`${this.segmentText} `;
184
+ }
185
+ get startTimeMs() {
186
+ return this.segmentStartMs || 0;
187
+ }
188
+ get durationMs() {
189
+ return this.segmentEndMs - this.segmentStartMs;
190
+ }
191
+ };
192
+ EFCaptionsAfterActiveWord.styles = [
193
+ css`
194
+ :host {
195
+ display: inline-block;
196
+ white-space: pre;
197
+ }
198
+ :host([hidden]) {
199
+ display: none;
200
+ }
201
+ `
202
+ ];
203
+ __decorateClass([
204
+ property({ type: Boolean, reflect: true })
205
+ ], EFCaptionsAfterActiveWord.prototype, "hidden", 2);
206
+ __decorateClass([
207
+ property({ type: String, attribute: false })
208
+ ], EFCaptionsAfterActiveWord.prototype, "segmentText", 2);
209
+ __decorateClass([
210
+ property({ type: Number, attribute: false })
211
+ ], EFCaptionsAfterActiveWord.prototype, "segmentStartMs", 2);
212
+ __decorateClass([
213
+ property({ type: Number, attribute: false })
214
+ ], EFCaptionsAfterActiveWord.prototype, "segmentEndMs", 2);
215
+ EFCaptionsAfterActiveWord = __decorateClass([
216
+ customElement("ef-captions-after-active-word")
217
+ ], EFCaptionsAfterActiveWord);
58
218
  let EFCaptions = class extends EFSourceMixin(
59
219
  EFTemporal(FetchMixin(LitElement)),
60
220
  { assetType: "caption_files" }
61
221
  ) {
62
222
  constructor() {
63
223
  super(...arguments);
224
+ this.displayMode = "segment";
225
+ this.contextWords = 3;
64
226
  this.targetSelector = "";
65
227
  this.wordStyle = "";
66
228
  this.activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
229
+ this.segmentContainers = this.getElementsByTagName("ef-captions-segment");
230
+ this.beforeActiveWordContainers = this.getElementsByTagName(
231
+ "ef-captions-before-active-word"
232
+ );
233
+ this.afterActiveWordContainers = this.getElementsByTagName(
234
+ "ef-captions-after-active-word"
235
+ );
67
236
  this.md5SumLoader = new Task(this, {
68
237
  autoRun: false,
69
238
  args: () => [this.target, this.fetch],
@@ -73,25 +242,72 @@ let EFCaptions = class extends EFSourceMixin(
73
242
  return response.headers.get("etag") ?? void 0;
74
243
  }
75
244
  });
76
- this.captionsDataTask = new Task(this, {
245
+ this.transcriptionDataTask = new Task(this, {
246
+ autoRun: EF_INTERACTIVE,
247
+ args: () => [this.transcriptionsPath(), this.fetch],
248
+ task: async ([transcriptionsPath, fetch], { signal }) => {
249
+ if (!transcriptionsPath) {
250
+ return null;
251
+ }
252
+ const response = await fetch(transcriptionsPath, { signal });
253
+ return response.json();
254
+ }
255
+ });
256
+ this.fragmentIndexTask = new Task(this, {
257
+ autoRun: EF_INTERACTIVE,
258
+ args: () => [this.transcriptionDataTask.value, this.ownCurrentTimeMs],
259
+ task: async ([transcription, ownCurrentTimeMs]) => {
260
+ if (!transcription) {
261
+ return null;
262
+ }
263
+ const fragmentIndex = Math.floor(
264
+ ownCurrentTimeMs / transcription.work_slice_ms
265
+ );
266
+ return fragmentIndex;
267
+ }
268
+ });
269
+ this.transcriptionFragmentDataTask = new Task(this, {
77
270
  autoRun: EF_INTERACTIVE,
78
- args: () => [this.captionsPath(), this.fetch],
79
- task: async ([captionsPath, fetch], { signal }) => {
80
- const response = await fetch(captionsPath, { signal });
271
+ args: () => [
272
+ this.transcriptionDataTask.value,
273
+ this.fragmentIndexTask.value,
274
+ this.fetch
275
+ ],
276
+ task: async ([transcription, fragmentIndex, fetch], { signal }) => {
277
+ if (transcription === null || transcription === void 0 || fragmentIndex === null || fragmentIndex === void 0) {
278
+ return null;
279
+ }
280
+ const fragmentPath = this.transcriptionFragmentPath(
281
+ transcription.id,
282
+ fragmentIndex
283
+ );
284
+ const response = await fetch(fragmentPath, { signal });
81
285
  return response.json();
82
286
  }
83
287
  });
84
288
  this.frameTask = new Task(this, {
85
289
  autoRun: EF_INTERACTIVE,
86
- args: () => [this.captionsDataTask.status],
290
+ args: () => [this.transcriptionFragmentDataTask.status],
87
291
  task: async () => {
88
- await this.captionsDataTask.taskComplete;
292
+ await this.transcriptionFragmentDataTask.taskComplete;
89
293
  }
90
294
  });
91
295
  }
92
296
  set target(value) {
93
297
  this.targetSelector = value;
94
298
  }
299
+ render() {
300
+ return html`<slot></slot>`;
301
+ }
302
+ transcriptionsPath() {
303
+ if (this.targetElement.assetId) {
304
+ if (EF_RENDERING()) {
305
+ return `editframe://api/v1/isobmff_files/${this.targetElement.assetId}/transcription`;
306
+ }
307
+ return `${this.apiHost}/api/v1/isobmff_files/${this.targetElement.assetId}/transcription`;
308
+ }
309
+ return null;
310
+ }
95
311
  captionsPath() {
96
312
  if (this.targetElement.assetId) {
97
313
  if (EF_RENDERING()) {
@@ -102,45 +318,66 @@ let EFCaptions = class extends EFSourceMixin(
102
318
  const targetSrc = this.targetElement.src;
103
319
  return `/@ef-captions/${targetSrc}`;
104
320
  }
321
+ transcriptionFragmentPath(transcriptionId, fragmentIndex) {
322
+ return `${this.apiHost}/api/v1/transcriptions/${transcriptionId}/fragments/${fragmentIndex}`;
323
+ }
105
324
  connectedCallback() {
106
325
  super.connectedCallback();
107
326
  if (this.targetElement) {
108
327
  new CrossUpdateController(this.targetElement, this);
109
328
  }
110
329
  }
111
- render() {
112
- return this.captionsDataTask.render({
113
- pending: () => html`<div>Generating captions data...</div>`,
114
- error: () => html`<div>🚫 Error generating captions data</div>`,
115
- complete: () => html`<slot></slot>`
116
- });
117
- }
118
330
  updated(_changedProperties) {
119
- this.updateActiveWord();
331
+ this.updateTextContainers();
120
332
  }
121
- updateActiveWord() {
122
- const caption = this.captionsDataTask.value;
123
- if (!caption) {
333
+ updateTextContainers() {
334
+ const transcriptionFragment = this.transcriptionFragmentDataTask.value;
335
+ if (!transcriptionFragment) {
124
336
  return;
125
337
  }
126
- const words = [];
127
- let startMs = 0;
128
- let endMs = 0;
129
- for (const segment of caption.segments) {
130
- if (this.targetElement.trimAdjustedOwnCurrentTimeMs >= segment.start * 1e3 && this.targetElement.trimAdjustedOwnCurrentTimeMs <= segment.end * 1e3) {
131
- for (const word of segment.words) {
132
- if (this.targetElement.trimAdjustedOwnCurrentTimeMs >= word.start * 1e3 && this.targetElement.trimAdjustedOwnCurrentTimeMs <= word.end * 1e3) {
133
- words.push(word.text);
134
- startMs = word.start * 1e3;
135
- endMs = word.end * 1e3;
136
- }
137
- }
338
+ const currentTimeMs = this.targetElement.trimAdjustedOwnCurrentTimeMs;
339
+ const currentTimeSec = currentTimeMs / 1e3;
340
+ const currentWord = transcriptionFragment.word_segments.find(
341
+ (word) => currentTimeSec >= word.start && currentTimeSec <= word.end
342
+ );
343
+ const currentSegment = transcriptionFragment.segments.find(
344
+ (segment) => currentTimeSec >= segment.start && currentTimeSec <= segment.end
345
+ );
346
+ for (const wordContainer of this.activeWordContainers) {
347
+ if (currentWord) {
348
+ wordContainer.wordText = currentWord.text;
349
+ wordContainer.wordStartMs = currentWord.start * 1e3;
350
+ wordContainer.wordEndMs = currentWord.end * 1e3;
138
351
  }
139
352
  }
140
- for (const container of Array.from(this.activeWordContainers)) {
141
- container.wordText = words.join(" ");
142
- container.wordStartMs = startMs;
143
- container.wordEndMs = endMs;
353
+ for (const segmentContainer of this.segmentContainers) {
354
+ if (currentSegment) {
355
+ segmentContainer.segmentText = currentSegment.text;
356
+ segmentContainer.segmentStartMs = currentSegment.start * 1e3;
357
+ segmentContainer.segmentEndMs = currentSegment.end * 1e3;
358
+ }
359
+ }
360
+ if (currentWord && currentSegment) {
361
+ const segmentWords = transcriptionFragment.word_segments.filter(
362
+ (word) => word.start >= currentSegment.start && word.end <= currentSegment.end
363
+ );
364
+ const currentWordIndex = segmentWords.findIndex(
365
+ (word) => word.start === currentWord.start && word.end === currentWord.end
366
+ );
367
+ if (currentWordIndex !== -1) {
368
+ const beforeWords = segmentWords.slice(0, currentWordIndex).map((w) => w.text.trim()).join(" ");
369
+ const afterWords = segmentWords.slice(currentWordIndex + 1).map((w) => w.text.trim()).join(" ");
370
+ for (const container of this.beforeActiveWordContainers) {
371
+ container.segmentText = beforeWords;
372
+ container.segmentStartMs = currentSegment.start * 1e3;
373
+ container.segmentEndMs = currentWord.start * 1e3;
374
+ }
375
+ for (const container of this.afterActiveWordContainers) {
376
+ container.segmentText = afterWords;
377
+ container.segmentStartMs = currentWord.end * 1e3;
378
+ container.segmentEndMs = currentSegment.end * 1e3;
379
+ }
380
+ }
144
381
  }
145
382
  }
146
383
  get targetElement() {
@@ -154,10 +391,23 @@ let EFCaptions = class extends EFSourceMixin(
154
391
  EFCaptions.styles = [
155
392
  css`
156
393
  :host {
157
- display: block;
394
+ display: flex;
395
+ flex-wrap: wrap;
396
+ align-items: baseline;
397
+ width: fit-content;
398
+ }
399
+ ::slotted(*) {
400
+ margin: 0;
401
+ padding: 0;
158
402
  }
159
403
  `
160
404
  ];
405
+ __decorateClass([
406
+ property({ type: String, attribute: "display-mode", reflect: true })
407
+ ], EFCaptions.prototype, "displayMode", 2);
408
+ __decorateClass([
409
+ property({ type: Number, attribute: "context-words", reflect: true })
410
+ ], EFCaptions.prototype, "contextWords", 2);
161
411
  __decorateClass([
162
412
  property({ type: String, attribute: "target", reflect: true })
163
413
  ], EFCaptions.prototype, "targetSelector", 2);
@@ -169,5 +419,8 @@ EFCaptions = __decorateClass([
169
419
  ], EFCaptions);
170
420
  export {
171
421
  EFCaptions,
172
- EFCaptionsActiveWord
422
+ EFCaptionsActiveWord,
423
+ EFCaptionsAfterActiveWord,
424
+ EFCaptionsBeforeActiveWord,
425
+ EFCaptionsSegment
173
426
  };
@@ -2,10 +2,10 @@ import { Task } from "@lit/task";
2
2
  import { html, css, LitElement } from "lit";
3
3
  import { property, customElement } from "lit/decorators.js";
4
4
  import { createRef, ref } from "lit/directives/ref.js";
5
- import { FetchMixin } from "./FetchMixin.js";
6
- import { EFSourceMixin } from "./EFSourceMixin.js";
7
5
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
8
6
  import { EF_RENDERING } from "../EF_RENDERING.js";
7
+ import { EFSourceMixin } from "./EFSourceMixin.js";
8
+ import { FetchMixin } from "./FetchMixin.js";
9
9
  var __defProp = Object.defineProperty;
10
10
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
11
11
  var __typeError = (msg) => {
@@ -59,12 +59,6 @@ let EFImage = class extends EFSourceMixin(FetchMixin(LitElement), {
59
59
  });
60
60
  }
61
61
  set assetId(value) {
62
- if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
63
- console.error(`EFMedia ${value} is not valid asset-id`);
64
- throw new Error(
65
- "EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)"
66
- );
67
- }
68
62
  __privateSet(this, _assetId, value);
69
63
  }
70
64
  get assetId() {
@@ -90,11 +84,7 @@ EFImage.styles = [
90
84
  display: block;
91
85
  }
92
86
  canvas {
93
- display: block;
94
- width: 100%;
95
- height: 100%;
96
- object-fit: fill;
97
- object-position: center;
87
+ all: inherit
98
88
  }
99
89
  `
100
90
  ];
@@ -230,11 +230,6 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
230
230
  }
231
231
  #assetId;
232
232
  set assetId(value) {
233
- if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
234
- throw new Error(
235
- "EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)"
236
- );
237
- }
238
233
  this.#assetId = value;
239
234
  }
240
235
  get assetId() {
@@ -18,15 +18,6 @@ const timegroupContext = createContext(
18
18
  );
19
19
  const isEFTemporal = (obj) => obj[EF_TEMPORAL];
20
20
  const EF_TEMPORAL = Symbol("EF_TEMPORAL");
21
- const deepGetTemporalElements = (element, temporals = []) => {
22
- for (const child of element.children) {
23
- if (isEFTemporal(child)) {
24
- temporals.push(child);
25
- }
26
- deepGetTemporalElements(child, temporals);
27
- }
28
- return temporals;
29
- };
30
21
  const deepGetElementsWithFrameTasks = (element, elements = []) => {
31
22
  for (const child of element.children) {
32
23
  if ("frameTask" in child && child.frameTask instanceof Task) {
@@ -115,10 +106,20 @@ const EFTemporal = (superClass) => {
115
106
  get parentTimegroup() {
116
107
  return this.#parentTimegroup;
117
108
  }
109
+ set duration(value) {
110
+ if (value !== void 0) {
111
+ this.setAttribute("duration", value);
112
+ } else {
113
+ this.removeAttribute("duration");
114
+ }
115
+ }
118
116
  get trimStartMs() {
119
117
  return this._trimStartMs;
120
118
  }
121
119
  set trimStartMs(value) {
120
+ if (this._trimStartMs === value) {
121
+ return;
122
+ }
122
123
  this._trimStartMs = value;
123
124
  this.setAttribute(
124
125
  "trimstart",
@@ -136,6 +137,9 @@ const EFTemporal = (superClass) => {
136
137
  return this._trimEndMs;
137
138
  }
138
139
  set trimEndMs(value) {
140
+ if (this._trimEndMs === value) {
141
+ return;
142
+ }
139
143
  this._trimEndMs = value;
140
144
  this.setAttribute("trimend", durationConverter.toAttribute(value / 1e3));
141
145
  }
@@ -366,7 +370,6 @@ export {
366
370
  EFTemporal,
367
371
  OwnCurrentTimeController,
368
372
  deepGetElementsWithFrameTasks,
369
- deepGetTemporalElements,
370
373
  isEFTemporal,
371
374
  shallowGetTemporalElements,
372
375
  timegroupContext
@@ -1,12 +1,13 @@
1
- import { html, css, LitElement } from "lit";
2
1
  import { provide } from "@lit/context";
3
2
  import { Task } from "@lit/task";
4
- import { property, customElement } from "lit/decorators.js";
5
3
  import debug from "debug";
6
- import { EFTemporal, shallowGetTemporalElements, isEFTemporal, timegroupContext } from "./EFTemporal.js";
7
- import { TimegroupController } from "./TimegroupController.js";
4
+ import { html, css, LitElement } from "lit";
5
+ import { property, customElement } from "lit/decorators.js";
8
6
  import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
7
+ import { isContextMixin } from "../gui/ContextMixin.js";
9
8
  import { deepGetMediaElements } from "./EFMedia.js";
9
+ import { EFTemporal, shallowGetTemporalElements, isEFTemporal, timegroupContext } from "./EFTemporal.js";
10
+ import { TimegroupController } from "./TimegroupController.js";
10
11
  import { durationConverter } from "./durationConverter.js";
11
12
  var __defProp = Object.defineProperty;
12
13
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
@@ -138,10 +139,16 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
138
139
  throw new Error(`Invalid time mode: ${this.mode}`);
139
140
  }
140
141
  }
142
+ /**
143
+ * Wait for all media elements to load their initial segments.
144
+ * Ideally we would only need the extracted index json data, but
145
+ * that caused issues with constructing audio data. We had negative durations
146
+ * in calculations and it was not clear why.
147
+ */
141
148
  async waitForMediaDurations() {
142
149
  return await Promise.all(
143
150
  deepGetMediaElements(this).map(
144
- (media) => media.trackFragmentIndexLoader.taskComplete
151
+ (media) => media.initSegmentsLoader.taskComplete
145
152
  )
146
153
  );
147
154
  }
@@ -212,8 +219,28 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
212
219
  }
213
220
  }
214
221
  }
222
+ get contextProvider() {
223
+ let parent = this.parentNode;
224
+ while (parent) {
225
+ if (isContextMixin(parent)) {
226
+ return parent;
227
+ }
228
+ parent = parent.parentNode;
229
+ }
230
+ return null;
231
+ }
232
+ /**
233
+ * Returns true if the timegroup should be wrapped with a workbench.
234
+ *
235
+ * A timegroup should be wrapped with a workbench if it is the root-most timegroup
236
+ * and EF_INTERACTIVE is true.
237
+ *
238
+ * If the timegroup is already wrappedin a context provider like ef-preview,
239
+ * it should NOT be wrapped in a workbench.
240
+ *
241
+ */
215
242
  shouldWrapWithWorkbench() {
216
- return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-workbench") === null && this.closest("ef-preview") === null;
243
+ return EF_INTERACTIVE && this.closest("ef-timegroup") === this && this.closest("ef-preview") === null && this.closest("ef-workbench") === null && this.closest("test-context") === null;
217
244
  }
218
245
  wrapWithWorkbench() {
219
246
  const workbench = document.createElement("ef-workbench");
@@ -270,10 +297,8 @@ _currentTime = /* @__PURE__ */ new WeakMap();
270
297
  _EFTimegroup_instances = /* @__PURE__ */ new WeakSet();
271
298
  addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
272
299
  await this.waitForMediaDurations();
273
- const durationMs = toMs - fromMs;
274
300
  await Promise.all(
275
301
  deepGetMediaElements(this).map(async (mediaElement) => {
276
- await mediaElement.trackFragmentIndexLoader.taskComplete;
277
302
  const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
278
303
  const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
279
304
  const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
@@ -284,15 +309,15 @@ addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
284
309
  if (!audio) {
285
310
  throw new Error("Failed to fetch audio");
286
311
  }
287
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
288
- const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
289
- const ctxDurationMs = ctxEndMs - ctxStartMs;
290
- const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
291
312
  const bufferSource = audioContext.createBufferSource();
292
313
  bufferSource.buffer = await audioContext.decodeAudioData(
293
314
  await audio.blob.arrayBuffer()
294
315
  );
295
316
  bufferSource.connect(audioContext.destination);
317
+ const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
318
+ const ctxEndMs = mediaElement.endTimeMs - fromMs;
319
+ const ctxDurationMs = ctxEndMs - ctxStartMs;
320
+ const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
296
321
  bufferSource.start(
297
322
  ctxStartMs / 1e3,
298
323
  offset / 1e3,
@@ -1,9 +1,9 @@
1
- import { css, html } from "lit";
2
1
  import { Task } from "@lit/task";
3
- import { createRef, ref } from "lit/directives/ref.js";
2
+ import { css, html } from "lit";
4
3
  import { customElement } from "lit/decorators.js";
5
- import { EFMedia } from "./EFMedia.js";
4
+ import { createRef, ref } from "lit/directives/ref.js";
6
5
  import { TWMixin } from "../gui/TWMixin.js";
6
+ import { EFMedia } from "./EFMedia.js";
7
7
  var __defProp = Object.defineProperty;
8
8
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
9
9
  var __typeError = (msg) => {
@@ -85,10 +85,7 @@ let EFVideo = class extends TWMixin(EFMedia) {
85
85
  });
86
86
  }
87
87
  render() {
88
- return html` <canvas
89
- class="h-full w-full object-fill"
90
- ${ref(this.canvasRef)}
91
- ></canvas>`;
88
+ return html` <canvas ${ref(this.canvasRef)}></canvas>`;
92
89
  }
93
90
  get canvasElement() {
94
91
  return this.canvasRef.value;
@@ -100,6 +97,9 @@ EFVideo.styles = [
100
97
  :host {
101
98
  display: block;
102
99
  }
100
+ canvas {
101
+ all: inherit;
102
+ }
103
103
  `
104
104
  ];
105
105
  EFVideo = __decorateClass([