@editframe/elements 0.8.0-beta.9 → 0.9.0-beta.3
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_RENDERING.d.ts +1 -0
- package/dist/elements/EFAudio.d.ts +5 -0
- package/dist/elements/EFCaptions.browsertest.d.ts +0 -0
- package/dist/elements/EFImage.browsertest.d.ts +0 -0
- package/dist/elements/EFImage.d.ts +8 -0
- package/dist/elements/EFMedia.browsertest.d.ts +9 -0
- package/dist/elements/EFMedia.d.ts +3 -1
- package/dist/elements/EFTemporal.d.ts +2 -0
- package/dist/elements/EFTimegroup.d.ts +1 -1
- package/dist/elements/EFVideo.d.ts +5 -0
- package/dist/elements/src/EF_RENDERING.js +4 -0
- package/dist/elements/src/elements/EFCaptions.js +7 -3
- package/dist/elements/src/elements/EFImage.js +32 -3
- package/dist/elements/src/elements/EFMedia.js +34 -13
- package/dist/elements/src/elements/parseTimeToMs.js +0 -1
- package/package.json +2 -2
- package/src/elements/EFAudio.ts +6 -0
- package/src/elements/EFCaptions.browsertest.ts +50 -0
- package/src/elements/EFCaptions.ts +7 -3
- package/src/elements/EFImage.browsertest.ts +48 -0
- package/src/elements/EFImage.ts +29 -3
- package/src/elements/EFMedia.browsertest.ts +83 -0
- package/src/elements/EFMedia.ts +29 -7
- package/src/elements/EFTemporal.ts +2 -0
- package/src/elements/EFVideo.ts +6 -0
- package/src/elements/parseTimeToMs.ts +0 -1
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const EF_RENDERING: () => boolean;
|
|
@@ -7,3 +7,8 @@ export declare class EFAudio extends EFMedia {
|
|
|
7
7
|
get audioElement(): HTMLAudioElement | undefined;
|
|
8
8
|
frameTask: Task<readonly [import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus, import('@lit/task').TaskStatus], void>;
|
|
9
9
|
}
|
|
10
|
+
declare global {
|
|
11
|
+
interface HTMLElementTagNameMap {
|
|
12
|
+
"ef-audio": EFAudio;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
File without changes
|
|
File without changes
|
|
@@ -2,12 +2,20 @@ import { Task } from '@lit/task';
|
|
|
2
2
|
import { LitElement } from 'lit';
|
|
3
3
|
declare const EFImage_base: (new (...args: any[]) => import('./EFSourceMixin.ts').EFSourceMixinInterface) & (new (...args: any[]) => import('./FetchMixin.ts').FetchMixinInterface) & typeof LitElement;
|
|
4
4
|
export declare class EFImage extends EFImage_base {
|
|
5
|
+
#private;
|
|
5
6
|
static styles: import('lit').CSSResult[];
|
|
6
7
|
imageRef: import('lit-html/directives/ref.js').Ref<HTMLImageElement>;
|
|
7
8
|
canvasRef: import('lit-html/directives/ref.js').Ref<HTMLCanvasElement>;
|
|
9
|
+
set assetId(value: string | null);
|
|
10
|
+
get assetId(): string | null;
|
|
8
11
|
render(): import('lit-html').TemplateResult<1>;
|
|
9
12
|
assetPath(): string;
|
|
10
13
|
fetchImage: Task<readonly [string, typeof fetch], void>;
|
|
11
14
|
frameTask: Task<readonly [import('@lit/task').TaskStatus], void>;
|
|
12
15
|
}
|
|
16
|
+
declare global {
|
|
17
|
+
interface HTMLElementTagNameMap {
|
|
18
|
+
"ef-image": EFImage;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
13
21
|
export {};
|
|
@@ -11,9 +11,11 @@ export declare class EFMedia extends EFMedia_base {
|
|
|
11
11
|
static styles: import('lit').CSSResult[];
|
|
12
12
|
currentTimeMs: number;
|
|
13
13
|
efHost?: string;
|
|
14
|
+
set assetId(value: string | null);
|
|
15
|
+
get assetId(): string | null;
|
|
14
16
|
fragmentIndexPath(): string;
|
|
15
17
|
fragmentTrackPath(trackId: string): string;
|
|
16
|
-
trackFragmentIndexLoader: Task<readonly [string, typeof fetch], Record<number, TrackFragmentIndex
|
|
18
|
+
trackFragmentIndexLoader: Task<readonly [string, typeof fetch], Record<number, TrackFragmentIndex>>;
|
|
17
19
|
protected initSegmentsLoader: Task<readonly [Record<number, TrackFragmentIndex> | undefined, string, typeof fetch], {
|
|
18
20
|
trackId: string;
|
|
19
21
|
buffer: MP4Box.MP4ArrayBuffer;
|
|
@@ -16,6 +16,8 @@ export declare class TemporalMixinInterface {
|
|
|
16
16
|
get trimAdjustedOwnCurrentTimeMs(): number;
|
|
17
17
|
set duration(value: string);
|
|
18
18
|
get duration(): string;
|
|
19
|
+
set trimstart(value: string);
|
|
20
|
+
set trimend(value: string);
|
|
19
21
|
parentTimegroup?: EFTimegroup;
|
|
20
22
|
rootTimegroup?: EFTimegroup;
|
|
21
23
|
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<
|
|
20
|
+
waitForMediaDurations(): Promise<Record<number, import('packages/assets/src/Probe.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;
|
|
@@ -10,4 +10,9 @@ export declare class EFVideo extends EFVideo_base {
|
|
|
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
11
|
paintTask: Task<readonly [import('packages/assets/dist/EncodedAsset.js').VideoAsset | undefined, number], number | undefined>;
|
|
12
12
|
}
|
|
13
|
+
declare global {
|
|
14
|
+
interface HTMLElementTagNameMap {
|
|
15
|
+
"ef-video": EFVideo;
|
|
16
|
+
}
|
|
17
|
+
}
|
|
13
18
|
export {};
|
|
@@ -8,6 +8,7 @@ import { CrossUpdateController } from "./CrossUpdateController.js";
|
|
|
8
8
|
import { FetchMixin } from "./FetchMixin.js";
|
|
9
9
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
10
10
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
11
|
+
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
11
12
|
var __defProp = Object.defineProperty;
|
|
12
13
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
13
14
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
@@ -92,10 +93,13 @@ let EFCaptions = class extends EFSourceMixin(
|
|
|
92
93
|
this.targetSelector = value;
|
|
93
94
|
}
|
|
94
95
|
captionsPath() {
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
if (this.targetElement.assetId) {
|
|
97
|
+
if (EF_RENDERING()) {
|
|
98
|
+
return `editframe://api/video2/caption_files/${this.targetElement.assetId}`;
|
|
99
|
+
}
|
|
100
|
+
return `https://editframe.dev/api/video2/caption_files/${this.targetElement.assetId}`;
|
|
98
101
|
}
|
|
102
|
+
const targetSrc = this.targetElement.src;
|
|
99
103
|
return `/@ef-captions/${targetSrc}`;
|
|
100
104
|
}
|
|
101
105
|
connectedCallback() {
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
2
|
import { html, css, LitElement } from "lit";
|
|
3
|
-
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { property, customElement } from "lit/decorators.js";
|
|
4
4
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
5
|
import { FetchMixin } from "./FetchMixin.js";
|
|
6
6
|
import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
7
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
8
|
+
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
8
9
|
var __defProp = Object.defineProperty;
|
|
9
10
|
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
11
|
+
var __typeError = (msg) => {
|
|
12
|
+
throw TypeError(msg);
|
|
13
|
+
};
|
|
10
14
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
11
15
|
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
12
16
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
@@ -15,6 +19,11 @@ var __decorateClass = (decorators, target, key, kind) => {
|
|
|
15
19
|
if (kind && result) __defProp(target, key, result);
|
|
16
20
|
return result;
|
|
17
21
|
};
|
|
22
|
+
var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot " + msg);
|
|
23
|
+
var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
|
|
24
|
+
var __privateAdd = (obj, member, value) => member.has(obj) ? __typeError("Cannot add the same private member more than once") : member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
|
|
25
|
+
var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
|
|
26
|
+
var _assetId;
|
|
18
27
|
let EFImage = class extends EFSourceMixin(FetchMixin(LitElement), {
|
|
19
28
|
assetType: "image_files"
|
|
20
29
|
}) {
|
|
@@ -22,6 +31,7 @@ let EFImage = class extends EFSourceMixin(FetchMixin(LitElement), {
|
|
|
22
31
|
super(...arguments);
|
|
23
32
|
this.imageRef = createRef();
|
|
24
33
|
this.canvasRef = createRef();
|
|
34
|
+
__privateAdd(this, _assetId, null);
|
|
25
35
|
this.fetchImage = new Task(this, {
|
|
26
36
|
autoRun: EF_INTERACTIVE,
|
|
27
37
|
args: () => [this.assetPath(), this.fetch],
|
|
@@ -48,16 +58,32 @@ let EFImage = class extends EFSourceMixin(FetchMixin(LitElement), {
|
|
|
48
58
|
}
|
|
49
59
|
});
|
|
50
60
|
}
|
|
61
|
+
set assetId(value) {
|
|
62
|
+
if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
|
|
63
|
+
console.error(`EFMedia ${value} is not valid asset-id`);
|
|
64
|
+
throw new Error(
|
|
65
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)"
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
__privateSet(this, _assetId, value);
|
|
69
|
+
}
|
|
70
|
+
get assetId() {
|
|
71
|
+
return __privateGet(this, _assetId) ?? this.getAttribute("asset-id");
|
|
72
|
+
}
|
|
51
73
|
render() {
|
|
52
74
|
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
53
75
|
}
|
|
54
76
|
assetPath() {
|
|
55
|
-
if (this.
|
|
56
|
-
|
|
77
|
+
if (this.assetId) {
|
|
78
|
+
if (EF_RENDERING()) {
|
|
79
|
+
return `editframe://api/video2/image_files/${this.assetId}`;
|
|
80
|
+
}
|
|
81
|
+
return `https://editframe.dev/api/video2/image_files/${this.assetId}`;
|
|
57
82
|
}
|
|
58
83
|
return `/@ef-image/${this.src}`;
|
|
59
84
|
}
|
|
60
85
|
};
|
|
86
|
+
_assetId = /* @__PURE__ */ new WeakMap();
|
|
61
87
|
EFImage.styles = [
|
|
62
88
|
css`
|
|
63
89
|
:host {
|
|
@@ -72,6 +98,9 @@ EFImage.styles = [
|
|
|
72
98
|
}
|
|
73
99
|
`
|
|
74
100
|
];
|
|
101
|
+
__decorateClass([
|
|
102
|
+
property({ type: String, attribute: "asset-id", reflect: true })
|
|
103
|
+
], EFImage.prototype, "assetId", 1);
|
|
75
104
|
EFImage = __decorateClass([
|
|
76
105
|
customElement("ef-image")
|
|
77
106
|
], EFImage);
|
|
@@ -12,13 +12,15 @@ import { EFSourceMixin } from "./EFSourceMixin.js";
|
|
|
12
12
|
import { getStartTimeMs } from "./util.js";
|
|
13
13
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.js";
|
|
14
14
|
import { apiHostContext } from "../gui/apiHostContext.js";
|
|
15
|
+
import { EF_RENDERING } from "../EF_RENDERING.js";
|
|
15
16
|
var __defProp = Object.defineProperty;
|
|
17
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
16
18
|
var __decorateClass = (decorators, target, key, kind) => {
|
|
17
|
-
var result = void 0;
|
|
19
|
+
var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
|
|
18
20
|
for (var i = decorators.length - 1, decorator; i >= 0; i--)
|
|
19
21
|
if (decorator = decorators[i])
|
|
20
|
-
result = decorator(target, key, result) || result;
|
|
21
|
-
if (result) __defProp(target, key, result);
|
|
22
|
+
result = (kind ? decorator(target, key, result) : decorator(result)) || result;
|
|
23
|
+
if (kind && result) __defProp(target, key, result);
|
|
22
24
|
return result;
|
|
23
25
|
};
|
|
24
26
|
const log = debug("ef:elements:EFMedia");
|
|
@@ -38,12 +40,10 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
38
40
|
constructor() {
|
|
39
41
|
super(...arguments);
|
|
40
42
|
this.currentTimeMs = 0;
|
|
43
|
+
this.#assetId = null;
|
|
41
44
|
this.trackFragmentIndexLoader = new Task(this, {
|
|
42
45
|
args: () => [this.fragmentIndexPath(), this.fetch],
|
|
43
46
|
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
44
|
-
if (this.src === "") {
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
47
|
const response = await fetch(fragmentIndexPath, { signal });
|
|
48
48
|
return await response.json();
|
|
49
49
|
},
|
|
@@ -228,15 +228,33 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
228
228
|
`
|
|
229
229
|
];
|
|
230
230
|
}
|
|
231
|
+
#assetId;
|
|
232
|
+
set assetId(value) {
|
|
233
|
+
if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
|
|
234
|
+
throw new Error(
|
|
235
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)"
|
|
236
|
+
);
|
|
237
|
+
}
|
|
238
|
+
this.#assetId = value;
|
|
239
|
+
}
|
|
240
|
+
get assetId() {
|
|
241
|
+
return this.#assetId || this.getAttribute("asset-id");
|
|
242
|
+
}
|
|
231
243
|
fragmentIndexPath() {
|
|
232
|
-
if (this.
|
|
233
|
-
|
|
244
|
+
if (this.assetId) {
|
|
245
|
+
if (EF_RENDERING()) {
|
|
246
|
+
return `editframe://api/video2/isobmff_files/${this.assetId}/index`;
|
|
247
|
+
}
|
|
248
|
+
return `https://editframe.dev/api/video2/isobmff_files/${this.assetId}/index`;
|
|
234
249
|
}
|
|
235
250
|
return `/@ef-track-fragment-index/${this.src ?? ""}`;
|
|
236
251
|
}
|
|
237
252
|
fragmentTrackPath(trackId) {
|
|
238
|
-
if (this.
|
|
239
|
-
|
|
253
|
+
if (this.assetId) {
|
|
254
|
+
if (EF_RENDERING()) {
|
|
255
|
+
return `editframe://api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
256
|
+
}
|
|
257
|
+
return `https://editframe.dev/api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
240
258
|
}
|
|
241
259
|
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
242
260
|
}
|
|
@@ -342,14 +360,17 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
342
360
|
}
|
|
343
361
|
__decorateClass([
|
|
344
362
|
property({ type: Number })
|
|
345
|
-
], EFMedia.prototype, "currentTimeMs");
|
|
363
|
+
], EFMedia.prototype, "currentTimeMs", 2);
|
|
346
364
|
__decorateClass([
|
|
347
365
|
consume({ context: apiHostContext, subscribe: true }),
|
|
348
366
|
state()
|
|
349
|
-
], EFMedia.prototype, "efHost");
|
|
367
|
+
], EFMedia.prototype, "efHost", 2);
|
|
368
|
+
__decorateClass([
|
|
369
|
+
property({ type: String, attribute: "asset-id", reflect: true })
|
|
370
|
+
], EFMedia.prototype, "assetId", 1);
|
|
350
371
|
__decorateClass([
|
|
351
372
|
state()
|
|
352
|
-
], EFMedia.prototype, "desiredSeekTimeMs");
|
|
373
|
+
], EFMedia.prototype, "desiredSeekTimeMs", 2);
|
|
353
374
|
export {
|
|
354
375
|
EFMedia,
|
|
355
376
|
deepGetMediaElements
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@editframe/elements",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.9.0-beta.3",
|
|
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.9.0-beta.3",
|
|
24
24
|
"@lit/context": "^1.1.2",
|
|
25
25
|
"@lit/task": "^1.0.1",
|
|
26
26
|
"d3": "^7.9.0",
|
package/src/elements/EFAudio.ts
CHANGED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import "./EFCaptions.ts";
|
|
3
|
+
import "./EFVideo.ts";
|
|
4
|
+
import { v4 } from "uuid";
|
|
5
|
+
|
|
6
|
+
describe("EFCaptions", () => {
|
|
7
|
+
describe("when rendering", () => {
|
|
8
|
+
beforeEach(() => {
|
|
9
|
+
// @ts-ignore
|
|
10
|
+
window.FRAMEGEN_BRIDGE = true;
|
|
11
|
+
});
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
// @ts-ignore
|
|
14
|
+
// biome-ignore lint/performance/noDelete: <explanation>
|
|
15
|
+
delete window.FRAMEGEN_BRIDGE;
|
|
16
|
+
});
|
|
17
|
+
test("captionsPath uses editframe:// protocol", () => {
|
|
18
|
+
const id = v4();
|
|
19
|
+
const workbench = document.createElement("ef-workbench");
|
|
20
|
+
|
|
21
|
+
const target = document.createElement("ef-video");
|
|
22
|
+
target.setAttribute("id", id);
|
|
23
|
+
target.assetId = "550e8400-e29b-41d4-a716-446655440000:example.mp4";
|
|
24
|
+
document.body.appendChild(target);
|
|
25
|
+
const captions = document.createElement("ef-captions");
|
|
26
|
+
captions.setAttribute("target", id);
|
|
27
|
+
document.body.appendChild(captions);
|
|
28
|
+
workbench.appendChild(captions);
|
|
29
|
+
expect(captions.captionsPath()).toBe(
|
|
30
|
+
"editframe://api/video2/caption_files/550e8400-e29b-41d4-a716-446655440000:example.mp4",
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
describe("attribute: asset-id", () => {
|
|
36
|
+
test("determines assetPath", () => {
|
|
37
|
+
const id = v4();
|
|
38
|
+
const target = document.createElement("ef-video");
|
|
39
|
+
target.setAttribute("id", id);
|
|
40
|
+
target.assetId = `${id}:example.mp4`;
|
|
41
|
+
document.body.appendChild(target);
|
|
42
|
+
const captions = document.createElement("ef-captions");
|
|
43
|
+
captions.setAttribute("target", id);
|
|
44
|
+
document.body.appendChild(captions);
|
|
45
|
+
expect(captions.captionsPath()).toBe(
|
|
46
|
+
`https://editframe.dev/api/video2/caption_files/${id}:example.mp4`,
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
});
|
|
@@ -8,6 +8,7 @@ import { CrossUpdateController } from "./CrossUpdateController.ts";
|
|
|
8
8
|
import { FetchMixin } from "./FetchMixin.ts";
|
|
9
9
|
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
10
10
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
11
|
+
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
11
12
|
|
|
12
13
|
interface Word {
|
|
13
14
|
text: string;
|
|
@@ -88,10 +89,13 @@ export class EFCaptions extends EFSourceMixin(
|
|
|
88
89
|
activeWordContainers = this.getElementsByTagName("ef-captions-active-word");
|
|
89
90
|
|
|
90
91
|
captionsPath() {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
92
|
+
if (this.targetElement.assetId) {
|
|
93
|
+
if (EF_RENDERING()) {
|
|
94
|
+
return `editframe://api/video2/caption_files/${this.targetElement.assetId}`;
|
|
95
|
+
}
|
|
96
|
+
return `https://editframe.dev/api/video2/caption_files/${this.targetElement.assetId}`;
|
|
94
97
|
}
|
|
98
|
+
const targetSrc = this.targetElement.src;
|
|
95
99
|
return `/@ef-captions/${targetSrc}`;
|
|
96
100
|
}
|
|
97
101
|
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "vitest";
|
|
2
|
+
import "./EFImage.ts";
|
|
3
|
+
import { v4 } from "uuid";
|
|
4
|
+
|
|
5
|
+
describe("EFImage", () => {
|
|
6
|
+
describe("when rendering", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
// @ts-ignore
|
|
9
|
+
window.FRAMEGEN_BRIDGE = true;
|
|
10
|
+
});
|
|
11
|
+
afterEach(() => {
|
|
12
|
+
// @ts-ignore
|
|
13
|
+
// biome-ignore lint/performance/noDelete: <explanation>
|
|
14
|
+
delete window.FRAMEGEN_BRIDGE;
|
|
15
|
+
});
|
|
16
|
+
test("assetPath uses editframe:// protocol", () => {
|
|
17
|
+
const workbench = document.createElement("ef-workbench");
|
|
18
|
+
const element = document.createElement("ef-image");
|
|
19
|
+
workbench.appendChild(element);
|
|
20
|
+
element.assetId = "550e8400-e29b-41d4-a716-446655440000:example.jpg";
|
|
21
|
+
expect(element.assetPath()).toBe(
|
|
22
|
+
"editframe://api/video2/image_files/550e8400-e29b-41d4-a716-446655440000:example.jpg",
|
|
23
|
+
);
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe("attribute: asset-id", () => {
|
|
28
|
+
test("must match :id/basename", () => {
|
|
29
|
+
const image = document.createElement("ef-image");
|
|
30
|
+
expect(() => {
|
|
31
|
+
image.assetId = "1234:example.mp4";
|
|
32
|
+
}).toThrowError(
|
|
33
|
+
new Error(
|
|
34
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)",
|
|
35
|
+
),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("determines assetPath", () => {
|
|
40
|
+
const id = v4();
|
|
41
|
+
const image = document.createElement("ef-image");
|
|
42
|
+
image.setAttribute("asset-id", `${id}:example.jpg`);
|
|
43
|
+
expect(image.assetPath()).toBe(
|
|
44
|
+
`https://editframe.dev/api/video2/image_files/${id}:example.jpg`,
|
|
45
|
+
);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
});
|
package/src/elements/EFImage.ts
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Task } from "@lit/task";
|
|
2
2
|
import { LitElement, html, css } from "lit";
|
|
3
|
-
import { customElement } from "lit/decorators.js";
|
|
3
|
+
import { customElement, property } from "lit/decorators.js";
|
|
4
4
|
import { createRef, ref } from "lit/directives/ref.js";
|
|
5
5
|
import { FetchMixin } from "./FetchMixin.ts";
|
|
6
6
|
import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
7
7
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
8
|
+
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
8
9
|
|
|
9
10
|
@customElement("ef-image")
|
|
10
11
|
export class EFImage extends EFSourceMixin(FetchMixin(LitElement), {
|
|
@@ -28,13 +29,32 @@ export class EFImage extends EFSourceMixin(FetchMixin(LitElement), {
|
|
|
28
29
|
imageRef = createRef<HTMLImageElement>();
|
|
29
30
|
canvasRef = createRef<HTMLCanvasElement>();
|
|
30
31
|
|
|
32
|
+
#assetId: string | null = null;
|
|
33
|
+
@property({ type: String, attribute: "asset-id", reflect: true })
|
|
34
|
+
set assetId(value: string | null) {
|
|
35
|
+
if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
|
|
36
|
+
console.error(`EFMedia ${value} is not valid asset-id`);
|
|
37
|
+
throw new Error(
|
|
38
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)",
|
|
39
|
+
);
|
|
40
|
+
}
|
|
41
|
+
this.#assetId = value;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
get assetId() {
|
|
45
|
+
return this.#assetId ?? this.getAttribute("asset-id");
|
|
46
|
+
}
|
|
47
|
+
|
|
31
48
|
render() {
|
|
32
49
|
return html`<canvas ${ref(this.canvasRef)}></canvas>`;
|
|
33
50
|
}
|
|
34
51
|
|
|
35
52
|
assetPath() {
|
|
36
|
-
if (this.
|
|
37
|
-
|
|
53
|
+
if (this.assetId) {
|
|
54
|
+
if (EF_RENDERING()) {
|
|
55
|
+
return `editframe://api/video2/image_files/${this.assetId}`;
|
|
56
|
+
}
|
|
57
|
+
return `https://editframe.dev/api/video2/image_files/${this.assetId}`;
|
|
38
58
|
}
|
|
39
59
|
return `/@ef-image/${this.src}`;
|
|
40
60
|
}
|
|
@@ -66,3 +86,9 @@ export class EFImage extends EFSourceMixin(FetchMixin(LitElement), {
|
|
|
66
86
|
},
|
|
67
87
|
});
|
|
68
88
|
}
|
|
89
|
+
|
|
90
|
+
declare global {
|
|
91
|
+
interface HTMLElementTagNameMap {
|
|
92
|
+
"ef-image": EFImage;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { describe, expect, test, beforeEach, afterEach } from "vitest";
|
|
2
|
+
import { v4 } from "uuid";
|
|
3
|
+
import { customElement } from "lit/decorators.js";
|
|
4
|
+
import { EFMedia } from "./EFMedia.ts";
|
|
5
|
+
import "../gui/EFWorkbench.ts";
|
|
6
|
+
|
|
7
|
+
@customElement("test-media")
|
|
8
|
+
class TestMedia extends EFMedia {}
|
|
9
|
+
|
|
10
|
+
declare global {
|
|
11
|
+
interface HTMLElementTagNameMap {
|
|
12
|
+
"test-media": TestMedia;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
describe("EFMedia", () => {
|
|
17
|
+
test("should be defined", () => {
|
|
18
|
+
const element = document.createElement("test-media");
|
|
19
|
+
expect(element.tagName).toBe("TEST-MEDIA");
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
describe("when rendering", () => {
|
|
23
|
+
beforeEach(() => {
|
|
24
|
+
// @ts-ignore
|
|
25
|
+
window.FRAMEGEN_BRIDGE = true;
|
|
26
|
+
});
|
|
27
|
+
afterEach(() => {
|
|
28
|
+
// @ts-ignore
|
|
29
|
+
// biome-ignore lint/performance/noDelete: <explanation>
|
|
30
|
+
delete window.FRAMEGEN_BRIDGE;
|
|
31
|
+
});
|
|
32
|
+
test("fragmentIndexPath uses editframe:// protocol", () => {
|
|
33
|
+
const workbench = document.createElement("ef-workbench");
|
|
34
|
+
const element = document.createElement("test-media");
|
|
35
|
+
workbench.appendChild(element);
|
|
36
|
+
element.assetId = "550e8400-e29b-41d4-a716-446655440000:example.mp4";
|
|
37
|
+
expect(element.fragmentIndexPath()).toBe(
|
|
38
|
+
"editframe://api/video2/isobmff_files/550e8400-e29b-41d4-a716-446655440000:example.mp4/index",
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("fragmentTrackPath uses editframe:// protocol", () => {
|
|
43
|
+
const workbench = document.createElement("ef-workbench");
|
|
44
|
+
const element = document.createElement("test-media");
|
|
45
|
+
workbench.appendChild(element);
|
|
46
|
+
element.assetId = "550e8400-e29b-41d4-a716-446655440000:example.mp4";
|
|
47
|
+
expect(element.fragmentTrackPath("1")).toBe(
|
|
48
|
+
"editframe://api/video2/isobmff_tracks/550e8400-e29b-41d4-a716-446655440000:example.mp4/1",
|
|
49
|
+
);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
describe("attribute: asset-id", () => {
|
|
54
|
+
test("must match :id/basename", () => {
|
|
55
|
+
const element = document.createElement("test-media");
|
|
56
|
+
expect(() => {
|
|
57
|
+
element.assetId = "1234:example.mp4";
|
|
58
|
+
}).toThrowError(
|
|
59
|
+
new Error(
|
|
60
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)",
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
test("determines fragmentIndexPath", () => {
|
|
66
|
+
const id = v4();
|
|
67
|
+
const element = document.createElement("test-media");
|
|
68
|
+
element.setAttribute("asset-id", `${id}:example.mp4`);
|
|
69
|
+
expect(element.fragmentIndexPath()).toBe(
|
|
70
|
+
`https://editframe.dev/api/video2/isobmff_files/${id}:example.mp4/index`,
|
|
71
|
+
);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("determines fragmentTrackPath", () => {
|
|
75
|
+
const id = v4();
|
|
76
|
+
const element = document.createElement("test-media");
|
|
77
|
+
element.setAttribute("asset-id", `${id}:example.mp4`);
|
|
78
|
+
expect(element.fragmentTrackPath("1")).toBe(
|
|
79
|
+
`https://editframe.dev/api/video2/isobmff_tracks/${id}:example.mp4/1`,
|
|
80
|
+
);
|
|
81
|
+
});
|
|
82
|
+
});
|
|
83
|
+
});
|
package/src/elements/EFMedia.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { EFSourceMixin } from "./EFSourceMixin.ts";
|
|
|
16
16
|
import { getStartTimeMs } from "./util.ts";
|
|
17
17
|
import { EF_INTERACTIVE } from "../EF_INTERACTIVE.ts";
|
|
18
18
|
import { apiHostContext } from "../gui/apiHostContext.ts";
|
|
19
|
+
import { EF_RENDERING } from "../EF_RENDERING.ts";
|
|
19
20
|
|
|
20
21
|
const log = debug("ef:elements:EFMedia");
|
|
21
22
|
|
|
@@ -53,26 +54,47 @@ export class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
53
54
|
@state()
|
|
54
55
|
efHost?: string;
|
|
55
56
|
|
|
57
|
+
#assetId: string | null = null;
|
|
58
|
+
@property({ type: String, attribute: "asset-id", reflect: true })
|
|
59
|
+
set assetId(value: string | null) {
|
|
60
|
+
if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
|
|
61
|
+
throw new Error(
|
|
62
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)",
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
this.#assetId = value;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
get assetId() {
|
|
69
|
+
return this.#assetId || this.getAttribute("asset-id");
|
|
70
|
+
}
|
|
71
|
+
|
|
56
72
|
fragmentIndexPath() {
|
|
57
|
-
if (this.
|
|
58
|
-
|
|
73
|
+
if (this.assetId) {
|
|
74
|
+
if (EF_RENDERING()) {
|
|
75
|
+
return `editframe://api/video2/isobmff_files/${this.assetId}/index`;
|
|
76
|
+
}
|
|
77
|
+
return `https://editframe.dev/api/video2/isobmff_files/${this.assetId}/index`;
|
|
59
78
|
}
|
|
60
79
|
return `/@ef-track-fragment-index/${this.src ?? ""}`;
|
|
61
80
|
}
|
|
62
81
|
|
|
63
82
|
fragmentTrackPath(trackId: string) {
|
|
64
|
-
if (this.
|
|
65
|
-
|
|
83
|
+
if (this.assetId) {
|
|
84
|
+
if (EF_RENDERING()) {
|
|
85
|
+
return `editframe://api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
86
|
+
}
|
|
87
|
+
return `https://editframe.dev/api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
66
88
|
}
|
|
89
|
+
// trackId is only specified as a query in the @ef-track url shape
|
|
90
|
+
// this is because that system doesn't have a full url matching system.
|
|
91
|
+
// This is an annoying incosistency that should be fixed.
|
|
67
92
|
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
68
93
|
}
|
|
69
94
|
|
|
70
95
|
public trackFragmentIndexLoader = new Task(this, {
|
|
71
96
|
args: () => [this.fragmentIndexPath(), this.fetch] as const,
|
|
72
97
|
task: async ([fragmentIndexPath, fetch], { signal }) => {
|
|
73
|
-
if (this.src === "") {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
98
|
const response = await fetch(fragmentIndexPath, { signal });
|
|
77
99
|
return (await response.json()) as Record<number, TrackFragmentIndex>;
|
|
78
100
|
},
|
package/src/elements/EFVideo.ts
CHANGED