@remotion/media 4.0.401 → 4.0.403
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-preview.js +2 -4
- package/dist/audio/audio-for-rendering.js +8 -6
- package/dist/audio-extraction/audio-iterator.js +3 -3
- package/dist/audio-extraction/audio-manager.js +9 -1
- package/dist/caches.d.ts +6 -7
- package/dist/caches.js +2 -2
- package/dist/debug-overlay/preview-overlay.d.ts +2 -2
- package/dist/debug-overlay/preview-overlay.js +5 -3
- package/dist/esm/index.mjs +376 -355
- package/dist/extract-frame-and-audio.js +14 -29
- package/dist/media-player.d.ts +3 -3
- package/dist/media-player.js +38 -24
- package/dist/video/video-for-preview.js +0 -2
- package/dist/video/video.js +2 -4
- package/dist/video-extraction/extract-frame.d.ts +1 -1
- package/dist/video-extraction/extract-frame.js +9 -10
- package/dist/video-extraction/get-frames-since-keyframe.d.ts +2 -12
- package/dist/video-extraction/get-frames-since-keyframe.js +14 -17
- package/dist/video-extraction/keyframe-bank.d.ts +12 -10
- package/dist/video-extraction/keyframe-bank.js +93 -52
- package/dist/video-extraction/keyframe-manager.d.ts +6 -7
- package/dist/video-extraction/keyframe-manager.js +72 -77
- package/package.json +3 -3
|
@@ -4,7 +4,7 @@ import { extractFrame } from './video-extraction/extract-frame';
|
|
|
4
4
|
import { rotateFrame } from './video-extraction/rotate-frame';
|
|
5
5
|
export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durationInSeconds, playbackRate, includeAudio, includeVideo, loop, audioStreamIndex, trimAfter, trimBefore, fps, maxCacheSize, }) => {
|
|
6
6
|
try {
|
|
7
|
-
const [
|
|
7
|
+
const [video, audio] = await Promise.all([
|
|
8
8
|
includeVideo
|
|
9
9
|
? extractFrame({
|
|
10
10
|
src,
|
|
@@ -34,59 +34,44 @@ export const extractFrameAndAudio = async ({ src, timeInSeconds, logLevel, durat
|
|
|
34
34
|
})
|
|
35
35
|
: null,
|
|
36
36
|
]);
|
|
37
|
-
if (
|
|
37
|
+
if (video?.type === 'cannot-decode') {
|
|
38
38
|
return {
|
|
39
39
|
type: 'cannot-decode',
|
|
40
|
-
durationInSeconds:
|
|
40
|
+
durationInSeconds: video.durationInSeconds,
|
|
41
41
|
};
|
|
42
42
|
}
|
|
43
|
-
if (
|
|
43
|
+
if (video?.type === 'unknown-container-format') {
|
|
44
44
|
return { type: 'unknown-container-format' };
|
|
45
45
|
}
|
|
46
|
-
if (
|
|
46
|
+
if (video?.type === 'cannot-decode-alpha') {
|
|
47
47
|
return {
|
|
48
48
|
type: 'cannot-decode-alpha',
|
|
49
|
-
durationInSeconds:
|
|
49
|
+
durationInSeconds: video.durationInSeconds,
|
|
50
50
|
};
|
|
51
51
|
}
|
|
52
|
-
if (
|
|
52
|
+
if (video?.type === 'network-error') {
|
|
53
53
|
return { type: 'network-error' };
|
|
54
54
|
}
|
|
55
55
|
if (audio === 'unknown-container-format') {
|
|
56
|
-
if (frame !== null) {
|
|
57
|
-
frame?.frame?.close();
|
|
58
|
-
}
|
|
59
56
|
return { type: 'unknown-container-format' };
|
|
60
57
|
}
|
|
61
58
|
if (audio === 'network-error') {
|
|
62
|
-
if (frame !== null) {
|
|
63
|
-
frame?.frame?.close();
|
|
64
|
-
}
|
|
65
59
|
return { type: 'network-error' };
|
|
66
60
|
}
|
|
67
61
|
if (audio === 'cannot-decode') {
|
|
68
|
-
if (frame?.type === 'success' && frame.frame !== null) {
|
|
69
|
-
frame?.frame.close();
|
|
70
|
-
}
|
|
71
62
|
return {
|
|
72
63
|
type: 'cannot-decode',
|
|
73
|
-
durationInSeconds:
|
|
74
|
-
};
|
|
75
|
-
}
|
|
76
|
-
if (!frame?.frame) {
|
|
77
|
-
return {
|
|
78
|
-
type: 'success',
|
|
79
|
-
frame: null,
|
|
80
|
-
audio: audio?.data ?? null,
|
|
81
|
-
durationInSeconds: audio?.durationInSeconds ?? null,
|
|
64
|
+
durationInSeconds: video?.type === 'success' ? video.durationInSeconds : null,
|
|
82
65
|
};
|
|
83
66
|
}
|
|
84
67
|
return {
|
|
85
68
|
type: 'success',
|
|
86
|
-
frame:
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
69
|
+
frame: video?.sample
|
|
70
|
+
? await rotateFrame({
|
|
71
|
+
frame: video.sample.toVideoFrame(),
|
|
72
|
+
rotation: video.sample.rotation,
|
|
73
|
+
})
|
|
74
|
+
: null,
|
|
90
75
|
audio: audio?.data ?? null,
|
|
91
76
|
durationInSeconds: audio?.durationInSeconds ?? null,
|
|
92
77
|
};
|
package/dist/media-player.d.ts
CHANGED
|
@@ -46,7 +46,7 @@ export declare class MediaPlayer {
|
|
|
46
46
|
canvas: HTMLCanvasElement | OffscreenCanvas | null;
|
|
47
47
|
src: string;
|
|
48
48
|
logLevel: LogLevel;
|
|
49
|
-
sharedAudioContext: AudioContext;
|
|
49
|
+
sharedAudioContext: AudioContext | null;
|
|
50
50
|
loop: boolean;
|
|
51
51
|
trimBefore: number | undefined;
|
|
52
52
|
trimAfter: number | undefined;
|
|
@@ -78,7 +78,7 @@ export declare class MediaPlayer {
|
|
|
78
78
|
setTrimBefore(trimBefore: number | undefined, unloopedTimeInSeconds: number): void;
|
|
79
79
|
setTrimAfter(trimAfter: number | undefined, unloopedTimeInSeconds: number): void;
|
|
80
80
|
setDebugOverlay(debugOverlay: boolean): void;
|
|
81
|
-
private
|
|
81
|
+
private updateAudioTimeAfterPlaybackRateChange;
|
|
82
82
|
setPlaybackRate(rate: number): void;
|
|
83
83
|
setGlobalPlaybackRate(rate: number): void;
|
|
84
84
|
setFps(fps: number): void;
|
|
@@ -87,7 +87,7 @@ export declare class MediaPlayer {
|
|
|
87
87
|
setLoop(loop: boolean): void;
|
|
88
88
|
dispose(): Promise<void>;
|
|
89
89
|
private scheduleAudioNode;
|
|
90
|
-
private
|
|
90
|
+
private getAudioPlaybackTime;
|
|
91
91
|
private setPlaybackTime;
|
|
92
92
|
setVideoFrameCallback(callback: null | ((frame: CanvasImageSource) => void)): void;
|
|
93
93
|
private drawDebugOverlay;
|
package/dist/media-player.js
CHANGED
|
@@ -29,9 +29,12 @@ export class MediaPlayer {
|
|
|
29
29
|
return this.bufferState.delayPlayback();
|
|
30
30
|
};
|
|
31
31
|
this.scheduleAudioNode = (node, mediaTimestamp) => {
|
|
32
|
-
const currentTime = this.
|
|
32
|
+
const currentTime = this.getAudioPlaybackTime();
|
|
33
33
|
const delayWithoutPlaybackRate = mediaTimestamp - currentTime;
|
|
34
34
|
const delay = delayWithoutPlaybackRate / (this.playbackRate * this.globalPlaybackRate);
|
|
35
|
+
if (!this.sharedAudioContext) {
|
|
36
|
+
throw new Error('Shared audio context not found');
|
|
37
|
+
}
|
|
35
38
|
if (delay >= 0) {
|
|
36
39
|
node.start(this.sharedAudioContext.currentTime + delay);
|
|
37
40
|
}
|
|
@@ -45,8 +48,8 @@ export class MediaPlayer {
|
|
|
45
48
|
if (this.context && this.canvas) {
|
|
46
49
|
drawPreviewOverlay({
|
|
47
50
|
context: this.context,
|
|
48
|
-
audioTime: this.sharedAudioContext
|
|
49
|
-
audioContextState: this.sharedAudioContext
|
|
51
|
+
audioTime: this.sharedAudioContext?.currentTime ?? null,
|
|
52
|
+
audioContextState: this.sharedAudioContext?.state ?? null,
|
|
50
53
|
audioSyncAnchor: this.audioSyncAnchor,
|
|
51
54
|
audioIteratorManager: this.audioIteratorManager,
|
|
52
55
|
playing: this.playing,
|
|
@@ -56,7 +59,7 @@ export class MediaPlayer {
|
|
|
56
59
|
};
|
|
57
60
|
this.canvas = canvas ?? null;
|
|
58
61
|
this.src = src;
|
|
59
|
-
this.logLevel = logLevel
|
|
62
|
+
this.logLevel = logLevel;
|
|
60
63
|
this.sharedAudioContext = sharedAudioContext;
|
|
61
64
|
this.playbackRate = playbackRate;
|
|
62
65
|
this.globalPlaybackRate = globalPlaybackRate;
|
|
@@ -181,7 +184,7 @@ export class MediaPlayer {
|
|
|
181
184
|
throw new Error(`should have asserted that the time is not null`);
|
|
182
185
|
}
|
|
183
186
|
this.setPlaybackTime(startTime, this.playbackRate * this.globalPlaybackRate);
|
|
184
|
-
if (audioTrack) {
|
|
187
|
+
if (audioTrack && this.sharedAudioContext) {
|
|
185
188
|
this.audioIteratorManager = audioIteratorManager({
|
|
186
189
|
audioTrack,
|
|
187
190
|
delayPlaybackHandleIfNotPremounting: this.delayPlaybackHandleIfNotPremounting,
|
|
@@ -254,24 +257,25 @@ export class MediaPlayer {
|
|
|
254
257
|
if (nonce.isStale()) {
|
|
255
258
|
return;
|
|
256
259
|
}
|
|
257
|
-
const
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
}
|
|
260
|
+
const shouldSeekAudio = this.audioIteratorManager &&
|
|
261
|
+
this.sharedAudioContext &&
|
|
262
|
+
this.getAudioPlaybackTime() !== newTime;
|
|
261
263
|
await Promise.all([
|
|
262
264
|
this.videoIteratorManager?.seek({
|
|
263
265
|
newTime,
|
|
264
266
|
nonce,
|
|
265
267
|
}),
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
268
|
+
shouldSeekAudio
|
|
269
|
+
? this.audioIteratorManager?.seek({
|
|
270
|
+
newTime,
|
|
271
|
+
nonce,
|
|
272
|
+
fps: this.fps,
|
|
273
|
+
playbackRate: this.playbackRate * this.globalPlaybackRate,
|
|
274
|
+
getIsPlaying: () => this.playing,
|
|
275
|
+
scheduleAudioNode: this.scheduleAudioNode,
|
|
276
|
+
bufferState: this.bufferState,
|
|
277
|
+
})
|
|
278
|
+
: null,
|
|
275
279
|
]);
|
|
276
280
|
}
|
|
277
281
|
async play(time) {
|
|
@@ -300,7 +304,8 @@ export class MediaPlayer {
|
|
|
300
304
|
scheduleAudioNode: this.scheduleAudioNode,
|
|
301
305
|
});
|
|
302
306
|
}
|
|
303
|
-
if (this.sharedAudioContext
|
|
307
|
+
if (this.sharedAudioContext &&
|
|
308
|
+
this.sharedAudioContext.state === 'suspended') {
|
|
304
309
|
await this.sharedAudioContext.resume();
|
|
305
310
|
}
|
|
306
311
|
this.drawDebugOverlay();
|
|
@@ -359,11 +364,14 @@ export class MediaPlayer {
|
|
|
359
364
|
setDebugOverlay(debugOverlay) {
|
|
360
365
|
this.debugOverlay = debugOverlay;
|
|
361
366
|
}
|
|
362
|
-
|
|
367
|
+
updateAudioTimeAfterPlaybackRateChange() {
|
|
363
368
|
if (!this.audioIteratorManager) {
|
|
364
369
|
return;
|
|
365
370
|
}
|
|
366
|
-
|
|
371
|
+
if (!this.sharedAudioContext) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
this.setPlaybackTime(this.getAudioPlaybackTime(), this.playbackRate * this.globalPlaybackRate);
|
|
367
375
|
const iterator = this.audioIteratorManager.getAudioBufferIterator();
|
|
368
376
|
if (!iterator) {
|
|
369
377
|
return;
|
|
@@ -378,11 +386,11 @@ export class MediaPlayer {
|
|
|
378
386
|
}
|
|
379
387
|
setPlaybackRate(rate) {
|
|
380
388
|
this.playbackRate = rate;
|
|
381
|
-
this.
|
|
389
|
+
this.updateAudioTimeAfterPlaybackRateChange();
|
|
382
390
|
}
|
|
383
391
|
setGlobalPlaybackRate(rate) {
|
|
384
392
|
this.globalPlaybackRate = rate;
|
|
385
|
-
this.
|
|
393
|
+
this.updateAudioTimeAfterPlaybackRateChange();
|
|
386
394
|
}
|
|
387
395
|
setFps(fps) {
|
|
388
396
|
this.fps = fps;
|
|
@@ -414,7 +422,10 @@ export class MediaPlayer {
|
|
|
414
422
|
this.audioIteratorManager?.destroyIterator();
|
|
415
423
|
this.input.dispose();
|
|
416
424
|
}
|
|
417
|
-
|
|
425
|
+
getAudioPlaybackTime() {
|
|
426
|
+
if (!this.sharedAudioContext) {
|
|
427
|
+
throw new Error('Shared audio context not found');
|
|
428
|
+
}
|
|
418
429
|
return calculatePlaybackTime({
|
|
419
430
|
audioSyncAnchor: this.audioSyncAnchor,
|
|
420
431
|
currentTime: this.sharedAudioContext.currentTime,
|
|
@@ -422,6 +433,9 @@ export class MediaPlayer {
|
|
|
422
433
|
});
|
|
423
434
|
}
|
|
424
435
|
setPlaybackTime(time, playbackRate) {
|
|
436
|
+
if (!this.sharedAudioContext) {
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
425
439
|
this.audioSyncAnchor =
|
|
426
440
|
this.sharedAudioContext.currentTime - time / playbackRate;
|
|
427
441
|
}
|
|
@@ -79,8 +79,6 @@ const VideoForPreviewAssertedShowing = ({ src: unpreloadedSrc, style, playbackRa
|
|
|
79
79
|
useEffect(() => {
|
|
80
80
|
if (!sharedAudioContext)
|
|
81
81
|
return;
|
|
82
|
-
if (!sharedAudioContext.audioContext)
|
|
83
|
-
return;
|
|
84
82
|
try {
|
|
85
83
|
const player = new MediaPlayer({
|
|
86
84
|
canvas: canvasRef.current,
|
package/dist/video/video.js
CHANGED
|
@@ -27,9 +27,7 @@ const InnerVideo = ({ src, audioStreamIndex, className, delayRenderRetries, dela
|
|
|
27
27
|
return (_jsx(VideoForPreview, { audioStreamIndex: audioStreamIndex ?? 0, className: className, name: name, logLevel: logLevel, loop: loop, loopVolumeCurveBehavior: loopVolumeCurveBehavior, muted: muted, onVideoFrame: onVideoFrame, playbackRate: playbackRate, src: src, style: style, volume: volume, showInTimeline: showInTimeline, trimAfter: trimAfterValue, trimBefore: trimBeforeValue, stack: stack ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps, debugOverlay: debugOverlay ?? false, headless: headless ?? false }));
|
|
28
28
|
};
|
|
29
29
|
export const Video = ({ src, audioStreamIndex, className, delayRenderRetries, delayRenderTimeoutInMilliseconds, disallowFallbackToOffthreadVideo, fallbackOffthreadVideoProps, logLevel, loop, loopVolumeCurveBehavior, muted, name, onVideoFrame, playbackRate, showInTimeline, style, trimAfter, trimBefore, volume, stack, toneFrequency, debugOverlay, headless, }) => {
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
? (window.remotion_logLevel ?? 'info')
|
|
33
|
-
: 'info'), loop: loop ?? false, loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? 'repeat', muted: muted ?? false, name: name, onVideoFrame: onVideoFrame, playbackRate: playbackRate ?? 1, showInTimeline: showInTimeline ?? true, src: src, style: style ?? {}, trimAfter: trimAfter, trimBefore: trimBefore, volume: volume ?? 1, toneFrequency: toneFrequency ?? 1, stack: stack, debugOverlay: debugOverlay ?? false, headless: headless ?? false }));
|
|
30
|
+
const fallbackLogLevel = Internals.useLogLevel();
|
|
31
|
+
return (_jsx(InnerVideo, { audioStreamIndex: audioStreamIndex ?? 0, className: className, delayRenderRetries: delayRenderRetries ?? null, delayRenderTimeoutInMilliseconds: delayRenderTimeoutInMilliseconds ?? null, disallowFallbackToOffthreadVideo: disallowFallbackToOffthreadVideo ?? false, fallbackOffthreadVideoProps: fallbackOffthreadVideoProps ?? {}, logLevel: logLevel ?? fallbackLogLevel, loop: loop ?? false, loopVolumeCurveBehavior: loopVolumeCurveBehavior ?? 'repeat', muted: muted ?? false, name: name, onVideoFrame: onVideoFrame, playbackRate: playbackRate ?? 1, showInTimeline: showInTimeline ?? true, src: src, style: style ?? {}, trimAfter: trimAfter, trimBefore: trimBefore, volume: volume ?? 1, toneFrequency: toneFrequency ?? 1, stack: stack, debugOverlay: debugOverlay ?? false, headless: headless ?? false }));
|
|
34
32
|
};
|
|
35
33
|
Internals.addSequenceStackTraces(Video);
|
|
@@ -23,6 +23,12 @@ const extractFrameInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
23
23
|
if (video === 'network-error') {
|
|
24
24
|
return { type: 'network-error' };
|
|
25
25
|
}
|
|
26
|
+
if (video === 'cannot-decode-alpha') {
|
|
27
|
+
return {
|
|
28
|
+
type: 'cannot-decode-alpha',
|
|
29
|
+
durationInSeconds: mediaDurationInSeconds,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
26
32
|
const timeInSeconds = getTimeInSeconds({
|
|
27
33
|
loop,
|
|
28
34
|
mediaDurationInSeconds,
|
|
@@ -37,7 +43,7 @@ const extractFrameInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
37
43
|
if (timeInSeconds === null) {
|
|
38
44
|
return {
|
|
39
45
|
type: 'success',
|
|
40
|
-
|
|
46
|
+
sample: null,
|
|
41
47
|
durationInSeconds: await sink.getDuration(),
|
|
42
48
|
};
|
|
43
49
|
}
|
|
@@ -46,30 +52,23 @@ const extractFrameInternal = async ({ src, timeInSeconds: unloopedTimeInSeconds,
|
|
|
46
52
|
// Should be able to remove once upgraded to Chrome 145
|
|
47
53
|
try {
|
|
48
54
|
const keyframeBank = await keyframeManager.requestKeyframeBank({
|
|
49
|
-
packetSink: video.packetSink,
|
|
50
55
|
videoSampleSink: video.sampleSink,
|
|
51
56
|
timestamp: timeInSeconds,
|
|
52
57
|
src,
|
|
53
58
|
logLevel,
|
|
54
59
|
maxCacheSize,
|
|
55
60
|
});
|
|
56
|
-
if (keyframeBank === 'has-alpha') {
|
|
57
|
-
return {
|
|
58
|
-
type: 'cannot-decode-alpha',
|
|
59
|
-
durationInSeconds: await sink.getDuration(),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
61
|
if (!keyframeBank) {
|
|
63
62
|
return {
|
|
64
63
|
type: 'success',
|
|
65
|
-
|
|
64
|
+
sample: null,
|
|
66
65
|
durationInSeconds: await sink.getDuration(),
|
|
67
66
|
};
|
|
68
67
|
}
|
|
69
68
|
const frame = await keyframeBank.getFrameFromTimestamp(timeInSeconds);
|
|
70
69
|
return {
|
|
71
70
|
type: 'success',
|
|
72
|
-
frame,
|
|
71
|
+
sample: frame,
|
|
73
72
|
durationInSeconds: await sink.getDuration(),
|
|
74
73
|
};
|
|
75
74
|
}
|
|
@@ -1,15 +1,12 @@
|
|
|
1
|
-
import
|
|
2
|
-
import { AudioSampleSink, EncodedPacketSink, VideoSampleSink } from 'mediabunny';
|
|
3
|
-
import type { LogLevel } from 'remotion';
|
|
1
|
+
import { AudioSampleSink, VideoSampleSink } from 'mediabunny';
|
|
4
2
|
type VideoSinks = {
|
|
5
3
|
sampleSink: VideoSampleSink;
|
|
6
|
-
packetSink: EncodedPacketSink;
|
|
7
4
|
};
|
|
8
5
|
type AudioSinks = {
|
|
9
6
|
sampleSink: AudioSampleSink;
|
|
10
7
|
};
|
|
11
8
|
export type AudioSinkResult = AudioSinks | 'no-audio-track' | 'cannot-decode-audio' | 'unknown-container-format' | 'network-error';
|
|
12
|
-
export type VideoSinkResult = VideoSinks | 'no-video-track' | 'cannot-decode' | 'unknown-container-format' | 'network-error';
|
|
9
|
+
export type VideoSinkResult = VideoSinks | 'no-video-track' | 'cannot-decode' | 'cannot-decode-alpha' | 'unknown-container-format' | 'network-error';
|
|
13
10
|
export declare const getSinks: (src: string) => Promise<{
|
|
14
11
|
getVideo: () => Promise<VideoSinkResult>;
|
|
15
12
|
getAudio: (index: number) => Promise<AudioSinkResult>;
|
|
@@ -21,11 +18,4 @@ export declare const getSinks: (src: string) => Promise<{
|
|
|
21
18
|
getDuration: () => Promise<number>;
|
|
22
19
|
}>;
|
|
23
20
|
export type GetSink = Awaited<ReturnType<typeof getSinks>>;
|
|
24
|
-
export declare const getFramesSinceKeyframe: ({ packetSink, videoSampleSink, startPacket, logLevel, src, }: {
|
|
25
|
-
packetSink: EncodedPacketSink;
|
|
26
|
-
videoSampleSink: VideoSampleSink;
|
|
27
|
-
startPacket: EncodedPacket;
|
|
28
|
-
logLevel: LogLevel;
|
|
29
|
-
src: string;
|
|
30
|
-
}) => Promise<import("./keyframe-bank").KeyframeBank>;
|
|
31
21
|
export {};
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ALL_FORMATS, AudioSampleSink, EncodedPacketSink, Input, MATROSKA, UrlSource, VideoSampleSink, WEBM, } from 'mediabunny';
|
|
2
|
+
import { canBrowserUseWebGl2 } from '../browser-can-use-webgl2';
|
|
2
3
|
import { isNetworkError } from '../is-type-of-error';
|
|
3
|
-
import { makeKeyframeBank } from './keyframe-bank';
|
|
4
4
|
import { rememberActualMatroskaTimestamps } from './remember-actual-matroska-timestamps';
|
|
5
5
|
const getRetryDelay = (() => {
|
|
6
6
|
return null;
|
|
@@ -40,9 +40,20 @@ export const getSinks = async (src) => {
|
|
|
40
40
|
if (!canDecode) {
|
|
41
41
|
return 'cannot-decode';
|
|
42
42
|
}
|
|
43
|
+
const sampleSink = new VideoSampleSink(videoTrack);
|
|
44
|
+
const packetSink = new EncodedPacketSink(videoTrack);
|
|
45
|
+
// Try to get the keypacket at the requested timestamp.
|
|
46
|
+
// If it returns null (timestamp is before the first keypacket), fall back to the first packet.
|
|
47
|
+
// This matches mediabunny's internal behavior and handles videos that don't start at timestamp 0.
|
|
48
|
+
const startPacket = await packetSink.getFirstPacket({
|
|
49
|
+
verifyKeyPackets: true,
|
|
50
|
+
});
|
|
51
|
+
const hasAlpha = startPacket?.sideData.alpha;
|
|
52
|
+
if (hasAlpha && !canBrowserUseWebGl2()) {
|
|
53
|
+
return 'cannot-decode-alpha';
|
|
54
|
+
}
|
|
43
55
|
return {
|
|
44
|
-
sampleSink
|
|
45
|
-
packetSink: new EncodedPacketSink(videoTrack),
|
|
56
|
+
sampleSink,
|
|
46
57
|
};
|
|
47
58
|
};
|
|
48
59
|
let videoSinksPromise = null;
|
|
@@ -92,17 +103,3 @@ export const getSinks = async (src) => {
|
|
|
92
103
|
},
|
|
93
104
|
};
|
|
94
105
|
};
|
|
95
|
-
export const getFramesSinceKeyframe = async ({ packetSink, videoSampleSink, startPacket, logLevel, src, }) => {
|
|
96
|
-
const nextKeyPacket = await packetSink.getNextKeyPacket(startPacket, {
|
|
97
|
-
verifyKeyPackets: true,
|
|
98
|
-
});
|
|
99
|
-
const sampleIterator = videoSampleSink.samples(startPacket.timestamp, nextKeyPacket ? nextKeyPacket.timestamp : Infinity);
|
|
100
|
-
const keyframeBank = makeKeyframeBank({
|
|
101
|
-
startTimestampInSeconds: startPacket.timestamp,
|
|
102
|
-
endTimestampInSeconds: nextKeyPacket ? nextKeyPacket.timestamp : Infinity,
|
|
103
|
-
sampleIterator,
|
|
104
|
-
logLevel,
|
|
105
|
-
src,
|
|
106
|
-
});
|
|
107
|
-
return keyframeBank;
|
|
108
|
-
};
|
|
@@ -1,11 +1,9 @@
|
|
|
1
|
-
import type { VideoSample } from 'mediabunny';
|
|
1
|
+
import type { VideoSample, VideoSampleSink } from 'mediabunny';
|
|
2
2
|
import { type LogLevel } from 'remotion';
|
|
3
3
|
export type KeyframeBank = {
|
|
4
4
|
src: string;
|
|
5
|
-
startTimestampInSeconds: number;
|
|
6
|
-
endTimestampInSeconds: number;
|
|
7
5
|
getFrameFromTimestamp: (timestamp: number) => Promise<VideoSample | null>;
|
|
8
|
-
prepareForDeletion: (logLevel: LogLevel) => {
|
|
6
|
+
prepareForDeletion: (logLevel: LogLevel, reason: string) => {
|
|
9
7
|
framesDeleted: number;
|
|
10
8
|
};
|
|
11
9
|
deleteFramesBeforeTimestamp: ({ logLevel, timestampInSeconds, }: {
|
|
@@ -13,17 +11,21 @@ export type KeyframeBank = {
|
|
|
13
11
|
logLevel: LogLevel;
|
|
14
12
|
}) => void;
|
|
15
13
|
hasTimestampInSecond: (timestamp: number) => Promise<boolean>;
|
|
16
|
-
addFrame: (frame: VideoSample) => void;
|
|
14
|
+
addFrame: (frame: VideoSample, logLevel: LogLevel) => void;
|
|
17
15
|
getOpenFrameCount: () => {
|
|
18
16
|
size: number;
|
|
19
17
|
timestamps: number[];
|
|
20
18
|
};
|
|
21
19
|
getLastUsed: () => number;
|
|
20
|
+
canSatisfyTimestamp: (timestamp: number) => boolean;
|
|
21
|
+
getRangeOfTimestamps: () => {
|
|
22
|
+
firstTimestamp: number;
|
|
23
|
+
lastTimestamp: number;
|
|
24
|
+
} | null;
|
|
22
25
|
};
|
|
23
|
-
export declare const makeKeyframeBank: ({
|
|
24
|
-
startTimestampInSeconds: number;
|
|
25
|
-
endTimestampInSeconds: number;
|
|
26
|
-
sampleIterator: AsyncGenerator<VideoSample, void, unknown>;
|
|
26
|
+
export declare const makeKeyframeBank: ({ logLevel: parentLogLevel, src, videoSampleSink, requestedTimestamp, }: {
|
|
27
27
|
logLevel: LogLevel;
|
|
28
28
|
src: string;
|
|
29
|
-
|
|
29
|
+
videoSampleSink: VideoSampleSink;
|
|
30
|
+
requestedTimestamp: number;
|
|
31
|
+
}) => Promise<KeyframeBank>;
|