@remotion/player 4.0.458 → 4.0.460

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,5 +1,5 @@
1
1
  export declare const ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT = 0.1;
2
- export declare const setGlobalTimeAnchor: ({ audioContext, audioSyncAnchor, absoluteTimeInSeconds, globalPlaybackRate, logLevel, }: {
2
+ export declare const setGlobalTimeAnchor: ({ audioContext, audioSyncAnchor, absoluteTimeInSeconds, globalPlaybackRate, logLevel, force, }: {
3
3
  audioContext: AudioContext;
4
4
  audioSyncAnchor: {
5
5
  value: number;
@@ -7,4 +7,5 @@ export declare const setGlobalTimeAnchor: ({ audioContext, audioSyncAnchor, abso
7
7
  absoluteTimeInSeconds: number;
8
8
  globalPlaybackRate: number;
9
9
  logLevel: "error" | "info" | "trace" | "verbose" | "warn";
10
+ force: boolean;
10
11
  }) => boolean;
@@ -3,17 +3,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.setGlobalTimeAnchor = exports.ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT = void 0;
4
4
  const remotion_1 = require("remotion");
5
5
  exports.ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT = 0.1;
6
- const setGlobalTimeAnchor = ({ audioContext, audioSyncAnchor, absoluteTimeInSeconds, globalPlaybackRate, logLevel, }) => {
6
+ const setGlobalTimeAnchor = ({ audioContext, audioSyncAnchor, absoluteTimeInSeconds, globalPlaybackRate, logLevel, force, }) => {
7
7
  const newAnchor = audioContext.currentTime - absoluteTimeInSeconds / globalPlaybackRate;
8
8
  const shift = (newAnchor - audioSyncAnchor.value) * globalPlaybackRate;
9
9
  const { outputLatency } = audioContext;
10
10
  const safeOutputLatency = outputLatency === 0 ? 0.3 : outputLatency;
11
11
  const latency = audioContext.baseLatency + safeOutputLatency;
12
12
  // Skip small shifts to avoid audio glitches from frame-quantized re-anchoring
13
- if (Math.abs(shift) < exports.ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT + latency) {
13
+ if (Math.abs(shift) < exports.ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT + latency && !force) {
14
14
  return false;
15
15
  }
16
- remotion_1.Internals.Log.verbose({ logLevel, tag: 'audio-scheduling' }, 'Anchor changed from %s to %s with shift %s', audioSyncAnchor.value, newAnchor, shift);
16
+ remotion_1.Internals.Log.verbose({ logLevel, tag: 'audio-scheduling' }, 'Anchor ' +
17
+ (force ? 'forcibly ' : '') +
18
+ 'changed from %s to %s with shift %s', audioSyncAnchor.value, newAnchor, shift);
17
19
  audioSyncAnchor.value = newAnchor;
18
20
  return true;
19
21
  };
@@ -31,6 +31,8 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
31
31
  playbackRate,
32
32
  videoConfig: config,
33
33
  });
34
+ // Update time anchor when seeking:
35
+ // If the user clicked on a different time in the timeline, we need to re-sync the anchor
34
36
  (0, react_1.useLayoutEffect)(() => {
35
37
  if (!sharedAudioContext) {
36
38
  return;
@@ -41,17 +43,58 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
41
43
  if (!config) {
42
44
  return;
43
45
  }
46
+ if (muted) {
47
+ return;
48
+ }
44
49
  const changed = (0, set_global_time_anchor_js_1.setGlobalTimeAnchor)({
45
50
  audioContext: sharedAudioContext.audioContext,
46
51
  audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
47
52
  absoluteTimeInSeconds: frame / config.fps,
48
53
  globalPlaybackRate: playbackRate,
49
54
  logLevel,
55
+ force: false,
50
56
  });
51
57
  if (changed) {
52
58
  sharedAudioContext.audioSyncAnchorEmitter.dispatch('changed');
53
59
  }
54
- }, [config, frame, logLevel, playbackRate, sharedAudioContext]);
60
+ }, [config, frame, logLevel, playbackRate, sharedAudioContext, muted]);
61
+ // When the audio context is suspended, we use the opportunity to
62
+ // re-anchor the time to be exact.
63
+ (0, react_1.useLayoutEffect)(() => {
64
+ const audioContext = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.audioContext;
65
+ if (!audioContext) {
66
+ return;
67
+ }
68
+ if (!config) {
69
+ return;
70
+ }
71
+ if (muted) {
72
+ return;
73
+ }
74
+ const callback = () => {
75
+ if (audioContext.state !== 'running') {
76
+ (0, set_global_time_anchor_js_1.setGlobalTimeAnchor)({
77
+ audioContext,
78
+ audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
79
+ absoluteTimeInSeconds: getCurrentFrame() / config.fps,
80
+ globalPlaybackRate: playbackRate,
81
+ logLevel,
82
+ force: true,
83
+ });
84
+ }
85
+ };
86
+ audioContext === null || audioContext === void 0 ? void 0 : audioContext.addEventListener('statechange', callback);
87
+ return () => {
88
+ audioContext === null || audioContext === void 0 ? void 0 : audioContext.removeEventListener('statechange', callback);
89
+ };
90
+ }, [
91
+ config,
92
+ getCurrentFrame,
93
+ logLevel,
94
+ muted,
95
+ playbackRate,
96
+ sharedAudioContext,
97
+ ]);
55
98
  (0, react_2.useEffect)(() => {
56
99
  var _a;
57
100
  if (!config) {
@@ -88,7 +131,7 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
88
131
  (_a = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.suspend) === null || _a === void 0 ? void 0 : _a.call(sharedAudioContext);
89
132
  return;
90
133
  }
91
- if (!muted) {
134
+ if (!muted && !context.buffering.current) {
92
135
  (_b = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.resume) === null || _b === void 0 ? void 0 : _b.call(sharedAudioContext);
93
136
  }
94
137
  const time = performance.now() - startedTime;
@@ -107,7 +150,8 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
107
150
  });
108
151
  framesAdvanced += framesToAdvance;
109
152
  if (nextFrame !== getCurrentFrame() &&
110
- (!hasEnded || moveToBeginningWhenEnded)) {
153
+ (!hasEnded || moveToBeginningWhenEnded) &&
154
+ !context.buffering.current) {
111
155
  setFrame((c) => ({ ...c, [config.id]: nextFrame }));
112
156
  }
113
157
  if (hasEnded) {
@@ -124,31 +168,16 @@ const usePlayback = ({ loop, playbackRate, moveToBeginningWhenEnded, inFrame, ou
124
168
  const getIsResumingAudioContext = (_c = (_a = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.getIsResumingAudioContext) === null || _a === void 0 ? void 0 : _a.call(sharedAudioContext)) !== null && _c !== void 0 ? _c : null;
125
169
  if (getIsResumingAudioContext !== null && !muted) {
126
170
  getIsResumingAudioContext.then(() => {
127
- if (!(sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.audioContext)) {
128
- return;
129
- }
130
- if (!sharedAudioContext.audioSyncAnchor) {
131
- return;
132
- }
133
- // set it here and DON'T propagate an event
134
- // the useLayoutEffect above is supposed to handle a user seek,
135
- // this is a natural wait for the audio playback to start.
136
- // we don't wanna destroy the iterators.
137
- (0, set_global_time_anchor_js_1.setGlobalTimeAnchor)({
138
- audioContext: sharedAudioContext.audioContext,
139
- audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
140
- absoluteTimeInSeconds: getCurrentFrame() / config.fps,
141
- globalPlaybackRate: playbackRate,
142
- logLevel,
143
- });
144
171
  startedTime = performance.now();
145
172
  framesAdvanced = 0;
146
173
  queueNextFrame();
147
174
  });
148
175
  return;
149
176
  }
150
- if (context.buffering.current && !muted) {
151
- (_b = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.suspend) === null || _b === void 0 ? void 0 : _b.call(sharedAudioContext);
177
+ if (context.buffering.current) {
178
+ if (!muted) {
179
+ (_b = sharedAudioContext === null || sharedAudioContext === void 0 ? void 0 : sharedAudioContext.suspend) === null || _b === void 0 ? void 0 : _b.call(sharedAudioContext);
180
+ }
152
181
  const stopListening = context.listenForResume(() => {
153
182
  stopListening.remove();
154
183
  startedTime = performance.now();
@@ -912,17 +912,18 @@ var setGlobalTimeAnchor = ({
912
912
  audioSyncAnchor,
913
913
  absoluteTimeInSeconds,
914
914
  globalPlaybackRate,
915
- logLevel
915
+ logLevel,
916
+ force
916
917
  }) => {
917
918
  const newAnchor = audioContext.currentTime - absoluteTimeInSeconds / globalPlaybackRate;
918
919
  const shift = (newAnchor - audioSyncAnchor.value) * globalPlaybackRate;
919
920
  const { outputLatency } = audioContext;
920
921
  const safeOutputLatency = outputLatency === 0 ? 0.3 : outputLatency;
921
922
  const latency = audioContext.baseLatency + safeOutputLatency;
922
- if (Math.abs(shift) < ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT + latency) {
923
+ if (Math.abs(shift) < ALLOWED_GLOBAL_TIME_ANCHOR_SHIFT + latency && !force) {
923
924
  return false;
924
925
  }
925
- Internals6.Log.verbose({ logLevel, tag: "audio-scheduling" }, "Anchor changed from %s to %s with shift %s", audioSyncAnchor.value, newAnchor, shift);
926
+ Internals6.Log.verbose({ logLevel, tag: "audio-scheduling" }, "Anchor " + (force ? "forcibly " : "") + "changed from %s to %s with shift %s", audioSyncAnchor.value, newAnchor, shift);
926
927
  audioSyncAnchor.value = newAnchor;
927
928
  return true;
928
929
  };
@@ -965,17 +966,56 @@ var usePlayback = ({
965
966
  if (!config) {
966
967
  return;
967
968
  }
969
+ if (muted) {
970
+ return;
971
+ }
968
972
  const changed = setGlobalTimeAnchor({
969
973
  audioContext: sharedAudioContext.audioContext,
970
974
  audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
971
975
  absoluteTimeInSeconds: frame / config.fps,
972
976
  globalPlaybackRate: playbackRate,
973
- logLevel
977
+ logLevel,
978
+ force: false
974
979
  });
975
980
  if (changed) {
976
981
  sharedAudioContext.audioSyncAnchorEmitter.dispatch("changed");
977
982
  }
978
- }, [config, frame, logLevel, playbackRate, sharedAudioContext]);
983
+ }, [config, frame, logLevel, playbackRate, sharedAudioContext, muted]);
984
+ useLayoutEffect2(() => {
985
+ const audioContext = sharedAudioContext?.audioContext;
986
+ if (!audioContext) {
987
+ return;
988
+ }
989
+ if (!config) {
990
+ return;
991
+ }
992
+ if (muted) {
993
+ return;
994
+ }
995
+ const callback = () => {
996
+ if (audioContext.state !== "running") {
997
+ setGlobalTimeAnchor({
998
+ audioContext,
999
+ audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
1000
+ absoluteTimeInSeconds: getCurrentFrame() / config.fps,
1001
+ globalPlaybackRate: playbackRate,
1002
+ logLevel,
1003
+ force: true
1004
+ });
1005
+ }
1006
+ };
1007
+ audioContext?.addEventListener("statechange", callback);
1008
+ return () => {
1009
+ audioContext?.removeEventListener("statechange", callback);
1010
+ };
1011
+ }, [
1012
+ config,
1013
+ getCurrentFrame,
1014
+ logLevel,
1015
+ muted,
1016
+ playbackRate,
1017
+ sharedAudioContext
1018
+ ]);
979
1019
  useEffect5(() => {
980
1020
  if (!config) {
981
1021
  return;
@@ -1009,7 +1049,7 @@ var usePlayback = ({
1009
1049
  sharedAudioContext?.suspend?.();
1010
1050
  return;
1011
1051
  }
1012
- if (!muted) {
1052
+ if (!muted && !context.buffering.current) {
1013
1053
  sharedAudioContext?.resume?.();
1014
1054
  }
1015
1055
  const time = performance.now() - startedTime;
@@ -1027,7 +1067,7 @@ var usePlayback = ({
1027
1067
  shouldLoop: loop
1028
1068
  });
1029
1069
  framesAdvanced += framesToAdvance;
1030
- if (nextFrame !== getCurrentFrame() && (!hasEnded || moveToBeginningWhenEnded)) {
1070
+ if (nextFrame !== getCurrentFrame() && (!hasEnded || moveToBeginningWhenEnded) && !context.buffering.current) {
1031
1071
  setFrame((c) => ({ ...c, [config.id]: nextFrame }));
1032
1072
  }
1033
1073
  if (hasEnded) {
@@ -1042,27 +1082,16 @@ var usePlayback = ({
1042
1082
  const getIsResumingAudioContext = sharedAudioContext?.getIsResumingAudioContext?.() ?? null;
1043
1083
  if (getIsResumingAudioContext !== null && !muted) {
1044
1084
  getIsResumingAudioContext.then(() => {
1045
- if (!sharedAudioContext?.audioContext) {
1046
- return;
1047
- }
1048
- if (!sharedAudioContext.audioSyncAnchor) {
1049
- return;
1050
- }
1051
- setGlobalTimeAnchor({
1052
- audioContext: sharedAudioContext.audioContext,
1053
- audioSyncAnchor: sharedAudioContext.audioSyncAnchor,
1054
- absoluteTimeInSeconds: getCurrentFrame() / config.fps,
1055
- globalPlaybackRate: playbackRate,
1056
- logLevel
1057
- });
1058
1085
  startedTime = performance.now();
1059
1086
  framesAdvanced = 0;
1060
1087
  queueNextFrame();
1061
1088
  });
1062
1089
  return;
1063
1090
  }
1064
- if (context.buffering.current && !muted) {
1065
- sharedAudioContext?.suspend?.();
1091
+ if (context.buffering.current) {
1092
+ if (!muted) {
1093
+ sharedAudioContext?.suspend?.();
1094
+ }
1066
1095
  const stopListening = context.listenForResume(() => {
1067
1096
  stopListening.remove();
1068
1097
  startedTime = performance.now();
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/player"
4
4
  },
5
5
  "name": "@remotion/player",
6
- "version": "4.0.458",
6
+ "version": "4.0.460",
7
7
  "description": "React component for embedding a Remotion preview into your app",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -36,7 +36,7 @@
36
36
  ],
37
37
  "license": "SEE LICENSE IN LICENSE.md",
38
38
  "dependencies": {
39
- "remotion": "4.0.458"
39
+ "remotion": "4.0.460"
40
40
  },
41
41
  "peerDependencies": {
42
42
  "react": ">=16.8.0",
@@ -50,7 +50,7 @@
50
50
  "react-dom": "19.2.3",
51
51
  "webpack": "5.105.0",
52
52
  "zod": "4.3.6",
53
- "@remotion/eslint-config-internal": "4.0.458",
53
+ "@remotion/eslint-config-internal": "4.0.460",
54
54
  "eslint": "9.19.0",
55
55
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
56
56
  },