remotion 4.0.522 → 4.0.524

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.
@@ -6,6 +6,7 @@ import type { VideoConfig } from './video-config.js';
6
6
  export type BaseMetadata = Pick<VideoConfig, 'durationInFrames' | 'fps' | 'props' | 'height' | 'width' | 'defaultCodec' | 'defaultOutName' | 'defaultVideoImageFormat' | 'defaultPixelFormat' | 'defaultProResProfile' | 'defaultSampleRate'>;
7
7
  export type AssetPreviewMetadata = BaseMetadata & {
8
8
  asset: string;
9
+ src: string;
9
10
  };
10
11
  export declare const getAssetPreviewCompositionId: (asset: string) => string;
11
12
  export type CanvasContent = {
@@ -49,6 +49,7 @@ export type AudioSyncAnchorEmitter = {
49
49
  };
50
50
  };
51
51
  type SharedAudioContextValue = {
52
+ sampleRate: number;
52
53
  audioContext: AudioContext | null;
53
54
  getAudioContextState: () => RemotionAudioContextState | null;
54
55
  gainNode: GainNode | null;
@@ -91,12 +91,6 @@ const shouldSaveForLater = (state) => {
91
91
  const SharedAudioContextProvider = ({ children, audioLatencyHint, audioEnabled, previewSampleRate, _experimentalKeepAudioContextAlive, }) => {
92
92
  const logLevel = (0, log_level_context_js_1.useLogLevel)();
93
93
  const sampleRate = previewSampleRate !== null && previewSampleRate !== void 0 ? previewSampleRate : 48000;
94
- (0, react_1.useEffect)(() => {
95
- if (typeof window === 'undefined') {
96
- return;
97
- }
98
- window.remotion_sampleRate = sampleRate;
99
- }, [sampleRate]);
100
94
  const ctxAndGain = (0, use_audio_context_js_1.useSingletonAudioContext)({
101
95
  logLevel,
102
96
  latencyHint: audioLatencyHint,
@@ -323,6 +317,7 @@ const SharedAudioContextProvider = ({ children, audioLatencyHint, audioEnabled,
323
317
  const audioContextValue = (0, react_1.useMemo)(() => {
324
318
  var _a, _b;
325
319
  return {
320
+ sampleRate,
326
321
  audioContext: (_a = ctxAndGain === null || ctxAndGain === void 0 ? void 0 : ctxAndGain.audioContext) !== null && _a !== void 0 ? _a : null,
327
322
  getAudioContextState: () => {
328
323
  var _a;
@@ -340,6 +335,7 @@ const SharedAudioContextProvider = ({ children, audioLatencyHint, audioEnabled,
340
335
  _experimentalKeepAudioContextAlive,
341
336
  };
342
337
  }, [
338
+ sampleRate,
343
339
  ctxAndGain,
344
340
  audioSyncAnchor,
345
341
  audioSyncAnchorEmitter,
@@ -0,0 +1,18 @@
1
+ type PlanarAudio = Float32Array[];
2
+ export declare class StreamingPitchShifter {
3
+ private readonly numberOfChannels;
4
+ private readonly stretcher;
5
+ private readonly resampler;
6
+ private readonly outputQueue;
7
+ private totalInputFrames;
8
+ private totalOutputFrames;
9
+ constructor({ numberOfChannels, sampleRate, toneFrequency }: {
10
+ numberOfChannels: number;
11
+ sampleRate: number;
12
+ toneFrequency: number;
13
+ });
14
+ append(audio: PlanarAudio): PlanarAudio;
15
+ private takeAvailableOutput;
16
+ finalize(): Float32Array<ArrayBuffer>[];
17
+ }
18
+ export {};
@@ -0,0 +1,349 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.StreamingPitchShifter = void 0;
4
+ // The pitch-shifting algorithm is adapted from Vanilagy's WSOLA audio
5
+ // stretcher: https://gist.github.com/Vanilagy/05f7901f4c4398356657e3a86c7aee05
6
+ const REFERENCE_SAMPLE_RATE = 48000;
7
+ const REFERENCE_HOP_SIZE = 512;
8
+ const makePlanarAudio = (numberOfChannels, length) => {
9
+ return new Array(numberOfChannels)
10
+ .fill(null)
11
+ .map(() => new Float32Array(length));
12
+ };
13
+ const ensurePlanarCapacity = ({ buffers, requiredLength, }) => {
14
+ if (buffers[0].length >= requiredLength) {
15
+ return buffers;
16
+ }
17
+ let newLength = buffers[0].length;
18
+ while (newLength < requiredLength) {
19
+ newLength *= 2;
20
+ }
21
+ return buffers.map((buffer) => {
22
+ const expanded = new Float32Array(newLength);
23
+ expanded.set(buffer);
24
+ return expanded;
25
+ });
26
+ };
27
+ class PlanarAudioQueue {
28
+ chunks = [];
29
+ length = 0;
30
+ push(audio) {
31
+ if (audio[0].length === 0) {
32
+ return;
33
+ }
34
+ this.chunks.push(audio);
35
+ this.length += audio[0].length;
36
+ }
37
+ take(numberOfFrames, numberOfChannels) {
38
+ const framesToTake = Math.min(numberOfFrames, this.length);
39
+ const result = makePlanarAudio(numberOfChannels, framesToTake);
40
+ let written = 0;
41
+ while (written < framesToTake) {
42
+ const first = this.chunks[0];
43
+ const available = first[0].length;
44
+ const count = Math.min(available, framesToTake - written);
45
+ for (let channel = 0; channel < numberOfChannels; channel++) {
46
+ result[channel].set(first[channel].subarray(0, count), written);
47
+ }
48
+ if (count === available) {
49
+ this.chunks.shift();
50
+ }
51
+ else {
52
+ this.chunks[0] = first.map((channel) => channel.subarray(count));
53
+ }
54
+ written += count;
55
+ this.length -= count;
56
+ }
57
+ return result;
58
+ }
59
+ getLength() {
60
+ return this.length;
61
+ }
62
+ }
63
+ // A streaming implementation of Waveform Similarity Overlap-Add. It changes
64
+ // duration while retaining pitch. Pitch shifting is achieved by following this
65
+ // stage with a resampler that restores the original duration.
66
+ class StreamingTimeStretcher {
67
+ numberOfChannels;
68
+ factor;
69
+ hopSize;
70
+ windowSize;
71
+ searchRadius;
72
+ analysisHop;
73
+ input;
74
+ inputLength = 0;
75
+ output;
76
+ outputLength = 0;
77
+ analysisPosition = 0;
78
+ synthesisPosition = 0;
79
+ initialized = false;
80
+ finalized = false;
81
+ totalInputFrames = 0;
82
+ totalOutputFrames = 0;
83
+ constructor({ numberOfChannels, sampleRate, factor, }) {
84
+ this.numberOfChannels = numberOfChannels;
85
+ this.factor = factor;
86
+ this.hopSize = Math.max(32, Math.round((REFERENCE_HOP_SIZE * sampleRate) / REFERENCE_SAMPLE_RATE));
87
+ this.windowSize = this.hopSize * 2;
88
+ this.searchRadius = this.hopSize;
89
+ this.analysisHop = this.hopSize / factor;
90
+ this.input = makePlanarAudio(numberOfChannels, 65536);
91
+ this.output = makePlanarAudio(numberOfChannels, 65536);
92
+ }
93
+ append(audio) {
94
+ if (this.finalized) {
95
+ throw new Error('Cannot append audio after the time stretcher was finalized.');
96
+ }
97
+ const { length } = audio[0];
98
+ this.input = ensurePlanarCapacity({
99
+ buffers: this.input,
100
+ requiredLength: this.inputLength + length,
101
+ });
102
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
103
+ this.input[channel].set(audio[channel], this.inputLength);
104
+ }
105
+ this.inputLength += length;
106
+ this.totalInputFrames += length;
107
+ this.process();
108
+ return this.drainFinalizedOutput();
109
+ }
110
+ findBestAnalysisPosition({ expectedPosition, nextSynthesisPosition, }) {
111
+ const minimum = Math.max(0, Math.floor(expectedPosition - this.searchRadius));
112
+ const maximum = Math.min(this.inputLength - this.windowSize, Math.ceil(expectedPosition + this.searchRadius));
113
+ let bestPosition = minimum;
114
+ let bestCorrelation = -Infinity;
115
+ for (let candidate = minimum; candidate <= maximum; candidate += 4) {
116
+ let dotProduct = 0;
117
+ let previousEnergy = 0;
118
+ let candidateEnergy = 0;
119
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
120
+ const previous = this.output[channel];
121
+ const incoming = this.input[channel];
122
+ for (let frame = 0; frame < this.hopSize; frame += 2) {
123
+ const previousValue = previous[nextSynthesisPosition + frame];
124
+ const candidateValue = incoming[candidate + frame];
125
+ dotProduct += previousValue * candidateValue;
126
+ previousEnergy += previousValue * previousValue;
127
+ candidateEnergy += candidateValue * candidateValue;
128
+ }
129
+ }
130
+ const correlation = dotProduct /
131
+ (Math.sqrt(previousEnergy * candidateEnergy) || Number.EPSILON);
132
+ if (correlation > bestCorrelation) {
133
+ bestCorrelation = correlation;
134
+ bestPosition = candidate;
135
+ }
136
+ }
137
+ const fineMinimum = Math.max(minimum, bestPosition - 4);
138
+ const fineMaximum = Math.min(maximum, bestPosition + 4);
139
+ for (let candidate = fineMinimum; candidate <= fineMaximum; candidate++) {
140
+ let dotProduct = 0;
141
+ let previousEnergy = 0;
142
+ let candidateEnergy = 0;
143
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
144
+ const previous = this.output[channel];
145
+ const incoming = this.input[channel];
146
+ for (let frame = 0; frame < this.hopSize; frame++) {
147
+ const previousValue = previous[nextSynthesisPosition + frame];
148
+ const candidateValue = incoming[candidate + frame];
149
+ dotProduct += previousValue * candidateValue;
150
+ previousEnergy += previousValue * previousValue;
151
+ candidateEnergy += candidateValue * candidateValue;
152
+ }
153
+ }
154
+ const correlation = dotProduct /
155
+ (Math.sqrt(previousEnergy * candidateEnergy) || Number.EPSILON);
156
+ if (correlation > bestCorrelation) {
157
+ bestCorrelation = correlation;
158
+ bestPosition = candidate;
159
+ }
160
+ }
161
+ return bestPosition;
162
+ }
163
+ process() {
164
+ if (!this.initialized) {
165
+ if (this.inputLength < this.windowSize + this.searchRadius) {
166
+ return;
167
+ }
168
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
169
+ this.output[channel].set(this.input[channel].subarray(0, this.windowSize));
170
+ }
171
+ this.outputLength = this.windowSize;
172
+ this.initialized = true;
173
+ }
174
+ while (true) {
175
+ const expectedPosition = this.analysisPosition + this.analysisHop;
176
+ if (expectedPosition + this.searchRadius + this.windowSize >
177
+ this.inputLength) {
178
+ break;
179
+ }
180
+ const nextSynthesisPosition = this.synthesisPosition + this.hopSize;
181
+ this.output = ensurePlanarCapacity({
182
+ buffers: this.output,
183
+ requiredLength: nextSynthesisPosition + this.windowSize,
184
+ });
185
+ const bestPosition = this.findBestAnalysisPosition({
186
+ expectedPosition,
187
+ nextSynthesisPosition,
188
+ });
189
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
190
+ for (let frame = 0; frame < this.hopSize; frame++) {
191
+ const fadeIn = 0.5 - 0.5 * Math.cos((Math.PI * (frame + 1)) / (this.hopSize + 1));
192
+ const outputIndex = nextSynthesisPosition + frame;
193
+ this.output[channel][outputIndex] =
194
+ this.output[channel][outputIndex] * (1 - fadeIn) +
195
+ this.input[channel][bestPosition + frame] * fadeIn;
196
+ }
197
+ this.output[channel].set(this.input[channel].subarray(bestPosition + this.hopSize, bestPosition + this.windowSize), nextSynthesisPosition + this.hopSize);
198
+ }
199
+ // Keep the analysis clock independent from the correlation correction.
200
+ // Periodic signals can have equally good matches at an earlier period; if
201
+ // the correction became the next clock position, the iterator could stop
202
+ // making forward progress.
203
+ this.analysisPosition = expectedPosition;
204
+ this.synthesisPosition = nextSynthesisPosition;
205
+ this.outputLength = nextSynthesisPosition + this.windowSize;
206
+ }
207
+ }
208
+ drainFinalizedOutput() {
209
+ if (!this.initialized) {
210
+ return makePlanarAudio(this.numberOfChannels, 0);
211
+ }
212
+ const finalizedLength = Math.max(0, this.synthesisPosition + this.hopSize);
213
+ const result = this.output.map((channel) => channel.slice(0, finalizedLength));
214
+ this.totalOutputFrames += finalizedLength;
215
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
216
+ this.output[channel].copyWithin(0, finalizedLength, this.outputLength);
217
+ }
218
+ this.outputLength -= finalizedLength;
219
+ this.synthesisPosition -= finalizedLength;
220
+ const inputFramesToDiscard = Math.max(0, Math.floor(this.analysisPosition) - this.searchRadius);
221
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
222
+ this.input[channel].copyWithin(0, inputFramesToDiscard, this.inputLength);
223
+ }
224
+ this.inputLength -= inputFramesToDiscard;
225
+ this.analysisPosition -= inputFramesToDiscard;
226
+ return result;
227
+ }
228
+ finalize() {
229
+ if (this.finalized) {
230
+ throw new Error('The time stretcher has already been finalized.');
231
+ }
232
+ this.finalized = true;
233
+ const targetLength = Math.round(this.totalInputFrames * this.factor);
234
+ const padding = makePlanarAudio(this.numberOfChannels, this.windowSize + this.searchRadius * 2);
235
+ this.input = ensurePlanarCapacity({
236
+ buffers: this.input,
237
+ requiredLength: this.inputLength + padding[0].length,
238
+ });
239
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
240
+ this.input[channel].set(padding[channel], this.inputLength);
241
+ }
242
+ this.inputLength += padding[0].length;
243
+ this.process();
244
+ const finalized = this.drainFinalizedOutput();
245
+ const remaining = Math.max(0, targetLength - this.totalOutputFrames + finalized[0].length);
246
+ if (finalized[0].length >= remaining) {
247
+ return finalized.map((channel) => channel.slice(0, remaining));
248
+ }
249
+ const result = makePlanarAudio(this.numberOfChannels, remaining);
250
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
251
+ result[channel].set(finalized[channel]);
252
+ }
253
+ return result;
254
+ }
255
+ }
256
+ class StreamingLinearResampler {
257
+ numberOfChannels;
258
+ step;
259
+ input;
260
+ inputLength = 0;
261
+ position = 0;
262
+ constructor({ numberOfChannels, step, }) {
263
+ this.numberOfChannels = numberOfChannels;
264
+ this.step = step;
265
+ this.input = makePlanarAudio(numberOfChannels, 65536);
266
+ }
267
+ append(audio) {
268
+ this.input = ensurePlanarCapacity({
269
+ buffers: this.input,
270
+ requiredLength: this.inputLength + audio[0].length,
271
+ });
272
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
273
+ this.input[channel].set(audio[channel], this.inputLength);
274
+ }
275
+ this.inputLength += audio[0].length;
276
+ return this.process(false);
277
+ }
278
+ process(finalizing) {
279
+ const outputLength = Math.max(0, Math.floor((this.inputLength - (finalizing ? 0 : 1) - this.position) / this.step) + 1);
280
+ const result = makePlanarAudio(this.numberOfChannels, outputLength);
281
+ for (let outputFrame = 0; outputFrame < outputLength; outputFrame++) {
282
+ const leftIndex = Math.floor(this.position);
283
+ const rightIndex = Math.min(leftIndex + 1, this.inputLength - 1);
284
+ const fraction = this.position - leftIndex;
285
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
286
+ const left = this.input[channel][leftIndex];
287
+ const right = this.input[channel][rightIndex];
288
+ result[channel][outputFrame] = left + (right - left) * fraction;
289
+ }
290
+ this.position += this.step;
291
+ }
292
+ const discard = Math.min(Math.floor(this.position), this.inputLength);
293
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
294
+ this.input[channel].copyWithin(0, discard, this.inputLength);
295
+ }
296
+ this.inputLength -= discard;
297
+ this.position -= discard;
298
+ return result;
299
+ }
300
+ finalize() {
301
+ return this.process(true);
302
+ }
303
+ }
304
+ class StreamingPitchShifter {
305
+ numberOfChannels;
306
+ stretcher;
307
+ resampler;
308
+ outputQueue = new PlanarAudioQueue();
309
+ totalInputFrames = 0;
310
+ totalOutputFrames = 0;
311
+ constructor({ numberOfChannels, sampleRate, toneFrequency, }) {
312
+ this.numberOfChannels = numberOfChannels;
313
+ this.stretcher = new StreamingTimeStretcher({
314
+ numberOfChannels,
315
+ sampleRate,
316
+ factor: toneFrequency,
317
+ });
318
+ this.resampler = new StreamingLinearResampler({
319
+ numberOfChannels,
320
+ step: toneFrequency,
321
+ });
322
+ }
323
+ append(audio) {
324
+ this.totalInputFrames += audio[0].length;
325
+ const stretched = this.stretcher.append(audio);
326
+ this.outputQueue.push(this.resampler.append(stretched));
327
+ return this.takeAvailableOutput();
328
+ }
329
+ takeAvailableOutput() {
330
+ const availableInputFrames = this.totalInputFrames - this.totalOutputFrames;
331
+ const framesToTake = Math.min(availableInputFrames, this.outputQueue.getLength());
332
+ const result = this.outputQueue.take(framesToTake, this.numberOfChannels);
333
+ this.totalOutputFrames += framesToTake;
334
+ return result;
335
+ }
336
+ finalize() {
337
+ this.outputQueue.push(this.resampler.append(this.stretcher.finalize()));
338
+ this.outputQueue.push(this.resampler.finalize());
339
+ const remaining = this.totalInputFrames - this.totalOutputFrames;
340
+ const available = this.outputQueue.take(Math.min(remaining, this.outputQueue.getLength()), this.numberOfChannels);
341
+ const result = makePlanarAudio(this.numberOfChannels, remaining);
342
+ for (let channel = 0; channel < this.numberOfChannels; channel++) {
343
+ result[channel].set(available[channel]);
344
+ }
345
+ this.totalOutputFrames += remaining;
346
+ return result;
347
+ }
348
+ }
349
+ exports.StreamingPitchShifter = StreamingPitchShifter;
@@ -816,6 +816,7 @@ export declare const Internals: {
816
816
  readonly SetMediaVolumeContext: import("react").Context<SetMediaVolumeContextValue>;
817
817
  readonly getRemotionEnvironment: () => RemotionEnvironment;
818
818
  readonly SharedAudioContext: import("react").Context<{
819
+ sampleRate: number;
819
820
  audioContext: AudioContext | null;
820
821
  getAudioContextState: () => RemotionAudioContextState | null;
821
822
  gainNode: GainNode | null;
@@ -3,4 +3,4 @@
3
3
  * @see [Documentation](https://remotion.dev/docs/version)
4
4
  * @returns {string} The current version of the remotion package
5
5
  */
6
- export declare const VERSION = "4.0.522";
6
+ export declare const VERSION = "4.0.524";
@@ -7,4 +7,4 @@ exports.VERSION = void 0;
7
7
  * @see [Documentation](https://remotion.dev/docs/version)
8
8
  * @returns {string} The current version of the remotion package
9
9
  */
10
- exports.VERSION = '4.0.522';
10
+ exports.VERSION = '4.0.524';
@@ -21,8 +21,16 @@ const isIosSafari = () => {
21
21
  if (typeof window === 'undefined') {
22
22
  return false;
23
23
  }
24
- const isIpadIPodIPhone = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
25
- return isIpadIPodIPhone && (0, exports.isSafari)();
24
+ const { userAgent, platform, maxTouchPoints } = window.navigator;
25
+ const isIpadIPodIPhone = /iP(ad|od|hone)/i.test(userAgent);
26
+ // Since iPadOS 13, Safari on iPad defaults to "Request Desktop Website"
27
+ // and sends a macOS user agent ("Macintosh; Intel Mac OS X 10_15_7"),
28
+ // so the string above never matches. A Mac platform with a touch
29
+ // screen is the only remaining signal — real Macs report 0 touch
30
+ // points. Without this, iPads get the blob + media fragment
31
+ // combination that Mobile Safari refuses to play (#1655).
32
+ const isIpadOsDesktopMode = platform === 'MacIntel' && maxTouchPoints > 1;
33
+ return (isIpadIPodIPhone || isIpadOsDesktopMode) && (0, exports.isSafari)();
26
34
  };
27
35
  exports.isIosSafari = isIosSafari;
28
36
  // https://github.com/remotion-dev/remotion/issues/1655
@@ -21,7 +21,8 @@ export type WithInteractivitySchemaOptions<S extends InteractivitySchema, Props
21
21
  readonly controls: SequenceControls | undefined;
22
22
  }>;
23
23
  componentName: string;
24
- componentIdentity: JsxComponentIdentity | null;
24
+ /** @internal */
25
+ componentIdentity?: JsxComponentIdentity | null;
25
26
  schema: S;
26
27
  supportsEffects: boolean;
27
28
  };
@@ -127,7 +127,7 @@ const DisableInteractivityProvider = ({ children }) => {
127
127
  return react_1.default.createElement(DisableInteractivityContext.Provider, { value: true }, children);
128
128
  };
129
129
  exports.DisableInteractivityProvider = DisableInteractivityProvider;
130
- const withInteractivitySchema = ({ Component, componentName, componentIdentity, schema, supportsEffects, }) => {
130
+ const withInteractivitySchema = ({ Component, componentName, componentIdentity = null, schema, supportsEffects, }) => {
131
131
  // Schema is static for a component, so we move this outside
132
132
  const schemaWithSequenceName = (0, interactivity_schema_js_1.extendSchemaWithSequenceName)(schema);
133
133
  const flatSchema = (0, flatten_schema_js_1.getFlatSchemaWithAllKeys)(schemaWithSequenceName);
@@ -1537,7 +1537,7 @@ var Composition = (props) => {
1537
1537
  };
1538
1538
 
1539
1539
  // src/version.ts
1540
- var VERSION = "4.0.522";
1540
+ var VERSION = "4.0.524";
1541
1541
 
1542
1542
  // src/multiple-versions-warning.ts
1543
1543
  var checkMultipleRemotionVersions = () => {
@@ -5215,7 +5215,7 @@ var DisableInteractivityProvider = ({ children }) => {
5215
5215
  var withInteractivitySchema = ({
5216
5216
  Component,
5217
5217
  componentName,
5218
- componentIdentity,
5218
+ componentIdentity = null,
5219
5219
  schema,
5220
5220
  supportsEffects
5221
5221
  }) => {
@@ -7812,12 +7812,6 @@ var SharedAudioContextProvider = ({
7812
7812
  }) => {
7813
7813
  const logLevel = useLogLevel();
7814
7814
  const sampleRate = previewSampleRate ?? 48000;
7815
- useEffect8(() => {
7816
- if (typeof window === "undefined") {
7817
- return;
7818
- }
7819
- window.remotion_sampleRate = sampleRate;
7820
- }, [sampleRate]);
7821
7815
  const ctxAndGain = useSingletonAudioContext({
7822
7816
  logLevel,
7823
7817
  latencyHint: audioLatencyHint,
@@ -8001,6 +7995,7 @@ var SharedAudioContextProvider = ({
8001
7995
  }, [ctxAndGain, _experimentalKeepAudioContextAlive]);
8002
7996
  const audioContextValue = useMemo21(() => {
8003
7997
  return {
7998
+ sampleRate,
8004
7999
  audioContext: ctxAndGain?.audioContext ?? null,
8005
8000
  getAudioContextState: () => ctxAndGain?.getState() ?? null,
8006
8001
  gainNode: ctxAndGain?.gainNode ?? null,
@@ -8015,6 +8010,7 @@ var SharedAudioContextProvider = ({
8015
8010
  _experimentalKeepAudioContextAlive
8016
8011
  };
8017
8012
  }, [
8013
+ sampleRate,
8018
8014
  ctxAndGain,
8019
8015
  audioSyncAnchor,
8020
8016
  audioSyncAnchorEmitter,
@@ -8299,8 +8295,10 @@ var isIosSafari = () => {
8299
8295
  if (typeof window === "undefined") {
8300
8296
  return false;
8301
8297
  }
8302
- const isIpadIPodIPhone = /iP(ad|od|hone)/i.test(window.navigator.userAgent);
8303
- return isIpadIPodIPhone && isSafari();
8298
+ const { userAgent, platform, maxTouchPoints } = window.navigator;
8299
+ const isIpadIPodIPhone = /iP(ad|od|hone)/i.test(userAgent);
8300
+ const isIpadOsDesktopMode = platform === "MacIntel" && maxTouchPoints > 1;
8301
+ return (isIpadIPodIPhone || isIpadOsDesktopMode) && isSafari();
8304
8302
  };
8305
8303
  var isIOSSafariAndBlob = (actualSrc) => {
8306
8304
  return isIosSafari() && actualSrc.startsWith("blob:");
@@ -1,5 +1,5 @@
1
1
  // src/version.ts
2
- var VERSION = "4.0.522";
2
+ var VERSION = "4.0.524";
3
3
  export {
4
4
  VERSION
5
5
  };
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "url": "https://github.com/remotion-dev/remotion/tree/main/packages/core"
4
4
  },
5
5
  "name": "remotion",
6
- "version": "4.0.522",
6
+ "version": "4.0.524",
7
7
  "description": "Make videos programmatically",
8
8
  "main": "dist/cjs/index.js",
9
9
  "types": "dist/cjs/index.d.ts",
@@ -35,7 +35,7 @@
35
35
  "react-dom": "19.2.3",
36
36
  "webpack": "5.105.0",
37
37
  "zod": "4.5.4",
38
- "@remotion/eslint-config-internal": "4.0.522",
38
+ "@remotion/eslint-config-internal": "4.0.524",
39
39
  "eslint": "9.19.0",
40
40
  "@typescript/native-preview": "7.0.0-dev.20260217.1"
41
41
  },