@editframe/elements 0.8.0-beta.8 → 0.9.0-beta.1
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 +2 -0
- 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 -10
- 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 +28 -4
- 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,6 +11,8 @@ 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
18
|
trackFragmentIndexLoader: Task<readonly [string, typeof fetch], Record<number, TrackFragmentIndex> | undefined>;
|
|
@@ -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,6 +40,7 @@ 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 }) => {
|
|
@@ -228,15 +231,33 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
228
231
|
`
|
|
229
232
|
];
|
|
230
233
|
}
|
|
234
|
+
#assetId;
|
|
235
|
+
set assetId(value) {
|
|
236
|
+
if (!value?.match(/^.{8}-.{4}-.{4}-.{4}-.{12}:.*$/)) {
|
|
237
|
+
throw new Error(
|
|
238
|
+
"EFMedia: asset-id must match <uuid>:<basename>. (like: 550e8400-e29b-41d4-a716-446655440000:example.mp4)"
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
this.#assetId = value;
|
|
242
|
+
}
|
|
243
|
+
get assetId() {
|
|
244
|
+
return this.#assetId || this.getAttribute("asset-id");
|
|
245
|
+
}
|
|
231
246
|
fragmentIndexPath() {
|
|
232
|
-
if (this.
|
|
233
|
-
|
|
247
|
+
if (this.assetId) {
|
|
248
|
+
if (EF_RENDERING()) {
|
|
249
|
+
return `editframe://api/video2/isobmff_files/${this.assetId}/index`;
|
|
250
|
+
}
|
|
251
|
+
return `https://editframe.dev/api/video2/isobmff_files/${this.assetId}/index`;
|
|
234
252
|
}
|
|
235
253
|
return `/@ef-track-fragment-index/${this.src ?? ""}`;
|
|
236
254
|
}
|
|
237
255
|
fragmentTrackPath(trackId) {
|
|
238
|
-
if (this.
|
|
239
|
-
|
|
256
|
+
if (this.assetId) {
|
|
257
|
+
if (EF_RENDERING()) {
|
|
258
|
+
return `editframe://api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
259
|
+
}
|
|
260
|
+
return `https://editframe.dev/api/video2/isobmff_tracks/${this.assetId}?trackId=${trackId}`;
|
|
240
261
|
}
|
|
241
262
|
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
242
263
|
}
|
|
@@ -342,14 +363,17 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
|
|
|
342
363
|
}
|
|
343
364
|
__decorateClass([
|
|
344
365
|
property({ type: Number })
|
|
345
|
-
], EFMedia.prototype, "currentTimeMs");
|
|
366
|
+
], EFMedia.prototype, "currentTimeMs", 2);
|
|
346
367
|
__decorateClass([
|
|
347
368
|
consume({ context: apiHostContext, subscribe: true }),
|
|
348
369
|
state()
|
|
349
|
-
], EFMedia.prototype, "efHost");
|
|
370
|
+
], EFMedia.prototype, "efHost", 2);
|
|
371
|
+
__decorateClass([
|
|
372
|
+
property({ type: String, attribute: "asset-id", reflect: true })
|
|
373
|
+
], EFMedia.prototype, "assetId", 1);
|
|
350
374
|
__decorateClass([
|
|
351
375
|
state()
|
|
352
|
-
], EFMedia.prototype, "desiredSeekTimeMs");
|
|
376
|
+
], EFMedia.prototype, "desiredSeekTimeMs", 2);
|
|
353
377
|
export {
|
|
354
378
|
EFMedia,
|
|
355
379
|
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.1",
|
|
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.1",
|
|
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?trackId=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,16 +54,39 @@ 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
|
+
// It is significant that the editframe:// protocol lists the trackId in the path rather than
|
|
86
|
+
// as a query parameter. This is a shortcut to loading track data from storage.
|
|
87
|
+
return `editframe://api/video2/isobmff_tracks/${this.assetId}/${trackId}`;
|
|
88
|
+
}
|
|
89
|
+
return `https://editframe.dev/api/video2/isobmff_tracks/${this.assetId}?trackId=${trackId}`;
|
|
66
90
|
}
|
|
67
91
|
return `/@ef-track/${this.src ?? ""}?trackId=${trackId}`;
|
|
68
92
|
}
|
package/src/elements/EFVideo.ts
CHANGED