@remotion/media 4.0.361 → 4.0.362

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.
@@ -226,11 +226,18 @@ function isNetworkError(error) {
226
226
 
227
227
  // src/video/timeout-utils.ts
228
228
  var sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
229
+
230
+ class TimeoutError extends Error {
231
+ constructor(message = "Operation timed out") {
232
+ super(message);
233
+ this.name = "TimeoutError";
234
+ }
235
+ }
229
236
  function withTimeout(promise, timeoutMs, errorMessage = "Operation timed out") {
230
237
  let timeoutId = null;
231
238
  const timeoutPromise = new Promise((_, reject) => {
232
239
  timeoutId = window.setTimeout(() => {
233
- reject(new Error(errorMessage));
240
+ reject(new TimeoutError(errorMessage));
234
241
  }, timeoutMs);
235
242
  });
236
243
  return Promise.race([
@@ -280,6 +287,7 @@ class MediaPlayer {
280
287
  audioBufferHealth = 0;
281
288
  audioIteratorStarted = false;
282
289
  HEALTHY_BUFER_THRESHOLD_SECONDS = 1;
290
+ mediaEnded = false;
283
291
  onVideoFrameCallback;
284
292
  constructor({
285
293
  canvas,
@@ -440,6 +448,8 @@ class MediaPlayer {
440
448
  src: this.src
441
449
  });
442
450
  if (newTime === null) {
451
+ this.videoAsyncId++;
452
+ this.nextFrame = null;
443
453
  this.clearCanvas();
444
454
  await this.cleanAudioIteratorAndNodes();
445
455
  return;
@@ -449,6 +459,7 @@ class MediaPlayer {
449
459
  if (isSignificantSeek) {
450
460
  this.nextFrame = null;
451
461
  this.audioSyncAnchor = this.sharedAudioContext.currentTime - newTime;
462
+ this.mediaEnded = false;
452
463
  if (this.audioSink) {
453
464
  await this.cleanAudioIteratorAndNodes();
454
465
  }
@@ -646,6 +657,7 @@ class MediaPlayer {
646
657
  while (true) {
647
658
  const newNextFrame = (await this.videoFrameIterator.next()).value ?? null;
648
659
  if (!newNextFrame) {
660
+ this.mediaEnded = true;
649
661
  break;
650
662
  }
651
663
  const playbackTime = this.getPlaybackTime();
@@ -716,12 +728,15 @@ class MediaPlayer {
716
728
  let result;
717
729
  try {
718
730
  result = await withTimeout(this.audioBufferIterator.next(), BUFFERING_TIMEOUT_MS, "Iterator timeout");
719
- } catch {
720
- this.setBufferingState(true);
731
+ } catch (error) {
732
+ if (error instanceof TimeoutError && !this.mediaEnded) {
733
+ this.setBufferingState(true);
734
+ }
721
735
  await sleep(10);
722
736
  continue;
723
737
  }
724
738
  if (result.done || !result.value) {
739
+ this.mediaEnded = true;
725
740
  break;
726
741
  }
727
742
  const { buffer, timestamp, duration } = result.value;
@@ -45,6 +45,7 @@ export declare class MediaPlayer {
45
45
  private audioBufferHealth;
46
46
  private audioIteratorStarted;
47
47
  private readonly HEALTHY_BUFER_THRESHOLD_SECONDS;
48
+ private mediaEnded;
48
49
  private onVideoFrameCallback?;
49
50
  constructor({ canvas, src, logLevel, sharedAudioContext, loop, trimBefore, trimAfter, playbackRate, audioStreamIndex, fps, }: {
50
51
  canvas: HTMLCanvasElement | null;
@@ -2,7 +2,7 @@ import { ALL_FORMATS, AudioBufferSink, CanvasSink, Input, UrlSource, } from 'med
2
2
  import { Internals } from 'remotion';
3
3
  import { getTimeInSeconds } from '../get-time-in-seconds';
4
4
  import { isNetworkError } from '../is-network-error';
5
- import { sleep, withTimeout } from './timeout-utils';
5
+ import { sleep, TimeoutError, withTimeout } from './timeout-utils';
6
6
  export const SEEK_THRESHOLD = 0.05;
7
7
  const AUDIO_BUFFER_TOLERANCE_THRESHOLD = 0.1;
8
8
  export class MediaPlayer {
@@ -30,6 +30,7 @@ export class MediaPlayer {
30
30
  this.audioBufferHealth = 0;
31
31
  this.audioIteratorStarted = false;
32
32
  this.HEALTHY_BUFER_THRESHOLD_SECONDS = 1;
33
+ this.mediaEnded = false;
33
34
  this.input = null;
34
35
  this.render = () => {
35
36
  if (this.isBuffering) {
@@ -100,6 +101,7 @@ export class MediaPlayer {
100
101
  while (true) {
101
102
  const newNextFrame = (await this.videoFrameIterator.next()).value ?? null;
102
103
  if (!newNextFrame) {
104
+ this.mediaEnded = true;
103
105
  break;
104
106
  }
105
107
  const playbackTime = this.getPlaybackTime();
@@ -138,12 +140,16 @@ export class MediaPlayer {
138
140
  try {
139
141
  result = await withTimeout(this.audioBufferIterator.next(), BUFFERING_TIMEOUT_MS, 'Iterator timeout');
140
142
  }
141
- catch {
142
- this.setBufferingState(true);
143
+ catch (error) {
144
+ if (error instanceof TimeoutError && !this.mediaEnded) {
145
+ this.setBufferingState(true);
146
+ }
143
147
  await sleep(10);
144
148
  continue;
145
149
  }
150
+ // media has ended
146
151
  if (result.done || !result.value) {
152
+ this.mediaEnded = true;
147
153
  break;
148
154
  }
149
155
  const { buffer, timestamp, duration } = result.value;
@@ -337,6 +343,9 @@ export class MediaPlayer {
337
343
  src: this.src,
338
344
  });
339
345
  if (newTime === null) {
346
+ // invalidate in-flight video operations
347
+ this.videoAsyncId++;
348
+ this.nextFrame = null;
340
349
  this.clearCanvas();
341
350
  await this.cleanAudioIteratorAndNodes();
342
351
  return;
@@ -347,6 +356,7 @@ export class MediaPlayer {
347
356
  if (isSignificantSeek) {
348
357
  this.nextFrame = null;
349
358
  this.audioSyncAnchor = this.sharedAudioContext.currentTime - newTime;
359
+ this.mediaEnded = false;
350
360
  if (this.audioSink) {
351
361
  await this.cleanAudioIteratorAndNodes();
352
362
  }
@@ -1,2 +1,5 @@
1
1
  export declare const sleep: (ms: number) => Promise<unknown>;
2
+ export declare class TimeoutError extends Error {
3
+ constructor(message?: string);
4
+ }
2
5
  export declare function withTimeout<T>(promise: Promise<T>, timeoutMs: number, errorMessage?: string): Promise<T>;
@@ -1,10 +1,16 @@
1
1
  /* eslint-disable no-promise-executor-return */
2
2
  export const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
3
+ export class TimeoutError extends Error {
4
+ constructor(message = 'Operation timed out') {
5
+ super(message);
6
+ this.name = 'TimeoutError';
7
+ }
8
+ }
3
9
  export function withTimeout(promise, timeoutMs, errorMessage = 'Operation timed out') {
4
10
  let timeoutId = null;
5
11
  const timeoutPromise = new Promise((_, reject) => {
6
12
  timeoutId = window.setTimeout(() => {
7
- reject(new Error(errorMessage));
13
+ reject(new TimeoutError(errorMessage));
8
14
  }, timeoutMs);
9
15
  });
10
16
  return Promise.race([
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remotion/media",
3
- "version": "4.0.361",
3
+ "version": "4.0.362",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "module": "dist/esm/index.mjs",
@@ -22,7 +22,7 @@
22
22
  },
23
23
  "dependencies": {
24
24
  "mediabunny": "1.23.0",
25
- "remotion": "4.0.361",
25
+ "remotion": "4.0.362",
26
26
  "webdriverio": "9.19.2"
27
27
  },
28
28
  "peerDependencies": {
@@ -30,7 +30,7 @@
30
30
  "react-dom": ">=16.8.0"
31
31
  },
32
32
  "devDependencies": {
33
- "@remotion/eslint-config-internal": "4.0.361",
33
+ "@remotion/eslint-config-internal": "4.0.362",
34
34
  "@vitest/browser": "^3.2.4",
35
35
  "eslint": "9.19.0",
36
36
  "react": "19.0.0",