@bendyline/squisq-video-react 2.3.2 → 2.3.4

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,6 +1,6 @@
1
1
  import {
2
2
  useFrameCapture
3
- } from "./chunk-YTEDBL6F.js";
3
+ } from "./chunk-UYJ7G34U.js";
4
4
  import {
5
5
  EXPORT_AUDIO_CHANNELS,
6
6
  EXPORT_AUDIO_SAMPLE_RATE,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useVideoExport
3
- } from "./chunk-U3RSDWQL.js";
3
+ } from "./chunk-DIWB5BH4.js";
4
4
 
5
5
  // src/VideoExportModal.tsx
6
6
  import { useState, useCallback, useId, useRef } from "react";
@@ -19,6 +19,7 @@ var MIME_MAP = {
19
19
  mp4: "video/mp4",
20
20
  webm: "video/webm"
21
21
  };
22
+ var VISUAL_UPDATE_FALLBACK_MS = 100;
22
23
  var CAPTURE_ASSET_TIMEOUT_MS = 15e3;
23
24
  var RENDER_TIME_EPSILON_SECONDS = 1e-6;
24
25
  var POTENTIALLY_ANIMATED_IMAGE_URL = /(?:^data:image\/(?:gif|webp|avif)[;,]|\.(?:gif|webp|avif)(?:[?#]|$))/i;
@@ -27,6 +28,33 @@ var CAPTURE_VIDEO_READINESS_POLL_MS = 16;
27
28
  var CAPTURE_VIDEO_END_PROBE_TIME = 1e101;
28
29
  var SCHEDULED_MEDIA_SELECTOR = ".doc-player__media-clips";
29
30
  var SCHEDULED_VIDEO_SELECTOR = `${SCHEDULED_MEDIA_SELECTOR} video[data-clip-id]`;
31
+ var CAPTION_OVERLAY_SELECTOR = ".caption-overlay, .social-caption-overlay";
32
+ var COVER_BLOCK_SELECTOR = ".doc-player__block--cover";
33
+ function waitForVisualUpdate(frameCount = 1) {
34
+ return new Promise((resolve) => {
35
+ let settled = false;
36
+ let animationFrame = null;
37
+ const finish = () => {
38
+ if (settled) return;
39
+ settled = true;
40
+ window.clearTimeout(fallback);
41
+ if (animationFrame !== null) window.cancelAnimationFrame(animationFrame);
42
+ resolve();
43
+ };
44
+ const fallback = window.setTimeout(
45
+ finish,
46
+ document.visibilityState === "visible" ? VISUAL_UPDATE_FALLBACK_MS : 0
47
+ );
48
+ if (document.visibilityState !== "visible") return;
49
+ const waitForFrame = (remaining) => {
50
+ animationFrame = window.requestAnimationFrame(() => {
51
+ if (remaining <= 1) finish();
52
+ else waitForFrame(remaining - 1);
53
+ });
54
+ };
55
+ waitForFrame(Math.max(1, frameCount));
56
+ });
57
+ }
30
58
  async function waitForImageDecode(image) {
31
59
  const src = image.currentSrc || image.src;
32
60
  if (!src) return;
@@ -144,6 +172,7 @@ async function primeIndeterminateCaptureVideos(captureRoot, primedVideos = /* @_
144
172
  return;
145
173
  }
146
174
  if (Number.isFinite(video.duration)) {
175
+ video.dataset.captureSequential = "true";
147
176
  primedVideos.add(video);
148
177
  return;
149
178
  }
@@ -208,6 +237,50 @@ function createInlineProvider(images) {
208
237
  }
209
238
  };
210
239
  }
240
+ function createCaptureMediaResolutionTracker(provider) {
241
+ const pending = /* @__PURE__ */ new Set();
242
+ let resolutionGeneration = 0;
243
+ let observedGeneration = 0;
244
+ const trackedProvider = {
245
+ resolveUrl(relativePath) {
246
+ resolutionGeneration += 1;
247
+ const resolution = provider.resolveUrl(relativePath);
248
+ const completion = resolution.then(
249
+ () => {
250
+ pending.delete(completion);
251
+ },
252
+ () => {
253
+ pending.delete(completion);
254
+ }
255
+ );
256
+ pending.add(completion);
257
+ return resolution;
258
+ },
259
+ listMedia: () => provider.listMedia(),
260
+ addMedia: (name, data, mimeType) => provider.addMedia(name, data, mimeType),
261
+ removeMedia: (relativePath) => provider.removeMedia(relativePath),
262
+ // The frame-capture hook retains ownership of inline providers and callers
263
+ // retain ownership of supplied providers. Context consumers must never
264
+ // dispose the wrapper and accidentally revoke the caller's live URLs.
265
+ dispose: () => void 0
266
+ };
267
+ return {
268
+ provider: trackedProvider,
269
+ async waitForSettled() {
270
+ while (pending.size > 0) {
271
+ await Promise.all([...pending]);
272
+ }
273
+ const changed = resolutionGeneration !== observedGeneration;
274
+ observedGeneration = resolutionGeneration;
275
+ return changed;
276
+ }
277
+ };
278
+ }
279
+ async function waitForCaptureMediaResolutions(tracker) {
280
+ while (await tracker?.waitForSettled()) {
281
+ await waitForVisualUpdate();
282
+ }
283
+ }
211
284
  function shouldIgnoreCaptureSibling(element, captureRoot) {
212
285
  const { head } = captureRoot.ownerDocument;
213
286
  const isInDocumentHead = element === head || head.contains(element);
@@ -581,7 +654,7 @@ function scheduledVideoIsVisual(video) {
581
654
  function scheduledVideoPresentation(video) {
582
655
  return video.closest(SCHEDULED_MEDIA_SELECTOR)?.dataset.presentation;
583
656
  }
584
- function planScheduledVideoComposite(captureRoot) {
657
+ function planScheduledVideoComposite(captureRoot, options = {}) {
585
658
  const activeVideos = Array.from(
586
659
  captureRoot.querySelectorAll(SCHEDULED_VIDEO_SELECTOR)
587
660
  ).filter(scheduledVideoIsVisual);
@@ -592,21 +665,60 @@ function planScheduledVideoComposite(captureRoot) {
592
665
  const presentation = scheduledVideoPresentation(video);
593
666
  if (presentation === "background") underlays.push(video);
594
667
  else if (presentation === "picture-in-picture") overlays.push(video);
595
- else return null;
668
+ else if (presentation === "full-frame" && options.includeFullFrameOverlays) {
669
+ overlays.push(video);
670
+ } else return null;
596
671
  }
597
672
  return { underlays, overlays };
598
673
  }
599
- function clearScheduledUnderlayBackdrops(clonedRoot) {
600
- const groups = clonedRoot.querySelectorAll(
601
- `${SCHEDULED_MEDIA_SELECTOR}[data-presentation="background"]`
602
- );
603
- for (const group of Array.from(groups)) {
604
- for (let element = group.parentElement; element; element = element === clonedRoot ? null : element.parentElement) {
674
+ function resolveScheduledVideoCompositePlan(captureRoot, options = {}) {
675
+ const plan = planScheduledVideoComposite(captureRoot, options);
676
+ if (!plan) return null;
677
+ if (captureRoot.querySelector(COVER_BLOCK_SELECTOR)) {
678
+ return { underlays: [], overlays: [] };
679
+ }
680
+ return plan;
681
+ }
682
+ function clearAncestorBackdropsFor(clonedRoot, targets) {
683
+ for (const target of targets) {
684
+ for (let element = target.parentElement; element; element = element === clonedRoot ? null : element.parentElement) {
605
685
  element.style.backgroundColor = "transparent";
606
686
  element.style.backgroundImage = "none";
607
687
  }
608
688
  }
609
689
  }
690
+ function clearScheduledUnderlayBackdrops(clonedRoot) {
691
+ clearAncestorBackdropsFor(
692
+ clonedRoot,
693
+ Array.from(
694
+ clonedRoot.querySelectorAll(
695
+ `${SCHEDULED_MEDIA_SELECTOR}[data-presentation="background"]`
696
+ )
697
+ )
698
+ );
699
+ }
700
+ function getCaptureCaptionOverlays(captureRoot) {
701
+ return Array.from(captureRoot.querySelectorAll(CAPTION_OVERLAY_SELECTOR));
702
+ }
703
+ function clearCaptionOverlayBackdrops(clonedRoot) {
704
+ clearAncestorBackdropsFor(clonedRoot, getCaptureCaptionOverlays(clonedRoot));
705
+ }
706
+ function shouldIgnoreCaptureCaptionSibling(element, captureRoot, captionOverlays) {
707
+ if (shouldIgnoreCaptureSibling(element, captureRoot)) return true;
708
+ if (element === captureRoot || element.contains(captureRoot)) return false;
709
+ if (!captureRoot.contains(element)) return false;
710
+ return !captionOverlays.some(
711
+ (overlay) => overlay === element || overlay.contains(element) || element.contains(overlay)
712
+ );
713
+ }
714
+ function getCaptionLayerStateKey(captionOverlays, ownerDocument, width, height) {
715
+ return JSON.stringify({
716
+ markup: captionOverlays.map((overlay) => overlay.outerHTML),
717
+ fontStatus: ownerDocument.fonts?.status ?? "unsupported",
718
+ width,
719
+ height
720
+ });
721
+ }
610
722
  function cssPixelValue(value) {
611
723
  const parsed = Number.parseFloat(value);
612
724
  return Number.isFinite(parsed) ? parsed : 0;
@@ -763,6 +875,7 @@ function useFrameCapture() {
763
875
  const rootRef = useRef(null);
764
876
  const renderAPIRef = useRef(null);
765
877
  const mediaProviderRef = useRef(null);
878
+ const mediaResolutionTrackerRef = useRef(null);
766
879
  const ownsMediaProviderRef = useRef(false);
767
880
  const captureCanvasRef = useRef(null);
768
881
  const captureBaseCanvasRef = useRef(null);
@@ -773,6 +886,9 @@ function useFrameCapture() {
773
886
  const captureSvgRasterCacheRef = useRef(createCaptureSvgRasterCache());
774
887
  const primedCaptureVideosRef = useRef(/* @__PURE__ */ new WeakSet());
775
888
  const dimensionsRef = useRef({ width: 1920, height: 1080 });
889
+ const captionLayerCanvasRef = useRef(null);
890
+ const captionLayerKeyRef = useRef(null);
891
+ const captionLayerHasContentRef = useRef(false);
776
892
  const init = useCallback(
777
893
  async (doc, renderOptions, captionMode) => {
778
894
  if (rootRef.current || containerRef.current || mediaProviderRef.current || captureCanvasRef.current || captureBaseCanvasRef.current) {
@@ -782,13 +898,18 @@ function useFrameCapture() {
782
898
  const oldOwnsMediaProvider = ownsMediaProviderRef.current;
783
899
  const oldCaptureCanvas = captureCanvasRef.current;
784
900
  const oldCaptureBaseCanvas = captureBaseCanvasRef.current;
901
+ const oldCaptionLayerCanvas = captionLayerCanvasRef.current;
785
902
  rootRef.current = null;
786
903
  containerRef.current = null;
787
904
  renderAPIRef.current = null;
788
905
  mediaProviderRef.current = null;
906
+ mediaResolutionTrackerRef.current = null;
789
907
  ownsMediaProviderRef.current = false;
790
908
  captureCanvasRef.current = null;
791
909
  captureBaseCanvasRef.current = null;
910
+ captionLayerCanvasRef.current = null;
911
+ captionLayerKeyRef.current = null;
912
+ captionLayerHasContentRef.current = false;
792
913
  lastVisualStateKeyRef.current = null;
793
914
  hasCapturedFrameRef.current = false;
794
915
  decodedImagesRef.current = /* @__PURE__ */ new WeakSet();
@@ -809,6 +930,10 @@ function useFrameCapture() {
809
930
  oldCaptureBaseCanvas.width = 0;
810
931
  oldCaptureBaseCanvas.height = 0;
811
932
  }
933
+ if (oldCaptionLayerCanvas) {
934
+ oldCaptionLayerCanvas.width = 0;
935
+ oldCaptionLayerCanvas.height = 0;
936
+ }
812
937
  resolve();
813
938
  }, 0);
814
939
  });
@@ -829,6 +954,8 @@ function useFrameCapture() {
829
954
  captureBaseCanvas.style.width = `${width}px`;
830
955
  captureBaseCanvas.style.height = `${height}px`;
831
956
  captureBaseCanvasRef.current = captureBaseCanvas;
957
+ captionLayerKeyRef.current = null;
958
+ captionLayerHasContentRef.current = false;
832
959
  lastVisualStateKeyRef.current = null;
833
960
  hasCapturedFrameRef.current = false;
834
961
  decodedImagesRef.current = /* @__PURE__ */ new WeakSet();
@@ -846,6 +973,8 @@ function useFrameCapture() {
846
973
  container.appendChild(renderRoot);
847
974
  const mediaProvider = renderOptions.images ? createInlineProvider(renderOptions.images) : renderOptions.mediaProvider ?? null;
848
975
  mediaProviderRef.current = mediaProvider;
976
+ const mediaResolutionTracker = mediaProvider ? createCaptureMediaResolutionTracker(mediaProvider) : null;
977
+ mediaResolutionTrackerRef.current = mediaResolutionTracker;
849
978
  ownsMediaProviderRef.current = !!renderOptions.images;
850
979
  const root = createRoot(renderRoot);
851
980
  rootRef.current = root;
@@ -880,7 +1009,13 @@ function useFrameCapture() {
880
1009
  });
881
1010
  await new Promise((resolve) => setTimeout(resolve, 0));
882
1011
  if (mediaProvider) {
883
- root.render(createElement(MediaContext.Provider, { value: mediaProvider }, playerElement));
1012
+ root.render(
1013
+ createElement(
1014
+ MediaContext.Provider,
1015
+ { value: mediaResolutionTracker.provider },
1016
+ playerElement
1017
+ )
1018
+ );
884
1019
  } else {
885
1020
  root.render(playerElement);
886
1021
  }
@@ -903,6 +1038,7 @@ function useFrameCapture() {
903
1038
  if (!(captureRoot instanceof HTMLElement)) {
904
1039
  throw new Error("Capture root element not found after player initialization.");
905
1040
  }
1041
+ await waitForCaptureMediaResolutions(mediaResolutionTrackerRef.current);
906
1042
  await waitForCaptureAssets(captureRoot, decodedImagesRef.current);
907
1043
  await primeIndeterminateCaptureVideos(captureRoot, primedCaptureVideosRef.current);
908
1044
  clearTimeout(timeout);
@@ -955,8 +1091,11 @@ function useFrameCapture() {
955
1091
  `Player committed ${renderedTime.toFixed(6)}s while capture requested ${time.toFixed(6)}s.`
956
1092
  );
957
1093
  }
1094
+ await waitForCaptureMediaResolutions(mediaResolutionTrackerRef.current);
958
1095
  await waitForCaptureAssets(root, decodedImagesRef.current);
959
- const compositePlan = planScheduledVideoComposite(root);
1096
+ const compositePlan = resolveScheduledVideoCompositePlan(root, {
1097
+ includeFullFrameOverlays: true
1098
+ });
960
1099
  const hasUnderlays = compositePlan !== null && compositePlan.underlays.length > 0;
961
1100
  const rasterMode = compositePlan ? hasUnderlays ? "base-underlay" : "base" : "full";
962
1101
  const visualStateKey = options.reuseIfUnchanged ? `${rasterMode}:${getFrameVisualStateKey(root, time, {
@@ -990,6 +1129,7 @@ function useFrameCapture() {
990
1129
  if (compositePlan) {
991
1130
  if (hasUnderlays) clearScheduledUnderlayBackdrops(clonedRoot);
992
1131
  clonedRoot.querySelectorAll(SCHEDULED_MEDIA_SELECTOR).forEach((element) => element.remove());
1132
+ clonedRoot.querySelectorAll(CAPTION_OVERLAY_SELECTOR).forEach((element) => element.remove());
993
1133
  }
994
1134
  transientCloneCanvases.push(...prepareScheduledVideoClones(root, clonedRoot));
995
1135
  await rasterizeCaptureSvgClones(
@@ -1021,6 +1161,64 @@ function useFrameCapture() {
1021
1161
  drawScheduledVideosOnto(captureCanvas, root, compositePlan.underlays);
1022
1162
  outputContext.drawImage(captureBaseCanvas, 0, 0);
1023
1163
  drawScheduledVideosOnto(captureCanvas, root, compositePlan.overlays);
1164
+ const captionOverlays = getCaptureCaptionOverlays(root);
1165
+ if (captionOverlays.length > 0) {
1166
+ const hasCaptionText = captionOverlays.some(
1167
+ (overlay) => (overlay.textContent ?? "").trim().length > 0
1168
+ );
1169
+ const captionKey = getCaptionLayerStateKey(
1170
+ captionOverlays,
1171
+ root.ownerDocument,
1172
+ width,
1173
+ height
1174
+ );
1175
+ if (captionLayerKeyRef.current !== captionKey) {
1176
+ if (hasCaptionText) {
1177
+ let captionCanvas = captionLayerCanvasRef.current;
1178
+ if (!captionCanvas) {
1179
+ captionCanvas = document.createElement("canvas");
1180
+ captionLayerCanvasRef.current = captionCanvas;
1181
+ }
1182
+ if (captionCanvas.width !== width || captionCanvas.height !== height) {
1183
+ captionCanvas.width = width;
1184
+ captionCanvas.height = height;
1185
+ }
1186
+ const captionContext = captionCanvas.getContext("2d");
1187
+ if (!captionContext) {
1188
+ throw new Error("Could not create the caption layer canvas context");
1189
+ }
1190
+ captionContext.setTransform(1, 0, 0, 1, 0, 0);
1191
+ captionContext.clearRect(0, 0, width, height);
1192
+ await html2canvas(root, {
1193
+ canvas: captionCanvas,
1194
+ width,
1195
+ height,
1196
+ scale: 1,
1197
+ useCORS: true,
1198
+ allowTaint: true,
1199
+ backgroundColor: null,
1200
+ logging: false,
1201
+ onclone: (_clonedDocument, clonedRoot) => {
1202
+ clearCaptionOverlayBackdrops(clonedRoot);
1203
+ },
1204
+ ignoreElements: (element) => shouldIgnoreCaptureCaptionSibling(element, root, captionOverlays)
1205
+ });
1206
+ }
1207
+ captionLayerKeyRef.current = captionKey;
1208
+ captionLayerHasContentRef.current = hasCaptionText;
1209
+ }
1210
+ if (captionLayerHasContentRef.current && captionLayerCanvasRef.current) {
1211
+ const overlayOpacity = Number.parseFloat(
1212
+ root.ownerDocument.defaultView?.getComputedStyle(captionOverlays[0]).opacity ?? "1"
1213
+ );
1214
+ if (!Number.isFinite(overlayOpacity) || overlayOpacity > 0) {
1215
+ outputContext.save();
1216
+ outputContext.globalAlpha = Number.isFinite(overlayOpacity) ? overlayOpacity : 1;
1217
+ outputContext.drawImage(captionLayerCanvasRef.current, 0, 0);
1218
+ outputContext.restore();
1219
+ }
1220
+ }
1221
+ }
1024
1222
  }
1025
1223
  return captureCanvas;
1026
1224
  },
@@ -1044,6 +1242,7 @@ function useFrameCapture() {
1044
1242
  }
1045
1243
  if (ownsMediaProviderRef.current) mediaProviderRef.current?.dispose();
1046
1244
  mediaProviderRef.current = null;
1245
+ mediaResolutionTrackerRef.current = null;
1047
1246
  ownsMediaProviderRef.current = false;
1048
1247
  if (captureCanvasRef.current) {
1049
1248
  captureCanvasRef.current.width = 0;
@@ -1055,6 +1254,13 @@ function useFrameCapture() {
1055
1254
  captureBaseCanvasRef.current.height = 0;
1056
1255
  captureBaseCanvasRef.current = null;
1057
1256
  }
1257
+ if (captionLayerCanvasRef.current) {
1258
+ captionLayerCanvasRef.current.width = 0;
1259
+ captionLayerCanvasRef.current.height = 0;
1260
+ captionLayerCanvasRef.current = null;
1261
+ }
1262
+ captionLayerKeyRef.current = null;
1263
+ captionLayerHasContentRef.current = false;
1058
1264
  lastVisualStateKeyRef.current = null;
1059
1265
  hasCapturedFrameRef.current = false;
1060
1266
  decodedImagesRef.current = /* @__PURE__ */ new WeakSet();
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useFrameCapture
3
- } from "./chunk-YTEDBL6F.js";
3
+ } from "./chunk-UYJ7G34U.js";
4
4
 
5
5
  // src/CoverImageExportModal.tsx
6
6
  import { useCallback, useId, useRef, useState } from "react";
@@ -1,14 +1,14 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "../chunk-CRXJOZSH.js";
4
+ } from "../chunk-J2O4TPEO.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
8
8
  validateCoverImageDimensions
9
- } from "../chunk-NTRCMTCF.js";
10
- import "../chunk-U3RSDWQL.js";
11
- import "../chunk-YTEDBL6F.js";
9
+ } from "../chunk-ZMDTR472.js";
10
+ import "../chunk-DIWB5BH4.js";
11
+ import "../chunk-UYJ7G34U.js";
12
12
  import "../chunk-32QCPFXE.js";
13
13
  import "../chunk-5MFQMJ5Z.js";
14
14
  export {
@@ -2,8 +2,8 @@ import {
2
2
  CoverImageExportModal,
3
3
  coverImageFilename,
4
4
  validateCoverImageDimensions
5
- } from "../chunk-NTRCMTCF.js";
6
- import "../chunk-YTEDBL6F.js";
5
+ } from "../chunk-ZMDTR472.js";
6
+ import "../chunk-UYJ7G34U.js";
7
7
  export {
8
8
  CoverImageExportModal,
9
9
  coverImageFilename,
@@ -3,10 +3,10 @@ import {
3
3
  resolveVideoCoverFramePlan,
4
4
  resolveVideoExportCover,
5
5
  useVideoExport
6
- } from "../chunk-U3RSDWQL.js";
6
+ } from "../chunk-DIWB5BH4.js";
7
7
  import {
8
8
  useFrameCapture
9
- } from "../chunk-YTEDBL6F.js";
9
+ } from "../chunk-UYJ7G34U.js";
10
10
  import "../chunk-32QCPFXE.js";
11
11
  import "../chunk-5MFQMJ5Z.js";
12
12
  export {
package/dist/index.js CHANGED
@@ -1,21 +1,21 @@
1
1
  import {
2
2
  VideoExportButton,
3
3
  VideoExportModal
4
- } from "./chunk-CRXJOZSH.js";
4
+ } from "./chunk-J2O4TPEO.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
8
8
  validateCoverImageDimensions
9
- } from "./chunk-NTRCMTCF.js";
9
+ } from "./chunk-ZMDTR472.js";
10
10
  import {
11
11
  DEFAULT_VIDEO_COVER_PRE_ROLL_SECONDS,
12
12
  resolveVideoCoverFramePlan,
13
13
  resolveVideoExportCover,
14
14
  useVideoExport
15
- } from "./chunk-U3RSDWQL.js";
15
+ } from "./chunk-DIWB5BH4.js";
16
16
  import {
17
17
  useFrameCapture
18
- } from "./chunk-YTEDBL6F.js";
18
+ } from "./chunk-UYJ7G34U.js";
19
19
  import {
20
20
  createEncoder,
21
21
  supportsWebCodecs,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bendyline/squisq-video-react",
3
- "version": "2.3.2",
3
+ "version": "2.3.4",
4
4
  "description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -69,9 +69,9 @@
69
69
  "react-dom": "^18.0.0 || ^19.0.0"
70
70
  },
71
71
  "dependencies": {
72
- "@bendyline/squisq": "2.7.0",
73
- "@bendyline/squisq-video": "2.2.11",
74
- "@bendyline/squisq-react": "2.7.0",
72
+ "@bendyline/squisq": "2.7.1",
73
+ "@bendyline/squisq-video": "2.2.12",
74
+ "@bendyline/squisq-react": "2.7.1",
75
75
  "@ffmpeg/core": "0.12.9",
76
76
  "@ffmpeg/ffmpeg": "0.12.15",
77
77
  "@ffmpeg/util": "0.12.2",