@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.
- package/dist/EF_FRAMEGEN.d.ts +44 -0
- package/dist/EF_INTERACTIVE.d.ts +1 -0
- package/dist/assets/dist/EncodedAsset.js +560 -0
- package/dist/assets/dist/MP4File.js +170 -0
- package/dist/assets/dist/memoize.js +14 -0
- package/dist/elements/CrossUpdateController.d.ts +9 -0
- package/dist/elements/EFAudio.d.ts +10 -0
- package/dist/elements/EFCaptions.d.ts +38 -0
- package/dist/elements/EFImage.d.ts +14 -0
- package/dist/elements/EFMedia.d.ts +61 -0
- package/dist/elements/EFSourceMixin.d.ts +12 -0
- package/dist/elements/EFTemporal.d.ts +38 -0
- package/dist/elements/EFTimegroup.browsertest.d.ts +12 -0
- package/dist/elements/EFTimegroup.d.ts +39 -0
- package/dist/elements/EFVideo.d.ts +14 -0
- package/dist/elements/EFWaveform.d.ts +30 -0
- package/dist/elements/FetchMixin.d.ts +8 -0
- package/dist/elements/TimegroupController.d.ts +14 -0
- package/dist/elements/durationConverter.d.ts +4 -0
- package/dist/elements/parseTimeToMs.d.ts +1 -0
- package/{src/EF_FRAMEGEN.ts → dist/elements/src/EF_FRAMEGEN.js} +35 -115
- package/dist/elements/src/EF_INTERACTIVE.js +7 -0
- package/dist/elements/src/elements/CrossUpdateController.js +16 -0
- package/dist/elements/src/elements/EFAudio.js +54 -0
- package/dist/elements/src/elements/EFCaptions.js +166 -0
- package/dist/elements/src/elements/EFImage.js +80 -0
- package/dist/elements/src/elements/EFMedia.js +339 -0
- package/dist/elements/src/elements/EFSourceMixin.js +55 -0
- package/dist/elements/src/elements/EFTemporal.js +234 -0
- package/dist/elements/src/elements/EFTimegroup.js +355 -0
- package/dist/elements/src/elements/EFVideo.js +110 -0
- package/dist/elements/src/elements/EFWaveform.js +226 -0
- package/dist/elements/src/elements/FetchMixin.js +28 -0
- package/dist/elements/src/elements/TimegroupController.js +20 -0
- package/dist/elements/src/elements/durationConverter.js +8 -0
- package/dist/elements/src/elements/parseTimeToMs.js +12 -0
- package/dist/elements/src/elements/util.js +11 -0
- package/dist/elements/src/gui/ContextMixin.js +234 -0
- package/dist/elements/src/gui/EFFilmstrip.js +729 -0
- package/dist/elements/src/gui/EFPreview.js +45 -0
- package/dist/elements/src/gui/EFWorkbench.js +128 -0
- package/dist/elements/src/gui/TWMixin.css.js +4 -0
- package/dist/elements/src/gui/TWMixin.js +36 -0
- package/dist/elements/src/gui/apiHostContext.js +5 -0
- package/dist/elements/src/gui/fetchContext.js +5 -0
- package/dist/elements/src/gui/focusContext.js +5 -0
- package/dist/elements/src/gui/focusedElementContext.js +7 -0
- package/dist/elements/src/gui/playingContext.js +5 -0
- package/dist/elements/src/index.js +27 -0
- package/dist/elements/src/msToTimeCode.js +15 -0
- package/dist/elements/util.d.ts +4 -0
- package/dist/gui/ContextMixin.d.ts +23 -0
- package/dist/gui/EFFilmstrip.d.ts +144 -0
- package/dist/gui/EFPreview.d.ts +27 -0
- package/dist/gui/EFWorkbench.d.ts +34 -0
- package/dist/gui/TWMixin.d.ts +3 -0
- package/dist/gui/apiHostContext.d.ts +3 -0
- package/dist/gui/fetchContext.d.ts +3 -0
- package/dist/gui/focusContext.d.ts +6 -0
- package/dist/gui/focusedElementContext.d.ts +3 -0
- package/dist/gui/playingContext.d.ts +3 -0
- package/dist/index.d.ts +11 -0
- package/dist/msToTimeCode.d.ts +1 -0
- package/dist/style.css +800 -0
- package/package.json +6 -9
- package/src/elements/EFAudio.ts +1 -1
- package/src/elements/EFCaptions.ts +9 -9
- package/src/elements/EFImage.ts +3 -3
- package/src/elements/EFMedia.ts +11 -8
- package/src/elements/EFSourceMixin.ts +1 -1
- package/src/elements/EFTemporal.ts +42 -5
- package/src/elements/EFTimegroup.browsertest.ts +3 -3
- package/src/elements/EFTimegroup.ts +9 -6
- package/src/elements/EFVideo.ts +2 -2
- package/src/elements/EFWaveform.ts +6 -6
- package/src/elements/FetchMixin.ts +5 -3
- package/src/elements/TimegroupController.ts +1 -1
- package/src/elements/durationConverter.ts +1 -1
- package/src/elements/util.ts +1 -1
- package/src/gui/ContextMixin.ts +254 -0
- package/src/gui/EFFilmstrip.ts +41 -150
- package/src/gui/EFPreview.ts +39 -0
- package/src/gui/EFWorkbench.ts +7 -105
- package/src/gui/TWMixin.ts +10 -3
- package/src/gui/apiHostContext.ts +3 -0
- package/src/gui/fetchContext.ts +5 -0
- package/src/gui/focusContext.ts +7 -0
- package/src/gui/focusedElementContext.ts +5 -0
- package/src/gui/playingContext.ts +3 -0
- package/CHANGELOG.md +0 -7
- package/postcss.config.cjs +0 -12
- package/src/EF_INTERACTIVE.ts +0 -2
- package/src/elements.css +0 -22
- package/src/index.ts +0 -33
- package/tailwind.config.ts +0 -10
- package/tsconfig.json +0 -4
- 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
|
|
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 /
|
|
170
|
-
renderOptions.encoderOptions.alignedToUs /
|
|
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
|
|
177
|
-
if (this.renderOptions ===
|
|
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
|
|
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
|
-
|
|
215
|
-
|
|
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,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
|
+
};
|