@remotion/media 4.0.354 → 4.0.356
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.
- package/dist/audio/audio-for-rendering.js +37 -3
- package/dist/audio/audio.js +1 -1
- package/dist/audio/props.d.ts +15 -0
- package/dist/audio-extraction/audio-iterator.d.ts +3 -2
- package/dist/audio-extraction/audio-iterator.js +13 -2
- package/dist/audio-extraction/audio-manager.d.ts +6 -5
- package/dist/audio-extraction/audio-manager.js +5 -3
- package/dist/audio-extraction/extract-audio.d.ts +3 -2
- package/dist/audio-extraction/extract-audio.js +12 -9
- package/dist/caches.d.ts +6 -5
- package/dist/convert-audiodata/apply-tonefrequency.d.ts +2 -0
- package/dist/convert-audiodata/apply-tonefrequency.js +44 -0
- package/dist/convert-audiodata/wsola.d.ts +13 -0
- package/dist/convert-audiodata/wsola.js +197 -0
- package/dist/esm/index.mjs +1519 -13269
- package/dist/extract-frame-and-audio.d.ts +3 -2
- package/dist/extract-frame-and-audio.js +60 -26
- package/dist/get-sink-weak.d.ts +13 -0
- package/dist/get-sink-weak.js +23 -0
- package/dist/index.d.ts +12 -3
- package/dist/index.js +11 -2
- package/dist/video/media-player.d.ts +8 -0
- package/dist/video/media-player.js +77 -19
- package/dist/video/props.d.ts +36 -18
- package/dist/video/video-for-preview.d.ts +13 -7
- package/dist/video/video-for-preview.js +115 -10
- package/dist/video/video-for-rendering.d.ts +23 -2
- package/dist/video/video-for-rendering.js +47 -4
- package/dist/video/video.js +13 -14
- package/dist/video-extraction/extract-frame-via-broadcast-channel.d.ts +3 -2
- package/dist/video-extraction/extract-frame-via-broadcast-channel.js +53 -4
- package/dist/video-extraction/extract-frame.d.ts +2 -3
- package/dist/video-extraction/extract-frame.js +11 -8
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +14 -9
- package/dist/video-extraction/get-frames-since-keyframe.js +72 -19
- package/package.json +5 -5
- package/dist/audio-for-rendering.d.ts +0 -3
- package/dist/audio-for-rendering.js +0 -94
- package/dist/audio.d.ts +0 -3
- package/dist/audio.js +0 -60
- package/dist/audiodata-to-array.d.ts +0 -0
- package/dist/audiodata-to-array.js +0 -1
- package/dist/convert-audiodata/data-types.d.ts +0 -1
- package/dist/convert-audiodata/data-types.js +0 -22
- package/dist/convert-audiodata/is-planar-format.d.ts +0 -1
- package/dist/convert-audiodata/is-planar-format.js +0 -3
- package/dist/convert-audiodata/log-audiodata.d.ts +0 -1
- package/dist/convert-audiodata/log-audiodata.js +0 -8
- package/dist/convert-audiodata/trim-audiodata.d.ts +0 -0
- package/dist/convert-audiodata/trim-audiodata.js +0 -1
- package/dist/deserialized-audiodata.d.ts +0 -15
- package/dist/deserialized-audiodata.js +0 -26
- package/dist/extract-audio.d.ts +0 -7
- package/dist/extract-audio.js +0 -98
- package/dist/extract-frame-via-broadcast-channel.d.ts +0 -15
- package/dist/extract-frame-via-broadcast-channel.js +0 -104
- package/dist/extract-frame.d.ts +0 -27
- package/dist/extract-frame.js +0 -21
- package/dist/extrct-audio.d.ts +0 -7
- package/dist/extrct-audio.js +0 -94
- package/dist/get-frames-since-keyframe.d.ts +0 -22
- package/dist/get-frames-since-keyframe.js +0 -41
- package/dist/keyframe-bank.d.ts +0 -25
- package/dist/keyframe-bank.js +0 -120
- package/dist/keyframe-manager.d.ts +0 -23
- package/dist/keyframe-manager.js +0 -170
- package/dist/new-video-for-rendering.d.ts +0 -3
- package/dist/new-video-for-rendering.js +0 -108
- package/dist/new-video.d.ts +0 -3
- package/dist/new-video.js +0 -37
- package/dist/props.d.ts +0 -29
- package/dist/props.js +0 -1
- package/dist/remember-actual-matroska-timestamps.d.ts +0 -4
- package/dist/remember-actual-matroska-timestamps.js +0 -19
- package/dist/serialize-videoframe.d.ts +0 -0
- package/dist/serialize-videoframe.js +0 -1
- package/dist/video/new-video-for-preview.d.ts +0 -10
- package/dist/video/new-video-for-preview.js +0 -108
- package/dist/video-extraction/media-player.d.ts +0 -64
- package/dist/video-extraction/media-player.js +0 -501
- package/dist/video-extraction/new-video-for-preview.d.ts +0 -10
- package/dist/video-extraction/new-video-for-preview.js +0 -114
- package/dist/video-for-rendering.d.ts +0 -3
- package/dist/video-for-rendering.js +0 -108
- package/dist/video.d.ts +0 -3
- package/dist/video.js +0 -37
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { LogLevel } from 'remotion';
|
|
2
2
|
import type { PcmS16AudioData } from './convert-audiodata/convert-audiodata';
|
|
3
|
-
export declare const extractFrameAndAudio: ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, }: {
|
|
3
|
+
export declare const extractFrameAndAudio: ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, audioStreamIndex, }: {
|
|
4
4
|
src: string;
|
|
5
5
|
timeInSeconds: number;
|
|
6
6
|
logLevel: LogLevel;
|
|
@@ -9,8 +9,9 @@ export declare const extractFrameAndAudio: ({ src, timeInSeconds, logLevel, dura
|
|
|
9
9
|
includeAudio: boolean;
|
|
10
10
|
includeVideo: boolean;
|
|
11
11
|
loop: boolean;
|
|
12
|
+
audioStreamIndex: number;
|
|
12
13
|
}) => Promise<{
|
|
13
14
|
frame: VideoFrame | null;
|
|
14
15
|
audio: PcmS16AudioData | null;
|
|
15
16
|
durationInSeconds: number | null;
|
|
16
|
-
}>;
|
|
17
|
+
} | "cannot-decode" | "unknown-container-format" | "network-error">;
|
|
@@ -1,29 +1,63 @@
|
|
|
1
1
|
import { extractAudio } from './audio-extraction/extract-audio';
|
|
2
2
|
import { extractFrame } from './video-extraction/extract-frame';
|
|
3
|
-
export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, }) => {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
3
|
+
export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, audioStreamIndex, }) => {
|
|
4
|
+
try {
|
|
5
|
+
const [frame, audio] = await Promise.all([
|
|
6
|
+
includeVideo
|
|
7
|
+
? extractFrame({
|
|
8
|
+
src,
|
|
9
|
+
timeInSeconds,
|
|
10
|
+
logLevel,
|
|
11
|
+
loop,
|
|
12
|
+
})
|
|
13
|
+
: null,
|
|
14
|
+
includeAudio
|
|
15
|
+
? extractAudio({
|
|
16
|
+
src,
|
|
17
|
+
timeInSeconds,
|
|
18
|
+
durationInSeconds,
|
|
19
|
+
logLevel,
|
|
20
|
+
loop,
|
|
21
|
+
playbackRate,
|
|
22
|
+
audioStreamIndex,
|
|
23
|
+
})
|
|
24
|
+
: null,
|
|
25
|
+
]);
|
|
26
|
+
if (frame === 'cannot-decode') {
|
|
27
|
+
return 'cannot-decode';
|
|
28
|
+
}
|
|
29
|
+
if (frame === 'unknown-container-format') {
|
|
30
|
+
return 'unknown-container-format';
|
|
31
|
+
}
|
|
32
|
+
if (audio === 'unknown-container-format') {
|
|
33
|
+
if (frame !== null) {
|
|
34
|
+
frame?.close();
|
|
35
|
+
}
|
|
36
|
+
return 'unknown-container-format';
|
|
37
|
+
}
|
|
38
|
+
if (audio === 'cannot-decode') {
|
|
39
|
+
if (frame !== null) {
|
|
40
|
+
frame?.close();
|
|
41
|
+
}
|
|
42
|
+
return 'cannot-decode';
|
|
43
|
+
}
|
|
44
|
+
return {
|
|
45
|
+
frame: frame?.toVideoFrame() ?? null,
|
|
46
|
+
audio: audio?.data ?? null,
|
|
47
|
+
durationInSeconds: audio?.durationInSeconds ?? null,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
const error = err;
|
|
52
|
+
if (
|
|
53
|
+
// Chrome
|
|
54
|
+
error.message.includes('Failed to fetch') ||
|
|
55
|
+
// Safari
|
|
56
|
+
error.message.includes('Load failed') ||
|
|
57
|
+
// Firefox
|
|
58
|
+
error.message.includes('NetworkError when attempting to fetch resource')) {
|
|
59
|
+
return 'network-error';
|
|
60
|
+
}
|
|
61
|
+
throw err;
|
|
62
|
+
}
|
|
29
63
|
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { LogLevel } from 'remotion';
|
|
2
|
+
import type { GetSink } from './video-extraction/get-frames-since-keyframe';
|
|
3
|
+
export declare const sinkPromises: Record<string, Promise<GetSink>>;
|
|
4
|
+
export declare const getSinkWeak: (src: string, logLevel: LogLevel) => Promise<{
|
|
5
|
+
getVideo: () => Promise<import("./video-extraction/get-frames-since-keyframe").VideoSinkResult>;
|
|
6
|
+
getAudio: (index: number) => Promise<import("./video-extraction/get-frames-since-keyframe").AudioSinkResult>;
|
|
7
|
+
actualMatroskaTimestamps: {
|
|
8
|
+
observeTimestamp: (startTime: number) => void;
|
|
9
|
+
getRealTimestamp: (observedTimestamp: number) => number | null;
|
|
10
|
+
};
|
|
11
|
+
isMatroska: boolean;
|
|
12
|
+
getDuration: () => Promise<number>;
|
|
13
|
+
}>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Internals } from 'remotion';
|
|
2
|
+
import { getSinks } from './video-extraction/get-frames-since-keyframe';
|
|
3
|
+
export const sinkPromises = {};
|
|
4
|
+
export const getSinkWeak = async (src, logLevel) => {
|
|
5
|
+
let promise = sinkPromises[src];
|
|
6
|
+
if (!promise) {
|
|
7
|
+
promise = getSinks(src);
|
|
8
|
+
sinkPromises[src] = promise;
|
|
9
|
+
}
|
|
10
|
+
let awaited = await promise;
|
|
11
|
+
let deferredValue = awaited.deref();
|
|
12
|
+
if (!deferredValue) {
|
|
13
|
+
Internals.Log.verbose({
|
|
14
|
+
logLevel,
|
|
15
|
+
tag: '@remotion/media',
|
|
16
|
+
}, `Sink for ${src} was garbage collected, creating new sink`);
|
|
17
|
+
promise = getSinks(src);
|
|
18
|
+
sinkPromises[src] = promise;
|
|
19
|
+
awaited = await promise;
|
|
20
|
+
deferredValue = awaited.deref();
|
|
21
|
+
}
|
|
22
|
+
return deferredValue;
|
|
23
|
+
};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Audio } from './audio/audio';
|
|
2
|
+
import { Video } from './video/video';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Now just `Audio`
|
|
5
|
+
*/
|
|
6
|
+
export declare const experimental_Audio: import("react").FC<import(".").AudioProps>;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Now just `Video`
|
|
9
|
+
*/
|
|
10
|
+
export declare const experimental_Video: import("react").FC<import(".").VideoProps>;
|
|
11
|
+
export { AudioProps, FallbackHtml5AudioProps } from './audio/props';
|
|
3
12
|
export { VideoProps } from './video/props';
|
|
4
|
-
export { Video
|
|
13
|
+
export { Audio, Video };
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,11 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
import { Audio } from './audio/audio';
|
|
2
|
+
import { Video } from './video/video';
|
|
3
|
+
/**
|
|
4
|
+
* @deprecated Now just `Audio`
|
|
5
|
+
*/
|
|
6
|
+
export const experimental_Audio = Audio;
|
|
7
|
+
/**
|
|
8
|
+
* @deprecated Now just `Video`
|
|
9
|
+
*/
|
|
10
|
+
export const experimental_Video = Video;
|
|
11
|
+
export { Audio, Video };
|
|
@@ -5,6 +5,7 @@ export declare class MediaPlayer {
|
|
|
5
5
|
private context;
|
|
6
6
|
private src;
|
|
7
7
|
private logLevel;
|
|
8
|
+
private playbackRate;
|
|
8
9
|
private canvasSink;
|
|
9
10
|
private videoFrameIterator;
|
|
10
11
|
private nextFrame;
|
|
@@ -15,6 +16,7 @@ export declare class MediaPlayer {
|
|
|
15
16
|
private sharedAudioContext;
|
|
16
17
|
private audioSyncAnchor;
|
|
17
18
|
private playing;
|
|
19
|
+
private muted;
|
|
18
20
|
private animationFrameId;
|
|
19
21
|
private videoAsyncId;
|
|
20
22
|
private initialized;
|
|
@@ -24,6 +26,7 @@ export declare class MediaPlayer {
|
|
|
24
26
|
private audioBufferHealth;
|
|
25
27
|
private audioIteratorStarted;
|
|
26
28
|
private readonly HEALTHY_BUFER_THRESHOLD_SECONDS;
|
|
29
|
+
private onVideoFrameCallback?;
|
|
27
30
|
constructor({ canvas, src, logLevel, sharedAudioContext, }: {
|
|
28
31
|
canvas: HTMLCanvasElement;
|
|
29
32
|
src: string;
|
|
@@ -40,10 +43,15 @@ export declare class MediaPlayer {
|
|
|
40
43
|
seekTo(time: number): Promise<void>;
|
|
41
44
|
play(): Promise<void>;
|
|
42
45
|
pause(): void;
|
|
46
|
+
setMuted(muted: boolean): void;
|
|
47
|
+
setVolume(volume: number): void;
|
|
48
|
+
setPlaybackRate(rate: number): Promise<void>;
|
|
43
49
|
dispose(): void;
|
|
44
50
|
private getPlaybackTime;
|
|
51
|
+
private getAdjustedTimestamp;
|
|
45
52
|
private scheduleAudioChunk;
|
|
46
53
|
onBufferingChange(callback: (isBuffering: boolean) => void): void;
|
|
54
|
+
onVideoFrame(callback: (frame: CanvasImageSource) => void): void;
|
|
47
55
|
private canRenderVideo;
|
|
48
56
|
private startRenderLoop;
|
|
49
57
|
private stopRenderLoop;
|
|
@@ -2,6 +2,7 @@ import { ALL_FORMATS, AudioBufferSink, CanvasSink, Input, UrlSource, } from 'med
|
|
|
2
2
|
import { Internals } from 'remotion';
|
|
3
3
|
import { sleep, withTimeout } from './timeout-utils';
|
|
4
4
|
export const SEEK_THRESHOLD = 0.05;
|
|
5
|
+
const AUDIO_BUFFER_TOLERANCE_THRESHOLD = 0.1;
|
|
5
6
|
export class MediaPlayer {
|
|
6
7
|
constructor({ canvas, src, logLevel, sharedAudioContext, }) {
|
|
7
8
|
this.canvasSink = null;
|
|
@@ -14,6 +15,7 @@ export class MediaPlayer {
|
|
|
14
15
|
// audioDelay = mediaTimestamp + audioSyncAnchor - sharedAudioContext.currentTime
|
|
15
16
|
this.audioSyncAnchor = 0;
|
|
16
17
|
this.playing = false;
|
|
18
|
+
this.muted = false;
|
|
17
19
|
this.animationFrameId = null;
|
|
18
20
|
this.videoAsyncId = 0;
|
|
19
21
|
this.initialized = false;
|
|
@@ -38,7 +40,7 @@ export class MediaPlayer {
|
|
|
38
40
|
this.animationFrameId = null;
|
|
39
41
|
}
|
|
40
42
|
};
|
|
41
|
-
this.startAudioIterator = async (
|
|
43
|
+
this.startAudioIterator = async (startFromSecond) => {
|
|
42
44
|
if (!this.hasAudio())
|
|
43
45
|
return;
|
|
44
46
|
// Clean up existing audio iterator
|
|
@@ -46,8 +48,8 @@ export class MediaPlayer {
|
|
|
46
48
|
this.audioIteratorStarted = false;
|
|
47
49
|
this.audioBufferHealth = 0;
|
|
48
50
|
try {
|
|
49
|
-
this.audioBufferIterator = this.audioSink.buffers(
|
|
50
|
-
this.runAudioIterator();
|
|
51
|
+
this.audioBufferIterator = this.audioSink.buffers(startFromSecond);
|
|
52
|
+
this.runAudioIterator(startFromSecond);
|
|
51
53
|
}
|
|
52
54
|
catch (error) {
|
|
53
55
|
Internals.Log.error({ logLevel: this.logLevel, tag: '@remotion/media' }, '[MediaPlayer] Failed to start audio iterator', error);
|
|
@@ -70,6 +72,9 @@ export class MediaPlayer {
|
|
|
70
72
|
if (firstFrame) {
|
|
71
73
|
Internals.Log.trace({ logLevel: this.logLevel, tag: '@remotion/media' }, `[MediaPlayer] Drew initial frame ${firstFrame.timestamp.toFixed(3)}s`);
|
|
72
74
|
this.context.drawImage(firstFrame.canvas, 0, 0);
|
|
75
|
+
if (this.onVideoFrameCallback) {
|
|
76
|
+
this.onVideoFrameCallback(this.canvas);
|
|
77
|
+
}
|
|
73
78
|
}
|
|
74
79
|
this.nextFrame = secondFrame ?? null;
|
|
75
80
|
if (secondFrame) {
|
|
@@ -90,7 +95,8 @@ export class MediaPlayer {
|
|
|
90
95
|
if (!newNextFrame) {
|
|
91
96
|
break;
|
|
92
97
|
}
|
|
93
|
-
if (newNextFrame.timestamp <=
|
|
98
|
+
if (this.getAdjustedTimestamp(newNextFrame.timestamp) <=
|
|
99
|
+
this.getPlaybackTime()) {
|
|
94
100
|
continue;
|
|
95
101
|
}
|
|
96
102
|
else {
|
|
@@ -106,7 +112,7 @@ export class MediaPlayer {
|
|
|
106
112
|
};
|
|
107
113
|
this.bufferingStartedAtMs = null;
|
|
108
114
|
this.minBufferingTimeoutMs = 500;
|
|
109
|
-
this.runAudioIterator = async () => {
|
|
115
|
+
this.runAudioIterator = async (startFromSecond) => {
|
|
110
116
|
if (!this.hasAudio() || !this.audioBufferIterator)
|
|
111
117
|
return;
|
|
112
118
|
try {
|
|
@@ -129,20 +135,28 @@ export class MediaPlayer {
|
|
|
129
135
|
}
|
|
130
136
|
const { buffer, timestamp, duration } = result.value;
|
|
131
137
|
totalBufferDuration += duration;
|
|
132
|
-
this.audioBufferHealth = Math.max(0, totalBufferDuration);
|
|
133
|
-
this.maybeResumeFromBuffering(totalBufferDuration);
|
|
134
|
-
if (this.playing) {
|
|
138
|
+
this.audioBufferHealth = Math.max(0, totalBufferDuration / this.playbackRate);
|
|
139
|
+
this.maybeResumeFromBuffering(totalBufferDuration / this.playbackRate);
|
|
140
|
+
if (this.playing && !this.muted) {
|
|
135
141
|
if (isFirstBuffer) {
|
|
136
142
|
this.audioSyncAnchor =
|
|
137
|
-
this.sharedAudioContext.currentTime -
|
|
143
|
+
this.sharedAudioContext.currentTime -
|
|
144
|
+
this.getAdjustedTimestamp(timestamp);
|
|
138
145
|
isFirstBuffer = false;
|
|
139
146
|
}
|
|
147
|
+
// if timestamp is less than timeToSeek, skip
|
|
148
|
+
// context: for some reason, mediabunny returns buffer at 9.984s, when requested at 10s
|
|
149
|
+
if (timestamp < startFromSecond - AUDIO_BUFFER_TOLERANCE_THRESHOLD) {
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
140
152
|
this.scheduleAudioChunk(buffer, timestamp);
|
|
141
153
|
}
|
|
142
|
-
if (timestamp - this.getPlaybackTime() >=
|
|
154
|
+
if (this.getAdjustedTimestamp(timestamp) - this.getPlaybackTime() >=
|
|
155
|
+
1) {
|
|
143
156
|
await new Promise((resolve) => {
|
|
144
157
|
const check = () => {
|
|
145
|
-
if (timestamp - this.getPlaybackTime() <
|
|
158
|
+
if (this.getAdjustedTimestamp(timestamp) - this.getPlaybackTime() <
|
|
159
|
+
1) {
|
|
146
160
|
resolve();
|
|
147
161
|
}
|
|
148
162
|
else {
|
|
@@ -162,6 +176,7 @@ export class MediaPlayer {
|
|
|
162
176
|
this.src = src;
|
|
163
177
|
this.logLevel = logLevel ?? 'info';
|
|
164
178
|
this.sharedAudioContext = sharedAudioContext;
|
|
179
|
+
this.playbackRate = 1;
|
|
165
180
|
const context = canvas.getContext('2d', {
|
|
166
181
|
alpha: false,
|
|
167
182
|
desynchronized: true,
|
|
@@ -211,8 +226,9 @@ export class MediaPlayer {
|
|
|
211
226
|
this.audioSyncAnchor = this.sharedAudioContext.currentTime - startTime;
|
|
212
227
|
}
|
|
213
228
|
this.initialized = true;
|
|
214
|
-
|
|
215
|
-
await this.
|
|
229
|
+
const mediaTime = startTime * this.playbackRate;
|
|
230
|
+
await this.startAudioIterator(mediaTime);
|
|
231
|
+
await this.startVideoIterator(mediaTime);
|
|
216
232
|
this.startRenderLoop();
|
|
217
233
|
}
|
|
218
234
|
catch (error) {
|
|
@@ -240,12 +256,14 @@ export class MediaPlayer {
|
|
|
240
256
|
const currentPlaybackTime = this.getPlaybackTime();
|
|
241
257
|
const isSignificantSeek = Math.abs(newTime - currentPlaybackTime) > SEEK_THRESHOLD;
|
|
242
258
|
if (isSignificantSeek) {
|
|
259
|
+
this.nextFrame = null;
|
|
243
260
|
this.audioSyncAnchor = this.sharedAudioContext.currentTime - newTime;
|
|
244
261
|
if (this.audioSink) {
|
|
245
262
|
await this.cleanAudioIteratorAndNodes();
|
|
246
263
|
}
|
|
247
|
-
|
|
248
|
-
await this.
|
|
264
|
+
const mediaTime = newTime * this.playbackRate;
|
|
265
|
+
await this.startAudioIterator(mediaTime);
|
|
266
|
+
await this.startVideoIterator(mediaTime);
|
|
249
267
|
}
|
|
250
268
|
if (!this.playing) {
|
|
251
269
|
this.render();
|
|
@@ -267,6 +285,30 @@ export class MediaPlayer {
|
|
|
267
285
|
this.cleanupAudioQueue();
|
|
268
286
|
this.stopRenderLoop();
|
|
269
287
|
}
|
|
288
|
+
setMuted(muted) {
|
|
289
|
+
this.muted = muted;
|
|
290
|
+
if (muted) {
|
|
291
|
+
this.cleanupAudioQueue();
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
setVolume(volume) {
|
|
295
|
+
if (!this.gainNode) {
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
const appliedVolume = Math.max(0, volume);
|
|
299
|
+
this.gainNode.gain.value = appliedVolume;
|
|
300
|
+
}
|
|
301
|
+
async setPlaybackRate(rate) {
|
|
302
|
+
if (this.playbackRate === rate)
|
|
303
|
+
return;
|
|
304
|
+
this.playbackRate = rate;
|
|
305
|
+
if (this.hasAudio() && this.playing) {
|
|
306
|
+
const currentPlaybackTime = this.getPlaybackTime();
|
|
307
|
+
const mediaTime = currentPlaybackTime * rate;
|
|
308
|
+
await this.cleanAudioIteratorAndNodes();
|
|
309
|
+
await this.startAudioIterator(mediaTime);
|
|
310
|
+
}
|
|
311
|
+
}
|
|
270
312
|
dispose() {
|
|
271
313
|
this.input?.dispose();
|
|
272
314
|
this.stopRenderLoop();
|
|
@@ -277,11 +319,16 @@ export class MediaPlayer {
|
|
|
277
319
|
getPlaybackTime() {
|
|
278
320
|
return this.sharedAudioContext.currentTime - this.audioSyncAnchor;
|
|
279
321
|
}
|
|
322
|
+
getAdjustedTimestamp(mediaTimestamp) {
|
|
323
|
+
return mediaTimestamp / this.playbackRate;
|
|
324
|
+
}
|
|
280
325
|
scheduleAudioChunk(buffer, mediaTimestamp) {
|
|
281
|
-
const
|
|
326
|
+
const adjustedTimestamp = this.getAdjustedTimestamp(mediaTimestamp);
|
|
327
|
+
const targetTime = adjustedTimestamp + this.audioSyncAnchor;
|
|
282
328
|
const delay = targetTime - this.sharedAudioContext.currentTime;
|
|
283
329
|
const node = this.sharedAudioContext.createBufferSource();
|
|
284
330
|
node.buffer = buffer;
|
|
331
|
+
node.playbackRate.value = this.playbackRate;
|
|
285
332
|
node.connect(this.gainNode);
|
|
286
333
|
if (delay >= 0) {
|
|
287
334
|
node.start(targetTime);
|
|
@@ -295,9 +342,16 @@ export class MediaPlayer {
|
|
|
295
342
|
onBufferingChange(callback) {
|
|
296
343
|
this.onBufferingChangeCallback = callback;
|
|
297
344
|
}
|
|
345
|
+
onVideoFrame(callback) {
|
|
346
|
+
this.onVideoFrameCallback = callback;
|
|
347
|
+
if (this.initialized && callback) {
|
|
348
|
+
callback(this.canvas);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
298
351
|
canRenderVideo() {
|
|
299
|
-
return (this.
|
|
300
|
-
this.
|
|
352
|
+
return (!this.hasAudio() ||
|
|
353
|
+
(this.audioIteratorStarted &&
|
|
354
|
+
this.audioBufferHealth >= this.HEALTHY_BUFER_THRESHOLD_SECONDS));
|
|
301
355
|
}
|
|
302
356
|
startRenderLoop() {
|
|
303
357
|
if (this.animationFrameId !== null) {
|
|
@@ -315,10 +369,14 @@ export class MediaPlayer {
|
|
|
315
369
|
return (!this.isBuffering &&
|
|
316
370
|
this.canRenderVideo() &&
|
|
317
371
|
this.nextFrame !== null &&
|
|
318
|
-
this.nextFrame.timestamp <=
|
|
372
|
+
this.getAdjustedTimestamp(this.nextFrame.timestamp) <=
|
|
373
|
+
this.getPlaybackTime());
|
|
319
374
|
}
|
|
320
375
|
drawCurrentFrame() {
|
|
321
376
|
this.context.drawImage(this.nextFrame.canvas, 0, 0);
|
|
377
|
+
if (this.onVideoFrameCallback) {
|
|
378
|
+
this.onVideoFrameCallback(this.canvas);
|
|
379
|
+
}
|
|
322
380
|
this.nextFrame = null;
|
|
323
381
|
this.updateNextFrame();
|
|
324
382
|
}
|
package/dist/video/props.d.ts
CHANGED
|
@@ -1,24 +1,42 @@
|
|
|
1
1
|
import type { LogLevel, LoopVolumeCurveBehavior, OnVideoFrame, VolumeProp } from 'remotion';
|
|
2
|
-
export type
|
|
2
|
+
export type FallbackOffthreadVideoProps = {
|
|
3
|
+
acceptableTimeShiftInSeconds?: number;
|
|
4
|
+
toneFrequency?: number;
|
|
5
|
+
transparent?: boolean;
|
|
6
|
+
toneMapped?: boolean;
|
|
7
|
+
onError?: (err: Error) => void;
|
|
8
|
+
};
|
|
9
|
+
type MandatoryVideoProps = {
|
|
3
10
|
src: string;
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
11
|
+
};
|
|
12
|
+
type OuterVideoProps = {
|
|
13
|
+
trimBefore: number | undefined;
|
|
14
|
+
trimAfter: number | undefined;
|
|
15
|
+
};
|
|
16
|
+
type OptionalVideoProps = {
|
|
17
|
+
className: string | undefined;
|
|
18
|
+
volume: VolumeProp;
|
|
19
|
+
loopVolumeCurveBehavior: LoopVolumeCurveBehavior;
|
|
20
|
+
name: string | undefined;
|
|
21
|
+
onVideoFrame: OnVideoFrame | undefined;
|
|
22
|
+
playbackRate: number;
|
|
23
|
+
muted: boolean;
|
|
24
|
+
delayRenderRetries: number | null;
|
|
25
|
+
delayRenderTimeoutInMilliseconds: number | null;
|
|
26
|
+
style: React.CSSProperties;
|
|
18
27
|
/**
|
|
19
28
|
* @deprecated For internal use only
|
|
20
29
|
*/
|
|
21
|
-
stack
|
|
22
|
-
logLevel
|
|
23
|
-
loop
|
|
30
|
+
stack: string | undefined;
|
|
31
|
+
logLevel: LogLevel;
|
|
32
|
+
loop: boolean;
|
|
33
|
+
audioStreamIndex: number;
|
|
34
|
+
disallowFallbackToOffthreadVideo: boolean;
|
|
35
|
+
fallbackOffthreadVideoProps: FallbackOffthreadVideoProps;
|
|
36
|
+
trimAfter: number | undefined;
|
|
37
|
+
trimBefore: number | undefined;
|
|
38
|
+
showInTimeline: boolean;
|
|
24
39
|
};
|
|
40
|
+
export type InnerVideoProps = MandatoryVideoProps & OuterVideoProps & OptionalVideoProps;
|
|
41
|
+
export type VideoProps = MandatoryVideoProps & Partial<OuterVideoProps> & Partial<OptionalVideoProps>;
|
|
42
|
+
export {};
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import type { LogLevel } from 'remotion';
|
|
3
|
-
type
|
|
2
|
+
import type { LogLevel, LoopVolumeCurveBehavior, OnVideoFrame, VolumeProp } from 'remotion';
|
|
3
|
+
type InnerVideoProps = {
|
|
4
|
+
readonly className: string | undefined;
|
|
5
|
+
readonly loop: boolean;
|
|
4
6
|
readonly src: string;
|
|
5
|
-
readonly
|
|
6
|
-
readonly
|
|
7
|
-
readonly
|
|
8
|
-
readonly
|
|
7
|
+
readonly logLevel: LogLevel;
|
|
8
|
+
readonly muted: boolean;
|
|
9
|
+
readonly name: string | undefined;
|
|
10
|
+
readonly volume: VolumeProp;
|
|
11
|
+
readonly loopVolumeCurveBehavior: LoopVolumeCurveBehavior;
|
|
12
|
+
readonly onVideoFrame: OnVideoFrame | undefined;
|
|
13
|
+
readonly playbackRate: number;
|
|
14
|
+
readonly style: React.CSSProperties;
|
|
9
15
|
};
|
|
10
|
-
export declare const
|
|
16
|
+
export declare const VideoForPreview: React.FC<InnerVideoProps>;
|
|
11
17
|
export {};
|