diff --git a/Makefile b/Makefile
index 71489d6..f59e218 100644
--- a/Makefile
+++ b/Makefile
@@ -28,11 +28,8 @@ WEB_DEMUXER_ARGS = \
-O3 \
-s EXPORT_ES6=1 \
-s INVOKE_RUN=0 \
- -s ENVIRONMENT=worker \
- -s EXPORTED_RUNTIME_METHODS=cwrap,getValue,UTF8ToString \
- -s EXPORTED_FUNCTIONS=_free \
+ -s ENVIRONMENT=worker \
-s ASYNCIFY \
- -s WASM_BIGINT \
-s ALLOW_MEMORY_GROWTH=1
clean:
diff --git a/README.md b/README.md
index 817e142..0191958 100644
--- a/README.md
+++ b/README.md
@@ -63,8 +63,6 @@ async function seek(file, time) {
- [Seek Video Frame](https://foreversc.github.io/web-demuxer/#example-seek) | [code](https://github.com/ForeverSc/web-demuxer/blob/main/index.html#L96)
- [Play Video](https://foreversc.github.io/web-demuxer/#example-play) | [code](https://github.com/ForeverSc/web-demuxer/blob/main/index.html#L123)
-Here is the translation of the provided TypeScript API documentation into English:
-
## API
```typescript
new WebDemuxer(options: WebDemuxerOptions)
diff --git a/index.html b/index.html
index 81031f5..31c0877 100644
--- a/index.html
+++ b/index.html
@@ -121,13 +121,9 @@
Get All Video Packets
await demuxer.load(file);
- const videoDecoderConfig = await demuxer.getVideoDecoderConfig().catch(e => {
- console.error('get video decoder config error:', e);
- });
+ const videoDecoderConfig = await demuxer.getVideoDecoderConfig();
const videoChunk = await demuxer.seekEncodedVideoChunk(seekTime);
- console.log("getVideoStream", await demuxer.getVideoStream())
-
const decoder = new VideoDecoder({
output: (frame) => {
const scale = Math.min(canvas.width / frame.displayWidth, canvas.height / frame.displayHeight);
diff --git a/lib/web-demuxer/post.js b/lib/web-demuxer/post.js
index 0c9ebef..2b8c143 100644
--- a/lib/web-demuxer/post.js
+++ b/lib/web-demuxer/post.js
@@ -253,6 +253,10 @@ function genSendAVPacket(messageId) {
}
}
+function setAVLogLevel(level) {
+ Module.set_av_log_level(level);
+}
+
// ============ Module Register ============
Module.getAVStream = getAVStream;
Module.getAVStreams = getAVStreams;
@@ -260,6 +264,7 @@ Module.getMediaInfo = getMediaInfo;
Module.getAVPacket = getAVPacket;
Module.getAVPackets = getAVPackets;
Module.readAVPacket = readAVPacket;
+Module.setAVLogLevel = setAVLogLevel;
Module.onRuntimeInitialized = () => {
self.postMessage({ type: "WASMRuntimeInitialized" });
diff --git a/lib/web-demuxer/web_demuxer.cpp b/lib/web-demuxer/web_demuxer.cpp
index b5f7682..4c997e0 100644
--- a/lib/web-demuxer/web_demuxer.cpp
+++ b/lib/web-demuxer/web_demuxer.cpp
@@ -228,8 +228,6 @@ void gen_web_stream(WebAVStream &web_stream, AVStream *stream, AVFormatContext *
WebAVStream get_av_stream(std::string filename, int type, int wanted_stream_nb)
{
- // av_log_set_level(AV_LOG_QUIET);
-
AVFormatContext *fmt_ctx = NULL;
int ret;
@@ -600,6 +598,10 @@ int read_av_packet(std::string filename, double start, double end, int type, int
return 1;
}
+void set_av_log_level(int level) {
+ av_log_set_level(level);
+}
+
EMSCRIPTEN_BINDINGS(web_demuxer)
{
value_object("Tag")
@@ -667,6 +669,7 @@ EMSCRIPTEN_BINDINGS(web_demuxer)
function("get_av_packet", &get_av_packet, return_value_policy::take_ownership());
function("get_av_packets", &get_av_packets, return_value_policy::take_ownership());
function("read_av_packet", &read_av_packet);
+ function("set_av_log_level", &set_av_log_level);
register_vector("vector");
register_vector("vector");
diff --git a/src/ffmpeg.worker.ts b/src/ffmpeg.worker.ts
index ee30cd9..12bf75c 100644
--- a/src/ffmpeg.worker.ts
+++ b/src/ffmpeg.worker.ts
@@ -1,4 +1,4 @@
-import { WebAVPacket, WebAVStream } from "./types";
+import { FFMpegWorkerMessageType, GetAVPacketMessageData, GetAVPacketsMessageData, GetAVStreamMessageData, GetAVStreamsMessageData, GetMediaInfoMessageData, LoadWASMMessageData, ReadAVPacketMessageData, SetAVLogLevelMessageData, WebAVPacket, WebAVStream } from "./types";
let Module: any; // TODO: rm any
@@ -7,114 +7,28 @@ self.postMessage({
});
self.addEventListener("message", async function (e) {
- const { type, data = {}, msgId } = e.data;
+ const { type, data, msgId } = e.data
try {
- if (type === "LoadWASM") {
- const { wasmLoaderPath } = data || {}
-
- const ModuleLoader = await import(/* @vite-ignore */wasmLoaderPath);
- Module = await ModuleLoader.default();
- } else if (type === "GetAVStream") {
- const {
- file,
- streamType,
- streamIndex,
- } = data;
- const result = Module.getAVStream(file, streamType, streamIndex);
-
- self.postMessage(
- {
- type,
- msgId,
- result,
- },
- [result.extradata.buffer],
- );
- } else if (type === 'GetAVStreams') {
- const {
- file,
- } = data;
- const result = Module.getAVStreams(file);
-
- self.postMessage(
- {
- type,
- msgId,
- result,
- },
- result.map((stream: WebAVStream) => stream.extradata.buffer)
- );
- } else if (type === "GetMediaInfo") {
- const {
- file,
- } = data;
- const result = Module.getMediaInfo(file);
-
- self.postMessage({
- type,
- msgId,
- result,
- }, result.streams.map((stream: WebAVStream) => stream.extradata.buffer));
- } else if (type === "GetAVPacket") {
- const {
- file,
- time,
- streamType,
- streamIndex,
- } = data;
- const result = Module.getAVPacket(
- file,
- time,
- streamType,
- streamIndex,
- );
-
- self.postMessage(
- {
- type,
- msgId,
- result,
- },
- [result.data.buffer],
- );
- } else if (type === 'GetAVPackets') {
- const {
- file,
- time,
- } = data;
- const result = Module.getAVPackets(file, time);
-
- self.postMessage(
- {
- type,
- msgId,
- result,
- },
- result.map((packet: WebAVPacket) => packet.data.buffer),
- );
- } else if (type === "ReadAVPacket") {
- const {
- file,
- start,
- end,
- streamType,
- streamIndex,
- } = data;
- const result = await Module.readAVPacket(
- msgId,
- file,
- start,
- end,
- streamType,
- streamIndex,
- );
-
- self.postMessage({
- type,
- msgId,
- result,
- });
+ switch (type) {
+ case "LoadWASM":
+ return handleLoadWASM(data);
+ case "GetAVStream":
+ return handleGetAVStream(data, msgId);
+ case "GetAVStreams":
+ return handleGetAVStreams(data, msgId);
+ case "GetMediaInfo":
+ return handleGetMediaInfo(data, msgId);
+ case "GetAVPacket":
+ return handleGetAVPacket(data, msgId);
+ case "GetAVPackets":
+ return handleGetAVPackets(data, msgId);
+ case "ReadAVPacket":
+ return handleReadAVPacket(data, msgId);
+ case "SetAVLogLevel":
+ return handleSetAVLogLevel(data, msgId);
+ default:
+ return;
}
} catch (e) {
self.postMessage({
@@ -124,3 +38,107 @@ self.addEventListener("message", async function (e) {
});
}
});
+
+async function handleLoadWASM(data: LoadWASMMessageData) {
+ const { wasmLoaderPath } = data || {};
+ const ModuleLoader = await import(/* @vite-ignore */wasmLoaderPath);
+ Module = await ModuleLoader.default();
+}
+
+function handleGetAVStream(data: GetAVStreamMessageData, msgId: number) {
+ const { file, streamType, streamIndex } = data;
+ const result = Module.getAVStream(file, streamType, streamIndex);
+
+ self.postMessage(
+ {
+ type: FFMpegWorkerMessageType.GetAVStream,
+ msgId,
+ result,
+ },
+ [result.extradata.buffer],
+ );
+}
+
+function handleGetAVStreams(data: GetAVStreamsMessageData, msgId: number) {
+ const { file } = data;
+ const result = Module.getAVStreams(file);
+
+ self.postMessage(
+ {
+ type: FFMpegWorkerMessageType.GetAVStreams,
+ msgId,
+ result,
+ },
+ result.map((stream: WebAVStream) => stream.extradata.buffer),
+ );
+}
+
+function handleGetMediaInfo(data: GetMediaInfoMessageData, msgId: number) {
+ const { file } = data;
+ const result = Module.getMediaInfo(file);
+
+ self.postMessage(
+ {
+ type: FFMpegWorkerMessageType.GetMediaInfo,
+ msgId,
+ result,
+ },
+ result.streams.map((stream: WebAVStream) => stream.extradata.buffer)
+ );
+}
+
+function handleGetAVPacket(data: GetAVPacketMessageData, msgId: number) {
+ const { file, time, streamType, streamIndex } = data;
+ const result = Module.getAVPacket(file, time, streamType, streamIndex);
+
+ self.postMessage(
+ {
+ type: FFMpegWorkerMessageType.GetAVPacket,
+ msgId,
+ result,
+ },
+ [result.data.buffer],
+ );
+}
+
+function handleGetAVPackets(data: GetAVPacketsMessageData, msgId: number) {
+ const { file, time } = data;
+ const result = Module.getAVPackets(file, time);
+
+ self.postMessage(
+ {
+ type: FFMpegWorkerMessageType.GetAVPackets,
+ msgId,
+ result,
+ },
+ result.map((packet: WebAVPacket) => packet.data.buffer),
+ );
+}
+
+async function handleReadAVPacket(data: ReadAVPacketMessageData, msgId: number) {
+ const { file, start, end, streamType, streamIndex } = data;
+ const result = await Module.readAVPacket(
+ msgId,
+ file,
+ start,
+ end,
+ streamType,
+ streamIndex,
+ );
+
+ self.postMessage({
+ type: FFMpegWorkerMessageType.ReadAVPacket,
+ msgId,
+ result,
+ });
+}
+
+function handleSetAVLogLevel(data: SetAVLogLevelMessageData, msgId: number) {
+ const { level } = data
+
+ Module.setAVLogLevel(level);
+ self.postMessage({
+ type: "SetAVLogLevel",
+ msgId,
+ })
+}
\ No newline at end of file
diff --git a/src/index.ts b/src/index.ts
index 96f1f3a..ff104e1 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,3 +1,6 @@
+import { WebDemuxer } from "./web-demuxer";
+
export type { WebAVStream, WebAVPacket } from './types';
-export { AVMediaType } from './types';
-export { WebDemuxer } from "./web-demuxer";
+export { AVMediaType, AVLogLevel } from './types';
+export { WebDemuxer };
+export default WebDemuxer;
diff --git a/src/types/avutil.ts b/src/types/avutil.ts
index d3c246f..1394334 100644
--- a/src/types/avutil.ts
+++ b/src/types/avutil.ts
@@ -10,3 +10,46 @@ export enum AVMediaType {
AVMEDIA_TYPE_ATTACHMENT, ///< Opaque data information usually sparse
AVMEDIA_TYPE_NB,
}
+
+export enum AVLogLevel {
+ /**
+ * Print no output.
+ */
+ AV_LOG_QUIET = -8,
+ /**
+ * Something went really wrong and we will crash now.
+ */
+ AV_LOG_PANIC = 0,
+ /**
+ * Something went wrong and recovery is not possible.
+ * For example, no header was found for a format which depends
+ * on headers or an illegal combination of parameters is used.
+ */
+ AV_LOG_FATAL = 8,
+ /**
+ * Something went wrong and cannot losslessly be recovered.
+ * However, not all future data is affected.
+ */
+ AV_LOG_ERROR = 16,
+ /**
+ * Something somehow does not look correct. This may or may not
+ * lead to problems. An example would be the use of '-vstrict -2'.
+ */
+ AV_LOG_WARNING = 24,
+ /**
+ * Standard information.
+ */
+ AV_LOG_INFO = 32,
+ /**
+ * Detailed information.
+ */
+ AV_LOG_VERBOSE = 40,
+ /**
+ * Stuff which is only useful for libav* developers.
+ */
+ AV_LOG_DEBUG = 48,
+ /**
+ * Extremely verbose debugging, useful for libav* development.
+ */
+ AV_LOG_TRACE = 56,
+}
diff --git a/src/types/demuxer.ts b/src/types/demuxer.ts
index b1f5841..7da78be 100644
--- a/src/types/demuxer.ts
+++ b/src/types/demuxer.ts
@@ -39,3 +39,14 @@ export interface WebAVPacket {
size: number;
data: Uint8Array;
}
+
+export interface WebMediaInfo {
+ format_name: string;
+ start_time: number;
+ duration: number;
+ bit_rate: string;
+ nb_streams: number;
+ nb_chapters: number;
+ flags: number;
+ streams: WebAVStream[];
+}
diff --git a/src/types/ffmpeg-worker-message.ts b/src/types/ffmpeg-worker-message.ts
index 953844b..7e783db 100644
--- a/src/types/ffmpeg-worker-message.ts
+++ b/src/types/ffmpeg-worker-message.ts
@@ -1,4 +1,4 @@
-import { AVMediaType } from "./avutil";
+import { AVLogLevel, AVMediaType } from "./avutil";
export enum FFMpegWorkerMessageType {
FFmpegWorkerLoaded = "FFmpegWorkerLoaded",
@@ -13,6 +13,7 @@ export enum FFMpegWorkerMessageType {
AVPacketStream = "AVPacketStream",
ReadNextAVPacket = "ReadNextAVPacket",
StopReadAVPacket = "StopReadAVPacket",
+ SetAVLogLevel = "SetAVLogLevel",
}
export type FFMpegWorkerMessageData =
@@ -21,7 +22,9 @@ export type FFMpegWorkerMessageData =
| GetAVStreamMessageData
| GetAVStreamsMessageData
| ReadAVPacketMessageData
- | LoadWASMMessageData;
+ | LoadWASMMessageData
+ | SetAVLogLevelMessageData
+ | GetMediaInfoMessageData;
export interface GetAVStreamMessageData {
file: File;
@@ -57,7 +60,16 @@ export interface LoadWASMMessageData {
wasmLoaderPath: string;
}
+export interface GetMediaInfoMessageData {
+ file: File;
+}
+
+export interface SetAVLogLevelMessageData {
+ level: AVLogLevel;
+}
+
export interface FFMpegWorkerMessage {
type: FFMpegWorkerMessageType;
data: FFMpegWorkerMessageData;
+ msgId: number;
}
diff --git a/src/web-demuxer.ts b/src/web-demuxer.ts
index 575498c..9a17061 100644
--- a/src/web-demuxer.ts
+++ b/src/web-demuxer.ts
@@ -1,9 +1,11 @@
import {
+ AVLogLevel,
AVMediaType,
FFMpegWorkerMessageData,
FFMpegWorkerMessageType,
WebAVPacket,
WebAVStream,
+ WebMediaInfo,
} from "./types";
import FFmpegWorker from "./ffmpeg.worker.ts?worker&inline";
@@ -91,7 +93,7 @@ export class WebDemuxer {
}
/**
- * load a file
+ * Load a file for demuxing
* @param file file to load
* @returns load status
*/
@@ -104,7 +106,8 @@ export class WebDemuxer {
}
/**
- * destroy the instance
+ * Destroy the demuxer instance
+ * terminate the worker
*/
public destroy() {
this.file = undefined;
@@ -113,9 +116,9 @@ export class WebDemuxer {
// ================ base api ================
/**
- * get av stream
- * @param streamType
- * @param streamIndex
+ * Gets information about a specified stream in the media file.
+ * @param streamType The type of media stream
+ * @param streamIndex The index of the media stream
* @returns WebAVStream
*/
public getAVStream(
@@ -130,7 +133,8 @@ export class WebDemuxer {
}
/**
- * get all av streams
+ * Get all streams
+ * @returns WebAVStream[]
*/
public getAVStreams(): Promise {
return this.getFromWorker(FFMpegWorkerMessageType.GetAVStreams, {
@@ -139,19 +143,20 @@ export class WebDemuxer {
}
/**
- * get file media info
+ * Get file media info
+ * @returns WebMediaInfo
*/
- public getMediaInfo() {
+ public getMediaInfo(): Promise {
return this.getFromWorker(FFMpegWorkerMessageType.GetMediaInfo, {
file: this.file!,
});
}
/**
- * get av packet
- * @param time
- * @param streamType
- * @param streamIndex
+ * Gets the data at a specified time point in the media file.
+ * @param time time in seconds
+ * @param streamType The type of media stream
+ * @param streamIndex The index of the media stream
* @returns WebAVPacket
*/
public getAVPacket(
@@ -168,8 +173,8 @@ export class WebDemuxer {
}
/**
- * get av packets in all streams
- * @param time
+ * Get all packets at a time point from all streams
+ * @param time time in seconds
* @returns WebAVPacket[]
*/
public getAVPackets(
@@ -182,11 +187,11 @@ export class WebDemuxer {
}
/**
- * read av packet
- * @param start start time
- * @param end end time
- * @param streamType
- * @param streamIndex
+ * Returns a `ReadableStream` for streaming packet data.
+ * @param start start time in seconds
+ * @param end end time in seconds
+ * @param streamType The type of media stream
+ * @param streamIndex The index of the media stream
* @returns ReadableStream
*/
public readAVPacket(
@@ -261,24 +266,58 @@ export class WebDemuxer {
);
}
+ /**
+ * Set log level
+ * @param level log level
+ */
+ public setLogLevel(level: AVLogLevel) {
+ return this.getFromWorker(FFMpegWorkerMessageType.SetAVLogLevel, { level })
+ }
+
// ================ convenience api ================
+ /**
+ * Get video stream
+ * @param streamType The type of media stream
+ * @returns WebAVStream
+ */
public getVideoStream(streamIndex?: number) {
return this.getAVStream(AVMediaType.AVMEDIA_TYPE_VIDEO, streamIndex);
}
+ /**
+ * Get audio stream
+ * @param streamIndex The index of the media stream
+ * @returns
+ */
public getAudioStream(streamIndex?: number) {
return this.getAVStream(AVMediaType.AVMEDIA_TYPE_AUDIO, streamIndex);
}
+ /**
+ * Seek video packet at a time point
+ * @param time seek time in seconds
+ * @returns WebAVPacket
+ */
public seekVideoPacket(time: number) {
return this.getAVPacket(time, AVMediaType.AVMEDIA_TYPE_VIDEO);
}
+ /**
+ * Seek audio packet at a time point
+ * @param time seek time in seconds
+ * @returns WebAVPacket
+ */
public seekAudioPacket(time: number) {
return this.getAVPacket(time, AVMediaType.AVMEDIA_TYPE_AUDIO);
}
+ /**
+ * Read video packet as a stream
+ * @param start start time in seconds
+ * @param end end time in seconds
+ * @returns ReadableStream
+ */
public readVideoPacket(start?: number, end?: number) {
return this.readAVPacket(
start,
@@ -287,6 +326,12 @@ export class WebDemuxer {
);
}
+ /**
+ * Read audio packet as a stream
+ * @param start start time in seconds
+ * @param end end time in seconds
+ * @returns ReadableStream
+ */
public readAudioPacket(start?: number, end?: number) {
return this.readAVPacket(
start,
@@ -297,6 +342,11 @@ export class WebDemuxer {
// =========== custom api for webcodecs ===========
+ /**
+ * Generate VideoDecoderConfig from WebAVStream
+ * @param avStream WebAVStream
+ * @returns VideoDecoderConfig
+ */
public genVideoDecoderConfig(avStream: WebAVStream): VideoDecoderConfig {
return {
codec: avStream.codec_string,
@@ -309,6 +359,11 @@ export class WebDemuxer {
};
}
+ /**
+ * Generate EncodedVideoChunk from WebAVPacket
+ * @param avPacket WebAVPacket
+ * @returns EncodedVideoChunk
+ */
public genEncodedVideoChunk(avPacket: WebAVPacket): EncodedVideoChunk {
return new EncodedVideoChunk({
type: avPacket.keyframe === 1 ? "key" : "delta",
@@ -318,6 +373,11 @@ export class WebDemuxer {
});
}
+ /**
+ * Generate AudioDecoderConfig from WebAVStream
+ * @param avStream WebAVStream
+ * @returns AudioDecoderConfig
+ */
public genAudioDecoderConfig(avStream: WebAVStream): AudioDecoderConfig {
return {
codec: avStream.codec_string || "",
@@ -330,6 +390,11 @@ export class WebDemuxer {
};
}
+ /**
+ * Generate EncodedAudioChunk from WebAVPacket
+ * @param avPacket WebAVPacket
+ * @returns EncodedAudioChunk
+ */
public genEncodedAudioChunk(avPacket: WebAVPacket): EncodedAudioChunk {
return new EncodedAudioChunk({
type: avPacket.keyframe === 1 ? "key" : "delta",
@@ -339,26 +404,44 @@ export class WebDemuxer {
});
}
+ /**
+ * Get WebCodecs VideoDecoderConfig
+ * @returns VideoDecoderConfig
+ */
public async getVideoDecoderConfig() {
const videoStream = await this.getVideoStream();
return this.genVideoDecoderConfig(videoStream);
}
- public async seekEncodedVideoChunk(timestamp: number) {
- const videoPacket = await this.seekVideoPacket(timestamp);
+ /**
+ * Seek and return EncodedVideoChunk
+ * @param time time in seconds
+ * @returns EncodedVideoChunk
+ */
+ public async seekEncodedVideoChunk(time: number) {
+ const videoPacket = await this.seekVideoPacket(time);
return this.genEncodedVideoChunk(videoPacket);
}
+ /**
+ * Get WebCodecs AudioDecoderConfig
+ * @returns AudioDecoderConfig
+ */
public async getAudioDecoderConfig() {
const audioStream = await this.getAudioStream();
return this.genAudioDecoderConfig(audioStream);
}
- public async seekEncodedAudioChunk(timestamp: number) {
- const audioPacket = await this.seekAudioPacket(timestamp);
+ /**
+ * Seek and return EncodedAudioChunk
+ * @param time time in seconds
+ * @returns EncodedAudioChunk
+ */
+ public async seekEncodedAudioChunk(time: number) {
+ const audioPacket = await this.seekAudioPacket(time);
return this.genEncodedAudioChunk(audioPacket);
}