@mediabunny/server 1.45.5 → 1.47.0
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 +16 -0
- package/dist/bundles/mediabunny-server.cjs +48 -5
- package/dist/bundles/mediabunny-server.min.cjs +2 -2
- package/dist/bundles/mediabunny-server.min.mjs +2 -2
- package/dist/bundles/mediabunny-server.mjs +55 -6
- package/dist/mediabunny-server.d.ts +20 -1
- package/dist/modules/src/audio-sample.d.ts.map +1 -1
- package/dist/modules/src/audio-sample.js +3 -0
- package/dist/modules/src/index.d.ts +21 -2
- package/dist/modules/src/index.d.ts.map +1 -1
- package/dist/modules/src/index.js +20 -2
- package/dist/modules/src/misc.d.ts +2 -2
- package/dist/modules/src/misc.d.ts.map +1 -1
- package/dist/modules/src/misc.js +23 -2
- package/dist/modules/src/video-decoder.js +1 -1
- package/dist/modules/src/video-encoder.js +1 -1
- package/dist/modules/src/video-sample.d.ts.map +1 -1
- package/dist/modules/src/video-sample.js +3 -0
- package/dist/modules/tsconfig.tsbuildinfo +1 -1
- package/package.json +2 -2
- package/src/audio-sample.ts +3 -0
- package/src/index.ts +59 -2
- package/src/misc.ts +31 -2
- package/src/video-decoder.ts +1 -1
- package/src/video-encoder.ts +1 -1
- package/src/video-sample.ts +3 -0
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mediabunny/server",
|
|
3
3
|
"author": "Vanilagy",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.47.0",
|
|
5
5
|
"description": "Adds full video and audio decoder and encoder support to Mediabunny for use in server-side environments (Node, Bun, Deno). Based on NodeAV.",
|
|
6
6
|
"main": "./dist/bundles/mediabunny-server.cjs",
|
|
7
7
|
"module": "./dist/bundles/mediabunny-server.mjs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"url": "https://github.com/sponsors/Vanilagy"
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
|
-
"node-av": "^
|
|
37
|
+
"node-av": "^6.0.0"
|
|
38
38
|
},
|
|
39
39
|
"peerDependencies": {
|
|
40
40
|
"mediabunny": "^1.45.0"
|
package/src/audio-sample.ts
CHANGED
|
@@ -42,6 +42,9 @@ export class AvFrameAudioSampleResource extends AudioSampleResource {
|
|
|
42
42
|
constructor(frame: NodeAv.Frame) {
|
|
43
43
|
super();
|
|
44
44
|
|
|
45
|
+
if (!(frame instanceof NodeAv.Frame)) {
|
|
46
|
+
throw new TypeError('frame must be a NodeAv.Frame.');
|
|
47
|
+
}
|
|
45
48
|
if (frame.getMediaType() !== NodeAv.AVMEDIA_TYPE_AUDIO) {
|
|
46
49
|
throw new Error('AvFrameAudioSampleResource must be initialized with an audio frame.');
|
|
47
50
|
}
|
package/src/index.ts
CHANGED
|
@@ -6,7 +6,14 @@
|
|
|
6
6
|
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import {
|
|
9
|
+
import {
|
|
10
|
+
AudioSample,
|
|
11
|
+
MaybePromise,
|
|
12
|
+
registerDecoder,
|
|
13
|
+
registerEncoder,
|
|
14
|
+
registerVideoSampleTransformer,
|
|
15
|
+
VideoSample,
|
|
16
|
+
} from 'mediabunny';
|
|
10
17
|
import * as NodeAv from 'node-av';
|
|
11
18
|
import { NodeAvVideoDecoder } from './video-decoder';
|
|
12
19
|
import { NodeAvVideoEncoder } from './video-encoder';
|
|
@@ -28,6 +35,29 @@ if ((globalThis as Record<symbol, unknown>)[SERVER_LOADED_SYMBOL]) {
|
|
|
28
35
|
|
|
29
36
|
let registered = false;
|
|
30
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Options for configuring Mediabunny's server-side polyfills.
|
|
40
|
+
* @group \@mediabunny/server
|
|
41
|
+
* @public
|
|
42
|
+
*/
|
|
43
|
+
type MediabunnyServerOptions = {
|
|
44
|
+
/**
|
|
45
|
+
* The hardware context to use for hardware-accelerated decoding and encoding. When set to a
|
|
46
|
+
* `NodeAv.HardwareContext`, that context is used directly. When set to a function, the function is invoked (with
|
|
47
|
+
* no caching) every time a hardware decoder or encoder codec is resolved, receiving the codec ID and returning the
|
|
48
|
+
* context to use for it (or `null` for no hardware acceleration).
|
|
49
|
+
*
|
|
50
|
+
* When not set, a context is automatically detected on first use and then cached.
|
|
51
|
+
*/
|
|
52
|
+
hardwareContext?:
|
|
53
|
+
| NodeAv.HardwareContext
|
|
54
|
+
| null
|
|
55
|
+
| ((codecId: NodeAv.AVCodecID) => MaybePromise<NodeAv.HardwareContext | null>);
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** @internal */
|
|
59
|
+
export let _serverOptions: MediabunnyServerOptions = {};
|
|
60
|
+
|
|
31
61
|
/**
|
|
32
62
|
* Registers video and audio decoders and encoders for all codecs, using FFmpeg's libavcodec under the hood.
|
|
33
63
|
* Additionally, a custom `VideoSample` transformer based on libavfilter is registered to enable resizing, rotation and
|
|
@@ -38,14 +68,32 @@ let registered = false;
|
|
|
38
68
|
* The decoders and encoders will automatically detect hardware acceleration support for each codec and platform and
|
|
39
69
|
* make use of it if applicable.
|
|
40
70
|
*
|
|
71
|
+
* You can pass optional {@link MediabunnyServerOptions} for additional configuration.
|
|
72
|
+
*
|
|
41
73
|
* @group \@mediabunny/server
|
|
42
74
|
* @public
|
|
43
75
|
*/
|
|
44
|
-
export const registerMediabunnyServer = () => {
|
|
76
|
+
export const registerMediabunnyServer = (options: MediabunnyServerOptions = {}) => {
|
|
77
|
+
if (typeof options !== 'object' || !options) {
|
|
78
|
+
throw new TypeError('options must be an object.');
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
options.hardwareContext != null
|
|
82
|
+
&& !(
|
|
83
|
+
options.hardwareContext instanceof NodeAv.HardwareContext || typeof options.hardwareContext === 'function'
|
|
84
|
+
)
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
'options.hardwareContext, when provided, must be a NodeAv.HardwareContext, a function, or null.',
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
|
|
45
91
|
if (registered) {
|
|
46
92
|
return;
|
|
47
93
|
}
|
|
94
|
+
|
|
48
95
|
registered = true;
|
|
96
|
+
_serverOptions = options;
|
|
49
97
|
|
|
50
98
|
NodeAv.Log.setLevel(NodeAv.AV_LOG_ERROR);
|
|
51
99
|
|
|
@@ -60,6 +108,8 @@ export const registerMediabunnyServer = () => {
|
|
|
60
108
|
registerVideoSampleTransformer(transformVideoSample);
|
|
61
109
|
};
|
|
62
110
|
|
|
111
|
+
export type { MediabunnyServerOptions };
|
|
112
|
+
|
|
63
113
|
export { AvFrameVideoSampleResource } from './video-sample';
|
|
64
114
|
export { AvFrameAudioSampleResource } from './audio-sample';
|
|
65
115
|
|
|
@@ -76,6 +126,13 @@ export { AvFrameAudioSampleResource } from './audio-sample';
|
|
|
76
126
|
* @public
|
|
77
127
|
*/
|
|
78
128
|
export const toAvFrame = async (sample: VideoSample | AudioSample, frame: NodeAv.Frame) => {
|
|
129
|
+
if (!(sample instanceof VideoSample) && !(sample instanceof AudioSample)) {
|
|
130
|
+
throw new TypeError('sample must be a VideoSample or an AudioSample.');
|
|
131
|
+
}
|
|
132
|
+
if (!(frame instanceof NodeAv.Frame)) {
|
|
133
|
+
throw new TypeError('frame must be a NodeAv.Frame.');
|
|
134
|
+
}
|
|
135
|
+
|
|
79
136
|
if (sample instanceof VideoSample) {
|
|
80
137
|
if (sample._data instanceof AvFrameVideoSampleResource) {
|
|
81
138
|
// We're overriding the frame, so release whatever it referenced before, otherwise av_frame_ref leaks it
|
package/src/misc.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
|
|
9
9
|
import { VideoSamplePixelFormat, MediaCodec } from 'mediabunny';
|
|
10
10
|
import * as NodeAv from 'node-av';
|
|
11
|
+
import { _serverOptions } from './index';
|
|
11
12
|
|
|
12
13
|
export const CODEC_TO_CODEC_ID: Partial<Record<MediaCodec, NodeAv.AVCodecID>> = {
|
|
13
14
|
avc: NodeAv.AV_CODEC_ID_H264,
|
|
@@ -27,14 +28,35 @@ export const CODEC_TO_CODEC_ID: Partial<Record<MediaCodec, NodeAv.AVCodecID>> =
|
|
|
27
28
|
|
|
28
29
|
let cachedHardwareContext: NodeAv.HardwareContext | null | undefined = undefined;
|
|
29
30
|
export const getHardwareContext = (): NodeAv.HardwareContext | null => {
|
|
31
|
+
if (_serverOptions.hardwareContext !== undefined && typeof _serverOptions.hardwareContext !== 'function') {
|
|
32
|
+
// Return the user-provided one
|
|
33
|
+
return _serverOptions.hardwareContext;
|
|
34
|
+
}
|
|
35
|
+
|
|
30
36
|
if (cachedHardwareContext === undefined) {
|
|
31
37
|
cachedHardwareContext = NodeAv.HardwareContext.auto();
|
|
32
38
|
}
|
|
33
39
|
return cachedHardwareContext;
|
|
34
40
|
};
|
|
35
41
|
|
|
42
|
+
const validateHwContext = (hw: NodeAv.HardwareContext | null) => {
|
|
43
|
+
if (hw !== null && !(hw instanceof NodeAv.HardwareContext)) {
|
|
44
|
+
throw new TypeError(
|
|
45
|
+
'When serverOptions.hardwareContext is a function, it must return or resolve to a'
|
|
46
|
+
+ ' NodeAv.HardwareContext or null.',
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
36
51
|
const hardwareDecoderCodecCache = new Map<NodeAv.AVCodecID, NodeAv.Codec | null>();
|
|
37
|
-
export const getHardwareDecoderCodec = (codecId: NodeAv.AVCodecID): NodeAv.Codec | null => {
|
|
52
|
+
export const getHardwareDecoderCodec = async (codecId: NodeAv.AVCodecID): Promise<NodeAv.Codec | null> => {
|
|
53
|
+
if (typeof _serverOptions.hardwareContext === 'function') {
|
|
54
|
+
const hw = await _serverOptions.hardwareContext(codecId);
|
|
55
|
+
validateHwContext(hw);
|
|
56
|
+
|
|
57
|
+
return hw?.getDecoderCodec(codecId) ?? null;
|
|
58
|
+
}
|
|
59
|
+
|
|
38
60
|
if (!hardwareDecoderCodecCache.has(codecId)) {
|
|
39
61
|
const hw = getHardwareContext();
|
|
40
62
|
hardwareDecoderCodecCache.set(codecId, hw?.getDecoderCodec(codecId) ?? null);
|
|
@@ -43,7 +65,14 @@ export const getHardwareDecoderCodec = (codecId: NodeAv.AVCodecID): NodeAv.Codec
|
|
|
43
65
|
};
|
|
44
66
|
|
|
45
67
|
const hardwareEncoderCodecCache = new Map<NodeAv.AVCodecID, NodeAv.Codec | null>();
|
|
46
|
-
export const getHardwareEncoderCodec = (codecId: NodeAv.AVCodecID): NodeAv.Codec | null => {
|
|
68
|
+
export const getHardwareEncoderCodec = async (codecId: NodeAv.AVCodecID): Promise<NodeAv.Codec | null> => {
|
|
69
|
+
if (typeof _serverOptions.hardwareContext === 'function') {
|
|
70
|
+
const hw = await _serverOptions.hardwareContext(codecId);
|
|
71
|
+
validateHwContext(hw);
|
|
72
|
+
|
|
73
|
+
return hw?.getEncoderCodec(codecId) ?? null;
|
|
74
|
+
}
|
|
75
|
+
|
|
47
76
|
if (!hardwareEncoderCodecCache.has(codecId)) {
|
|
48
77
|
const hw = getHardwareContext();
|
|
49
78
|
hardwareEncoderCodecCache.set(codecId, hw?.getEncoderCodec(codecId) ?? null);
|
package/src/video-decoder.ts
CHANGED
|
@@ -58,7 +58,7 @@ export class NodeAvVideoDecoder extends CustomVideoDecoder {
|
|
|
58
58
|
) {
|
|
59
59
|
codec = NodeAv.Codec.findDecoder(codecId);
|
|
60
60
|
} else {
|
|
61
|
-
codec = getHardwareDecoderCodec(codecId) ?? NodeAv.Codec.findDecoder(codecId);
|
|
61
|
+
codec = (await getHardwareDecoderCodec(codecId)) ?? NodeAv.Codec.findDecoder(codecId);
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
if (!codec) {
|
package/src/video-encoder.ts
CHANGED
|
@@ -83,7 +83,7 @@ export class NodeAvVideoEncoder extends CustomVideoEncoder {
|
|
|
83
83
|
} else if (this.config.hardwareAcceleration === 'prefer-software') {
|
|
84
84
|
codec = NodeAv.Codec.findEncoder(codecId);
|
|
85
85
|
} else {
|
|
86
|
-
codec = getHardwareEncoderCodec(codecId) ?? NodeAv.Codec.findEncoder(codecId);
|
|
86
|
+
codec = (await getHardwareEncoderCodec(codecId)) ?? NodeAv.Codec.findEncoder(codecId);
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
if (!codec) {
|
package/src/video-sample.ts
CHANGED
|
@@ -73,6 +73,9 @@ export class AvFrameVideoSampleResource extends VideoSampleResource {
|
|
|
73
73
|
constructor(frame: NodeAv.Frame) {
|
|
74
74
|
super();
|
|
75
75
|
|
|
76
|
+
if (!(frame instanceof NodeAv.Frame)) {
|
|
77
|
+
throw new TypeError('frame must be a NodeAv.Frame.');
|
|
78
|
+
}
|
|
76
79
|
if (frame.getMediaType() !== NodeAv.AVMEDIA_TYPE_VIDEO) {
|
|
77
80
|
throw new Error('AvFrameVideoSampleResource must be initialized with a video frame.');
|
|
78
81
|
}
|