@remotion/media 4.0.463 → 4.0.465

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.
@@ -87,6 +87,7 @@ export declare const drawPreviewOverlay: ({ context, audioTime, audioContextStat
87
87
  };
88
88
  } | null;
89
89
  drawFrame: (frame: import("mediabunny").WrappedCanvas) => Promise<void>;
90
+ redrawCurrentFrame: () => Promise<void>;
90
91
  getFramesRendered: () => number;
91
92
  } | null;
92
93
  playbackRate: number;
@@ -853,6 +853,84 @@ var makeNonceManager = () => {
853
853
  };
854
854
  };
855
855
 
856
+ // src/premount-aware-delay-playback.ts
857
+ class PremountAwareDelayPlayback {
858
+ isPremounting;
859
+ isPostmounting;
860
+ activeHandles = new Set;
861
+ delayPlayback;
862
+ constructor({
863
+ bufferState,
864
+ isPremounting,
865
+ isPostmounting
866
+ }) {
867
+ this.delayPlayback = bufferState.delayPlayback;
868
+ this.isPremounting = isPremounting;
869
+ this.isPostmounting = isPostmounting;
870
+ }
871
+ shouldDelayPlayback() {
872
+ return !this.isPremounting && !this.isPostmounting;
873
+ }
874
+ syncHandles() {
875
+ for (const handle of this.activeHandles) {
876
+ if (this.shouldDelayPlayback()) {
877
+ handle.arm();
878
+ } else {
879
+ handle.disarm();
880
+ }
881
+ }
882
+ }
883
+ setIsPremounting(isPremounting) {
884
+ this.isPremounting = isPremounting;
885
+ this.syncHandles();
886
+ }
887
+ setIsPostmounting(isPostmounting) {
888
+ this.isPostmounting = isPostmounting;
889
+ this.syncHandles();
890
+ }
891
+ createHandle() {
892
+ let armed = false;
893
+ let unblock = null;
894
+ let disposed = false;
895
+ const arm = () => {
896
+ if (armed || disposed) {
897
+ return;
898
+ }
899
+ unblock = this.delayPlayback().unblock;
900
+ armed = true;
901
+ };
902
+ const disarm = () => {
903
+ if (!armed) {
904
+ return;
905
+ }
906
+ unblock?.();
907
+ unblock = null;
908
+ armed = false;
909
+ };
910
+ const entry = {
911
+ arm,
912
+ disarm,
913
+ dispose: () => {}
914
+ };
915
+ entry.dispose = () => {
916
+ if (disposed) {
917
+ return;
918
+ }
919
+ disposed = true;
920
+ disarm();
921
+ this.activeHandles.delete(entry);
922
+ };
923
+ this.activeHandles.add(entry);
924
+ if (this.shouldDelayPlayback()) {
925
+ arm();
926
+ }
927
+ return {
928
+ unblock: entry.dispose,
929
+ [Symbol.dispose]: entry.dispose
930
+ };
931
+ }
932
+ }
933
+
856
934
  // src/video-iterator-manager.ts
857
935
  import { CanvasSink } from "mediabunny";
858
936
  import { Internals as Internals4 } from "remotion";
@@ -1126,6 +1204,10 @@ var videoIteratorManager = async ({
1126
1204
  let videoFrameIterator = null;
1127
1205
  let framesRendered = 0;
1128
1206
  let currentDelayHandle = null;
1207
+ let lastDrawnFrame = null;
1208
+ const clearLastDrawnFrame = () => {
1209
+ lastDrawnFrame = null;
1210
+ };
1129
1211
  if (canvas) {
1130
1212
  const displayWidth = await videoTrack.getDisplayWidth();
1131
1213
  const displayHeight = await videoTrack.getDisplayHeight();
@@ -1140,7 +1222,7 @@ var videoIteratorManager = async ({
1140
1222
  alpha: true
1141
1223
  });
1142
1224
  const prewarmedVideoIteratorCache = makePrewarmedVideoIteratorCache(canvasSink);
1143
- const drawFrame = async (frame) => {
1225
+ const paintFrame = async (frame) => {
1144
1226
  if (context && canvas) {
1145
1227
  const effects = getEffects();
1146
1228
  const chainState = getEffectChainState(canvas.width, canvas.height);
@@ -1158,6 +1240,10 @@ var videoIteratorManager = async ({
1158
1240
  context.drawImage(frame.canvas, 0, 0);
1159
1241
  }
1160
1242
  }
1243
+ };
1244
+ const drawFrame = async (frame) => {
1245
+ await paintFrame(frame);
1246
+ lastDrawnFrame = frame;
1161
1247
  framesRendered++;
1162
1248
  drawDebugOverlay();
1163
1249
  const callback = getOnVideoFrameCallback();
@@ -1166,9 +1252,22 @@ var videoIteratorManager = async ({
1166
1252
  }
1167
1253
  Internals4.Log.trace({ logLevel, tag: "@remotion/media" }, `[MediaPlayer] Drew frame ${frame.timestamp.toFixed(3)}s`);
1168
1254
  };
1255
+ const redrawCurrentFrame = async () => {
1256
+ if (!lastDrawnFrame) {
1257
+ return;
1258
+ }
1259
+ await paintFrame(lastDrawnFrame);
1260
+ drawDebugOverlay();
1261
+ const callback = getOnVideoFrameCallback();
1262
+ if (callback) {
1263
+ callback(lastDrawnFrame.canvas);
1264
+ }
1265
+ Internals4.Log.trace({ logLevel, tag: "@remotion/media" }, `[MediaPlayer] Redrew frame ${lastDrawnFrame.timestamp.toFixed(3)}s with updated effects`);
1266
+ };
1169
1267
  const startVideoIterator = async (timeToSeek, nonce) => {
1170
1268
  let __stack = [];
1171
1269
  try {
1270
+ clearLastDrawnFrame();
1172
1271
  videoFrameIterator?.destroy();
1173
1272
  const delayHandle = __using(__stack, delayPlaybackHandleIfNotPremounting(), 0);
1174
1273
  currentDelayHandle = delayHandle;
@@ -1220,6 +1319,7 @@ var videoIteratorManager = async ({
1220
1319
  getVideoIteratorsCreated: () => videoIteratorsCreated,
1221
1320
  seek,
1222
1321
  destroy: () => {
1322
+ clearLastDrawnFrame();
1223
1323
  prewarmedVideoIteratorCache.destroy();
1224
1324
  videoFrameIterator?.destroy();
1225
1325
  if (context && canvas) {
@@ -1233,6 +1333,7 @@ var videoIteratorManager = async ({
1233
1333
  },
1234
1334
  getVideoFrameIterator: () => videoFrameIterator,
1235
1335
  drawFrame,
1336
+ redrawCurrentFrame,
1236
1337
  getFramesRendered: () => framesRendered
1237
1338
  };
1238
1339
  };
@@ -1264,9 +1365,7 @@ class MediaPlayer {
1264
1365
  getEffects;
1265
1366
  getEffectChainState;
1266
1367
  initializationPromise = null;
1267
- bufferState;
1268
- isPremounting;
1269
- isPostmounting;
1368
+ premountAwareDelayPlayback;
1270
1369
  seekPromiseChain = Promise.resolve();
1271
1370
  constructor({
1272
1371
  canvas,
@@ -1305,9 +1404,11 @@ class MediaPlayer {
1305
1404
  this.audioStreamIndex = audioStreamIndex;
1306
1405
  this.fps = fps;
1307
1406
  this.debugOverlay = debugOverlay;
1308
- this.bufferState = bufferState;
1309
- this.isPremounting = isPremounting;
1310
- this.isPostmounting = isPostmounting;
1407
+ this.premountAwareDelayPlayback = new PremountAwareDelayPlayback({
1408
+ bufferState,
1409
+ isPremounting,
1410
+ isPostmounting
1411
+ });
1311
1412
  this.sequenceDurationInFrames = durationInFrames;
1312
1413
  this.nonceManager = makeNonceManager();
1313
1414
  this.onVideoFrameCallback = onVideoFrameCallback;
@@ -1563,19 +1664,7 @@ class MediaPlayer {
1563
1664
  this.drawDebugOverlay();
1564
1665
  }
1565
1666
  delayPlaybackHandleIfNotPremounting = () => {
1566
- if (this.isPremounting || this.isPostmounting) {
1567
- return {
1568
- unblock: () => {},
1569
- [Symbol.dispose]: () => {}
1570
- };
1571
- }
1572
- const { unblock } = this.bufferState.delayPlayback();
1573
- return {
1574
- unblock,
1575
- [Symbol.dispose]: () => {
1576
- unblock();
1577
- }
1578
- };
1667
+ return this.premountAwareDelayPlayback.createHandle();
1579
1668
  };
1580
1669
  pause() {
1581
1670
  if (!this.playing) {
@@ -1648,10 +1737,10 @@ class MediaPlayer {
1648
1737
  }
1649
1738
  }
1650
1739
  setIsPremounting(isPremounting) {
1651
- this.isPremounting = isPremounting;
1740
+ this.premountAwareDelayPlayback.setIsPremounting(isPremounting);
1652
1741
  }
1653
1742
  setIsPostmounting(isPostmounting) {
1654
- this.isPostmounting = isPostmounting;
1743
+ this.premountAwareDelayPlayback.setIsPostmounting(isPostmounting);
1655
1744
  }
1656
1745
  async setLoop(loop, unloopedTimeInSeconds) {
1657
1746
  const previousLoop = this.loop;
@@ -1743,6 +1832,9 @@ class MediaPlayer {
1743
1832
  setVideoFrameCallback(callback) {
1744
1833
  this.onVideoFrameCallback = callback;
1745
1834
  }
1835
+ async redrawVideoEffects() {
1836
+ await this.videoIteratorManager?.redrawCurrentFrame();
1837
+ }
1746
1838
  drawDebugOverlay = () => {
1747
1839
  if (!this.debugOverlay)
1748
1840
  return;
@@ -4574,7 +4666,7 @@ var VideoForPreviewAssertedShowing = ({
4574
4666
  credentials,
4575
4667
  objectFit: objectFitProp,
4576
4668
  _experimentalInitiallyDrawCachedFrame,
4577
- _experimentalEffects,
4669
+ effects,
4578
4670
  setMediaDurationInSeconds
4579
4671
  }) => {
4580
4672
  const src = usePreload2(unpreloadedSrc);
@@ -4604,8 +4696,8 @@ var VideoForPreviewAssertedShowing = ({
4604
4696
  }
4605
4697
  warnAboutTooHighVolume2(userPreferredVolume);
4606
4698
  const effectChainState = useEffectChainState();
4607
- const experimentalEffectsRef = useRef2(_experimentalEffects);
4608
- experimentalEffectsRef.current = _experimentalEffects;
4699
+ const effectsRef = useRef2(effects);
4700
+ effectsRef.current = effects;
4609
4701
  const effectChainStateRef = useRef2(effectChainState);
4610
4702
  effectChainStateRef.current = effectChainState;
4611
4703
  const parentSequence = useContext4(SequenceContext2);
@@ -4712,7 +4804,7 @@ var VideoForPreviewAssertedShowing = ({
4712
4804
  sequenceOffset: initialSequenceOffset.current,
4713
4805
  credentials,
4714
4806
  tagType: "video",
4715
- getEffects: () => experimentalEffectsRef.current,
4807
+ getEffects: () => effectsRef.current,
4716
4808
  getEffectChainState: (width, height) => effectChainStateRef.current?.get(width, height)
4717
4809
  });
4718
4810
  mediaPlayerRef.current = player;
@@ -4848,6 +4940,13 @@ var VideoForPreviewAssertedShowing = ({
4848
4940
  }
4849
4941
  mediaPlayer.setVideoFrameCallback(onVideoFrame ?? null);
4850
4942
  }, [onVideoFrame, mediaPlayerReady]);
4943
+ useLayoutEffect3(() => {
4944
+ const mediaPlayer = mediaPlayerRef.current;
4945
+ if (!mediaPlayer || !mediaPlayerReady) {
4946
+ return;
4947
+ }
4948
+ mediaPlayer.redrawVideoEffects().catch(() => {});
4949
+ }, [effects, mediaPlayerReady, mediaPlayerRef]);
4851
4950
  const actualStyle = useMemo4(() => {
4852
4951
  return {
4853
4952
  ...style,
@@ -5256,7 +5355,7 @@ var videoSchema = {
5256
5355
  description: "Hidden"
5257
5356
  },
5258
5357
  loop: { type: "boolean", default: false, description: "Loop" },
5259
- ...Internals20.sequenceStyleSchema
5358
+ ...Internals20.sequenceVisualStyleSchema
5260
5359
  };
5261
5360
  var InnerVideo = ({
5262
5361
  src,
@@ -5286,7 +5385,7 @@ var InnerVideo = ({
5286
5385
  _experimentalControls: controls,
5287
5386
  objectFit,
5288
5387
  _experimentalInitiallyDrawCachedFrame,
5289
- _experimentalEffects,
5388
+ effects,
5290
5389
  setMediaDurationInSeconds
5291
5390
  }) => {
5292
5391
  const environment = useRemotionEnvironment4();
@@ -5358,7 +5457,7 @@ var InnerVideo = ({
5358
5457
  credentials,
5359
5458
  controls,
5360
5459
  objectFit,
5361
- _experimentalEffects,
5460
+ effects,
5362
5461
  _experimentalInitiallyDrawCachedFrame
5363
5462
  });
5364
5463
  };
@@ -5391,7 +5490,7 @@ var VideoInner = ({
5391
5490
  _experimentalControls: controls,
5392
5491
  objectFit,
5393
5492
  _experimentalInitiallyDrawCachedFrame,
5394
- _experimentalEffects,
5493
+ effects,
5395
5494
  durationInFrames,
5396
5495
  from,
5397
5496
  hidden
@@ -5437,10 +5536,10 @@ var VideoInner = ({
5437
5536
  data: basicInfo
5438
5537
  }), [basicInfo]);
5439
5538
  const memoizedEffects = Internals20.useMemoizedEffects({
5440
- effects: _experimentalEffects ?? [],
5539
+ effects: effects ?? [],
5441
5540
  overrideId: controls?.overrideId ?? null
5442
5541
  });
5443
- const memoizedEffectDefinitions = Internals20.useMemoizedEffectDefinitions(_experimentalEffects ?? []);
5542
+ const memoizedEffectDefinitions = Internals20.useMemoizedEffectDefinitions(effects ?? []);
5444
5543
  if (sequenceDurationInFrames === 0) {
5445
5544
  return null;
5446
5545
  }
@@ -5453,7 +5552,7 @@ var VideoInner = ({
5453
5552
  name: name ?? "<Video>",
5454
5553
  _experimentalControls: controls,
5455
5554
  _remotionInternalLoopDisplay: loopDisplay,
5456
- _experimentalEffects: memoizedEffectDefinitions,
5555
+ _remotionInternalEffects: memoizedEffectDefinitions,
5457
5556
  showInTimeline: showInTimeline ?? true,
5458
5557
  hidden,
5459
5558
  children: /* @__PURE__ */ jsx6(InnerVideo, {
@@ -5484,7 +5583,7 @@ var VideoInner = ({
5484
5583
  _experimentalControls: controls,
5485
5584
  objectFit: objectFit ?? "contain",
5486
5585
  _experimentalInitiallyDrawCachedFrame: _experimentalInitiallyDrawCachedFrame ?? false,
5487
- _experimentalEffects: memoizedEffects,
5586
+ effects: memoizedEffects,
5488
5587
  setMediaDurationInSeconds
5489
5588
  })
5490
5589
  });
package/dist/index.d.ts CHANGED
@@ -59,7 +59,7 @@ export declare const experimental_Video: import("react").ComponentType<{
59
59
  credentials: RequestCredentials | undefined;
60
60
  objectFit: import(".").VideoObjectFit;
61
61
  _experimentalInitiallyDrawCachedFrame: boolean;
62
- _experimentalEffects: import("remotion").EffectsProp;
62
+ effects: import("remotion").EffectsProp;
63
63
  }> & Pick<import("remotion").SequenceProps, "durationInFrames" | "from" | "hidden" | "name">>;
64
64
  export { AudioForPreview } from './audio/audio-for-preview';
65
65
  export { AudioProps, FallbackHtml5AudioProps } from './audio/props';
@@ -43,9 +43,7 @@ export declare class MediaPlayer {
43
43
  private getEffects;
44
44
  private getEffectChainState;
45
45
  private initializationPromise;
46
- private bufferState;
47
- private isPremounting;
48
- private isPostmounting;
46
+ private premountAwareDelayPlayback;
49
47
  private seekPromiseChain;
50
48
  constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, globalPlaybackRate, audioStreamIndex, fps, debugOverlay, bufferState, isPremounting, isPostmounting, durationInFrames, onVideoFrameCallback, playing, sequenceOffset, credentials, tagType, getEffects, getEffectChainState }: {
51
49
  canvas: HTMLCanvasElement | OffscreenCanvas | null;
@@ -105,6 +103,7 @@ export declare class MediaPlayer {
105
103
  private getTargetTime;
106
104
  private scheduleAudioNode;
107
105
  setVideoFrameCallback(callback: null | ((frame: CanvasImageSource) => void)): void;
106
+ redrawVideoEffects(): Promise<void>;
108
107
  private drawDebugOverlay;
109
108
  audioSyncAnchorChanged: () => void;
110
109
  }
@@ -0,0 +1,18 @@
1
+ import type { useBufferState } from 'remotion';
2
+ import type { DelayPlaybackIfNotPremounting } from './delay-playback-if-not-premounting';
3
+ export declare class PremountAwareDelayPlayback {
4
+ private isPremounting;
5
+ private isPostmounting;
6
+ private readonly activeHandles;
7
+ private readonly delayPlayback;
8
+ constructor({ bufferState, isPremounting, isPostmounting }: {
9
+ bufferState: ReturnType<typeof useBufferState>;
10
+ isPremounting: boolean;
11
+ isPostmounting: boolean;
12
+ });
13
+ private shouldDelayPlayback;
14
+ private syncHandles;
15
+ setIsPremounting(isPremounting: boolean): void;
16
+ setIsPostmounting(isPostmounting: boolean): void;
17
+ createHandle(): DelayPlaybackIfNotPremounting;
18
+ }
@@ -51,10 +51,10 @@ type OptionalVideoProps = {
51
51
  credentials: RequestCredentials | undefined;
52
52
  objectFit: VideoObjectFit;
53
53
  _experimentalInitiallyDrawCachedFrame: boolean;
54
- _experimentalEffects: EffectsProp;
54
+ effects: EffectsProp;
55
55
  };
56
- export type InnerVideoProps = MandatoryVideoProps & OuterVideoProps & Omit<OptionalVideoProps, '_experimentalEffects'> & {
57
- _experimentalEffects: EffectDefinitionAndStack<unknown>[];
56
+ export type InnerVideoProps = MandatoryVideoProps & OuterVideoProps & Omit<OptionalVideoProps, 'effects'> & {
57
+ effects: EffectDefinitionAndStack<unknown>[];
58
58
  };
59
59
  export type VideoProps = MandatoryVideoProps & Partial<OuterVideoProps> & Partial<OptionalVideoProps> & Pick<SequenceProps, 'durationInFrames' | 'from' | 'name' | 'hidden'>;
60
60
  export {};
@@ -27,7 +27,7 @@ type VideoForPreviewProps = {
27
27
  readonly objectFit: VideoObjectFit;
28
28
  readonly setMediaDurationInSeconds: (durationInSeconds: number) => void;
29
29
  readonly _experimentalInitiallyDrawCachedFrame: boolean;
30
- readonly _experimentalEffects: EffectDefinitionAndStack<unknown>[];
30
+ readonly effects: EffectDefinitionAndStack<unknown>[];
31
31
  };
32
32
  export declare const VideoForPreview: React.FC<VideoForPreviewProps & {
33
33
  readonly controls: SequenceControls | undefined;
@@ -30,5 +30,5 @@ export declare const Video: React.ComponentType<{
30
30
  credentials: RequestCredentials | undefined;
31
31
  objectFit: import("./props").VideoObjectFit;
32
32
  _experimentalInitiallyDrawCachedFrame: boolean;
33
- _experimentalEffects: import("remotion").EffectsProp;
33
+ effects: import("remotion").EffectsProp;
34
34
  }> & Pick<import("remotion").SequenceProps, "durationInFrames" | "from" | "hidden" | "name">>;
@@ -36,6 +36,7 @@ export declare const videoIteratorManager: ({ delayPlaybackHandleIfNotPremountin
36
36
  };
37
37
  } | null;
38
38
  drawFrame: (frame: WrappedCanvas) => Promise<void>;
39
+ redrawCurrentFrame: () => Promise<void>;
39
40
  getFramesRendered: () => number;
40
41
  }>;
41
42
  export type VideoIteratorManager = Awaited<ReturnType<typeof videoIteratorManager>>;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.463",
3
+ "version": "4.0.465",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -23,7 +23,7 @@
23
23
  },
24
24
  "dependencies": {
25
25
  "mediabunny": "1.45.0",
26
- "remotion": "4.0.463",
26
+ "remotion": "4.0.465",
27
27
  "zod": "4.3.6"
28
28
  },
29
29
  "peerDependencies": {
@@ -31,7 +31,7 @@
31
31
  "react-dom": ">=16.8.0"
32
32
  },
33
33
  "devDependencies": {
34
- "@remotion/eslint-config-internal": "4.0.463",
34
+ "@remotion/eslint-config-internal": "4.0.465",
35
35
  "@vitest/browser-webdriverio": "4.0.9",
36
36
  "eslint": "9.19.0",
37
37
  "react": "19.2.3",