@m7mdxzx1/discord-video-strem 0.0.1
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 +294 -0
- package/dist/client/GatewayEvents.d.ts +27 -0
- package/dist/client/GatewayEvents.js +1 -0
- package/dist/client/GatewayOpCodes.d.ts +40 -0
- package/dist/client/GatewayOpCodes.js +41 -0
- package/dist/client/Streamer.d.ts +41 -0
- package/dist/client/Streamer.js +189 -0
- package/dist/client/encryptor/TransportEncryptor.d.ts +16 -0
- package/dist/client/encryptor/TransportEncryptor.js +38 -0
- package/dist/client/index.d.ts +4 -0
- package/dist/client/index.js +4 -0
- package/dist/client/packet/AudioPacketizer.d.ts +8 -0
- package/dist/client/packet/AudioPacketizer.js +22 -0
- package/dist/client/packet/BaseMediaPacketizer.d.ts +109 -0
- package/dist/client/packet/BaseMediaPacketizer.js +243 -0
- package/dist/client/packet/VideoPacketizerAnnexB.d.ts +132 -0
- package/dist/client/packet/VideoPacketizerAnnexB.js +231 -0
- package/dist/client/packet/VideoPacketizerVP8.d.ts +15 -0
- package/dist/client/packet/VideoPacketizerVP8.js +56 -0
- package/dist/client/packet/index.d.ts +4 -0
- package/dist/client/packet/index.js +4 -0
- package/dist/client/processing/AnnexBHelper.d.ts +93 -0
- package/dist/client/processing/AnnexBHelper.js +132 -0
- package/dist/client/voice/BaseMediaConnection.d.ts +118 -0
- package/dist/client/voice/BaseMediaConnection.js +319 -0
- package/dist/client/voice/MediaUdp.d.ts +26 -0
- package/dist/client/voice/MediaUdp.js +140 -0
- package/dist/client/voice/StreamConnection.d.ts +10 -0
- package/dist/client/voice/StreamConnection.js +30 -0
- package/dist/client/voice/VoiceConnection.d.ts +7 -0
- package/dist/client/voice/VoiceConnection.js +10 -0
- package/dist/client/voice/VoiceMessageTypes.d.ts +136 -0
- package/dist/client/voice/VoiceMessageTypes.js +1 -0
- package/dist/client/voice/VoiceOpCodes.d.ts +21 -0
- package/dist/client/voice/VoiceOpCodes.js +22 -0
- package/dist/client/voice/index.d.ts +5 -0
- package/dist/client/voice/index.js +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/media/AudioStream.d.ts +25 -0
- package/dist/media/AudioStream.js +63 -0
- package/dist/media/BaseMediaStream.d.ts +31 -0
- package/dist/media/BaseMediaStream.js +145 -0
- package/dist/media/LibavCodecId.d.ts +541 -0
- package/dist/media/LibavCodecId.js +552 -0
- package/dist/media/LibavDecoder.d.ts +5 -0
- package/dist/media/LibavDecoder.js +63 -0
- package/dist/media/LibavDemuxer.d.ts +23 -0
- package/dist/media/LibavDemuxer.js +295 -0
- package/dist/media/VideoStream.d.ts +7 -0
- package/dist/media/VideoStream.js +10 -0
- package/dist/media/index.d.ts +1 -0
- package/dist/media/index.js +1 -0
- package/dist/media/newApi.d.ts +126 -0
- package/dist/media/newApi.js +387 -0
- package/dist/media/utils.d.ts +1 -0
- package/dist/media/utils.js +4 -0
- package/dist/utils.d.ts +28 -0
- package/dist/utils.js +54 -0
- package/package.json +69 -0
- package/src/client/GatewayEvents.ts +41 -0
- package/src/client/GatewayOpCodes.ts +40 -0
- package/src/client/Streamer.ts +279 -0
- package/src/client/encryptor/TransportEncryptor.ts +62 -0
- package/src/client/index.ts +4 -0
- package/src/client/packet/AudioPacketizer.ts +28 -0
- package/src/client/packet/BaseMediaPacketizer.ts +307 -0
- package/src/client/packet/VideoPacketizerAnnexB.ts +263 -0
- package/src/client/packet/VideoPacketizerVP8.ts +73 -0
- package/src/client/packet/index.ts +4 -0
- package/src/client/processing/AnnexBHelper.ts +142 -0
- package/src/client/voice/BaseMediaConnection.ts +407 -0
- package/src/client/voice/MediaUdp.ts +171 -0
- package/src/client/voice/StreamConnection.ts +33 -0
- package/src/client/voice/VoiceConnection.ts +15 -0
- package/src/client/voice/VoiceMessageTypes.ts +164 -0
- package/src/client/voice/VoiceOpCodes.ts +21 -0
- package/src/client/voice/index.ts +5 -0
- package/src/index.ts +5 -0
- package/src/media/AudioStream.ts +81 -0
- package/src/media/BaseMediaStream.ts +173 -0
- package/src/media/LibavCodecId.ts +566 -0
- package/src/media/LibavDecoder.ts +82 -0
- package/src/media/LibavDemuxer.ts +348 -0
- package/src/media/VideoStream.ts +15 -0
- package/src/media/index.ts +1 -0
- package/src/media/newApi.ts +618 -0
- package/src/media/utils.ts +6 -0
- package/src/utils.ts +77 -0
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import pDebounce from "p-debounce";
|
|
2
|
+
import LibAV, { type CodecParameters } from "@lng2004/libav.js-variant-webcodecs-avf-with-decoders";
|
|
3
|
+
import { Log } from "debug-level";
|
|
4
|
+
import { uid } from "uid";
|
|
5
|
+
import { AVCodecID } from "./LibavCodecId.js";
|
|
6
|
+
import {
|
|
7
|
+
H264Helpers, H264NalUnitTypes,
|
|
8
|
+
H265Helpers, H265NalUnitTypes,
|
|
9
|
+
splitNalu, mergeNalu
|
|
10
|
+
} from "../client/processing/AnnexBHelper.js";
|
|
11
|
+
import { PassThrough } from "node:stream";
|
|
12
|
+
import type { Readable } from "node:stream";
|
|
13
|
+
|
|
14
|
+
type MediaStreamInfoCommon = {
|
|
15
|
+
index: number,
|
|
16
|
+
codec: AVCodecID,
|
|
17
|
+
codecpar: CodecParameters,
|
|
18
|
+
}
|
|
19
|
+
type VideoStreamInfo = MediaStreamInfoCommon & {
|
|
20
|
+
width: number,
|
|
21
|
+
height: number,
|
|
22
|
+
framerate_num: number,
|
|
23
|
+
framerate_den: number,
|
|
24
|
+
extradata?: unknown
|
|
25
|
+
}
|
|
26
|
+
type AudioStreamInfo = MediaStreamInfoCommon & {
|
|
27
|
+
sample_rate: number
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
type H264ParamSets = Record<"sps" | "pps", Buffer[]>
|
|
31
|
+
type H265ParamSets = Record<"vps" | "sps" | "pps", Buffer[]>
|
|
32
|
+
|
|
33
|
+
const allowedVideoCodec = new Set([
|
|
34
|
+
AVCodecID.AV_CODEC_ID_H264,
|
|
35
|
+
AVCodecID.AV_CODEC_ID_H265,
|
|
36
|
+
AVCodecID.AV_CODEC_ID_VP8,
|
|
37
|
+
AVCodecID.AV_CODEC_ID_VP9,
|
|
38
|
+
AVCodecID.AV_CODEC_ID_AV1
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
const allowedAudioCodec = new Set([
|
|
42
|
+
AVCodecID.AV_CODEC_ID_OPUS
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
// Parse the avcC atom, which contains SPS and PPS
|
|
46
|
+
function parseavcC(input: Buffer) {
|
|
47
|
+
let buf = input;
|
|
48
|
+
if (buf[0] !== 1)
|
|
49
|
+
throw new Error("Only configurationVersion 1 is supported");
|
|
50
|
+
// Skip a bunch of stuff we don't care about
|
|
51
|
+
buf = buf.subarray(5);
|
|
52
|
+
|
|
53
|
+
const sps: Buffer[] = [];
|
|
54
|
+
const pps: Buffer[] = [];
|
|
55
|
+
|
|
56
|
+
// Read the SPS
|
|
57
|
+
const spsCount = buf[0] & 0b11111;
|
|
58
|
+
buf = buf.subarray(1);
|
|
59
|
+
for (let i = 0; i < spsCount; ++i) {
|
|
60
|
+
const spsLength = buf.readUInt16BE();
|
|
61
|
+
buf = buf.subarray(2);
|
|
62
|
+
sps.push(buf.subarray(0, spsLength));
|
|
63
|
+
buf = buf.subarray(spsLength);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Read the PPS
|
|
67
|
+
const ppsCount = buf[0];
|
|
68
|
+
buf = buf.subarray(1);
|
|
69
|
+
for (let i = 0; i < ppsCount; ++i) {
|
|
70
|
+
const ppsLength = buf.readUInt16BE();
|
|
71
|
+
buf = buf.subarray(2);
|
|
72
|
+
pps.push(buf.subarray(0, ppsLength));
|
|
73
|
+
buf = buf.subarray(ppsLength);
|
|
74
|
+
}
|
|
75
|
+
return { sps, pps }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Parse the hvcC atom, which contains VPS, SPS, PPS
|
|
79
|
+
function parsehvcC(input: Buffer) {
|
|
80
|
+
let buf = input;
|
|
81
|
+
if (buf[0] !== 1)
|
|
82
|
+
throw new Error("Only configurationVersion 1 is supported");
|
|
83
|
+
// Skip a bunch of stuff we don't care about
|
|
84
|
+
buf = buf.subarray(22);
|
|
85
|
+
|
|
86
|
+
const vps: Buffer[] = [];
|
|
87
|
+
const sps: Buffer[] = [];
|
|
88
|
+
const pps: Buffer[] = [];
|
|
89
|
+
|
|
90
|
+
const numOfArrays = buf[0];
|
|
91
|
+
buf = buf.subarray(1);
|
|
92
|
+
|
|
93
|
+
for (let i = 0; i < numOfArrays; ++i) {
|
|
94
|
+
const naluType = buf[0] & 0b111111;
|
|
95
|
+
buf = buf.subarray(1);
|
|
96
|
+
|
|
97
|
+
const naluCount = buf.readUInt16BE();
|
|
98
|
+
buf = buf.subarray(2);
|
|
99
|
+
|
|
100
|
+
for (let j = 0; j < naluCount; ++j) {
|
|
101
|
+
const naluLength = buf.readUInt16BE();
|
|
102
|
+
buf = buf.subarray(2);
|
|
103
|
+
|
|
104
|
+
const nalu = buf.subarray(0, naluLength);
|
|
105
|
+
buf = buf.subarray(naluLength);
|
|
106
|
+
|
|
107
|
+
if (naluType === H265NalUnitTypes.VPS_NUT)
|
|
108
|
+
vps.push(nalu);
|
|
109
|
+
else if (naluType === H265NalUnitTypes.SPS_NUT)
|
|
110
|
+
sps.push(nalu);
|
|
111
|
+
else if (naluType === H265NalUnitTypes.PPS_NUT)
|
|
112
|
+
pps.push(nalu);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
return { vps, sps, pps }
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function h264AddParamSets(frame: Buffer, paramSets: H264ParamSets) {
|
|
119
|
+
const { sps, pps } = paramSets;
|
|
120
|
+
const nalus = splitNalu(frame);
|
|
121
|
+
// Technically non-IDR I frames exist ("open GOP"), but they're exceedingly
|
|
122
|
+
// rare in the wild, and no encoder produces it by default
|
|
123
|
+
let isIDR = false;
|
|
124
|
+
let hasSPS = false;
|
|
125
|
+
let hasPPS = false;
|
|
126
|
+
for (const nalu of nalus) {
|
|
127
|
+
const naluType = H264Helpers.getUnitType(nalu);
|
|
128
|
+
if (naluType === H264NalUnitTypes.CodedSliceIdr)
|
|
129
|
+
isIDR = true;
|
|
130
|
+
else if (naluType === H264NalUnitTypes.SPS)
|
|
131
|
+
hasSPS = true;
|
|
132
|
+
else if (naluType === H264NalUnitTypes.PPS)
|
|
133
|
+
hasPPS = true;
|
|
134
|
+
}
|
|
135
|
+
if (!isIDR) {
|
|
136
|
+
// Not an IDR, return as is
|
|
137
|
+
return frame;
|
|
138
|
+
}
|
|
139
|
+
const chunks = [];
|
|
140
|
+
if (!hasPPS)
|
|
141
|
+
chunks.push(...sps);
|
|
142
|
+
if (!hasSPS)
|
|
143
|
+
chunks.push(...pps);
|
|
144
|
+
return mergeNalu([...chunks, ...nalus]);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function h265AddParamSets(frame: Buffer, paramSets: H265ParamSets) {
|
|
148
|
+
const { vps, sps, pps } = paramSets;
|
|
149
|
+
const nalus = splitNalu(frame);
|
|
150
|
+
// Technically non-IDR I frames exist ("open GOP"), but they're exceedingly
|
|
151
|
+
// rare in the wild, and no encoder produces it by default
|
|
152
|
+
let isIDR = false;
|
|
153
|
+
let hasVPS = false;
|
|
154
|
+
let hasSPS = false;
|
|
155
|
+
let hasPPS = false;
|
|
156
|
+
for (const nalu of nalus) {
|
|
157
|
+
const naluType = H265Helpers.getUnitType(nalu);
|
|
158
|
+
if (naluType === H265NalUnitTypes.IDR_N_LP || naluType === H265NalUnitTypes.IDR_W_RADL)
|
|
159
|
+
isIDR = true;
|
|
160
|
+
else if (naluType === H265NalUnitTypes.VPS_NUT)
|
|
161
|
+
hasVPS = true;
|
|
162
|
+
else if (naluType === H265NalUnitTypes.SPS_NUT)
|
|
163
|
+
hasSPS = true;
|
|
164
|
+
else if (naluType === H265NalUnitTypes.PPS_NUT)
|
|
165
|
+
hasPPS = true;
|
|
166
|
+
}
|
|
167
|
+
if (!isIDR) {
|
|
168
|
+
// Not an IDR, return as is
|
|
169
|
+
return frame;
|
|
170
|
+
}
|
|
171
|
+
const chunks = [];
|
|
172
|
+
if (!hasVPS)
|
|
173
|
+
chunks.push(...vps);
|
|
174
|
+
if (!hasPPS)
|
|
175
|
+
chunks.push(...sps);
|
|
176
|
+
if (!hasSPS)
|
|
177
|
+
chunks.push(...pps);
|
|
178
|
+
return mergeNalu([...chunks, ...nalus]);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
const idToStream = new Map<string, Readable>();
|
|
182
|
+
const libavInstance = LibAV.LibAV();
|
|
183
|
+
libavInstance.then((libav) => {
|
|
184
|
+
libav.onread = (id) => {
|
|
185
|
+
idToStream.get(id)?.resume();
|
|
186
|
+
}
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
export async function demux(input: Readable, cancelSignal?: AbortSignal) {
|
|
190
|
+
const loggerInput = new Log("demux:input");
|
|
191
|
+
const loggerFormat = new Log("demux:format");
|
|
192
|
+
const loggerFrameCommon = new Log("demux:frame:common");
|
|
193
|
+
const loggerFrameVideo = new Log("demux:frame:video");
|
|
194
|
+
const loggerFrameAudio = new Log("demux:frame:audio");
|
|
195
|
+
|
|
196
|
+
const libav = await libavInstance;
|
|
197
|
+
const filename = uid();
|
|
198
|
+
await libav.mkreaderdev(filename);
|
|
199
|
+
idToStream.set(filename, input);
|
|
200
|
+
|
|
201
|
+
const ondata = (chunk: Buffer) => {
|
|
202
|
+
loggerInput.trace(`Received ${chunk.length} bytes of data for input ${filename}`);
|
|
203
|
+
libav.ff_reader_dev_send(filename, chunk)
|
|
204
|
+
};
|
|
205
|
+
const onend = () => {
|
|
206
|
+
loggerInput.trace(`Reached the end of input ${filename}`);
|
|
207
|
+
libav.ff_reader_dev_send(filename, null);
|
|
208
|
+
}
|
|
209
|
+
input.on("data", ondata);
|
|
210
|
+
input.on("end", onend);
|
|
211
|
+
|
|
212
|
+
const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename, "matroska");
|
|
213
|
+
const pkt = await libav.av_packet_alloc();
|
|
214
|
+
|
|
215
|
+
const cleanup = () => {
|
|
216
|
+
vPipe.off("drain", readFrame);
|
|
217
|
+
aPipe.off("drain", readFrame);
|
|
218
|
+
input.off("data", ondata);
|
|
219
|
+
input.off("end", onend);
|
|
220
|
+
idToStream.delete(filename);
|
|
221
|
+
libav.avformat_close_input_js(fmt_ctx);
|
|
222
|
+
libav.av_packet_free(pkt);
|
|
223
|
+
libav.unlink(filename);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const vStream = streams.find((stream) => stream.codec_type === libav.AVMEDIA_TYPE_VIDEO)
|
|
227
|
+
const aStream = streams.find((stream) => stream.codec_type === libav.AVMEDIA_TYPE_AUDIO)
|
|
228
|
+
let vInfo: VideoStreamInfo | undefined
|
|
229
|
+
let aInfo: AudioStreamInfo | undefined;
|
|
230
|
+
const vPipe = new PassThrough({ objectMode: true, writableHighWaterMark: 128 });
|
|
231
|
+
const aPipe = new PassThrough({ objectMode: true, writableHighWaterMark: 128 });
|
|
232
|
+
|
|
233
|
+
if (vStream) {
|
|
234
|
+
if (!allowedVideoCodec.has(vStream.codec_id)) {
|
|
235
|
+
const codecName = await libav.avcodec_get_name(vStream.codec_id);
|
|
236
|
+
cleanup();
|
|
237
|
+
throw new Error(`Video codec ${codecName} is not allowed`)
|
|
238
|
+
}
|
|
239
|
+
const codecpar = await libav.ff_copyout_codecpar(vStream.codecpar);
|
|
240
|
+
vInfo = {
|
|
241
|
+
index: vStream.index,
|
|
242
|
+
codec: vStream.codec_id,
|
|
243
|
+
codecpar,
|
|
244
|
+
width: codecpar.width ?? 0,
|
|
245
|
+
height: codecpar.height ?? 0,
|
|
246
|
+
framerate_num: await libav.AVCodecParameters_framerate_num(vStream.codecpar),
|
|
247
|
+
framerate_den: await libav.AVCodecParameters_framerate_den(vStream.codecpar),
|
|
248
|
+
}
|
|
249
|
+
if (vStream.codec_id === AVCodecID.AV_CODEC_ID_H264) {
|
|
250
|
+
const { extradata } = codecpar;
|
|
251
|
+
vInfo = {
|
|
252
|
+
...vInfo,
|
|
253
|
+
// biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
|
|
254
|
+
extradata: parseavcC(Buffer.from(extradata!))
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
else if (vStream.codec_id === AVCodecID.AV_CODEC_ID_H265) {
|
|
258
|
+
const { extradata } = codecpar;
|
|
259
|
+
vInfo = {
|
|
260
|
+
...vInfo,
|
|
261
|
+
// biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
|
|
262
|
+
extradata: parsehvcC(Buffer.from(extradata!))
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
loggerFormat.info({
|
|
266
|
+
info: vInfo
|
|
267
|
+
}, `Found video stream in input ${filename}`)
|
|
268
|
+
}
|
|
269
|
+
if (aStream) {
|
|
270
|
+
if (!allowedAudioCodec.has(aStream.codec_id)) {
|
|
271
|
+
const codecName = await libav.avcodec_get_name(aStream.codec_id);
|
|
272
|
+
cleanup();
|
|
273
|
+
throw new Error(`Audio codec ${codecName} is not allowed`);
|
|
274
|
+
}
|
|
275
|
+
const codecpar = await libav.ff_copyout_codecpar(aStream.codecpar);
|
|
276
|
+
aInfo = {
|
|
277
|
+
index: aStream.index,
|
|
278
|
+
codec: aStream.codec_id,
|
|
279
|
+
codecpar,
|
|
280
|
+
sample_rate: codecpar.sample_rate ?? 0
|
|
281
|
+
}
|
|
282
|
+
loggerFormat.info({
|
|
283
|
+
info: aInfo
|
|
284
|
+
}, `Found audio stream in input ${filename}`)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const readFrame = pDebounce.promise(async () => {
|
|
288
|
+
let resume = true;
|
|
289
|
+
while (resume) {
|
|
290
|
+
const [status, streams] = await libav.ff_read_frame_multi(fmt_ctx, pkt, {
|
|
291
|
+
limit: 1,
|
|
292
|
+
unify: true
|
|
293
|
+
});
|
|
294
|
+
for (const packet of streams[0] ?? []) {
|
|
295
|
+
if (vInfo && vInfo.index === packet.stream_index) {
|
|
296
|
+
if (vInfo.codec === AVCodecID.AV_CODEC_ID_H264) {
|
|
297
|
+
packet.data = h264AddParamSets(
|
|
298
|
+
Buffer.from(packet.data),
|
|
299
|
+
// biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
|
|
300
|
+
vInfo.extradata! as H264ParamSets
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
else if (vInfo.codec === AVCodecID.AV_CODEC_ID_H265) {
|
|
304
|
+
packet.data = h265AddParamSets(
|
|
305
|
+
Buffer.from(packet.data),
|
|
306
|
+
// biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
|
|
307
|
+
vInfo.extradata! as H265ParamSets
|
|
308
|
+
);
|
|
309
|
+
}
|
|
310
|
+
resume &&= vPipe.write(packet);
|
|
311
|
+
loggerFrameVideo.trace("Pushed a frame into the video pipe");
|
|
312
|
+
}
|
|
313
|
+
else if (aInfo && aInfo.index === packet.stream_index) {
|
|
314
|
+
resume &&= aPipe.write(packet);
|
|
315
|
+
loggerFrameAudio.trace("Pushed a frame into the audio pipe");
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (status < 0 && status !== -libav.EAGAIN) {
|
|
319
|
+
// End of file, or some error happened
|
|
320
|
+
cleanup();
|
|
321
|
+
vPipe.end();
|
|
322
|
+
aPipe.end();
|
|
323
|
+
if (status === LibAV.AVERROR_EOF)
|
|
324
|
+
loggerFrameCommon.info("Reached end of stream. Stopping");
|
|
325
|
+
else
|
|
326
|
+
loggerFrameCommon.info({ status }, "Received an error during frame extraction. Stopping");
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
if (!resume) {
|
|
330
|
+
input.pause();
|
|
331
|
+
loggerInput.trace("Input stream paused");
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
});
|
|
335
|
+
vPipe.on("drain", () => {
|
|
336
|
+
loggerFrameVideo.trace("Video pipe drained");
|
|
337
|
+
readFrame();
|
|
338
|
+
});
|
|
339
|
+
aPipe.on("drain", () => {
|
|
340
|
+
loggerFrameAudio.trace("Audio pipe drained");
|
|
341
|
+
readFrame();
|
|
342
|
+
});
|
|
343
|
+
readFrame();
|
|
344
|
+
return {
|
|
345
|
+
video: vInfo ? { ...vInfo, stream: vPipe as Readable } : undefined,
|
|
346
|
+
audio: aInfo ? { ...aInfo, stream: aPipe as Readable } : undefined
|
|
347
|
+
}
|
|
348
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { MediaUdp } from "../client/voice/MediaUdp.js";
|
|
2
|
+
import { BaseMediaStream } from "./BaseMediaStream.js";
|
|
3
|
+
|
|
4
|
+
export class VideoStream extends BaseMediaStream {
|
|
5
|
+
public udp: MediaUdp;
|
|
6
|
+
|
|
7
|
+
constructor(udp: MediaUdp, noSleep = false) {
|
|
8
|
+
super("video", noSleep);
|
|
9
|
+
this.udp = udp;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
protected override async _sendFrame(frame: Buffer, frametime: number): Promise<void> {
|
|
13
|
+
await this.udp.sendVideoFrame(frame, frametime);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './LibavDemuxer.js';
|