@editframe/elements 0.5.0-beta.9 → 0.6.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/lib/av/EncodedAsset.cjs +561 -0
- package/dist/lib/av/{EncodedAsset.mjs → EncodedAsset.js} +18 -13
- package/dist/lib/av/MP4File.cjs +182 -0
- package/dist/lib/av/{MP4File.mjs → MP4File.js} +15 -10
- package/dist/lib/av/msToTimeCode.cjs +15 -0
- package/dist/lib/util/awaitMicrotask.cjs +8 -0
- package/dist/lib/util/memoize.cjs +14 -0
- package/dist/packages/elements/src/EF_FRAMEGEN.cjs +197 -0
- package/dist/packages/elements/src/EF_FRAMEGEN.d.ts +45 -0
- package/dist/packages/elements/src/{EF_FRAMEGEN.mjs → EF_FRAMEGEN.js} +21 -7
- package/dist/packages/elements/src/EF_INTERACTIVE.cjs +4 -0
- package/dist/packages/elements/src/EF_INTERACTIVE.d.ts +1 -0
- package/dist/packages/elements/src/elements/CrossUpdateController.cjs +16 -0
- package/dist/packages/elements/src/elements/CrossUpdateController.d.ts +9 -0
- package/dist/packages/elements/src/elements/EFAudio.cjs +53 -0
- package/dist/packages/elements/src/elements/EFAudio.d.ts +10 -0
- package/dist/packages/elements/src/elements/{EFAudio.mjs → EFAudio.js} +1 -1
- package/dist/packages/elements/src/elements/EFCaptions.cjs +171 -0
- package/dist/packages/elements/src/elements/EFCaptions.d.ts +39 -0
- package/dist/packages/elements/src/elements/{EFCaptions.mjs → EFCaptions.js} +13 -13
- package/dist/packages/elements/src/elements/EFImage.cjs +79 -0
- package/dist/packages/elements/src/elements/EFImage.d.ts +14 -0
- package/dist/packages/elements/src/elements/{EFImage.mjs → EFImage.js} +6 -4
- package/dist/packages/elements/src/elements/EFMedia.cjs +334 -0
- package/dist/packages/elements/src/elements/EFMedia.d.ts +61 -0
- package/dist/packages/elements/src/elements/{EFMedia.mjs → EFMedia.js} +29 -18
- package/dist/packages/elements/src/elements/EFSourceMixin.cjs +55 -0
- package/dist/packages/elements/src/elements/EFSourceMixin.d.ts +12 -0
- package/dist/packages/elements/src/elements/{EFSourceMixin.mjs → EFSourceMixin.js} +1 -1
- package/dist/packages/elements/src/elements/EFTemporal.cjs +198 -0
- package/dist/packages/elements/src/elements/EFTemporal.d.ts +36 -0
- package/dist/packages/elements/src/elements/{EFTemporal.mjs → EFTemporal.js} +4 -7
- package/dist/packages/elements/src/elements/EFTimegroup.browsertest.d.ts +12 -0
- package/dist/packages/elements/src/elements/EFTimegroup.cjs +343 -0
- package/dist/packages/elements/src/elements/EFTimegroup.d.ts +39 -0
- package/dist/packages/elements/src/elements/{EFTimegroup.mjs → EFTimegroup.js} +23 -22
- package/dist/packages/elements/src/elements/EFTimeline.cjs +15 -0
- package/dist/packages/elements/src/elements/EFTimeline.d.ts +3 -0
- package/dist/packages/elements/src/elements/{EFTimeline.mjs → EFTimeline.js} +5 -2
- package/dist/packages/elements/src/elements/EFVideo.cjs +110 -0
- package/dist/packages/elements/src/elements/EFVideo.d.ts +14 -0
- package/dist/packages/elements/src/elements/{EFVideo.mjs → EFVideo.js} +2 -2
- package/dist/packages/elements/src/elements/EFWaveform.cjs +235 -0
- package/dist/packages/elements/src/elements/EFWaveform.d.ts +28 -0
- package/dist/packages/elements/src/elements/{EFWaveform.mjs → EFWaveform.js} +14 -14
- package/dist/packages/elements/src/elements/FetchMixin.cjs +28 -0
- package/dist/packages/elements/src/elements/FetchMixin.d.ts +8 -0
- package/dist/packages/elements/src/elements/{FetchMixin.mjs → FetchMixin.js} +1 -1
- package/dist/packages/elements/src/elements/TimegroupController.cjs +20 -0
- package/dist/packages/elements/src/elements/TimegroupController.d.ts +14 -0
- package/dist/packages/elements/src/elements/durationConverter.cjs +8 -0
- package/dist/packages/elements/src/elements/durationConverter.d.ts +4 -0
- package/dist/packages/elements/src/elements/{durationConverter.mjs → durationConverter.js} +1 -1
- package/dist/packages/elements/src/elements/parseTimeToMs.cjs +12 -0
- package/dist/packages/elements/src/elements/parseTimeToMs.d.ts +1 -0
- package/dist/packages/elements/src/elements/parseTimeToMs.js +12 -0
- package/dist/packages/elements/src/elements/util.cjs +11 -0
- package/dist/packages/elements/src/elements/util.d.ts +4 -0
- package/dist/packages/elements/src/elements/{util.mjs → util.js} +1 -1
- package/dist/packages/elements/src/gui/EFFilmstrip.cjs +675 -0
- package/dist/packages/elements/src/gui/EFFilmstrip.d.ts +138 -0
- package/dist/packages/elements/src/gui/{EFFilmstrip.mjs → EFFilmstrip.js} +48 -34
- package/dist/packages/elements/src/gui/EFWorkbench.cjs +199 -0
- package/dist/packages/elements/src/gui/EFWorkbench.d.ts +44 -0
- package/dist/packages/elements/src/gui/{EFWorkbench.mjs → EFWorkbench.js} +26 -27
- package/dist/packages/elements/src/gui/TWMixin.cjs +28 -0
- package/dist/packages/elements/src/gui/TWMixin.css.cjs +3 -0
- package/dist/packages/elements/src/gui/TWMixin.d.ts +3 -0
- package/dist/packages/elements/src/gui/{TWMixin.mjs → TWMixin.js} +4 -3
- package/dist/packages/elements/src/index.cjs +47 -0
- package/dist/packages/elements/src/index.d.ts +10 -0
- package/dist/packages/elements/src/index.js +23 -0
- package/package.json +17 -3
- package/dist/packages/elements/src/elements/parseTimeToMs.mjs +0 -13
- package/dist/packages/elements/src/elements.mjs +0 -12
- /package/dist/lib/av/{msToTimeCode.mjs → msToTimeCode.js} +0 -0
- /package/dist/lib/util/{awaitMicrotask.mjs → awaitMicrotask.js} +0 -0
- /package/dist/lib/util/{memoize.mjs → memoize.js} +0 -0
- /package/dist/packages/elements/src/{EF_INTERACTIVE.mjs → EF_INTERACTIVE.js} +0 -0
- /package/dist/packages/elements/src/elements/{CrossUpdateController.mjs → CrossUpdateController.js} +0 -0
- /package/dist/packages/elements/src/elements/{TimegroupController.mjs → TimegroupController.js} +0 -0
- /package/dist/packages/elements/src/gui/{TWMixin.css.mjs → TWMixin.css.js} +0 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const lit = require("lit");
|
|
4
|
+
const EFTemporal = require("./EFTemporal.cjs");
|
|
5
|
+
const decorators_js = require("lit/decorators.js");
|
|
6
|
+
const deepEquals_js = require("@lit/task/deep-equals.js");
|
|
7
|
+
const task = require("@lit/task");
|
|
8
|
+
const MP4File = require("../../../../lib/av/MP4File.cjs");
|
|
9
|
+
const util = require("./util.cjs");
|
|
10
|
+
const EncodedAsset = require("../../../../lib/av/EncodedAsset.cjs");
|
|
11
|
+
const FetchMixin = require("./FetchMixin.cjs");
|
|
12
|
+
const EFWorkbench = require("../gui/EFWorkbench.cjs");
|
|
13
|
+
const context = require("@lit/context");
|
|
14
|
+
const EFSourceMixin = require("./EFSourceMixin.cjs");
|
|
15
|
+
const EF_INTERACTIVE = require("../EF_INTERACTIVE.cjs");
|
|
16
|
+
var __defProp = Object.defineProperty;
|
|
17
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
18
|
+
var result = void 0;
|
|
19
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
20
|
+
if (decorator = decorators[i])
|
|
21
|
+
result = decorator(target, key, result) || result;
|
|
22
|
+
if (result) __defProp(target, key, result);
|
|
23
|
+
return result;
|
|
24
|
+
};
|
|
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.EFSourceMixin(EFTemporal.EFTemporal(FetchMixin.FetchMixin(lit.LitElement)), {
|
|
36
|
+
assetType: "isobmff_files"
|
|
37
|
+
}) {
|
|
38
|
+
constructor() {
|
|
39
|
+
super(...arguments);
|
|
40
|
+
this.currentTimeMs = 0;
|
|
41
|
+
this.trackFragmentIndexLoader = new task.Task(this, {
|
|
42
|
+
args: () => [this.fragmentIndexPath(), this.fetch],
|
|
43
|
+
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
44
|
+
const response = await fetch(fragmentIndexPath, { signal });
|
|
45
|
+
return await response.json();
|
|
46
|
+
},
|
|
47
|
+
onComplete: () => {
|
|
48
|
+
this.requestUpdate("ownCurrentTimeMs");
|
|
49
|
+
this.parentTimegroup?.requestUpdate("ownCurrentTimeMs");
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
this.initSegmentsLoader = new task.Task(this, {
|
|
53
|
+
autoRun: EF_INTERACTIVE.EF_INTERACTIVE,
|
|
54
|
+
args: () => [this.trackFragmentIndexLoader.value, this.src, this.fetch],
|
|
55
|
+
task: async ([fragmentIndex, _src, fetch], { signal }) => {
|
|
56
|
+
if (!fragmentIndex) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
return await Promise.all(
|
|
60
|
+
Object.entries(fragmentIndex).map(async ([trackId, track]) => {
|
|
61
|
+
const start = track.initSegment.offset;
|
|
62
|
+
const end = track.initSegment.offset + track.initSegment.size - 1;
|
|
63
|
+
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
64
|
+
signal,
|
|
65
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
66
|
+
});
|
|
67
|
+
const buffer = await response.arrayBuffer();
|
|
68
|
+
buffer.fileStart = 0;
|
|
69
|
+
const mp4File = new MP4File.MP4File();
|
|
70
|
+
mp4File.appendBuffer(buffer, true);
|
|
71
|
+
mp4File.flush();
|
|
72
|
+
await mp4File.readyPromise;
|
|
73
|
+
return { trackId, buffer, mp4File };
|
|
74
|
+
})
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
this.seekTask = new task.Task(this, {
|
|
79
|
+
autoRun: EF_INTERACTIVE.EF_INTERACTIVE,
|
|
80
|
+
args: () => [
|
|
81
|
+
this.desiredSeekTimeMs,
|
|
82
|
+
this.trackFragmentIndexLoader.value,
|
|
83
|
+
this.initSegmentsLoader.value
|
|
84
|
+
],
|
|
85
|
+
task: async ([seekToMs, fragmentIndex, initSegments], { signal: _signal }) => {
|
|
86
|
+
if (fragmentIndex === void 0) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (initSegments === void 0) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const result = {};
|
|
93
|
+
for (const index of Object.values(fragmentIndex)) {
|
|
94
|
+
const track = initSegments.find((segment2) => segment2.trackId === String(index.track))?.mp4File.getInfo().tracks[0];
|
|
95
|
+
if (!track) {
|
|
96
|
+
throw new Error("Could not finding matching track");
|
|
97
|
+
}
|
|
98
|
+
const segment = index.segments.toReversed().find((segment2) => {
|
|
99
|
+
return segment2.dts / track.timescale * 1e3 <= seekToMs;
|
|
100
|
+
});
|
|
101
|
+
if (!segment) {
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
result[index.track] = { segment, track };
|
|
105
|
+
}
|
|
106
|
+
return result;
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
this.fetchSeekTask = new task.Task(this, {
|
|
110
|
+
autoRun: EF_INTERACTIVE.EF_INTERACTIVE,
|
|
111
|
+
argsEqual: deepEquals_js.deepArrayEquals,
|
|
112
|
+
args: () => [this.initSegmentsLoader.value, this.seekTask.value, this.fetch],
|
|
113
|
+
task: async ([initSegments, seekResult, fetch], { signal }) => {
|
|
114
|
+
if (!initSegments) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!seekResult) {
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const files = {};
|
|
121
|
+
for (const [trackId, { segment, track }] of Object.entries(seekResult)) {
|
|
122
|
+
const start = segment.offset;
|
|
123
|
+
const end = segment.offset + segment.size;
|
|
124
|
+
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
125
|
+
signal,
|
|
126
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
127
|
+
});
|
|
128
|
+
const initSegment = Object.values(initSegments).find(
|
|
129
|
+
(initSegment2) => initSegment2.trackId === String(track.id)
|
|
130
|
+
);
|
|
131
|
+
if (!initSegment) {
|
|
132
|
+
throw new Error("Could not find matching init segment");
|
|
133
|
+
}
|
|
134
|
+
const initBuffer = initSegment.buffer;
|
|
135
|
+
const mediaBuffer = await response.arrayBuffer();
|
|
136
|
+
files[trackId] = new File([initBuffer, mediaBuffer], "video.mp4", {
|
|
137
|
+
type: "video/mp4"
|
|
138
|
+
});
|
|
139
|
+
}
|
|
140
|
+
return files;
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
this.videoAssetTask = new task.Task(this, {
|
|
144
|
+
autoRun: EF_INTERACTIVE.EF_INTERACTIVE,
|
|
145
|
+
args: () => [this.fetchSeekTask.value],
|
|
146
|
+
task: async ([files], { signal: _signal }) => {
|
|
147
|
+
if (!files) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
if (!this.defaultVideoTrackId) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
const videoFile = files[this.defaultVideoTrackId];
|
|
154
|
+
if (!videoFile) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
for (const frame of this.videoAssetTask.value?.decodedFrames || []) {
|
|
158
|
+
frame.close();
|
|
159
|
+
}
|
|
160
|
+
this.videoAssetTask.value?.videoDecoder?.close();
|
|
161
|
+
return await EncodedAsset.VideoAsset.createFromReadableStream(
|
|
162
|
+
"video.mp4",
|
|
163
|
+
videoFile.stream(),
|
|
164
|
+
videoFile
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
});
|
|
168
|
+
this.desiredSeekTimeMs = 0;
|
|
169
|
+
this.#audioContext = new OfflineAudioContext(2, 48e3 / 30, 48e3);
|
|
170
|
+
this.audioBufferTask = new task.Task(this, {
|
|
171
|
+
autoRun: EF_INTERACTIVE.EF_INTERACTIVE,
|
|
172
|
+
args: () => [this.fetchSeekTask.value, this.seekTask.value],
|
|
173
|
+
task: async ([files, segments], { signal: _signal }) => {
|
|
174
|
+
if (!files) {
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (!segments) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (!this.defaultAudioTrackId) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
const segment = segments[this.defaultAudioTrackId];
|
|
184
|
+
if (!segment) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const audioFile = files[this.defaultAudioTrackId];
|
|
188
|
+
if (!audioFile) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
buffer: await this.#audioContext.decodeAudioData(
|
|
193
|
+
await audioFile.arrayBuffer()
|
|
194
|
+
),
|
|
195
|
+
startOffsetMs: segment.segment.cts / segment.track.timescale * 1e3
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
static {
|
|
201
|
+
this.styles = [
|
|
202
|
+
lit.css`
|
|
203
|
+
:host {
|
|
204
|
+
display: block;
|
|
205
|
+
position: relative;
|
|
206
|
+
overflow: hidden;
|
|
207
|
+
}
|
|
208
|
+
`
|
|
209
|
+
];
|
|
210
|
+
}
|
|
211
|
+
fragmentIndexPath() {
|
|
212
|
+
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
213
|
+
return `${this.src}/index`;
|
|
214
|
+
}
|
|
215
|
+
return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
|
|
216
|
+
}
|
|
217
|
+
fragmentTrackPath(trackId) {
|
|
218
|
+
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
219
|
+
return `${this.src.replace("files", "tracks")}/${trackId}`;
|
|
220
|
+
}
|
|
221
|
+
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
222
|
+
}
|
|
223
|
+
get defaultVideoTrackId() {
|
|
224
|
+
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
225
|
+
(track) => track.type === "video"
|
|
226
|
+
)?.track;
|
|
227
|
+
}
|
|
228
|
+
get defaultAudioTrackId() {
|
|
229
|
+
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
230
|
+
(track) => track.type === "audio"
|
|
231
|
+
)?.track;
|
|
232
|
+
}
|
|
233
|
+
async executeSeek(seekToMs) {
|
|
234
|
+
this.desiredSeekTimeMs = seekToMs;
|
|
235
|
+
}
|
|
236
|
+
updated(changedProperties) {
|
|
237
|
+
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
238
|
+
this.executeSeek(this.ownCurrentTimeMs);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
get hasOwnDuration() {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
get durationMs() {
|
|
245
|
+
if (!this.trackFragmentIndexLoader.value) {
|
|
246
|
+
return 0;
|
|
247
|
+
}
|
|
248
|
+
const durations = Object.values(this.trackFragmentIndexLoader.value).map(
|
|
249
|
+
(track) => {
|
|
250
|
+
return track.duration / track.timescale * 1e3;
|
|
251
|
+
}
|
|
252
|
+
);
|
|
253
|
+
if (durations.length === 0) {
|
|
254
|
+
return 0;
|
|
255
|
+
}
|
|
256
|
+
return Math.max(...durations);
|
|
257
|
+
}
|
|
258
|
+
get startTimeMs() {
|
|
259
|
+
return util.getStartTimeMs(this);
|
|
260
|
+
}
|
|
261
|
+
#audioContext;
|
|
262
|
+
async fetchAudioSpanningTime(fromMs, toMs) {
|
|
263
|
+
fromMs -= this.startTimeMs;
|
|
264
|
+
toMs -= this.startTimeMs;
|
|
265
|
+
await this.trackFragmentIndexLoader.taskComplete;
|
|
266
|
+
const audioTrackId = this.defaultAudioTrackId;
|
|
267
|
+
if (!audioTrackId) {
|
|
268
|
+
console.warn("No audio track found");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
const audioTrackIndex = this.trackFragmentIndexLoader.value?.[audioTrackId];
|
|
272
|
+
if (!audioTrackIndex) {
|
|
273
|
+
console.warn("No audio track found");
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
const start = audioTrackIndex.initSegment.offset;
|
|
277
|
+
const end = audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size - 1;
|
|
278
|
+
const audioInitFragmentRequest = this.fetch(
|
|
279
|
+
this.fragmentTrackPath(String(audioTrackId)),
|
|
280
|
+
{
|
|
281
|
+
headers: { Range: `bytes=${start}-${end}` }
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
const fragments = Object.values(audioTrackIndex.segments).filter(
|
|
285
|
+
(segment) => {
|
|
286
|
+
const segmentStartsBeforeEnd = segment.dts <= toMs * audioTrackIndex.timescale / 1e3;
|
|
287
|
+
const segmentEndsAfterStart = segment.dts + segment.duration >= fromMs * audioTrackIndex.timescale / 1e3;
|
|
288
|
+
return segmentStartsBeforeEnd && segmentEndsAfterStart;
|
|
289
|
+
}
|
|
290
|
+
);
|
|
291
|
+
const firstFragment = fragments[0];
|
|
292
|
+
if (!firstFragment) {
|
|
293
|
+
console.warn("No audio fragments found");
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
const lastFragment = fragments[fragments.length - 1];
|
|
297
|
+
if (!lastFragment) {
|
|
298
|
+
console.warn("No audio fragments found");
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
const fragmentStart = firstFragment.offset;
|
|
302
|
+
const fragmentEnd = lastFragment.offset + lastFragment.size - 1;
|
|
303
|
+
const audioFragmentRequest = this.fetch(
|
|
304
|
+
this.fragmentTrackPath(String(audioTrackId)),
|
|
305
|
+
{
|
|
306
|
+
headers: { Range: `bytes=${fragmentStart}-${fragmentEnd}` }
|
|
307
|
+
}
|
|
308
|
+
);
|
|
309
|
+
const initResponse = await audioInitFragmentRequest;
|
|
310
|
+
const dataResponse = await audioFragmentRequest;
|
|
311
|
+
const initBuffer = await initResponse.arrayBuffer();
|
|
312
|
+
const dataBuffer = await dataResponse.arrayBuffer();
|
|
313
|
+
const audioBlob = new Blob([initBuffer, dataBuffer], {
|
|
314
|
+
type: "audio/mp4"
|
|
315
|
+
});
|
|
316
|
+
return {
|
|
317
|
+
blob: audioBlob,
|
|
318
|
+
startMs: firstFragment.dts / audioTrackIndex.timescale * 1e3,
|
|
319
|
+
endMs: lastFragment.dts / audioTrackIndex.timescale * 1e3 + lastFragment.duration / audioTrackIndex.timescale * 1e3
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
__decorateClass([
|
|
324
|
+
decorators_js.property({ type: Number })
|
|
325
|
+
], EFMedia.prototype, "currentTimeMs");
|
|
326
|
+
__decorateClass([
|
|
327
|
+
context.consume({ context: EFWorkbench.apiHostContext, subscribe: true }),
|
|
328
|
+
decorators_js.state()
|
|
329
|
+
], EFMedia.prototype, "efHost");
|
|
330
|
+
__decorateClass([
|
|
331
|
+
decorators_js.state()
|
|
332
|
+
], EFMedia.prototype, "desiredSeekTimeMs");
|
|
333
|
+
exports.EFMedia = EFMedia;
|
|
334
|
+
exports.deepGetMediaElements = deepGetMediaElements;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { LitElement, PropertyValueMap } from 'lit';
|
|
2
|
+
import { Task } from '@lit/task';
|
|
3
|
+
import { MP4File } from '../../../../lib/av/MP4File';
|
|
4
|
+
import { TrackFragmentIndex, TrackSegment } from '../../../assets';
|
|
5
|
+
import { VideoAsset } from '../../../../lib/av/EncodedAsset';
|
|
6
|
+
|
|
7
|
+
import type * as MP4Box from "mp4box";
|
|
8
|
+
export declare const deepGetMediaElements: (element: Element, medias?: EFMedia[]) => EFMedia[];
|
|
9
|
+
declare const EFMedia_base: (new (...args: any[]) => import('./EFSourceMixin').EFSourceMixinInterface) & (new (...args: any[]) => import('./EFTemporal').TemporalMixinInterface) & (new (...args: any[]) => import('./FetchMixin').FetchMixinInterface) & typeof LitElement;
|
|
10
|
+
export declare class EFMedia extends EFMedia_base {
|
|
11
|
+
#private;
|
|
12
|
+
static styles: import('lit').CSSResult[];
|
|
13
|
+
currentTimeMs: number;
|
|
14
|
+
efHost?: string;
|
|
15
|
+
fragmentIndexPath(): string;
|
|
16
|
+
fragmentTrackPath(trackId: string): string;
|
|
17
|
+
trackFragmentIndexLoader: Task<readonly [string, typeof fetch], Record<number, TrackFragmentIndex>>;
|
|
18
|
+
protected initSegmentsLoader: Task<readonly [Record<number, TrackFragmentIndex> | undefined, string, typeof fetch], {
|
|
19
|
+
trackId: string;
|
|
20
|
+
buffer: MP4Box.MP4ArrayBuffer;
|
|
21
|
+
mp4File: MP4File;
|
|
22
|
+
}[] | undefined>;
|
|
23
|
+
get defaultVideoTrackId(): number | undefined;
|
|
24
|
+
get defaultAudioTrackId(): number | undefined;
|
|
25
|
+
seekTask: Task<readonly [number, Record<number, TrackFragmentIndex> | undefined, {
|
|
26
|
+
trackId: string;
|
|
27
|
+
buffer: MP4Box.MP4ArrayBuffer;
|
|
28
|
+
mp4File: MP4File;
|
|
29
|
+
}[] | undefined], Record<string, {
|
|
30
|
+
segment: TrackSegment;
|
|
31
|
+
track: MP4Box.TrackInfo;
|
|
32
|
+
}> | undefined>;
|
|
33
|
+
fetchSeekTask: Task<readonly [{
|
|
34
|
+
trackId: string;
|
|
35
|
+
buffer: MP4Box.MP4ArrayBuffer;
|
|
36
|
+
mp4File: MP4File;
|
|
37
|
+
}[] | undefined, Record<string, {
|
|
38
|
+
segment: TrackSegment;
|
|
39
|
+
track: MP4Box.TrackInfo;
|
|
40
|
+
}> | undefined, typeof fetch], Record<string, File> | undefined>;
|
|
41
|
+
videoAssetTask: Task<readonly [Record<string, File> | undefined], VideoAsset | undefined>;
|
|
42
|
+
desiredSeekTimeMs: number;
|
|
43
|
+
protected executeSeek(seekToMs: number): Promise<void>;
|
|
44
|
+
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
45
|
+
get hasOwnDuration(): boolean;
|
|
46
|
+
get durationMs(): number;
|
|
47
|
+
get startTimeMs(): number;
|
|
48
|
+
audioBufferTask: Task<readonly [Record<string, File> | undefined, Record<string, {
|
|
49
|
+
segment: TrackSegment;
|
|
50
|
+
track: MP4Box.TrackInfo;
|
|
51
|
+
}> | undefined], {
|
|
52
|
+
buffer: AudioBuffer;
|
|
53
|
+
startOffsetMs: number;
|
|
54
|
+
} | undefined>;
|
|
55
|
+
fetchAudioSpanningTime(fromMs: number, toMs: number): Promise<{
|
|
56
|
+
blob: Blob;
|
|
57
|
+
startMs: number;
|
|
58
|
+
endMs: number;
|
|
59
|
+
} | undefined>;
|
|
60
|
+
}
|
|
61
|
+
export {};
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { css, LitElement } from "lit";
|
|
2
|
-
import { EFTemporal } from "./EFTemporal.
|
|
2
|
+
import { EFTemporal } from "./EFTemporal.js";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
5
5
|
import { Task } from "@lit/task";
|
|
6
|
-
import { MP4File } from "../../../../lib/av/MP4File.
|
|
7
|
-
import { getStartTimeMs } from "./util.
|
|
8
|
-
import { VideoAsset } from "../../../../lib/av/EncodedAsset.
|
|
9
|
-
import { FetchMixin } from "./FetchMixin.
|
|
10
|
-
import { apiHostContext } from "../gui/EFWorkbench.
|
|
6
|
+
import { MP4File } from "../../../../lib/av/MP4File.js";
|
|
7
|
+
import { getStartTimeMs } from "./util.js";
|
|
8
|
+
import { VideoAsset } from "../../../../lib/av/EncodedAsset.js";
|
|
9
|
+
import { FetchMixin } from "./FetchMixin.js";
|
|
10
|
+
import { apiHostContext } from "../gui/EFWorkbench.js";
|
|
11
11
|
import { consume } from "@lit/context";
|
|
12
|
-
import { EFSourceMixin } from "./EFSourceMixin.
|
|
13
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.
|
|
12
|
+
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
13
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
14
14
|
var __defProp = Object.defineProperty;
|
|
15
15
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
16
16
|
var result = void 0;
|
|
@@ -80,7 +80,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
80
80
|
this.trackFragmentIndexLoader.value,
|
|
81
81
|
this.initSegmentsLoader.value
|
|
82
82
|
],
|
|
83
|
-
task: async ([seekToMs, fragmentIndex, initSegments], { signal }) => {
|
|
83
|
+
task: async ([seekToMs, fragmentIndex, initSegments], { signal: _signal }) => {
|
|
84
84
|
if (fragmentIndex === void 0) {
|
|
85
85
|
return;
|
|
86
86
|
}
|
|
@@ -88,7 +88,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
90
90
|
const result = {};
|
|
91
|
-
Object.values(fragmentIndex)
|
|
91
|
+
for (const index of Object.values(fragmentIndex)) {
|
|
92
92
|
const track = initSegments.find((segment2) => segment2.trackId === String(index.track))?.mp4File.getInfo().tracks[0];
|
|
93
93
|
if (!track) {
|
|
94
94
|
throw new Error("Could not finding matching track");
|
|
@@ -100,7 +100,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
result[index.track] = { segment, track };
|
|
103
|
-
}
|
|
103
|
+
}
|
|
104
104
|
return result;
|
|
105
105
|
}
|
|
106
106
|
});
|
|
@@ -126,6 +126,9 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
126
126
|
const initSegment = Object.values(initSegments).find(
|
|
127
127
|
(initSegment2) => initSegment2.trackId === String(track.id)
|
|
128
128
|
);
|
|
129
|
+
if (!initSegment) {
|
|
130
|
+
throw new Error("Could not find matching init segment");
|
|
131
|
+
}
|
|
129
132
|
const initBuffer = initSegment.buffer;
|
|
130
133
|
const mediaBuffer = await response.arrayBuffer();
|
|
131
134
|
files[trackId] = new File([initBuffer, mediaBuffer], "video.mp4", {
|
|
@@ -138,7 +141,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
138
141
|
this.videoAssetTask = new Task(this, {
|
|
139
142
|
autoRun: EF_INTERACTIVE,
|
|
140
143
|
args: () => [this.fetchSeekTask.value],
|
|
141
|
-
task: async ([files], { signal }) => {
|
|
144
|
+
task: async ([files], { signal: _signal }) => {
|
|
142
145
|
if (!files) {
|
|
143
146
|
return;
|
|
144
147
|
}
|
|
@@ -149,9 +152,9 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
149
152
|
if (!videoFile) {
|
|
150
153
|
return;
|
|
151
154
|
}
|
|
152
|
-
this.videoAssetTask.value?.decodedFrames
|
|
153
|
-
|
|
154
|
-
|
|
155
|
+
for (const frame of this.videoAssetTask.value?.decodedFrames || []) {
|
|
156
|
+
frame.close();
|
|
157
|
+
}
|
|
155
158
|
this.videoAssetTask.value?.videoDecoder?.close();
|
|
156
159
|
return await VideoAsset.createFromReadableStream(
|
|
157
160
|
"video.mp4",
|
|
@@ -165,7 +168,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
165
168
|
this.audioBufferTask = new Task(this, {
|
|
166
169
|
autoRun: EF_INTERACTIVE,
|
|
167
170
|
args: () => [this.fetchSeekTask.value, this.seekTask.value],
|
|
168
|
-
task: async ([files, segments], { signal }) => {
|
|
171
|
+
task: async ([files, segments], { signal: _signal }) => {
|
|
169
172
|
if (!files) {
|
|
170
173
|
return;
|
|
171
174
|
}
|
|
@@ -205,13 +208,13 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
205
208
|
}
|
|
206
209
|
fragmentIndexPath() {
|
|
207
210
|
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
208
|
-
return this.src
|
|
211
|
+
return `${this.src}/index`;
|
|
209
212
|
}
|
|
210
213
|
return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
|
|
211
214
|
}
|
|
212
215
|
fragmentTrackPath(trackId) {
|
|
213
216
|
if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
|
|
214
|
-
return this.src.replace("files", "tracks")
|
|
217
|
+
return `${this.src.replace("files", "tracks")}/${trackId}`;
|
|
215
218
|
}
|
|
216
219
|
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
217
220
|
}
|
|
@@ -284,7 +287,15 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
284
287
|
}
|
|
285
288
|
);
|
|
286
289
|
const firstFragment = fragments[0];
|
|
290
|
+
if (!firstFragment) {
|
|
291
|
+
console.warn("No audio fragments found");
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
287
294
|
const lastFragment = fragments[fragments.length - 1];
|
|
295
|
+
if (!lastFragment) {
|
|
296
|
+
console.warn("No audio fragments found");
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
288
299
|
const fragmentStart = firstFragment.offset;
|
|
289
300
|
const fragmentEnd = lastFragment.offset + lastFragment.size - 1;
|
|
290
301
|
const audioFragmentRequest = this.fetch(
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const context = require("@lit/context");
|
|
4
|
+
const EFWorkbench = require("../gui/EFWorkbench.cjs");
|
|
5
|
+
const state_js = require("lit/decorators/state.js");
|
|
6
|
+
const task = require("@lit/task");
|
|
7
|
+
const property_js = require("lit/decorators/property.js");
|
|
8
|
+
var __defProp = Object.defineProperty;
|
|
9
|
+
var __decorateClass = (decorators, target, key, kind) => {
|
|
10
|
+
var result = void 0;
|
|
11
|
+
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
12
|
+
if (decorator = decorators[i])
|
|
13
|
+
result = decorator(target, key, result) || result;
|
|
14
|
+
if (result) __defProp(target, key, result);
|
|
15
|
+
return result;
|
|
16
|
+
};
|
|
17
|
+
function EFSourceMixin(superClass, options) {
|
|
18
|
+
class EFSourceElement extends superClass {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
this.src = "";
|
|
22
|
+
this.md5SumLoader = new task.Task(this, {
|
|
23
|
+
autoRun: false,
|
|
24
|
+
args: () => [this.src],
|
|
25
|
+
task: async ([src], { signal }) => {
|
|
26
|
+
const md5Path = `/@ef-asset/${src}`;
|
|
27
|
+
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
28
|
+
return response.headers.get("etag") ?? void 0;
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
productionSrc() {
|
|
33
|
+
if (!this.md5SumLoader.value) {
|
|
34
|
+
throw new Error(
|
|
35
|
+
`MD5 sum not available for ${this}. Cannot generate production URL`
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
if (!this.efHost) {
|
|
39
|
+
throw new Error(
|
|
40
|
+
`efHost not available for ${this}. Cannot generate production URL`
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
return `${this.efHost}/api/video2/${options.assetType}/${this.md5SumLoader.value}`;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
__decorateClass([
|
|
47
|
+
context.consume({ context: EFWorkbench.apiHostContext, subscribe: true }),
|
|
48
|
+
state_js.state()
|
|
49
|
+
], EFSourceElement.prototype, "efHost");
|
|
50
|
+
__decorateClass([
|
|
51
|
+
property_js.property({ type: String })
|
|
52
|
+
], EFSourceElement.prototype, "src");
|
|
53
|
+
return EFSourceElement;
|
|
54
|
+
}
|
|
55
|
+
exports.EFSourceMixin = EFSourceMixin;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
|
|
3
|
+
export declare class EFSourceMixinInterface {
|
|
4
|
+
productionSrc(): string;
|
|
5
|
+
src: string;
|
|
6
|
+
}
|
|
7
|
+
interface EFSourceMixinOptions {
|
|
8
|
+
assetType: string;
|
|
9
|
+
}
|
|
10
|
+
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
11
|
+
export declare function EFSourceMixin<T extends Constructor<LitElement>>(superClass: T, options: EFSourceMixinOptions): Constructor<EFSourceMixinInterface> & T;
|
|
12
|
+
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { consume } from "@lit/context";
|
|
2
|
-
import { apiHostContext } from "../gui/EFWorkbench.
|
|
2
|
+
import { apiHostContext } from "../gui/EFWorkbench.js";
|
|
3
3
|
import { state } from "lit/decorators/state.js";
|
|
4
4
|
import { Task } from "@lit/task";
|
|
5
5
|
import { property } from "lit/decorators/property.js";
|