156 lines
6.4 KiB
JavaScript
156 lines
6.4 KiB
JavaScript
"use strict";
|
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
exports.internalExtractFrames = void 0;
|
|
const media_parser_1 = require("@remotion/media-parser");
|
|
const create_video_decoder_1 = require("./create-video-decoder");
|
|
const with_resolvers_1 = require("./create/with-resolvers");
|
|
const log_1 = require("./log");
|
|
const internalExtractFrames = ({ src, onFrame, signal, timestampsInSeconds, acknowledgeRemotionLicense, logLevel, parseMediaImplementation, }) => {
|
|
const controller = (0, media_parser_1.mediaParserController)();
|
|
const expectedFrames = [];
|
|
const resolvers = (0, with_resolvers_1.withResolvers)();
|
|
const abortListener = () => {
|
|
controller.abort();
|
|
resolvers.reject(new media_parser_1.MediaParserAbortError('Aborted by user'));
|
|
};
|
|
signal?.addEventListener('abort', abortListener, { once: true });
|
|
let dur = null;
|
|
let lastFrame;
|
|
let lastFrameEmitted;
|
|
parseMediaImplementation({
|
|
src: new URL(src, window.location.href),
|
|
acknowledgeRemotionLicense,
|
|
controller,
|
|
logLevel,
|
|
onDurationInSeconds(durationInSeconds) {
|
|
dur = durationInSeconds;
|
|
},
|
|
onVideoTrack: async ({ track, container }) => {
|
|
const timestampTargetsUnsorted = typeof timestampsInSeconds === 'function'
|
|
? await timestampsInSeconds({
|
|
track,
|
|
container,
|
|
durationInSeconds: dur,
|
|
})
|
|
: timestampsInSeconds;
|
|
const timestampTargets = timestampTargetsUnsorted.sort((a, b) => a - b);
|
|
if (timestampTargets.length === 0) {
|
|
throw new Error('expected at least one timestamp to extract but found zero');
|
|
}
|
|
controller.seek(timestampTargets[0]);
|
|
const decoder = await (0, create_video_decoder_1.createVideoDecoder)({
|
|
onFrame: (frame) => {
|
|
log_1.Log.trace(logLevel, 'Received frame with timestamp', frame.timestamp);
|
|
if (expectedFrames.length === 0) {
|
|
frame.close();
|
|
return;
|
|
}
|
|
if (frame.timestamp < expectedFrames[0] - 1) {
|
|
if (lastFrame) {
|
|
lastFrame.close();
|
|
}
|
|
lastFrame = frame;
|
|
return;
|
|
}
|
|
// A WebM might have a timestamp of 67000 but we request 66666
|
|
// See a test with this problem in it-tests/rendering/frame-accuracy.test.ts
|
|
// Solution: We allow a 10.000ms - 3.333ms = 6.667ms difference between the requested timestamp and the actual timestamp
|
|
if (expectedFrames[0] + 6667 < frame.timestamp &&
|
|
lastFrame &&
|
|
lastFrame !== lastFrameEmitted) {
|
|
onFrame(lastFrame);
|
|
lastFrameEmitted = lastFrame;
|
|
expectedFrames.shift();
|
|
lastFrame = frame;
|
|
return;
|
|
}
|
|
expectedFrames.shift();
|
|
onFrame(frame);
|
|
if (lastFrame && lastFrame !== lastFrameEmitted) {
|
|
lastFrame.close();
|
|
}
|
|
lastFrameEmitted = frame;
|
|
lastFrame = frame;
|
|
},
|
|
onError: (e) => {
|
|
controller.abort();
|
|
try {
|
|
decoder.close();
|
|
}
|
|
catch {
|
|
// Ignore
|
|
}
|
|
resolvers.reject(e);
|
|
},
|
|
track,
|
|
});
|
|
const queued = [];
|
|
const doProcess = async () => {
|
|
expectedFrames.push(timestampTargets.shift() * media_parser_1.WEBCODECS_TIMESCALE);
|
|
while (queued.length > 0) {
|
|
const sam = queued.shift();
|
|
if (!sam) {
|
|
throw new Error('Sample is undefined');
|
|
}
|
|
await decoder.waitForQueueToBeLessThan(20);
|
|
log_1.Log.trace(logLevel, 'Decoding sample', sam.timestamp);
|
|
await decoder.decode(sam);
|
|
}
|
|
};
|
|
return async (sample) => {
|
|
const nextTimestampWeWant = timestampTargets[0];
|
|
log_1.Log.trace(logLevel, `Received ${sample.type} sample with dts`, sample.decodingTimestamp, 'and cts', sample.timestamp);
|
|
if (sample.type === 'key') {
|
|
await decoder.flush();
|
|
queued.length = 0;
|
|
}
|
|
queued.push(sample);
|
|
if (sample.decodingTimestamp >=
|
|
timestampTargets[timestampTargets.length - 1] * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
await doProcess();
|
|
await decoder.flush();
|
|
controller.abort();
|
|
return;
|
|
}
|
|
if (nextTimestampWeWant === undefined) {
|
|
throw new Error('this should not happen');
|
|
}
|
|
if (sample.decodingTimestamp >=
|
|
nextTimestampWeWant * media_parser_1.WEBCODECS_TIMESCALE) {
|
|
await doProcess();
|
|
if (timestampTargets.length === 0) {
|
|
await decoder.flush();
|
|
controller.abort();
|
|
}
|
|
}
|
|
return async () => {
|
|
await doProcess();
|
|
await decoder.flush();
|
|
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
lastFrame.close();
|
|
}
|
|
};
|
|
};
|
|
},
|
|
})
|
|
.then(() => {
|
|
resolvers.resolve();
|
|
})
|
|
.catch((e) => {
|
|
if (!(0, media_parser_1.hasBeenAborted)(e)) {
|
|
resolvers.reject(e);
|
|
}
|
|
else {
|
|
resolvers.resolve();
|
|
}
|
|
})
|
|
.finally(() => {
|
|
if (lastFrame && lastFrameEmitted !== lastFrame) {
|
|
lastFrame.close();
|
|
}
|
|
signal?.removeEventListener('abort', abortListener);
|
|
});
|
|
return resolvers.promise;
|
|
};
|
|
exports.internalExtractFrames = internalExtractFrames;
|