@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
package/package.json
CHANGED
|
@@ -1,16 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0-beta.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
7
7
|
"import": {
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
},
|
|
11
|
-
"require": {
|
|
12
|
-
"default": "./dist/packages/elements/src/index.cjs",
|
|
13
|
-
"types": "./dist/packages/elements/src/index.d.ts"
|
|
8
|
+
"types": "./dist/index.d.ts",
|
|
9
|
+
"default": "./dist/elements/src/index.js"
|
|
14
10
|
}
|
|
15
11
|
},
|
|
16
12
|
"./styles.css": "./dist/style.css"
|
|
@@ -24,7 +20,7 @@
|
|
|
24
20
|
"author": "",
|
|
25
21
|
"license": "UNLICENSED",
|
|
26
22
|
"dependencies": {
|
|
27
|
-
"@editframe/assets": "0.
|
|
23
|
+
"@editframe/assets": "0.8.0-beta.10",
|
|
28
24
|
"@lit/context": "^1.1.2",
|
|
29
25
|
"@lit/task": "^1.0.1",
|
|
30
26
|
"d3": "^7.9.0",
|
|
@@ -35,10 +31,11 @@
|
|
|
35
31
|
"devDependencies": {
|
|
36
32
|
"@types/d3": "^7.4.3",
|
|
37
33
|
"@types/dom-webcodecs": "^0.1.11",
|
|
38
|
-
"@types/node": "^20.14.
|
|
34
|
+
"@types/node": "^20.14.13",
|
|
39
35
|
"autoprefixer": "^10.4.19",
|
|
40
36
|
"rollup-plugin-tsconfig-paths": "^1.5.2",
|
|
41
|
-
"
|
|
37
|
+
"typescript": "^5.5.4",
|
|
38
|
+
"vite-plugin-dts": "^4.0.3",
|
|
42
39
|
"vite-tsconfig-paths": "^4.3.2"
|
|
43
40
|
}
|
|
44
41
|
}
|
package/src/elements/EFAudio.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { html } from "lit";
|
|
2
2
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
3
3
|
import { customElement, property } from "lit/decorators.js";
|
|
4
|
-
import { EFMedia } from "./EFMedia";
|
|
4
|
+
import { EFMedia } from "./EFMedia.ts";
|
|
5
5
|
import { Task } from "@lit/task";
|
|
6
6
|
|
|
7
7
|
@customElement("ef-audio")
|
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { EFAudio } from "./EFAudio";
|
|
2
1
|
import { LitElement, type PropertyValueMap, html, css } from "lit";
|
|
3
2
|
import { Task } from "@lit/task";
|
|
4
3
|
import { customElement, property } from "lit/decorators.js";
|
|
5
|
-
import { EFVideo } from "./EFVideo";
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
4
|
+
import { EFVideo } from "./EFVideo.ts";
|
|
5
|
+
import { EFAudio } from "./EFAudio.ts";
|
|
6
|
+
import { EFTemporal } from "./EFTemporal.ts";
|
|
7
|
+
import { CrossUpdateController } from "./CrossUpdateController.ts";
|
|
8
|
+
import { FetchMixin } from "./FetchMixin.ts";
|
|
9
|
+
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
10
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
11
11
|
|
|
12
12
|
interface Word {
|
|
13
13
|
text: string;
|
|
@@ -75,8 +75,12 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
75
75
|
`,
|
|
76
76
|
];
|
|
77
77
|
|
|
78
|
-
@property({ type: String, attribute: "target" })
|
|
79
|
-
|
|
78
|
+
@property({ type: String, attribute: "target", reflect: true })
|
|
79
|
+
targetSelector = "";
|
|
80
|
+
|
|
81
|
+
set target(value: string) {
|
|
82
|
+
this.targetSelector = value;
|
|
83
|
+
}
|
|
80
84
|
|
|
81
85
|
@property({ attribute: "word-style" })
|
|
82
86
|
wordStyle = "";
|
|
@@ -93,8 +97,8 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
93
97
|
|
|
94
98
|
protected md5SumLoader = new Task(this, {
|
|
95
99
|
autoRun: false,
|
|
96
|
-
args: () => [this.target] as const,
|
|
97
|
-
task: async ([], { signal }) => {
|
|
100
|
+
args: () => [this.target, this.fetch] as const,
|
|
101
|
+
task: async ([_target, fetch], { signal }) => {
|
|
98
102
|
const md5Path = `/@ef-asset/${this.targetElement.src ?? ""}`;
|
|
99
103
|
const response = await fetch(md5Path, { method: "HEAD", signal });
|
|
100
104
|
return response.headers.get("etag") ?? undefined;
|
|
@@ -149,13 +153,15 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
149
153
|
let endMs = 0;
|
|
150
154
|
for (const segment of caption.segments) {
|
|
151
155
|
if (
|
|
152
|
-
this.targetElement.
|
|
153
|
-
|
|
156
|
+
this.targetElement.trimAdjustedOwnCurrentTimeMs >=
|
|
157
|
+
segment.start * 1000 &&
|
|
158
|
+
this.targetElement.trimAdjustedOwnCurrentTimeMs <= segment.end * 1000
|
|
154
159
|
) {
|
|
155
160
|
for (const word of segment.words) {
|
|
156
161
|
if (
|
|
157
|
-
this.targetElement.
|
|
158
|
-
|
|
162
|
+
this.targetElement.trimAdjustedOwnCurrentTimeMs >=
|
|
163
|
+
word.start * 1000 &&
|
|
164
|
+
this.targetElement.trimAdjustedOwnCurrentTimeMs <= word.end * 1000
|
|
159
165
|
) {
|
|
160
166
|
words.push(word.text);
|
|
161
167
|
startMs = word.start * 1000;
|
|
@@ -172,11 +178,11 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
172
178
|
}
|
|
173
179
|
|
|
174
180
|
get targetElement() {
|
|
175
|
-
const target = document.getElementById(this.
|
|
181
|
+
const target = document.getElementById(this.targetSelector ?? "");
|
|
176
182
|
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
177
183
|
return target;
|
|
178
184
|
}
|
|
179
|
-
throw new Error("Invalid target, must be an EFAudio
|
|
185
|
+
throw new Error("Invalid target, must be an EFAudio or EFVideo element");
|
|
180
186
|
}
|
|
181
187
|
}
|
|
182
188
|
|
package/src/elements/EFImage.ts
CHANGED
|
@@ -2,9 +2,9 @@ import { Task } from "@lit/task";
|
|
|
2
2
|
import { LitElement, html, css } from "lit";
|
|
3
3
|
import { customElement } from "lit/decorators.js";
|
|
4
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";
|
|
5
|
+
import { FetchMixin } from "./FetchMixin.ts";
|
|
6
|
+
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
7
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
8
8
|
|
|
9
9
|
@customElement("ef-image")
|
|
10
10
|
export class EFImage extends EFSourceMixin(FetchMixin(LitElement), {
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -8,14 +8,14 @@ import debug from "debug";
|
|
|
8
8
|
|
|
9
9
|
import type { TrackFragmentIndex, TrackSegment } from "@editframe/assets";
|
|
10
10
|
|
|
11
|
-
import { MP4File } from "
|
|
12
|
-
import {
|
|
13
|
-
import {
|
|
14
|
-
import { FetchMixin } from "./FetchMixin";
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
18
|
-
import {
|
|
11
|
+
import { MP4File } from "@editframe/assets/MP4File.js";
|
|
12
|
+
import { VideoAsset } from "@editframe/assets/EncodedAsset.js";
|
|
13
|
+
import { EFTemporal } from "./EFTemporal.ts";
|
|
14
|
+
import { FetchMixin } from "./FetchMixin.ts";
|
|
15
|
+
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
16
|
+
import { getStartTimeMs } from "./util.ts";
|
|
17
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
18
|
+
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
19
19
|
|
|
20
20
|
const log = debug("ef:elements:EFMedia");
|
|
21
21
|
|
|
@@ -70,6 +70,9 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
70
70
|
public trackFragmentIndexLoader = new Task(this, {
|
|
71
71
|
args: () => [this.fragmentIndexPath(), this.fetch] as const,
|
|
72
72
|
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
73
|
+
if (this.src === "") {
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
73
76
|
const response = await fetch(fragmentIndexPath, { signal });
|
|
74
77
|
return (await response.json()) as Record<number, TrackFragmentIndex>;
|
|
75
78
|
},
|
|
@@ -142,7 +145,11 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
142
145
|
|
|
143
146
|
const result: Record<
|
|
144
147
|
string,
|
|
145
|
-
{
|
|
148
|
+
{
|
|
149
|
+
segment: TrackSegment;
|
|
150
|
+
track: MP4Box.TrackInfo;
|
|
151
|
+
nextSegment?: TrackSegment;
|
|
152
|
+
}
|
|
146
153
|
> = {};
|
|
147
154
|
|
|
148
155
|
for (const index of Object.values(fragmentIndex)) {
|
|
@@ -158,11 +165,15 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
158
165
|
return (segment.dts / track.timescale) * 1000 <= seekToMs;
|
|
159
166
|
});
|
|
160
167
|
|
|
168
|
+
const nextSegment = index.segments.find((segment) => {
|
|
169
|
+
return (segment.dts / track.timescale) * 1000 > seekToMs;
|
|
170
|
+
});
|
|
171
|
+
|
|
161
172
|
if (!segment) {
|
|
162
173
|
return;
|
|
163
174
|
}
|
|
164
175
|
|
|
165
|
-
result[index.track] = { segment, track };
|
|
176
|
+
result[index.track] = { segment, track, nextSegment };
|
|
166
177
|
}
|
|
167
178
|
|
|
168
179
|
return result;
|
|
@@ -184,7 +195,9 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
184
195
|
|
|
185
196
|
const files: Record<string, File> = {};
|
|
186
197
|
|
|
187
|
-
for (const [trackId, { segment, track }] of Object.entries(
|
|
198
|
+
for (const [trackId, { segment, track, nextSegment }] of Object.entries(
|
|
199
|
+
seekResult,
|
|
200
|
+
)) {
|
|
188
201
|
const start = segment.offset;
|
|
189
202
|
const end = segment.offset + segment.size;
|
|
190
203
|
|
|
@@ -193,6 +206,21 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
193
206
|
headers: { Range: `bytes=${start}-${end}` },
|
|
194
207
|
});
|
|
195
208
|
|
|
209
|
+
if (nextSegment) {
|
|
210
|
+
const nextStart = nextSegment.offset;
|
|
211
|
+
const nextEnd = nextSegment.offset + nextSegment.size;
|
|
212
|
+
fetch(this.fragmentTrackPath(trackId), {
|
|
213
|
+
signal,
|
|
214
|
+
headers: { Range: `bytes=${nextStart}-${nextEnd}` },
|
|
215
|
+
})
|
|
216
|
+
.then(() => {
|
|
217
|
+
log("Prefetched next segment");
|
|
218
|
+
})
|
|
219
|
+
.catch((error) => {
|
|
220
|
+
log("Failed to prefetch next segment", error);
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
|
|
196
224
|
const initSegment = Object.values(initSegments).find(
|
|
197
225
|
(initSegment) => initSegment.trackId === String(track.id),
|
|
198
226
|
);
|
|
@@ -250,7 +278,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
250
278
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
251
279
|
): void {
|
|
252
280
|
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
253
|
-
this.executeSeek(this.
|
|
281
|
+
this.executeSeek(this.trimAdjustedOwnCurrentTimeMs);
|
|
254
282
|
}
|
|
255
283
|
}
|
|
256
284
|
|
|
@@ -271,7 +299,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
271
299
|
if (durations.length === 0) {
|
|
272
300
|
return 0;
|
|
273
301
|
}
|
|
274
|
-
return Math.max(...durations);
|
|
302
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
275
303
|
}
|
|
276
304
|
|
|
277
305
|
get startTimeMs() {
|
|
@@ -312,8 +340,8 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
312
340
|
|
|
313
341
|
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
314
342
|
// Adjust range for track's own time
|
|
315
|
-
fromMs -= this.startTimeMs;
|
|
316
|
-
toMs -= this.startTimeMs;
|
|
343
|
+
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
344
|
+
toMs -= this.startTimeMs - this.trimStartMs;
|
|
317
345
|
|
|
318
346
|
await this.trackFragmentIndexLoader.taskComplete;
|
|
319
347
|
const audioTrackId = this.defaultAudioTrackId;
|
|
@@ -380,10 +408,13 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
380
408
|
|
|
381
409
|
return {
|
|
382
410
|
blob: audioBlob,
|
|
383
|
-
startMs:
|
|
411
|
+
startMs:
|
|
412
|
+
(firstFragment.dts / audioTrackIndex.timescale) * 1000 -
|
|
413
|
+
this.trimStartMs,
|
|
384
414
|
endMs:
|
|
385
415
|
(lastFragment.dts / audioTrackIndex.timescale) * 1000 +
|
|
386
|
-
(lastFragment.duration / audioTrackIndex.timescale) * 1000
|
|
416
|
+
(lastFragment.duration / audioTrackIndex.timescale) * 1000 -
|
|
417
|
+
this.trimEndMs,
|
|
387
418
|
};
|
|
388
419
|
}
|
|
389
420
|
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { consume } from "@lit/context";
|
|
2
2
|
import type { LitElement } from "lit";
|
|
3
|
-
import { apiHostContext } from "../gui/EFWorkbench";
|
|
4
3
|
import { state } from "lit/decorators/state.js";
|
|
5
4
|
import { Task } from "@lit/task";
|
|
6
5
|
import { property } from "lit/decorators/property.js";
|
|
6
|
+
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
7
7
|
|
|
8
8
|
export declare class EFSourceMixinInterface {
|
|
9
9
|
productionSrc(): string;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import type { LitElement, ReactiveController } from "lit";
|
|
2
2
|
import { consume, createContext } from "@lit/context";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
|
-
import type { EFTimegroup } from "./EFTimegroup";
|
|
4
|
+
import type { EFTimegroup } from "./EFTimegroup.ts";
|
|
5
5
|
|
|
6
|
-
import { durationConverter } from "./durationConverter";
|
|
6
|
+
import { durationConverter } from "./durationConverter.ts";
|
|
7
7
|
import { Task } from "@lit/task";
|
|
8
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
|
|
8
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
9
9
|
|
|
10
10
|
export const timegroupContext = createContext<EFTimegroup>(
|
|
11
11
|
Symbol("timeGroupContext"),
|
|
@@ -13,11 +13,14 @@ export const timegroupContext = createContext<EFTimegroup>(
|
|
|
13
13
|
|
|
14
14
|
export declare class TemporalMixinInterface {
|
|
15
15
|
get hasOwnDuration(): boolean;
|
|
16
|
+
get trimStartMs(): number;
|
|
17
|
+
get trimEndMs(): number;
|
|
16
18
|
get durationMs(): number;
|
|
17
19
|
get startTimeMs(): number;
|
|
18
20
|
get startTimeWithinParentMs(): number;
|
|
19
21
|
get endTimeMs(): number;
|
|
20
22
|
get ownCurrentTimeMs(): number;
|
|
23
|
+
get trimAdjustedOwnCurrentTimeMs(): number;
|
|
21
24
|
|
|
22
25
|
set duration(value: string);
|
|
23
26
|
get duration(): string;
|
|
@@ -59,10 +62,23 @@ export const deepGetElementsWithFrameTasks = (
|
|
|
59
62
|
return elements;
|
|
60
63
|
};
|
|
61
64
|
|
|
65
|
+
let temporalCache: Map<Element, TemporalMixinInterface[]>;
|
|
66
|
+
const resetTemporalCache = () => {
|
|
67
|
+
temporalCache = new Map();
|
|
68
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
69
|
+
requestAnimationFrame(resetTemporalCache);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
resetTemporalCache();
|
|
73
|
+
|
|
62
74
|
export const shallowGetTemporalElements = (
|
|
63
75
|
element: Element,
|
|
64
76
|
temporals: TemporalMixinInterface[] = [],
|
|
65
77
|
) => {
|
|
78
|
+
const cachedResult = temporalCache.get(element);
|
|
79
|
+
if (cachedResult) {
|
|
80
|
+
return cachedResult;
|
|
81
|
+
}
|
|
66
82
|
for (const child of element.children) {
|
|
67
83
|
if (isEFTemporal(child)) {
|
|
68
84
|
temporals.push(child);
|
|
@@ -70,6 +86,7 @@ export const shallowGetTemporalElements = (
|
|
|
70
86
|
shallowGetTemporalElements(child, temporals);
|
|
71
87
|
}
|
|
72
88
|
}
|
|
89
|
+
temporalCache.set(element, temporals);
|
|
73
90
|
return temporals;
|
|
74
91
|
};
|
|
75
92
|
|
|
@@ -92,6 +109,15 @@ export class OwnCurrentTimeController implements ReactiveController {
|
|
|
92
109
|
|
|
93
110
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
94
111
|
|
|
112
|
+
let startTimeMsCache = new WeakMap<Element, number>();
|
|
113
|
+
const resetStartTimeMsCache = () => {
|
|
114
|
+
startTimeMsCache = new WeakMap();
|
|
115
|
+
if (typeof requestAnimationFrame !== "undefined") {
|
|
116
|
+
requestAnimationFrame(resetStartTimeMsCache);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
resetStartTimeMsCache();
|
|
120
|
+
|
|
95
121
|
export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
96
122
|
superClass: T,
|
|
97
123
|
) => {
|
|
@@ -130,6 +156,36 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
130
156
|
})
|
|
131
157
|
private _durationMs?: number;
|
|
132
158
|
|
|
159
|
+
@property({
|
|
160
|
+
type: Number,
|
|
161
|
+
attribute: "trimstart",
|
|
162
|
+
converter: durationConverter,
|
|
163
|
+
})
|
|
164
|
+
private _trimStartMs = 0;
|
|
165
|
+
public get trimStartMs(): number {
|
|
166
|
+
return this._trimStartMs;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@property({
|
|
170
|
+
type: Number,
|
|
171
|
+
attribute: "trimend",
|
|
172
|
+
converter: durationConverter,
|
|
173
|
+
})
|
|
174
|
+
private _trimEndMs = 0;
|
|
175
|
+
public get trimEndMs(): number {
|
|
176
|
+
return this._trimEndMs;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
@property({
|
|
180
|
+
type: Number,
|
|
181
|
+
attribute: "startoffset",
|
|
182
|
+
converter: durationConverter,
|
|
183
|
+
})
|
|
184
|
+
private _startOffsetMs = 0;
|
|
185
|
+
public get startOffsetMs(): number {
|
|
186
|
+
return this._startOffsetMs;
|
|
187
|
+
}
|
|
188
|
+
|
|
133
189
|
@state()
|
|
134
190
|
rootTimegroup?: EFTimegroup = this.getRootTimegroup();
|
|
135
191
|
|
|
@@ -172,27 +228,47 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
172
228
|
}
|
|
173
229
|
|
|
174
230
|
get startTimeMs(): number {
|
|
231
|
+
const cachedStartTime = startTimeMsCache.get(this);
|
|
232
|
+
if (cachedStartTime !== undefined) {
|
|
233
|
+
return cachedStartTime;
|
|
234
|
+
}
|
|
175
235
|
const parentTimegroup = this.parentTimegroup;
|
|
176
236
|
if (!parentTimegroup) {
|
|
237
|
+
startTimeMsCache.set(this, 0);
|
|
177
238
|
return 0;
|
|
178
239
|
}
|
|
179
240
|
switch (parentTimegroup.mode) {
|
|
180
241
|
case "sequence": {
|
|
181
242
|
const siblingTemorals = shallowGetTemporalElements(parentTimegroup);
|
|
182
|
-
const ownIndex = siblingTemorals
|
|
243
|
+
const ownIndex = siblingTemorals?.indexOf(
|
|
183
244
|
this as InstanceType<Constructor<TemporalMixinInterface> & T>,
|
|
184
245
|
);
|
|
185
246
|
if (ownIndex === 0) {
|
|
247
|
+
startTimeMsCache.set(this, parentTimegroup.startTimeMs);
|
|
186
248
|
return parentTimegroup.startTimeMs;
|
|
187
249
|
}
|
|
188
|
-
const previous = siblingTemorals[ownIndex - 1];
|
|
250
|
+
const previous = siblingTemorals?.[(ownIndex ?? 0) - 1];
|
|
189
251
|
if (!previous) {
|
|
190
252
|
throw new Error("Previous temporal element not found");
|
|
191
253
|
}
|
|
192
|
-
|
|
254
|
+
startTimeMsCache.set(
|
|
255
|
+
this,
|
|
256
|
+
previous.startTimeMs +
|
|
257
|
+
previous.durationMs -
|
|
258
|
+
parentTimegroup.overlapMs,
|
|
259
|
+
);
|
|
260
|
+
return (
|
|
261
|
+
previous.startTimeMs +
|
|
262
|
+
previous.durationMs -
|
|
263
|
+
parentTimegroup.overlapMs
|
|
264
|
+
);
|
|
193
265
|
}
|
|
194
266
|
case "contain":
|
|
195
267
|
case "fixed":
|
|
268
|
+
startTimeMsCache.set(
|
|
269
|
+
this,
|
|
270
|
+
parentTimegroup.startTimeMs + this.offsetMs,
|
|
271
|
+
);
|
|
196
272
|
return parentTimegroup.startTimeMs + this.offsetMs;
|
|
197
273
|
default:
|
|
198
274
|
throw new Error(`Invalid time mode: ${parentTimegroup.mode}`);
|
|
@@ -213,6 +289,25 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
213
289
|
return 0;
|
|
214
290
|
}
|
|
215
291
|
|
|
292
|
+
/**
|
|
293
|
+
* Used to calculate the internal currentTimeMs of the element. This is useful
|
|
294
|
+
* for mapping to internal media time codes for audio/video elements.
|
|
295
|
+
*/
|
|
296
|
+
get trimAdjustedOwnCurrentTimeMs() {
|
|
297
|
+
if (this.rootTimegroup) {
|
|
298
|
+
return Math.min(
|
|
299
|
+
Math.max(
|
|
300
|
+
0,
|
|
301
|
+
this.rootTimegroup.currentTimeMs -
|
|
302
|
+
this.startTimeMs +
|
|
303
|
+
this.trimStartMs,
|
|
304
|
+
),
|
|
305
|
+
this.durationMs + Math.abs(this.startOffsetMs) + this.trimStartMs,
|
|
306
|
+
);
|
|
307
|
+
}
|
|
308
|
+
return 0;
|
|
309
|
+
}
|
|
310
|
+
|
|
216
311
|
frameTask = new Task(this, {
|
|
217
312
|
autoRun: EF_INTERACTIVE,
|
|
218
313
|
args: () => [this.ownCurrentTimeMs] as const,
|
|
@@ -5,10 +5,10 @@ import {
|
|
|
5
5
|
html,
|
|
6
6
|
render as litRender,
|
|
7
7
|
} from "lit";
|
|
8
|
-
import { EFTimegroup } from "./EFTimegroup";
|
|
9
|
-
import "./EFTimegroup";
|
|
8
|
+
import { EFTimegroup } from "./EFTimegroup.ts";
|
|
9
|
+
import "./EFTimegroup.ts";
|
|
10
10
|
import { customElement } from "lit/decorators/custom-element.js";
|
|
11
|
-
import { EFTemporal } from "./EFTemporal";
|
|
11
|
+
import { EFTemporal } from "./EFTemporal.ts";
|
|
12
12
|
|
|
13
13
|
beforeEach(() => {
|
|
14
14
|
for (let i = 0; i < localStorage.length; i++) {
|
|
@@ -9,10 +9,11 @@ import {
|
|
|
9
9
|
isEFTemporal,
|
|
10
10
|
shallowGetTemporalElements,
|
|
11
11
|
timegroupContext,
|
|
12
|
-
} from "./EFTemporal";
|
|
13
|
-
import { TimegroupController } from "./TimegroupController";
|
|
14
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE";
|
|
15
|
-
import { deepGetMediaElements } from "./EFMedia";
|
|
12
|
+
} from "./EFTemporal.ts";
|
|
13
|
+
import { TimegroupController } from "./TimegroupController.ts";
|
|
14
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
15
|
+
import { deepGetMediaElements } from "./EFMedia.ts";
|
|
16
|
+
import { durationConverter } from "./durationConverter.ts";
|
|
16
17
|
|
|
17
18
|
const log = debug("ef:elements:EFTimegroup");
|
|
18
19
|
|
|
@@ -37,7 +38,7 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
37
38
|
display: block;
|
|
38
39
|
width: 100%;
|
|
39
40
|
height: 100%;
|
|
40
|
-
position:
|
|
41
|
+
position: absolute;
|
|
41
42
|
top: 0;
|
|
42
43
|
}
|
|
43
44
|
`;
|
|
@@ -53,6 +54,13 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
53
54
|
})
|
|
54
55
|
mode: "fixed" | "sequence" | "contain" = "sequence";
|
|
55
56
|
|
|
57
|
+
@property({
|
|
58
|
+
type: Number,
|
|
59
|
+
converter: durationConverter,
|
|
60
|
+
attribute: "overlap",
|
|
61
|
+
})
|
|
62
|
+
overlapMs = 0;
|
|
63
|
+
|
|
56
64
|
@property({ type: Number })
|
|
57
65
|
set currentTime(time: number) {
|
|
58
66
|
this.#currentTime = Math.max(0, Math.min(time, this.durationMs / 1000));
|
|
@@ -76,25 +84,6 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
76
84
|
this.currentTime = ms / 1000;
|
|
77
85
|
}
|
|
78
86
|
|
|
79
|
-
@property({
|
|
80
|
-
attribute: "crossover",
|
|
81
|
-
converter: {
|
|
82
|
-
fromAttribute: (value: string): number => {
|
|
83
|
-
if (value.endsWith("ms")) {
|
|
84
|
-
return Number.parseFloat(value);
|
|
85
|
-
}
|
|
86
|
-
if (value.endsWith("s")) {
|
|
87
|
-
return Number.parseFloat(value) * 1000;
|
|
88
|
-
}
|
|
89
|
-
throw new Error(
|
|
90
|
-
"`crossover` MUST be in milliseconds or seconds (10s, 10000ms)",
|
|
91
|
-
);
|
|
92
|
-
},
|
|
93
|
-
toAttribute: (value: number) => `${value}ms`,
|
|
94
|
-
},
|
|
95
|
-
})
|
|
96
|
-
crossoverMs = 0;
|
|
97
|
-
|
|
98
87
|
render() {
|
|
99
88
|
return html`<slot></slot> `;
|
|
100
89
|
}
|
|
@@ -113,7 +102,9 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
113
102
|
connectedCallback() {
|
|
114
103
|
super.connectedCallback();
|
|
115
104
|
if (this.id) {
|
|
116
|
-
this
|
|
105
|
+
this.waitForMediaDurations().then(() => {
|
|
106
|
+
this.#currentTime = this.maybeLoadTimeFromLocalStorage();
|
|
107
|
+
});
|
|
117
108
|
}
|
|
118
109
|
|
|
119
110
|
if (this.parentTimegroup) {
|
|
@@ -132,32 +123,18 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
132
123
|
return `ef-timegroup-${this.id}`;
|
|
133
124
|
}
|
|
134
125
|
|
|
135
|
-
get crossoverStartMs() {
|
|
136
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
137
|
-
if (!parentTimeGroup || !this.previousElementSibling) {
|
|
138
|
-
return 0;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
return parentTimeGroup.crossoverMs;
|
|
142
|
-
}
|
|
143
|
-
get crossoverEndMs() {
|
|
144
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
145
|
-
if (!parentTimeGroup || !this.nextElementSibling) {
|
|
146
|
-
return 0;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return parentTimeGroup.crossoverMs;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
126
|
get durationMs() {
|
|
153
127
|
switch (this.mode) {
|
|
154
128
|
case "fixed":
|
|
155
129
|
return super.durationMs;
|
|
156
130
|
case "sequence": {
|
|
157
131
|
let duration = 0;
|
|
158
|
-
|
|
132
|
+
this.childTemporals.forEach((node, index) => {
|
|
133
|
+
if (index > 0) {
|
|
134
|
+
duration -= this.overlapMs;
|
|
135
|
+
}
|
|
159
136
|
duration += node.durationMs;
|
|
160
|
-
}
|
|
137
|
+
});
|
|
161
138
|
return duration;
|
|
162
139
|
}
|
|
163
140
|
case "contain": {
|
|
@@ -205,9 +182,14 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
205
182
|
this.style.display = "";
|
|
206
183
|
|
|
207
184
|
const animations = this.getAnimations({ subtree: true });
|
|
185
|
+
this.style.setProperty("--ef-duration", `${this.durationMs}ms`);
|
|
186
|
+
this.style.setProperty(
|
|
187
|
+
"--ef-transition--duration",
|
|
188
|
+
`${this.parentTimegroup?.overlapMs ?? 0}ms`,
|
|
189
|
+
);
|
|
208
190
|
this.style.setProperty(
|
|
209
|
-
"--ef-
|
|
210
|
-
`${this.durationMs
|
|
191
|
+
"--ef-transition-out-start",
|
|
192
|
+
`${this.durationMs - (this.parentTimegroup?.overlapMs ?? 0)}ms`,
|
|
211
193
|
);
|
|
212
194
|
|
|
213
195
|
for (const animation of animations) {
|
|
@@ -265,7 +247,8 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
265
247
|
return (
|
|
266
248
|
EF_INTERACTIVE &&
|
|
267
249
|
this.closest("ef-timegroup") === this &&
|
|
268
|
-
this.closest("ef-workbench") === null
|
|
250
|
+
this.closest("ef-workbench") === null &&
|
|
251
|
+
this.closest("ef-preview") === null
|
|
269
252
|
);
|
|
270
253
|
}
|
|
271
254
|
|
package/src/elements/EFVideo.ts
CHANGED
|
@@ -3,8 +3,8 @@ import { Task } from "@lit/task";
|
|
|
3
3
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
4
4
|
import { customElement } from "lit/decorators.js";
|
|
5
5
|
|
|
6
|
-
import { EFMedia } from "./EFMedia";
|
|
7
|
-
import { TWMixin } from "../gui/TWMixin";
|
|
6
|
+
import { EFMedia } from "./EFMedia.ts";
|
|
7
|
+
import { TWMixin } from "../gui/TWMixin.ts";
|
|
8
8
|
|
|
9
9
|
@customElement("ef-video")
|
|
10
10
|
export class EFVideo extends TWMixin(EFMedia) {
|