@editframe/elements 0.5.0-beta.8 → 0.5.0-beta.9
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/{editor/util/EncodedAsset → lib/av}/EncodedAsset.mjs +24 -22
- package/dist/{editor/util → lib/av}/MP4File.mjs +50 -51
- package/dist/{util → lib/util}/memoize.mjs +1 -2
- package/dist/packages/elements/src/EF_FRAMEGEN.mjs +183 -0
- package/dist/{elements → packages/elements}/src/elements/EFAudio.mjs +1 -4
- package/dist/{elements → packages/elements}/src/elements/EFCaptions.mjs +5 -7
- package/dist/{elements → packages/elements}/src/elements/EFImage.mjs +2 -3
- package/dist/{elements → packages/elements}/src/elements/EFMedia.mjs +18 -27
- package/dist/{elements → packages/elements}/src/elements/EFSourceMixin.mjs +5 -7
- package/dist/{elements → packages/elements}/src/elements/EFTemporal.mjs +2 -15
- package/dist/{elements → packages/elements}/src/elements/EFTimegroup.mjs +32 -43
- package/dist/{elements → packages/elements}/src/elements/EFVideo.mjs +8 -30
- package/dist/{elements → packages/elements}/src/elements/EFWaveform.mjs +1 -2
- package/dist/{elements → packages/elements}/src/elements/FetchMixin.mjs +4 -6
- package/dist/{elements → packages/elements}/src/gui/EFFilmstrip.mjs +10 -22
- package/dist/{elements → packages/elements}/src/gui/EFWorkbench.mjs +4 -25
- package/dist/packages/elements/src/gui/TWMixin.css.mjs +4 -0
- package/dist/style.css +13 -4
- package/package.json +7 -2
- package/dist/elements/src/EF_FRAMEGEN.mjs +0 -130
- package/dist/elements/src/gui/TWMixin.css.mjs +0 -4
- package/dist/util/awaitAnimationFrame.mjs +0 -11
- package/docker-compose.yaml +0 -17
- package/src/EF_FRAMEGEN.ts +0 -208
- package/src/EF_INTERACTIVE.ts +0 -2
- package/src/elements/CrossUpdateController.ts +0 -18
- package/src/elements/EFAudio.ts +0 -42
- package/src/elements/EFCaptions.ts +0 -202
- package/src/elements/EFImage.ts +0 -70
- package/src/elements/EFMedia.ts +0 -395
- package/src/elements/EFSourceMixin.ts +0 -57
- package/src/elements/EFTemporal.ts +0 -246
- package/src/elements/EFTimegroup.browsertest.ts +0 -360
- package/src/elements/EFTimegroup.ts +0 -394
- package/src/elements/EFTimeline.ts +0 -13
- package/src/elements/EFVideo.ts +0 -114
- package/src/elements/EFWaveform.ts +0 -407
- package/src/elements/FetchMixin.ts +0 -18
- package/src/elements/TimegroupController.ts +0 -25
- package/src/elements/buildLitFixture.ts +0 -13
- package/src/elements/durationConverter.ts +0 -6
- package/src/elements/parseTimeToMs.ts +0 -10
- package/src/elements/util.ts +0 -24
- package/src/gui/EFFilmstrip.ts +0 -702
- package/src/gui/EFWorkbench.ts +0 -242
- package/src/gui/TWMixin.css +0 -3
- package/src/gui/TWMixin.ts +0 -27
- package/src/util.d.ts +0 -1
- /package/dist/{editor → lib/av}/msToTimeCode.mjs +0 -0
- /package/dist/{util → lib/util}/awaitMicrotask.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/EF_INTERACTIVE.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/CrossUpdateController.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/EFTimeline.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/TimegroupController.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/durationConverter.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/parseTimeToMs.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements/util.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/elements.mjs +0 -0
- /package/dist/{elements → packages/elements}/src/gui/TWMixin.mjs +0 -0
package/src/elements/EFImage.ts
DELETED
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
import { Task } from "@lit/task";
|
|
2
|
-
import { LitElement, html, css } from "lit";
|
|
3
|
-
import { customElement } from "lit/decorators.js";
|
|
4
|
-
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
|
-
import { FetchMixin } from "./FetchMixin";
|
|
6
|
-
import { EFSourceMixin } from "./EFSourceMixin";
|
|
7
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
|
|
8
|
-
|
|
9
|
-
@customElement("ef-image")
|
|
10
|
-
export class EFImage extends EFSourceMixin(FetchMixin(LitElement), {
|
|
11
|
-
assetType: "image_files",
|
|
12
|
-
}) {
|
|
13
|
-
static styles = [
|
|
14
|
-
css`
|
|
15
|
-
:host {
|
|
16
|
-
display: block;
|
|
17
|
-
}
|
|
18
|
-
canvas {
|
|
19
|
-
display: block;
|
|
20
|
-
width: 100%;
|
|
21
|
-
height: 100%;
|
|
22
|
-
object-fit: fill;
|
|
23
|
-
object-position: center;
|
|
24
|
-
}
|
|
25
|
-
`,
|
|
26
|
-
];
|
|
27
|
-
|
|
28
|
-
imageRef = createRef<HTMLImageElement>();
|
|
29
|
-
canvasRef = createRef<HTMLCanvasElement>();
|
|
30
|
-
|
|
31
|
-
render() {
|
|
32
|
-
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
assetPath() {
|
|
36
|
-
if (this.src.startsWith("http")) {
|
|
37
|
-
return this.src;
|
|
38
|
-
}
|
|
39
|
-
return `/@ef-image/${this.src}`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
// get requiredAssets() {
|
|
43
|
-
// return { [this.md5SumLoader.value]: [this.assetPath()] };
|
|
44
|
-
// }
|
|
45
|
-
|
|
46
|
-
fetchImage = new Task(this, {
|
|
47
|
-
autoRun: EF_INTERACTIVE,
|
|
48
|
-
args: () => [this.assetPath(), this.fetch] as const,
|
|
49
|
-
task: async ([assetPath, fetch], { signal }) => {
|
|
50
|
-
const response = await fetch(assetPath, { signal });
|
|
51
|
-
const image = new Image();
|
|
52
|
-
image.src = URL.createObjectURL(await response.blob());
|
|
53
|
-
await new Promise((resolve) => {
|
|
54
|
-
image.onload = resolve;
|
|
55
|
-
});
|
|
56
|
-
this.canvasRef.value!.width = image.width;
|
|
57
|
-
this.canvasRef.value!.height = image.height;
|
|
58
|
-
const ctx = this.canvasRef.value!.getContext("2d")!;
|
|
59
|
-
ctx.drawImage(image, 0, 0);
|
|
60
|
-
},
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
frameTask = new Task(this, {
|
|
64
|
-
autoRun: EF_INTERACTIVE,
|
|
65
|
-
args: () => [this.fetchImage.status] as const,
|
|
66
|
-
task: async () => {
|
|
67
|
-
await this.fetchImage.taskComplete;
|
|
68
|
-
},
|
|
69
|
-
});
|
|
70
|
-
}
|
package/src/elements/EFMedia.ts
DELETED
|
@@ -1,395 +0,0 @@
|
|
|
1
|
-
import { LitElement, PropertyValueMap, css } from "lit";
|
|
2
|
-
import { EFTemporal } from "./EFTemporal";
|
|
3
|
-
import { property, state } from "lit/decorators.js";
|
|
4
|
-
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
5
|
-
import { Task } from "@lit/task";
|
|
6
|
-
import * as MP4Box from "mp4box";
|
|
7
|
-
import { MP4File } from "@/editor/util/MP4File";
|
|
8
|
-
import { TrackFragmentIndex, TrackSegment } from "@/assets/Probe";
|
|
9
|
-
import { getStartTimeMs } from "./util";
|
|
10
|
-
import { VideoAsset } from "@/editor/util/EncodedAsset/EncodedAsset";
|
|
11
|
-
import { FetchMixin } from "./FetchMixin";
|
|
12
|
-
import { apiHostContext } from "../gui/EFWorkbench";
|
|
13
|
-
import { consume } from "@lit/context";
|
|
14
|
-
import { EFSourceMixin } from "./EFSourceMixin";
|
|
15
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
|
|
16
|
-
|
|
17
|
-
export const deepGetMediaElements = (
|
|
18
|
-
element: Element,
|
|
19
|
-
medias: EFMedia[] = [],
|
|
20
|
-
) => {
|
|
21
|
-
for (const child of element.children) {
|
|
22
|
-
if (child instanceof EFMedia) {
|
|
23
|
-
medias.push(child);
|
|
24
|
-
} else {
|
|
25
|
-
deepGetMediaElements(child, medias);
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
return medias;
|
|
29
|
-
};
|
|
30
|
-
|
|
31
|
-
export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
32
|
-
assetType: "isobmff_files",
|
|
33
|
-
}) {
|
|
34
|
-
static styles = [
|
|
35
|
-
css`
|
|
36
|
-
:host {
|
|
37
|
-
display: block;
|
|
38
|
-
position: relative;
|
|
39
|
-
overflow: hidden;
|
|
40
|
-
}
|
|
41
|
-
`,
|
|
42
|
-
];
|
|
43
|
-
|
|
44
|
-
@property({ type: Number })
|
|
45
|
-
currentTimeMs = 0;
|
|
46
|
-
|
|
47
|
-
@consume({ context: apiHostContext, subscribe: true })
|
|
48
|
-
@state()
|
|
49
|
-
efHost?: string;
|
|
50
|
-
|
|
51
|
-
// get requiredAssets() {
|
|
52
|
-
// return { [this.md5SumLoader.value]: this.requiredAssetsPath.value ?? [] };
|
|
53
|
-
// }
|
|
54
|
-
|
|
55
|
-
fragmentIndexPath() {
|
|
56
|
-
if (this.getAttribute("src")?.startsWith("http")) {
|
|
57
|
-
return this.getAttribute("src")! + "/index";
|
|
58
|
-
}
|
|
59
|
-
return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
fragmentTrackPath(trackId: string) {
|
|
63
|
-
if (this.getAttribute("src")?.startsWith("http")) {
|
|
64
|
-
return (
|
|
65
|
-
this.getAttribute("src")!.replace("files", "tracks") + `/${trackId}`
|
|
66
|
-
);
|
|
67
|
-
}
|
|
68
|
-
return `/@ef-track/${this.getAttribute("src") ?? ""}?trackId=${trackId}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// requiredAssetsPath = new Task(this, {
|
|
72
|
-
// args: () => [this.trackFragmentIndexLoader.value] as const,
|
|
73
|
-
// task: ([trackFragmentIndex]) => {
|
|
74
|
-
// if (!trackFragmentIndex) {
|
|
75
|
-
// return [];
|
|
76
|
-
// }
|
|
77
|
-
// const paths = [this.fragmentIndexPath()];
|
|
78
|
-
|
|
79
|
-
// Object.values(trackFragmentIndex).forEach((track) => {
|
|
80
|
-
// paths.push(this.fragmentTrackPath(String(track.track)));
|
|
81
|
-
// });
|
|
82
|
-
|
|
83
|
-
// return paths;
|
|
84
|
-
// },
|
|
85
|
-
// });
|
|
86
|
-
|
|
87
|
-
public trackFragmentIndexLoader = new Task(this, {
|
|
88
|
-
args: () => [this.fragmentIndexPath(), this.fetch] as const,
|
|
89
|
-
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
90
|
-
console.log("EFMedia trackFragmentIndexLoader");
|
|
91
|
-
const response = await fetch(fragmentIndexPath, { signal });
|
|
92
|
-
return (await response.json()) as Record<number, TrackFragmentIndex>;
|
|
93
|
-
},
|
|
94
|
-
onComplete: () => {
|
|
95
|
-
this.requestUpdate("ownCurrentTimeMs");
|
|
96
|
-
this.parentTimegroup?.requestUpdate("ownCurrentTimeMs");
|
|
97
|
-
},
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
protected initSegmentsLoader = new Task(this, {
|
|
101
|
-
autoRun: EF_INTERACTIVE,
|
|
102
|
-
args: () =>
|
|
103
|
-
[this.trackFragmentIndexLoader.value, this.src, this.fetch] as const,
|
|
104
|
-
task: async ([fragmentIndex, _src, fetch], { signal }) => {
|
|
105
|
-
console.log("EFMedia initSegmentsLoader");
|
|
106
|
-
if (!fragmentIndex) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
return await Promise.all(
|
|
110
|
-
Object.entries(fragmentIndex).map(async ([trackId, track]) => {
|
|
111
|
-
const start = track.initSegment.offset;
|
|
112
|
-
const end = track.initSegment.offset + track.initSegment.size - 1;
|
|
113
|
-
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
114
|
-
signal,
|
|
115
|
-
headers: { Range: `bytes=${start}-${end}` },
|
|
116
|
-
});
|
|
117
|
-
const buffer =
|
|
118
|
-
(await response.arrayBuffer()) as MP4Box.MP4ArrayBuffer;
|
|
119
|
-
buffer.fileStart = 0;
|
|
120
|
-
const mp4File = new MP4File();
|
|
121
|
-
mp4File.appendBuffer(buffer, true);
|
|
122
|
-
mp4File.flush();
|
|
123
|
-
await mp4File.readyPromise;
|
|
124
|
-
|
|
125
|
-
return { trackId, buffer, mp4File };
|
|
126
|
-
}),
|
|
127
|
-
);
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
|
|
131
|
-
get defaultVideoTrackId() {
|
|
132
|
-
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
133
|
-
(track) => track.type === "video",
|
|
134
|
-
)?.track;
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
get defaultAudioTrackId() {
|
|
138
|
-
return Object.values(this.trackFragmentIndexLoader.value ?? {}).find(
|
|
139
|
-
(track) => track.type === "audio",
|
|
140
|
-
)?.track;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
seekTask = new Task(this, {
|
|
144
|
-
autoRun: EF_INTERACTIVE,
|
|
145
|
-
args: () =>
|
|
146
|
-
[
|
|
147
|
-
this.desiredSeekTimeMs,
|
|
148
|
-
this.trackFragmentIndexLoader.value,
|
|
149
|
-
this.initSegmentsLoader.value,
|
|
150
|
-
] as const,
|
|
151
|
-
task: async ([seekToMs, fragmentIndex, initSegments], { signal }) => {
|
|
152
|
-
console.log("EFMedia seekTask");
|
|
153
|
-
if (fragmentIndex === undefined) {
|
|
154
|
-
return;
|
|
155
|
-
}
|
|
156
|
-
if (initSegments === undefined) {
|
|
157
|
-
return;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
const result: Record<
|
|
161
|
-
string,
|
|
162
|
-
{ segment: TrackSegment; track: MP4Box.TrackInfo }
|
|
163
|
-
> = {};
|
|
164
|
-
|
|
165
|
-
Object.values(fragmentIndex).forEach((index) => {
|
|
166
|
-
const track = initSegments
|
|
167
|
-
.find((segment) => segment.trackId === String(index.track))
|
|
168
|
-
?.mp4File.getInfo().tracks[0];
|
|
169
|
-
|
|
170
|
-
if (!track) {
|
|
171
|
-
throw new Error("Could not finding matching track");
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
const segment = index.segments.toReversed().find((segment) => {
|
|
175
|
-
return (segment.dts / track.timescale) * 1000 <= seekToMs;
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
if (!segment) {
|
|
179
|
-
return;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
result[index.track] = { segment, track };
|
|
183
|
-
});
|
|
184
|
-
|
|
185
|
-
return result;
|
|
186
|
-
},
|
|
187
|
-
});
|
|
188
|
-
|
|
189
|
-
fetchSeekTask = new Task(this, {
|
|
190
|
-
autoRun: EF_INTERACTIVE,
|
|
191
|
-
argsEqual: deepArrayEquals,
|
|
192
|
-
args: () =>
|
|
193
|
-
[this.initSegmentsLoader.value, this.seekTask.value, this.fetch] as const,
|
|
194
|
-
task: async ([initSegments, seekResult, fetch], { signal }) => {
|
|
195
|
-
console.log("EFMedia fetchSeekTask");
|
|
196
|
-
if (!initSegments) {
|
|
197
|
-
return;
|
|
198
|
-
}
|
|
199
|
-
if (!seekResult) {
|
|
200
|
-
return;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
const files: Record<string, File> = {};
|
|
204
|
-
|
|
205
|
-
for (const [trackId, { segment, track }] of Object.entries(seekResult)) {
|
|
206
|
-
const start = segment.offset;
|
|
207
|
-
const end = segment.offset + segment.size;
|
|
208
|
-
|
|
209
|
-
const response = await fetch(this.fragmentTrackPath(trackId), {
|
|
210
|
-
signal,
|
|
211
|
-
headers: { Range: `bytes=${start}-${end}` },
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
const initSegment = Object.values(initSegments).find(
|
|
215
|
-
(initSegment) => initSegment.trackId === String(track.id),
|
|
216
|
-
);
|
|
217
|
-
const initBuffer = initSegment!.buffer;
|
|
218
|
-
|
|
219
|
-
const mediaBuffer =
|
|
220
|
-
(await response.arrayBuffer()) as unknown as MP4Box.MP4ArrayBuffer;
|
|
221
|
-
|
|
222
|
-
files[trackId] = new File([initBuffer, mediaBuffer], "video.mp4", {
|
|
223
|
-
type: "video/mp4",
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return files;
|
|
228
|
-
},
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
videoAssetTask = new Task(this, {
|
|
232
|
-
autoRun: EF_INTERACTIVE,
|
|
233
|
-
args: () => [this.fetchSeekTask.value] as const,
|
|
234
|
-
task: async ([files], { signal }) => {
|
|
235
|
-
console.log("EFMedia videoAssetTask");
|
|
236
|
-
if (!files) {
|
|
237
|
-
return;
|
|
238
|
-
}
|
|
239
|
-
if (!this.defaultVideoTrackId) {
|
|
240
|
-
return;
|
|
241
|
-
}
|
|
242
|
-
const videoFile = files[this.defaultVideoTrackId];
|
|
243
|
-
if (!videoFile) {
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
return await VideoAsset.createFromReadableStream(
|
|
247
|
-
"video.mp4",
|
|
248
|
-
videoFile.stream(),
|
|
249
|
-
videoFile,
|
|
250
|
-
);
|
|
251
|
-
},
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
@state() desiredSeekTimeMs = 0;
|
|
255
|
-
|
|
256
|
-
protected async executeSeek(seekToMs: number) {
|
|
257
|
-
this.desiredSeekTimeMs = seekToMs;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
protected updated(
|
|
261
|
-
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
262
|
-
): void {
|
|
263
|
-
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
264
|
-
this.executeSeek(this.ownCurrentTimeMs);
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
get hasOwnDuration() {
|
|
269
|
-
return true;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
get durationMs() {
|
|
273
|
-
if (!this.trackFragmentIndexLoader.value) {
|
|
274
|
-
return 0;
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
const durations = Object.values(this.trackFragmentIndexLoader.value).map(
|
|
278
|
-
(track) => {
|
|
279
|
-
return (track.duration / track.timescale) * 1000;
|
|
280
|
-
},
|
|
281
|
-
);
|
|
282
|
-
if (durations.length === 0) {
|
|
283
|
-
return 0;
|
|
284
|
-
}
|
|
285
|
-
return Math.max(...durations);
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
get startTimeMs() {
|
|
289
|
-
return getStartTimeMs(this);
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
#audioContext = new OfflineAudioContext(2, 48000 / 30, 48000);
|
|
293
|
-
|
|
294
|
-
audioBufferTask = new Task(this, {
|
|
295
|
-
autoRun: EF_INTERACTIVE,
|
|
296
|
-
args: () => [this.fetchSeekTask.value, this.seekTask.value] as const,
|
|
297
|
-
task: async ([files, segments], { signal }) => {
|
|
298
|
-
console.log("EFMedia audioBufferTask", this.outerHTML);
|
|
299
|
-
if (!files) {
|
|
300
|
-
return;
|
|
301
|
-
}
|
|
302
|
-
if (!segments) {
|
|
303
|
-
return;
|
|
304
|
-
}
|
|
305
|
-
if (!this.defaultAudioTrackId) {
|
|
306
|
-
return;
|
|
307
|
-
}
|
|
308
|
-
const segment = segments[this.defaultAudioTrackId];
|
|
309
|
-
if (!segment) {
|
|
310
|
-
return;
|
|
311
|
-
}
|
|
312
|
-
const audioFile = files[this.defaultAudioTrackId];
|
|
313
|
-
if (!audioFile) {
|
|
314
|
-
return;
|
|
315
|
-
}
|
|
316
|
-
return {
|
|
317
|
-
buffer: await this.#audioContext.decodeAudioData(
|
|
318
|
-
await audioFile.arrayBuffer(),
|
|
319
|
-
),
|
|
320
|
-
startOffsetMs: (segment.segment.cts / segment.track.timescale) * 1000,
|
|
321
|
-
};
|
|
322
|
-
},
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
326
|
-
// Adjust range for track's own time
|
|
327
|
-
fromMs -= this.startTimeMs;
|
|
328
|
-
toMs -= this.startTimeMs;
|
|
329
|
-
|
|
330
|
-
await this.trackFragmentIndexLoader.taskComplete;
|
|
331
|
-
const audioTrackId = this.defaultAudioTrackId;
|
|
332
|
-
if (!audioTrackId) {
|
|
333
|
-
console.warn("No audio track found");
|
|
334
|
-
return;
|
|
335
|
-
}
|
|
336
|
-
const audioTrackIndex = this.trackFragmentIndexLoader.value?.[audioTrackId];
|
|
337
|
-
if (!audioTrackIndex) {
|
|
338
|
-
console.warn("No audio track found");
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
const start = audioTrackIndex.initSegment.offset;
|
|
343
|
-
const end =
|
|
344
|
-
audioTrackIndex.initSegment.offset + audioTrackIndex.initSegment.size - 1;
|
|
345
|
-
const audioInitFragmentRequest = this.fetch(
|
|
346
|
-
this.fragmentTrackPath(String(audioTrackId)),
|
|
347
|
-
{
|
|
348
|
-
headers: { Range: `bytes=${start}-${end}` },
|
|
349
|
-
},
|
|
350
|
-
);
|
|
351
|
-
|
|
352
|
-
const fragments = Object.values(audioTrackIndex.segments).filter(
|
|
353
|
-
(segment) => {
|
|
354
|
-
const segmentStartsBeforeEnd =
|
|
355
|
-
segment.dts <= (toMs * audioTrackIndex.timescale) / 1000;
|
|
356
|
-
const segmentEndsAfterStart =
|
|
357
|
-
segment.dts + segment.duration >=
|
|
358
|
-
(fromMs * audioTrackIndex.timescale) / 1000;
|
|
359
|
-
return segmentStartsBeforeEnd && segmentEndsAfterStart;
|
|
360
|
-
},
|
|
361
|
-
);
|
|
362
|
-
|
|
363
|
-
console.log("FRAGMENTS SPANNING TIME", JSON.stringify(fragments));
|
|
364
|
-
const firstFragment = fragments[0]!;
|
|
365
|
-
const lastFragment = fragments[fragments.length - 1]!;
|
|
366
|
-
const fragmentStart = firstFragment.offset;
|
|
367
|
-
const fragmentEnd = lastFragment.offset + lastFragment.size - 1;
|
|
368
|
-
|
|
369
|
-
console.log("FETCHING BYTES", `bytes=${fragmentStart}-${fragmentEnd}`);
|
|
370
|
-
const audioFragmentRequest = this.fetch(
|
|
371
|
-
this.fragmentTrackPath(String(audioTrackId)),
|
|
372
|
-
{
|
|
373
|
-
headers: { Range: `bytes=${fragmentStart}-${fragmentEnd}` },
|
|
374
|
-
},
|
|
375
|
-
);
|
|
376
|
-
|
|
377
|
-
const initResponse = await audioInitFragmentRequest;
|
|
378
|
-
const dataResponse = await audioFragmentRequest;
|
|
379
|
-
|
|
380
|
-
const initBuffer = await initResponse.arrayBuffer();
|
|
381
|
-
const dataBuffer = await dataResponse.arrayBuffer();
|
|
382
|
-
|
|
383
|
-
const audioBlob = new Blob([initBuffer, dataBuffer], {
|
|
384
|
-
type: "audio/mp4",
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
return {
|
|
388
|
-
blob: audioBlob,
|
|
389
|
-
startMs: (firstFragment.dts / audioTrackIndex.timescale) * 1000,
|
|
390
|
-
endMs:
|
|
391
|
-
(lastFragment.dts / audioTrackIndex.timescale) * 1000 +
|
|
392
|
-
(lastFragment.duration / audioTrackIndex.timescale) * 1000,
|
|
393
|
-
};
|
|
394
|
-
}
|
|
395
|
-
}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { consume } from "@lit/context";
|
|
2
|
-
import { LitElement } from "lit";
|
|
3
|
-
import { apiHostContext } from "../gui/EFWorkbench";
|
|
4
|
-
import { state } from "lit/decorators/state.js";
|
|
5
|
-
import { Task } from "@lit/task";
|
|
6
|
-
import { property } from "lit/decorators/property.js";
|
|
7
|
-
|
|
8
|
-
export declare class EFSourceMixinInterface {
|
|
9
|
-
productionSrc(): string;
|
|
10
|
-
src: string;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
interface EFSourceMixinOptions {
|
|
14
|
-
assetType: string;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function EFSourceMixin<T extends Constructor<LitElement>>(
|
|
18
|
-
superClass: T,
|
|
19
|
-
options: EFSourceMixinOptions,
|
|
20
|
-
) {
|
|
21
|
-
class EFSourceElement extends superClass {
|
|
22
|
-
@consume({ context: apiHostContext, subscribe: true })
|
|
23
|
-
@state()
|
|
24
|
-
private efHost?: string;
|
|
25
|
-
|
|
26
|
-
@property({ type: String })
|
|
27
|
-
src = "";
|
|
28
|
-
|
|
29
|
-
productionSrc() {
|
|
30
|
-
if (!this.md5SumLoader.value) {
|
|
31
|
-
throw new Error(
|
|
32
|
-
`MD5 sum not available for ${this}. Cannot generate production URL`,
|
|
33
|
-
);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (!this.efHost) {
|
|
37
|
-
throw new Error(
|
|
38
|
-
`efHost not available for ${this}. Cannot generate production URL`,
|
|
39
|
-
);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
return `${this.efHost}/api/video2/${options.assetType}/${this.md5SumLoader.value}`;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
md5SumLoader = new Task(this, {
|
|
46
|
-
autoRun: false,
|
|
47
|
-
args: () => [this.src] as const,
|
|
48
|
-
task: async ([src], { signal }) => {
|
|
49
|
-
const md5Path = `/@ef-asset/${src}`;
|
|
50
|
-
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
51
|
-
return response.headers.get("etag") ?? undefined;
|
|
52
|
-
},
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
return EFSourceElement as Constructor<EFSourceMixinInterface> & T;
|
|
57
|
-
}
|