Add .gitignore to exclude all node packages and lock files
This commit is contained in:
Generated
Vendored
+18244
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+112
@@ -0,0 +1,112 @@
|
||||
// src/writers/node.ts
|
||||
import fs from "node:fs";
|
||||
|
||||
// src/log.ts
|
||||
var logLevels = ["trace", "verbose", "info", "warn", "error"];
|
||||
var getNumberForLogLevel = (level) => {
|
||||
return logLevels.indexOf(level);
|
||||
};
|
||||
var isEqualOrBelowLogLevel = (currentLevel, level) => {
|
||||
return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
|
||||
};
|
||||
var Log = {
|
||||
trace: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "trace")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
verbose: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
info: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "info")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
warn: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "warn")) {
|
||||
return console.warn(...args);
|
||||
}
|
||||
},
|
||||
error: (...args) => {
|
||||
return console.error(...args);
|
||||
}
|
||||
};
|
||||
|
||||
// src/writers/node.ts
|
||||
var createContent = (filename) => {
|
||||
return async ({ logLevel }) => {
|
||||
let writPromise = Promise.resolve();
|
||||
const remove = async () => {
|
||||
Log.verbose(logLevel, "Removing file", filename);
|
||||
await fs.promises.unlink(filename).catch(() => {});
|
||||
};
|
||||
await remove();
|
||||
if (!fs.existsSync(filename)) {
|
||||
Log.verbose(logLevel, "Creating file", filename);
|
||||
fs.writeFileSync(filename, "");
|
||||
}
|
||||
const writeStream = fs.openSync(filename, "w");
|
||||
let written = 0;
|
||||
const write = async (data) => {
|
||||
await new Promise((resolve, reject) => {
|
||||
fs.write(writeStream, data, 0, data.length, undefined, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
Log.verbose(logLevel, "Wrote", data.length, "bytes to", filename);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
written += data.byteLength;
|
||||
};
|
||||
const updateDataAt = (position, data) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
fs.write(writeStream, data, 0, data.length, position, (err) => {
|
||||
if (err) {
|
||||
reject(err);
|
||||
return;
|
||||
}
|
||||
Log.verbose(logLevel, "Wrote", data.length, "bytes to", filename, "at position", position);
|
||||
resolve();
|
||||
});
|
||||
});
|
||||
};
|
||||
const writer = {
|
||||
write: (arr) => {
|
||||
writPromise = writPromise.then(() => write(arr));
|
||||
return writPromise;
|
||||
},
|
||||
updateDataAt: (position, data) => {
|
||||
writPromise = writPromise.then(() => updateDataAt(position, data));
|
||||
return writPromise;
|
||||
},
|
||||
getWrittenByteCount: () => written,
|
||||
remove,
|
||||
finish: async () => {
|
||||
await writPromise;
|
||||
try {
|
||||
Log.verbose(logLevel, "Closing file", filename);
|
||||
fs.closeSync(writeStream);
|
||||
return Promise.resolve();
|
||||
} catch (e) {
|
||||
return Promise.reject(e);
|
||||
}
|
||||
},
|
||||
getBlob: async () => {
|
||||
const file = await fs.promises.readFile(filename);
|
||||
return new Blob([file]);
|
||||
}
|
||||
};
|
||||
return writer;
|
||||
};
|
||||
};
|
||||
var nodeWriter = (path) => {
|
||||
return { createContent: createContent(path) };
|
||||
};
|
||||
export {
|
||||
nodeWriter
|
||||
};
|
||||
Generated
Vendored
+104
@@ -0,0 +1,104 @@
|
||||
// src/readers/from-node.ts
|
||||
import { createReadStream, existsSync, promises, statSync } from "fs";
|
||||
import { dirname, join, relative, sep } from "path";
|
||||
var nodeReadContent = async ({
|
||||
src,
|
||||
range,
|
||||
controller
|
||||
}) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
await Promise.resolve();
|
||||
const ownController = new AbortController;
|
||||
try {
|
||||
if (!existsSync(src)) {
|
||||
throw new Error(`File does not exist: ${src}`);
|
||||
}
|
||||
const stream = createReadStream(src, {
|
||||
start: range === null ? 0 : typeof range === "number" ? range : range[0],
|
||||
end: range === null ? Infinity : typeof range === "number" ? Infinity : range[1]
|
||||
});
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
ownController.abort();
|
||||
}, { once: true });
|
||||
const stats = statSync(src);
|
||||
let readerCancelled = false;
|
||||
const reader = new ReadableStream({
|
||||
start(c) {
|
||||
if (readerCancelled) {
|
||||
return;
|
||||
}
|
||||
stream.on("data", (chunk) => {
|
||||
c.enqueue(chunk);
|
||||
});
|
||||
stream.on("end", () => {
|
||||
if (readerCancelled) {
|
||||
return;
|
||||
}
|
||||
c.close();
|
||||
});
|
||||
stream.on("error", (err) => {
|
||||
c.error(err);
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
readerCancelled = true;
|
||||
stream.destroy();
|
||||
}
|
||||
}).getReader();
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
reader.cancel().catch(() => {});
|
||||
}, { once: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
reader: {
|
||||
reader,
|
||||
abort: async () => {
|
||||
try {
|
||||
stream.destroy();
|
||||
ownController.abort();
|
||||
await reader.cancel();
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
contentLength: stats.size,
|
||||
contentType: null,
|
||||
name: src.split(sep).pop(),
|
||||
supportsContentRange: true,
|
||||
needsContentRange: true
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
};
|
||||
var nodeReadWholeAsText = (src) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
return promises.readFile(src, "utf8");
|
||||
};
|
||||
var nodeCreateAdjacentFileSource = (relativePath, src) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
const result = join(dirname(src), relativePath);
|
||||
const rel = relative(dirname(src), result);
|
||||
if (rel.startsWith("..")) {
|
||||
throw new Error("Path is outside of the parent directory - not allowing reading of arbitrary files");
|
||||
}
|
||||
return result;
|
||||
};
|
||||
var nodeReader = {
|
||||
read: nodeReadContent,
|
||||
readWholeAsText: nodeReadWholeAsText,
|
||||
createAdjacentFileSource: nodeCreateAdjacentFileSource,
|
||||
preload: () => {}
|
||||
};
|
||||
export {
|
||||
nodeReader,
|
||||
nodeReadWholeAsText,
|
||||
nodeReadContent,
|
||||
nodeCreateAdjacentFileSource
|
||||
};
|
||||
Generated
Vendored
+535
@@ -0,0 +1,535 @@
|
||||
// src/with-resolvers.ts
|
||||
var withResolvers = function() {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
};
|
||||
|
||||
// src/errors.ts
|
||||
class IsAnImageError extends Error {
|
||||
imageType;
|
||||
dimensions;
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
dimensions,
|
||||
imageType,
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAnImageError";
|
||||
this.imageType = imageType;
|
||||
this.dimensions = dimensions;
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAnImageError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IsAPdfError extends Error {
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAPdfError";
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAPdfError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IsAnUnsupportedFileTypeError extends Error {
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAnUnsupportedFileTypeError";
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaParserAbortError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MediaParserAbortError";
|
||||
this.cause = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// src/worker/serialize-error.ts
|
||||
var deserializeError = (error) => {
|
||||
switch (error.errorName) {
|
||||
case "IsAnImageError":
|
||||
return new IsAnImageError({
|
||||
dimensions: error.dimensions,
|
||||
fileName: error.fileName,
|
||||
imageType: error.imageType,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "IsAPdfError":
|
||||
return new IsAPdfError({
|
||||
fileName: error.fileName,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "IsAnUnsupportedFileTypeError":
|
||||
return new IsAnUnsupportedFileTypeError({
|
||||
fileName: error.fileName,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "MediaParserAbortError":
|
||||
return new MediaParserAbortError(error.errorMessage);
|
||||
case "Error":
|
||||
return new Error(error.errorMessage);
|
||||
case "AbortError":
|
||||
return new Error(error.errorMessage);
|
||||
case "NotReadableError":
|
||||
return new Error(error.errorMessage);
|
||||
case "TypeError":
|
||||
return new TypeError(error.errorMessage);
|
||||
default:
|
||||
throw new Error(`Unknown error name: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
// src/parse-media-on-worker-entry.ts
|
||||
var convertToWorkerPayload = (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),
|
||||
src: src instanceof URL ? src.toString() : src
|
||||
};
|
||||
};
|
||||
var post = (worker, payload) => {
|
||||
worker.postMessage(payload);
|
||||
};
|
||||
var 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 } = 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?._internals.seekSignal.clearSeekIfStillSame(seek);
|
||||
};
|
||||
const seekingHintPromises = [];
|
||||
let finalSeekingHints = null;
|
||||
controller?._internals.attachSeekingHintResolution(() => {
|
||||
if (finalSeekingHints) {
|
||||
return Promise.resolve(finalSeekingHints);
|
||||
}
|
||||
if (workerTerminated) {
|
||||
return Promise.reject(new Error("Worker terminated"));
|
||||
}
|
||||
const prom = withResolvers();
|
||||
post(worker, { type: "request-get-seeking-hints" });
|
||||
seekingHintPromises.push(prom);
|
||||
return prom.promise;
|
||||
});
|
||||
const simulateSeekPromises = {};
|
||||
controller?._internals.attachSimulateSeekResolution((seek) => {
|
||||
const prom = withResolvers();
|
||||
const nonce = String(Math.random());
|
||||
post(worker, { type: "request-simulate-seek", payload: seek, nonce });
|
||||
simulateSeekPromises[nonce] = prom;
|
||||
return prom.promise;
|
||||
});
|
||||
const callbacks = {};
|
||||
const trackDoneCallbacks = {};
|
||||
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") {
|
||||
cleanup();
|
||||
const error = deserializeError(data);
|
||||
error.stack = data.errorStack;
|
||||
reject(error);
|
||||
if (data.errorName === "MediaParserAbortError") {
|
||||
finalSeekingHints = data.seekingHints;
|
||||
for (const prom of seekingHintPromises) {
|
||||
prom.resolve(finalSeekingHints);
|
||||
}
|
||||
} else {
|
||||
for (const prom of seekingHintPromises) {
|
||||
prom.reject(error);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.type === "response-on-callback-request") {
|
||||
Promise.resolve().then(async () => {
|
||||
if (data.payload.callbackType === "audio-codec") {
|
||||
await params.onAudioCodec?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "container") {
|
||||
await params.onContainer?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "dimensions") {
|
||||
await params.onDimensions?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "unrotated-dimensions") {
|
||||
await params.onUnrotatedDimensions?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "video-codec") {
|
||||
await params.onVideoCodec?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "tracks") {
|
||||
await params.onTracks?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "rotation") {
|
||||
await params.onRotation?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "sample-rate") {
|
||||
await params.onSampleRate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-audio-bitrate") {
|
||||
await params.onSlowAudioBitrate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-duration-in-seconds") {
|
||||
await params.onSlowDurationInSeconds?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-fps") {
|
||||
await params.onSlowFps?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-keyframes") {
|
||||
await params.onSlowKeyframes?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-number-of-frames") {
|
||||
await params.onSlowNumberOfFrames?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-video-bitrate") {
|
||||
await params.onSlowVideoBitrate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-structure") {
|
||||
await params.onSlowStructure?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "fps") {
|
||||
await params.onFps?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "images") {
|
||||
await params.onImages?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "internal-stats") {
|
||||
await params.onInternalStats?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "is-hdr") {
|
||||
await params.onIsHdr?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "keyframes") {
|
||||
await params.onKeyframes?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "location") {
|
||||
await params.onLocation?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "m3u-streams") {
|
||||
await params.onM3uStreams?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "metadata") {
|
||||
await params.onMetadata?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "mime-type") {
|
||||
await params.onMimeType?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "name") {
|
||||
await params.onName?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "number-of-audio-channels") {
|
||||
await params.onNumberOfAudioChannels?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "size") {
|
||||
await params.onSize?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "duration-in-seconds") {
|
||||
await params.onDurationInSeconds?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "parse-progress") {
|
||||
await params.onParseProgress?.(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 params.onAudioTrack?.(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 params.onVideoTrack?.(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}`);
|
||||
}
|
||||
const trackDoneCallback = await callback(data.payload.value);
|
||||
if (trackDoneCallback) {
|
||||
trackDoneCallbacks[data.payload.trackId] = trackDoneCallback;
|
||||
}
|
||||
return {
|
||||
payloadType: "on-sample-response",
|
||||
registeredTrackDoneCallback: Boolean(trackDoneCallback)
|
||||
};
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
const trackDoneCallback = await callback(data.payload.value);
|
||||
if (trackDoneCallback) {
|
||||
trackDoneCallbacks[data.payload.trackId] = trackDoneCallback;
|
||||
}
|
||||
return {
|
||||
payloadType: "on-sample-response",
|
||||
registeredTrackDoneCallback: Boolean(trackDoneCallback)
|
||||
};
|
||||
}
|
||||
if (data.payload.callbackType === "track-done") {
|
||||
const trackDoneCallback = trackDoneCallbacks[data.payload.trackId];
|
||||
if (!trackDoneCallback) {
|
||||
throw new Error(`No track done callback registered for track ${data.payload.trackId}`);
|
||||
}
|
||||
trackDoneCallback();
|
||||
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?.addEventListener("abort", onAbort);
|
||||
controller?.addEventListener("resume", onResume);
|
||||
controller?.addEventListener("pause", onPause);
|
||||
controller?.addEventListener("seek", onSeek);
|
||||
function cleanup() {
|
||||
worker.removeEventListener("message", onMessage);
|
||||
controller?.removeEventListener("abort", onAbort);
|
||||
controller?.removeEventListener("resume", onResume);
|
||||
controller?.removeEventListener("pause", onPause);
|
||||
controller?.removeEventListener("seek", onSeek);
|
||||
workerTerminated = true;
|
||||
worker.terminate();
|
||||
}
|
||||
controller?._internals.markAsReadyToEmitEvents();
|
||||
const val = await promise;
|
||||
cleanup();
|
||||
return val;
|
||||
};
|
||||
|
||||
// src/server-worker.module.ts
|
||||
var parseMediaOnServerWorker = (params) => {
|
||||
if (typeof Worker === "undefined") {
|
||||
throw new Error('"Worker" is not available. Cannot call parseMediaOnServerWorker()');
|
||||
}
|
||||
const worker = new Worker(new URL("./worker-server-entry", import.meta.url));
|
||||
return parseMediaOnWorkerImplementation(params, worker, "parseMediaOnServerWorker");
|
||||
};
|
||||
export {
|
||||
parseMediaOnServerWorker
|
||||
};
|
||||
Generated
Vendored
+482
@@ -0,0 +1,482 @@
|
||||
// src/errors.ts
|
||||
class MediaParserAbortError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MediaParserAbortError";
|
||||
this.cause = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// src/log.ts
|
||||
var logLevels = ["trace", "verbose", "info", "warn", "error"];
|
||||
var getNumberForLogLevel = (level) => {
|
||||
return logLevels.indexOf(level);
|
||||
};
|
||||
var isEqualOrBelowLogLevel = (currentLevel, level) => {
|
||||
return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
|
||||
};
|
||||
var Log = {
|
||||
trace: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "trace")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
verbose: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
info: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "info")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
warn: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "warn")) {
|
||||
return console.warn(...args);
|
||||
}
|
||||
},
|
||||
error: (...args) => {
|
||||
return console.error(...args);
|
||||
}
|
||||
};
|
||||
|
||||
// src/readers/fetch/get-body-and-reader.ts
|
||||
var getLengthAndReader = async ({
|
||||
canLiveWithoutContentLength,
|
||||
res,
|
||||
ownController,
|
||||
requestedWithoutRange
|
||||
}) => {
|
||||
const length = res.headers.get("content-length");
|
||||
const contentLength = length === null ? null : parseInt(length, 10);
|
||||
if (requestedWithoutRange || canLiveWithoutContentLength && contentLength === null) {
|
||||
const buffer = await res.arrayBuffer();
|
||||
const encoded = new Uint8Array(buffer);
|
||||
let streamCancelled = false;
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
if (ownController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (streamCancelled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
controller.enqueue(encoded);
|
||||
controller.close();
|
||||
} catch {}
|
||||
},
|
||||
cancel() {
|
||||
streamCancelled = true;
|
||||
}
|
||||
});
|
||||
return {
|
||||
contentLength: encoded.byteLength,
|
||||
reader: {
|
||||
reader: stream.getReader(),
|
||||
abort: () => {
|
||||
ownController.abort();
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
needsContentRange: false
|
||||
};
|
||||
}
|
||||
if (!res.body) {
|
||||
throw new Error("No body");
|
||||
}
|
||||
const reader = res.body.getReader();
|
||||
return {
|
||||
reader: {
|
||||
reader,
|
||||
abort: () => {
|
||||
ownController.abort();
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
contentLength,
|
||||
needsContentRange: true
|
||||
};
|
||||
};
|
||||
|
||||
// src/readers/fetch/resolve-url.ts
|
||||
var resolveUrl = (src) => {
|
||||
try {
|
||||
const resolvedUrl = typeof window !== "undefined" && typeof window.location !== "undefined" ? new URL(src, window.location.origin) : new URL(src);
|
||||
return resolvedUrl;
|
||||
} catch {
|
||||
return src;
|
||||
}
|
||||
};
|
||||
|
||||
// src/readers/from-fetch.ts
|
||||
function parseContentRange(input) {
|
||||
const matches = input.match(/^(\w+) ((\d+)-(\d+)|\*)\/(\d+|\*)$/);
|
||||
if (!matches)
|
||||
return null;
|
||||
const [, unit, , start, end, size] = matches;
|
||||
const range = {
|
||||
unit,
|
||||
start: start != null ? Number(start) : null,
|
||||
end: end != null ? Number(end) : null,
|
||||
size: size === "*" ? null : Number(size)
|
||||
};
|
||||
if (range.start === null && range.end === null && range.size === null) {
|
||||
return null;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
var validateContentRangeAndDetectIfSupported = ({
|
||||
requestedRange,
|
||||
parsedContentRange,
|
||||
statusCode
|
||||
}) => {
|
||||
if (statusCode === 206) {
|
||||
return { supportsContentRange: true };
|
||||
}
|
||||
if (typeof requestedRange === "number" && parsedContentRange?.start !== requestedRange) {
|
||||
if (requestedRange === 0) {
|
||||
return { supportsContentRange: false };
|
||||
}
|
||||
throw new Error(`Range header (${requestedRange}) does not match content-range header (${parsedContentRange?.start})`);
|
||||
}
|
||||
if (requestedRange !== null && typeof requestedRange !== "number" && (parsedContentRange?.start !== requestedRange[0] || parsedContentRange?.end !== requestedRange[1])) {
|
||||
throw new Error(`Range header (${requestedRange}) does not match content-range header (${parsedContentRange?.start})`);
|
||||
}
|
||||
return { supportsContentRange: true };
|
||||
};
|
||||
var makeFetchRequest = async ({
|
||||
range,
|
||||
src,
|
||||
controller
|
||||
}) => {
|
||||
const resolvedUrl = resolveUrl(src);
|
||||
const resolvedUrlString = resolvedUrl.toString();
|
||||
if (!resolvedUrlString.startsWith("https://") && !resolvedUrlString.startsWith("blob:") && !resolvedUrlString.startsWith("data:") && !resolvedUrlString.startsWith("http://")) {
|
||||
return Promise.reject(new Error(`${resolvedUrlString} is not a URL - needs to start with http:// or https:// or blob:. If you want to read a local file, pass \`reader: nodeReader\` to parseMedia().`));
|
||||
}
|
||||
const ownController = new AbortController;
|
||||
const cache = typeof navigator !== "undefined" && navigator.userAgent.includes("Cloudflare-Workers") ? undefined : "no-store";
|
||||
const requestedRange = range === null ? 0 : range;
|
||||
const asString = typeof resolvedUrl === "string" ? resolvedUrl : resolvedUrl.pathname;
|
||||
const requestWithoutRange = asString.endsWith(".m3u8");
|
||||
const canLiveWithoutContentLength = asString.endsWith(".m3u8") || asString.endsWith(".ts");
|
||||
const headers = requestedRange === 0 && requestWithoutRange ? {} : typeof requestedRange === "number" ? {
|
||||
Range: `bytes=${requestedRange}-`
|
||||
} : {
|
||||
Range: `bytes=${`${requestedRange[0]}-${requestedRange[1]}`}`
|
||||
};
|
||||
const res = await fetch(resolvedUrl, {
|
||||
headers,
|
||||
signal: ownController.signal,
|
||||
cache
|
||||
});
|
||||
const contentRange = res.headers.get("content-range");
|
||||
const parsedContentRange = contentRange ? parseContentRange(contentRange) : null;
|
||||
if (!res.ok) {
|
||||
throw new Error(`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`);
|
||||
}
|
||||
const { supportsContentRange } = validateContentRangeAndDetectIfSupported({
|
||||
requestedRange,
|
||||
parsedContentRange,
|
||||
statusCode: res.status
|
||||
});
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
ownController.abort(new MediaParserAbortError("Aborted by user"));
|
||||
}, { once: true });
|
||||
}
|
||||
const contentDisposition = res.headers.get("content-disposition");
|
||||
const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
|
||||
const { contentLength, needsContentRange, reader } = await getLengthAndReader({
|
||||
canLiveWithoutContentLength,
|
||||
res,
|
||||
ownController,
|
||||
requestedWithoutRange: requestWithoutRange
|
||||
});
|
||||
const contentType = res.headers.get("content-type");
|
||||
return {
|
||||
contentLength,
|
||||
needsContentRange,
|
||||
reader,
|
||||
name,
|
||||
contentType,
|
||||
supportsContentRange
|
||||
};
|
||||
};
|
||||
var cacheKey = ({
|
||||
src,
|
||||
range
|
||||
}) => {
|
||||
return `${src}-${JSON.stringify(range)}`;
|
||||
};
|
||||
var makeFetchRequestOrGetCached = ({
|
||||
range,
|
||||
src,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
const key = cacheKey({ src, range });
|
||||
const cached = prefetchCache.get(key);
|
||||
if (cached) {
|
||||
Log.verbose(logLevel, `Reading from preload cache for ${key}`);
|
||||
return cached;
|
||||
}
|
||||
Log.verbose(logLevel, `Fetching ${key}`);
|
||||
const result = makeFetchRequest({ range, src, controller });
|
||||
prefetchCache.set(key, result);
|
||||
return result;
|
||||
};
|
||||
var fetchReadContent = async ({
|
||||
src,
|
||||
range,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const fallbackName = src.toString().split("/").pop();
|
||||
const res = makeFetchRequestOrGetCached({
|
||||
range,
|
||||
src,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
});
|
||||
const key = cacheKey({ src, range });
|
||||
prefetchCache.delete(key);
|
||||
const {
|
||||
reader,
|
||||
contentLength,
|
||||
needsContentRange,
|
||||
name,
|
||||
supportsContentRange,
|
||||
contentType
|
||||
} = await res;
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
reader.reader.cancel().catch(() => {});
|
||||
}, { once: true });
|
||||
}
|
||||
return {
|
||||
reader,
|
||||
contentLength,
|
||||
contentType,
|
||||
name: name ?? fallbackName,
|
||||
supportsContentRange,
|
||||
needsContentRange
|
||||
};
|
||||
};
|
||||
var fetchPreload = ({
|
||||
src,
|
||||
range,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const key = cacheKey({ src, range });
|
||||
if (prefetchCache.has(key)) {
|
||||
return prefetchCache.get(key);
|
||||
}
|
||||
makeFetchRequestOrGetCached({
|
||||
range,
|
||||
src,
|
||||
controller: null,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
});
|
||||
};
|
||||
var fetchReadWholeAsText = async (src) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const res = await fetch(src);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch ${src} (HTTP code: ${res.status})`);
|
||||
}
|
||||
return res.text();
|
||||
};
|
||||
var fetchCreateAdjacentFileSource = (relativePath, src) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string or URL when using `fetchReader`");
|
||||
}
|
||||
return new URL(relativePath, src).toString();
|
||||
};
|
||||
|
||||
// src/readers/from-node.ts
|
||||
import { createReadStream, existsSync, promises, statSync } from "fs";
|
||||
import { dirname, join, relative, sep } from "path";
|
||||
var nodeReadContent = async ({
|
||||
src,
|
||||
range,
|
||||
controller
|
||||
}) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
await Promise.resolve();
|
||||
const ownController = new AbortController;
|
||||
try {
|
||||
if (!existsSync(src)) {
|
||||
throw new Error(`File does not exist: ${src}`);
|
||||
}
|
||||
const stream = createReadStream(src, {
|
||||
start: range === null ? 0 : typeof range === "number" ? range : range[0],
|
||||
end: range === null ? Infinity : typeof range === "number" ? Infinity : range[1]
|
||||
});
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
ownController.abort();
|
||||
}, { once: true });
|
||||
const stats = statSync(src);
|
||||
let readerCancelled = false;
|
||||
const reader = new ReadableStream({
|
||||
start(c) {
|
||||
if (readerCancelled) {
|
||||
return;
|
||||
}
|
||||
stream.on("data", (chunk) => {
|
||||
c.enqueue(chunk);
|
||||
});
|
||||
stream.on("end", () => {
|
||||
if (readerCancelled) {
|
||||
return;
|
||||
}
|
||||
c.close();
|
||||
});
|
||||
stream.on("error", (err) => {
|
||||
c.error(err);
|
||||
});
|
||||
},
|
||||
cancel() {
|
||||
readerCancelled = true;
|
||||
stream.destroy();
|
||||
}
|
||||
}).getReader();
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
reader.cancel().catch(() => {});
|
||||
}, { once: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
reader: {
|
||||
reader,
|
||||
abort: async () => {
|
||||
try {
|
||||
stream.destroy();
|
||||
ownController.abort();
|
||||
await reader.cancel();
|
||||
} catch {}
|
||||
}
|
||||
},
|
||||
contentLength: stats.size,
|
||||
contentType: null,
|
||||
name: src.split(sep).pop(),
|
||||
supportsContentRange: true,
|
||||
needsContentRange: true
|
||||
});
|
||||
} catch (err) {
|
||||
return Promise.reject(err);
|
||||
}
|
||||
};
|
||||
var nodeReadWholeAsText = (src) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
return promises.readFile(src, "utf8");
|
||||
};
|
||||
var nodeCreateAdjacentFileSource = (relativePath, src) => {
|
||||
if (typeof src !== "string") {
|
||||
throw new Error("src must be a string when using `nodeReader`");
|
||||
}
|
||||
const result = join(dirname(src), relativePath);
|
||||
const rel = relative(dirname(src), result);
|
||||
if (rel.startsWith("..")) {
|
||||
throw new Error("Path is outside of the parent directory - not allowing reading of arbitrary files");
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// src/readers/from-web-file.ts
|
||||
var webFileReadContent = ({ src, range, controller }) => {
|
||||
if (typeof src === "string" || src instanceof URL) {
|
||||
throw new Error("`inputTypeFileReader` only supports `File` objects");
|
||||
}
|
||||
const part = range === null ? src : typeof range === "number" ? src.slice(range) : src.slice(range[0], range[1] + 1);
|
||||
const stream = part.stream();
|
||||
const streamReader = stream.getReader();
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
streamReader.cancel();
|
||||
}, { once: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
reader: {
|
||||
reader: streamReader,
|
||||
async abort() {
|
||||
try {
|
||||
await streamReader.cancel();
|
||||
} catch {}
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
contentLength: src.size,
|
||||
name: src instanceof File ? src.name : src.toString(),
|
||||
supportsContentRange: true,
|
||||
contentType: src.type,
|
||||
needsContentRange: true
|
||||
});
|
||||
};
|
||||
var webFileReadWholeAsText = () => {
|
||||
throw new Error("`webFileReader` cannot read auxiliary files.");
|
||||
};
|
||||
var webFileCreateAdjacentFileSource = () => {
|
||||
throw new Error("`webFileReader` cannot create adjacent file sources.");
|
||||
};
|
||||
|
||||
// src/readers/universal.ts
|
||||
var universalReader = {
|
||||
read: (params) => {
|
||||
if (params.src instanceof Blob) {
|
||||
return webFileReadContent(params);
|
||||
}
|
||||
if (params.src.toString().startsWith("http") || params.src.toString().startsWith("blob:")) {
|
||||
return fetchReadContent(params);
|
||||
}
|
||||
return nodeReadContent(params);
|
||||
},
|
||||
readWholeAsText: (src) => {
|
||||
if (src instanceof Blob) {
|
||||
return webFileReadWholeAsText(src);
|
||||
}
|
||||
if (src.toString().startsWith("http") || src.toString().startsWith("blob:")) {
|
||||
return fetchReadWholeAsText(src);
|
||||
}
|
||||
return nodeReadWholeAsText(src);
|
||||
},
|
||||
createAdjacentFileSource: (relativePath, src) => {
|
||||
if (src instanceof Blob) {
|
||||
return webFileCreateAdjacentFileSource(relativePath, src);
|
||||
}
|
||||
if (src.toString().startsWith("http") || src.toString().startsWith("blob:")) {
|
||||
return fetchCreateAdjacentFileSource(relativePath, src);
|
||||
}
|
||||
return nodeCreateAdjacentFileSource(relativePath, src);
|
||||
},
|
||||
preload: ({ src, range, logLevel, prefetchCache }) => {
|
||||
if (src instanceof Blob) {
|
||||
return;
|
||||
}
|
||||
if (src.toString().startsWith("http") || src.toString().startsWith("blob:")) {
|
||||
return fetchPreload({ range, src, logLevel, prefetchCache });
|
||||
}
|
||||
}
|
||||
};
|
||||
export {
|
||||
universalReader
|
||||
};
|
||||
Generated
Vendored
+378
@@ -0,0 +1,378 @@
|
||||
// src/errors.ts
|
||||
class MediaParserAbortError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MediaParserAbortError";
|
||||
this.cause = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// src/log.ts
|
||||
var logLevels = ["trace", "verbose", "info", "warn", "error"];
|
||||
var getNumberForLogLevel = (level) => {
|
||||
return logLevels.indexOf(level);
|
||||
};
|
||||
var isEqualOrBelowLogLevel = (currentLevel, level) => {
|
||||
return getNumberForLogLevel(currentLevel) <= getNumberForLogLevel(level);
|
||||
};
|
||||
var Log = {
|
||||
trace: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "trace")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
verbose: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "verbose")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
info: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "info")) {
|
||||
return console.log(...args);
|
||||
}
|
||||
},
|
||||
warn: (logLevel, ...args) => {
|
||||
if (isEqualOrBelowLogLevel(logLevel, "warn")) {
|
||||
return console.warn(...args);
|
||||
}
|
||||
},
|
||||
error: (...args) => {
|
||||
return console.error(...args);
|
||||
}
|
||||
};
|
||||
|
||||
// src/readers/fetch/get-body-and-reader.ts
|
||||
var getLengthAndReader = async ({
|
||||
canLiveWithoutContentLength,
|
||||
res,
|
||||
ownController,
|
||||
requestedWithoutRange
|
||||
}) => {
|
||||
const length = res.headers.get("content-length");
|
||||
const contentLength = length === null ? null : parseInt(length, 10);
|
||||
if (requestedWithoutRange || canLiveWithoutContentLength && contentLength === null) {
|
||||
const buffer = await res.arrayBuffer();
|
||||
const encoded = new Uint8Array(buffer);
|
||||
let streamCancelled = false;
|
||||
const stream = new ReadableStream({
|
||||
start(controller) {
|
||||
if (ownController.signal.aborted) {
|
||||
return;
|
||||
}
|
||||
if (streamCancelled) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
controller.enqueue(encoded);
|
||||
controller.close();
|
||||
} catch {}
|
||||
},
|
||||
cancel() {
|
||||
streamCancelled = true;
|
||||
}
|
||||
});
|
||||
return {
|
||||
contentLength: encoded.byteLength,
|
||||
reader: {
|
||||
reader: stream.getReader(),
|
||||
abort: () => {
|
||||
ownController.abort();
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
needsContentRange: false
|
||||
};
|
||||
}
|
||||
if (!res.body) {
|
||||
throw new Error("No body");
|
||||
}
|
||||
const reader = res.body.getReader();
|
||||
return {
|
||||
reader: {
|
||||
reader,
|
||||
abort: () => {
|
||||
ownController.abort();
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
contentLength,
|
||||
needsContentRange: true
|
||||
};
|
||||
};
|
||||
|
||||
// src/readers/fetch/resolve-url.ts
|
||||
var resolveUrl = (src) => {
|
||||
try {
|
||||
const resolvedUrl = typeof window !== "undefined" && typeof window.location !== "undefined" ? new URL(src, window.location.origin) : new URL(src);
|
||||
return resolvedUrl;
|
||||
} catch {
|
||||
return src;
|
||||
}
|
||||
};
|
||||
|
||||
// src/readers/from-fetch.ts
|
||||
function parseContentRange(input) {
|
||||
const matches = input.match(/^(\w+) ((\d+)-(\d+)|\*)\/(\d+|\*)$/);
|
||||
if (!matches)
|
||||
return null;
|
||||
const [, unit, , start, end, size] = matches;
|
||||
const range = {
|
||||
unit,
|
||||
start: start != null ? Number(start) : null,
|
||||
end: end != null ? Number(end) : null,
|
||||
size: size === "*" ? null : Number(size)
|
||||
};
|
||||
if (range.start === null && range.end === null && range.size === null) {
|
||||
return null;
|
||||
}
|
||||
return range;
|
||||
}
|
||||
var validateContentRangeAndDetectIfSupported = ({
|
||||
requestedRange,
|
||||
parsedContentRange,
|
||||
statusCode
|
||||
}) => {
|
||||
if (statusCode === 206) {
|
||||
return { supportsContentRange: true };
|
||||
}
|
||||
if (typeof requestedRange === "number" && parsedContentRange?.start !== requestedRange) {
|
||||
if (requestedRange === 0) {
|
||||
return { supportsContentRange: false };
|
||||
}
|
||||
throw new Error(`Range header (${requestedRange}) does not match content-range header (${parsedContentRange?.start})`);
|
||||
}
|
||||
if (requestedRange !== null && typeof requestedRange !== "number" && (parsedContentRange?.start !== requestedRange[0] || parsedContentRange?.end !== requestedRange[1])) {
|
||||
throw new Error(`Range header (${requestedRange}) does not match content-range header (${parsedContentRange?.start})`);
|
||||
}
|
||||
return { supportsContentRange: true };
|
||||
};
|
||||
var makeFetchRequest = async ({
|
||||
range,
|
||||
src,
|
||||
controller
|
||||
}) => {
|
||||
const resolvedUrl = resolveUrl(src);
|
||||
const resolvedUrlString = resolvedUrl.toString();
|
||||
if (!resolvedUrlString.startsWith("https://") && !resolvedUrlString.startsWith("blob:") && !resolvedUrlString.startsWith("data:") && !resolvedUrlString.startsWith("http://")) {
|
||||
return Promise.reject(new Error(`${resolvedUrlString} is not a URL - needs to start with http:// or https:// or blob:. If you want to read a local file, pass \`reader: nodeReader\` to parseMedia().`));
|
||||
}
|
||||
const ownController = new AbortController;
|
||||
const cache = typeof navigator !== "undefined" && navigator.userAgent.includes("Cloudflare-Workers") ? undefined : "no-store";
|
||||
const requestedRange = range === null ? 0 : range;
|
||||
const asString = typeof resolvedUrl === "string" ? resolvedUrl : resolvedUrl.pathname;
|
||||
const requestWithoutRange = asString.endsWith(".m3u8");
|
||||
const canLiveWithoutContentLength = asString.endsWith(".m3u8") || asString.endsWith(".ts");
|
||||
const headers = requestedRange === 0 && requestWithoutRange ? {} : typeof requestedRange === "number" ? {
|
||||
Range: `bytes=${requestedRange}-`
|
||||
} : {
|
||||
Range: `bytes=${`${requestedRange[0]}-${requestedRange[1]}`}`
|
||||
};
|
||||
const res = await fetch(resolvedUrl, {
|
||||
headers,
|
||||
signal: ownController.signal,
|
||||
cache
|
||||
});
|
||||
const contentRange = res.headers.get("content-range");
|
||||
const parsedContentRange = contentRange ? parseContentRange(contentRange) : null;
|
||||
if (!res.ok) {
|
||||
throw new Error(`Server returned status code ${res.status} for ${resolvedUrl} and range ${requestedRange}`);
|
||||
}
|
||||
const { supportsContentRange } = validateContentRangeAndDetectIfSupported({
|
||||
requestedRange,
|
||||
parsedContentRange,
|
||||
statusCode: res.status
|
||||
});
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
ownController.abort(new MediaParserAbortError("Aborted by user"));
|
||||
}, { once: true });
|
||||
}
|
||||
const contentDisposition = res.headers.get("content-disposition");
|
||||
const name = contentDisposition?.match(/filename="([^"]+)"/)?.[1];
|
||||
const { contentLength, needsContentRange, reader } = await getLengthAndReader({
|
||||
canLiveWithoutContentLength,
|
||||
res,
|
||||
ownController,
|
||||
requestedWithoutRange: requestWithoutRange
|
||||
});
|
||||
const contentType = res.headers.get("content-type");
|
||||
return {
|
||||
contentLength,
|
||||
needsContentRange,
|
||||
reader,
|
||||
name,
|
||||
contentType,
|
||||
supportsContentRange
|
||||
};
|
||||
};
|
||||
var cacheKey = ({
|
||||
src,
|
||||
range
|
||||
}) => {
|
||||
return `${src}-${JSON.stringify(range)}`;
|
||||
};
|
||||
var makeFetchRequestOrGetCached = ({
|
||||
range,
|
||||
src,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
const key = cacheKey({ src, range });
|
||||
const cached = prefetchCache.get(key);
|
||||
if (cached) {
|
||||
Log.verbose(logLevel, `Reading from preload cache for ${key}`);
|
||||
return cached;
|
||||
}
|
||||
Log.verbose(logLevel, `Fetching ${key}`);
|
||||
const result = makeFetchRequest({ range, src, controller });
|
||||
prefetchCache.set(key, result);
|
||||
return result;
|
||||
};
|
||||
var fetchReadContent = async ({
|
||||
src,
|
||||
range,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const fallbackName = src.toString().split("/").pop();
|
||||
const res = makeFetchRequestOrGetCached({
|
||||
range,
|
||||
src,
|
||||
controller,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
});
|
||||
const key = cacheKey({ src, range });
|
||||
prefetchCache.delete(key);
|
||||
const {
|
||||
reader,
|
||||
contentLength,
|
||||
needsContentRange,
|
||||
name,
|
||||
supportsContentRange,
|
||||
contentType
|
||||
} = await res;
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
reader.reader.cancel().catch(() => {});
|
||||
}, { once: true });
|
||||
}
|
||||
return {
|
||||
reader,
|
||||
contentLength,
|
||||
contentType,
|
||||
name: name ?? fallbackName,
|
||||
supportsContentRange,
|
||||
needsContentRange
|
||||
};
|
||||
};
|
||||
var fetchPreload = ({
|
||||
src,
|
||||
range,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
}) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const key = cacheKey({ src, range });
|
||||
if (prefetchCache.has(key)) {
|
||||
return prefetchCache.get(key);
|
||||
}
|
||||
makeFetchRequestOrGetCached({
|
||||
range,
|
||||
src,
|
||||
controller: null,
|
||||
logLevel,
|
||||
prefetchCache
|
||||
});
|
||||
};
|
||||
var fetchReadWholeAsText = async (src) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string when using `fetchReader`");
|
||||
}
|
||||
const res = await fetch(src);
|
||||
if (!res.ok) {
|
||||
throw new Error(`Failed to fetch ${src} (HTTP code: ${res.status})`);
|
||||
}
|
||||
return res.text();
|
||||
};
|
||||
var fetchCreateAdjacentFileSource = (relativePath, src) => {
|
||||
if (typeof src !== "string" && src instanceof URL === false) {
|
||||
throw new Error("src must be a string or URL when using `fetchReader`");
|
||||
}
|
||||
return new URL(relativePath, src).toString();
|
||||
};
|
||||
|
||||
// src/readers/from-web-file.ts
|
||||
var webFileReadContent = ({ src, range, controller }) => {
|
||||
if (typeof src === "string" || src instanceof URL) {
|
||||
throw new Error("`inputTypeFileReader` only supports `File` objects");
|
||||
}
|
||||
const part = range === null ? src : typeof range === "number" ? src.slice(range) : src.slice(range[0], range[1] + 1);
|
||||
const stream = part.stream();
|
||||
const streamReader = stream.getReader();
|
||||
if (controller) {
|
||||
controller._internals.signal.addEventListener("abort", () => {
|
||||
streamReader.cancel();
|
||||
}, { once: true });
|
||||
}
|
||||
return Promise.resolve({
|
||||
reader: {
|
||||
reader: streamReader,
|
||||
async abort() {
|
||||
try {
|
||||
await streamReader.cancel();
|
||||
} catch {}
|
||||
return Promise.resolve();
|
||||
}
|
||||
},
|
||||
contentLength: src.size,
|
||||
name: src instanceof File ? src.name : src.toString(),
|
||||
supportsContentRange: true,
|
||||
contentType: src.type,
|
||||
needsContentRange: true
|
||||
});
|
||||
};
|
||||
var webFileReadWholeAsText = () => {
|
||||
throw new Error("`webFileReader` cannot read auxiliary files.");
|
||||
};
|
||||
var webFileCreateAdjacentFileSource = () => {
|
||||
throw new Error("`webFileReader` cannot create adjacent file sources.");
|
||||
};
|
||||
|
||||
// src/readers/web.ts
|
||||
var webReader = {
|
||||
read: (params) => {
|
||||
if (params.src instanceof Blob) {
|
||||
return webFileReadContent(params);
|
||||
}
|
||||
return fetchReadContent(params);
|
||||
},
|
||||
createAdjacentFileSource: (relativePath, src) => {
|
||||
if (src instanceof Blob) {
|
||||
return webFileCreateAdjacentFileSource(relativePath, src);
|
||||
}
|
||||
return fetchCreateAdjacentFileSource(relativePath, src);
|
||||
},
|
||||
readWholeAsText: (src) => {
|
||||
if (src instanceof Blob) {
|
||||
return webFileReadWholeAsText(src);
|
||||
}
|
||||
return fetchReadWholeAsText(src);
|
||||
},
|
||||
preload: ({ range, src, logLevel, prefetchCache }) => {
|
||||
if (src instanceof Blob) {
|
||||
return;
|
||||
}
|
||||
return fetchPreload({ range, src, logLevel, prefetchCache });
|
||||
}
|
||||
};
|
||||
export {
|
||||
webReader
|
||||
};
|
||||
Generated
Vendored
+18781
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+18650
File diff suppressed because it is too large
Load Diff
Generated
Vendored
+554
@@ -0,0 +1,554 @@
|
||||
// src/with-resolvers.ts
|
||||
var withResolvers = function() {
|
||||
let resolve;
|
||||
let reject;
|
||||
const promise = new Promise((res, rej) => {
|
||||
resolve = res;
|
||||
reject = rej;
|
||||
});
|
||||
return { promise, resolve, reject };
|
||||
};
|
||||
|
||||
// src/errors.ts
|
||||
class IsAnImageError extends Error {
|
||||
imageType;
|
||||
dimensions;
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
dimensions,
|
||||
imageType,
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAnImageError";
|
||||
this.imageType = imageType;
|
||||
this.dimensions = dimensions;
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAnImageError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IsAPdfError extends Error {
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAPdfError";
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAPdfError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IsAnUnsupportedFileTypeError extends Error {
|
||||
mimeType;
|
||||
sizeInBytes;
|
||||
fileName;
|
||||
constructor({
|
||||
message,
|
||||
mimeType,
|
||||
sizeInBytes,
|
||||
fileName
|
||||
}) {
|
||||
super(message);
|
||||
this.name = "IsAnUnsupportedFileTypeError";
|
||||
this.mimeType = mimeType;
|
||||
this.sizeInBytes = sizeInBytes;
|
||||
this.fileName = fileName;
|
||||
if (Error.captureStackTrace) {
|
||||
Error.captureStackTrace(this, IsAnUnsupportedFileTypeError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class MediaParserAbortError extends Error {
|
||||
constructor(message) {
|
||||
super(message);
|
||||
this.name = "MediaParserAbortError";
|
||||
this.cause = undefined;
|
||||
}
|
||||
}
|
||||
var hasBeenAborted = (error) => {
|
||||
return error instanceof MediaParserAbortError || error.name === "MediaParserAbortError" || error.name === "AbortError";
|
||||
};
|
||||
|
||||
// src/worker/serialize-error.ts
|
||||
var deserializeError = (error) => {
|
||||
switch (error.errorName) {
|
||||
case "IsAnImageError":
|
||||
return new IsAnImageError({
|
||||
dimensions: error.dimensions,
|
||||
fileName: error.fileName,
|
||||
imageType: error.imageType,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "IsAPdfError":
|
||||
return new IsAPdfError({
|
||||
fileName: error.fileName,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "IsAnUnsupportedFileTypeError":
|
||||
return new IsAnUnsupportedFileTypeError({
|
||||
fileName: error.fileName,
|
||||
mimeType: error.mimeType,
|
||||
sizeInBytes: error.sizeInBytes,
|
||||
message: error.errorMessage
|
||||
});
|
||||
case "MediaParserAbortError":
|
||||
return new MediaParserAbortError(error.errorMessage);
|
||||
case "Error":
|
||||
return new Error(error.errorMessage);
|
||||
case "AbortError":
|
||||
return new Error(error.errorMessage);
|
||||
case "NotReadableError":
|
||||
return new Error(error.errorMessage);
|
||||
case "TypeError":
|
||||
return new TypeError(error.errorMessage);
|
||||
default:
|
||||
throw new Error(`Unknown error name: ${error}`);
|
||||
}
|
||||
};
|
||||
|
||||
// src/parse-media-on-worker-entry.ts
|
||||
var convertToWorkerPayload = (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),
|
||||
src: src instanceof URL ? src.toString() : src
|
||||
};
|
||||
};
|
||||
var post = (worker, payload) => {
|
||||
worker.postMessage(payload);
|
||||
};
|
||||
var 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 } = 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?._internals.seekSignal.clearSeekIfStillSame(seek);
|
||||
};
|
||||
const seekingHintPromises = [];
|
||||
let finalSeekingHints = null;
|
||||
controller?._internals.attachSeekingHintResolution(() => {
|
||||
if (finalSeekingHints) {
|
||||
return Promise.resolve(finalSeekingHints);
|
||||
}
|
||||
if (workerTerminated) {
|
||||
return Promise.reject(new Error("Worker terminated"));
|
||||
}
|
||||
const prom = withResolvers();
|
||||
post(worker, { type: "request-get-seeking-hints" });
|
||||
seekingHintPromises.push(prom);
|
||||
return prom.promise;
|
||||
});
|
||||
const simulateSeekPromises = {};
|
||||
controller?._internals.attachSimulateSeekResolution((seek) => {
|
||||
const prom = withResolvers();
|
||||
const nonce = String(Math.random());
|
||||
post(worker, { type: "request-simulate-seek", payload: seek, nonce });
|
||||
simulateSeekPromises[nonce] = prom;
|
||||
return prom.promise;
|
||||
});
|
||||
const callbacks = {};
|
||||
const trackDoneCallbacks = {};
|
||||
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") {
|
||||
cleanup();
|
||||
const error = deserializeError(data);
|
||||
error.stack = data.errorStack;
|
||||
reject(error);
|
||||
if (data.errorName === "MediaParserAbortError") {
|
||||
finalSeekingHints = data.seekingHints;
|
||||
for (const prom of seekingHintPromises) {
|
||||
prom.resolve(finalSeekingHints);
|
||||
}
|
||||
} else {
|
||||
for (const prom of seekingHintPromises) {
|
||||
prom.reject(error);
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (data.type === "response-on-callback-request") {
|
||||
Promise.resolve().then(async () => {
|
||||
if (data.payload.callbackType === "audio-codec") {
|
||||
await params.onAudioCodec?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "container") {
|
||||
await params.onContainer?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "dimensions") {
|
||||
await params.onDimensions?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "unrotated-dimensions") {
|
||||
await params.onUnrotatedDimensions?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "video-codec") {
|
||||
await params.onVideoCodec?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "tracks") {
|
||||
await params.onTracks?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "rotation") {
|
||||
await params.onRotation?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "sample-rate") {
|
||||
await params.onSampleRate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-audio-bitrate") {
|
||||
await params.onSlowAudioBitrate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-duration-in-seconds") {
|
||||
await params.onSlowDurationInSeconds?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-fps") {
|
||||
await params.onSlowFps?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-keyframes") {
|
||||
await params.onSlowKeyframes?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-number-of-frames") {
|
||||
await params.onSlowNumberOfFrames?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-video-bitrate") {
|
||||
await params.onSlowVideoBitrate?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "slow-structure") {
|
||||
await params.onSlowStructure?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "fps") {
|
||||
await params.onFps?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "images") {
|
||||
await params.onImages?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "internal-stats") {
|
||||
await params.onInternalStats?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "is-hdr") {
|
||||
await params.onIsHdr?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "keyframes") {
|
||||
await params.onKeyframes?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "location") {
|
||||
await params.onLocation?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "m3u-streams") {
|
||||
await params.onM3uStreams?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "metadata") {
|
||||
await params.onMetadata?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "mime-type") {
|
||||
await params.onMimeType?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "name") {
|
||||
await params.onName?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "number-of-audio-channels") {
|
||||
await params.onNumberOfAudioChannels?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "size") {
|
||||
await params.onSize?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "duration-in-seconds") {
|
||||
await params.onDurationInSeconds?.(data.payload.value);
|
||||
return { payloadType: "void" };
|
||||
}
|
||||
if (data.payload.callbackType === "parse-progress") {
|
||||
await params.onParseProgress?.(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 params.onAudioTrack?.(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 params.onVideoTrack?.(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}`);
|
||||
}
|
||||
const trackDoneCallback = await callback(data.payload.value);
|
||||
if (trackDoneCallback) {
|
||||
trackDoneCallbacks[data.payload.trackId] = trackDoneCallback;
|
||||
}
|
||||
return {
|
||||
payloadType: "on-sample-response",
|
||||
registeredTrackDoneCallback: Boolean(trackDoneCallback)
|
||||
};
|
||||
}
|
||||
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}`);
|
||||
}
|
||||
const trackDoneCallback = await callback(data.payload.value);
|
||||
if (trackDoneCallback) {
|
||||
trackDoneCallbacks[data.payload.trackId] = trackDoneCallback;
|
||||
}
|
||||
return {
|
||||
payloadType: "on-sample-response",
|
||||
registeredTrackDoneCallback: Boolean(trackDoneCallback)
|
||||
};
|
||||
}
|
||||
if (data.payload.callbackType === "track-done") {
|
||||
const trackDoneCallback = trackDoneCallbacks[data.payload.trackId];
|
||||
if (!trackDoneCallback) {
|
||||
throw new Error(`No track done callback registered for track ${data.payload.trackId}`);
|
||||
}
|
||||
trackDoneCallback();
|
||||
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?.addEventListener("abort", onAbort);
|
||||
controller?.addEventListener("resume", onResume);
|
||||
controller?.addEventListener("pause", onPause);
|
||||
controller?.addEventListener("seek", onSeek);
|
||||
function cleanup() {
|
||||
worker.removeEventListener("message", onMessage);
|
||||
controller?.removeEventListener("abort", onAbort);
|
||||
controller?.removeEventListener("resume", onResume);
|
||||
controller?.removeEventListener("pause", onPause);
|
||||
controller?.removeEventListener("seek", onSeek);
|
||||
workerTerminated = true;
|
||||
worker.terminate();
|
||||
}
|
||||
controller?._internals.markAsReadyToEmitEvents();
|
||||
const val = await promise;
|
||||
cleanup();
|
||||
return val;
|
||||
};
|
||||
|
||||
// src/worker.module.ts
|
||||
var parseMediaOnWebWorker = (params) => {
|
||||
if (typeof Worker === "undefined") {
|
||||
throw new Error('"Worker" is not available. Cannot call parseMediaOnWebWorker()');
|
||||
}
|
||||
if (import.meta.url.includes(".vite/deps")) {
|
||||
const err = [
|
||||
"Detected Vite pre-bundling, which will break the worker.",
|
||||
"Please add the following to your vite.config.js:",
|
||||
" optimizeDeps: {",
|
||||
' exclude: ["@remotion/media-parser/worker"]',
|
||||
" }"
|
||||
].join(`
|
||||
`);
|
||||
throw new Error(err);
|
||||
}
|
||||
const worker = new Worker(new URL("./worker-web-entry.mjs", import.meta.url));
|
||||
return parseMediaOnWorkerImplementation(params, worker, "parseMediaOnWebWorker");
|
||||
};
|
||||
export {
|
||||
parseMediaOnWebWorker,
|
||||
hasBeenAborted,
|
||||
MediaParserAbortError,
|
||||
IsAnUnsupportedFileTypeError,
|
||||
IsAnImageError,
|
||||
IsAPdfError
|
||||
};
|
||||
Reference in New Issue
Block a user