@editframe/elements 0.10.0-beta.7 → 0.11.0-beta.2
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 +1 -1
- package/dist/elements/EFMedia.d.ts +4 -4
- package/dist/elements/EFTemporal.browsertest.d.ts +10 -0
- package/dist/elements/EFTemporal.d.ts +10 -2
- package/dist/elements/EFTimegroup.d.ts +1 -1
- package/dist/elements/EFVideo.d.ts +1 -1
- package/dist/elements/durationConverter.d.ts +4 -0
- package/dist/elements/src/elements/EFMedia.js +24 -12
- package/dist/elements/src/elements/EFTemporal.js +93 -3
- package/dist/elements/src/gui/EFFilmstrip.js +10 -10
- package/dist/gui/EFFilmstrip.d.ts +4 -4
- package/package.json +2 -2
- package/src/elements/EFMedia.browsertest.ts +231 -2
- package/src/elements/EFMedia.ts +43 -11
- package/src/elements/EFTemporal.browsertest.ts +65 -0
- package/src/elements/EFTemporal.ts +125 -6
- package/src/elements/EFTimegroup.browsertest.ts +1 -1
- package/src/elements/durationConverter.ts +4 -0
- package/src/gui/EFFilmstrip.ts +19 -19
- /package/dist/assets/{dist → src}/EncodedAsset.js +0 -0
- /package/dist/assets/{dist → src}/MP4File.js +0 -0
- /package/dist/assets/{dist → src}/memoize.js +0 -0
package/dist/EF_FRAMEGEN.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { LitElement, PropertyValueMap } from 'lit';
|
|
2
1
|
import { Task } from '@lit/task';
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import { VideoAsset } from '
|
|
2
|
+
import { LitElement, PropertyValueMap } from 'lit';
|
|
3
|
+
import { TrackFragmentIndex, TrackSegment } from '../../../assets/src/index.ts';
|
|
4
|
+
import { VideoAsset } from '../../../assets/src/EncodedAsset.ts';
|
|
5
|
+
import { MP4File } from '../../../assets/src/MP4File.ts';
|
|
6
6
|
import type * as MP4Box from "mp4box";
|
|
7
7
|
export declare const deepGetMediaElements: (element: Element, medias?: EFMedia[]) => EFMedia[];
|
|
8
8
|
declare const EFMedia_base: (new (...args: any[]) => import('./EFSourceMixin.ts').EFSourceMixinInterface) & (new (...args: any[]) => import('./EFTemporal.ts').TemporalMixinInterface) & (new (...args: any[]) => import('./FetchMixin.ts').FetchMixinInterface) & typeof LitElement;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { LitElement } from 'lit';
|
|
2
|
+
declare const TestTemporal_base: (new (...args: any[]) => import('./EFTemporal.ts').TemporalMixinInterface) & typeof LitElement;
|
|
3
|
+
declare class TestTemporal extends TestTemporal_base {
|
|
4
|
+
}
|
|
5
|
+
declare global {
|
|
6
|
+
interface HTMLElementTagNameMap {
|
|
7
|
+
"test-temporal": TestTemporal;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export {};
|
|
@@ -8,6 +8,16 @@ export declare class TemporalMixinInterface {
|
|
|
8
8
|
get hasOwnDuration(): boolean;
|
|
9
9
|
get trimStartMs(): number;
|
|
10
10
|
get trimEndMs(): number;
|
|
11
|
+
set trimStartMs(value: number);
|
|
12
|
+
set trimEndMs(value: number);
|
|
13
|
+
set trimstart(value: string);
|
|
14
|
+
set trimend(value: string);
|
|
15
|
+
get sourceInMs(): number;
|
|
16
|
+
get sourceOutMs(): number;
|
|
17
|
+
set sourceInMs(value: number);
|
|
18
|
+
set sourceOutMs(value: number);
|
|
19
|
+
set sourcein(value: string);
|
|
20
|
+
set sourceout(value: string);
|
|
11
21
|
get durationMs(): number;
|
|
12
22
|
get startTimeMs(): number;
|
|
13
23
|
get startTimeWithinParentMs(): number;
|
|
@@ -16,8 +26,6 @@ export declare class TemporalMixinInterface {
|
|
|
16
26
|
get trimAdjustedOwnCurrentTimeMs(): number;
|
|
17
27
|
set duration(value: string);
|
|
18
28
|
get duration(): string;
|
|
19
|
-
set trimstart(value: string);
|
|
20
|
-
set trimend(value: string);
|
|
21
29
|
parentTimegroup?: EFTimegroup;
|
|
22
30
|
rootTimegroup?: EFTimegroup;
|
|
23
31
|
frameTask: Task<readonly unknown[], unknown>;
|
|
@@ -17,7 +17,7 @@ export declare class EFTimegroup extends EFTimegroup_base {
|
|
|
17
17
|
connectedCallback(): void;
|
|
18
18
|
get storageKey(): string;
|
|
19
19
|
get durationMs(): number;
|
|
20
|
-
waitForMediaDurations(): Promise<Record<number, import('
|
|
20
|
+
waitForMediaDurations(): Promise<Record<number, import('../../../assets/src/index.ts').TrackFragmentIndex>[]>;
|
|
21
21
|
get childTemporals(): import('./EFTemporal.ts').TemporalMixinInterface[];
|
|
22
22
|
protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
|
|
23
23
|
shouldWrapWithWorkbench(): boolean;
|
|
@@ -8,7 +8,7 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
8
8
|
render(): import('lit-html').TemplateResult<1>;
|
|
9
9
|
get canvasElement(): HTMLCanvasElement | undefined;
|
|
10
10
|
frameTask: Task<readonly [import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus], void>;
|
|
11
|
-
paintTask: Task<readonly [import('
|
|
11
|
+
paintTask: Task<readonly [import('../../../assets/src/EncodedAsset.ts').VideoAsset | undefined, number], number | undefined>;
|
|
12
12
|
}
|
|
13
13
|
declare global {
|
|
14
14
|
interface HTMLElementTagNameMap {
|
|
@@ -10,3 +10,7 @@ export declare const imageDurationConverter: {
|
|
|
10
10
|
fromAttribute: (value: string) => number;
|
|
11
11
|
toAttribute: (value: number) => string;
|
|
12
12
|
};
|
|
13
|
+
export declare const sourceDurationConverter: {
|
|
14
|
+
fromAttribute: (value: string) => number;
|
|
15
|
+
toAttribute: (value: number) => string;
|
|
16
|
+
};
|
|
@@ -1,18 +1,18 @@
|
|
|
1
|
-
import { css, LitElement } from "lit";
|
|
2
|
-
import { property, state } from "lit/decorators.js";
|
|
3
|
-
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
-
import { Task } from "@lit/task";
|
|
5
1
|
import { consume } from "@lit/context";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
6
4
|
import debug from "debug";
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
5
|
+
import { css, LitElement } from "lit";
|
|
6
|
+
import { property, state } from "lit/decorators.js";
|
|
7
|
+
import { VideoAsset } from "../../../assets/src/EncodedAsset.js";
|
|
8
|
+
import { MP4File } from "../../../assets/src/MP4File.js";
|
|
9
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
10
|
+
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
11
|
+
import { apiHostContext } from "../gui/apiHostContext.js";
|
|
12
|
+
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
9
13
|
import { EFTemporal } from "./EFTemporal.js";
|
|
10
14
|
import { FetchMixin } from "./FetchMixin.js";
|
|
11
|
-
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
12
15
|
import { getStartTimeMs } from "./util.js";
|
|
13
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
14
|
-
import { apiHostContext } from "../gui/apiHostContext.js";
|
|
15
|
-
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
16
16
|
var __defProp = Object.defineProperty;
|
|
17
17
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
18
18
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -291,6 +291,18 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
291
291
|
if (durations.length === 0) {
|
|
292
292
|
return 0;
|
|
293
293
|
}
|
|
294
|
+
if (this.sourceInMs && this.sourceOutMs && this.sourceOutMs > this.sourceInMs) {
|
|
295
|
+
return Math.max(this.sourceOutMs - this.sourceInMs);
|
|
296
|
+
}
|
|
297
|
+
if (this.sourceInMs) {
|
|
298
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs - this.sourceInMs;
|
|
299
|
+
}
|
|
300
|
+
if (this.sourceOutMs) {
|
|
301
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs - this.sourceOutMs;
|
|
302
|
+
}
|
|
303
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
304
|
+
return Math.max(...durations) - this.trimStartMs - this.trimEndMs - this.sourceOutMs - this.sourceInMs;
|
|
305
|
+
}
|
|
294
306
|
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
295
307
|
}
|
|
296
308
|
get startTimeMs() {
|
|
@@ -298,8 +310,8 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
298
310
|
}
|
|
299
311
|
#audioContext;
|
|
300
312
|
async fetchAudioSpanningTime(fromMs, toMs) {
|
|
301
|
-
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
302
|
-
toMs -= this.startTimeMs - this.trimStartMs;
|
|
313
|
+
fromMs -= this.startTimeMs - this.trimStartMs - this.sourceInMs;
|
|
314
|
+
toMs -= this.startTimeMs - this.trimStartMs - this.sourceOutMs;
|
|
303
315
|
await this.trackFragmentIndexLoader.taskComplete;
|
|
304
316
|
const audioTrackId = this.defaultAudioTrackId;
|
|
305
317
|
if (!audioTrackId) {
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { createContext, consume } from "@lit/context";
|
|
2
2
|
import { property, state } from "lit/decorators.js";
|
|
3
|
-
import { durationConverter } from "./durationConverter.js";
|
|
4
3
|
import { Task } from "@lit/task";
|
|
5
4
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
5
|
+
import { durationConverter } from "./durationConverter.js";
|
|
6
6
|
var __defProp = Object.defineProperty;
|
|
7
7
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
8
8
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -118,9 +118,68 @@ const EFTemporal = (superClass) => {
|
|
|
118
118
|
get trimStartMs() {
|
|
119
119
|
return this._trimStartMs;
|
|
120
120
|
}
|
|
121
|
+
set trimStartMs(value) {
|
|
122
|
+
this._trimStartMs = value;
|
|
123
|
+
this.setAttribute(
|
|
124
|
+
"trimstart",
|
|
125
|
+
durationConverter.toAttribute(value / 1e3)
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
set trimstart(value) {
|
|
129
|
+
if (value !== void 0) {
|
|
130
|
+
this.setAttribute("trimstart", value);
|
|
131
|
+
} else {
|
|
132
|
+
this.removeAttribute("trimstart");
|
|
133
|
+
}
|
|
134
|
+
}
|
|
121
135
|
get trimEndMs() {
|
|
122
136
|
return this._trimEndMs;
|
|
123
137
|
}
|
|
138
|
+
set trimEndMs(value) {
|
|
139
|
+
this._trimEndMs = value;
|
|
140
|
+
this.setAttribute("trimend", durationConverter.toAttribute(value / 1e3));
|
|
141
|
+
}
|
|
142
|
+
set trimend(value) {
|
|
143
|
+
if (value !== void 0) {
|
|
144
|
+
this.setAttribute("trimend", value);
|
|
145
|
+
} else {
|
|
146
|
+
this.removeAttribute("trimend");
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
get sourceInMs() {
|
|
150
|
+
return this._sourceInMs;
|
|
151
|
+
}
|
|
152
|
+
set sourceInMs(value) {
|
|
153
|
+
this._sourceInMs = value;
|
|
154
|
+
value !== void 0 ? this.setAttribute(
|
|
155
|
+
"sourcein",
|
|
156
|
+
durationConverter.toAttribute(value / 1e3)
|
|
157
|
+
) : this.removeAttribute("sourcein");
|
|
158
|
+
}
|
|
159
|
+
set sourcein(value) {
|
|
160
|
+
if (value !== void 0) {
|
|
161
|
+
this.setAttribute("sourcein", value);
|
|
162
|
+
} else {
|
|
163
|
+
this.removeAttribute("sourcein");
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
get sourceOutMs() {
|
|
167
|
+
return this._sourceOutMs;
|
|
168
|
+
}
|
|
169
|
+
set sourceOutMs(value) {
|
|
170
|
+
this._sourceOutMs = value;
|
|
171
|
+
value !== void 0 ? this.setAttribute(
|
|
172
|
+
"sourceout",
|
|
173
|
+
durationConverter.toAttribute(value / 1e3)
|
|
174
|
+
) : this.removeAttribute("sourceout");
|
|
175
|
+
}
|
|
176
|
+
set sourceout(value) {
|
|
177
|
+
if (value !== void 0) {
|
|
178
|
+
this.setAttribute("sourceout", value);
|
|
179
|
+
} else {
|
|
180
|
+
this.removeAttribute("sourceout");
|
|
181
|
+
}
|
|
182
|
+
}
|
|
124
183
|
get startOffsetMs() {
|
|
125
184
|
return this._startOffsetMs;
|
|
126
185
|
}
|
|
@@ -137,6 +196,12 @@ const EFTemporal = (superClass) => {
|
|
|
137
196
|
// Defining this as a getter to a private property allows us to
|
|
138
197
|
// override it classes that include this mixin.
|
|
139
198
|
get durationMs() {
|
|
199
|
+
if (this.sourceInMs) {
|
|
200
|
+
return this._durationMs || this.parentTimegroup?.durationMs || 0 - this.sourceInMs;
|
|
201
|
+
}
|
|
202
|
+
if (this.sourceOutMs) {
|
|
203
|
+
return this._durationMs || this.parentTimegroup?.durationMs || 0 - this.sourceOutMs;
|
|
204
|
+
}
|
|
140
205
|
return this._durationMs || this.parentTimegroup?.durationMs || 0;
|
|
141
206
|
}
|
|
142
207
|
get offsetMs() {
|
|
@@ -214,6 +279,15 @@ const EFTemporal = (superClass) => {
|
|
|
214
279
|
*/
|
|
215
280
|
get trimAdjustedOwnCurrentTimeMs() {
|
|
216
281
|
if (this.rootTimegroup) {
|
|
282
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
283
|
+
return Math.min(
|
|
284
|
+
Math.max(
|
|
285
|
+
0,
|
|
286
|
+
this.rootTimegroup.currentTimeMs - this.startTimeMs + this.trimStartMs + this.sourceInMs
|
|
287
|
+
),
|
|
288
|
+
this.durationMs + Math.abs(this.startOffsetMs) + this.trimStartMs + this.sourceInMs
|
|
289
|
+
);
|
|
290
|
+
}
|
|
217
291
|
return Math.min(
|
|
218
292
|
Math.max(
|
|
219
293
|
0,
|
|
@@ -249,14 +323,30 @@ const EFTemporal = (superClass) => {
|
|
|
249
323
|
attribute: "trimstart",
|
|
250
324
|
converter: durationConverter
|
|
251
325
|
})
|
|
252
|
-
], TemporalMixinClass.prototype, "
|
|
326
|
+
], TemporalMixinClass.prototype, "trimStartMs", 1);
|
|
253
327
|
__decorateClass([
|
|
254
328
|
property({
|
|
255
329
|
type: Number,
|
|
256
330
|
attribute: "trimend",
|
|
257
331
|
converter: durationConverter
|
|
258
332
|
})
|
|
259
|
-
], TemporalMixinClass.prototype, "
|
|
333
|
+
], TemporalMixinClass.prototype, "trimEndMs", 1);
|
|
334
|
+
__decorateClass([
|
|
335
|
+
property({
|
|
336
|
+
type: Number,
|
|
337
|
+
attribute: "sourcein",
|
|
338
|
+
converter: durationConverter,
|
|
339
|
+
reflect: true
|
|
340
|
+
})
|
|
341
|
+
], TemporalMixinClass.prototype, "sourceInMs", 1);
|
|
342
|
+
__decorateClass([
|
|
343
|
+
property({
|
|
344
|
+
type: Number,
|
|
345
|
+
attribute: "sourceout",
|
|
346
|
+
converter: durationConverter,
|
|
347
|
+
reflect: true
|
|
348
|
+
})
|
|
349
|
+
], TemporalMixinClass.prototype, "sourceOutMs", 1);
|
|
260
350
|
__decorateClass([
|
|
261
351
|
property({
|
|
262
352
|
type: Number,
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
1
2
|
import { css, html, nothing, LitElement } from "lit";
|
|
2
3
|
import { property, customElement, state, eventOptions } from "lit/decorators.js";
|
|
3
|
-
import { consume } from "@lit/context";
|
|
4
|
-
import { styleMap } from "lit/directives/style-map.js";
|
|
5
4
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
6
|
-
import {
|
|
5
|
+
import { styleMap } from "lit/directives/style-map.js";
|
|
7
6
|
import { EFAudio } from "../elements/EFAudio.js";
|
|
8
|
-
import { EFVideo } from "../elements/EFVideo.js";
|
|
9
7
|
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.js";
|
|
10
|
-
import {
|
|
8
|
+
import { EFImage } from "../elements/EFImage.js";
|
|
11
9
|
import { EFTimegroup } from "../elements/EFTimegroup.js";
|
|
10
|
+
import { EFVideo } from "../elements/EFVideo.js";
|
|
11
|
+
import { EFWaveform } from "../elements/EFWaveform.js";
|
|
12
12
|
import { TimegroupController } from "../elements/TimegroupController.js";
|
|
13
|
-
import { TWMixin } from "./TWMixin.js";
|
|
14
13
|
import { msToTimeCode } from "../msToTimeCode.js";
|
|
15
|
-
import {
|
|
14
|
+
import { TWMixin } from "./TWMixin.js";
|
|
16
15
|
import { focusContext } from "./focusContext.js";
|
|
16
|
+
import { focusedElementContext } from "./focusedElementContext.js";
|
|
17
17
|
import { playingContext, loopContext } from "./playingContext.js";
|
|
18
18
|
var __defProp = Object.defineProperty;
|
|
19
19
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
@@ -76,14 +76,14 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
76
76
|
get gutterStyles() {
|
|
77
77
|
return {
|
|
78
78
|
position: "relative",
|
|
79
|
-
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs)}px`,
|
|
80
|
-
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs)}px`
|
|
79
|
+
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs - this.element.sourceInMs)}px`,
|
|
80
|
+
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs + this.element.sourceOutMs + this.element.sourceInMs)}px`
|
|
81
81
|
};
|
|
82
82
|
}
|
|
83
83
|
get trimPortionStyles() {
|
|
84
84
|
return {
|
|
85
85
|
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
86
|
-
left: `${this.pixelsPerMs * this.element.trimStartMs}px`
|
|
86
|
+
left: `${this.pixelsPerMs * (this.element.trimStartMs + this.element.sourceInMs)}px`
|
|
87
87
|
};
|
|
88
88
|
}
|
|
89
89
|
render() {
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import { LitElement,
|
|
2
|
-
import { EFImage } from '../elements/EFImage.ts';
|
|
1
|
+
import { LitElement, PropertyValueMap, ReactiveController, TemplateResult, nothing } from 'lit';
|
|
3
2
|
import { EFAudio } from '../elements/EFAudio.ts';
|
|
4
|
-
import {
|
|
5
|
-
import { EFTimegroup } from '../elements/EFTimegroup.ts';
|
|
3
|
+
import { EFImage } from '../elements/EFImage.ts';
|
|
6
4
|
import { TemporalMixinInterface } from '../elements/EFTemporal.ts';
|
|
5
|
+
import { EFTimegroup } from '../elements/EFTimegroup.ts';
|
|
6
|
+
import { EFVideo } from '../elements/EFVideo.ts';
|
|
7
7
|
import { TimegroupController } from '../elements/TimegroupController.ts';
|
|
8
8
|
import { FocusContext } from './focusContext.ts';
|
|
9
9
|
declare class ElementFilmstripController implements ReactiveController {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0-beta.2",
|
|
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.
|
|
23
|
+
"@editframe/assets": "0.11.0-beta.2",
|
|
24
24
|
"@lit/context": "^1.1.2",
|
|
25
25
|
"@lit/task": "^1.0.1",
|
|
26
26
|
"d3": "^7.9.0",
|
|
@@ -1,9 +1,12 @@
|
|
|
1
|
-
import { describe, expect, test, beforeEach, afterEach } from "vitest";
|
|
2
|
-
import { v4 } from "uuid";
|
|
3
1
|
import { customElement } from "lit/decorators.js";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
4
4
|
import { EFMedia } from "./EFMedia.ts";
|
|
5
5
|
import "../gui/EFWorkbench.ts";
|
|
6
6
|
import "../gui/EFPreview.ts";
|
|
7
|
+
import { createTestFragmentIndex } from "TEST/createTestFragmentIndex.ts";
|
|
8
|
+
import { useMockWorker } from "TEST/useMockWorker.ts";
|
|
9
|
+
import { http, HttpResponse } from "msw";
|
|
7
10
|
|
|
8
11
|
@customElement("test-media")
|
|
9
12
|
class TestMedia extends EFMedia {}
|
|
@@ -15,6 +18,8 @@ declare global {
|
|
|
15
18
|
}
|
|
16
19
|
|
|
17
20
|
describe("EFMedia", () => {
|
|
21
|
+
const worker = useMockWorker();
|
|
22
|
+
|
|
18
23
|
test("should be defined", () => {
|
|
19
24
|
const element = document.createElement("test-media");
|
|
20
25
|
expect(element.tagName).toBe("TEST-MEDIA");
|
|
@@ -107,4 +112,228 @@ describe("EFMedia", () => {
|
|
|
107
112
|
);
|
|
108
113
|
});
|
|
109
114
|
});
|
|
115
|
+
|
|
116
|
+
describe("calculating duration", () => {
|
|
117
|
+
test("Computes duration from track fragment index", async () => {
|
|
118
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
119
|
+
worker.use(
|
|
120
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
121
|
+
return HttpResponse.json(
|
|
122
|
+
createTestFragmentIndex({
|
|
123
|
+
audio: { duration: 10_000 },
|
|
124
|
+
video: { duration: 10_000 },
|
|
125
|
+
}),
|
|
126
|
+
);
|
|
127
|
+
}),
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
const element = document.createElement("test-media");
|
|
131
|
+
element.src = "/assets/10s-bars.mp4";
|
|
132
|
+
|
|
133
|
+
const preview = document.createElement("ef-preview");
|
|
134
|
+
preview.appendChild(element);
|
|
135
|
+
document.body.appendChild(preview);
|
|
136
|
+
|
|
137
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
138
|
+
await Promise.resolve();
|
|
139
|
+
|
|
140
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
141
|
+
|
|
142
|
+
expect(element.durationMs).toBe(10_000);
|
|
143
|
+
});
|
|
144
|
+
test("Computes duration from track fragment index sourcein", async () => {
|
|
145
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
146
|
+
worker.use(
|
|
147
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
148
|
+
return HttpResponse.json(
|
|
149
|
+
createTestFragmentIndex({
|
|
150
|
+
audio: { duration: 10_000 },
|
|
151
|
+
video: { duration: 10_000 },
|
|
152
|
+
}),
|
|
153
|
+
);
|
|
154
|
+
}),
|
|
155
|
+
);
|
|
156
|
+
|
|
157
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
158
|
+
timegroup.mode = "sequence";
|
|
159
|
+
const element = document.createElement("test-media");
|
|
160
|
+
element.src = "/assets/10s-bars.mp4";
|
|
161
|
+
element.sourcein = "1s";
|
|
162
|
+
|
|
163
|
+
const preview = document.createElement("ef-preview");
|
|
164
|
+
timegroup.appendChild(element);
|
|
165
|
+
preview.appendChild(timegroup);
|
|
166
|
+
document.body.appendChild(preview);
|
|
167
|
+
|
|
168
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
169
|
+
await Promise.resolve();
|
|
170
|
+
|
|
171
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
172
|
+
|
|
173
|
+
expect(element.durationMs).toBe(9_000);
|
|
174
|
+
expect(timegroup.durationMs).toBe(9_000);
|
|
175
|
+
});
|
|
176
|
+
test("Computes duration from track fragment index sourcein", async () => {
|
|
177
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
178
|
+
worker.use(
|
|
179
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
180
|
+
return HttpResponse.json(
|
|
181
|
+
createTestFragmentIndex({
|
|
182
|
+
audio: { duration: 10_000 },
|
|
183
|
+
video: { duration: 10_000 },
|
|
184
|
+
}),
|
|
185
|
+
);
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
|
|
189
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
190
|
+
timegroup.mode = "sequence";
|
|
191
|
+
const element = document.createElement("test-media");
|
|
192
|
+
element.src = "/assets/10s-bars.mp4";
|
|
193
|
+
element.sourcein = "6s";
|
|
194
|
+
|
|
195
|
+
const preview = document.createElement("ef-preview");
|
|
196
|
+
timegroup.appendChild(element);
|
|
197
|
+
preview.appendChild(timegroup);
|
|
198
|
+
document.body.appendChild(preview);
|
|
199
|
+
|
|
200
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
201
|
+
await Promise.resolve();
|
|
202
|
+
|
|
203
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
204
|
+
|
|
205
|
+
expect(element.durationMs).toBe(4_000);
|
|
206
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
207
|
+
});
|
|
208
|
+
test("Computes duration from track fragment index sourceout", async () => {
|
|
209
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
210
|
+
worker.use(
|
|
211
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
212
|
+
return HttpResponse.json(
|
|
213
|
+
createTestFragmentIndex({
|
|
214
|
+
audio: { duration: 10_000 },
|
|
215
|
+
video: { duration: 10_000 },
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
}),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
222
|
+
timegroup.mode = "sequence";
|
|
223
|
+
const element = document.createElement("test-media");
|
|
224
|
+
element.src = "/assets/10s-bars.mp4";
|
|
225
|
+
element.sourceout = "6s";
|
|
226
|
+
|
|
227
|
+
const preview = document.createElement("ef-preview");
|
|
228
|
+
timegroup.appendChild(element);
|
|
229
|
+
preview.appendChild(timegroup);
|
|
230
|
+
document.body.appendChild(preview);
|
|
231
|
+
|
|
232
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
233
|
+
await Promise.resolve();
|
|
234
|
+
|
|
235
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
236
|
+
|
|
237
|
+
expect(element.durationMs).toBe(4_000);
|
|
238
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
239
|
+
});
|
|
240
|
+
test("Computes duration from track fragment index sourceout", async () => {
|
|
241
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
242
|
+
worker.use(
|
|
243
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
244
|
+
return HttpResponse.json(
|
|
245
|
+
createTestFragmentIndex({
|
|
246
|
+
audio: { duration: 10_000 },
|
|
247
|
+
video: { duration: 10_000 },
|
|
248
|
+
}),
|
|
249
|
+
);
|
|
250
|
+
}),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
254
|
+
timegroup.mode = "sequence";
|
|
255
|
+
const element = document.createElement("test-media");
|
|
256
|
+
element.src = "/assets/10s-bars.mp4";
|
|
257
|
+
element.sourceout = "5s";
|
|
258
|
+
|
|
259
|
+
const preview = document.createElement("ef-preview");
|
|
260
|
+
timegroup.appendChild(element);
|
|
261
|
+
preview.appendChild(timegroup);
|
|
262
|
+
document.body.appendChild(preview);
|
|
263
|
+
|
|
264
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
265
|
+
await Promise.resolve();
|
|
266
|
+
|
|
267
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
268
|
+
|
|
269
|
+
expect(element.durationMs).toBe(5_000);
|
|
270
|
+
expect(timegroup.durationMs).toBe(5_000);
|
|
271
|
+
});
|
|
272
|
+
test("Computes duration from track fragment index sourceout and sourcein", async () => {
|
|
273
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
274
|
+
worker.use(
|
|
275
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
276
|
+
return HttpResponse.json(
|
|
277
|
+
createTestFragmentIndex({
|
|
278
|
+
audio: { duration: 10_000 },
|
|
279
|
+
video: { duration: 10_000 },
|
|
280
|
+
}),
|
|
281
|
+
);
|
|
282
|
+
}),
|
|
283
|
+
);
|
|
284
|
+
|
|
285
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
286
|
+
timegroup.mode = "sequence";
|
|
287
|
+
const element = document.createElement("test-media");
|
|
288
|
+
element.src = "/assets/10s-bars.mp4";
|
|
289
|
+
element.sourcein = "1s";
|
|
290
|
+
element.sourceout = "5s";
|
|
291
|
+
|
|
292
|
+
const preview = document.createElement("ef-preview");
|
|
293
|
+
timegroup.appendChild(element);
|
|
294
|
+
preview.appendChild(timegroup);
|
|
295
|
+
document.body.appendChild(preview);
|
|
296
|
+
|
|
297
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
298
|
+
await Promise.resolve();
|
|
299
|
+
|
|
300
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
301
|
+
|
|
302
|
+
expect(element.durationMs).toBe(4_000);
|
|
303
|
+
expect(timegroup.durationMs).toBe(4_000);
|
|
304
|
+
});
|
|
305
|
+
test("Computes duration from track fragment index sourceout and sourcein", async () => {
|
|
306
|
+
// Mock the request for the track fragment index, responds with a 10 second duration
|
|
307
|
+
worker.use(
|
|
308
|
+
http.get("/@ef-track-fragment-index//assets/10s-bars.mp4", () => {
|
|
309
|
+
return HttpResponse.json(
|
|
310
|
+
createTestFragmentIndex({
|
|
311
|
+
audio: { duration: 10_000 },
|
|
312
|
+
video: { duration: 10_000 },
|
|
313
|
+
}),
|
|
314
|
+
);
|
|
315
|
+
}),
|
|
316
|
+
);
|
|
317
|
+
|
|
318
|
+
const timegroup = document.createElement("ef-timegroup");
|
|
319
|
+
timegroup.mode = "sequence";
|
|
320
|
+
const element = document.createElement("test-media");
|
|
321
|
+
element.src = "/assets/10s-bars.mp4";
|
|
322
|
+
element.sourcein = "9s";
|
|
323
|
+
element.sourceout = "10s";
|
|
324
|
+
|
|
325
|
+
const preview = document.createElement("ef-preview");
|
|
326
|
+
timegroup.appendChild(element);
|
|
327
|
+
preview.appendChild(timegroup);
|
|
328
|
+
document.body.appendChild(preview);
|
|
329
|
+
|
|
330
|
+
// Await the next tick to ensure the element has a chance to load the track fragment
|
|
331
|
+
await Promise.resolve();
|
|
332
|
+
|
|
333
|
+
await element.trackFragmentIndexLoader.taskComplete;
|
|
334
|
+
|
|
335
|
+
expect(element.durationMs).toBe(1_000);
|
|
336
|
+
expect(timegroup.durationMs).toBe(1_000);
|
|
337
|
+
});
|
|
338
|
+
});
|
|
110
339
|
});
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -1,22 +1,22 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
2
|
+
import { Task } from "@lit/task";
|
|
3
|
+
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
+
import debug from "debug";
|
|
1
5
|
import { LitElement, type PropertyValueMap, css } from "lit";
|
|
2
6
|
import { property, state } from "lit/decorators.js";
|
|
3
|
-
import { deepArrayEquals } from "@lit/task/deep-equals.js";
|
|
4
|
-
import { Task } from "@lit/task";
|
|
5
7
|
import type * as MP4Box from "mp4box";
|
|
6
|
-
import { consume } from "@lit/context";
|
|
7
|
-
import debug from "debug";
|
|
8
8
|
|
|
9
9
|
import type { TrackFragmentIndex, TrackSegment } from "@editframe/assets";
|
|
10
10
|
|
|
11
|
-
import { MP4File } from "@editframe/assets/MP4File.js";
|
|
12
11
|
import { VideoAsset } from "@editframe/assets/EncodedAsset.js";
|
|
12
|
+
import { MP4File } from "@editframe/assets/MP4File.js";
|
|
13
|
+
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
14
|
+
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
15
|
+
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
16
|
+
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
13
17
|
import { EFTemporal } from "./EFTemporal.ts";
|
|
14
18
|
import { FetchMixin } from "./FetchMixin.ts";
|
|
15
|
-
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
16
19
|
import { getStartTimeMs } from "./util.ts";
|
|
17
|
-
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
18
|
-
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
19
|
-
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
20
20
|
|
|
21
21
|
const log = debug("ef:elements:EFMedia");
|
|
22
22
|
|
|
@@ -321,6 +321,38 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
321
321
|
if (durations.length === 0) {
|
|
322
322
|
return 0;
|
|
323
323
|
}
|
|
324
|
+
if (
|
|
325
|
+
this.sourceInMs &&
|
|
326
|
+
this.sourceOutMs &&
|
|
327
|
+
this.sourceOutMs > this.sourceInMs
|
|
328
|
+
) {
|
|
329
|
+
return Math.max(this.sourceOutMs - this.sourceInMs);
|
|
330
|
+
}
|
|
331
|
+
if (this.sourceInMs) {
|
|
332
|
+
return (
|
|
333
|
+
Math.max(...durations) -
|
|
334
|
+
this.trimStartMs -
|
|
335
|
+
this.trimEndMs -
|
|
336
|
+
this.sourceInMs
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
if (this.sourceOutMs) {
|
|
340
|
+
return (
|
|
341
|
+
Math.max(...durations) -
|
|
342
|
+
this.trimStartMs -
|
|
343
|
+
this.trimEndMs -
|
|
344
|
+
this.sourceOutMs
|
|
345
|
+
);
|
|
346
|
+
}
|
|
347
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
348
|
+
return (
|
|
349
|
+
Math.max(...durations) -
|
|
350
|
+
this.trimStartMs -
|
|
351
|
+
this.trimEndMs -
|
|
352
|
+
this.sourceOutMs -
|
|
353
|
+
this.sourceInMs
|
|
354
|
+
);
|
|
355
|
+
}
|
|
324
356
|
return Math.max(...durations) - this.trimStartMs - this.trimEndMs;
|
|
325
357
|
}
|
|
326
358
|
|
|
@@ -362,8 +394,8 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
362
394
|
|
|
363
395
|
async fetchAudioSpanningTime(fromMs: number, toMs: number) {
|
|
364
396
|
// Adjust range for track's own time
|
|
365
|
-
fromMs -= this.startTimeMs - this.trimStartMs;
|
|
366
|
-
toMs -= this.startTimeMs - this.trimStartMs;
|
|
397
|
+
fromMs -= this.startTimeMs - this.trimStartMs - this.sourceInMs;
|
|
398
|
+
toMs -= this.startTimeMs - this.trimStartMs - this.sourceOutMs;
|
|
367
399
|
|
|
368
400
|
await this.trackFragmentIndexLoader.taskComplete;
|
|
369
401
|
const audioTrackId = this.defaultAudioTrackId;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { LitElement } from "lit";
|
|
2
|
+
import { customElement } from "lit/decorators/custom-element.js";
|
|
3
|
+
import { describe, expect, test } from "vitest";
|
|
4
|
+
import { EFTemporal } from "./EFTemporal.ts";
|
|
5
|
+
|
|
6
|
+
@customElement("test-temporal")
|
|
7
|
+
class TestTemporal extends EFTemporal(LitElement) {}
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
interface HTMLElementTagNameMap {
|
|
11
|
+
"test-temporal": TestTemporal;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
describe("sourcein and sourceout", () => {
|
|
16
|
+
test("sourcein and sourceout are parsed correctly", () => {
|
|
17
|
+
const element = document.createElement("test-temporal");
|
|
18
|
+
element.setAttribute("sourcein", "1s");
|
|
19
|
+
element.setAttribute("sourceout", "5s");
|
|
20
|
+
expect(element.sourceInMs).toBe(1_000);
|
|
21
|
+
expect(element.sourceOutMs).toBe(5_000);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
test("sourcein and sourceout can be set directly on the element", () => {
|
|
25
|
+
const element = document.createElement("test-temporal");
|
|
26
|
+
element.sourcein = "1s";
|
|
27
|
+
element.sourceout = "5s";
|
|
28
|
+
expect(element.sourceInMs).toBe(1_000);
|
|
29
|
+
expect(element.sourceOutMs).toBe(5_000);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("sourcein and sourceout are reflected correctly", () => {
|
|
33
|
+
const element = document.createElement("test-temporal");
|
|
34
|
+
element.sourceInMs = 1_000;
|
|
35
|
+
element.sourceOutMs = 5_000;
|
|
36
|
+
expect(element.getAttribute("sourcein")).toBe("1s");
|
|
37
|
+
expect(element.getAttribute("sourceout")).toBe("5s");
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
describe("trimstart and trimend", () => {
|
|
42
|
+
test("trimstart and trimend attributes are parsed correctly", () => {
|
|
43
|
+
const element = document.createElement("test-temporal");
|
|
44
|
+
element.setAttribute("trimstart", "1s");
|
|
45
|
+
element.setAttribute("trimend", "5s");
|
|
46
|
+
expect(element.trimStartMs).toBe(1_000);
|
|
47
|
+
expect(element.trimEndMs).toBe(5_000);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("trimstart and trimend properties are reflected correctly", () => {
|
|
51
|
+
const element = document.createElement("test-temporal");
|
|
52
|
+
element.trimStartMs = 1_000;
|
|
53
|
+
element.trimEndMs = 5_000;
|
|
54
|
+
expect(element.getAttribute("trimstart")).toBe("1s");
|
|
55
|
+
expect(element.getAttribute("trimend")).toBe("5s");
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
test("trimstart and trimend can be set directly on the element", () => {
|
|
59
|
+
const element = document.createElement("test-temporal");
|
|
60
|
+
element.trimstart = "1s";
|
|
61
|
+
element.trimend = "5s";
|
|
62
|
+
expect(element.trimStartMs).toBe(1_000);
|
|
63
|
+
expect(element.trimEndMs).toBe(5_000);
|
|
64
|
+
});
|
|
65
|
+
});
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import type { LitElement, ReactiveController } from "lit";
|
|
2
1
|
import { consume, createContext } from "@lit/context";
|
|
2
|
+
import type { LitElement, ReactiveController } from "lit";
|
|
3
3
|
import { property, state } from "lit/decorators.js";
|
|
4
4
|
import type { EFTimegroup } from "./EFTimegroup.ts";
|
|
5
5
|
|
|
6
|
-
import { durationConverter } from "./durationConverter.ts";
|
|
7
6
|
import { Task } from "@lit/task";
|
|
8
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
8
|
+
import { durationConverter } from "./durationConverter.ts";
|
|
9
9
|
|
|
10
10
|
export const timegroupContext = createContext<EFTimegroup>(
|
|
11
11
|
Symbol("timeGroupContext"),
|
|
@@ -15,6 +15,19 @@ export declare class TemporalMixinInterface {
|
|
|
15
15
|
get hasOwnDuration(): boolean;
|
|
16
16
|
get trimStartMs(): number;
|
|
17
17
|
get trimEndMs(): number;
|
|
18
|
+
set trimStartMs(value: number);
|
|
19
|
+
set trimEndMs(value: number);
|
|
20
|
+
set trimstart(value: string);
|
|
21
|
+
set trimend(value: string);
|
|
22
|
+
|
|
23
|
+
get sourceInMs(): number;
|
|
24
|
+
get sourceOutMs(): number;
|
|
25
|
+
|
|
26
|
+
set sourceInMs(value: number);
|
|
27
|
+
set sourceOutMs(value: number);
|
|
28
|
+
set sourcein(value: string);
|
|
29
|
+
set sourceout(value: string);
|
|
30
|
+
|
|
18
31
|
get durationMs(): number;
|
|
19
32
|
get startTimeMs(): number;
|
|
20
33
|
get startTimeWithinParentMs(): number;
|
|
@@ -24,8 +37,6 @@ export declare class TemporalMixinInterface {
|
|
|
24
37
|
|
|
25
38
|
set duration(value: string);
|
|
26
39
|
get duration(): string;
|
|
27
|
-
set trimstart(value: string);
|
|
28
|
-
set trimend(value: string);
|
|
29
40
|
|
|
30
41
|
parentTimegroup?: EFTimegroup;
|
|
31
42
|
rootTimegroup?: EFTimegroup;
|
|
@@ -158,25 +169,104 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
158
169
|
})
|
|
159
170
|
private _durationMs?: number;
|
|
160
171
|
|
|
172
|
+
private _trimStartMs = 0;
|
|
161
173
|
@property({
|
|
162
174
|
type: Number,
|
|
163
175
|
attribute: "trimstart",
|
|
164
176
|
converter: durationConverter,
|
|
165
177
|
})
|
|
166
|
-
private _trimStartMs = 0;
|
|
167
178
|
public get trimStartMs(): number {
|
|
168
179
|
return this._trimStartMs;
|
|
169
180
|
}
|
|
181
|
+
public set trimStartMs(value: number) {
|
|
182
|
+
this._trimStartMs = value;
|
|
183
|
+
this.setAttribute(
|
|
184
|
+
"trimstart",
|
|
185
|
+
durationConverter.toAttribute(value / 1000),
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
set trimstart(value: string | undefined) {
|
|
189
|
+
if (value !== undefined) {
|
|
190
|
+
this.setAttribute("trimstart", value);
|
|
191
|
+
} else {
|
|
192
|
+
this.removeAttribute("trimstart");
|
|
193
|
+
}
|
|
194
|
+
}
|
|
170
195
|
|
|
196
|
+
private _trimEndMs = 0;
|
|
171
197
|
@property({
|
|
172
198
|
type: Number,
|
|
173
199
|
attribute: "trimend",
|
|
174
200
|
converter: durationConverter,
|
|
175
201
|
})
|
|
176
|
-
private _trimEndMs = 0;
|
|
177
202
|
public get trimEndMs(): number {
|
|
178
203
|
return this._trimEndMs;
|
|
179
204
|
}
|
|
205
|
+
public set trimEndMs(value: number) {
|
|
206
|
+
this._trimEndMs = value;
|
|
207
|
+
this.setAttribute("trimend", durationConverter.toAttribute(value / 1000));
|
|
208
|
+
}
|
|
209
|
+
set trimend(value: string | undefined) {
|
|
210
|
+
if (value !== undefined) {
|
|
211
|
+
this.setAttribute("trimend", value);
|
|
212
|
+
} else {
|
|
213
|
+
this.removeAttribute("trimend");
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
private _sourceInMs: number | undefined;
|
|
218
|
+
@property({
|
|
219
|
+
type: Number,
|
|
220
|
+
attribute: "sourcein",
|
|
221
|
+
converter: durationConverter,
|
|
222
|
+
reflect: true,
|
|
223
|
+
})
|
|
224
|
+
get sourceInMs(): number | undefined {
|
|
225
|
+
return this._sourceInMs;
|
|
226
|
+
}
|
|
227
|
+
set sourceInMs(value: number | undefined) {
|
|
228
|
+
this._sourceInMs = value;
|
|
229
|
+
value !== undefined
|
|
230
|
+
? this.setAttribute(
|
|
231
|
+
"sourcein",
|
|
232
|
+
durationConverter.toAttribute(value / 1000),
|
|
233
|
+
)
|
|
234
|
+
: this.removeAttribute("sourcein");
|
|
235
|
+
}
|
|
236
|
+
set sourcein(value: string | undefined) {
|
|
237
|
+
if (value !== undefined) {
|
|
238
|
+
this.setAttribute("sourcein", value);
|
|
239
|
+
} else {
|
|
240
|
+
this.removeAttribute("sourcein");
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
private _sourceOutMs: number | undefined;
|
|
245
|
+
@property({
|
|
246
|
+
type: Number,
|
|
247
|
+
attribute: "sourceout",
|
|
248
|
+
converter: durationConverter,
|
|
249
|
+
reflect: true,
|
|
250
|
+
})
|
|
251
|
+
get sourceOutMs(): number | undefined {
|
|
252
|
+
return this._sourceOutMs;
|
|
253
|
+
}
|
|
254
|
+
set sourceOutMs(value: number | undefined) {
|
|
255
|
+
this._sourceOutMs = value;
|
|
256
|
+
value !== undefined
|
|
257
|
+
? this.setAttribute(
|
|
258
|
+
"sourceout",
|
|
259
|
+
durationConverter.toAttribute(value / 1000),
|
|
260
|
+
)
|
|
261
|
+
: this.removeAttribute("sourceout");
|
|
262
|
+
}
|
|
263
|
+
set sourceout(value: string | undefined) {
|
|
264
|
+
if (value !== undefined) {
|
|
265
|
+
this.setAttribute("sourceout", value);
|
|
266
|
+
} else {
|
|
267
|
+
this.removeAttribute("sourceout");
|
|
268
|
+
}
|
|
269
|
+
}
|
|
180
270
|
|
|
181
271
|
@property({
|
|
182
272
|
type: Number,
|
|
@@ -207,6 +297,20 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
207
297
|
// Defining this as a getter to a private property allows us to
|
|
208
298
|
// override it classes that include this mixin.
|
|
209
299
|
get durationMs() {
|
|
300
|
+
if (this.sourceInMs) {
|
|
301
|
+
return (
|
|
302
|
+
this._durationMs ||
|
|
303
|
+
this.parentTimegroup?.durationMs ||
|
|
304
|
+
0 - this.sourceInMs
|
|
305
|
+
);
|
|
306
|
+
}
|
|
307
|
+
if (this.sourceOutMs) {
|
|
308
|
+
return (
|
|
309
|
+
this._durationMs ||
|
|
310
|
+
this.parentTimegroup?.durationMs ||
|
|
311
|
+
0 - this.sourceOutMs
|
|
312
|
+
);
|
|
313
|
+
}
|
|
210
314
|
return this._durationMs || this.parentTimegroup?.durationMs || 0;
|
|
211
315
|
}
|
|
212
316
|
|
|
@@ -297,6 +401,21 @@ export const EFTemporal = <T extends Constructor<LitElement>>(
|
|
|
297
401
|
*/
|
|
298
402
|
get trimAdjustedOwnCurrentTimeMs() {
|
|
299
403
|
if (this.rootTimegroup) {
|
|
404
|
+
if (this.sourceInMs && this.sourceOutMs) {
|
|
405
|
+
return Math.min(
|
|
406
|
+
Math.max(
|
|
407
|
+
0,
|
|
408
|
+
this.rootTimegroup.currentTimeMs -
|
|
409
|
+
this.startTimeMs +
|
|
410
|
+
this.trimStartMs +
|
|
411
|
+
this.sourceInMs,
|
|
412
|
+
),
|
|
413
|
+
this.durationMs +
|
|
414
|
+
Math.abs(this.startOffsetMs) +
|
|
415
|
+
this.trimStartMs +
|
|
416
|
+
this.sourceInMs,
|
|
417
|
+
);
|
|
418
|
+
}
|
|
300
419
|
return Math.min(
|
|
301
420
|
Math.max(
|
|
302
421
|
0,
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import { describe, test, assert, beforeEach } from "vitest";
|
|
2
1
|
import {
|
|
3
2
|
LitElement,
|
|
4
3
|
type TemplateResult,
|
|
5
4
|
html,
|
|
6
5
|
render as litRender,
|
|
7
6
|
} from "lit";
|
|
7
|
+
import { assert, beforeEach, describe, test } from "vitest";
|
|
8
8
|
import { EFTimegroup } from "./EFTimegroup.ts";
|
|
9
9
|
import "./EFTimegroup.ts";
|
|
10
10
|
import { customElement } from "lit/decorators/custom-element.js";
|
|
@@ -24,3 +24,7 @@ export const trimDurationConverter = positiveDurationConverter(
|
|
|
24
24
|
export const imageDurationConverter = positiveDurationConverter(
|
|
25
25
|
"Image duration must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
26
26
|
);
|
|
27
|
+
|
|
28
|
+
export const sourceDurationConverter = positiveDurationConverter(
|
|
29
|
+
"Sourcein & sourceout must be a positive value in milliseconds or seconds (1s, 1000ms)",
|
|
30
|
+
);
|
package/src/gui/EFFilmstrip.ts
CHANGED
|
@@ -1,37 +1,37 @@
|
|
|
1
|
+
import { consume } from "@lit/context";
|
|
1
2
|
import {
|
|
2
3
|
LitElement,
|
|
3
|
-
|
|
4
|
+
type PropertyValueMap,
|
|
5
|
+
type ReactiveController,
|
|
6
|
+
type TemplateResult,
|
|
4
7
|
css,
|
|
8
|
+
html,
|
|
5
9
|
nothing,
|
|
6
|
-
type TemplateResult,
|
|
7
|
-
type ReactiveController,
|
|
8
|
-
type PropertyValueMap,
|
|
9
10
|
} from "lit";
|
|
10
11
|
import {
|
|
11
12
|
customElement,
|
|
12
|
-
property,
|
|
13
13
|
eventOptions,
|
|
14
|
+
property,
|
|
14
15
|
state,
|
|
15
16
|
} from "lit/decorators.js";
|
|
16
|
-
import {
|
|
17
|
+
import { createRef, ref } from "lit/directives/ref.js";
|
|
17
18
|
import { styleMap } from "lit/directives/style-map.js";
|
|
18
|
-
import { ref, createRef } from "lit/directives/ref.js";
|
|
19
19
|
|
|
20
|
-
import { EFImage } from "../elements/EFImage.ts";
|
|
21
20
|
import { EFAudio } from "../elements/EFAudio.ts";
|
|
22
|
-
import { EFVideo } from "../elements/EFVideo.ts";
|
|
23
21
|
import { EFCaptions, EFCaptionsActiveWord } from "../elements/EFCaptions.ts";
|
|
24
|
-
import {
|
|
25
|
-
import { EFTimegroup } from "../elements/EFTimegroup.ts";
|
|
22
|
+
import { EFImage } from "../elements/EFImage.ts";
|
|
26
23
|
import type { TemporalMixinInterface } from "../elements/EFTemporal.ts";
|
|
24
|
+
import { EFTimegroup } from "../elements/EFTimegroup.ts";
|
|
25
|
+
import { EFVideo } from "../elements/EFVideo.ts";
|
|
26
|
+
import { EFWaveform } from "../elements/EFWaveform.ts";
|
|
27
27
|
import { TimegroupController } from "../elements/TimegroupController.ts";
|
|
28
|
-
import { TWMixin } from "./TWMixin.ts";
|
|
29
28
|
import { msToTimeCode } from "../msToTimeCode.ts";
|
|
30
|
-
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
31
|
-
import { type FocusContext, focusContext } from "./focusContext.ts";
|
|
32
|
-
import { playingContext, loopContext } from "./playingContext.ts";
|
|
33
|
-
import type { EFWorkbench } from "./EFWorkbench.ts";
|
|
34
29
|
import type { EFPreview } from "./EFPreview.ts";
|
|
30
|
+
import type { EFWorkbench } from "./EFWorkbench.ts";
|
|
31
|
+
import { TWMixin } from "./TWMixin.ts";
|
|
32
|
+
import { type FocusContext, focusContext } from "./focusContext.ts";
|
|
33
|
+
import { focusedElementContext } from "./focusedElementContext.ts";
|
|
34
|
+
import { loopContext, playingContext } from "./playingContext.ts";
|
|
35
35
|
|
|
36
36
|
class ElementFilmstripController implements ReactiveController {
|
|
37
37
|
constructor(
|
|
@@ -89,15 +89,15 @@ class FilmstripItem extends TWMixin(LitElement) {
|
|
|
89
89
|
get gutterStyles() {
|
|
90
90
|
return {
|
|
91
91
|
position: "relative",
|
|
92
|
-
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs)}px`,
|
|
93
|
-
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs)}px`,
|
|
92
|
+
left: `${this.pixelsPerMs * (this.element.startTimeWithinParentMs - this.element.trimStartMs - this.element.sourceInMs)}px`,
|
|
93
|
+
width: `${this.pixelsPerMs * (this.element.durationMs + this.element.trimStartMs + this.element.trimEndMs + this.element.sourceOutMs + this.element.sourceInMs)}px`,
|
|
94
94
|
};
|
|
95
95
|
}
|
|
96
96
|
|
|
97
97
|
get trimPortionStyles() {
|
|
98
98
|
return {
|
|
99
99
|
width: `${this.pixelsPerMs * this.element.durationMs}px`,
|
|
100
|
-
left: `${this.pixelsPerMs * this.element.trimStartMs}px`,
|
|
100
|
+
left: `${this.pixelsPerMs * (this.element.trimStartMs + this.element.sourceInMs)}px`,
|
|
101
101
|
};
|
|
102
102
|
}
|
|
103
103
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|