@editframe/elements 0.11.0-beta.9 → 0.12.0-beta.10

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 (51) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +8 -15
  2. package/dist/assets/src/MP4File.js +73 -20
  3. package/dist/elements/EFCaptions.d.ts +50 -6
  4. package/dist/elements/EFMedia.d.ts +1 -2
  5. package/dist/elements/EFTimegroup.browsertest.d.ts +4 -0
  6. package/dist/elements/EFTimegroup.d.ts +23 -2
  7. package/dist/elements/EFWaveform.d.ts +15 -11
  8. package/dist/elements/src/EF_FRAMEGEN.js +24 -26
  9. package/dist/elements/src/elements/EFCaptions.js +295 -42
  10. package/dist/elements/src/elements/EFImage.js +0 -6
  11. package/dist/elements/src/elements/EFMedia.js +70 -18
  12. package/dist/elements/src/elements/EFTemporal.js +13 -10
  13. package/dist/elements/src/elements/EFTimegroup.js +37 -12
  14. package/dist/elements/src/elements/EFVideo.js +1 -4
  15. package/dist/elements/src/elements/EFWaveform.js +250 -143
  16. package/dist/elements/src/gui/ContextMixin.js +44 -11
  17. package/dist/elements/src/gui/EFPreview.js +3 -1
  18. package/dist/elements/src/gui/EFScrubber.js +142 -0
  19. package/dist/elements/src/gui/EFTimeDisplay.js +81 -0
  20. package/dist/elements/src/gui/EFTogglePlay.js +11 -19
  21. package/dist/elements/src/gui/EFWorkbench.js +1 -24
  22. package/dist/elements/src/gui/TWMixin.css.js +1 -1
  23. package/dist/elements/src/index.js +8 -1
  24. package/dist/gui/ContextMixin.d.ts +2 -1
  25. package/dist/gui/EFScrubber.d.ts +23 -0
  26. package/dist/gui/EFTimeDisplay.d.ts +17 -0
  27. package/dist/gui/EFTogglePlay.d.ts +0 -2
  28. package/dist/gui/EFWorkbench.d.ts +0 -1
  29. package/dist/index.d.ts +3 -1
  30. package/dist/style.css +6 -801
  31. package/package.json +2 -2
  32. package/src/elements/EFCaptions.browsertest.ts +6 -6
  33. package/src/elements/EFCaptions.ts +325 -56
  34. package/src/elements/EFImage.browsertest.ts +4 -17
  35. package/src/elements/EFImage.ts +0 -6
  36. package/src/elements/EFMedia.browsertest.ts +10 -19
  37. package/src/elements/EFMedia.ts +87 -20
  38. package/src/elements/EFTemporal.browsertest.ts +14 -0
  39. package/src/elements/EFTemporal.ts +14 -0
  40. package/src/elements/EFTimegroup.browsertest.ts +37 -0
  41. package/src/elements/EFTimegroup.ts +42 -17
  42. package/src/elements/EFVideo.ts +1 -4
  43. package/src/elements/EFWaveform.ts +339 -314
  44. package/src/gui/ContextMixin.browsertest.ts +28 -2
  45. package/src/gui/ContextMixin.ts +52 -14
  46. package/src/gui/EFPreview.ts +4 -2
  47. package/src/gui/EFScrubber.ts +145 -0
  48. package/src/gui/EFTimeDisplay.ts +81 -0
  49. package/src/gui/EFTogglePlay.ts +19 -25
  50. package/src/gui/EFWorkbench.ts +3 -36
  51. package/dist/elements/src/elements/util.js +0 -11
@@ -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
  };
@@ -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() {
@@ -10,9 +10,8 @@ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
10
10
  import { EF_RENDERING } from "../EF_RENDERING.js";
11
11
  import { apiHostContext } from "../gui/apiHostContext.js";
12
12
  import { EFSourceMixin } from "./EFSourceMixin.js";
13
- import { EFTemporal } from "./EFTemporal.js";
13
+ import { EFTemporal, isEFTemporal } from "./EFTemporal.js";
14
14
  import { FetchMixin } from "./FetchMixin.js";
15
- import { getStartTimeMs } from "./util.js";
16
15
  var __defProp = Object.defineProperty;
17
16
  var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
18
17
  var __decorateClass = (decorators, target, key, kind) => {
@@ -62,10 +61,10 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
62
61
  return await Promise.all(
63
62
  Object.entries(fragmentIndex).map(async ([trackId, track]) => {
64
63
  const start = track.initSegment.offset;
65
- const end = track.initSegment.offset + track.initSegment.size - 1;
64
+ const end = track.initSegment.offset + track.initSegment.size;
66
65
  const response = await fetch(this.fragmentTrackPath(trackId), {
67
66
  signal,
68
- headers: { Range: `bytes=${start}-${end}` }
67
+ headers: { Range: `bytes=${start}-${end - 1}` }
69
68
  });
70
69
  const buffer = await response.arrayBuffer();
71
70
  buffer.fileStart = 0;
@@ -131,14 +130,14 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
131
130
  const end = segment.offset + segment.size;
132
131
  const response = await fetch(this.fragmentTrackPath(trackId), {
133
132
  signal,
134
- headers: { Range: `bytes=${start}-${end}` }
133
+ headers: { Range: `bytes=${start}-${end - 1}` }
135
134
  });
136
135
  if (nextSegment) {
137
136
  const nextStart = nextSegment.offset;
138
137
  const nextEnd = nextSegment.offset + nextSegment.size;
139
138
  fetch(this.fragmentTrackPath(trackId), {
140
139
  signal,
141
- headers: { Range: `bytes=${nextStart}-${nextEnd}` }
140
+ headers: { Range: `bytes=${nextStart}-${nextEnd - 1}` }
142
141
  }).then(() => {
143
142
  log("Prefetched next segment");
144
143
  }).catch((error) => {
@@ -230,11 +229,6 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
230
229
  }
231
230
  #assetId;
232
231
  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
232
  this.#assetId = value;
239
233
  }
240
234
  get assetId() {
@@ -275,6 +269,67 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
275
269
  if (changedProperties.has("ownCurrentTimeMs")) {
276
270
  this.executeSeek(this.trimAdjustedOwnCurrentTimeMs);
277
271
  }
272
+ if (changedProperties.has("currentTime") || changedProperties.has("ownCurrentTimeMs")) {
273
+ const timelineTimeMs = (this.rootTimegroup ?? this).currentTimeMs;
274
+ if (this.startTimeMs > timelineTimeMs || this.endTimeMs < timelineTimeMs) {
275
+ this.style.display = "none";
276
+ return;
277
+ }
278
+ this.style.display = "";
279
+ const animations = this.getAnimations({ subtree: true });
280
+ this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
281
+ this.style.setProperty(
282
+ "--ef-transition--duration",
283
+ `${this.parentTimegroup?.overlapMs ?? 0}ms`
284
+ );
285
+ this.style.setProperty(
286
+ "--ef-transition-out-start",
287
+ `${this.durationMs - (this.parentTimegroup?.overlapMs ?? 0)}ms`
288
+ );
289
+ for (const animation of animations) {
290
+ if (animation.playState === "running") {
291
+ animation.pause();
292
+ }
293
+ const effect = animation.effect;
294
+ if (!(effect && effect instanceof KeyframeEffect)) {
295
+ return;
296
+ }
297
+ const target = effect.target;
298
+ if (!target) {
299
+ return;
300
+ }
301
+ if (target.closest("ef-video, ef-audio") !== this) {
302
+ return;
303
+ }
304
+ if (isEFTemporal(target)) {
305
+ const timing = effect.getTiming();
306
+ const duration = Number(timing.duration) ?? 0;
307
+ const delay = Number(timing.delay);
308
+ const newTime = Math.floor(
309
+ Math.min(target.ownCurrentTimeMs, duration - 1 + delay)
310
+ );
311
+ if (Number.isNaN(newTime)) {
312
+ return;
313
+ }
314
+ animation.currentTime = newTime;
315
+ } else if (target) {
316
+ const nearestTimegroup = target.closest("ef-timegroup");
317
+ if (!nearestTimegroup) {
318
+ return;
319
+ }
320
+ const timing = effect.getTiming();
321
+ const duration = Number(timing.duration) ?? 0;
322
+ const delay = Number(timing.delay);
323
+ const newTime = Math.floor(
324
+ Math.min(nearestTimegroup.ownCurrentTimeMs, duration - 1 + delay)
325
+ );
326
+ if (Number.isNaN(newTime)) {
327
+ return;
328
+ }
329
+ animation.currentTime = newTime;
330
+ }
331
+ }
332
+ }
278
333
  }
279
334
  get hasOwnDuration() {
280
335
  return true;
@@ -305,9 +360,6 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
305
360
  }
306
361
  return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
307
362
  }
308
- get startTimeMs() {
309
- return getStartTimeMs(this);
310
- }
311
363
  #audioContext;
312
364
  async fetchAudioSpanningTime(fromMs, toMs) {
313
365
  if (this.sourceInMs) {
@@ -330,11 +382,11 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
330
382
  return;
331
383
  }
332
384
  const start = audioTrackIndex.initSegment.offset;
333
- const end = audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size - 1;
385
+ const end = audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size;
334
386
  const audioInitFragmentRequest = this.fetch(
335
387
  this.fragmentTrackPath(String(audioTrackId)),
336
388
  {
337
- headers: { Range: `bytes=${start}-${end}` }
389
+ headers: { Range: `bytes=${start}-${end - 1}` }
338
390
  }
339
391
  );
340
392
  const fragments = Object.values(audioTrackIndex.segments).filter(
@@ -355,11 +407,11 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
355
407
  return;
356
408
  }
357
409
  const fragmentStart = firstFragment.offset;
358
- const fragmentEnd = lastFragment.offset + lastFragment.size - 1;
410
+ const fragmentEnd = lastFragment.offset + lastFragment.size;
359
411
  const audioFragmentRequest = this.fetch(
360
412
  this.fragmentTrackPath(String(audioTrackId)),
361
413
  {
362
- headers: { Range: `bytes=${fragmentStart}-${fragmentEnd}` }
414
+ headers: { Range: `bytes=${fragmentStart}-${fragmentEnd - 1}` }
363
415
  }
364
416
  );
365
417
  const initResponse = await audioInitFragmentRequest;
@@ -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