@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.
Files changed (41) hide show
  1. package/LICENSE +373 -0
  2. package/README.md +276 -0
  3. package/dist/bundles/mediabunny-server.cjs +3361 -0
  4. package/dist/bundles/mediabunny-server.min.cjs +10 -0
  5. package/dist/bundles/mediabunny-server.min.mjs +9 -0
  6. package/dist/bundles/mediabunny-server.mjs +3334 -0
  7. package/dist/mediabunny-server.d.ts +105 -0
  8. package/dist/modules/src/audio-decoder.d.ts +21 -0
  9. package/dist/modules/src/audio-decoder.d.ts.map +1 -0
  10. package/dist/modules/src/audio-decoder.js +132 -0
  11. package/dist/modules/src/audio-encoder.d.ts +35 -0
  12. package/dist/modules/src/audio-encoder.d.ts.map +1 -0
  13. package/dist/modules/src/audio-encoder.js +329 -0
  14. package/dist/modules/src/audio-sample.d.ts +38 -0
  15. package/dist/modules/src/audio-sample.d.ts.map +1 -0
  16. package/dist/modules/src/audio-sample.js +119 -0
  17. package/dist/modules/src/index.d.ts +39 -0
  18. package/dist/modules/src/index.d.ts.map +1 -0
  19. package/dist/modules/src/index.js +132 -0
  20. package/dist/modules/src/misc.d.ts +26 -0
  21. package/dist/modules/src/misc.d.ts.map +1 -0
  22. package/dist/modules/src/misc.js +255 -0
  23. package/dist/modules/src/video-decoder.d.ts +30 -0
  24. package/dist/modules/src/video-decoder.d.ts.map +1 -0
  25. package/dist/modules/src/video-decoder.js +214 -0
  26. package/dist/modules/src/video-encoder.d.ts +35 -0
  27. package/dist/modules/src/video-encoder.d.ts.map +1 -0
  28. package/dist/modules/src/video-encoder.js +474 -0
  29. package/dist/modules/src/video-sample.d.ts +45 -0
  30. package/dist/modules/src/video-sample.d.ts.map +1 -0
  31. package/dist/modules/src/video-sample.js +276 -0
  32. package/dist/modules/tsconfig.tsbuildinfo +1 -0
  33. package/package.json +59 -0
  34. package/src/audio-decoder.ts +120 -0
  35. package/src/audio-encoder.ts +396 -0
  36. package/src/audio-sample.ts +101 -0
  37. package/src/index.ts +104 -0
  38. package/src/misc.ts +242 -0
  39. package/src/video-decoder.ts +224 -0
  40. package/src/video-encoder.ts +568 -0
  41. 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"}