@editframe/elements 0.19.2-beta.0 → 0.20.0-beta.0

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.
Files changed (96) hide show
  1. package/dist/elements/ContextProxiesController.d.ts +40 -0
  2. package/dist/elements/ContextProxiesController.js +69 -0
  3. package/dist/elements/EFCaptions.d.ts +45 -6
  4. package/dist/elements/EFCaptions.js +220 -26
  5. package/dist/elements/EFImage.js +4 -1
  6. package/dist/elements/EFMedia/AssetIdMediaEngine.d.ts +2 -1
  7. package/dist/elements/EFMedia/AssetIdMediaEngine.js +9 -0
  8. package/dist/elements/EFMedia/AssetMediaEngine.d.ts +1 -0
  9. package/dist/elements/EFMedia/AssetMediaEngine.js +11 -0
  10. package/dist/elements/EFMedia/BaseMediaEngine.d.ts +13 -1
  11. package/dist/elements/EFMedia/BaseMediaEngine.js +9 -0
  12. package/dist/elements/EFMedia/JitMediaEngine.d.ts +7 -1
  13. package/dist/elements/EFMedia/JitMediaEngine.js +24 -0
  14. package/dist/elements/EFMedia/shared/GlobalInputCache.d.ts +39 -0
  15. package/dist/elements/EFMedia/shared/GlobalInputCache.js +57 -0
  16. package/dist/elements/EFMedia/shared/ThumbnailExtractor.d.ts +27 -0
  17. package/dist/elements/EFMedia/shared/ThumbnailExtractor.js +106 -0
  18. package/dist/elements/EFMedia.js +25 -1
  19. package/dist/elements/EFSurface.browsertest.d.ts +0 -0
  20. package/dist/elements/EFSurface.d.ts +30 -0
  21. package/dist/elements/EFSurface.js +96 -0
  22. package/dist/elements/EFTemporal.js +7 -6
  23. package/dist/elements/EFThumbnailStrip.browsertest.d.ts +0 -0
  24. package/dist/elements/EFThumbnailStrip.d.ts +86 -0
  25. package/dist/elements/EFThumbnailStrip.js +490 -0
  26. package/dist/elements/EFThumbnailStrip.media-engine.browsertest.d.ts +0 -0
  27. package/dist/elements/EFTimegroup.d.ts +7 -7
  28. package/dist/elements/EFTimegroup.js +59 -16
  29. package/dist/elements/updateAnimations.browsertest.d.ts +13 -0
  30. package/dist/elements/updateAnimations.d.ts +5 -0
  31. package/dist/elements/updateAnimations.js +37 -13
  32. package/dist/getRenderInfo.js +1 -1
  33. package/dist/gui/ContextMixin.js +27 -14
  34. package/dist/gui/EFControls.browsertest.d.ts +0 -0
  35. package/dist/gui/EFControls.d.ts +38 -0
  36. package/dist/gui/EFControls.js +51 -0
  37. package/dist/gui/EFFilmstrip.d.ts +40 -1
  38. package/dist/gui/EFFilmstrip.js +240 -3
  39. package/dist/gui/EFPreview.js +2 -1
  40. package/dist/gui/EFScrubber.d.ts +6 -5
  41. package/dist/gui/EFScrubber.js +31 -21
  42. package/dist/gui/EFTimeDisplay.browsertest.d.ts +0 -0
  43. package/dist/gui/EFTimeDisplay.d.ts +2 -6
  44. package/dist/gui/EFTimeDisplay.js +13 -23
  45. package/dist/gui/TWMixin.js +1 -1
  46. package/dist/gui/currentTimeContext.d.ts +3 -0
  47. package/dist/gui/currentTimeContext.js +3 -0
  48. package/dist/gui/durationContext.d.ts +3 -0
  49. package/dist/gui/durationContext.js +3 -0
  50. package/dist/index.d.ts +3 -0
  51. package/dist/index.js +4 -1
  52. package/dist/style.css +1 -1
  53. package/dist/transcoding/types/index.d.ts +11 -0
  54. package/dist/utils/LRUCache.d.ts +46 -0
  55. package/dist/utils/LRUCache.js +382 -1
  56. package/dist/utils/LRUCache.test.d.ts +1 -0
  57. package/package.json +2 -2
  58. package/src/elements/ContextProxiesController.ts +123 -0
  59. package/src/elements/EFCaptions.browsertest.ts +1820 -0
  60. package/src/elements/EFCaptions.ts +373 -36
  61. package/src/elements/EFImage.ts +4 -1
  62. package/src/elements/EFMedia/AssetIdMediaEngine.ts +30 -1
  63. package/src/elements/EFMedia/AssetMediaEngine.ts +33 -0
  64. package/src/elements/EFMedia/BaseMediaEngine.browsertest.ts +3 -8
  65. package/src/elements/EFMedia/BaseMediaEngine.ts +35 -0
  66. package/src/elements/EFMedia/JitMediaEngine.ts +48 -0
  67. package/src/elements/EFMedia/shared/GlobalInputCache.ts +77 -0
  68. package/src/elements/EFMedia/shared/ThumbnailExtractor.ts +227 -0
  69. package/src/elements/EFMedia.ts +38 -1
  70. package/src/elements/EFSurface.browsertest.ts +155 -0
  71. package/src/elements/EFSurface.ts +141 -0
  72. package/src/elements/EFTemporal.ts +14 -8
  73. package/src/elements/EFThumbnailStrip.browsertest.ts +591 -0
  74. package/src/elements/EFThumbnailStrip.media-engine.browsertest.ts +713 -0
  75. package/src/elements/EFThumbnailStrip.ts +905 -0
  76. package/src/elements/EFTimegroup.browsertest.ts +56 -7
  77. package/src/elements/EFTimegroup.ts +88 -18
  78. package/src/elements/updateAnimations.browsertest.ts +361 -12
  79. package/src/elements/updateAnimations.ts +68 -19
  80. package/src/gui/ContextMixin.browsertest.ts +0 -25
  81. package/src/gui/ContextMixin.ts +44 -20
  82. package/src/gui/EFControls.browsertest.ts +175 -0
  83. package/src/gui/EFControls.ts +84 -0
  84. package/src/gui/EFFilmstrip.ts +323 -4
  85. package/src/gui/EFPreview.ts +2 -1
  86. package/src/gui/EFScrubber.ts +29 -25
  87. package/src/gui/EFTimeDisplay.browsertest.ts +237 -0
  88. package/src/gui/EFTimeDisplay.ts +12 -40
  89. package/src/gui/currentTimeContext.ts +5 -0
  90. package/src/gui/durationContext.ts +3 -0
  91. package/src/transcoding/types/index.ts +13 -0
  92. package/src/utils/LRUCache.test.ts +272 -0
  93. package/src/utils/LRUCache.ts +543 -0
  94. package/types.json +1 -1
  95. package/dist/transcoding/cache/CacheManager.d.ts +0 -73
  96. package/src/transcoding/cache/CacheManager.ts +0 -208
@@ -157,5 +157,16 @@ var AssetMediaEngine = class AssetMediaEngine extends BaseMediaEngine {
157
157
  maxAudioBufferFetches: 1
158
158
  };
159
159
  }
160
+ convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
161
+ {
162
+ if (!rendition.trackId) throw new Error("Track ID is required for asset metadata");
163
+ const trackData = this.data[rendition.trackId];
164
+ if (!trackData) throw new Error("Track not found");
165
+ const segment = trackData.segments?.[segmentId];
166
+ if (!segment) throw new Error("Segment not found");
167
+ const segmentStartMs = segment.cts / trackData.timescale * 1e3;
168
+ return globalTimestamps.map((globalMs) => (globalMs - segmentStartMs) / 1e3);
169
+ }
170
+ }
160
171
  };
161
172
  export { AssetMediaEngine };
@@ -1,7 +1,8 @@
1
1
  import { RequestDeduplicator } from '../../transcoding/cache/RequestDeduplicator.js';
2
- import { AudioRendition, SegmentTimeRange, VideoRendition } from '../../transcoding/types';
2
+ import { AudioRendition, SegmentTimeRange, ThumbnailResult, VideoRendition } from '../../transcoding/types';
3
3
  import { SizeAwareLRUCache } from '../../utils/LRUCache.js';
4
4
  import { EFMedia } from '../EFMedia.js';
5
+ import { MediaRendition } from './shared/MediaTaskUtils.js';
5
6
  export declare const mediaCache: SizeAwareLRUCache<string>;
6
7
  export declare const globalRequestDeduplicator: RequestDeduplicator;
7
8
  export declare abstract class BaseMediaEngine {
@@ -43,6 +44,11 @@ export declare abstract class BaseMediaEngine {
43
44
  trackId: number | undefined;
44
45
  src: string;
45
46
  }): Promise<ArrayBuffer>;
47
+ abstract fetchInitSegment(rendition: {
48
+ trackId: number | undefined;
49
+ src: string;
50
+ }, signal: AbortSignal): Promise<ArrayBuffer>;
51
+ abstract computeSegmentId(desiredSeekTimeMs: number, rendition: MediaRendition): number | undefined;
46
52
  /**
47
53
  * Fetch media segment with built-in deduplication
48
54
  * Now uses global deduplication for all requests
@@ -80,4 +86,10 @@ export declare abstract class BaseMediaEngine {
80
86
  * Get cached segment IDs from a list for a given rendition
81
87
  */
82
88
  getCachedSegments(segmentIds: number[], rendition: AudioRendition | VideoRendition): Set<number>;
89
+ /**
90
+ * Extract thumbnail canvases at multiple timestamps efficiently
91
+ * Default implementation provides helpful error information
92
+ */
93
+ extractThumbnails(timestamps: number[]): Promise<(ThumbnailResult | null)[]>;
94
+ abstract convertToSegmentRelativeTimestamps(globalTimestamps: number[], segmentId: number, rendition: VideoRendition): number[];
83
95
  }
@@ -190,5 +190,14 @@ var BaseMediaEngine = class {
190
190
  getCachedSegments(segmentIds, rendition) {
191
191
  return new Set(segmentIds.filter((id) => this.isSegmentCached(id, rendition)));
192
192
  }
193
+ /**
194
+ * Extract thumbnail canvases at multiple timestamps efficiently
195
+ * Default implementation provides helpful error information
196
+ */
197
+ async extractThumbnails(timestamps) {
198
+ const engineName = this.constructor.name;
199
+ console.warn(`${engineName}: extractThumbnails not properly implemented. This MediaEngine type does not support thumbnail generation. Supported engines: JitMediaEngine. Requested ${timestamps.length} thumbnail${timestamps.length === 1 ? "" : "s"}.`);
200
+ return timestamps.map(() => null);
201
+ }
193
202
  };
194
203
  export { BaseMediaEngine };
@@ -1,10 +1,11 @@
1
- import { AudioRendition, MediaEngine, RenditionId, VideoRendition } from '../../transcoding/types';
1
+ import { AudioRendition, MediaEngine, RenditionId, ThumbnailResult, VideoRendition } from '../../transcoding/types';
2
2
  import { UrlGenerator } from '../../transcoding/utils/UrlGenerator';
3
3
  import { EFMedia } from '../EFMedia.js';
4
4
  import { BaseMediaEngine } from './BaseMediaEngine';
5
5
  export declare class JitMediaEngine extends BaseMediaEngine implements MediaEngine {
6
6
  private urlGenerator;
7
7
  private data;
8
+ private thumbnailExtractor;
8
9
  static fetch(host: EFMedia, urlGenerator: UrlGenerator, url: string): Promise<JitMediaEngine>;
9
10
  constructor(host: EFMedia, urlGenerator: UrlGenerator);
10
11
  get durationMs(): number;
@@ -37,4 +38,9 @@ export declare class JitMediaEngine extends BaseMediaEngine implements MediaEngi
37
38
  maxVideoBufferFetches: number;
38
39
  maxAudioBufferFetches: number;
39
40
  };
41
+ /**
42
+ * Extract thumbnail canvases using same rendition priority as video playback for frame alignment
43
+ */
44
+ extractThumbnails(timestamps: number[]): Promise<(ThumbnailResult | null)[]>;
45
+ convertToSegmentRelativeTimestamps(globalTimestamps: number[], _segmentId: number, _rendition: VideoRendition): number[];
40
46
  }
@@ -1,4 +1,5 @@
1
1
  import { BaseMediaEngine } from "./BaseMediaEngine.js";
2
+ import { ThumbnailExtractor } from "./shared/ThumbnailExtractor.js";
2
3
  var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
3
4
  static async fetch(host, urlGenerator, url) {
4
5
  const engine = new JitMediaEngine(host, urlGenerator);
@@ -10,6 +11,7 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
10
11
  super(host);
11
12
  this.data = {};
12
13
  this.urlGenerator = urlGenerator;
14
+ this.thumbnailExtractor = new ThumbnailExtractor(this);
13
15
  }
14
16
  get durationMs() {
15
17
  return this.data.durationMs;
@@ -101,5 +103,27 @@ var JitMediaEngine = class JitMediaEngine extends BaseMediaEngine {
101
103
  maxAudioBufferFetches: 3
102
104
  };
103
105
  }
106
+ /**
107
+ * Extract thumbnail canvases using same rendition priority as video playback for frame alignment
108
+ */
109
+ async extractThumbnails(timestamps) {
110
+ let rendition;
111
+ try {
112
+ const mainRendition = this.getVideoRendition();
113
+ if (mainRendition) rendition = mainRendition;
114
+ else {
115
+ const scrubRendition = this.getScrubVideoRendition();
116
+ if (scrubRendition) rendition = scrubRendition;
117
+ else throw new Error("No video rendition available");
118
+ }
119
+ } catch (error) {
120
+ console.warn("JitMediaEngine: No video rendition available for thumbnails", error);
121
+ return timestamps.map(() => null);
122
+ }
123
+ return this.thumbnailExtractor.extractThumbnails(timestamps, rendition, this.durationMs);
124
+ }
125
+ convertToSegmentRelativeTimestamps(globalTimestamps, _segmentId, _rendition) {
126
+ return globalTimestamps.map((timestamp) => timestamp / 1e3);
127
+ }
104
128
  };
105
129
  export { JitMediaEngine };
@@ -0,0 +1,39 @@
1
+ import { Input } from 'mediabunny';
2
+ /**
3
+ * Global cache for MediaBunny Input instances
4
+ * Shared across all MediaEngine instances to prevent duplicate decoding
5
+ * of the same segment data
6
+ */
7
+ declare class GlobalInputCache {
8
+ private cache;
9
+ /**
10
+ * Generate standardized cache key for Input objects
11
+ * Format: "input:{src}:{segmentId}:{renditionId}"
12
+ */
13
+ private generateKey;
14
+ /**
15
+ * Get cached Input object
16
+ */
17
+ get(src: string, segmentId: number, renditionId?: string): Input | undefined;
18
+ /**
19
+ * Cache Input object
20
+ */
21
+ set(src: string, segmentId: number, input: Input, renditionId?: string): void;
22
+ /**
23
+ * Check if Input is cached
24
+ */
25
+ has(src: string, segmentId: number, renditionId?: string): boolean;
26
+ /**
27
+ * Clear all cached Input objects
28
+ */
29
+ clear(): void;
30
+ /**
31
+ * Get cache statistics for debugging
32
+ */
33
+ getStats(): {
34
+ size: number;
35
+ cachedKeys: unknown[];
36
+ };
37
+ }
38
+ export declare const globalInputCache: GlobalInputCache;
39
+ export {};
@@ -0,0 +1,57 @@
1
+ import { LRUCache } from "../../../utils/LRUCache.js";
2
+ /**
3
+ * Global cache for MediaBunny Input instances
4
+ * Shared across all MediaEngine instances to prevent duplicate decoding
5
+ * of the same segment data
6
+ */
7
+ var GlobalInputCache = class {
8
+ constructor() {
9
+ this.cache = new LRUCache(50);
10
+ }
11
+ /**
12
+ * Generate standardized cache key for Input objects
13
+ * Format: "input:{src}:{segmentId}:{renditionId}"
14
+ */
15
+ generateKey(src, segmentId, renditionId) {
16
+ return `input:${src}:${segmentId}:${renditionId || "default"}`;
17
+ }
18
+ /**
19
+ * Get cached Input object
20
+ */
21
+ get(src, segmentId, renditionId) {
22
+ const key = this.generateKey(src, segmentId, renditionId);
23
+ return this.cache.get(key);
24
+ }
25
+ /**
26
+ * Cache Input object
27
+ */
28
+ set(src, segmentId, input, renditionId) {
29
+ const key = this.generateKey(src, segmentId, renditionId);
30
+ this.cache.set(key, input);
31
+ }
32
+ /**
33
+ * Check if Input is cached
34
+ */
35
+ has(src, segmentId, renditionId) {
36
+ const key = this.generateKey(src, segmentId, renditionId);
37
+ return this.cache.has(key);
38
+ }
39
+ /**
40
+ * Clear all cached Input objects
41
+ */
42
+ clear() {
43
+ this.cache.clear();
44
+ }
45
+ /**
46
+ * Get cache statistics for debugging
47
+ */
48
+ getStats() {
49
+ return {
50
+ size: this.cache.size,
51
+ cachedKeys: Array.from(this.cache.cache.keys())
52
+ };
53
+ }
54
+ };
55
+ const globalInputCache = new GlobalInputCache();
56
+ globalThis.debugInputCache = globalInputCache;
57
+ export { globalInputCache };
@@ -0,0 +1,27 @@
1
+ import { ThumbnailResult, VideoRendition } from '../../../transcoding/types/index.js';
2
+ import { BaseMediaEngine } from '../BaseMediaEngine.js';
3
+ /**
4
+ * Shared thumbnail extraction logic for all MediaEngine implementations
5
+ * Eliminates code duplication and provides consistent behavior
6
+ */
7
+ export declare class ThumbnailExtractor {
8
+ private mediaEngine;
9
+ constructor(mediaEngine: BaseMediaEngine);
10
+ /**
11
+ * Extract thumbnails at multiple timestamps efficiently using segment batching
12
+ */
13
+ extractThumbnails(timestamps: number[], rendition: VideoRendition, durationMs: number): Promise<(ThumbnailResult | null)[]>;
14
+ /**
15
+ * Group timestamps by segment ID for efficient batch processing
16
+ */
17
+ private groupTimestampsBySegment;
18
+ /**
19
+ * Extract thumbnails for a specific segment using CanvasSink
20
+ */
21
+ private extractSegmentThumbnails;
22
+ /**
23
+ * Convert global timestamps to segment-relative timestamps for mediabunny
24
+ * This is where the main difference between JIT and Asset engines lies
25
+ */
26
+ private convertToSegmentRelativeTimestamps;
27
+ }
@@ -0,0 +1,106 @@
1
+ import { globalInputCache } from "./GlobalInputCache.js";
2
+ import { ALL_FORMATS, BlobSource, CanvasSink, Input } from "mediabunny";
3
+ /**
4
+ * Shared thumbnail extraction logic for all MediaEngine implementations
5
+ * Eliminates code duplication and provides consistent behavior
6
+ */
7
+ var ThumbnailExtractor = class {
8
+ constructor(mediaEngine) {
9
+ this.mediaEngine = mediaEngine;
10
+ }
11
+ /**
12
+ * Extract thumbnails at multiple timestamps efficiently using segment batching
13
+ */
14
+ async extractThumbnails(timestamps, rendition, durationMs) {
15
+ if (timestamps.length === 0) return [];
16
+ const validTimestamps = timestamps.filter((timeMs) => timeMs >= 0 && timeMs <= durationMs);
17
+ if (validTimestamps.length === 0) {
18
+ console.warn(`ThumbnailExtractor: All timestamps out of bounds (0-${durationMs}ms)`);
19
+ return timestamps.map(() => null);
20
+ }
21
+ const segmentGroups = this.groupTimestampsBySegment(validTimestamps, rendition);
22
+ const results = /* @__PURE__ */ new Map();
23
+ for (const [segmentId, segmentTimestamps] of segmentGroups) try {
24
+ const segmentResults = await this.extractSegmentThumbnails(segmentId, segmentTimestamps, rendition);
25
+ for (const [timestamp, thumbnail] of segmentResults) results.set(timestamp, thumbnail);
26
+ } catch (error) {
27
+ console.warn(`ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`, error);
28
+ for (const timestamp of segmentTimestamps) results.set(timestamp, null);
29
+ }
30
+ return timestamps.map((t) => {
31
+ if (t < 0 || t > durationMs) return null;
32
+ return results.get(t) || null;
33
+ });
34
+ }
35
+ /**
36
+ * Group timestamps by segment ID for efficient batch processing
37
+ */
38
+ groupTimestampsBySegment(timestamps, rendition) {
39
+ const segmentGroups = /* @__PURE__ */ new Map();
40
+ for (const timeMs of timestamps) try {
41
+ const segmentId = this.mediaEngine.computeSegmentId(timeMs, rendition);
42
+ if (segmentId !== void 0) {
43
+ if (!segmentGroups.has(segmentId)) segmentGroups.set(segmentId, []);
44
+ const segmentGroup = segmentGroups.get(segmentId) ?? [];
45
+ if (!segmentGroup) segmentGroups.set(segmentId, []);
46
+ segmentGroup.push(timeMs);
47
+ }
48
+ } catch (error) {
49
+ console.warn(`ThumbnailExtractor: Could not compute segment for timestamp ${timeMs}:`, error);
50
+ }
51
+ return segmentGroups;
52
+ }
53
+ /**
54
+ * Extract thumbnails for a specific segment using CanvasSink
55
+ */
56
+ async extractSegmentThumbnails(segmentId, timestamps, rendition) {
57
+ const results = /* @__PURE__ */ new Map();
58
+ try {
59
+ const abortController = new AbortController();
60
+ const [initSegment, mediaSegment] = await Promise.all([this.mediaEngine.fetchInitSegment(rendition, abortController.signal), this.mediaEngine.fetchMediaSegment(segmentId, rendition)]);
61
+ const segmentBlob = new Blob([initSegment, mediaSegment]);
62
+ let input = globalInputCache.get(rendition.src, segmentId, rendition.id);
63
+ if (!input) {
64
+ input = new Input({
65
+ formats: ALL_FORMATS,
66
+ source: new BlobSource(segmentBlob)
67
+ });
68
+ globalInputCache.set(rendition.src, segmentId, input, rendition.id);
69
+ }
70
+ const videoTrack = await input.getPrimaryVideoTrack();
71
+ if (!videoTrack) {
72
+ for (const timestamp of timestamps) results.set(timestamp, null);
73
+ return results;
74
+ }
75
+ const sink = new CanvasSink(videoTrack);
76
+ const relativeTimestamps = this.convertToSegmentRelativeTimestamps(timestamps, segmentId, rendition);
77
+ const timestampResults = [];
78
+ for await (const result of sink.canvasesAtTimestamps(relativeTimestamps)) timestampResults.push(result);
79
+ for (let i = 0; i < timestamps.length; i++) {
80
+ const globalTimestamp = timestamps[i];
81
+ if (globalTimestamp === void 0) continue;
82
+ const result = timestampResults[i];
83
+ if (result?.canvas) {
84
+ const canvas = result.canvas;
85
+ if (canvas instanceof HTMLCanvasElement || canvas instanceof OffscreenCanvas) results.set(globalTimestamp, {
86
+ timestamp: globalTimestamp,
87
+ thumbnail: canvas
88
+ });
89
+ else results.set(globalTimestamp, null);
90
+ } else results.set(globalTimestamp, null);
91
+ }
92
+ } catch (error) {
93
+ console.error(`ThumbnailExtractor: Failed to extract thumbnails for segment ${segmentId}:`, error);
94
+ for (const timestamp of timestamps) results.set(timestamp, null);
95
+ }
96
+ return results;
97
+ }
98
+ /**
99
+ * Convert global timestamps to segment-relative timestamps for mediabunny
100
+ * This is where the main difference between JIT and Asset engines lies
101
+ */
102
+ convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition) {
103
+ return this.mediaEngine.convertToSegmentRelativeTimestamps(globalTimestamps, segmentId, rendition);
104
+ }
105
+ };
106
+ export { ThumbnailExtractor };
@@ -1,3 +1,4 @@
1
+ import { isContextMixin } from "../gui/ContextMixin.js";
1
2
  import { UrlGenerator } from "../transcoding/utils/UrlGenerator.js";
2
3
  import { makeMediaEngineTask } from "./EFMedia/tasks/makeMediaEngineTask.js";
3
4
  import { makeAudioBufferTask } from "./EFMedia/audioTasks/makeAudioBufferTask.js";
@@ -64,7 +65,9 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
64
65
  "asset-id",
65
66
  "audio-buffer-duration",
66
67
  "max-audio-buffer-fetches",
67
- "enable-audio-buffering"
68
+ "enable-audio-buffering",
69
+ "sourcein",
70
+ "sourceout"
68
71
  ];
69
72
  }
70
73
  static {
@@ -105,6 +108,27 @@ var EFMedia = class extends EFTargetable(EFSourceMixin(EFTemporal(FetchMixin(Lit
105
108
  const newCurrentSourceTimeMs = this.currentSourceTimeMs;
106
109
  if (newCurrentSourceTimeMs !== this.desiredSeekTimeMs) this.executeSeek(newCurrentSourceTimeMs);
107
110
  if (changedProperties.has("ownCurrentTimeMs")) this.executeSeek(this.currentSourceTimeMs);
111
+ const durationAffectingProps = [
112
+ "_trimStartMs",
113
+ "_trimEndMs",
114
+ "_sourceInMs",
115
+ "_sourceOutMs"
116
+ ];
117
+ const hasDurationChange = durationAffectingProps.some((prop) => changedProperties.has(prop));
118
+ if (hasDurationChange) {
119
+ if (this.parentTimegroup) {
120
+ this.parentTimegroup.requestUpdate("durationMs");
121
+ this.parentTimegroup.requestUpdate("currentTime");
122
+ let parent = this.parentNode;
123
+ while (parent) {
124
+ if (isContextMixin(parent)) {
125
+ parent.dispatchEvent(new CustomEvent("child-duration-changed", { detail: { source: this } }));
126
+ break;
127
+ }
128
+ parent = parent.parentNode;
129
+ }
130
+ }
131
+ }
108
132
  }
109
133
  get hasOwnDuration() {
110
134
  return true;
File without changes
@@ -0,0 +1,30 @@
1
+ import { Task } from '@lit/task';
2
+ import { LitElement } from 'lit';
3
+ import { ContextMixinInterface } from '../gui/ContextMixin.ts';
4
+ export declare class EFSurface extends LitElement {
5
+ #private;
6
+ static styles: import('lit').CSSResult[];
7
+ canvasRef: import('lit-html/directives/ref').Ref<HTMLCanvasElement>;
8
+ targetElement: ContextMixinInterface | null;
9
+ target: string;
10
+ render(): import('lit-html').TemplateResult<1>;
11
+ get rootTimegroup(): any;
12
+ get currentTimeMs(): number;
13
+ get durationMs(): number;
14
+ get startTimeMs(): number;
15
+ get endTimeMs(): number;
16
+ /**
17
+ * Minimal integration with EFTimegroup's frame scheduling:
18
+ * - Waits for the target video element's frameTask to complete (ensuring it painted)
19
+ * - Copies the target's canvas into this element's canvas
20
+ */
21
+ frameTask: Task<readonly [ContextMixinInterface | null], void>;
22
+ protected updated(): void;
23
+ private getSourceCanvas;
24
+ private copyFromTarget;
25
+ }
26
+ declare global {
27
+ interface HTMLElementTagNameMap {
28
+ "ef-surface": EFSurface;
29
+ }
30
+ }
@@ -0,0 +1,96 @@
1
+ import { TargetController } from "./TargetController.js";
2
+ import { Task } from "@lit/task";
3
+ import { LitElement, css, html } from "lit";
4
+ import { customElement, property, state } from "lit/decorators.js";
5
+ import _decorate from "@oxc-project/runtime/helpers/decorate";
6
+ import { createRef, ref } from "lit/directives/ref.js";
7
+ let EFSurface = class EFSurface$1 extends LitElement {
8
+ constructor(..._args) {
9
+ super(..._args);
10
+ this.canvasRef = createRef();
11
+ this.targetElement = null;
12
+ this.target = "";
13
+ this.frameTask = new Task(this, {
14
+ autoRun: false,
15
+ args: () => [this.targetElement],
16
+ task: async ([target]) => {
17
+ if (!target) return;
18
+ try {
19
+ const maybeTask = target.frameTask;
20
+ if (maybeTask && typeof maybeTask.run === "function") {
21
+ maybeTask.run();
22
+ await maybeTask.taskComplete;
23
+ }
24
+ } catch (_err) {}
25
+ this.copyFromTarget(target);
26
+ }
27
+ });
28
+ }
29
+ static {
30
+ this.styles = [css`
31
+ :host {
32
+ display: block;
33
+ position: relative;
34
+ }
35
+ canvas {
36
+ all: inherit;
37
+ width: 100%;
38
+ height: 100%;
39
+ display: block;
40
+ }
41
+ `];
42
+ }
43
+ #targetController = new TargetController(this);
44
+ render() {
45
+ return html`<canvas ${ref(this.canvasRef)}></canvas>`;
46
+ }
47
+ get rootTimegroup() {
48
+ const target = this.targetElement;
49
+ if (target && "rootTimegroup" in target) return target.rootTimegroup;
50
+ let root = this.closest("ef-timegroup");
51
+ while (root?.parentTimegroup) root = root.parentTimegroup;
52
+ return root;
53
+ }
54
+ get currentTimeMs() {
55
+ return this.rootTimegroup?.currentTimeMs ?? 0;
56
+ }
57
+ get durationMs() {
58
+ return this.rootTimegroup?.durationMs ?? 0;
59
+ }
60
+ get startTimeMs() {
61
+ return this.rootTimegroup?.startTimeMs ?? 0;
62
+ }
63
+ get endTimeMs() {
64
+ return this.startTimeMs + this.durationMs;
65
+ }
66
+ updated() {
67
+ if (this.targetElement) this.copyFromTarget(this.targetElement);
68
+ }
69
+ getSourceCanvas(from) {
70
+ const anyEl = from;
71
+ if ("canvasElement" in anyEl) return anyEl.canvasElement ?? null;
72
+ const sr = from.shadowRoot;
73
+ if (sr) {
74
+ const c = sr.querySelector("canvas");
75
+ return c ?? null;
76
+ }
77
+ return null;
78
+ }
79
+ copyFromTarget(target) {
80
+ const dst = this.canvasRef.value;
81
+ const src = this.getSourceCanvas(target);
82
+ if (!dst || !src) return;
83
+ if (!src.width || !src.height) return;
84
+ if (dst.width !== src.width || dst.height !== src.height) {
85
+ dst.width = src.width;
86
+ dst.height = src.height;
87
+ }
88
+ const ctx = dst.getContext("2d");
89
+ if (!ctx) return;
90
+ ctx.drawImage(src, 0, 0, dst.width, dst.height);
91
+ }
92
+ };
93
+ _decorate([state()], EFSurface.prototype, "targetElement", void 0);
94
+ _decorate([property({ type: String })], EFSurface.prototype, "target", void 0);
95
+ EFSurface = _decorate([customElement("ef-surface")], EFSurface);
96
+ export { EFSurface };
@@ -140,25 +140,26 @@ const EFTemporal = (superClass) => {
140
140
  return void 0;
141
141
  }
142
142
  get hasOwnDuration() {
143
- return false;
143
+ return this.intrinsicDurationMs !== void 0 || this.hasExplicitDuration;
144
144
  }
145
145
  get intrinsicDurationMs() {
146
146
  return void 0;
147
147
  }
148
148
  get durationMs() {
149
- if (this.intrinsicDurationMs === void 0) return this._durationMs || this.parentTimegroup?.durationMs || 0;
149
+ const baseDurationMs = this.intrinsicDurationMs ?? this._durationMs ?? this.parentTimegroup?.durationMs ?? 0;
150
+ if (baseDurationMs === 0) return 0;
150
151
  if (this.trimStartMs || this.trimEndMs) {
151
- const trimmedDurationMs = this.intrinsicDurationMs - (this.trimStartMs ?? 0) - (this.trimEndMs ?? 0);
152
+ const trimmedDurationMs = baseDurationMs - (this.trimStartMs ?? 0) - (this.trimEndMs ?? 0);
152
153
  if (trimmedDurationMs < 0) return 0;
153
154
  return trimmedDurationMs;
154
155
  }
155
156
  if (this.sourceInMs || this.sourceOutMs) {
156
157
  const sourceInMs = this.sourceInMs ?? 0;
157
- const sourceOutMs = this.sourceOutMs ?? this.intrinsicDurationMs;
158
+ const sourceOutMs = this.sourceOutMs ?? baseDurationMs;
158
159
  if (sourceInMs >= sourceOutMs) return 0;
159
160
  return sourceOutMs - sourceInMs;
160
161
  }
161
- return this.intrinsicDurationMs;
162
+ return baseDurationMs;
162
163
  }
163
164
  get sourceStartMs() {
164
165
  return this.trimStartMs ?? this.sourceInMs ?? 0;
@@ -284,4 +285,4 @@ const EFTemporal = (superClass) => {
284
285
  Object.defineProperty(TemporalMixinClass.prototype, EF_TEMPORAL, { value: true });
285
286
  return TemporalMixinClass;
286
287
  };
287
- export { EFTemporal, deepGetElementsWithFrameTasks, deepGetTemporalElements, flushStartTimeMsCache, isEFTemporal, resetTemporalCache, shallowGetTemporalElements, timegroupContext };
288
+ export { EFTemporal, deepGetElementsWithFrameTasks, deepGetTemporalElements, flushStartTimeMsCache, resetTemporalCache, shallowGetTemporalElements, timegroupContext };
@@ -0,0 +1,86 @@
1
+ import { LitElement } from 'lit';
2
+ import { EFVideo } from './EFVideo.js';
3
+ export declare class EFThumbnailStrip extends LitElement {
4
+ static styles: import('lit').CSSResult[];
5
+ canvasRef: import('lit-html/directives/ref').Ref<HTMLCanvasElement>;
6
+ private _targetController;
7
+ private _targetElement;
8
+ get targetElement(): EFVideo | null;
9
+ set targetElement(value: EFVideo | null);
10
+ target: string;
11
+ /**
12
+ * Desired thumbnail width in pixels (height is determined by aspect ratio)
13
+ * Number of thumbnails is derived from this and available strip width
14
+ */
15
+ thumbnailWidth: number;
16
+ /**
17
+ * Custom start time in milliseconds relative to trimmed timeline (0 = start of trimmed portion)
18
+ * In trimmed mode: 0ms = sourceStartMs, 1000ms = sourceStartMs + 1000ms
19
+ * In intrinsic mode: 0ms = 0ms in source media
20
+ */
21
+ startTimeMs?: number;
22
+ /**
23
+ * Custom end time in milliseconds relative to trimmed timeline
24
+ * In trimmed mode: relative to sourceStartMs
25
+ * In intrinsic mode: relative to source media start (0ms)
26
+ */
27
+ endTimeMs?: number;
28
+ /**
29
+ * Use intrinsic duration instead of trimmed duration
30
+ * Accepts "true"/"false" string values or boolean
31
+ */
32
+ useIntrinsicDuration: boolean;
33
+ private _stripWidth;
34
+ private _stripHeight;
35
+ private _pendingStripWidth;
36
+ private _thumbnailLayoutTask;
37
+ private set stripWidth(value);
38
+ private get stripWidth();
39
+ /**
40
+ * Run thumbnail render task directly with provided layout (bypasses task args dependency)
41
+ */
42
+ private runThumbnailRenderTask;
43
+ private resizeObserver?;
44
+ private _thumbnailUpdateInProgress;
45
+ private _pendingThumbnailUpdate;
46
+ private _videoPropertyObserver?;
47
+ updated(changedProperties: Map<string | number | symbol, unknown>): void;
48
+ /**
49
+ * Run thumbnail update with responsive debouncing (based on EFTimegroup currentTime pattern)
50
+ */
51
+ private runThumbnailUpdate;
52
+ private thumbnailLayoutTask;
53
+ /**
54
+ * Calculate layout with a ready media engine
55
+ */
56
+ private calculateLayoutWithMediaEngine;
57
+ /**
58
+ * Generate layout from calculated time range
59
+ */
60
+ private generateLayoutFromTimeRange;
61
+ private thumbnailRenderTask;
62
+ /**
63
+ * Render thumbnails with provided layout (main rendering logic)
64
+ */
65
+ private renderThumbnails;
66
+ connectedCallback(): void;
67
+ disconnectedCallback(): void;
68
+ /**
69
+ * Draw thumbnails to the canvas with cache hits and placeholders
70
+ */
71
+ private drawThumbnails;
72
+ /**
73
+ * Load missing thumbnails using MediaEngine batch extraction
74
+ */
75
+ private loadMissingThumbnails;
76
+ /**
77
+ * Convert Canvas to ImageData for caching
78
+ */
79
+ private canvasToImageData;
80
+ render(): import('lit-html').TemplateResult<1>;
81
+ }
82
+ declare global {
83
+ interface HTMLElementTagNameMap {
84
+ "ef-thumbnail-strip": EFThumbnailStrip;
85
+ }
86
+ }