@editframe/elements 0.8.0-beta.1 → 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 +0 -1
- package/dist/elements/CrossUpdateController.d.ts +0 -1
- package/dist/elements/EFAudio.d.ts +0 -1
- package/dist/elements/EFCaptions.d.ts +3 -3
- package/dist/elements/EFImage.d.ts +0 -1
- package/dist/elements/EFMedia.d.ts +5 -3
- package/dist/elements/EFSourceMixin.d.ts +0 -1
- package/dist/elements/EFTemporal.d.ts +3 -1
- package/dist/elements/EFTimegroup.browsertest.d.ts +0 -1
- package/dist/elements/EFTimegroup.d.ts +2 -5
- package/dist/elements/EFVideo.d.ts +0 -1
- package/dist/elements/EFWaveform.d.ts +0 -1
- package/dist/elements/FetchMixin.d.ts +0 -1
- package/dist/elements/TimegroupController.d.ts +0 -1
- package/dist/elements/durationConverter.d.ts +8 -0
- package/dist/elements/src/elements/EFCaptions.js +10 -7
- package/dist/elements/src/elements/EFMedia.js +25 -8
- package/dist/elements/src/elements/EFTemporal.js +51 -2
- package/dist/elements/src/elements/EFTimegroup.js +22 -39
- package/dist/elements/src/elements/EFWaveform.js +3 -3
- package/dist/elements/src/elements/parseTimeToMs.js +1 -0
- package/dist/elements/src/gui/ContextMixin.js +37 -25
- package/dist/elements/src/gui/EFFilmstrip.js +27 -25
- 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 +4 -4
- package/dist/elements/src/gui/TWMixin.css.js +1 -1
- package/dist/elements/src/gui/efContext.js +7 -0
- package/dist/elements/src/gui/playingContext.js +2 -0
- package/dist/elements/src/index.js +4 -0
- package/dist/elements/util.d.ts +0 -1
- package/dist/gui/ContextMixin.d.ts +8 -12
- package/dist/gui/EFFilmstrip.d.ts +7 -3
- package/dist/gui/EFPreview.d.ts +1 -16
- package/dist/gui/EFToggleLoop.d.ts +12 -0
- package/dist/gui/EFTogglePlay.d.ts +12 -0
- package/dist/gui/EFWorkbench.d.ts +1 -17
- package/dist/gui/TWMixin.d.ts +0 -1
- package/dist/gui/efContext.d.ts +4 -0
- package/dist/gui/playingContext.d.ts +3 -0
- package/dist/index.d.ts +2 -1
- package/dist/style.css +7 -5
- package/package.json +3 -3
- package/src/elements/EFCaptions.ts +14 -8
- package/src/elements/EFMedia.ts +37 -9
- package/src/elements/EFTemporal.ts +60 -2
- package/src/elements/EFTimegroup.ts +21 -41
- package/src/elements/EFWaveform.ts +3 -3
- package/src/elements/durationConverter.ts +20 -0
- package/src/elements/parseTimeToMs.ts +1 -0
- package/src/gui/ContextMixin.ts +50 -36
- package/src/gui/EFFilmstrip.ts +27 -28
- package/src/gui/EFToggleLoop.ts +34 -0
- package/src/gui/EFTogglePlay.ts +38 -0
- package/src/gui/EFWorkbench.ts +4 -4
- package/src/gui/efContext.ts +6 -0
- package/src/gui/playingContext.ts +2 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
export { EFTimegroup } from './elements/EFTimegroup.ts';
|
|
3
2
|
export { EFImage } from './elements/EFImage.ts';
|
|
4
3
|
export type { EFMedia } from './elements/EFMedia.ts';
|
|
@@ -9,3 +8,5 @@ export { EFWaveform } from './elements/EFWaveform.ts';
|
|
|
9
8
|
export { EFWorkbench } from './gui/EFWorkbench.ts';
|
|
10
9
|
export { EFPreview } from './gui/EFPreview.ts';
|
|
11
10
|
export { EFFilmstrip } from './gui/EFFilmstrip.ts';
|
|
11
|
+
export { EFTogglePlay } from './gui/EFTogglePlay.ts';
|
|
12
|
+
export { EFToggleLoop } from './gui/EFToggleLoop.ts';
|
package/dist/style.css
CHANGED
|
@@ -624,6 +624,9 @@ video {
|
|
|
624
624
|
.items-center {
|
|
625
625
|
align-items: center;
|
|
626
626
|
}
|
|
627
|
+
.justify-center {
|
|
628
|
+
justify-content: center;
|
|
629
|
+
}
|
|
627
630
|
.overflow-auto {
|
|
628
631
|
overflow: auto;
|
|
629
632
|
}
|
|
@@ -675,6 +678,10 @@ video {
|
|
|
675
678
|
--tw-bg-opacity: 1;
|
|
676
679
|
background-color: rgb(226 232 240 / var(--tw-bg-opacity));
|
|
677
680
|
}
|
|
681
|
+
.bg-slate-300 {
|
|
682
|
+
--tw-bg-opacity: 1;
|
|
683
|
+
background-color: rgb(203 213 225 / var(--tw-bg-opacity));
|
|
684
|
+
}
|
|
678
685
|
.bg-opacity-20 {
|
|
679
686
|
--tw-bg-opacity: 0.2;
|
|
680
687
|
}
|
|
@@ -764,11 +771,6 @@ body {
|
|
|
764
771
|
-webkit-text-size-adjust: 100%;
|
|
765
772
|
}
|
|
766
773
|
|
|
767
|
-
.hover\:bg-blue-400:hover {
|
|
768
|
-
--tw-bg-opacity: 1;
|
|
769
|
-
background-color: rgb(96 165 250 / var(--tw-bg-opacity));
|
|
770
|
-
}
|
|
771
|
-
|
|
772
774
|
.hover\:bg-slate-400:hover {
|
|
773
775
|
--tw-bg-opacity: 1;
|
|
774
776
|
background-color: rgb(148 163 184 / var(--tw-bg-opacity));
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.8.0-beta.
|
|
3
|
+
"version": "0.8.0-beta.10",
|
|
4
4
|
"description": "",
|
|
5
5
|
"exports": {
|
|
6
6
|
".": {
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
"author": "",
|
|
21
21
|
"license": "UNLICENSED",
|
|
22
22
|
"dependencies": {
|
|
23
|
-
"@editframe/assets": "0.8.0-beta.
|
|
23
|
+
"@editframe/assets": "0.8.0-beta.10",
|
|
24
24
|
"@lit/context": "^1.1.2",
|
|
25
25
|
"@lit/task": "^1.0.1",
|
|
26
26
|
"d3": "^7.9.0",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"autoprefixer": "^10.4.19",
|
|
36
36
|
"rollup-plugin-tsconfig-paths": "^1.5.2",
|
|
37
37
|
"typescript": "^5.5.4",
|
|
38
|
-
"vite-plugin-dts": "^
|
|
38
|
+
"vite-plugin-dts": "^4.0.3",
|
|
39
39
|
"vite-tsconfig-paths": "^4.3.2"
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -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 = "";
|
|
@@ -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/EFMedia.ts
CHANGED
|
@@ -145,7 +145,11 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
145
145
|
|
|
146
146
|
const result: Record<
|
|
147
147
|
string,
|
|
148
|
-
{
|
|
148
|
+
{
|
|
149
|
+
segment: TrackSegment;
|
|
150
|
+
track: MP4Box.TrackInfo;
|
|
151
|
+
nextSegment?: TrackSegment;
|
|
152
|
+
}
|
|
149
153
|
> = {};
|
|
150
154
|
|
|
151
155
|
for (const index of Object.values(fragmentIndex)) {
|
|
@@ -161,11 +165,15 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
161
165
|
return (segment.dts / track.timescale) * 1000 <= seekToMs;
|
|
162
166
|
});
|
|
163
167
|
|
|
168
|
+
const nextSegment = index.segments.find((segment) => {
|
|
169
|
+
return (segment.dts / track.timescale) * 1000 > seekToMs;
|
|
170
|
+
});
|
|
171
|
+
|
|
164
172
|
if (!segment) {
|
|
165
173
|
return;
|
|
166
174
|
}
|
|
167
175
|
|
|
168
|
-
result[index.track] = { segment, track };
|
|
176
|
+
result[index.track] = { segment, track, nextSegment };
|
|
169
177
|
}
|
|
170
178
|
|
|
171
179
|
return result;
|
|
@@ -187,7 +195,9 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
187
195
|
|
|
188
196
|
const files: Record<string, File> = {};
|
|
189
197
|
|
|
190
|
-
for (const [trackId, { segment, track }] of Object.entries(
|
|
198
|
+
for (const [trackId, { segment, track, nextSegment }] of Object.entries(
|
|
199
|
+
seekResult,
|
|
200
|
+
)) {
|
|
191
201
|
const start = segment.offset;
|
|
192
202
|
const end = segment.offset + segment.size;
|
|
193
203
|
|
|
@@ -196,6 +206,21 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
196
206
|
headers: { Range: `bytes=${start}-${end}` },
|
|
197
207
|
});
|
|
198
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
|
+
|
|
199
224
|
const initSegment = Object.values(initSegments).find(
|
|
200
225
|
(initSegment) => initSegment.trackId === String(track.id),
|
|
201
226
|
);
|
|
@@ -253,7 +278,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
253
278
|
changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>,
|
|
254
279
|
): void {
|
|
255
280
|
if (changedProperties.has("ownCurrentTimeMs")) {
|
|
256
|
-
this.executeSeek(this.
|
|
281
|
+
this.executeSeek(this.trimAdjustedOwnCurrentTimeMs);
|
|
257
282
|
}
|
|
258
283
|
}
|
|
259
284
|
|
|
@@ -274,7 +299,7 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
274
299
|
if (durations.length === 0) {
|
|
275
300
|
return 0;
|
|
276
301
|
}
|
|
277
|
-
return Math.max(...durations);
|
|
302
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
278
303
|
}
|
|
279
304
|
|
|
280
305
|
get startTimeMs() {
|
|
@@ -315,8 +340,8 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
315
340
|
|
|
316
341
|
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
317
342
|
// Adjust range for track's own time
|
|
318
|
-
fromMs -= this.startTimeMs;
|
|
319
|
-
toMs -= this.startTimeMs;
|
|
343
|
+
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
344
|
+
toMs -= this.startTimeMs - this.trimStartMs;
|
|
320
345
|
|
|
321
346
|
await this.trackFragmentIndexLoader.taskComplete;
|
|
322
347
|
const audioTrackId = this.defaultAudioTrackId;
|
|
@@ -383,10 +408,13 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
383
408
|
|
|
384
409
|
return {
|
|
385
410
|
blob: audioBlob,
|
|
386
|
-
startMs:
|
|
411
|
+
startMs:
|
|
412
|
+
(firstFragment.dts / audioTrackIndex.timescale) * 1000 -
|
|
413
|
+
this.trimStartMs,
|
|
387
414
|
endMs:
|
|
388
415
|
(lastFragment.dts / audioTrackIndex.timescale) * 1000 +
|
|
389
|
-
(lastFragment.duration / audioTrackIndex.timescale) * 1000
|
|
416
|
+
(lastFragment.duration / audioTrackIndex.timescale) * 1000 -
|
|
417
|
+
this.trimEndMs,
|
|
390
418
|
};
|
|
391
419
|
}
|
|
392
420
|
}
|
|
@@ -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;
|
|
@@ -153,6 +156,36 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
153
156
|
})
|
|
154
157
|
private _durationMs?: number;
|
|
155
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
|
+
|
|
156
189
|
@state()
|
|
157
190
|
rootTimegroup?: EFTimegroup = this.getRootTimegroup();
|
|
158
191
|
|
|
@@ -220,9 +253,15 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
220
253
|
}
|
|
221
254
|
startTimeMsCache.set(
|
|
222
255
|
this,
|
|
223
|
-
previous.startTimeMs +
|
|
256
|
+
previous.startTimeMs +
|
|
257
|
+
previous.durationMs -
|
|
258
|
+
parentTimegroup.overlapMs,
|
|
259
|
+
);
|
|
260
|
+
return (
|
|
261
|
+
previous.startTimeMs +
|
|
262
|
+
previous.durationMs -
|
|
263
|
+
parentTimegroup.overlapMs
|
|
224
264
|
);
|
|
225
|
-
return previous.startTimeMs + previous.durationMs;
|
|
226
265
|
}
|
|
227
266
|
case "contain":
|
|
228
267
|
case "fixed":
|
|
@@ -250,6 +289,25 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
250
289
|
return 0;
|
|
251
290
|
}
|
|
252
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
|
+
|
|
253
311
|
frameTask = new Task(this, {
|
|
254
312
|
autoRun: EF_INTERACTIVE,
|
|
255
313
|
args: () => [this.ownCurrentTimeMs] as const,
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
import { TimegroupController } from "./TimegroupController.ts";
|
|
14
14
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
15
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
|
}
|
|
@@ -134,32 +123,18 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
134
123
|
return `ef-timegroup-${this.id}`;
|
|
135
124
|
}
|
|
136
125
|
|
|
137
|
-
get crossoverStartMs() {
|
|
138
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
139
|
-
if (!parentTimeGroup || !this.previousElementSibling) {
|
|
140
|
-
return 0;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
return parentTimeGroup.crossoverMs;
|
|
144
|
-
}
|
|
145
|
-
get crossoverEndMs() {
|
|
146
|
-
const parentTimeGroup = this.parentTimegroup;
|
|
147
|
-
if (!parentTimeGroup || !this.nextElementSibling) {
|
|
148
|
-
return 0;
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
return parentTimeGroup.crossoverMs;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
126
|
get durationMs() {
|
|
155
127
|
switch (this.mode) {
|
|
156
128
|
case "fixed":
|
|
157
129
|
return super.durationMs;
|
|
158
130
|
case "sequence": {
|
|
159
131
|
let duration = 0;
|
|
160
|
-
|
|
132
|
+
this.childTemporals.forEach((node, index) => {
|
|
133
|
+
if (index > 0) {
|
|
134
|
+
duration -= this.overlapMs;
|
|
135
|
+
}
|
|
161
136
|
duration += node.durationMs;
|
|
162
|
-
}
|
|
137
|
+
});
|
|
163
138
|
return duration;
|
|
164
139
|
}
|
|
165
140
|
case "contain": {
|
|
@@ -207,9 +182,14 @@ export class EFTimegroup extends EFTemporal(LitElement) {
|
|
|
207
182
|
this.style.display = "";
|
|
208
183
|
|
|
209
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
|
+
);
|
|
210
190
|
this.style.setProperty(
|
|
211
|
-
"--ef-
|
|
212
|
-
`${this.durationMs
|
|
191
|
+
"--ef-transition-out-start",
|
|
192
|
+
`${this.durationMs - (this.parentTimegroup?.overlapMs ?? 0)}ms`,
|
|
213
193
|
);
|
|
214
194
|
|
|
215
195
|
for (const animation of animations) {
|
|
@@ -351,7 +351,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
351
351
|
if (!this.targetElement.audioBufferTask.value) {
|
|
352
352
|
return;
|
|
353
353
|
}
|
|
354
|
-
if (this.targetElement.
|
|
354
|
+
if (this.targetElement.trimAdjustedOwnCurrentTimeMs > 0) {
|
|
355
355
|
const audioContext = new OfflineAudioContext(2, 48000 / 25, 48000);
|
|
356
356
|
const audioBufferSource = audioContext.createBufferSource();
|
|
357
357
|
audioBufferSource.buffer =
|
|
@@ -364,7 +364,7 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
364
364
|
0,
|
|
365
365
|
Math.max(
|
|
366
366
|
0,
|
|
367
|
-
(this.targetElement.
|
|
367
|
+
(this.targetElement.trimAdjustedOwnCurrentTimeMs -
|
|
368
368
|
this.targetElement.audioBufferTask.value.startOffsetMs) /
|
|
369
369
|
1000,
|
|
370
370
|
),
|
|
@@ -412,6 +412,6 @@ export class EFWaveform extends EFTemporal(TWMixin(LitElement)) {
|
|
|
412
412
|
if (target instanceof EFAudio || target instanceof EFVideo) {
|
|
413
413
|
return target;
|
|
414
414
|
}
|
|
415
|
-
throw new Error("Invalid target, must be an EFAudio element");
|
|
415
|
+
throw new Error("Invalid target, must be an EFAudio or EFVideo element");
|
|
416
416
|
}
|
|
417
417
|
}
|
|
@@ -4,3 +4,23 @@ export const durationConverter = {
|
|
|
4
4
|
fromAttribute: (value: string): number => parseTimeToMs(value),
|
|
5
5
|
toAttribute: (value: number) => `${value}s`,
|
|
6
6
|
};
|
|
7
|
+
|
|
8
|
+
const positiveDurationConverter = (error: string) => {
|
|
9
|
+
return {
|
|
10
|
+
fromAttribute: (value: string): number => {
|
|
11
|
+
if (value.startsWith("-")) {
|
|
12
|
+
throw new Error(error);
|
|
13
|
+
}
|
|
14
|
+
return parseTimeToMs(value);
|
|
15
|
+
},
|
|
16
|
+
toAttribute: (value: number) => `${value}s`,
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export const trimDurationConverter = positiveDurationConverter(
|
|
21
|
+
"Trimstart & trimend must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export const imageDurationConverter = positiveDurationConverter(
|
|
25
|
+
"Image duration must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
26
|
+
);
|
package/src/gui/ContextMixin.ts
CHANGED
|
@@ -5,25 +5,23 @@ import { property, state } from "lit/decorators.js";
|
|
|
5
5
|
import { focusContext, type FocusContext } from "./focusContext.ts";
|
|
6
6
|
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
7
7
|
import { fetchContext } from "./fetchContext.ts";
|
|
8
|
-
import { apiHostContext } from "./apiHostContext.ts";
|
|
9
8
|
import { createRef } from "lit/directives/ref.js";
|
|
10
|
-
import { playingContext } from "./playingContext.ts";
|
|
9
|
+
import { loopContext, playingContext } from "./playingContext.ts";
|
|
11
10
|
import type { EFTimegroup } from "../elements/EFTimegroup.ts";
|
|
11
|
+
import { efContext } from "./efContext.ts";
|
|
12
12
|
|
|
13
|
-
declare class ContextMixinInterface {
|
|
14
|
-
focusContext: FocusContext;
|
|
15
|
-
focusedElement?: HTMLElement;
|
|
16
|
-
fetch: typeof fetch;
|
|
13
|
+
export declare class ContextMixinInterface {
|
|
17
14
|
signingURL?: string;
|
|
18
|
-
apiToken?: string;
|
|
19
|
-
apiHost: string;
|
|
20
|
-
stageScale: number;
|
|
21
15
|
rendering: boolean;
|
|
22
|
-
stageRef: ReturnType<typeof createRef<HTMLDivElement>>;
|
|
23
|
-
canvasRef: ReturnType<typeof createRef<HTMLElement>>;
|
|
24
16
|
playing: boolean;
|
|
25
|
-
|
|
17
|
+
loop: boolean;
|
|
26
18
|
currentTimeMs: number;
|
|
19
|
+
focusedElement?: HTMLElement;
|
|
20
|
+
stageRef: ReturnType<typeof createRef<HTMLDivElement>>;
|
|
21
|
+
canvasRef: ReturnType<typeof createRef<HTMLElement>>;
|
|
22
|
+
targetTimegroup: EFTimegroup | null;
|
|
23
|
+
play(): void;
|
|
24
|
+
pause(): void;
|
|
27
25
|
}
|
|
28
26
|
|
|
29
27
|
type Constructor<T = {}> = new (...args: any[]) => T;
|
|
@@ -36,6 +34,9 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
36
34
|
@state()
|
|
37
35
|
focusedElement?: HTMLElement;
|
|
38
36
|
|
|
37
|
+
@provide({ context: efContext })
|
|
38
|
+
efContext = this;
|
|
39
|
+
|
|
39
40
|
@provide({ context: fetchContext })
|
|
40
41
|
fetch = async (url: string, init: RequestInit = {}) => {
|
|
41
42
|
init.headers ||= {};
|
|
@@ -43,21 +44,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
43
44
|
"Content-Type": "application/json",
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
const bearerToken = this.apiToken;
|
|
47
|
-
if (bearerToken) {
|
|
48
|
-
Object.assign(init.headers, {
|
|
49
|
-
Authorization: `Bearer ${bearerToken}`,
|
|
50
|
-
});
|
|
51
|
-
}
|
|
52
|
-
|
|
53
47
|
if (this.signingURL) {
|
|
54
|
-
if (!this.#
|
|
55
|
-
this.#
|
|
48
|
+
if (!this.#URLTokens[url]) {
|
|
49
|
+
this.#URLTokens[url] = fetch(this.signingURL, {
|
|
56
50
|
method: "POST",
|
|
57
51
|
body: JSON.stringify({ url }),
|
|
58
52
|
}).then(async (response) => {
|
|
59
53
|
if (response.ok) {
|
|
60
|
-
return (await response.json()).
|
|
54
|
+
return (await response.json()).token;
|
|
61
55
|
}
|
|
62
56
|
throw new Error(
|
|
63
57
|
`Failed to sign URL: ${url}. SigningURL: ${this.signingURL} ${response.status} ${response.statusText}`,
|
|
@@ -65,35 +59,35 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
65
59
|
});
|
|
66
60
|
}
|
|
67
61
|
|
|
68
|
-
const
|
|
62
|
+
const urlToken = await this.#URLTokens[url];
|
|
69
63
|
|
|
70
|
-
|
|
64
|
+
Object.assign(init.headers, {
|
|
65
|
+
authorization: `Bearer ${urlToken}`,
|
|
66
|
+
});
|
|
71
67
|
}
|
|
72
68
|
|
|
73
69
|
return fetch(url, init);
|
|
74
70
|
};
|
|
75
71
|
|
|
76
|
-
#
|
|
72
|
+
#URLTokens: Record<string, Promise<string>> = {};
|
|
77
73
|
|
|
74
|
+
/**
|
|
75
|
+
* A URL that will be used to generated signed tokens for accessing media files from the
|
|
76
|
+
* editframe API. This is used to authenticate media requests per-user.
|
|
77
|
+
*/
|
|
78
78
|
@property({ type: String })
|
|
79
79
|
signingURL?: string;
|
|
80
80
|
|
|
81
|
-
@property({ type: String })
|
|
82
|
-
apiToken?: string;
|
|
83
|
-
|
|
84
|
-
@provide({ context: apiHostContext })
|
|
85
|
-
@property({ type: String })
|
|
86
|
-
apiHost = "";
|
|
87
|
-
|
|
88
81
|
@provide({ context: playingContext })
|
|
89
82
|
@property({ type: Boolean, reflect: true })
|
|
90
83
|
playing = false;
|
|
91
84
|
|
|
85
|
+
@provide({ context: loopContext })
|
|
92
86
|
@property({ type: Boolean, reflect: true })
|
|
93
87
|
loop = false;
|
|
94
88
|
|
|
95
89
|
@state()
|
|
96
|
-
stageScale = 1;
|
|
90
|
+
private stageScale = 1;
|
|
97
91
|
|
|
98
92
|
@property({ type: Boolean })
|
|
99
93
|
rendering = false;
|
|
@@ -108,9 +102,11 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
108
102
|
if (this.isConnected && !this.rendering) {
|
|
109
103
|
const canvasElement = this.canvasRef.value;
|
|
110
104
|
const stageElement = this.stageRef.value;
|
|
111
|
-
|
|
105
|
+
const canvasChild = canvasElement?.assignedElements()[0];
|
|
106
|
+
if (stageElement && canvasElement && canvasChild) {
|
|
112
107
|
// Determine the appropriate scale factor to make the canvas fit into
|
|
113
|
-
|
|
108
|
+
canvasElement.style.width = `${canvasChild.clientWidth}px`;
|
|
109
|
+
canvasElement.style.height = `${canvasChild.clientHeight}px`;
|
|
114
110
|
const stageWidth = stageElement.clientWidth;
|
|
115
111
|
const stageHeight = stageElement.clientHeight;
|
|
116
112
|
const canvasWidth = canvasElement.clientWidth;
|
|
@@ -121,12 +117,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
121
117
|
const scale = stageHeight / canvasHeight;
|
|
122
118
|
if (this.stageScale !== scale) {
|
|
123
119
|
canvasElement.style.transform = `scale(${scale})`;
|
|
120
|
+
canvasElement.style.transformOrigin = "top";
|
|
124
121
|
}
|
|
125
122
|
this.stageScale = scale;
|
|
126
123
|
} else {
|
|
127
124
|
const scale = stageWidth / canvasWidth;
|
|
128
125
|
if (this.stageScale !== scale) {
|
|
129
126
|
canvasElement.style.transform = `scale(${scale})`;
|
|
127
|
+
canvasElement.style.transformOrigin = "top";
|
|
130
128
|
}
|
|
131
129
|
this.stageScale = scale;
|
|
132
130
|
}
|
|
@@ -165,6 +163,14 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
165
163
|
return this.querySelector("ef-timegroup");
|
|
166
164
|
}
|
|
167
165
|
|
|
166
|
+
play() {
|
|
167
|
+
this.playing = true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
pause() {
|
|
171
|
+
this.playing = false;
|
|
172
|
+
}
|
|
173
|
+
|
|
168
174
|
#playbackAudioContext: AudioContext | null = null;
|
|
169
175
|
#playbackAnimationFrameRequest: number | null = null;
|
|
170
176
|
#AUDIO_PLAYBACK_SLICE_MS = 1000;
|
|
@@ -237,7 +243,15 @@ export function ContextMixin<T extends Constructor<LitElement>>(superClass: T) {
|
|
|
237
243
|
source.onended = () => {
|
|
238
244
|
bufferCount--;
|
|
239
245
|
if (endMs >= toMs) {
|
|
240
|
-
this.
|
|
246
|
+
this.pause();
|
|
247
|
+
if (this.loop) {
|
|
248
|
+
this.updateComplete.then(() => {
|
|
249
|
+
this.currentTimeMs = 0;
|
|
250
|
+
this.updateComplete.then(() => {
|
|
251
|
+
this.play();
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
}
|
|
241
255
|
} else {
|
|
242
256
|
fillBuffer();
|
|
243
257
|
}
|