@editframe/elements 0.8.0-beta.9 → 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.
@@ -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,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 {};
@@ -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,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.src.startsWith("editframe://") || this.src.startsWith("http")) {
233
- return `${this.src}/index`;
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.src.startsWith("editframe://") || this.src.startsWith("http")) {
239
- return `${this.src.replace("files", "tracks")}/${trackId}`;
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
@@ -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.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.8.0-beta.9",
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",
@@ -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?trackId=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,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.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
+ // 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
  }
@@ -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
  }