@remotion/media-parser
Version:
A pure JavaScript library for parsing video files
355 lines (354 loc) • 19.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseMediaOnWorkerImplementation = void 0;
const with_resolvers_1 = require("./with-resolvers");
const serialize_error_1 = require("./worker/serialize-error");
const convertToWorkerPayload = (
// eslint-disable-next-line @typescript-eslint/no-explicit-any
payload) => {
const { onAudioCodec, onContainer, onDimensions, onUnrotatedDimensions, onVideoCodec, onFps, onAudioTrack, onDurationInSeconds, onImages, onInternalStats, onIsHdr, onKeyframes, onLocation, onM3uStreams, onMetadata, onMimeType, onName, onNumberOfAudioChannels, onParseProgress, onRotation, onSampleRate, onSlowAudioBitrate, onSize, onSlowDurationInSeconds, onSlowFps, onSlowKeyframes, onSlowNumberOfFrames, onSlowVideoBitrate, onSlowStructure, onTracks, onVideoTrack, selectM3uStream, selectM3uAssociatedPlaylists, src, ...others } = payload;
return {
type: 'request-worker',
payload: others,
postAudioCodec: Boolean(onAudioCodec),
postContainer: Boolean(onContainer),
postDimensions: Boolean(onDimensions),
postDurationInSeconds: Boolean(onDurationInSeconds),
postFps: Boolean(onFps),
postImages: Boolean(onImages),
postInternalStats: Boolean(onInternalStats),
postIsHdr: Boolean(onIsHdr),
postKeyframes: Boolean(onKeyframes),
postLocation: Boolean(onLocation),
postM3uStreams: Boolean(onM3uStreams),
postMetadata: Boolean(onMetadata),
postMimeType: Boolean(onMimeType),
postName: Boolean(onName),
postNumberOfAudioChannels: Boolean(onNumberOfAudioChannels),
postRotation: Boolean(onRotation),
postSampleRate: Boolean(onSampleRate),
postSlowAudioBitrate: Boolean(onSlowAudioBitrate),
postSlowDurationInSeconds: Boolean(onSlowDurationInSeconds),
postSlowFps: Boolean(onSlowFps),
postSlowKeyframes: Boolean(onSlowKeyframes),
postSlowNumberOfFrames: Boolean(onSlowNumberOfFrames),
postSlowVideoBitrate: Boolean(onSlowVideoBitrate),
postSlowStructure: Boolean(onSlowStructure),
postTracks: Boolean(onTracks),
postUnrotatedDimensions: Boolean(onUnrotatedDimensions),
postVideoCodec: Boolean(onVideoCodec),
postSize: Boolean(onSize),
postParseProgress: Boolean(onParseProgress),
postM3uStreamSelection: Boolean(selectM3uStream),
postM3uAssociatedPlaylistsSelection: Boolean(selectM3uAssociatedPlaylists),
postOnAudioTrack: Boolean(onAudioTrack),
postOnVideoTrack: Boolean(onVideoTrack),
// URL cannot be serialized, so we convert it to a string
src: src instanceof URL ? src.toString() : src,
};
};
const post = (worker, payload) => {
worker.postMessage(payload);
};
const parseMediaOnWorkerImplementation = async ({ controller, reader, ...params }, worker, apiName) => {
if (reader) {
throw new Error(`\`reader\` should not be provided to \`${apiName}\`. If you want to use it in the browser, use parseMediaOnWorker(). If you also want to read files from the file system, use parseMediaOnServerWorker().`);
}
post(worker, convertToWorkerPayload(params));
let workerTerminated = false;
const { promise, resolve, reject } = (0, with_resolvers_1.withResolvers)();
const onAbort = () => {
post(worker, { type: 'request-abort' });
};
const onResume = () => {
post(worker, { type: 'request-resume' });
};
const onPause = () => {
post(worker, { type: 'request-pause' });
};
const onSeek = ({ detail: { seek } }) => {
post(worker, { type: 'request-seek', payload: seek });
controller === null || controller === void 0 ? void 0 : controller._internals.seekSignal.clearSeekIfStillSame(seek);
};
const seekingHintPromises = [];
let finalSeekingHints = null;
controller === null || controller === void 0 ? void 0 : controller._internals.attachSeekingHintResolution(() => {
if (finalSeekingHints) {
return Promise.resolve(finalSeekingHints);
}
if (workerTerminated) {
return Promise.reject(new Error('Worker terminated'));
}
const prom = (0, with_resolvers_1.withResolvers)();
post(worker, { type: 'request-get-seeking-hints' });
seekingHintPromises.push(prom);
return prom.promise;
});
const simulateSeekPromises = {};
controller === null || controller === void 0 ? void 0 : controller._internals.attachSimulateSeekResolution((seek) => {
const prom = (0, with_resolvers_1.withResolvers)();
const nonce = String(Math.random());
post(worker, { type: 'request-simulate-seek', payload: seek, nonce });
simulateSeekPromises[nonce] = prom;
return prom.promise;
});
const callbacks = {};
function onMessage(message) {
const data = message.data;
if (data.type === 'response-done') {
resolve(data.payload);
if (data.seekingHints) {
finalSeekingHints = data.seekingHints;
for (const prom of seekingHintPromises) {
prom.resolve(finalSeekingHints);
}
}
return;
}
if (data.type === 'response-error') {
// eslint-disable-next-line @typescript-eslint/no-use-before-define
cleanup();
// Reject main loop
const error = (0, serialize_error_1.deserializeError)(data);
error.stack = data.errorStack;
reject(error);
// If aborted, we send the seeking hints we got,
// otherwise we reject all .getSeekingHints() promises
if (data.errorName === 'MediaParserAbortError') {
finalSeekingHints = data.seekingHints;
for (const prom of seekingHintPromises) {
prom.resolve(finalSeekingHints);
}
}
else {
// Reject all .getSeekingHints() promises
for (const prom of seekingHintPromises) {
prom.reject(error);
}
}
return;
}
if (data.type === 'response-on-callback-request') {
Promise.resolve()
.then(async () => {
var _a, _b, _c, _d, _e, _f, _g, _h, _j, _k, _l, _m, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _0, _1, _2, _3, _4, _5, _6;
if (data.payload.callbackType === 'audio-codec') {
await ((_a = params.onAudioCodec) === null || _a === void 0 ? void 0 : _a.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'container') {
await ((_b = params.onContainer) === null || _b === void 0 ? void 0 : _b.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'dimensions') {
await ((_c = params.onDimensions) === null || _c === void 0 ? void 0 : _c.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'unrotated-dimensions') {
await ((_d = params.onUnrotatedDimensions) === null || _d === void 0 ? void 0 : _d.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'video-codec') {
await ((_e = params.onVideoCodec) === null || _e === void 0 ? void 0 : _e.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'tracks') {
await ((_f = params.onTracks) === null || _f === void 0 ? void 0 : _f.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'rotation') {
await ((_g = params.onRotation) === null || _g === void 0 ? void 0 : _g.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'sample-rate') {
await ((_h = params.onSampleRate) === null || _h === void 0 ? void 0 : _h.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-audio-bitrate') {
await ((_j = params.onSlowAudioBitrate) === null || _j === void 0 ? void 0 : _j.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-duration-in-seconds') {
await ((_k = params.onSlowDurationInSeconds) === null || _k === void 0 ? void 0 : _k.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-fps') {
await ((_l = params.onSlowFps) === null || _l === void 0 ? void 0 : _l.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-keyframes') {
await ((_m = params.onSlowKeyframes) === null || _m === void 0 ? void 0 : _m.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-number-of-frames') {
await ((_o = params.onSlowNumberOfFrames) === null || _o === void 0 ? void 0 : _o.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-video-bitrate') {
await ((_p = params.onSlowVideoBitrate) === null || _p === void 0 ? void 0 : _p.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'slow-structure') {
await ((_q = params.onSlowStructure) === null || _q === void 0 ? void 0 : _q.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'fps') {
await ((_r = params.onFps) === null || _r === void 0 ? void 0 : _r.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'images') {
await ((_s = params.onImages) === null || _s === void 0 ? void 0 : _s.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'internal-stats') {
await ((_t = params.onInternalStats) === null || _t === void 0 ? void 0 : _t.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'is-hdr') {
await ((_u = params.onIsHdr) === null || _u === void 0 ? void 0 : _u.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'keyframes') {
await ((_v = params.onKeyframes) === null || _v === void 0 ? void 0 : _v.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'location') {
await ((_w = params.onLocation) === null || _w === void 0 ? void 0 : _w.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'm3u-streams') {
await ((_x = params.onM3uStreams) === null || _x === void 0 ? void 0 : _x.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'metadata') {
await ((_y = params.onMetadata) === null || _y === void 0 ? void 0 : _y.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'mime-type') {
await ((_z = params.onMimeType) === null || _z === void 0 ? void 0 : _z.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'name') {
await ((_0 = params.onName) === null || _0 === void 0 ? void 0 : _0.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'number-of-audio-channels') {
await ((_1 = params.onNumberOfAudioChannels) === null || _1 === void 0 ? void 0 : _1.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'size') {
await ((_2 = params.onSize) === null || _2 === void 0 ? void 0 : _2.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'duration-in-seconds') {
await ((_3 = params.onDurationInSeconds) === null || _3 === void 0 ? void 0 : _3.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'parse-progress') {
await ((_4 = params.onParseProgress) === null || _4 === void 0 ? void 0 : _4.call(params, data.payload.value));
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'm3u-stream-selection') {
const selection = await params.selectM3uStream(data.payload.value);
return { payloadType: 'm3u-stream-selection', value: selection };
}
if (data.payload.callbackType === 'm3u-associated-playlists-selection') {
const selection = await params.selectM3uAssociatedPlaylists(data.payload.value);
return {
payloadType: 'm3u-associated-playlists-selection',
value: selection,
};
}
if (data.payload.callbackType === 'on-audio-track') {
const possibleCallback = await ((_5 = params.onAudioTrack) === null || _5 === void 0 ? void 0 : _5.call(params, data.payload.value));
if (possibleCallback) {
callbacks[data.payload.value.track.trackId] = possibleCallback;
}
return {
payloadType: 'on-audio-track-response',
registeredCallback: Boolean(possibleCallback),
};
}
if (data.payload.callbackType === 'on-video-track') {
const possibleCallback = await ((_6 = params.onVideoTrack) === null || _6 === void 0 ? void 0 : _6.call(params, data.payload.value));
if (possibleCallback) {
callbacks[data.payload.value.track.trackId] = possibleCallback;
}
return {
payloadType: 'on-video-track-response',
registeredCallback: Boolean(possibleCallback),
};
}
if (data.payload.callbackType === 'on-audio-sample') {
const callback = callbacks[data.payload.trackId];
if (!callback) {
throw new Error(`No callback registered for track ${data.payload.trackId}`);
}
await callback(data.payload.value);
return { payloadType: 'void' };
}
if (data.payload.callbackType === 'on-video-sample') {
const callback = callbacks[data.payload.trackId];
if (!callback) {
throw new Error(`No callback registered for track ${data.payload.trackId}`);
}
await callback(data.payload.value);
return { payloadType: 'void' };
}
throw new Error(`Unknown callback type: ${data.payload}`);
})
.then((payload) => {
post(worker, {
type: 'acknowledge-callback',
nonce: data.nonce,
...payload,
});
})
.catch((err) => {
reject(err);
post(worker, {
type: 'signal-error-in-callback',
nonce: data.nonce,
});
});
return;
}
if (data.type === 'response-get-seeking-hints') {
const firstPromise = seekingHintPromises.shift();
if (!firstPromise) {
throw new Error('No seeking hint promise found');
}
firstPromise.resolve(data.payload);
return;
}
if (data.type === 'response-simulate-seek') {
const prom = simulateSeekPromises[data.nonce];
if (!prom) {
throw new Error('No simulate seek promise found');
}
prom.resolve(data.payload);
delete simulateSeekPromises[data.nonce];
return;
}
throw new Error(`Unknown response type: ${JSON.stringify(data)}`);
}
worker.addEventListener('message', onMessage);
controller === null || controller === void 0 ? void 0 : controller.addEventListener('abort', onAbort);
controller === null || controller === void 0 ? void 0 : controller.addEventListener('resume', onResume);
controller === null || controller === void 0 ? void 0 : controller.addEventListener('pause', onPause);
controller === null || controller === void 0 ? void 0 : controller.addEventListener('seek', onSeek);
function cleanup() {
worker.removeEventListener('message', onMessage);
controller === null || controller === void 0 ? void 0 : controller.removeEventListener('abort', onAbort);
controller === null || controller === void 0 ? void 0 : controller.removeEventListener('resume', onResume);
controller === null || controller === void 0 ? void 0 : controller.removeEventListener('pause', onPause);
controller === null || controller === void 0 ? void 0 : controller.removeEventListener('seek', onSeek);
workerTerminated = true;
worker.terminate();
}
controller === null || controller === void 0 ? void 0 : controller._internals.markAsReadyToEmitEvents();
const val = await promise;
cleanup();
return val;
};
exports.parseMediaOnWorkerImplementation = parseMediaOnWorkerImplementation;