webcodecs-utils 0.2.4 → 0.2.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +142 -0
- package/dist/audio/extract-channels.d.ts +45 -0
- package/dist/audio/extract-channels.d.ts.map +1 -0
- package/dist/audio/extract-channels.js +25 -0
- package/dist/audio/get-sample-rate.d.ts +19 -0
- package/dist/audio/get-sample-rate.d.ts.map +1 -0
- package/dist/audio/get-sample-rate.js +14 -0
- package/dist/audio/mp3.d.ts +162 -0
- package/dist/audio/mp3.d.ts.map +1 -0
- package/dist/audio/mp3.js +173 -0
- package/dist/demux/example-muxer.d.ts +74 -0
- package/dist/demux/example-muxer.d.ts.map +1 -0
- package/dist/demux/example-muxer.js +40 -0
- package/dist/demux/get-chunks.d.ts +81 -0
- package/dist/demux/get-chunks.d.ts.map +1 -0
- package/dist/demux/get-chunks.js +88 -0
- package/dist/demux/mp4-demuxer.d.ts +84 -0
- package/dist/demux/mp4-demuxer.d.ts.map +1 -0
- package/dist/demux/mp4-demuxer.js +215 -0
- package/dist/demux/simple-demuxer.d.ts +49 -0
- package/dist/demux/simple-demuxer.d.ts.map +1 -0
- package/dist/demux/simple-demuxer.js +87 -0
- package/dist/in-memory-storage.d.ts +30 -0
- package/dist/in-memory-storage.d.ts.map +1 -0
- package/dist/in-memory-storage.js +54 -0
- package/dist/index.cjs +5 -298
- package/dist/index.d.ts +17 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +36 -13873
- package/dist/mux/simple-muxer.d.ts +47 -0
- package/dist/mux/simple-muxer.d.ts.map +1 -0
- package/dist/mux/simple-muxer.js +83 -0
- package/dist/polyfills/media-stream-track-processor.d.ts +5 -0
- package/dist/polyfills/media-stream-track-processor.d.ts.map +1 -0
- package/dist/polyfills/media-stream-track-processor.js +109 -0
- package/dist/streams/video-decode-stream.d.ts +11 -0
- package/dist/streams/video-decode-stream.d.ts.map +1 -0
- package/dist/streams/video-decode-stream.js +39 -0
- package/dist/streams/video-encode-stream.d.ts +15 -0
- package/dist/streams/video-encode-stream.d.ts.map +1 -0
- package/dist/streams/video-encode-stream.js +40 -0
- package/dist/streams/video-process-stream.d.ts +22 -0
- package/dist/streams/video-process-stream.d.ts.map +1 -0
- package/dist/streams/video-process-stream.js +21 -0
- package/dist/video/get-bitrate.d.ts +35 -0
- package/dist/video/get-bitrate.d.ts.map +1 -0
- package/dist/video/get-bitrate.js +12 -0
- package/dist/video/get-codec-string.d.ts +46 -0
- package/dist/video/get-codec-string.d.ts.map +1 -0
- package/dist/video/get-codec-string.js +195 -0
- package/dist/video/gpu-renderer.d.ts +108 -0
- package/dist/video/gpu-renderer.d.ts.map +1 -0
- package/dist/video/gpu-renderer.js +266 -0
- package/package.json +1 -1
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { BufferTarget as i, Output as o, Mp4OutputFormat as r, EncodedVideoPacketSource as s, EncodedAudioPacketSource as a, EncodedPacket as u } from "mediabunny";
|
|
2
|
+
class c {
|
|
3
|
+
/**
|
|
4
|
+
* Create a new ExampleMuxer.
|
|
5
|
+
*
|
|
6
|
+
* @param type - Track type: 'video' (default) or 'audio'
|
|
7
|
+
*/
|
|
8
|
+
constructor(t) {
|
|
9
|
+
this.type = t || "video", this.target = new i();
|
|
10
|
+
const e = new o({
|
|
11
|
+
format: new r(),
|
|
12
|
+
target: this.target
|
|
13
|
+
});
|
|
14
|
+
this.started = !1, this.output = e, this.source = this.type === "video" ? new s("avc") : new a("aac"), this.output.addVideoTrack(this.source), console.warn(
|
|
15
|
+
"⚠️ Demo/Learning Function: This utility is intended for demos and learning purposes only. For production use, please use a proper muxing library like MediaBunny (https://mediabunny.dev/) "
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Add an encoded chunk to the MP4 file.
|
|
20
|
+
*
|
|
21
|
+
* @param chunk - EncodedVideoChunk or EncodedAudioChunk from encoder output
|
|
22
|
+
* @param meta - Optional metadata from encoder (for video, contains decoderConfig)
|
|
23
|
+
*/
|
|
24
|
+
addChunk(t, e) {
|
|
25
|
+
this.started || (this.output.start(), this.started = !0), this.source.add(u.fromEncodedChunk(t), e);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Finalize the MP4 file and return the complete buffer.
|
|
29
|
+
*
|
|
30
|
+
* Call this after all chunks have been added.
|
|
31
|
+
*
|
|
32
|
+
* @returns Complete MP4 file as ArrayBuffer
|
|
33
|
+
*/
|
|
34
|
+
async finish() {
|
|
35
|
+
return await this.output.finalize(), this.target.buffer;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
export {
|
|
39
|
+
c as ExampleMuxer
|
|
40
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Extract all video chunks from a media file.
|
|
3
|
+
*
|
|
4
|
+
* **⚠️ Demo/Learning Only**: For production use, use MediaBunny or web-demuxer directly.
|
|
5
|
+
*
|
|
6
|
+
* @param file - Media file to demux
|
|
7
|
+
* @returns Array of EncodedVideoChunk objects
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* const chunks = await getVideoChunks(file);
|
|
12
|
+
* // Returns all video chunks from the file
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function getVideoChunks(file: File): Promise<EncodedVideoChunk[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Extract all audio chunks and decoder config from a media file.
|
|
18
|
+
*
|
|
19
|
+
* **⚠️ Demo/Learning Only**: For production use, use MediaBunny or web-demuxer directly.
|
|
20
|
+
*
|
|
21
|
+
* @param file - Media file to demux
|
|
22
|
+
* @returns Object containing chunks array and AudioDecoderConfig
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const { chunks, config } = await demuxAudio(file);
|
|
27
|
+
*
|
|
28
|
+
* // Configure decoder
|
|
29
|
+
* decoder.configure(config);
|
|
30
|
+
*
|
|
31
|
+
* // Decode all chunks
|
|
32
|
+
* for (const chunk of chunks) {
|
|
33
|
+
* decoder.decode(chunk);
|
|
34
|
+
* }
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare function demuxAudio(file: File): Promise<{
|
|
38
|
+
chunks: EncodedAudioChunk[];
|
|
39
|
+
config: AudioDecoderConfig;
|
|
40
|
+
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Extract all video chunks and decoder config from a media file.
|
|
43
|
+
*
|
|
44
|
+
* **⚠️ Demo/Learning Only**: For production use, use MediaBunny or web-demuxer directly.
|
|
45
|
+
*
|
|
46
|
+
* @param file - Media file to demux
|
|
47
|
+
* @returns Object containing chunks array and VideoDecoderConfig
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* ```typescript
|
|
51
|
+
* const { chunks, config } = await demuxVideo(file);
|
|
52
|
+
*
|
|
53
|
+
* // Configure decoder
|
|
54
|
+
* decoder.configure(config);
|
|
55
|
+
*
|
|
56
|
+
* // Decode all chunks
|
|
57
|
+
* for (const chunk of chunks) {
|
|
58
|
+
* decoder.decode(chunk);
|
|
59
|
+
* }
|
|
60
|
+
* ```
|
|
61
|
+
*/
|
|
62
|
+
export declare function demuxVideo(file: File): Promise<{
|
|
63
|
+
chunks: EncodedVideoChunk[];
|
|
64
|
+
config: VideoDecoderConfig;
|
|
65
|
+
}>;
|
|
66
|
+
/**
|
|
67
|
+
* Extract all audio chunks from a media file.
|
|
68
|
+
*
|
|
69
|
+
* **⚠️ Demo/Learning Only**: For production use, use MediaBunny or web-demuxer directly.
|
|
70
|
+
*
|
|
71
|
+
* @param file - Media file to demux
|
|
72
|
+
* @returns Array of EncodedAudioChunk objects
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* ```typescript
|
|
76
|
+
* const chunks = await getAudioChunks(file);
|
|
77
|
+
* // Returns all audio chunks from the file
|
|
78
|
+
* ```
|
|
79
|
+
*/
|
|
80
|
+
export declare function getAudioChunks(file: File): Promise<EncodedAudioChunk[]>;
|
|
81
|
+
//# sourceMappingURL=get-chunks.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-chunks.d.ts","sourceRoot":"","sources":["../../src/demux/get-chunks.ts"],"names":[],"mappings":"AAgBA;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyB7E;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAC,CAAC,CAwC/G;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,UAAU,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC;IAAC,MAAM,EAAE,iBAAiB,EAAE,CAAC;IAAC,MAAM,EAAE,kBAAkB,CAAA;CAAC,CAAC,CAyC/G;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,cAAc,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAyB7E"}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
import { WebDemuxer as u } from "web-demuxer";
|
|
2
|
+
let f = !1;
|
|
3
|
+
function m() {
|
|
4
|
+
f || (console.warn(
|
|
5
|
+
"⚠️ Demo/Learning Function: This utility is intended for demos and learning purposes only. For production use, please use a proper demuxing library like MediaBunny (https://mediabunny.dev/) or web-demuxer (https://github.com/bilibili/web-demuxer) directly."
|
|
6
|
+
), f = !0);
|
|
7
|
+
}
|
|
8
|
+
async function p(i) {
|
|
9
|
+
m();
|
|
10
|
+
const e = new u({
|
|
11
|
+
wasmFilePath: "https://cdn.jsdelivr.net/npm/web-demuxer@latest/dist/wasm-files/web-demuxer.wasm"
|
|
12
|
+
});
|
|
13
|
+
await e.load(i);
|
|
14
|
+
const a = e.read("video", 0).getReader(), n = [];
|
|
15
|
+
return new Promise(function(d) {
|
|
16
|
+
a.read().then(async function t(o) {
|
|
17
|
+
const { done: s, value: r } = o;
|
|
18
|
+
return r && n.push(r), s ? d(n) : a.read().then(t);
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
async function g(i) {
|
|
23
|
+
m();
|
|
24
|
+
const e = new u({
|
|
25
|
+
wasmFilePath: "https://cdn.jsdelivr.net/npm/web-demuxer@latest/dist/wasm-files/web-demuxer.wasm"
|
|
26
|
+
});
|
|
27
|
+
await e.load(i);
|
|
28
|
+
const a = e.read("audio", 0).getReader(), n = [];
|
|
29
|
+
await new Promise(function(s) {
|
|
30
|
+
a.read().then(async function r(w) {
|
|
31
|
+
const { done: l, value: c } = w;
|
|
32
|
+
return c && n.push(c), l ? s(n) : a.read().then(r);
|
|
33
|
+
});
|
|
34
|
+
});
|
|
35
|
+
const t = (await e.getMediaInfo()).streams.filter((s) => s.codec_type_string === "audio")[0], o = {
|
|
36
|
+
codec: t.codec_string,
|
|
37
|
+
sampleRate: t.sample_rate,
|
|
38
|
+
numberOfChannels: t.channels
|
|
39
|
+
};
|
|
40
|
+
return {
|
|
41
|
+
chunks: n,
|
|
42
|
+
config: o
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
async function x(i) {
|
|
46
|
+
m();
|
|
47
|
+
const e = new u({
|
|
48
|
+
wasmFilePath: "https://cdn.jsdelivr.net/npm/web-demuxer@latest/dist/wasm-files/web-demuxer.wasm"
|
|
49
|
+
});
|
|
50
|
+
await e.load(i);
|
|
51
|
+
const a = e.read("video", 0).getReader(), n = [];
|
|
52
|
+
await new Promise(function(s) {
|
|
53
|
+
a.read().then(async function r(w) {
|
|
54
|
+
const { done: l, value: c } = w;
|
|
55
|
+
return c && n.push(c), l ? s(n) : a.read().then(r);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
const t = (await e.getMediaInfo()).streams.filter((s) => s.codec_type_string === "video")[0], o = {
|
|
59
|
+
codec: t.codec_string,
|
|
60
|
+
codedWidth: t.width,
|
|
61
|
+
codedHeight: t.height,
|
|
62
|
+
description: t.extradata
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
chunks: n,
|
|
66
|
+
config: o
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
async function b(i) {
|
|
70
|
+
m();
|
|
71
|
+
const e = new u({
|
|
72
|
+
wasmFilePath: "https://cdn.jsdelivr.net/npm/web-demuxer@latest/dist/wasm-files/web-demuxer.wasm"
|
|
73
|
+
});
|
|
74
|
+
await e.load(i);
|
|
75
|
+
const a = e.read("audio", 0).getReader(), n = [];
|
|
76
|
+
return new Promise(function(d) {
|
|
77
|
+
a.read().then(async function t(o) {
|
|
78
|
+
const { done: s, value: r } = o;
|
|
79
|
+
return r && n.push(r), s ? d(n) : a.read().then(t);
|
|
80
|
+
});
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
export {
|
|
84
|
+
g as demuxAudio,
|
|
85
|
+
x as demuxVideo,
|
|
86
|
+
b as getAudioChunks,
|
|
87
|
+
p as getVideoChunks
|
|
88
|
+
};
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { MP4Info } from 'mp4box';
|
|
2
|
+
type MP4Info = any;
|
|
3
|
+
export interface AudioTrackData {
|
|
4
|
+
codec: string;
|
|
5
|
+
sampleRate: number;
|
|
6
|
+
numberOfChannels: number;
|
|
7
|
+
}
|
|
8
|
+
export interface VideoTrackData {
|
|
9
|
+
codec: string;
|
|
10
|
+
codedHeight: number;
|
|
11
|
+
codedWidth: number;
|
|
12
|
+
description: Uint8Array;
|
|
13
|
+
frameRate: number;
|
|
14
|
+
}
|
|
15
|
+
export interface TrackData {
|
|
16
|
+
duration: number;
|
|
17
|
+
audio?: AudioTrackData;
|
|
18
|
+
video?: VideoTrackData;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* MP4 demuxer for extracting video/audio chunks from MP4 files.
|
|
22
|
+
* Wraps MP4Box.js with a simpler API and built-in caching.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```typescript
|
|
26
|
+
* const demuxer = new MP4Demuxer(file);
|
|
27
|
+
* await demuxer.load();
|
|
28
|
+
*
|
|
29
|
+
* const tracks = demuxer.getTracks();
|
|
30
|
+
* const videoChunks = await demuxer.extractSegment('video', 0, 10);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
export declare class MP4Demuxer {
|
|
34
|
+
private file;
|
|
35
|
+
private mp4Data;
|
|
36
|
+
/**
|
|
37
|
+
* Create a new MP4Demuxer instance.
|
|
38
|
+
* @param file - The MP4 file to demux
|
|
39
|
+
* @param options - Optional configuration
|
|
40
|
+
*/
|
|
41
|
+
constructor(file: File);
|
|
42
|
+
/**
|
|
43
|
+
* Load and parse the MP4 file metadata.
|
|
44
|
+
* Must be called before extracting segments.
|
|
45
|
+
*/
|
|
46
|
+
load(onProgress?: (progress: number) => void): Promise<void>;
|
|
47
|
+
/**
|
|
48
|
+
* Get track information from the loaded MP4 file.
|
|
49
|
+
* @returns Track data including duration, codec info, etc.
|
|
50
|
+
* @throws Error if load() hasn't been called yet
|
|
51
|
+
*/
|
|
52
|
+
getTracks(): TrackData;
|
|
53
|
+
getVideoDecoderConfig(): VideoDecoderConfig | undefined;
|
|
54
|
+
getAudioDecoderConfig(): AudioDecoderConfig | undefined;
|
|
55
|
+
/**
|
|
56
|
+
* Get video track information.
|
|
57
|
+
* @returns Video track data or undefined if no video track
|
|
58
|
+
* @throws Error if load() hasn't been called yet
|
|
59
|
+
*/
|
|
60
|
+
getVideoTrack(): VideoTrackData | undefined;
|
|
61
|
+
/**
|
|
62
|
+
* Get audio track information.
|
|
63
|
+
* @returns Audio track data or undefined if no audio track
|
|
64
|
+
* @throws Error if load() hasn't been called yet
|
|
65
|
+
*/
|
|
66
|
+
getAudioTrack(): AudioTrackData | undefined;
|
|
67
|
+
/**
|
|
68
|
+
* Extract encoded chunks from a specific time range.
|
|
69
|
+
* @param trackType - "audio" or "video"
|
|
70
|
+
* @param startTime - Start time in seconds
|
|
71
|
+
* @param endTime - End time in seconds
|
|
72
|
+
* @returns Array of EncodedVideoChunk or EncodedAudioChunk
|
|
73
|
+
* @throws Error if load() hasn't been called yet
|
|
74
|
+
*/
|
|
75
|
+
extractSegment(trackType: "audio" | "video", startTime: number, endTime: number): Promise<EncodedVideoChunk[] | EncodedAudioChunk[]>;
|
|
76
|
+
/**
|
|
77
|
+
* Get the full MP4 info object from MP4Box.
|
|
78
|
+
* @returns MP4Info object with detailed track information
|
|
79
|
+
* @throws Error if load() hasn't been called yet
|
|
80
|
+
*/
|
|
81
|
+
getInfo(): MP4Info;
|
|
82
|
+
}
|
|
83
|
+
export {};
|
|
84
|
+
//# sourceMappingURL=mp4-demuxer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mp4-demuxer.d.ts","sourceRoot":"","sources":["../../src/demux/mp4-demuxer.ts"],"names":[],"mappings":"AACA,OAAe,EAAU,OAAO,EAAiE,MAAM,QAAQ,CAAA;AAK/G,KAAK,OAAO,GAAG,GAAG,CAAC;AAMnB,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,UAAU,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,KAAK,CAAC,EAAE,cAAc,CAAC;CACxB;AAqSD;;;;;;;;;;;;GAYG;AACH,qBAAa,UAAU;IACrB,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,OAAO,CAAwB;IAEvC;;;;OAIG;gBACS,IAAI,EAAE,IAAI;IAItB;;;OAGG;IACG,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlE;;;;OAIG;IACH,SAAS,IAAI,SAAS;IAStB,qBAAqB,IAAI,kBAAkB,GAAG,SAAS;IAIvD,qBAAqB,IAAI,kBAAkB,GAAG,SAAS;IAMvD;;;;OAIG;IACH,aAAa,IAAI,cAAc,GAAG,SAAS;IAI3C;;;;OAIG;IACH,aAAa,IAAI,cAAc,GAAG,SAAS;IAI3C;;;;;;;OAOG;IACG,cAAc,CAClB,SAAS,EAAE,OAAO,GAAG,OAAO,EAC5B,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,GACd,OAAO,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAOrD;;;;OAIG;IACH,OAAO,IAAI,OAAO;CAMnB"}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import M, { DataStream as x } from "mp4box";
|
|
2
|
+
const S = 100, y = 0.5, R = 0.1;
|
|
3
|
+
function I(s, e) {
|
|
4
|
+
const o = s.getTrackById(e.id);
|
|
5
|
+
for (const r of o.mdia.minf.stbl.stsd.entries) {
|
|
6
|
+
const c = r.avcC || r.hvcC || r.vpcC || r.av1C;
|
|
7
|
+
if (c) {
|
|
8
|
+
const t = new x(void 0, 0, x.BIG_ENDIAN);
|
|
9
|
+
return c.write(t), new Uint8Array(t.buffer, 8);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
throw new Error(
|
|
13
|
+
"Codec description box (avcC, hvcC, vpcC, or av1C) not found"
|
|
14
|
+
);
|
|
15
|
+
}
|
|
16
|
+
function _(s, e) {
|
|
17
|
+
var r, c;
|
|
18
|
+
const o = {
|
|
19
|
+
duration: e.duration / e.timescale
|
|
20
|
+
};
|
|
21
|
+
if (e.videoTracks.length > 0) {
|
|
22
|
+
const t = e.videoTracks[0], a = t.samples_duration / t.timescale;
|
|
23
|
+
o.video = {
|
|
24
|
+
codec: t.codec,
|
|
25
|
+
codedHeight: t.video.height,
|
|
26
|
+
codedWidth: t.video.width,
|
|
27
|
+
description: I(s, t),
|
|
28
|
+
frameRate: t.nb_samples / a
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
if (e.audioTracks.length > 0) {
|
|
32
|
+
const t = e.audioTracks[0], a = ((r = t.audio) == null ? void 0 : r.sample_rate) ?? t.timescale, d = ((c = t.audio) == null ? void 0 : c.channel_count) ?? 2;
|
|
33
|
+
o.audio = {
|
|
34
|
+
codec: t.codec,
|
|
35
|
+
sampleRate: a,
|
|
36
|
+
numberOfChannels: d
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
return o;
|
|
40
|
+
}
|
|
41
|
+
function P(s, e) {
|
|
42
|
+
return new Promise((o, r) => {
|
|
43
|
+
const c = s.stream().getReader();
|
|
44
|
+
let t = 0;
|
|
45
|
+
const a = M.createFile(!1);
|
|
46
|
+
let d = !1;
|
|
47
|
+
a.onReady = (n) => {
|
|
48
|
+
d = !0;
|
|
49
|
+
const u = _(a, n);
|
|
50
|
+
o({ info: n, trackData: u, mp4: a });
|
|
51
|
+
}, a.onError = (n) => {
|
|
52
|
+
r(
|
|
53
|
+
new Error(
|
|
54
|
+
`MP4Box parsing error: ${n instanceof Error ? n.message : String(n)}`
|
|
55
|
+
)
|
|
56
|
+
);
|
|
57
|
+
};
|
|
58
|
+
const h = async () => {
|
|
59
|
+
try {
|
|
60
|
+
const { done: n, value: u } = await c.read();
|
|
61
|
+
if (n) {
|
|
62
|
+
if (!d)
|
|
63
|
+
throw new Error("Invalid MP4 file: metadata not available");
|
|
64
|
+
a.flush();
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
if (d) {
|
|
68
|
+
c.releaseLock(), a.flush();
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
const m = u.buffer;
|
|
72
|
+
if (m.fileStart = t, t += u.length, e && e(t / s.size), a.appendBuffer(m), t < s.size)
|
|
73
|
+
return h();
|
|
74
|
+
if (a.flush(), !d)
|
|
75
|
+
throw new Error("Invalid MP4 file: metadata not available");
|
|
76
|
+
} catch (n) {
|
|
77
|
+
r(n);
|
|
78
|
+
}
|
|
79
|
+
};
|
|
80
|
+
h().catch(r);
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function A(s, e, o, r, c) {
|
|
84
|
+
const { mp4: t, info: a } = e;
|
|
85
|
+
return new Promise((d, h) => {
|
|
86
|
+
let n = 0, u = !1, m = 0;
|
|
87
|
+
const b = o === "audio" ? EncodedAudioChunk : EncodedVideoChunk, k = [], D = o === "audio" ? a.audioTracks[0] ?? null : a.videoTracks[0] ?? null;
|
|
88
|
+
if (!D) {
|
|
89
|
+
d([]);
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
m = D.id;
|
|
93
|
+
const w = a.duration / a.timescale - R, E = Math.min(c || w, w);
|
|
94
|
+
for (const i in a.tracks) {
|
|
95
|
+
const p = a.tracks[i];
|
|
96
|
+
t.unsetExtractionOptions(p.id);
|
|
97
|
+
}
|
|
98
|
+
t.onSamples = (i, p, l) => {
|
|
99
|
+
for (const f of l) {
|
|
100
|
+
const g = f.cts / f.timescale;
|
|
101
|
+
g < E && k.push(
|
|
102
|
+
new b({
|
|
103
|
+
type: f.is_sync ? "key" : "delta",
|
|
104
|
+
timestamp: Math.round(1e6 * g),
|
|
105
|
+
duration: Math.round(
|
|
106
|
+
1e6 * (f.duration / f.timescale)
|
|
107
|
+
),
|
|
108
|
+
data: f.data
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
if (l.length > 0 && t.releaseUsedSamples(m, l[l.length - 1].number), k.length > 0) {
|
|
113
|
+
const g = k[k.length - 1].timestamp / 1e6;
|
|
114
|
+
(Math.abs(g - E) < y || g > E) && (u = !0, t.stop(), t.flush(), d(k));
|
|
115
|
+
}
|
|
116
|
+
}, t.onError = (i) => {
|
|
117
|
+
h(
|
|
118
|
+
new Error(
|
|
119
|
+
`Extraction error: ${i instanceof Error ? i.message : String(i)}`
|
|
120
|
+
)
|
|
121
|
+
);
|
|
122
|
+
}, t.setExtractionOptions(m, null, { nbSamples: S });
|
|
123
|
+
const T = t.seek(r, !0), v = s.slice(T.offset).stream().getReader();
|
|
124
|
+
n = T.offset;
|
|
125
|
+
const C = async () => {
|
|
126
|
+
try {
|
|
127
|
+
const { done: i, value: p } = await v.read();
|
|
128
|
+
if (i || u) {
|
|
129
|
+
v.releaseLock(), t.flush();
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
const l = p.buffer;
|
|
133
|
+
return l.fileStart = n, n += p.length, t.appendBuffer(l), C();
|
|
134
|
+
} catch (i) {
|
|
135
|
+
h(i);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
t.start(), C().catch(h);
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
class N {
|
|
142
|
+
/**
|
|
143
|
+
* Create a new MP4Demuxer instance.
|
|
144
|
+
* @param file - The MP4 file to demux
|
|
145
|
+
* @param options - Optional configuration
|
|
146
|
+
*/
|
|
147
|
+
constructor(e) {
|
|
148
|
+
this.mp4Data = null, this.file = e;
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Load and parse the MP4 file metadata.
|
|
152
|
+
* Must be called before extracting segments.
|
|
153
|
+
*/
|
|
154
|
+
async load(e) {
|
|
155
|
+
this.mp4Data = await P(this.file, e);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Get track information from the loaded MP4 file.
|
|
159
|
+
* @returns Track data including duration, codec info, etc.
|
|
160
|
+
* @throws Error if load() hasn't been called yet
|
|
161
|
+
*/
|
|
162
|
+
getTracks() {
|
|
163
|
+
if (!this.mp4Data)
|
|
164
|
+
throw new Error("MP4Demuxer: Must call load() before getTracks()");
|
|
165
|
+
return this.mp4Data.trackData;
|
|
166
|
+
}
|
|
167
|
+
getVideoDecoderConfig() {
|
|
168
|
+
return this.getVideoTrack();
|
|
169
|
+
}
|
|
170
|
+
getAudioDecoderConfig() {
|
|
171
|
+
return this.getAudioTrack();
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Get video track information.
|
|
175
|
+
* @returns Video track data or undefined if no video track
|
|
176
|
+
* @throws Error if load() hasn't been called yet
|
|
177
|
+
*/
|
|
178
|
+
getVideoTrack() {
|
|
179
|
+
return this.getTracks().video;
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Get audio track information.
|
|
183
|
+
* @returns Audio track data or undefined if no audio track
|
|
184
|
+
* @throws Error if load() hasn't been called yet
|
|
185
|
+
*/
|
|
186
|
+
getAudioTrack() {
|
|
187
|
+
return this.getTracks().audio;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Extract encoded chunks from a specific time range.
|
|
191
|
+
* @param trackType - "audio" or "video"
|
|
192
|
+
* @param startTime - Start time in seconds
|
|
193
|
+
* @param endTime - End time in seconds
|
|
194
|
+
* @returns Array of EncodedVideoChunk or EncodedAudioChunk
|
|
195
|
+
* @throws Error if load() hasn't been called yet
|
|
196
|
+
*/
|
|
197
|
+
async extractSegment(e, o, r) {
|
|
198
|
+
if (!this.mp4Data)
|
|
199
|
+
throw new Error("MP4Demuxer: Must call load() before extractSegment()");
|
|
200
|
+
return A(this.file, this.mp4Data, e, o, r);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Get the full MP4 info object from MP4Box.
|
|
204
|
+
* @returns MP4Info object with detailed track information
|
|
205
|
+
* @throws Error if load() hasn't been called yet
|
|
206
|
+
*/
|
|
207
|
+
getInfo() {
|
|
208
|
+
if (!this.mp4Data)
|
|
209
|
+
throw new Error("MP4Demuxer: Must call load() before getInfo()");
|
|
210
|
+
return this.mp4Data.info;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
export {
|
|
214
|
+
N as MP4Demuxer
|
|
215
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Simple wrapper around web-demuxer for easier usage
|
|
3
|
+
* Provides streaming and batch access to encoded chunks
|
|
4
|
+
*
|
|
5
|
+
* **⚠️ Demo/Learning Only**: For production use, use web-demuxer or MediaBunny directly.
|
|
6
|
+
*/
|
|
7
|
+
export declare class SimpleDemuxer {
|
|
8
|
+
private demuxer;
|
|
9
|
+
private file;
|
|
10
|
+
private loaded;
|
|
11
|
+
constructor(file: File, options?: {
|
|
12
|
+
wasmFilePath?: string;
|
|
13
|
+
});
|
|
14
|
+
/**
|
|
15
|
+
* Load and parse the media file
|
|
16
|
+
*/
|
|
17
|
+
load(): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Get a ReadableStream of video chunks from start time (default: 0)
|
|
20
|
+
* @param startTime Start time in seconds (default: 0)
|
|
21
|
+
*/
|
|
22
|
+
videoStream(startTime?: number): ReadableStream<EncodedVideoChunk>;
|
|
23
|
+
/**
|
|
24
|
+
* Get a ReadableStream of audio chunks from start time (default: 0)
|
|
25
|
+
* @param startTime Start time in seconds (default: 0)
|
|
26
|
+
*/
|
|
27
|
+
audioStream(startTime?: number): ReadableStream<EncodedAudioChunk>;
|
|
28
|
+
/**
|
|
29
|
+
* Get video decoder configuration
|
|
30
|
+
*/
|
|
31
|
+
getVideoDecoderConfig(): Promise<VideoDecoderConfig>;
|
|
32
|
+
/**
|
|
33
|
+
* Get audio decoder configuration
|
|
34
|
+
*/
|
|
35
|
+
getAudioDecoderConfig(): Promise<AudioDecoderConfig>;
|
|
36
|
+
/**
|
|
37
|
+
* Get a segment of encoded chunks as an array
|
|
38
|
+
* @param type Track type ('video' or 'audio')
|
|
39
|
+
* @param start Start time in seconds
|
|
40
|
+
* @param end End time in seconds
|
|
41
|
+
* @returns Array of encoded chunks
|
|
42
|
+
*/
|
|
43
|
+
getSegment(type: 'video' | 'audio', start: number, end: number): Promise<EncodedVideoChunk[] | EncodedAudioChunk[]>;
|
|
44
|
+
/**
|
|
45
|
+
* Get media information (tracks, duration, etc.)
|
|
46
|
+
*/
|
|
47
|
+
getMediaInfo(): Promise<import("web-demuxer").WebMediaInfo>;
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=simple-demuxer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"simple-demuxer.d.ts","sourceRoot":"","sources":["../../src/demux/simple-demuxer.ts"],"names":[],"mappings":"AAgBA;;;;;GAKG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,OAAO,CAAa;IAC5B,OAAO,CAAC,IAAI,CAAO;IACnB,OAAO,CAAC,MAAM,CAAS;gBAEX,IAAI,EAAE,IAAI,EAAE,OAAO,CAAC,EAAE;QAAE,YAAY,CAAC,EAAE,MAAM,CAAA;KAAE;IAS3D;;OAEG;IACG,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IAK3B;;;OAGG;IACH,WAAW,CAAC,SAAS,GAAE,MAAU,GAAG,cAAc,CAAC,iBAAiB,CAAC;IAOrE;;;OAGG;IACH,WAAW,CAAC,SAAS,GAAE,MAAU,GAAG,cAAc,CAAC,iBAAiB,CAAC;IAOrE;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAO1D;;OAEG;IACG,qBAAqB,IAAI,OAAO,CAAC,kBAAkB,CAAC;IAO1D;;;;;;OAMG;IACG,UAAU,CACd,IAAI,EAAE,OAAO,GAAG,OAAO,EACvB,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,iBAAiB,EAAE,GAAG,iBAAiB,EAAE,CAAC;IAsBrD;;OAEG;IACG,YAAY;CAMnB"}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { WebDemuxer as l } from "web-demuxer";
|
|
2
|
+
let a = !1;
|
|
3
|
+
function u() {
|
|
4
|
+
a || (console.warn(
|
|
5
|
+
"⚠️ Demo/Learning Function: SimpleDemuxer is intended for demos and learning purposes only. For production use, please use web-demuxer directly (https://github.com/bilibili/web-demuxer) or MediaBunny (https://mediabunny.dev/) for more features."
|
|
6
|
+
), a = !0);
|
|
7
|
+
}
|
|
8
|
+
class h {
|
|
9
|
+
constructor(e, r) {
|
|
10
|
+
this.loaded = !1, u(), this.file = e, this.demuxer = new l({
|
|
11
|
+
wasmFilePath: (r == null ? void 0 : r.wasmFilePath) || "https://cdn.jsdelivr.net/npm/web-demuxer@latest/dist/wasm-files/web-demuxer.wasm"
|
|
12
|
+
});
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load and parse the media file
|
|
16
|
+
*/
|
|
17
|
+
async load() {
|
|
18
|
+
await this.demuxer.load(this.file), this.loaded = !0;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Get a ReadableStream of video chunks from start time (default: 0)
|
|
22
|
+
* @param startTime Start time in seconds (default: 0)
|
|
23
|
+
*/
|
|
24
|
+
videoStream(e = 0) {
|
|
25
|
+
if (!this.loaded)
|
|
26
|
+
throw new Error("SimpleDemuxer: Must call load() before accessing streams");
|
|
27
|
+
return this.demuxer.read("video", e);
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Get a ReadableStream of audio chunks from start time (default: 0)
|
|
31
|
+
* @param startTime Start time in seconds (default: 0)
|
|
32
|
+
*/
|
|
33
|
+
audioStream(e = 0) {
|
|
34
|
+
if (!this.loaded)
|
|
35
|
+
throw new Error("SimpleDemuxer: Must call load() before accessing streams");
|
|
36
|
+
return this.demuxer.read("audio", e);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Get video decoder configuration
|
|
40
|
+
*/
|
|
41
|
+
async getVideoDecoderConfig() {
|
|
42
|
+
if (!this.loaded)
|
|
43
|
+
throw new Error("SimpleDemuxer: Must call load() before getting config");
|
|
44
|
+
return await this.demuxer.getDecoderConfig("video");
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Get audio decoder configuration
|
|
48
|
+
*/
|
|
49
|
+
async getAudioDecoderConfig() {
|
|
50
|
+
if (!this.loaded)
|
|
51
|
+
throw new Error("SimpleDemuxer: Must call load() before getting config");
|
|
52
|
+
return await this.demuxer.getDecoderConfig("audio");
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Get a segment of encoded chunks as an array
|
|
56
|
+
* @param type Track type ('video' or 'audio')
|
|
57
|
+
* @param start Start time in seconds
|
|
58
|
+
* @param end End time in seconds
|
|
59
|
+
* @returns Array of encoded chunks
|
|
60
|
+
*/
|
|
61
|
+
async getSegment(e, r, o) {
|
|
62
|
+
if (!this.loaded)
|
|
63
|
+
throw new Error("SimpleDemuxer: Must call load() before getting segments");
|
|
64
|
+
const d = this.demuxer.read(e, r, o), t = [], i = d.getReader();
|
|
65
|
+
try {
|
|
66
|
+
for (; ; ) {
|
|
67
|
+
const { done: s, value: n } = await i.read();
|
|
68
|
+
if (s) break;
|
|
69
|
+
t.push(n);
|
|
70
|
+
}
|
|
71
|
+
} finally {
|
|
72
|
+
i.releaseLock();
|
|
73
|
+
}
|
|
74
|
+
return t;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Get media information (tracks, duration, etc.)
|
|
78
|
+
*/
|
|
79
|
+
async getMediaInfo() {
|
|
80
|
+
if (!this.loaded)
|
|
81
|
+
throw new Error("SimpleDemuxer: Must call load() before getting media info");
|
|
82
|
+
return await this.demuxer.getMediaInfo();
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
export {
|
|
86
|
+
h as SimpleDemuxer
|
|
87
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* In-memory storage system that stores data in fixed-size chunks
|
|
3
|
+
* and efficiently handles overlapping writes.
|
|
4
|
+
*/
|
|
5
|
+
export declare class InMemoryStorage {
|
|
6
|
+
private chunks;
|
|
7
|
+
private _chunkSize;
|
|
8
|
+
private _size;
|
|
9
|
+
/**
|
|
10
|
+
* Create a new InMemoryStorage instance
|
|
11
|
+
* @param chunkSize Size of each chunk in bytes (default: 10MB)
|
|
12
|
+
*/
|
|
13
|
+
constructor(chunkSize?: number);
|
|
14
|
+
/**
|
|
15
|
+
* Write data to storage, handling overlaps efficiently
|
|
16
|
+
* @param data Data to write
|
|
17
|
+
* @param position Position to write at
|
|
18
|
+
*/
|
|
19
|
+
write(data: Uint8Array, position: number): void;
|
|
20
|
+
/**
|
|
21
|
+
* Get the total size of data written
|
|
22
|
+
*/
|
|
23
|
+
get size(): number;
|
|
24
|
+
/**
|
|
25
|
+
* Convert all stored chunks to a single Blob
|
|
26
|
+
* @param type MIME type for the Blob
|
|
27
|
+
*/
|
|
28
|
+
toBlob(type?: string): Blob;
|
|
29
|
+
}
|
|
30
|
+
//# sourceMappingURL=in-memory-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"in-memory-storage.d.ts","sourceRoot":"","sources":["../src/in-memory-storage.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,qBAAa,eAAe;IACxB,OAAO,CAAC,MAAM,CAAiC;IAC/C,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,KAAK,CAAK;IAElB;;;OAGG;gBACS,SAAS,GAAE,MAAyB;IAIhD;;;;OAIG;IACH,KAAK,CAAC,IAAI,EAAE,UAAU,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA2C/C;;OAEG;IACH,IAAI,IAAI,IAAI,MAAM,CAEjB;IAED;;;OAGG;IACH,MAAM,CAAC,IAAI,GAAE,MAAmC,GAAG,IAAI;CAgC1D"}
|