@bendyline/squisq-video-react 2.3.3 → 2.3.5

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-I5PNMCSZ.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-UXTYD6U5.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;
@@ -29,6 +30,31 @@ var SCHEDULED_MEDIA_SELECTOR = ".doc-player__media-clips";
29
30
  var SCHEDULED_VIDEO_SELECTOR = `${SCHEDULED_MEDIA_SELECTOR} video[data-clip-id]`;
30
31
  var CAPTION_OVERLAY_SELECTOR = ".caption-overlay, .social-caption-overlay";
31
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
+ }
32
58
  async function waitForImageDecode(image) {
33
59
  const src = image.currentSrc || image.src;
34
60
  if (!src) return;
@@ -211,6 +237,50 @@ function createInlineProvider(images) {
211
237
  }
212
238
  };
213
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
+ }
214
284
  function shouldIgnoreCaptureSibling(element, captureRoot) {
215
285
  const { head } = captureRoot.ownerDocument;
216
286
  const isInDocumentHead = element === head || head.contains(element);
@@ -805,6 +875,7 @@ function useFrameCapture() {
805
875
  const rootRef = useRef(null);
806
876
  const renderAPIRef = useRef(null);
807
877
  const mediaProviderRef = useRef(null);
878
+ const mediaResolutionTrackerRef = useRef(null);
808
879
  const ownsMediaProviderRef = useRef(false);
809
880
  const captureCanvasRef = useRef(null);
810
881
  const captureBaseCanvasRef = useRef(null);
@@ -832,6 +903,7 @@ function useFrameCapture() {
832
903
  containerRef.current = null;
833
904
  renderAPIRef.current = null;
834
905
  mediaProviderRef.current = null;
906
+ mediaResolutionTrackerRef.current = null;
835
907
  ownsMediaProviderRef.current = false;
836
908
  captureCanvasRef.current = null;
837
909
  captureBaseCanvasRef.current = null;
@@ -901,6 +973,8 @@ function useFrameCapture() {
901
973
  container.appendChild(renderRoot);
902
974
  const mediaProvider = renderOptions.images ? createInlineProvider(renderOptions.images) : renderOptions.mediaProvider ?? null;
903
975
  mediaProviderRef.current = mediaProvider;
976
+ const mediaResolutionTracker = mediaProvider ? createCaptureMediaResolutionTracker(mediaProvider) : null;
977
+ mediaResolutionTrackerRef.current = mediaResolutionTracker;
904
978
  ownsMediaProviderRef.current = !!renderOptions.images;
905
979
  const root = createRoot(renderRoot);
906
980
  rootRef.current = root;
@@ -935,7 +1009,13 @@ function useFrameCapture() {
935
1009
  });
936
1010
  await new Promise((resolve) => setTimeout(resolve, 0));
937
1011
  if (mediaProvider) {
938
- root.render(createElement(MediaContext.Provider, { value: mediaProvider }, playerElement));
1012
+ root.render(
1013
+ createElement(
1014
+ MediaContext.Provider,
1015
+ { value: mediaResolutionTracker.provider },
1016
+ playerElement
1017
+ )
1018
+ );
939
1019
  } else {
940
1020
  root.render(playerElement);
941
1021
  }
@@ -958,6 +1038,7 @@ function useFrameCapture() {
958
1038
  if (!(captureRoot instanceof HTMLElement)) {
959
1039
  throw new Error("Capture root element not found after player initialization.");
960
1040
  }
1041
+ await waitForCaptureMediaResolutions(mediaResolutionTrackerRef.current);
961
1042
  await waitForCaptureAssets(captureRoot, decodedImagesRef.current);
962
1043
  await primeIndeterminateCaptureVideos(captureRoot, primedCaptureVideosRef.current);
963
1044
  clearTimeout(timeout);
@@ -1010,6 +1091,7 @@ function useFrameCapture() {
1010
1091
  `Player committed ${renderedTime.toFixed(6)}s while capture requested ${time.toFixed(6)}s.`
1011
1092
  );
1012
1093
  }
1094
+ await waitForCaptureMediaResolutions(mediaResolutionTrackerRef.current);
1013
1095
  await waitForCaptureAssets(root, decodedImagesRef.current);
1014
1096
  const compositePlan = resolveScheduledVideoCompositePlan(root, {
1015
1097
  includeFullFrameOverlays: true
@@ -1160,6 +1242,7 @@ function useFrameCapture() {
1160
1242
  }
1161
1243
  if (ownsMediaProviderRef.current) mediaProviderRef.current?.dispose();
1162
1244
  mediaProviderRef.current = null;
1245
+ mediaResolutionTrackerRef.current = null;
1163
1246
  ownsMediaProviderRef.current = false;
1164
1247
  if (captureCanvasRef.current) {
1165
1248
  captureCanvasRef.current.width = 0;
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  useFrameCapture
3
- } from "./chunk-I5PNMCSZ.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-FX5CHFGY.js";
4
+ } from "../chunk-J2O4TPEO.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
8
8
  validateCoverImageDimensions
9
- } from "../chunk-YZN7URUB.js";
10
- import "../chunk-UXTYD6U5.js";
11
- import "../chunk-I5PNMCSZ.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-YZN7URUB.js";
6
- import "../chunk-I5PNMCSZ.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-UXTYD6U5.js";
6
+ } from "../chunk-DIWB5BH4.js";
7
7
  import {
8
8
  useFrameCapture
9
- } from "../chunk-I5PNMCSZ.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-FX5CHFGY.js";
4
+ } from "./chunk-J2O4TPEO.js";
5
5
  import {
6
6
  CoverImageExportModal,
7
7
  coverImageFilename,
8
8
  validateCoverImageDimensions
9
- } from "./chunk-YZN7URUB.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-UXTYD6U5.js";
15
+ } from "./chunk-DIWB5BH4.js";
16
16
  import {
17
17
  useFrameCapture
18
- } from "./chunk-I5PNMCSZ.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.3",
3
+ "version": "2.3.5",
4
4
  "description": "React components for browser-based MP4 and animated-GIF export of Squisq documents",
5
5
  "license": "MIT",
6
6
  "author": "Bendyline",
@@ -71,7 +71,7 @@
71
71
  "dependencies": {
72
72
  "@bendyline/squisq": "2.7.1",
73
73
  "@bendyline/squisq-video": "2.2.12",
74
- "@bendyline/squisq-react": "2.7.1",
74
+ "@bendyline/squisq-react": "2.7.2",
75
75
  "@ffmpeg/core": "0.12.9",
76
76
  "@ffmpeg/ffmpeg": "0.12.15",
77
77
  "@ffmpeg/util": "0.12.2",