@editframe/elements 0.6.0-beta.16 → 0.6.0-beta.18

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 (40) hide show
  1. package/dist/lib/av/EncodedAsset.cjs +4 -1
  2. package/dist/lib/av/EncodedAsset.js +4 -1
  3. package/dist/packages/elements/src/EF_FRAMEGEN.d.ts +1 -1
  4. package/dist/packages/elements/src/elements/EFMedia.cjs +1 -1
  5. package/dist/packages/elements/src/elements/EFMedia.d.ts +1 -1
  6. package/dist/packages/elements/src/elements/EFMedia.js +1 -1
  7. package/dist/packages/elements/src/elements/EFTimegroup.cjs +38 -31
  8. package/dist/packages/elements/src/elements/EFTimegroup.d.ts +1 -1
  9. package/dist/packages/elements/src/elements/EFTimegroup.js +38 -31
  10. package/dist/packages/elements/src/gui/EFFilmstrip.cjs +128 -27
  11. package/dist/packages/elements/src/gui/EFFilmstrip.d.ts +7 -6
  12. package/dist/packages/elements/src/gui/EFFilmstrip.js +129 -28
  13. package/dist/packages/elements/src/gui/TWMixin.css.cjs +1 -1
  14. package/dist/packages/elements/src/gui/TWMixin.css.js +1 -1
  15. package/dist/packages/elements/src/index.cjs +6 -2
  16. package/dist/packages/elements/src/index.d.ts +2 -2
  17. package/dist/packages/elements/src/index.js +4 -3
  18. package/dist/style.css +21 -3
  19. package/package.json +2 -2
  20. package/src/elements/CrossUpdateController.ts +22 -0
  21. package/src/elements/EFAudio.ts +40 -0
  22. package/src/elements/EFCaptions.ts +188 -0
  23. package/src/elements/EFImage.ts +68 -0
  24. package/src/elements/EFMedia.ts +384 -0
  25. package/src/elements/EFSourceMixin.ts +57 -0
  26. package/src/elements/EFTemporal.ts +231 -0
  27. package/src/elements/EFTimegroup.browsertest.ts +333 -0
  28. package/src/elements/EFTimegroup.ts +389 -0
  29. package/src/elements/EFTimeline.ts +13 -0
  30. package/src/elements/EFVideo.ts +103 -0
  31. package/src/elements/EFWaveform.ts +409 -0
  32. package/src/elements/FetchMixin.ts +19 -0
  33. package/src/elements/TimegroupController.ts +25 -0
  34. package/src/elements/durationConverter.ts +6 -0
  35. package/src/elements/parseTimeToMs.ts +9 -0
  36. package/src/elements/util.ts +24 -0
  37. package/src/gui/EFFilmstrip.ts +878 -0
  38. package/src/gui/EFWorkbench.ts +231 -0
  39. package/src/gui/TWMixin.css +3 -0
  40. package/src/gui/TWMixin.ts +30 -0
@@ -436,7 +436,10 @@ const _VideoAsset = class _VideoAsset2 extends ISOFileAsset {
436
436
  this.removeEventListener("frame", maybeFrame);
437
437
  if (frame) {
438
438
  if (this.lastSoughtFrame && !this.decodedFrames.includes(this.lastSoughtFrame)) {
439
- this.lastSoughtFrame.close();
439
+ try {
440
+ this.lastSoughtFrame.close();
441
+ } catch (error) {
442
+ }
440
443
  }
441
444
  this.lastSoughtFrame = frame;
442
445
  }
@@ -417,7 +417,10 @@ const _VideoAsset = class _VideoAsset2 extends ISOFileAsset {
417
417
  this.removeEventListener("frame", maybeFrame);
418
418
  if (frame) {
419
419
  if (this.lastSoughtFrame && !this.decodedFrames.includes(this.lastSoughtFrame)) {
420
- this.lastSoughtFrame.close();
420
+ try {
421
+ this.lastSoughtFrame.close();
422
+ } catch (error) {
423
+ }
421
424
  }
422
425
  this.lastSoughtFrame = frame;
423
426
  }
@@ -1,4 +1,4 @@
1
- import { VideoRenderOptions } from '../../assets';
1
+ import { VideoRenderOptions } from '../../assets/src';
2
2
 
3
3
  declare global {
4
4
  interface Window {
@@ -212,7 +212,7 @@ class EFMedia extends EFSourceMixin.EFSourceMixin(EFTemporal.EFTemporal(FetchMix
212
212
  if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
213
213
  return `${this.src}/index`;
214
214
  }
215
- return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
215
+ return `/@ef-track-fragment-index/${this.src ?? ""}`;
216
216
  }
217
217
  fragmentTrackPath(trackId) {
218
218
  if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
@@ -1,7 +1,7 @@
1
1
  import { LitElement, PropertyValueMap } from 'lit';
2
2
  import { Task } from '@lit/task';
3
3
  import { MP4File } from '../../../../lib/av/MP4File';
4
- import { TrackFragmentIndex, TrackSegment } from '../../../assets';
4
+ import { TrackFragmentIndex, TrackSegment } from '../../../assets/src';
5
5
  import { VideoAsset } from '../../../../lib/av/EncodedAsset';
6
6
 
7
7
  import type * as MP4Box from "mp4box";
@@ -210,7 +210,7 @@ class EFMedia extends EFSourceMixin(EFTemporal(FetchMixin(LitElement)), {
210
210
  if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
211
211
  return `${this.src}/index`;
212
212
  }
213
- return `/@ef-track-fragment-index/${this.getAttribute("src") ?? ""}`;
213
+ return `/@ef-track-fragment-index/${this.src ?? ""}`;
214
214
  }
215
215
  fragmentTrackPath(trackId) {
216
216
  if (this.src.startsWith("editframe://") || this.src.startsWith("http")) {
@@ -25,7 +25,8 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot
25
25
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
26
26
  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);
27
27
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
28
- var _currentTime;
28
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
29
+ var _currentTime, _EFTimegroup_instances, addAudioToContext_fn;
29
30
  const shallowGetTimegroups = (element, groups = []) => {
30
31
  for (const child of Array.from(element.children)) {
31
32
  if (child instanceof exports.EFTimegroup) {
@@ -39,6 +40,7 @@ const shallowGetTimegroups = (element, groups = []) => {
39
40
  exports.EFTimegroup = class EFTimegroup extends EFTemporal.EFTemporal(lit.LitElement) {
40
41
  constructor() {
41
42
  super(...arguments);
43
+ __privateAdd(this, _EFTimegroup_instances);
42
44
  this._timeGroupContext = this;
43
45
  __privateAdd(this, _currentTime, 0);
44
46
  this.mode = "sequence";
@@ -240,42 +242,13 @@ exports.EFTimegroup = class EFTimegroup extends EFTemporal.EFTemporal(lit.LitEle
240
242
  );
241
243
  }
242
244
  async renderAudio(fromMs, toMs) {
243
- await this.waitForMediaDurations();
244
245
  const durationMs = toMs - fromMs;
245
246
  const audioContext = new OfflineAudioContext(
246
247
  2,
247
248
  Math.round(48e3 * durationMs / 1e3),
248
249
  48e3
249
250
  );
250
- await Promise.all(
251
- EFMedia.deepGetMediaElements(this).map(async (mediaElement) => {
252
- await mediaElement.trackFragmentIndexLoader.taskComplete;
253
- const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
254
- const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
255
- const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
256
- if (!mediaOverlaps || mediaElement.defaultAudioTrackId === void 0) {
257
- return;
258
- }
259
- const audio = await mediaElement.fetchAudioSpanningTime(fromMs, toMs);
260
- if (!audio) {
261
- throw new Error("Failed to fetch audio");
262
- }
263
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
264
- const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
265
- const ctxDurationMs = ctxEndMs - ctxStartMs;
266
- const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
267
- const bufferSource = audioContext.createBufferSource();
268
- bufferSource.buffer = await audioContext.decodeAudioData(
269
- await audio.blob.arrayBuffer()
270
- );
271
- bufferSource.connect(audioContext.destination);
272
- bufferSource.start(
273
- ctxStartMs / 1e3,
274
- offset / 1e3,
275
- ctxDurationMs / 1e3
276
- );
277
- })
278
- );
251
+ await __privateMethod(this, _EFTimegroup_instances, addAudioToContext_fn).call(this, audioContext, fromMs, toMs);
279
252
  return await audioContext.startRendering();
280
253
  }
281
254
  async loadMd5Sums() {
@@ -297,6 +270,40 @@ exports.EFTimegroup = class EFTimegroup extends EFTemporal.EFTemporal(lit.LitEle
297
270
  }
298
271
  };
299
272
  _currentTime = /* @__PURE__ */ new WeakMap();
273
+ _EFTimegroup_instances = /* @__PURE__ */ new WeakSet();
274
+ addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
275
+ await this.waitForMediaDurations();
276
+ const durationMs = toMs - fromMs;
277
+ await Promise.all(
278
+ EFMedia.deepGetMediaElements(this).map(async (mediaElement) => {
279
+ await mediaElement.trackFragmentIndexLoader.taskComplete;
280
+ const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
281
+ const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
282
+ const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
283
+ if (!mediaOverlaps || mediaElement.defaultAudioTrackId === void 0) {
284
+ return;
285
+ }
286
+ const audio = await mediaElement.fetchAudioSpanningTime(fromMs, toMs);
287
+ if (!audio) {
288
+ throw new Error("Failed to fetch audio");
289
+ }
290
+ const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
291
+ const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
292
+ const ctxDurationMs = ctxEndMs - ctxStartMs;
293
+ const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
294
+ const bufferSource = audioContext.createBufferSource();
295
+ bufferSource.buffer = await audioContext.decodeAudioData(
296
+ await audio.blob.arrayBuffer()
297
+ );
298
+ bufferSource.connect(audioContext.destination);
299
+ bufferSource.start(
300
+ ctxStartMs / 1e3,
301
+ offset / 1e3,
302
+ ctxDurationMs / 1e3
303
+ );
304
+ })
305
+ );
306
+ };
300
307
  exports.EFTimegroup.styles = lit.css`
301
308
  :host {
302
309
  display: block;
@@ -20,7 +20,7 @@ export declare class EFTimegroup extends EFTimegroup_base {
20
20
  get crossoverStartMs(): number;
21
21
  get crossoverEndMs(): number;
22
22
  get durationMs(): number;
23
- waitForMediaDurations(): Promise<Record<number, import('../../../assets/dist/packages/assets/src').TrackFragmentIndex>[]>;
23
+ waitForMediaDurations(): Promise<Record<number, import('../../../assets/src').TrackFragmentIndex>[]>;
24
24
  get childTemporals(): import('./EFTemporal').TemporalMixinInterface[];
25
25
  protected updated(changedProperties: PropertyValueMap<any> | Map<PropertyKey, unknown>): void;
26
26
  shouldWrapWithWorkbench(): boolean;
@@ -23,7 +23,8 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot
23
23
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), getter ? getter.call(obj) : member.get(obj));
24
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
25
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
26
- var _currentTime;
26
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
27
+ var _currentTime, _EFTimegroup_instances, addAudioToContext_fn;
27
28
  const shallowGetTimegroups = (element, groups = []) => {
28
29
  for (const child of Array.from(element.children)) {
29
30
  if (child instanceof EFTimegroup) {
@@ -37,6 +38,7 @@ const shallowGetTimegroups = (element, groups = []) => {
37
38
  let EFTimegroup = class extends EFTemporal(LitElement) {
38
39
  constructor() {
39
40
  super(...arguments);
41
+ __privateAdd(this, _EFTimegroup_instances);
40
42
  this._timeGroupContext = this;
41
43
  __privateAdd(this, _currentTime, 0);
42
44
  this.mode = "sequence";
@@ -238,42 +240,13 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
238
240
  );
239
241
  }
240
242
  async renderAudio(fromMs, toMs) {
241
- await this.waitForMediaDurations();
242
243
  const durationMs = toMs - fromMs;
243
244
  const audioContext = new OfflineAudioContext(
244
245
  2,
245
246
  Math.round(48e3 * durationMs / 1e3),
246
247
  48e3
247
248
  );
248
- await Promise.all(
249
- deepGetMediaElements(this).map(async (mediaElement) => {
250
- await mediaElement.trackFragmentIndexLoader.taskComplete;
251
- const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
252
- const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
253
- const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
254
- if (!mediaOverlaps || mediaElement.defaultAudioTrackId === void 0) {
255
- return;
256
- }
257
- const audio = await mediaElement.fetchAudioSpanningTime(fromMs, toMs);
258
- if (!audio) {
259
- throw new Error("Failed to fetch audio");
260
- }
261
- const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
262
- const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
263
- const ctxDurationMs = ctxEndMs - ctxStartMs;
264
- const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
265
- const bufferSource = audioContext.createBufferSource();
266
- bufferSource.buffer = await audioContext.decodeAudioData(
267
- await audio.blob.arrayBuffer()
268
- );
269
- bufferSource.connect(audioContext.destination);
270
- bufferSource.start(
271
- ctxStartMs / 1e3,
272
- offset / 1e3,
273
- ctxDurationMs / 1e3
274
- );
275
- })
276
- );
249
+ await __privateMethod(this, _EFTimegroup_instances, addAudioToContext_fn).call(this, audioContext, fromMs, toMs);
277
250
  return await audioContext.startRendering();
278
251
  }
279
252
  async loadMd5Sums() {
@@ -295,6 +268,40 @@ let EFTimegroup = class extends EFTemporal(LitElement) {
295
268
  }
296
269
  };
297
270
  _currentTime = /* @__PURE__ */ new WeakMap();
271
+ _EFTimegroup_instances = /* @__PURE__ */ new WeakSet();
272
+ addAudioToContext_fn = async function(audioContext, fromMs, toMs) {
273
+ await this.waitForMediaDurations();
274
+ const durationMs = toMs - fromMs;
275
+ await Promise.all(
276
+ deepGetMediaElements(this).map(async (mediaElement) => {
277
+ await mediaElement.trackFragmentIndexLoader.taskComplete;
278
+ const mediaStartsBeforeEnd = mediaElement.startTimeMs <= toMs;
279
+ const mediaEndsAfterStart = mediaElement.endTimeMs >= fromMs;
280
+ const mediaOverlaps = mediaStartsBeforeEnd && mediaEndsAfterStart;
281
+ if (!mediaOverlaps || mediaElement.defaultAudioTrackId === void 0) {
282
+ return;
283
+ }
284
+ const audio = await mediaElement.fetchAudioSpanningTime(fromMs, toMs);
285
+ if (!audio) {
286
+ throw new Error("Failed to fetch audio");
287
+ }
288
+ const ctxStartMs = Math.max(0, mediaElement.startTimeMs - fromMs);
289
+ const ctxEndMs = Math.min(durationMs, mediaElement.endTimeMs - fromMs);
290
+ const ctxDurationMs = ctxEndMs - ctxStartMs;
291
+ const offset = Math.max(0, fromMs - mediaElement.startTimeMs) - audio.startMs;
292
+ const bufferSource = audioContext.createBufferSource();
293
+ bufferSource.buffer = await audioContext.decodeAudioData(
294
+ await audio.blob.arrayBuffer()
295
+ );
296
+ bufferSource.connect(audioContext.destination);
297
+ bufferSource.start(
298
+ ctxStartMs / 1e3,
299
+ offset / 1e3,
300
+ ctxDurationMs / 1e3
301
+ );
302
+ })
303
+ );
304
+ };
298
305
  EFTimegroup.styles = css`
299
306
  :host {
300
307
  display: block;
@@ -32,7 +32,8 @@ var __accessCheck = (obj, member, msg) => member.has(obj) || __typeError("Cannot
32
32
  var __privateGet = (obj, member, getter) => (__accessCheck(obj, member, "read from private field"), member.get(obj));
33
33
  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);
34
34
  var __privateSet = (obj, member, value, setter) => (__accessCheck(obj, member, "write to private field"), member.set(obj, value), value);
35
- var _lastTick;
35
+ var __privateMethod = (obj, member, method) => (__accessCheck(obj, member, "access private method"), method);
36
+ var _handleKeyPress, _lastTick, _playbackAudioContext, _playbackAnimationFrameRequest, _AUDIO_PLAYBACK_SLICE_MS, _EFFilmstrip_instances, syncPlayheadToAudioContext_fn, stopPlayback_fn, startPlayback_fn;
36
37
  class ElementFilmstripController {
37
38
  constructor(host, filmstrip) {
38
39
  this.host = host;
@@ -94,7 +95,7 @@ class FilmstripItem extends TWMixin.TWMixin(lit.LitElement) {
94
95
  }
95
96
  }}
96
97
  ?data-focused=${this.isFocused}
97
- class="border-outset relative mb-[1px] block h-[1.25rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
98
+ class="border-outset relative mb-[1px] block h-[1.1rem] text-nowrap border border-slate-500 bg-blue-200 text-sm data-[focused]:bg-slate-400"
98
99
  >
99
100
  ${this.animations()}
100
101
  </div>
@@ -102,7 +103,7 @@ class FilmstripItem extends TWMixin.TWMixin(lit.LitElement) {
102
103
  </div>`;
103
104
  }
104
105
  renderChildren() {
105
- return renderFilmStripChildren(
106
+ return renderFilmstripChildren(
106
107
  Array.from(this.element.children),
107
108
  this.pixelsPerMs
108
109
  );
@@ -221,7 +222,7 @@ exports.EFTimegroupFilmstrip = class EFTimegroupFilmstrip extends FilmstripItem
221
222
  contents() {
222
223
  return lit.html`
223
224
  <span>TIME GROUP</span>
224
- ${renderFilmStripChildren(
225
+ ${renderFilmstripChildren(
225
226
  Array.from(this.element.children || []),
226
227
  this.pixelsPerMs
227
228
  )}
@@ -236,7 +237,7 @@ exports.EFHTMLFilmstrip = class EFHTMLFilmstrip extends FilmstripItem {
236
237
  contents() {
237
238
  return lit.html`
238
239
  <span>${this.element.tagName}</span>
239
- ${renderFilmStripChildren(
240
+ ${renderFilmstripChildren(
240
241
  Array.from(this.element.children || []),
241
242
  this.pixelsPerMs
242
243
  )}
@@ -266,8 +267,8 @@ let EFHierarchyItem = class extends TWMixin.TWMixin(lit.LitElement) {
266
267
  <div
267
268
  ?data-focused=${this.isFocused}
268
269
  class="peer
269
- flex h-[1.25rem] items-center overflow-hidden text-nowrap border border-slate-500
270
- bg-slate-200 pl-2 text-sm hover:bg-slate-400 data-[focused]:bg-slate-400"
270
+ flex h-[1.1rem] items-center overflow-hidden text-nowrap border border-slate-500
271
+ bg-slate-200 pl-2 text-xs font-mono hover:bg-slate-400 data-[focused]:bg-slate-400"
271
272
  @mouseenter=${() => {
272
273
  if (this.focusContext) {
273
274
  this.focusContext.focusedElement = this.element;
@@ -320,7 +321,7 @@ let EFAudioHierarchyItem = class extends EFHierarchyItem {
320
321
  return "🔊";
321
322
  }
322
323
  displayLabel() {
323
- return this.element.getAttribute("src") ?? "(no src)";
324
+ return this.element.src ?? "(no src)";
324
325
  }
325
326
  };
326
327
  EFAudioHierarchyItem = __decorateClass([
@@ -331,7 +332,7 @@ let EFVideoHierarchyItem = class extends EFHierarchyItem {
331
332
  return "📼";
332
333
  }
333
334
  displayLabel() {
334
- return this.element.getAttribute("src") ?? "(no src)";
335
+ return this.element.src ?? "(no src)";
335
336
  }
336
337
  };
337
338
  EFVideoHierarchyItem = __decorateClass([
@@ -369,7 +370,7 @@ let EFImageHierarchyItem = class extends EFHierarchyItem {
369
370
  return "🖼️";
370
371
  }
371
372
  displayLabel() {
372
- return this.element.getAttribute("src") ?? "(no src)";
373
+ return this.element.src ?? "(no src)";
373
374
  }
374
375
  };
375
376
  EFImageHierarchyItem = __decorateClass([
@@ -425,7 +426,7 @@ const renderHierarchyChildren = (children) => {
425
426
  ></ef-html-hierarchy-item>`;
426
427
  });
427
428
  };
428
- const renderFilmStripChildren = (children, pixelsPerMs) => {
429
+ const renderFilmstripChildren = (children, pixelsPerMs) => {
429
430
  return children.map((child) => {
430
431
  if (child instanceof EFTimegroup.EFTimegroup) {
431
432
  return lit.html`<ef-timegroup-filmstrip
@@ -470,16 +471,33 @@ const renderFilmStripChildren = (children, pixelsPerMs) => {
470
471
  ></ef-html-filmstrip>`;
471
472
  });
472
473
  };
473
- exports.EFFilmStrip = class EFFilmStrip extends TWMixin.TWMixin(lit.LitElement) {
474
+ exports.EFFilmstrip = class EFFilmstrip extends TWMixin.TWMixin(lit.LitElement) {
474
475
  constructor() {
475
476
  super(...arguments);
477
+ __privateAdd(this, _EFFilmstrip_instances);
476
478
  this.pixelsPerMs = 0.04;
477
479
  this.currentTimeMs = 0;
478
480
  this.targetSelector = "";
479
481
  this.scrubbing = false;
480
482
  this.playing = false;
481
483
  this.timelineScrolltop = 0;
484
+ __privateAdd(this, _handleKeyPress, (event) => {
485
+ if (event.key === " ") {
486
+ const interactiveSelector = "input, textarea, button, select, a, [contenteditable]";
487
+ const closestInteractive = event.target?.closest(
488
+ interactiveSelector
489
+ );
490
+ if (closestInteractive) {
491
+ return;
492
+ }
493
+ event.preventDefault();
494
+ this.playing = !this.playing;
495
+ }
496
+ });
482
497
  __privateAdd(this, _lastTick);
498
+ __privateAdd(this, _playbackAudioContext, null);
499
+ __privateAdd(this, _playbackAnimationFrameRequest, null);
500
+ __privateAdd(this, _AUDIO_PLAYBACK_SLICE_MS, 1e3);
483
501
  this.advancePlayhead = (tick) => {
484
502
  if (__privateGet(this, _lastTick) && tick && this.target) {
485
503
  this.target.currentTimeMs += tick - __privateGet(this, _lastTick);
@@ -503,6 +521,11 @@ exports.EFFilmStrip = class EFFilmStrip extends TWMixin.TWMixin(lit.LitElement)
503
521
  this.timegroupController = new TimegroupController.TimegroupController(target, this);
504
522
  this.currentTimeMs = target.currentTimeMs;
505
523
  }
524
+ window.addEventListener("keypress", __privateGet(this, _handleKeyPress));
525
+ }
526
+ disconnectedCallback() {
527
+ super.disconnectedCallback();
528
+ window.removeEventListener("keypress", __privateGet(this, _handleKeyPress));
506
529
  }
507
530
  syncGutterScroll() {
508
531
  if (this.gutter && this.hierarchyRef.value) {
@@ -643,7 +666,7 @@ exports.EFFilmStrip = class EFFilmStrip extends TWMixin.TWMixin(lit.LitElement)
643
666
  ${ref_js.ref(this.playheadRef)}
644
667
  ></div>
645
668
 
646
- ${renderFilmStripChildren(
669
+ ${renderFilmstripChildren(
647
670
  Array.from(target?.children || []),
648
671
  this.pixelsPerMs
649
672
  )}
@@ -654,7 +677,9 @@ exports.EFFilmStrip = class EFFilmStrip extends TWMixin.TWMixin(lit.LitElement)
654
677
  update(changedProperties) {
655
678
  if (changedProperties.has("playing")) {
656
679
  if (this.playing) {
657
- this.advancePlayhead(0);
680
+ __privateMethod(this, _EFFilmstrip_instances, startPlayback_fn).call(this);
681
+ } else {
682
+ __privateMethod(this, _EFFilmstrip_instances, stopPlayback_fn).call(this);
658
683
  }
659
684
  }
660
685
  super.update(changedProperties);
@@ -680,40 +705,116 @@ exports.EFFilmStrip = class EFFilmStrip extends TWMixin.TWMixin(lit.LitElement)
680
705
  return void 0;
681
706
  }
682
707
  };
708
+ _handleKeyPress = /* @__PURE__ */ new WeakMap();
683
709
  _lastTick = /* @__PURE__ */ new WeakMap();
710
+ _playbackAudioContext = /* @__PURE__ */ new WeakMap();
711
+ _playbackAnimationFrameRequest = /* @__PURE__ */ new WeakMap();
712
+ _AUDIO_PLAYBACK_SLICE_MS = /* @__PURE__ */ new WeakMap();
713
+ _EFFilmstrip_instances = /* @__PURE__ */ new WeakSet();
714
+ syncPlayheadToAudioContext_fn = function(target, startMs) {
715
+ target.currentTimeMs = startMs + (__privateGet(this, _playbackAudioContext)?.currentTime ?? 0) * 1e3;
716
+ __privateSet(this, _playbackAnimationFrameRequest, requestAnimationFrame(() => {
717
+ __privateMethod(this, _EFFilmstrip_instances, syncPlayheadToAudioContext_fn).call(this, target, startMs);
718
+ }));
719
+ };
720
+ stopPlayback_fn = async function() {
721
+ if (__privateGet(this, _playbackAudioContext)) {
722
+ if (__privateGet(this, _playbackAudioContext).state !== "closed") {
723
+ await __privateGet(this, _playbackAudioContext).close();
724
+ }
725
+ }
726
+ if (__privateGet(this, _playbackAnimationFrameRequest)) {
727
+ cancelAnimationFrame(__privateGet(this, _playbackAnimationFrameRequest));
728
+ }
729
+ __privateSet(this, _playbackAudioContext, null);
730
+ };
731
+ startPlayback_fn = async function() {
732
+ await __privateMethod(this, _EFFilmstrip_instances, stopPlayback_fn).call(this);
733
+ const timegroup = this.target;
734
+ if (!timegroup) {
735
+ return;
736
+ }
737
+ let currentMs = timegroup.currentTimeMs;
738
+ let bufferCount = 0;
739
+ __privateSet(this, _playbackAudioContext, new AudioContext({
740
+ latencyHint: "playback"
741
+ }));
742
+ if (__privateGet(this, _playbackAnimationFrameRequest)) {
743
+ cancelAnimationFrame(__privateGet(this, _playbackAnimationFrameRequest));
744
+ }
745
+ __privateMethod(this, _EFFilmstrip_instances, syncPlayheadToAudioContext_fn).call(this, timegroup, currentMs);
746
+ const playbackContext = __privateGet(this, _playbackAudioContext);
747
+ await playbackContext.suspend();
748
+ const fillBuffer = async () => {
749
+ if (bufferCount > 1) {
750
+ return;
751
+ }
752
+ const canFillBuffer = await queueBufferSource();
753
+ if (canFillBuffer) {
754
+ fillBuffer();
755
+ }
756
+ };
757
+ const fromMs = currentMs;
758
+ const toMs = timegroup.endTimeMs;
759
+ const queueBufferSource = async () => {
760
+ if (currentMs >= toMs) {
761
+ return false;
762
+ }
763
+ const startMs = currentMs;
764
+ const endMs = currentMs + __privateGet(this, _AUDIO_PLAYBACK_SLICE_MS);
765
+ currentMs += __privateGet(this, _AUDIO_PLAYBACK_SLICE_MS);
766
+ const audioBuffer = await timegroup.renderAudio(startMs, endMs);
767
+ bufferCount++;
768
+ const source = playbackContext.createBufferSource();
769
+ source.buffer = audioBuffer;
770
+ source.connect(playbackContext.destination);
771
+ source.start((startMs - fromMs) / 1e3);
772
+ source.onended = () => {
773
+ bufferCount--;
774
+ if (endMs >= toMs) {
775
+ this.playing = false;
776
+ } else {
777
+ fillBuffer();
778
+ }
779
+ };
780
+ return true;
781
+ };
782
+ await fillBuffer();
783
+ await playbackContext.resume();
784
+ };
684
785
  __decorateClass([
685
786
  decorators_js.property({ type: Number })
686
- ], exports.EFFilmStrip.prototype, "pixelsPerMs", 2);
787
+ ], exports.EFFilmstrip.prototype, "pixelsPerMs", 2);
687
788
  __decorateClass([
688
789
  decorators_js.property({ type: Number })
689
- ], exports.EFFilmStrip.prototype, "currentTimeMs", 2);
790
+ ], exports.EFFilmstrip.prototype, "currentTimeMs", 2);
690
791
  __decorateClass([
691
792
  decorators_js.property({ type: String, attribute: "target" })
692
- ], exports.EFFilmStrip.prototype, "targetSelector", 2);
793
+ ], exports.EFFilmstrip.prototype, "targetSelector", 2);
693
794
  __decorateClass([
694
795
  decorators_js.state()
695
- ], exports.EFFilmStrip.prototype, "scrubbing", 2);
796
+ ], exports.EFFilmstrip.prototype, "scrubbing", 2);
696
797
  __decorateClass([
697
798
  decorators_js.state()
698
- ], exports.EFFilmStrip.prototype, "playing", 2);
799
+ ], exports.EFFilmstrip.prototype, "playing", 2);
699
800
  __decorateClass([
700
801
  decorators_js.state()
701
- ], exports.EFFilmStrip.prototype, "timelineScrolltop", 2);
802
+ ], exports.EFFilmstrip.prototype, "timelineScrolltop", 2);
702
803
  __decorateClass([
703
804
  decorators_js.eventOptions({ passive: false })
704
- ], exports.EFFilmStrip.prototype, "syncGutterScroll", 1);
805
+ ], exports.EFFilmstrip.prototype, "syncGutterScroll", 1);
705
806
  __decorateClass([
706
807
  decorators_js.eventOptions({ passive: false })
707
- ], exports.EFFilmStrip.prototype, "syncHierarchyScroll", 1);
808
+ ], exports.EFFilmstrip.prototype, "syncHierarchyScroll", 1);
708
809
  __decorateClass([
709
810
  decorators_js.eventOptions({ capture: false })
710
- ], exports.EFFilmStrip.prototype, "scrub", 1);
811
+ ], exports.EFFilmstrip.prototype, "scrub", 1);
711
812
  __decorateClass([
712
813
  decorators_js.eventOptions({ capture: false })
713
- ], exports.EFFilmStrip.prototype, "startScrub", 1);
814
+ ], exports.EFFilmstrip.prototype, "startScrub", 1);
714
815
  __decorateClass([
715
816
  decorators_js.eventOptions({ passive: false })
716
- ], exports.EFFilmStrip.prototype, "scrollScrub", 1);
717
- exports.EFFilmStrip = __decorateClass([
817
+ ], exports.EFFilmstrip.prototype, "scrollScrub", 1);
818
+ exports.EFFilmstrip = __decorateClass([
718
819
  decorators_js.customElement("ef-filmstrip")
719
- ], exports.EFFilmStrip);
820
+ ], exports.EFFilmstrip);
@@ -71,11 +71,11 @@ declare class EFTimegroupHierarchyItem extends EFHierarchyItem<EFTimegroup> {
71
71
  }
72
72
  declare class EFAudioHierarchyItem extends EFHierarchyItem {
73
73
  get icon(): string;
74
- displayLabel(): string;
74
+ displayLabel(): any;
75
75
  }
76
76
  declare class EFVideoHierarchyItem extends EFHierarchyItem {
77
77
  get icon(): string;
78
- displayLabel(): string;
78
+ displayLabel(): any;
79
79
  }
80
80
  declare class EFCaptionsHierarchyItem extends EFHierarchyItem {
81
81
  get icon(): string;
@@ -89,13 +89,13 @@ declare class EFWaveformHierarchyItem extends EFHierarchyItem {
89
89
  }
90
90
  declare class EFImageHierarchyItem extends EFHierarchyItem {
91
91
  get icon(): string;
92
- displayLabel(): string;
92
+ displayLabel(): any;
93
93
  }
94
94
  declare class EFHTMLHierarchyItem extends EFHierarchyItem {
95
95
  get icon(): TemplateResult<1>;
96
96
  }
97
- declare const EFFilmStrip_base: typeof LitElement;
98
- export declare class EFFilmStrip extends EFFilmStrip_base {
97
+ declare const EFFilmstrip_base: typeof LitElement;
98
+ export declare class EFFilmstrip extends EFFilmstrip_base {
99
99
  #private;
100
100
  pixelsPerMs: number;
101
101
  currentTimeMs: number;
@@ -105,6 +105,7 @@ export declare class EFFilmStrip extends EFFilmStrip_base {
105
105
  timelineScrolltop: number;
106
106
  timegroupController?: TimegroupController;
107
107
  connectedCallback(): void;
108
+ disconnectedCallback(): void;
108
109
  syncGutterScroll(): void;
109
110
  syncHierarchyScroll(): void;
110
111
  advancePlayhead: (tick?: DOMHighResTimeStamp) => void;
@@ -122,7 +123,7 @@ export declare class EFFilmStrip extends EFFilmStrip_base {
122
123
  }
123
124
  declare global {
124
125
  interface HTMLElementTagNameMap {
125
- "ef-filmstrip": EFFilmStrip;
126
+ "ef-filmstrip": EFFilmstrip;
126
127
  "ef-timegroup-hierarchy-item": EFTimegroupHierarchyItem;
127
128
  "ef-audio-hierarchy-item": EFAudioHierarchyItem;
128
129
  "ef-video-hierarchy-item": EFVideoHierarchyItem;