@editframe/elements 0.7.0-beta.9 → 0.8.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 (97) hide show
  1. package/dist/EF_FRAMEGEN.d.ts +44 -0
  2. package/dist/EF_INTERACTIVE.d.ts +1 -0
  3. package/dist/assets/dist/EncodedAsset.js +560 -0
  4. package/dist/assets/dist/MP4File.js +170 -0
  5. package/dist/assets/dist/memoize.js +14 -0
  6. package/dist/elements/CrossUpdateController.d.ts +9 -0
  7. package/dist/elements/EFAudio.d.ts +10 -0
  8. package/dist/elements/EFCaptions.d.ts +38 -0
  9. package/dist/elements/EFImage.d.ts +14 -0
  10. package/dist/elements/EFMedia.d.ts +61 -0
  11. package/dist/elements/EFSourceMixin.d.ts +12 -0
  12. package/dist/elements/EFTemporal.d.ts +38 -0
  13. package/dist/elements/EFTimegroup.browsertest.d.ts +12 -0
  14. package/dist/elements/EFTimegroup.d.ts +39 -0
  15. package/dist/elements/EFVideo.d.ts +14 -0
  16. package/dist/elements/EFWaveform.d.ts +30 -0
  17. package/dist/elements/FetchMixin.d.ts +8 -0
  18. package/dist/elements/TimegroupController.d.ts +14 -0
  19. package/dist/elements/durationConverter.d.ts +4 -0
  20. package/dist/elements/parseTimeToMs.d.ts +1 -0
  21. package/{src/EF_FRAMEGEN.ts → dist/elements/src/EF_FRAMEGEN.js} +35 -115
  22. package/dist/elements/src/EF_INTERACTIVE.js +7 -0
  23. package/dist/elements/src/elements/CrossUpdateController.js +16 -0
  24. package/dist/elements/src/elements/EFAudio.js +54 -0
  25. package/dist/elements/src/elements/EFCaptions.js +166 -0
  26. package/dist/elements/src/elements/EFImage.js +80 -0
  27. package/dist/elements/src/elements/EFMedia.js +339 -0
  28. package/dist/elements/src/elements/EFSourceMixin.js +55 -0
  29. package/dist/elements/src/elements/EFTemporal.js +234 -0
  30. package/dist/elements/src/elements/EFTimegroup.js +355 -0
  31. package/dist/elements/src/elements/EFVideo.js +110 -0
  32. package/dist/elements/src/elements/EFWaveform.js +226 -0
  33. package/dist/elements/src/elements/FetchMixin.js +28 -0
  34. package/dist/elements/src/elements/TimegroupController.js +20 -0
  35. package/dist/elements/src/elements/durationConverter.js +8 -0
  36. package/dist/elements/src/elements/parseTimeToMs.js +12 -0
  37. package/dist/elements/src/elements/util.js +11 -0
  38. package/dist/elements/src/gui/ContextMixin.js +234 -0
  39. package/dist/elements/src/gui/EFFilmstrip.js +729 -0
  40. package/dist/elements/src/gui/EFPreview.js +45 -0
  41. package/dist/elements/src/gui/EFWorkbench.js +128 -0
  42. package/dist/elements/src/gui/TWMixin.css.js +4 -0
  43. package/dist/elements/src/gui/TWMixin.js +36 -0
  44. package/dist/elements/src/gui/apiHostContext.js +5 -0
  45. package/dist/elements/src/gui/fetchContext.js +5 -0
  46. package/dist/elements/src/gui/focusContext.js +5 -0
  47. package/dist/elements/src/gui/focusedElementContext.js +7 -0
  48. package/dist/elements/src/gui/playingContext.js +5 -0
  49. package/dist/elements/src/index.js +27 -0
  50. package/dist/elements/src/msToTimeCode.js +15 -0
  51. package/dist/elements/util.d.ts +4 -0
  52. package/dist/gui/ContextMixin.d.ts +23 -0
  53. package/dist/gui/EFFilmstrip.d.ts +144 -0
  54. package/dist/gui/EFPreview.d.ts +27 -0
  55. package/dist/gui/EFWorkbench.d.ts +34 -0
  56. package/dist/gui/TWMixin.d.ts +3 -0
  57. package/dist/gui/apiHostContext.d.ts +3 -0
  58. package/dist/gui/fetchContext.d.ts +3 -0
  59. package/dist/gui/focusContext.d.ts +6 -0
  60. package/dist/gui/focusedElementContext.d.ts +3 -0
  61. package/dist/gui/playingContext.d.ts +3 -0
  62. package/dist/index.d.ts +11 -0
  63. package/dist/msToTimeCode.d.ts +1 -0
  64. package/dist/style.css +800 -0
  65. package/package.json +6 -9
  66. package/src/elements/EFAudio.ts +1 -1
  67. package/src/elements/EFCaptions.ts +9 -9
  68. package/src/elements/EFImage.ts +3 -3
  69. package/src/elements/EFMedia.ts +11 -8
  70. package/src/elements/EFSourceMixin.ts +1 -1
  71. package/src/elements/EFTemporal.ts +42 -5
  72. package/src/elements/EFTimegroup.browsertest.ts +3 -3
  73. package/src/elements/EFTimegroup.ts +9 -6
  74. package/src/elements/EFVideo.ts +2 -2
  75. package/src/elements/EFWaveform.ts +6 -6
  76. package/src/elements/FetchMixin.ts +5 -3
  77. package/src/elements/TimegroupController.ts +1 -1
  78. package/src/elements/durationConverter.ts +1 -1
  79. package/src/elements/util.ts +1 -1
  80. package/src/gui/ContextMixin.ts +254 -0
  81. package/src/gui/EFFilmstrip.ts +41 -150
  82. package/src/gui/EFPreview.ts +39 -0
  83. package/src/gui/EFWorkbench.ts +7 -105
  84. package/src/gui/TWMixin.ts +10 -3
  85. package/src/gui/apiHostContext.ts +3 -0
  86. package/src/gui/fetchContext.ts +5 -0
  87. package/src/gui/focusContext.ts +7 -0
  88. package/src/gui/focusedElementContext.ts +5 -0
  89. package/src/gui/playingContext.ts +3 -0
  90. package/CHANGELOG.md +0 -7
  91. package/postcss.config.cjs +0 -12
  92. package/src/EF_INTERACTIVE.ts +0 -2
  93. package/src/elements.css +0 -22
  94. package/src/index.ts +0 -33
  95. package/tailwind.config.ts +0 -10
  96. package/tsconfig.json +0 -4
  97. package/vite.config.ts +0 -8
@@ -1,49 +1,9 @@
1
1
  import debug from "debug";
2
2
  import { TaskStatus } from "@lit/task";
3
-
4
- import type { VideoRenderOptions } from "@editframe/assets";
5
- import { awaitMicrotask } from "@/util/awaitMicrotask";
6
-
7
- import { deepGetElementsWithFrameTasks } from "./elements/EFTemporal";
8
- import { shallowGetTimegroups } from "./elements/EFTimegroup";
9
-
3
+ import { deepGetElementsWithFrameTasks } from "./elements/EFTemporal.js";
4
+ import { shallowGetTimegroups } from "./elements/EFTimegroup.js";
10
5
  const log = debug("ef:elements:EF_FRAMEGEN");
11
-
12
- declare global {
13
- interface Window {
14
- EF_FRAMEGEN?: EfFramegen;
15
- FRAMEGEN_BRIDGE?: {
16
- onInitialize: (
17
- callback: (renderId: string, renderOptions: VideoRenderOptions) => void,
18
- ) => void;
19
-
20
- initialized(renderId: string): void;
21
-
22
- onBeginFrame(
23
- callback: (
24
- renderId: string,
25
- frameNumber: number,
26
- isLast: boolean,
27
- ) => void,
28
- ): void;
29
-
30
- onTriggerCanvas(callback: () => void): void;
31
-
32
- frameReady(
33
- renderId: string,
34
- frameNumber: number,
35
- audioSamples: ArrayBuffer,
36
- ): void;
37
-
38
- error(renderId: string, error: Error): void;
39
- };
40
- }
41
- }
42
-
43
6
  class TriggerCanvas {
44
- private canvas: HTMLCanvasElement;
45
- private ctx: CanvasRenderingContext2D;
46
-
47
7
  constructor() {
48
8
  this.canvas = document.createElement("canvas");
49
9
  this.canvas.width = 1;
@@ -54,7 +14,7 @@ class TriggerCanvas {
54
14
  left: "0px",
55
15
  width: "1px",
56
16
  height: "1px",
57
- zIndex: "100000",
17
+ zIndex: "100000"
58
18
  });
59
19
  document.body.prepend(this.canvas);
60
20
  const ctx = this.canvas.getContext("2d", { willReadFrequently: true });
@@ -62,70 +22,54 @@ class TriggerCanvas {
62
22
  this.ctx = ctx;
63
23
  this.ctx.fillStyle = "black";
64
24
  }
65
-
66
25
  trigger() {
67
26
  log("TRIGGERING CANVAS");
68
27
  this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
69
28
  }
70
29
  }
71
-
72
- export class EfFramegen {
73
- time = 0;
74
- frameDurationMs = 0;
75
- initialBusyTasks: Promise<unknown[]> = Promise.resolve([]);
76
- audioBufferPromise?: Promise<AudioBuffer>;
77
- renderOptions?: VideoRenderOptions;
78
- frameBox = document.createElement("div");
79
- BRIDGE = window.FRAMEGEN_BRIDGE;
80
- triggerCanvas = new TriggerCanvas();
81
-
82
- trace(...args: any[]) {
83
- console.trace("[EF_FRAMEGEN]", ...args);
84
- }
85
-
30
+ class EfFramegen {
86
31
  constructor() {
32
+ this.time = 0;
33
+ this.frameDurationMs = 0;
34
+ this.initialBusyTasks = Promise.resolve([]);
35
+ this.frameBox = document.createElement("div");
36
+ this.BRIDGE = window.FRAMEGEN_BRIDGE;
37
+ this.triggerCanvas = new TriggerCanvas();
87
38
  if (this.BRIDGE) {
88
39
  this.connectToBridge();
89
40
  }
90
41
  }
91
-
42
+ trace(...args) {
43
+ console.trace("[EF_FRAMEGEN]", ...args);
44
+ }
92
45
  connectToBridge() {
93
46
  const BRIDGE = this.BRIDGE;
94
47
  if (!BRIDGE) {
95
48
  throw new Error("No BRIDGE when attempting to connect to bridge");
96
49
  }
97
-
98
50
  BRIDGE.onInitialize(async (renderId, renderOptions) => {
99
51
  log("BRIDGE.onInitialize", renderId, renderOptions);
100
52
  await this.initialize(renderId, renderOptions);
101
53
  BRIDGE.initialized(renderId);
102
54
  });
103
-
104
55
  BRIDGE.onBeginFrame((renderId, frameNumber, isLast) => {
105
56
  log("BRIDGE.onBeginFrame", renderId, frameNumber, isLast);
106
57
  this.beginFrame(renderId, frameNumber, isLast);
107
58
  });
108
-
109
- // BRIDGE.onTriggerCanvas(() => {
110
- // // this.triggerCanvas.trigger();
111
- // });
112
59
  }
113
-
114
- async initialize(renderId: string, renderOptions: VideoRenderOptions) {
60
+ async initialize(renderId, renderOptions) {
115
61
  addEventListener("unhandledrejection", (event) => {
116
62
  this.trace("Unhandled rejection:", event.reason);
117
63
  if (this.BRIDGE) {
118
64
  this.BRIDGE.error(renderId, event.reason);
119
65
  }
120
66
  });
121
-
122
67
  addEventListener("error", (event) => {
123
68
  this.trace("Uncaught error", event.error);
124
69
  if (this.BRIDGE) {
125
70
  this.BRIDGE.error(renderId, event.error);
126
71
  }
127
72
  });
128
-
129
73
  this.renderOptions = renderOptions;
130
74
  const workbench = document.querySelector("ef-workbench");
131
75
  if (!workbench) {
@@ -135,21 +79,14 @@ export class EfFramegen {
135
79
  const timegroups = shallowGetTimegroups(workbench);
136
80
  const temporals = deepGetElementsWithFrameTasks(workbench);
137
81
  const firstGroup = timegroups[0];
138
-
139
82
  if (!firstGroup) {
140
83
  throw new Error("No temporal elements found");
141
84
  }
142
85
  firstGroup.currentTimeMs = renderOptions.encoderOptions.fromMs;
143
-
144
- this.frameDurationMs = 1000 / renderOptions.encoderOptions.video.framerate;
145
-
86
+ this.frameDurationMs = 1e3 / renderOptions.encoderOptions.video.framerate;
146
87
  this.initialBusyTasks = Promise.all(
147
- temporals
148
- .filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE)
149
- .map((temporal) => temporal.frameTask)
150
- .map((task) => task.taskComplete),
88
+ temporals.filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE).map((temporal) => temporal.frameTask).map((task) => task.taskComplete)
151
89
  );
152
-
153
90
  this.time = 0;
154
91
  if (renderOptions.showFrameBox) {
155
92
  Object.assign(this.frameBox.style, {
@@ -160,21 +97,20 @@ export class EfFramegen {
160
97
  position: "absolute",
161
98
  top: "0px",
162
99
  left: "0px",
163
- zIndex: "100000",
100
+ zIndex: "100000"
164
101
  });
165
102
  document.body.prepend(this.frameBox);
166
103
  }
167
-
168
104
  this.audioBufferPromise = firstGroup.renderAudio(
169
- renderOptions.encoderOptions.alignedFromUs / 1000,
170
- renderOptions.encoderOptions.alignedToUs / 1000,
105
+ renderOptions.encoderOptions.alignedFromUs / 1e3,
106
+ renderOptions.encoderOptions.alignedToUs / 1e3
171
107
  // renderOptions.encoderOptions.fromMs,
172
108
  // renderOptions.encoderOptions.toMs,
173
109
  );
174
110
  log("Initialized");
175
111
  }
176
- async beginFrame(renderId: string, frameNumber: number, isLast: boolean) {
177
- if (this.renderOptions === undefined) {
112
+ async beginFrame(renderId, frameNumber, isLast) {
113
+ if (this.renderOptions === void 0) {
178
114
  throw new Error("No renderOptions");
179
115
  }
180
116
  if (this.renderOptions.showFrameBox) {
@@ -194,60 +130,40 @@ export class EfFramegen {
194
130
  if (!firstGroup) {
195
131
  throw new Error("No temporal elements found");
196
132
  }
197
-
198
- this.time =
199
- this.renderOptions.encoderOptions.fromMs +
200
- frameNumber * this.frameDurationMs;
201
-
133
+ this.time = this.renderOptions.encoderOptions.fromMs + frameNumber * this.frameDurationMs;
202
134
  firstGroup.currentTimeMs = this.time;
203
-
204
135
  log("Awaiting initialBusyTasks");
205
136
  await this.initialBusyTasks;
206
-
207
137
  log("Awaiting microtask");
208
- await awaitMicrotask();
209
-
138
+ await new Promise(queueMicrotask);
210
139
  log("Awaiting frame tasks");
211
140
  const now = performance.now();
212
141
  await Promise.all(
213
- temporals
214
- .filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE)
215
- .map((temporal) => {
216
- return temporal.frameTask;
217
- })
218
- .map((task) => task.taskComplete),
142
+ temporals.filter((temporal) => temporal.frameTask.status < TaskStatus.COMPLETE).map((temporal) => {
143
+ return temporal.frameTask;
144
+ }).map((task) => task.taskComplete)
219
145
  );
220
-
221
146
  log(`frame:${frameNumber} All tasks complete ${performance.now() - now}ms`);
222
-
223
147
  if (isLast && this.audioBufferPromise) {
224
- // Currently we emit the audio in one belch at the end of the render.
225
- // This is not ideal, but it's the simplest thing that could possibly work.
226
- // We could either emit it slices, or in parallel with the video.
227
- // But in any case, it's fine for now.
228
148
  const renderedAudio = await this.audioBufferPromise;
229
-
230
149
  const channelCount = renderedAudio.numberOfChannels;
231
-
232
150
  const interleavedSamples = new Float32Array(
233
- channelCount * renderedAudio.length,
151
+ channelCount * renderedAudio.length
234
152
  );
235
-
236
153
  for (let i = 0; i < renderedAudio.length; i++) {
237
154
  for (let j = 0; j < channelCount; j++) {
238
155
  interleavedSamples.set(
239
156
  renderedAudio.getChannelData(j).slice(i, i + 1),
240
- i * channelCount + j,
157
+ i * channelCount + j
241
158
  );
242
159
  }
243
160
  }
244
-
245
161
  if (this.BRIDGE) {
246
162
  this.triggerCanvas.trigger();
247
163
  this.BRIDGE.frameReady(
248
164
  renderId,
249
165
  frameNumber,
250
- interleavedSamples.buffer,
166
+ interleavedSamples.buffer
251
167
  );
252
168
  } else {
253
169
  const fileReader = new FileReader();
@@ -274,5 +190,9 @@ export class EfFramegen {
274
190
  }
275
191
  }
276
192
  }
277
-
278
- window.EF_FRAMEGEN = new EfFramegen();
193
+ if (typeof window !== "undefined") {
194
+ window.EF_FRAMEGEN = new EfFramegen();
195
+ }
196
+ export {
197
+ EfFramegen
198
+ };
@@ -0,0 +1,7 @@
1
+ let EF_INTERACTIVE = false;
2
+ if (typeof window !== "undefined") {
3
+ EF_INTERACTIVE = !window.location?.search.includes("EF_NONINTERACTIVE");
4
+ }
5
+ export {
6
+ EF_INTERACTIVE
7
+ };
@@ -0,0 +1,16 @@
1
+ class CrossUpdateController {
2
+ constructor(host, target) {
3
+ this.host = host;
4
+ this.target = target;
5
+ this.host.addController(this);
6
+ }
7
+ hostUpdate() {
8
+ this.target.requestUpdate();
9
+ }
10
+ remove() {
11
+ this.host.removeController(this);
12
+ }
13
+ }
14
+ export {
15
+ CrossUpdateController
16
+ };
@@ -0,0 +1,54 @@
1
+ import { html } from "lit";
2
+ import { createRef, ref } from "lit/directives/ref.js";
3
+ import { property, customElement } from "lit/decorators.js";
4
+ import { EFMedia } from "./EFMedia.js";
5
+ import { Task } from "@lit/task";
6
+ var __defProp = Object.defineProperty;
7
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
8
+ var __decorateClass = (decorators, target, key, kind) => {
9
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
10
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
11
+ if (decorator = decorators[i])
12
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
13
+ if (kind && result) __defProp(target, key, result);
14
+ return result;
15
+ };
16
+ let EFAudio = class extends EFMedia {
17
+ constructor() {
18
+ super(...arguments);
19
+ this.audioElementRef = createRef();
20
+ this.src = "";
21
+ this.frameTask = new Task(this, {
22
+ args: () => [
23
+ this.trackFragmentIndexLoader.status,
24
+ this.initSegmentsLoader.status,
25
+ this.seekTask.status,
26
+ this.fetchSeekTask.status,
27
+ this.videoAssetTask.status
28
+ ],
29
+ task: async () => {
30
+ await this.trackFragmentIndexLoader.taskComplete;
31
+ await this.initSegmentsLoader.taskComplete;
32
+ await this.seekTask.taskComplete;
33
+ await this.fetchSeekTask.taskComplete;
34
+ await this.videoAssetTask.taskComplete;
35
+ this.rootTimegroup?.requestUpdate();
36
+ }
37
+ });
38
+ }
39
+ render() {
40
+ return html`<audio ${ref(this.audioElementRef)}></audio>`;
41
+ }
42
+ get audioElement() {
43
+ return this.audioElementRef.value;
44
+ }
45
+ };
46
+ __decorateClass([
47
+ property({ type: String })
48
+ ], EFAudio.prototype, "src", 2);
49
+ EFAudio = __decorateClass([
50
+ customElement("ef-audio")
51
+ ], EFAudio);
52
+ export {
53
+ EFAudio
54
+ };
@@ -0,0 +1,166 @@
1
+ import { html, css, LitElement } from "lit";
2
+ import { Task } from "@lit/task";
3
+ import { property, customElement } from "lit/decorators.js";
4
+ import { EFVideo } from "./EFVideo.js";
5
+ import { EFAudio } from "./EFAudio.js";
6
+ import { EFTemporal } from "./EFTemporal.js";
7
+ import { CrossUpdateController } from "./CrossUpdateController.js";
8
+ import { FetchMixin } from "./FetchMixin.js";
9
+ import { EFSourceMixin } from "./EFSourceMixin.js";
10
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
11
+ var __defProp = Object.defineProperty;
12
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
13
+ var __decorateClass = (decorators, target, key, kind) => {
14
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
15
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
16
+ if (decorator = decorators[i])
17
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
18
+ if (kind && result) __defProp(target, key, result);
19
+ return result;
20
+ };
21
+ let EFCaptionsActiveWord = class extends EFTemporal(LitElement) {
22
+ constructor() {
23
+ super(...arguments);
24
+ this.wordStartMs = 0;
25
+ this.wordEndMs = 0;
26
+ this.wordText = "";
27
+ }
28
+ render() {
29
+ return html`${this.wordText}`;
30
+ }
31
+ get startTimeMs() {
32
+ return this.wordStartMs || 0;
33
+ }
34
+ get durationMs() {
35
+ return this.wordEndMs - this.wordStartMs;
36
+ }
37
+ };
38
+ EFCaptionsActiveWord.styles = [
39
+ css`
40
+ :host {
41
+ display: inline-block;
42
+ }
43
+ `
44
+ ];
45
+ __decorateClass([
46
+ property({ type: Number, attribute: false })
47
+ ], EFCaptionsActiveWord.prototype, "wordStartMs", 2);
48
+ __decorateClass([
49
+ property({ type: Number, attribute: false })
50
+ ], EFCaptionsActiveWord.prototype, "wordEndMs", 2);
51
+ __decorateClass([
52
+ property({ type: String, attribute: false })
53
+ ], EFCaptionsActiveWord.prototype, "wordText", 2);
54
+ EFCaptionsActiveWord = __decorateClass([
55
+ customElement("ef-captions-active-word")
56
+ ], EFCaptionsActiveWord);
57
+ let EFCaptions = class extends EFSourceMixin(
58
+ EFTemporal(FetchMixin(LitElement)),
59
+ { assetType: "caption_files" }
60
+ ) {
61
+ constructor() {
62
+ super(...arguments);
63
+ this.target = null;
64
+ this.wordStyle = "";
65
+ this.activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
66
+ this.md5SumLoader = new Task(this, {
67
+ autoRun: false,
68
+ args: () => [this.target, this.fetch],
69
+ task: async ([_target, fetch], { signal }) => {
70
+ const md5Path = `/@ef-asset/${this.targetElement.src ?? ""}`;
71
+ const response = await fetch(md5Path, { method: "HEAD", signal });
72
+ return response.headers.get("etag") ?? void 0;
73
+ }
74
+ });
75
+ this.captionsDataTask = new Task(this, {
76
+ autoRun: EF_INTERACTIVE,
77
+ args: () => [this.captionsPath(), this.fetch],
78
+ task: async ([captionsPath, fetch], { signal }) => {
79
+ const response = await fetch(captionsPath, { signal });
80
+ return response.json();
81
+ }
82
+ });
83
+ this.frameTask = new Task(this, {
84
+ autoRun: EF_INTERACTIVE,
85
+ args: () => [this.captionsDataTask.status],
86
+ task: async () => {
87
+ await this.captionsDataTask.taskComplete;
88
+ }
89
+ });
90
+ }
91
+ captionsPath() {
92
+ const targetSrc = this.targetElement.src;
93
+ if (targetSrc.startsWith("editframe://") || targetSrc.startsWith("http")) {
94
+ return targetSrc.replace("isobmff", "caption");
95
+ }
96
+ return `/@ef-captions/${targetSrc}`;
97
+ }
98
+ connectedCallback() {
99
+ super.connectedCallback();
100
+ if (this.targetElement) {
101
+ new CrossUpdateController(this.targetElement, this);
102
+ }
103
+ }
104
+ render() {
105
+ return this.captionsDataTask.render({
106
+ pending: () => html`<div>Generating captions data...</div>`,
107
+ error: () => html`<div>🚫 Error generating captions data</div>`,
108
+ complete: () => html`<slot></slot>`
109
+ });
110
+ }
111
+ updated(_changedProperties) {
112
+ this.updateActiveWord();
113
+ }
114
+ updateActiveWord() {
115
+ const caption = this.captionsDataTask.value;
116
+ if (!caption) {
117
+ return;
118
+ }
119
+ const words = [];
120
+ let startMs = 0;
121
+ let endMs = 0;
122
+ for (const segment of caption.segments) {
123
+ if (this.targetElement.ownCurrentTimeMs >= segment.start * 1e3 && this.targetElement.ownCurrentTimeMs <= segment.end * 1e3) {
124
+ for (const word of segment.words) {
125
+ if (this.targetElement.ownCurrentTimeMs >= word.start * 1e3 && this.targetElement.ownCurrentTimeMs <= word.end * 1e3) {
126
+ words.push(word.text);
127
+ startMs = word.start * 1e3;
128
+ endMs = word.end * 1e3;
129
+ }
130
+ }
131
+ }
132
+ }
133
+ for (const container of Array.from(this.activeWordContainers)) {
134
+ container.wordText = words.join(" ");
135
+ container.wordStartMs = startMs;
136
+ container.wordEndMs = endMs;
137
+ }
138
+ }
139
+ get targetElement() {
140
+ const target = document.getElementById(this.getAttribute("target") ?? "");
141
+ if (target instanceof EFAudio || target instanceof EFVideo) {
142
+ return target;
143
+ }
144
+ throw new Error("Invalid target, must be an EFAudio or EFVideo element");
145
+ }
146
+ };
147
+ EFCaptions.styles = [
148
+ css`
149
+ :host {
150
+ display: block;
151
+ }
152
+ `
153
+ ];
154
+ __decorateClass([
155
+ property({ type: String, attribute: "target" })
156
+ ], EFCaptions.prototype, "target", 2);
157
+ __decorateClass([
158
+ property({ attribute: "word-style" })
159
+ ], EFCaptions.prototype, "wordStyle", 2);
160
+ EFCaptions = __decorateClass([
161
+ customElement("ef-captions")
162
+ ], EFCaptions);
163
+ export {
164
+ EFCaptions,
165
+ EFCaptionsActiveWord
166
+ };
@@ -0,0 +1,80 @@
1
+ import { Task } from "@lit/task";
2
+ import { html, css, LitElement } from "lit";
3
+ import { customElement } from "lit/decorators.js";
4
+ import { createRef, ref } from "lit/directives/ref.js";
5
+ import { FetchMixin } from "./FetchMixin.js";
6
+ import { EFSourceMixin } from "./EFSourceMixin.js";
7
+ import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
8
+ var __defProp = Object.defineProperty;
9
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
10
+ var __decorateClass = (decorators, target, key, kind) => {
11
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
12
+ for (var i = decorators.length - 1, decorator; i >= 0; i--)
13
+ if (decorator = decorators[i])
14
+ result = (kind ? decorator(target, key, result) : decorator(result)) || result;
15
+ if (kind && result) __defProp(target, key, result);
16
+ return result;
17
+ };
18
+ let EFImage = class extends EFSourceMixin(FetchMixin(LitElement), {
19
+ assetType: "image_files"
20
+ }) {
21
+ constructor() {
22
+ super(...arguments);
23
+ this.imageRef = createRef();
24
+ this.canvasRef = createRef();
25
+ this.fetchImage = new Task(this, {
26
+ autoRun: EF_INTERACTIVE,
27
+ args: () => [this.assetPath(), this.fetch],
28
+ task: async ([assetPath, fetch], { signal }) => {
29
+ const response = await fetch(assetPath, { signal });
30
+ const image = new Image();
31
+ image.src = URL.createObjectURL(await response.blob());
32
+ await new Promise((resolve) => {
33
+ image.onload = resolve;
34
+ });
35
+ if (!this.canvasRef.value) throw new Error("Canvas not ready");
36
+ const ctx = this.canvasRef.value.getContext("2d");
37
+ if (!ctx) throw new Error("Canvas 2d context not ready");
38
+ this.canvasRef.value.width = image.width;
39
+ this.canvasRef.value.height = image.height;
40
+ ctx.drawImage(image, 0, 0);
41
+ }
42
+ });
43
+ this.frameTask = new Task(this, {
44
+ autoRun: EF_INTERACTIVE,
45
+ args: () => [this.fetchImage.status],
46
+ task: async () => {
47
+ await this.fetchImage.taskComplete;
48
+ }
49
+ });
50
+ }
51
+ render() {
52
+ return html`<canvas ${ref(this.canvasRef)}></canvas>`;
53
+ }
54
+ assetPath() {
55
+ if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
56
+ return this.src;
57
+ }
58
+ return `/@ef-image/${this.src}`;
59
+ }
60
+ };
61
+ EFImage.styles = [
62
+ css`
63
+ :host {
64
+ display: block;
65
+ }
66
+ canvas {
67
+ display: block;
68
+ width: 100%;
69
+ height: 100%;
70
+ object-fit: fill;
71
+ object-position: center;
72
+ }
73
+ `
74
+ ];
75
+ EFImage = __decorateClass([
76
+ customElement("ef-image")
77
+ ], EFImage);
78
+ export {
79
+ EFImage
80
+ };