@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.
@@ -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 {};
@@ -0,0 +1,9 @@
1
+ import { EFMedia } from './EFMedia.ts';
2
+ declare class TestMedia extends EFMedia {
3
+ }
4
+ declare global {
5
+ interface HTMLElementTagNameMap {
6
+ "test-media": TestMedia;
7
+ }
8
+ }
9
+ 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> | undefined>;
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<(Record<number, import('packages/assets/src/Probe.ts').TrackFragmentIndex> | undefined)[]>;
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 {};
@@ -0,0 +1,4 @@
1
+ const EF_RENDERING = () => typeof window !== "undefined" && "FRAMEGEN_BRIDGE" in window;
2
+ export {
3
+ EF_RENDERING
4
+ };
@@ -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
- const targetSrc = this.targetElement.src;
96
- if (targetSrc.startsWith("editframe://") || targetSrc.startsWith("http")) {
97
- return targetSrc.replace("isobmff", "caption");
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.src.startsWith("editframe://") || this.src.startsWith("http")) {
56
- return this.src;
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.src.startsWith("editframe://") || this.src.startsWith("http")) {
233
- return `${this.src}/index`;
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.src.startsWith("editframe://") || this.src.startsWith("http")) {
239
- return `${this.src.replace("files", "tracks")}/${trackId}`;
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
@@ -1,5 +1,4 @@
1
1
  const parseTimeToMs = (time) => {
2
- console.log("parseTimeToMs", time);
3
2
  if (time.endsWith("ms")) {
4
3
  return Number.parseFloat(time);
5
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@editframe/elements",
3
- "version": "0.8.0-beta.9",
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.8.0-beta.9",
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",
@@ -38,3 +38,9 @@ export class EFAudio extends EFMedia {
38
38
  },
39
39
  });
40
40
  }
41
+
42
+ declare global {
43
+ interface HTMLElementTagNameMap {
44
+ "ef-audio": EFAudio;
45
+ }
46
+ }
@@ -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
- const targetSrc = this.targetElement.src;
92
- if (targetSrc.startsWith("editframe://") || targetSrc.startsWith("http")) {
93
- return targetSrc.replace("isobmff", "caption");
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
+ });
@@ -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.src.startsWith("editframe://") || this.src.startsWith("http")) {
37
- return this.src;
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
+ });
@@ -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.src.startsWith("editframe://") || this.src.startsWith("http")) {
58
- return `${this.src}/index`;
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.src.startsWith("editframe://") || this.src.startsWith("http")) {
65
- return `${this.src.replace("files", "tracks")}/${trackId}`;
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
  },
@@ -24,6 +24,8 @@ export declare class TemporalMixinInterface {
24
24
 
25
25
  set duration(value: string);
26
26
  get duration(): string;
27
+ set trimstart(value: string);
28
+ set trimend(value: string);
27
29
 
28
30
  parentTimegroup?: EFTimegroup;
29
31
  rootTimegroup?: EFTimegroup;
@@ -101,3 +101,9 @@ export class EFVideo extends TWMixin(EFMedia) {
101
101
  },
102
102
  });
103
103
  }
104
+
105
+ declare global {
106
+ interface HTMLElementTagNameMap {
107
+ "ef-video": EFVideo;
108
+ }
109
+ }
@@ -1,5 +1,4 @@
1
1
  export const parseTimeToMs = (time: string) => {
2
- console.log("parseTimeToMs", time);
3
2
  if (time.endsWith("ms")) {
4
3
  return Number.parseFloat(time);
5
4
  }