@editframe/elements 0.7.0-beta.9 → 0.8.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.
- package/dist/EF_FRAMEGEN.d.ts +43 -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 +8 -0
- package/dist/elements/EFAudio.d.ts +9 -0
- package/dist/elements/EFCaptions.d.ts +38 -0
- package/dist/elements/EFImage.d.ts +13 -0
- package/dist/elements/EFMedia.d.ts +63 -0
- package/dist/elements/EFSourceMixin.d.ts +11 -0
- package/dist/elements/EFTemporal.d.ts +40 -0
- package/dist/elements/EFTimegroup.browsertest.d.ts +11 -0
- package/dist/elements/EFTimegroup.d.ts +36 -0
- package/dist/elements/EFVideo.d.ts +13 -0
- package/dist/elements/EFWaveform.d.ts +29 -0
- package/dist/elements/FetchMixin.d.ts +7 -0
- package/dist/elements/TimegroupController.d.ts +13 -0
- package/dist/elements/durationConverter.d.ts +12 -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 +169 -0
- package/dist/elements/src/elements/EFImage.js +80 -0
- package/dist/elements/src/elements/EFMedia.js +356 -0
- package/dist/elements/src/elements/EFSourceMixin.js +55 -0
- package/dist/elements/src/elements/EFTemporal.js +283 -0
- package/dist/elements/src/elements/EFTimegroup.js +338 -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 +13 -0
- package/dist/elements/src/elements/util.js +11 -0
- package/dist/elements/src/gui/ContextMixin.js +246 -0
- package/dist/elements/src/gui/EFFilmstrip.js +731 -0
- package/dist/elements/src/gui/EFPreview.js +45 -0
- package/dist/elements/src/gui/EFToggleLoop.js +39 -0
- package/dist/elements/src/gui/EFTogglePlay.js +43 -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/efContext.js +7 -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 +7 -0
- package/dist/elements/src/index.js +31 -0
- package/dist/elements/src/msToTimeCode.js +15 -0
- package/dist/elements/util.d.ts +3 -0
- package/dist/gui/ContextMixin.d.ts +19 -0
- package/dist/gui/EFFilmstrip.d.ts +148 -0
- package/dist/gui/EFPreview.d.ts +12 -0
- package/dist/gui/EFToggleLoop.d.ts +12 -0
- package/dist/gui/EFTogglePlay.d.ts +12 -0
- package/dist/gui/EFWorkbench.d.ts +18 -0
- package/dist/gui/TWMixin.d.ts +2 -0
- package/dist/gui/apiHostContext.d.ts +3 -0
- package/dist/gui/efContext.d.ts +4 -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 +6 -0
- package/dist/index.d.ts +12 -0
- package/dist/msToTimeCode.d.ts +1 -0
- package/dist/style.css +802 -0
- package/package.json +7 -10
- package/src/elements/EFAudio.ts +1 -1
- package/src/elements/EFCaptions.ts +23 -17
- package/src/elements/EFImage.ts +3 -3
- package/src/elements/EFMedia.ts +48 -17
- package/src/elements/EFSourceMixin.ts +1 -1
- package/src/elements/EFTemporal.ts +101 -6
- package/src/elements/EFTimegroup.browsertest.ts +3 -3
- package/src/elements/EFTimegroup.ts +30 -47
- package/src/elements/EFVideo.ts +2 -2
- package/src/elements/EFWaveform.ts +9 -9
- package/src/elements/FetchMixin.ts +5 -3
- package/src/elements/TimegroupController.ts +1 -1
- package/src/elements/durationConverter.ts +21 -1
- package/src/elements/parseTimeToMs.ts +1 -0
- package/src/elements/util.ts +1 -1
- package/src/gui/ContextMixin.ts +268 -0
- package/src/gui/EFFilmstrip.ts +61 -171
- package/src/gui/EFPreview.ts +39 -0
- package/src/gui/EFToggleLoop.ts +34 -0
- package/src/gui/EFTogglePlay.ts +38 -0
- package/src/gui/EFWorkbench.ts +11 -109
- package/src/gui/TWMixin.ts +10 -3
- package/src/gui/apiHostContext.ts +3 -0
- package/src/gui/efContext.ts +6 -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 +5 -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
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
import { css, LitElement } from "lit";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
+
import { Task } from "@lit/task";
|
|
5
|
+
import { consume } from "@lit/context";
|
|
6
|
+
import debug from "debug";
|
|
7
|
+
import { MP4File } from "../../../assets/dist/MP4File.js";
|
|
8
|
+
import { VideoAsset } from "../../../assets/dist/EncodedAsset.js";
|
|
9
|
+
import { EFTemporal } from "./EFTemporal.js";
|
|
10
|
+
import { FetchMixin } from "./FetchMixin.js";
|
|
11
|
+
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
12
|
+
import { getStartTimeMs } from "./util.js";
|
|
13
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
14
|
+
import { apiHostContext } from "../gui/apiHostContext.js";
|
|
15
|
+
var __defProp = Object.defineProperty;
|
|
16
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
17
|
+
var result = void 0;
|
|
18
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
19
|
+
if (decorator = decorators[i])
|
|
20
|
+
result = decorator(target, key, result) || result;
|
|
21
|
+
if (result) __defProp(target, key, result);
|
|
22
|
+
return result;
|
|
23
|
+
};
|
|
24
|
+
const log = debug("ef:elements:EFMedia");
|
|
25
|
+
const deepGetMediaElements = (element, medias = []) => {
|
|
26
|
+
for (const child of Array.from(element.children)) {
|
|
27
|
+
if (child instanceof EFMedia) {
|
|
28
|
+
medias.push(child);
|
|
29
|
+
} else {
|
|
30
|
+
deepGetMediaElements(child, medias);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return medias;
|
|
34
|
+
};
|
|
35
|
+
class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
36
|
+
assetType: "isobmff_files"
|
|
37
|
+
}) {
|
|
38
|
+
constructor() {
|
|
39
|
+
super(...arguments);
|
|
40
|
+
this.currentTimeMs = 0;
|
|
41
|
+
this.trackFragmentIndexLoader = new Task(this, {
|
|
42
|
+
args: () => [this.fragmentIndexPath(), this.fetch],
|
|
43
|
+
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
44
|
+
if (this.src === "") {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const response = await fetch(fragmentIndexPath, { signal });
|
|
48
|
+
return await response.json();
|
|
49
|
+
},
|
|
50
|
+
onComplete: () => {
|
|
51
|
+
this.requestUpdate("ownCurrentTimeMs");
|
|
52
|
+
this.rootTimegroup?.requestUpdate("ownCurrentTimeMs");
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
this.initSegmentsLoader = new Task(this, {
|
|
56
|
+
autoRun: EF_INTERACTIVE,
|
|
57
|
+
args: () => [this.trackFragmentIndexLoader.value, this.src, this.fetch],
|
|
58
|
+
task: async ([fragmentIndex, _src, fetch], { signal }) => {
|
|
59
|
+
if (!fragmentIndex) {
|
|
60
|
+
return;
|
|
61
|
+
}
|
|
62
|
+
return await Promise.all(
|
|
63
|
+
Object.entries(fragmentIndex).map(async ([trackId, track]) => {
|
|
64
|
+
const start = track.initSegment.offset;
|
|
65
|
+
const end = track.initSegment.offset + track.initSegment.size - 1;
|
|
66
|
+
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
67
|
+
signal,
|
|
68
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
69
|
+
});
|
|
70
|
+
const buffer = await response.arrayBuffer();
|
|
71
|
+
buffer.fileStart = 0;
|
|
72
|
+
const mp4File = new MP4File();
|
|
73
|
+
mp4File.appendBuffer(buffer, true);
|
|
74
|
+
mp4File.flush();
|
|
75
|
+
await mp4File.readyPromise;
|
|
76
|
+
return { trackId, buffer, mp4File };
|
|
77
|
+
})
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
});
|
|
81
|
+
this.seekTask = new Task(this, {
|
|
82
|
+
autoRun: EF_INTERACTIVE,
|
|
83
|
+
args: () => [
|
|
84
|
+
this.desiredSeekTimeMs,
|
|
85
|
+
this.trackFragmentIndexLoader.value,
|
|
86
|
+
this.initSegmentsLoader.value
|
|
87
|
+
],
|
|
88
|
+
task: async ([seekToMs, fragmentIndex, initSegments], { signal: _signal }) => {
|
|
89
|
+
if (fragmentIndex === void 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
if (initSegments === void 0) {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
const result = {};
|
|
96
|
+
for (const index of Object.values(fragmentIndex)) {
|
|
97
|
+
const track = initSegments.find((segment2) => segment2.trackId === String(index.track))?.mp4File.getInfo().tracks[0];
|
|
98
|
+
if (!track) {
|
|
99
|
+
throw new Error("Could not finding matching track");
|
|
100
|
+
}
|
|
101
|
+
const segment = index.segments.toReversed().find((segment2) => {
|
|
102
|
+
return segment2.dts / track.timescale * 1e3 <= seekToMs;
|
|
103
|
+
});
|
|
104
|
+
const nextSegment = index.segments.find((segment2) => {
|
|
105
|
+
return segment2.dts / track.timescale * 1e3 > seekToMs;
|
|
106
|
+
});
|
|
107
|
+
if (!segment) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
result[index.track] = { segment, track, nextSegment };
|
|
111
|
+
}
|
|
112
|
+
return result;
|
|
113
|
+
}
|
|
114
|
+
});
|
|
115
|
+
this.fetchSeekTask = new Task(this, {
|
|
116
|
+
autoRun: EF_INTERACTIVE,
|
|
117
|
+
argsEqual: deepArrayEquals,
|
|
118
|
+
args: () => [this.initSegmentsLoader.value, this.seekTask.value, this.fetch],
|
|
119
|
+
task: async ([initSegments, seekResult, fetch], { signal }) => {
|
|
120
|
+
if (!initSegments) {
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
if (!seekResult) {
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
const files = {};
|
|
127
|
+
for (const [trackId, { segment, track, nextSegment }] of Object.entries(
|
|
128
|
+
seekResult
|
|
129
|
+
)) {
|
|
130
|
+
const start = segment.offset;
|
|
131
|
+
const end = segment.offset + segment.size;
|
|
132
|
+
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
133
|
+
signal,
|
|
134
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
135
|
+
});
|
|
136
|
+
if (nextSegment) {
|
|
137
|
+
const nextStart = nextSegment.offset;
|
|
138
|
+
const nextEnd = nextSegment.offset + nextSegment.size;
|
|
139
|
+
fetch(this.fragmentTrackPath(trackId), {
|
|
140
|
+
signal,
|
|
141
|
+
headers: { Range: `bytes=${nextStart}-${nextEnd}` }
|
|
142
|
+
}).then(() => {
|
|
143
|
+
log("Prefetched next segment");
|
|
144
|
+
}).catch((error) => {
|
|
145
|
+
log("Failed to prefetch next segment", error);
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
const initSegment = Object.values(initSegments).find(
|
|
149
|
+
(initSegment2) => initSegment2.trackId === String(track.id)
|
|
150
|
+
);
|
|
151
|
+
if (!initSegment) {
|
|
152
|
+
throw new Error("Could not find matching init segment");
|
|
153
|
+
}
|
|
154
|
+
const initBuffer = initSegment.buffer;
|
|
155
|
+
const mediaBuffer = await response.arrayBuffer();
|
|
156
|
+
files[trackId] = new File([initBuffer, mediaBuffer], "video.mp4", {
|
|
157
|
+
type: "video/mp4"
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
return files;
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
this.videoAssetTask = new Task(this, {
|
|
164
|
+
autoRun: EF_INTERACTIVE,
|
|
165
|
+
args: () => [this.fetchSeekTask.value],
|
|
166
|
+
task: async ([files], { signal: _signal }) => {
|
|
167
|
+
if (!files) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
if (!this.defaultVideoTrackId) {
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
173
|
+
const videoFile = files[this.defaultVideoTrackId];
|
|
174
|
+
if (!videoFile) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
for (const frame of this.videoAssetTask.value?.decodedFrames || []) {
|
|
178
|
+
frame.close();
|
|
179
|
+
}
|
|
180
|
+
this.videoAssetTask.value?.videoDecoder?.close();
|
|
181
|
+
return await VideoAsset.createFromReadableStream(
|
|
182
|
+
"video.mp4",
|
|
183
|
+
videoFile.stream(),
|
|
184
|
+
videoFile
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
this.desiredSeekTimeMs = 0;
|
|
189
|
+
this.#audioContext = new OfflineAudioContext(2, 48e3 / 30, 48e3);
|
|
190
|
+
this.audioBufferTask = new Task(this, {
|
|
191
|
+
autoRun: EF_INTERACTIVE,
|
|
192
|
+
args: () => [this.fetchSeekTask.value, this.seekTask.value],
|
|
193
|
+
task: async ([files, segments], { signal: _signal }) => {
|
|
194
|
+
if (!files) {
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
if (!segments) {
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (!this.defaultAudioTrackId) {
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
const segment = segments[this.defaultAudioTrackId];
|
|
204
|
+
if (!segment) {
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
const audioFile = files[this.defaultAudioTrackId];
|
|
208
|
+
if (!audioFile) {
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
return {
|
|
212
|
+
buffer: await this.#audioContext.decodeAudioData(
|
|
213
|
+
await audioFile.arrayBuffer()
|
|
214
|
+
),
|
|
215
|
+
startOffsetMs: segment.segment.cts / segment.track.timescale * 1e3
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
}
|
|
220
|
+
static {
|
|
221
|
+
this.styles = [
|
|
222
|
+
css`
|
|
223
|
+
:host {
|
|
224
|
+
display: block;
|
|
225
|
+
position: relative;
|
|
226
|
+
overflow: hidden;
|
|
227
|
+
}
|
|
228
|
+
`
|
|
229
|
+
];
|
|
230
|
+
}
|
|
231
|
+
fragmentIndexPath() {
|
|
232
|
+
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
233
|
+
return `${this.src}/index`;
|
|
234
|
+
}
|
|
235
|
+
return `/@ef-track-fragment-index/${this.src ?? ""}`;
|
|
236
|
+
}
|
|
237
|
+
fragmentTrackPath(trackId) {
|
|
238
|
+
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
239
|
+
return `${this.src.replace("files", "tracks")}/${trackId}`;
|
|
240
|
+
}
|
|
241
|
+
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
242
|
+
}
|
|
243
|
+
get defaultVideoTrackId() {
|
|
244
|
+
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
245
|
+
(track) => track.type === "video"
|
|
246
|
+
)?.track;
|
|
247
|
+
}
|
|
248
|
+
get defaultAudioTrackId() {
|
|
249
|
+
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
250
|
+
(track) => track.type === "audio"
|
|
251
|
+
)?.track;
|
|
252
|
+
}
|
|
253
|
+
async executeSeek(seekToMs) {
|
|
254
|
+
this.desiredSeekTimeMs = seekToMs;
|
|
255
|
+
}
|
|
256
|
+
updated(changedProperties) {
|
|
257
|
+
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
258
|
+
this.executeSeek(this.trimAdjustedOwnCurrentTimeMs);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
get hasOwnDuration() {
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
get durationMs() {
|
|
265
|
+
if (!this.trackFragmentIndexLoader.value) {
|
|
266
|
+
return 0;
|
|
267
|
+
}
|
|
268
|
+
const durations = Object.values(this.trackFragmentIndexLoader.value).map(
|
|
269
|
+
(track) => {
|
|
270
|
+
return track.duration / track.timescale * 1e3;
|
|
271
|
+
}
|
|
272
|
+
);
|
|
273
|
+
if (durations.length === 0) {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
277
|
+
}
|
|
278
|
+
get startTimeMs() {
|
|
279
|
+
return getStartTimeMs(this);
|
|
280
|
+
}
|
|
281
|
+
#audioContext;
|
|
282
|
+
async fetchAudioSpanningTime(fromMs, toMs) {
|
|
283
|
+
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
284
|
+
toMs -= this.startTimeMs - this.trimStartMs;
|
|
285
|
+
await this.trackFragmentIndexLoader.taskComplete;
|
|
286
|
+
const audioTrackId = this.defaultAudioTrackId;
|
|
287
|
+
if (!audioTrackId) {
|
|
288
|
+
log("No audio track found");
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
const audioTrackIndex = this.trackFragmentIndexLoader.value?.[audioTrackId];
|
|
292
|
+
if (!audioTrackIndex) {
|
|
293
|
+
log("No audio track found");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const start = audioTrackIndex.initSegment.offset;
|
|
297
|
+
const end = audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size - 1;
|
|
298
|
+
const audioInitFragmentRequest = this.fetch(
|
|
299
|
+
this.fragmentTrackPath(String(audioTrackId)),
|
|
300
|
+
{
|
|
301
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
302
|
+
}
|
|
303
|
+
);
|
|
304
|
+
const fragments = Object.values(audioTrackIndex.segments).filter(
|
|
305
|
+
(segment) => {
|
|
306
|
+
const segmentStartsBeforeEnd = segment.dts <= toMs * audioTrackIndex.timescale / 1e3;
|
|
307
|
+
const segmentEndsAfterStart = segment.dts + segment.duration >= fromMs * audioTrackIndex.timescale / 1e3;
|
|
308
|
+
return segmentStartsBeforeEnd && segmentEndsAfterStart;
|
|
309
|
+
}
|
|
310
|
+
);
|
|
311
|
+
const firstFragment = fragments[0];
|
|
312
|
+
if (!firstFragment) {
|
|
313
|
+
log("No audio fragments found");
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
const lastFragment = fragments[fragments.length - 1];
|
|
317
|
+
if (!lastFragment) {
|
|
318
|
+
log("No audio fragments found");
|
|
319
|
+
return;
|
|
320
|
+
}
|
|
321
|
+
const fragmentStart = firstFragment.offset;
|
|
322
|
+
const fragmentEnd = lastFragment.offset + lastFragment.size - 1;
|
|
323
|
+
const audioFragmentRequest = this.fetch(
|
|
324
|
+
this.fragmentTrackPath(String(audioTrackId)),
|
|
325
|
+
{
|
|
326
|
+
headers: { Range: `bytes=${fragmentStart}-${fragmentEnd}` }
|
|
327
|
+
}
|
|
328
|
+
);
|
|
329
|
+
const initResponse = await audioInitFragmentRequest;
|
|
330
|
+
const dataResponse = await audioFragmentRequest;
|
|
331
|
+
const initBuffer = await initResponse.arrayBuffer();
|
|
332
|
+
const dataBuffer = await dataResponse.arrayBuffer();
|
|
333
|
+
const audioBlob = new Blob([initBuffer, dataBuffer], {
|
|
334
|
+
type: "audio/mp4"
|
|
335
|
+
});
|
|
336
|
+
return {
|
|
337
|
+
blob: audioBlob,
|
|
338
|
+
startMs: firstFragment.dts / audioTrackIndex.timescale * 1e3 - this.trimStartMs,
|
|
339
|
+
endMs: lastFragment.dts / audioTrackIndex.timescale * 1e3 + lastFragment.duration / audioTrackIndex.timescale * 1e3 - this.trimEndMs
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
__decorateClass([
|
|
344
|
+
property({ type: Number })
|
|
345
|
+
], EFMedia.prototype, "currentTimeMs");
|
|
346
|
+
__decorateClass([
|
|
347
|
+
consume({ context: apiHostContext, subscribe: true }),
|
|
348
|
+
state()
|
|
349
|
+
], EFMedia.prototype, "efHost");
|
|
350
|
+
__decorateClass([
|
|
351
|
+
state()
|
|
352
|
+
], EFMedia.prototype, "desiredSeekTimeMs");
|
|
353
|
+
export {
|
|
354
|
+
EFMedia,
|
|
355
|
+
deepGetMediaElements
|
|
356
|
+
};
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
2
|
+
import { state } from "lit/decorators/state.js";
|
|
3
|
+
import { Task } from "@lit/task";
|
|
4
|
+
import { property } from "lit/decorators/property.js";
|
|
5
|
+
import { apiHostContext } from "../gui/apiHostContext.js";
|
|
6
|
+
var __defProp = Object.defineProperty;
|
|
7
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
8
|
+
var result = void 0;
|
|
9
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
10
|
+
if (decorator = decorators[i])
|
|
11
|
+
result = decorator(target, key, result) || result;
|
|
12
|
+
if (result) __defProp(target, key, result);
|
|
13
|
+
return result;
|
|
14
|
+
};
|
|
15
|
+
function EFSourceMixin(superClass, options) {
|
|
16
|
+
class EFSourceElement extends superClass {
|
|
17
|
+
constructor() {
|
|
18
|
+
super(...arguments);
|
|
19
|
+
this.src = "";
|
|
20
|
+
this.md5SumLoader = new Task(this, {
|
|
21
|
+
autoRun: false,
|
|
22
|
+
args: () => [this.src],
|
|
23
|
+
task: async ([src], { signal }) => {
|
|
24
|
+
const md5Path = `/@ef-asset/${src}`;
|
|
25
|
+
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
26
|
+
return response.headers.get("etag") ?? void 0;
|
|
27
|
+
}
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
productionSrc() {
|
|
31
|
+
if (!this.md5SumLoader.value) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`MD5 sum not available for ${this}. Cannot generate production URL`
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
if (!this.efHost) {
|
|
37
|
+
throw new Error(
|
|
38
|
+
`efHost not available for ${this}. Cannot generate production URL`
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
return `${this.efHost}/api/video2/${options.assetType}/${this.md5SumLoader.value}`;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
__decorateClass([
|
|
45
|
+
consume({ context: apiHostContext, subscribe: true }),
|
|
46
|
+
state()
|
|
47
|
+
], EFSourceElement.prototype, "efHost");
|
|
48
|
+
__decorateClass([
|
|
49
|
+
property({ type: String })
|
|
50
|
+
], EFSourceElement.prototype, "src");
|
|
51
|
+
return EFSourceElement;
|
|
52
|
+
}
|
|
53
|
+
export {
|
|
54
|
+
EFSourceMixin
|
|
55
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
import { createContext, consume } from "@lit/context";
|
|
2
|
+
import { property, state } from "lit/decorators.js";
|
|
3
|
+
import { durationConverter } from "./durationConverter.js";
|
|
4
|
+
import { Task } from "@lit/task";
|
|
5
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
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
|
+
const timegroupContext = createContext(
|
|
17
|
+
Symbol("timeGroupContext")
|
|
18
|
+
);
|
|
19
|
+
const isEFTemporal = (obj) => obj[EF_TEMPORAL];
|
|
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
|
+
const deepGetElementsWithFrameTasks = (element, elements = []) => {
|
|
31
|
+
for (const child of element.children) {
|
|
32
|
+
if ("frameTask" in child && child.frameTask instanceof Task) {
|
|
33
|
+
elements.push(child);
|
|
34
|
+
}
|
|
35
|
+
deepGetElementsWithFrameTasks(child, elements);
|
|
36
|
+
}
|
|
37
|
+
return elements;
|
|
38
|
+
};
|
|
39
|
+
let temporalCache;
|
|
40
|
+
const resetTemporalCache = () => {
|
|
41
|
+
temporalCache = /* @__PURE__ */ new Map();
|
|
42
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
43
|
+
requestAnimationFrame(resetTemporalCache);
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
resetTemporalCache();
|
|
47
|
+
const shallowGetTemporalElements = (element, temporals = []) => {
|
|
48
|
+
const cachedResult = temporalCache.get(element);
|
|
49
|
+
if (cachedResult) {
|
|
50
|
+
return cachedResult;
|
|
51
|
+
}
|
|
52
|
+
for (const child of element.children) {
|
|
53
|
+
if (isEFTemporal(child)) {
|
|
54
|
+
temporals.push(child);
|
|
55
|
+
} else {
|
|
56
|
+
shallowGetTemporalElements(child, temporals);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
temporalCache.set(element, temporals);
|
|
60
|
+
return temporals;
|
|
61
|
+
};
|
|
62
|
+
class OwnCurrentTimeController {
|
|
63
|
+
constructor(host, temporal) {
|
|
64
|
+
this.host = host;
|
|
65
|
+
this.temporal = temporal;
|
|
66
|
+
host.addController(this);
|
|
67
|
+
}
|
|
68
|
+
hostUpdated() {
|
|
69
|
+
this.temporal.requestUpdate("ownCurrentTimeMs");
|
|
70
|
+
}
|
|
71
|
+
remove() {
|
|
72
|
+
this.host.removeController(this);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
let startTimeMsCache = /* @__PURE__ */ new WeakMap();
|
|
76
|
+
const resetStartTimeMsCache = () => {
|
|
77
|
+
startTimeMsCache = /* @__PURE__ */ new WeakMap();
|
|
78
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
79
|
+
requestAnimationFrame(resetStartTimeMsCache);
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
resetStartTimeMsCache();
|
|
83
|
+
const EFTemporal = (superClass) => {
|
|
84
|
+
class TemporalMixinClass extends superClass {
|
|
85
|
+
constructor() {
|
|
86
|
+
super(...arguments);
|
|
87
|
+
this._offsetMs = 0;
|
|
88
|
+
this._trimStartMs = 0;
|
|
89
|
+
this._trimEndMs = 0;
|
|
90
|
+
this._startOffsetMs = 0;
|
|
91
|
+
this.rootTimegroup = this.getRootTimegroup();
|
|
92
|
+
this.frameTask = new Task(this, {
|
|
93
|
+
autoRun: EF_INTERACTIVE,
|
|
94
|
+
args: () => [this.ownCurrentTimeMs],
|
|
95
|
+
task: async ([], { signal: _signal }) => {
|
|
96
|
+
let fullyUpdated = await this.updateComplete;
|
|
97
|
+
while (!fullyUpdated) {
|
|
98
|
+
fullyUpdated = await this.updateComplete;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
#parentTimegroup;
|
|
104
|
+
set parentTimegroup(value) {
|
|
105
|
+
this.#parentTimegroup = value;
|
|
106
|
+
this.ownCurrentTimeController?.remove();
|
|
107
|
+
this.rootTimegroup = this.getRootTimegroup();
|
|
108
|
+
if (this.rootTimegroup) {
|
|
109
|
+
this.ownCurrentTimeController = new OwnCurrentTimeController(
|
|
110
|
+
this.rootTimegroup,
|
|
111
|
+
this
|
|
112
|
+
);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
get parentTimegroup() {
|
|
116
|
+
return this.#parentTimegroup;
|
|
117
|
+
}
|
|
118
|
+
get trimStartMs() {
|
|
119
|
+
return this._trimStartMs;
|
|
120
|
+
}
|
|
121
|
+
get trimEndMs() {
|
|
122
|
+
return this._trimEndMs;
|
|
123
|
+
}
|
|
124
|
+
get startOffsetMs() {
|
|
125
|
+
return this._startOffsetMs;
|
|
126
|
+
}
|
|
127
|
+
getRootTimegroup() {
|
|
128
|
+
let parent = this.tagName === "EF-TIMEGROUP" ? this : this.parentTimegroup;
|
|
129
|
+
while (parent?.parentTimegroup) {
|
|
130
|
+
parent = parent.parentTimegroup;
|
|
131
|
+
}
|
|
132
|
+
return parent;
|
|
133
|
+
}
|
|
134
|
+
get hasOwnDuration() {
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
// Defining this as a getter to a private property allows us to
|
|
138
|
+
// override it classes that include this mixin.
|
|
139
|
+
get durationMs() {
|
|
140
|
+
return this._durationMs || this.parentTimegroup?.durationMs || 0;
|
|
141
|
+
}
|
|
142
|
+
get offsetMs() {
|
|
143
|
+
return this._offsetMs || 0;
|
|
144
|
+
}
|
|
145
|
+
get parentTemporal() {
|
|
146
|
+
let parent = this.parentElement;
|
|
147
|
+
while (parent && !isEFTemporal(parent)) {
|
|
148
|
+
parent = parent.parentElement;
|
|
149
|
+
}
|
|
150
|
+
return parent;
|
|
151
|
+
}
|
|
152
|
+
get startTimeWithinParentMs() {
|
|
153
|
+
if (!this.parentTemporal) {
|
|
154
|
+
return 0;
|
|
155
|
+
}
|
|
156
|
+
return this.startTimeMs - this.parentTemporal.startTimeMs;
|
|
157
|
+
}
|
|
158
|
+
get startTimeMs() {
|
|
159
|
+
const cachedStartTime = startTimeMsCache.get(this);
|
|
160
|
+
if (cachedStartTime !== void 0) {
|
|
161
|
+
return cachedStartTime;
|
|
162
|
+
}
|
|
163
|
+
const parentTimegroup = this.parentTimegroup;
|
|
164
|
+
if (!parentTimegroup) {
|
|
165
|
+
startTimeMsCache.set(this, 0);
|
|
166
|
+
return 0;
|
|
167
|
+
}
|
|
168
|
+
switch (parentTimegroup.mode) {
|
|
169
|
+
case "sequence": {
|
|
170
|
+
const siblingTemorals = shallowGetTemporalElements(parentTimegroup);
|
|
171
|
+
const ownIndex = siblingTemorals?.indexOf(
|
|
172
|
+
this
|
|
173
|
+
);
|
|
174
|
+
if (ownIndex === 0) {
|
|
175
|
+
startTimeMsCache.set(this, parentTimegroup.startTimeMs);
|
|
176
|
+
return parentTimegroup.startTimeMs;
|
|
177
|
+
}
|
|
178
|
+
const previous = siblingTemorals?.[(ownIndex ?? 0) - 1];
|
|
179
|
+
if (!previous) {
|
|
180
|
+
throw new Error("Previous temporal element not found");
|
|
181
|
+
}
|
|
182
|
+
startTimeMsCache.set(
|
|
183
|
+
this,
|
|
184
|
+
previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs
|
|
185
|
+
);
|
|
186
|
+
return previous.startTimeMs + previous.durationMs - parentTimegroup.overlapMs;
|
|
187
|
+
}
|
|
188
|
+
case "contain":
|
|
189
|
+
case "fixed":
|
|
190
|
+
startTimeMsCache.set(
|
|
191
|
+
this,
|
|
192
|
+
parentTimegroup.startTimeMs + this.offsetMs
|
|
193
|
+
);
|
|
194
|
+
return parentTimegroup.startTimeMs + this.offsetMs;
|
|
195
|
+
default:
|
|
196
|
+
throw new Error(`Invalid time mode: ${parentTimegroup.mode}`);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
get endTimeMs() {
|
|
200
|
+
return this.startTimeMs + this.durationMs;
|
|
201
|
+
}
|
|
202
|
+
get ownCurrentTimeMs() {
|
|
203
|
+
if (this.rootTimegroup) {
|
|
204
|
+
return Math.min(
|
|
205
|
+
Math.max(0, this.rootTimegroup.currentTimeMs - this.startTimeMs),
|
|
206
|
+
this.durationMs
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Used to calculate the internal currentTimeMs of the element. This is useful
|
|
213
|
+
* for mapping to internal media time codes for audio/video elements.
|
|
214
|
+
*/
|
|
215
|
+
get trimAdjustedOwnCurrentTimeMs() {
|
|
216
|
+
if (this.rootTimegroup) {
|
|
217
|
+
return Math.min(
|
|
218
|
+
Math.max(
|
|
219
|
+
0,
|
|
220
|
+
this.rootTimegroup.currentTimeMs - this.startTimeMs + this.trimStartMs
|
|
221
|
+
),
|
|
222
|
+
this.durationMs + Math.abs(this.startOffsetMs) + this.trimStartMs
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
return 0;
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
__decorateClass([
|
|
229
|
+
consume({ context: timegroupContext, subscribe: true }),
|
|
230
|
+
property({ attribute: false })
|
|
231
|
+
], TemporalMixinClass.prototype, "parentTimegroup", 1);
|
|
232
|
+
__decorateClass([
|
|
233
|
+
property({
|
|
234
|
+
type: String,
|
|
235
|
+
attribute: "offset",
|
|
236
|
+
converter: durationConverter
|
|
237
|
+
})
|
|
238
|
+
], TemporalMixinClass.prototype, "_offsetMs", 2);
|
|
239
|
+
__decorateClass([
|
|
240
|
+
property({
|
|
241
|
+
type: Number,
|
|
242
|
+
attribute: "duration",
|
|
243
|
+
converter: durationConverter
|
|
244
|
+
})
|
|
245
|
+
], TemporalMixinClass.prototype, "_durationMs", 2);
|
|
246
|
+
__decorateClass([
|
|
247
|
+
property({
|
|
248
|
+
type: Number,
|
|
249
|
+
attribute: "trimstart",
|
|
250
|
+
converter: durationConverter
|
|
251
|
+
})
|
|
252
|
+
], TemporalMixinClass.prototype, "_trimStartMs", 2);
|
|
253
|
+
__decorateClass([
|
|
254
|
+
property({
|
|
255
|
+
type: Number,
|
|
256
|
+
attribute: "trimend",
|
|
257
|
+
converter: durationConverter
|
|
258
|
+
})
|
|
259
|
+
], TemporalMixinClass.prototype, "_trimEndMs", 2);
|
|
260
|
+
__decorateClass([
|
|
261
|
+
property({
|
|
262
|
+
type: Number,
|
|
263
|
+
attribute: "startoffset",
|
|
264
|
+
converter: durationConverter
|
|
265
|
+
})
|
|
266
|
+
], TemporalMixinClass.prototype, "_startOffsetMs", 2);
|
|
267
|
+
__decorateClass([
|
|
268
|
+
state()
|
|
269
|
+
], TemporalMixinClass.prototype, "rootTimegroup", 2);
|
|
270
|
+
Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, {
|
|
271
|
+
value: true
|
|
272
|
+
});
|
|
273
|
+
return TemporalMixinClass;
|
|
274
|
+
};
|
|
275
|
+
export {
|
|
276
|
+
EFTemporal,
|
|
277
|
+
OwnCurrentTimeController,
|
|
278
|
+
deepGetElementsWithFrameTasks,
|
|
279
|
+
deepGetTemporalElements,
|
|
280
|
+
isEFTemporal,
|
|
281
|
+
shallowGetTemporalElements,
|
|
282
|
+
timegroupContext
|
|
283
|
+
};
|