pixi-effects 0.34.0 → 0.35.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.
@@ -1,4 +1,4 @@
1
- import { l as Movie } from './Movie-f0tQG9y5.js';
1
+ import { l as Movie } from './Movie-VYtUN4TS.js';
2
2
  import 'pixi.js';
3
3
  import './types-CavnUvd5.js';
4
4
 
@@ -36,6 +36,12 @@ interface ControllerOptions {
36
36
  pauseAtStops?: boolean;
37
37
  /** The loop button (and the L key) next to the volume: it flips `movie.loop`, so playing goes back to the start at the end. Default true; `false` leaves the loop to the page. */
38
38
  loopButton?: boolean;
39
+ /**
40
+ * A small picture of the frame under the pointer, above the seek bar, with its time (like a video site). Default false. It is made with `movie.thumbnail()` without
41
+ * moving the playhead or changing the picture, also while the movie plays. It switches itself off for a movie with video footage or variables (`movie.thumbnailsOff`
42
+ * says why), where a frame cannot be drawn on the side.
43
+ */
44
+ thumbnails?: boolean;
39
45
  className?: string;
40
46
  }
41
47
  interface ResolvedOptions {
@@ -46,6 +52,7 @@ interface ResolvedOptions {
46
52
  presentButton: boolean;
47
53
  pauseAtStops: boolean;
48
54
  loopButton: boolean;
55
+ thumbnails: boolean;
49
56
  className: string;
50
57
  }
51
58
  declare class Controller {
@@ -93,6 +100,17 @@ declare class Controller {
93
100
  private onReady;
94
101
  /** A rebuild (setVars, resize, rebuild) may have moved the stops: draw their marks again. */
95
102
  private onRebuilt;
103
+ private previewEl;
104
+ private previewCanvas;
105
+ private previewTimeEl;
106
+ /** Pictures already made, by frame bucket (a frame is a pure function of time; cleared on a rebuild). */
107
+ private previewCache;
108
+ private previewWanted;
109
+ private previewRunning;
110
+ private previewRetry;
111
+ private previewRetries;
112
+ private previewCleanup;
113
+ private static readonly PREVIEW_WIDTH_PX;
96
114
  private presenter;
97
115
  /** `pixi-effects/present`, loaded as soon as the movie has stops (a press of play must not wait for it). */
98
116
  private present;
@@ -165,6 +183,10 @@ declare class Controller {
165
183
  private refreshProgress;
166
184
  private refreshTime;
167
185
  private bindScrubbing;
186
+ /** The hover preview above the seek bar (`thumbnails: true`). */
187
+ private bindPreview;
188
+ /** Draws the picture of the wanted frame into the preview; one at a time, and the last wish wins. */
189
+ private runPreview;
168
190
  private seekFromPointer;
169
191
  private bindExportPopover;
170
192
  private toggleSettings;
@@ -61,6 +61,19 @@ var CONTROLLER_CSS = `
61
61
  .mc-progress:hover .mc-progress-thumb,
62
62
  .mc-progress.mc-scrubbing .mc-progress-thumb { transform: translate(-50%, -50%) scale(1); }
63
63
 
64
+ .movie-controller > .mc-preview {
65
+ position: absolute; bottom: 74px; left: 0;
66
+ transform: translateX(-50%);
67
+ display: flex; flex-direction: column; align-items: center; gap: 4px;
68
+ padding: 3px; border-radius: 4px;
69
+ background: rgba(0,0,0,0.75);
70
+ pointer-events: none;
71
+ opacity: 0; transition: opacity 100ms ease;
72
+ }
73
+ .mc-preview[data-show="1"] { opacity: 1; }
74
+ .mc-preview canvas { display: block; width: 160px; height: auto; border-radius: 2px; }
75
+ .mc-preview-time { font-size: 12px; line-height: 1; color: var(--mc-fg, #fff); font-variant-numeric: tabular-nums; }
76
+
64
77
  .mc-bar {
65
78
  position: relative;
66
79
  display: flex;
@@ -293,7 +306,7 @@ var THEME_PROPERTIES = {
293
306
  trackHeight: "--mc-track-height",
294
307
  font: "--mc-font"
295
308
  };
296
- var CONTROLLER_OPTION_KEYS = ["canvas", "theme", "exportButton", "keyboard", "clickToPlay", "presentButton", "pauseAtStops", "loopButton", "className"];
309
+ var CONTROLLER_OPTION_KEYS = ["canvas", "theme", "exportButton", "keyboard", "clickToPlay", "presentButton", "pauseAtStops", "loopButton", "thumbnails", "className"];
297
310
  var Controller = class _Controller {
298
311
  movie;
299
312
  options;
@@ -339,6 +352,17 @@ var Controller = class _Controller {
339
352
  onReady = null;
340
353
  /** A rebuild (setVars, resize, rebuild) may have moved the stops: draw their marks again. */
341
354
  onRebuilt = null;
355
+ previewEl = null;
356
+ previewCanvas = null;
357
+ previewTimeEl = null;
358
+ /** Pictures already made, by frame bucket (a frame is a pure function of time; cleared on a rebuild). */
359
+ previewCache = /* @__PURE__ */ new Map();
360
+ previewWanted = null;
361
+ previewRunning = false;
362
+ previewRetry = null;
363
+ previewRetries = 0;
364
+ previewCleanup = null;
365
+ static PREVIEW_WIDTH_PX = 160;
342
366
  presenter = null;
343
367
  /** `pixi-effects/present`, loaded as soon as the movie has stops (a press of play must not wait for it). */
344
368
  present = null;
@@ -385,6 +409,7 @@ var Controller = class _Controller {
385
409
  presentButton: options.presentButton ?? true,
386
410
  pauseAtStops: options.pauseAtStops ?? true,
387
411
  loopButton: options.loopButton ?? true,
412
+ thumbnails: options.thumbnails ?? false,
388
413
  className: options.className ?? "movie-controller"
389
414
  };
390
415
  installStyles();
@@ -407,6 +432,7 @@ var Controller = class _Controller {
407
432
  this.bindPlayButton();
408
433
  if (this.options.clickToPlay) this.bindCanvasClick();
409
434
  this.bindScrubbing();
435
+ if (this.options.thumbnails) this.bindPreview();
410
436
  this.bindMuteButton();
411
437
  this.bindLoopButton();
412
438
  this.bindVolumeSlider();
@@ -586,6 +612,7 @@ var Controller = class _Controller {
586
612
  };
587
613
  this.onRebuilt = () => {
588
614
  this.refreshStops();
615
+ this.previewCache.clear();
589
616
  };
590
617
  this.movie.on("ready", this.onReady);
591
618
  this.movie.on("rebuilt", this.onRebuilt);
@@ -909,6 +936,104 @@ var Controller = class _Controller {
909
936
  };
910
937
  window.addEventListener("blur", this.windowBlurHandler);
911
938
  }
939
+ /** The hover preview above the seek bar (`thumbnails: true`). */
940
+ bindPreview() {
941
+ const el = document.createElement("div");
942
+ el.className = "mc-preview";
943
+ el.setAttribute("aria-hidden", "true");
944
+ const canvas = document.createElement("canvas");
945
+ const time = document.createElement("span");
946
+ time.className = "mc-preview-time";
947
+ el.append(canvas, time);
948
+ this.root.appendChild(el);
949
+ this.previewEl = el;
950
+ this.previewCanvas = canvas;
951
+ this.previewTimeEl = time;
952
+ const hide = () => {
953
+ this.previewWanted = null;
954
+ el.removeAttribute("data-show");
955
+ };
956
+ const onMove = (e) => {
957
+ if (this.isScrubbing || this.movie.thumbnailsOff || e.pointerType === "touch") {
958
+ hide();
959
+ return;
960
+ }
961
+ const rect = this.progressEl.getBoundingClientRect();
962
+ const inset = _Controller.PROGRESS_INSET_PX;
963
+ const frame = pxToFrame(e.clientX, { left: rect.left + inset, width: Math.max(0, rect.width - 2 * inset) }, this.movie.totalFrames);
964
+ const rootRect = this.root.getBoundingClientRect();
965
+ const half = el.getBoundingClientRect().width / 2 + 2;
966
+ const x = Math.min(Math.max(e.clientX - rootRect.left, half), Math.max(half, rootRect.width - half));
967
+ el.style.left = `${rootRect.width > 0 ? x / rootRect.width * 100 : 50}%`;
968
+ this.previewRetries = 0;
969
+ time.textContent = formatTime(frame / (this.movie.frameRate || 1));
970
+ this.previewWanted = frame;
971
+ void this.runPreview();
972
+ };
973
+ this.progressEl.addEventListener("pointermove", onMove);
974
+ this.progressEl.addEventListener("pointerleave", hide);
975
+ this.progressEl.addEventListener("pointerdown", hide);
976
+ this.previewCleanup = () => {
977
+ this.progressEl.removeEventListener("pointermove", onMove);
978
+ this.progressEl.removeEventListener("pointerleave", hide);
979
+ this.progressEl.removeEventListener("pointerdown", hide);
980
+ if (this.previewRetry) {
981
+ clearTimeout(this.previewRetry);
982
+ this.previewRetry = null;
983
+ }
984
+ el.remove();
985
+ };
986
+ }
987
+ /** Draws the picture of the wanted frame into the preview; one at a time, and the last wish wins. */
988
+ async runPreview() {
989
+ if (this.previewRunning) return;
990
+ this.previewRunning = true;
991
+ try {
992
+ while (this.previewWanted !== null && !this.destroyed) {
993
+ const wanted = this.previewWanted;
994
+ const fps = this.movie.frameRate || 30;
995
+ const step = Math.max(1, Math.round(fps / 10));
996
+ const key = Math.min(this.movie.totalFrames, Math.round(wanted / step) * step);
997
+ let pic = this.previewCache.get(key) ?? null;
998
+ if (!pic) {
999
+ try {
1000
+ pic = await this.movie.thumbnail(key, { width: _Controller.PREVIEW_WIDTH_PX });
1001
+ } catch {
1002
+ this.previewWanted = null;
1003
+ this.previewEl?.removeAttribute("data-show");
1004
+ break;
1005
+ }
1006
+ if (pic) {
1007
+ if (this.previewCache.size >= 120) this.previewCache.delete(this.previewCache.keys().next().value);
1008
+ this.previewCache.set(key, pic);
1009
+ }
1010
+ }
1011
+ if (this.previewWanted === null || this.destroyed) break;
1012
+ if (pic) {
1013
+ const c = this.previewCanvas;
1014
+ if (c.width !== pic.width || c.height !== pic.height) {
1015
+ c.width = pic.width;
1016
+ c.height = pic.height;
1017
+ }
1018
+ c.getContext("2d").drawImage(pic, 0, 0);
1019
+ this.previewEl.setAttribute("data-show", "1");
1020
+ if (this.previewWanted === wanted) this.previewWanted = null;
1021
+ } else if (this.previewWanted !== wanted) {
1022
+ } else if (!this.movie.thumbnailsOff && this.previewRetries++ < 10) {
1023
+ this.previewRetry = setTimeout(() => {
1024
+ this.previewRetry = null;
1025
+ void this.runPreview();
1026
+ }, 120);
1027
+ break;
1028
+ } else {
1029
+ this.previewWanted = null;
1030
+ this.previewEl?.removeAttribute("data-show");
1031
+ }
1032
+ }
1033
+ } finally {
1034
+ this.previewRunning = false;
1035
+ }
1036
+ }
912
1037
  seekFromPointer(clientX) {
913
1038
  const rect = this.progressEl.getBoundingClientRect();
914
1039
  const inset = _Controller.PROGRESS_INSET_PX;
@@ -1185,6 +1310,10 @@ var Controller = class _Controller {
1185
1310
  if (this.destroyed) return;
1186
1311
  this.destroyed = true;
1187
1312
  this.stopPresenting();
1313
+ if (this.previewCleanup) {
1314
+ this.previewCleanup();
1315
+ this.previewCleanup = null;
1316
+ }
1188
1317
  if (this.onReady) {
1189
1318
  this.movie.off("ready", this.onReady);
1190
1319
  this.onReady = null;