@mediabunny/server 1.45.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/LICENSE +373 -0
- package/README.md +276 -0
- package/dist/bundles/mediabunny-server.cjs +3361 -0
- package/dist/bundles/mediabunny-server.min.cjs +10 -0
- package/dist/bundles/mediabunny-server.min.mjs +9 -0
- package/dist/bundles/mediabunny-server.mjs +3334 -0
- package/dist/mediabunny-server.d.ts +105 -0
- package/dist/modules/src/audio-decoder.d.ts +21 -0
- package/dist/modules/src/audio-decoder.d.ts.map +1 -0
- package/dist/modules/src/audio-decoder.js +132 -0
- package/dist/modules/src/audio-encoder.d.ts +35 -0
- package/dist/modules/src/audio-encoder.d.ts.map +1 -0
- package/dist/modules/src/audio-encoder.js +329 -0
- package/dist/modules/src/audio-sample.d.ts +38 -0
- package/dist/modules/src/audio-sample.d.ts.map +1 -0
- package/dist/modules/src/audio-sample.js +119 -0
- package/dist/modules/src/index.d.ts +39 -0
- package/dist/modules/src/index.d.ts.map +1 -0
- package/dist/modules/src/index.js +132 -0
- package/dist/modules/src/misc.d.ts +26 -0
- package/dist/modules/src/misc.d.ts.map +1 -0
- package/dist/modules/src/misc.js +255 -0
- package/dist/modules/src/video-decoder.d.ts +30 -0
- package/dist/modules/src/video-decoder.d.ts.map +1 -0
- package/dist/modules/src/video-decoder.js +214 -0
- package/dist/modules/src/video-encoder.d.ts +35 -0
- package/dist/modules/src/video-encoder.d.ts.map +1 -0
- package/dist/modules/src/video-encoder.js +474 -0
- package/dist/modules/src/video-sample.d.ts +45 -0
- package/dist/modules/src/video-sample.d.ts.map +1 -0
- package/dist/modules/src/video-sample.js +276 -0
- package/dist/modules/tsconfig.tsbuildinfo +1 -0
- package/package.json +59 -0
- package/src/audio-decoder.ts +120 -0
- package/src/audio-encoder.ts +396 -0
- package/src/audio-sample.ts +101 -0
- package/src/index.ts +104 -0
- package/src/misc.ts +242 -0
- package/src/video-decoder.ts +224 -0
- package/src/video-encoder.ts +568 -0
- package/src/video-sample.ts +313 -0
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*!
|
|
3
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
4
|
+
*
|
|
5
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
6
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
7
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
8
|
+
*/
|
|
9
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
12
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
13
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
14
|
+
}
|
|
15
|
+
Object.defineProperty(o, k2, desc);
|
|
16
|
+
}) : (function(o, m, k, k2) {
|
|
17
|
+
if (k2 === undefined) k2 = k;
|
|
18
|
+
o[k2] = m[k];
|
|
19
|
+
}));
|
|
20
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
21
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
22
|
+
}) : function(o, v) {
|
|
23
|
+
o["default"] = v;
|
|
24
|
+
});
|
|
25
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
26
|
+
var ownKeys = function(o) {
|
|
27
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
28
|
+
var ar = [];
|
|
29
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
30
|
+
return ar;
|
|
31
|
+
};
|
|
32
|
+
return ownKeys(o);
|
|
33
|
+
};
|
|
34
|
+
return function (mod) {
|
|
35
|
+
if (mod && mod.__esModule) return mod;
|
|
36
|
+
var result = {};
|
|
37
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
38
|
+
__setModuleDefault(result, mod);
|
|
39
|
+
return result;
|
|
40
|
+
};
|
|
41
|
+
})();
|
|
42
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
|
+
exports.NodeAvVideoDecoder = void 0;
|
|
44
|
+
const mediabunny_1 = require("mediabunny");
|
|
45
|
+
const NodeAv = __importStar(require("node-av"));
|
|
46
|
+
const misc_1 = require("./misc");
|
|
47
|
+
const misc_2 = require("../../../src/misc");
|
|
48
|
+
const video_sample_1 = require("./video-sample");
|
|
49
|
+
class NodeAvVideoDecoder extends mediabunny_1.CustomVideoDecoder {
|
|
50
|
+
constructor() {
|
|
51
|
+
super(...arguments);
|
|
52
|
+
this.codecContext = null;
|
|
53
|
+
// Bookkeeping to restore the original timing information
|
|
54
|
+
this.preciseTimings = [];
|
|
55
|
+
}
|
|
56
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
57
|
+
static supports(codec, config) {
|
|
58
|
+
return codec === 'avc' || codec === 'hevc' || codec === 'vp8' || codec === 'vp9' || codec === 'av1';
|
|
59
|
+
}
|
|
60
|
+
async init() {
|
|
61
|
+
this.frame = new NodeAv.Frame();
|
|
62
|
+
this.frame.alloc();
|
|
63
|
+
this.packet = new NodeAv.Packet();
|
|
64
|
+
this.packet.alloc();
|
|
65
|
+
}
|
|
66
|
+
async initCodecContext(packet) {
|
|
67
|
+
(0, misc_2.assert)(this.codecContext === null);
|
|
68
|
+
const codecId = misc_1.CODEC_TO_CODEC_ID[this.codec];
|
|
69
|
+
(0, misc_2.assert)(codecId !== undefined);
|
|
70
|
+
let codec;
|
|
71
|
+
if (this.codec === 'vp9' && packet.sideData.alpha) {
|
|
72
|
+
codec = NodeAv.Codec.findDecoderByName(misc_1.LIBVPX_VP9) ?? NodeAv.Codec.findDecoder(codecId);
|
|
73
|
+
}
|
|
74
|
+
else if (
|
|
75
|
+
// This check used to be "is not prefer-hardware", meaning it would default to using software decode. I
|
|
76
|
+
// didn't leave a comment for that back then so I actually don't know what it was for. I'm sure it was to
|
|
77
|
+
// work around an issue but, I don't know. Not using hardware decode at all by defaults feels wrong to me,
|
|
78
|
+
// so I changed it to what it is right now. If an issue is encountered, I can always change it.
|
|
79
|
+
this.config.hardwareAcceleration === 'prefer-software'
|
|
80
|
+
|| this.codec === 'av1' // https://github.com/opencv/opencv/issues/24430
|
|
81
|
+
) {
|
|
82
|
+
codec = NodeAv.Codec.findDecoder(codecId);
|
|
83
|
+
}
|
|
84
|
+
else {
|
|
85
|
+
codec = (0, misc_1.getHardwareDecoderCodec)(codecId) ?? NodeAv.Codec.findDecoder(codecId);
|
|
86
|
+
}
|
|
87
|
+
if (!codec) {
|
|
88
|
+
throw new Error(`Unable to obtain libav codec for '${this.codec}'.`);
|
|
89
|
+
}
|
|
90
|
+
const codecContext = new NodeAv.CodecContext();
|
|
91
|
+
codecContext.allocContext3(codec);
|
|
92
|
+
this.pixelAspectRatio = (0, misc_2.simplifyRational)({
|
|
93
|
+
num: (this.config.displayAspectWidth ?? this.config.codedWidth ?? 0) * (this.config.codedHeight ?? 0),
|
|
94
|
+
den: (this.config.displayAspectHeight ?? this.config.codedHeight ?? 0) * (this.config.codedWidth ?? 0) || 1,
|
|
95
|
+
});
|
|
96
|
+
codecContext.width = this.config.codedWidth ?? 0; // Fucky that the dimensions can be optional, but oh well
|
|
97
|
+
codecContext.height = this.config.codedHeight ?? 0;
|
|
98
|
+
codecContext.codecType = NodeAv.AVMEDIA_TYPE_VIDEO;
|
|
99
|
+
codecContext.codecId = codecId;
|
|
100
|
+
codecContext.extraData = this.config.description
|
|
101
|
+
? Buffer.from((0, misc_2.toUint8Array)(this.config.description))
|
|
102
|
+
: null;
|
|
103
|
+
codecContext.sampleAspectRatio = new NodeAv.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
104
|
+
const ret = await codecContext.open2();
|
|
105
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Open codec context');
|
|
106
|
+
this.codecContext = codecContext;
|
|
107
|
+
}
|
|
108
|
+
async decode(packet) {
|
|
109
|
+
if (this.codecContext === null) {
|
|
110
|
+
await this.initCodecContext(packet);
|
|
111
|
+
(0, misc_2.assert)(this.codecContext);
|
|
112
|
+
}
|
|
113
|
+
this.packet.isKeyframe = packet.type === 'key';
|
|
114
|
+
this.packet.data = Buffer.from(packet.data);
|
|
115
|
+
this.packet.timeBase = { num: 1, den: 1e6 };
|
|
116
|
+
this.packet.pts = BigInt(packet.microsecondTimestamp);
|
|
117
|
+
this.packet.dts = NodeAv.AV_NOPTS_VALUE;
|
|
118
|
+
this.packet.duration = BigInt(packet.microsecondDuration);
|
|
119
|
+
if (packet.sideData.alpha) {
|
|
120
|
+
const matroskaBlockAdditional = Buffer.alloc(8 + packet.sideData.alpha.byteLength);
|
|
121
|
+
matroskaBlockAdditional[7] = 1; // BlockAddId
|
|
122
|
+
matroskaBlockAdditional.set(packet.sideData.alpha, 8);
|
|
123
|
+
this.packet.addSideData(NodeAv.AV_PKT_DATA_MATROSKA_BLOCKADDITIONAL, matroskaBlockAdditional);
|
|
124
|
+
}
|
|
125
|
+
const preciseTimingIndex = (0, misc_2.binarySearchLessOrEqual)(this.preciseTimings, packet.microsecondTimestamp, x => x.microsecondTimestamp);
|
|
126
|
+
const existingEntry = preciseTimingIndex !== -1
|
|
127
|
+
? this.preciseTimings[preciseTimingIndex]
|
|
128
|
+
: null;
|
|
129
|
+
if (existingEntry && existingEntry.microsecondTimestamp === packet.microsecondTimestamp) {
|
|
130
|
+
if (existingEntry.timestamp !== packet.timestamp) {
|
|
131
|
+
// Mapping isn't unique, can't use the timestamp
|
|
132
|
+
existingEntry.timestampIsValid = false;
|
|
133
|
+
}
|
|
134
|
+
if (existingEntry.duration !== packet.duration) {
|
|
135
|
+
// Mapping isn't unique, can't use the duration
|
|
136
|
+
existingEntry.durationIsValid = false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
else {
|
|
140
|
+
this.preciseTimings.splice(preciseTimingIndex + 1, 0, {
|
|
141
|
+
microsecondTimestamp: packet.microsecondTimestamp,
|
|
142
|
+
timestamp: packet.timestamp,
|
|
143
|
+
duration: packet.duration,
|
|
144
|
+
timestampIsValid: true,
|
|
145
|
+
durationIsValid: true,
|
|
146
|
+
});
|
|
147
|
+
// Make sure it doesn't grow indefinitely
|
|
148
|
+
if (this.preciseTimings.length > 128) {
|
|
149
|
+
this.preciseTimings.shift();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
const ret = await this.codecContext.sendPacket(this.packet);
|
|
153
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Send packet');
|
|
154
|
+
while (true) {
|
|
155
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
156
|
+
if (receiveRet === NodeAv.AVERROR_EAGAIN || receiveRet === NodeAv.AVERROR_EOF) {
|
|
157
|
+
break;
|
|
158
|
+
}
|
|
159
|
+
this.receiveFrame(receiveRet);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
receiveFrame(ret) {
|
|
163
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Receive frame');
|
|
164
|
+
this.frame.sampleAspectRatio = new NodeAv.Rational(this.pixelAspectRatio.num, this.pixelAspectRatio.den);
|
|
165
|
+
let timestamp = Number(this.frame.pts) / 1e6;
|
|
166
|
+
let duration = Number(this.frame.duration) / 1e6;
|
|
167
|
+
const preciseTimingIndex = (0, misc_2.binarySearchLessOrEqual)(this.preciseTimings, Number(this.frame.pts), x => x.microsecondTimestamp);
|
|
168
|
+
const entry = preciseTimingIndex !== -1
|
|
169
|
+
? this.preciseTimings[preciseTimingIndex]
|
|
170
|
+
: null;
|
|
171
|
+
// If there's a relevant timing entry, refine the frame's timing data to get better accuracy than
|
|
172
|
+
// microseconds
|
|
173
|
+
if (entry && entry.microsecondTimestamp === Number(this.frame.pts)) {
|
|
174
|
+
if (entry.timestampIsValid) {
|
|
175
|
+
timestamp = entry.timestamp;
|
|
176
|
+
}
|
|
177
|
+
if (entry.durationIsValid) {
|
|
178
|
+
duration = entry.duration;
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const clone = this.frame.clone();
|
|
182
|
+
if (!clone) {
|
|
183
|
+
throw new Error('Frame clone allocation failed.');
|
|
184
|
+
}
|
|
185
|
+
this.onSample(new mediabunny_1.VideoSample(new video_sample_1.AvFrameVideoSampleResource(clone), {
|
|
186
|
+
timestamp,
|
|
187
|
+
duration,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
async flush() {
|
|
191
|
+
if (!this.codecContext) {
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
// Send null packet to signal flush
|
|
195
|
+
const ret = await this.codecContext.sendPacket(null);
|
|
196
|
+
NodeAv.FFmpegError.throwIfError(ret, 'Flush decoder');
|
|
197
|
+
// Keep receiving frames until no more are available
|
|
198
|
+
while (true) {
|
|
199
|
+
const receiveRet = await this.codecContext.receiveFrame(this.frame);
|
|
200
|
+
if (receiveRet === NodeAv.AVERROR_EAGAIN || receiveRet === NodeAv.AVERROR_EOF) {
|
|
201
|
+
// No more frames available
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
this.receiveFrame(receiveRet);
|
|
205
|
+
}
|
|
206
|
+
this.codecContext.flushBuffers();
|
|
207
|
+
}
|
|
208
|
+
close() {
|
|
209
|
+
this.codecContext?.freeContext();
|
|
210
|
+
this.frame.free();
|
|
211
|
+
this.packet.free();
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
exports.NodeAvVideoDecoder = NodeAvVideoDecoder;
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026-present, Vanilagy and contributors
|
|
3
|
+
*
|
|
4
|
+
* This Source Code Form is subject to the terms of the Mozilla Public
|
|
5
|
+
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
6
|
+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
7
|
+
*/
|
|
8
|
+
import { CustomVideoEncoder, type MaybePromise, VideoCodec, VideoSample } from 'mediabunny';
|
|
9
|
+
import * as NodeAv from 'node-av';
|
|
10
|
+
export declare class NodeAvVideoEncoder extends CustomVideoEncoder {
|
|
11
|
+
frame: NodeAv.Frame;
|
|
12
|
+
packet: NodeAv.Packet;
|
|
13
|
+
avCodec: NodeAv.Codec;
|
|
14
|
+
codecContext: NodeAv.CodecContext | null;
|
|
15
|
+
lastBuffer: Buffer | null;
|
|
16
|
+
packetEmitted: boolean;
|
|
17
|
+
scaler: NodeAv.SoftwareScaleContext | null;
|
|
18
|
+
lastScalerKey: string | null;
|
|
19
|
+
dstFrame: NodeAv.Frame | null;
|
|
20
|
+
preciseTimings: {
|
|
21
|
+
microsecondTimestamp: number;
|
|
22
|
+
timestamp: number;
|
|
23
|
+
duration: number;
|
|
24
|
+
timestampIsValid: boolean;
|
|
25
|
+
durationIsValid: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
static supports(codec: VideoCodec, config: VideoEncoderConfig): boolean;
|
|
28
|
+
init(): Promise<void>;
|
|
29
|
+
createCodecContext(): Promise<void>;
|
|
30
|
+
encode(videoSample: VideoSample, options: VideoEncoderEncodeOptions): Promise<void>;
|
|
31
|
+
receivePacket(ret: number): void;
|
|
32
|
+
flush(): Promise<void>;
|
|
33
|
+
close(): MaybePromise<void>;
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=video-encoder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"video-encoder.d.ts","sourceRoot":"","sources":["../../../src/video-encoder.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EACN,kBAAkB,EAClB,KAAK,YAAY,EAEjB,UAAU,EACV,WAAW,EAGX,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,MAAM,MAAM,SAAS,CAAC;AA2BlC,qBAAa,kBAAmB,SAAQ,kBAAkB;IACzD,KAAK,EAAG,MAAM,CAAC,KAAK,CAAC;IACrB,MAAM,EAAG,MAAM,CAAC,MAAM,CAAC;IACvB,OAAO,EAAG,MAAM,CAAC,KAAK,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC,YAAY,GAAG,IAAI,CAAQ;IAChD,UAAU,EAAE,MAAM,GAAG,IAAI,CAAQ;IACjC,aAAa,UAAS;IAEtB,MAAM,EAAE,MAAM,CAAC,oBAAoB,GAAG,IAAI,CAAQ;IAClD,aAAa,EAAE,MAAM,GAAG,IAAI,CAAQ;IACpC,QAAQ,EAAE,MAAM,CAAC,KAAK,GAAG,IAAI,CAAQ;IAGrC,cAAc,EAAE;QACf,oBAAoB,EAAE,MAAM,CAAC;QAC7B,SAAS,EAAE,MAAM,CAAC;QAClB,QAAQ,EAAE,MAAM,CAAC;QACjB,gBAAgB,EAAE,OAAO,CAAC;QAC1B,eAAe,EAAE,OAAO,CAAC;KACzB,EAAE,CAAM;WAEO,QAAQ,CAAC,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO;IAK1E,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BrB,kBAAkB;IAoFlB,MAAM,CAAC,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC;IAoHzF,aAAa,CAAC,GAAG,EAAE,MAAM;IA4OnB,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAyB5B,KAAK,IAAI,YAAY,CAAC,IAAI,CAAC;CAO3B"}
|