@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.
Files changed (89) hide show
  1. package/README.md +294 -0
  2. package/dist/client/GatewayEvents.d.ts +27 -0
  3. package/dist/client/GatewayEvents.js +1 -0
  4. package/dist/client/GatewayOpCodes.d.ts +40 -0
  5. package/dist/client/GatewayOpCodes.js +41 -0
  6. package/dist/client/Streamer.d.ts +41 -0
  7. package/dist/client/Streamer.js +189 -0
  8. package/dist/client/encryptor/TransportEncryptor.d.ts +16 -0
  9. package/dist/client/encryptor/TransportEncryptor.js +38 -0
  10. package/dist/client/index.d.ts +4 -0
  11. package/dist/client/index.js +4 -0
  12. package/dist/client/packet/AudioPacketizer.d.ts +8 -0
  13. package/dist/client/packet/AudioPacketizer.js +22 -0
  14. package/dist/client/packet/BaseMediaPacketizer.d.ts +109 -0
  15. package/dist/client/packet/BaseMediaPacketizer.js +243 -0
  16. package/dist/client/packet/VideoPacketizerAnnexB.d.ts +132 -0
  17. package/dist/client/packet/VideoPacketizerAnnexB.js +231 -0
  18. package/dist/client/packet/VideoPacketizerVP8.d.ts +15 -0
  19. package/dist/client/packet/VideoPacketizerVP8.js +56 -0
  20. package/dist/client/packet/index.d.ts +4 -0
  21. package/dist/client/packet/index.js +4 -0
  22. package/dist/client/processing/AnnexBHelper.d.ts +93 -0
  23. package/dist/client/processing/AnnexBHelper.js +132 -0
  24. package/dist/client/voice/BaseMediaConnection.d.ts +118 -0
  25. package/dist/client/voice/BaseMediaConnection.js +319 -0
  26. package/dist/client/voice/MediaUdp.d.ts +26 -0
  27. package/dist/client/voice/MediaUdp.js +140 -0
  28. package/dist/client/voice/StreamConnection.d.ts +10 -0
  29. package/dist/client/voice/StreamConnection.js +30 -0
  30. package/dist/client/voice/VoiceConnection.d.ts +7 -0
  31. package/dist/client/voice/VoiceConnection.js +10 -0
  32. package/dist/client/voice/VoiceMessageTypes.d.ts +136 -0
  33. package/dist/client/voice/VoiceMessageTypes.js +1 -0
  34. package/dist/client/voice/VoiceOpCodes.d.ts +21 -0
  35. package/dist/client/voice/VoiceOpCodes.js +22 -0
  36. package/dist/client/voice/index.d.ts +5 -0
  37. package/dist/client/voice/index.js +5 -0
  38. package/dist/index.d.ts +5 -0
  39. package/dist/index.js +5 -0
  40. package/dist/media/AudioStream.d.ts +25 -0
  41. package/dist/media/AudioStream.js +63 -0
  42. package/dist/media/BaseMediaStream.d.ts +31 -0
  43. package/dist/media/BaseMediaStream.js +145 -0
  44. package/dist/media/LibavCodecId.d.ts +541 -0
  45. package/dist/media/LibavCodecId.js +552 -0
  46. package/dist/media/LibavDecoder.d.ts +5 -0
  47. package/dist/media/LibavDecoder.js +63 -0
  48. package/dist/media/LibavDemuxer.d.ts +23 -0
  49. package/dist/media/LibavDemuxer.js +295 -0
  50. package/dist/media/VideoStream.d.ts +7 -0
  51. package/dist/media/VideoStream.js +10 -0
  52. package/dist/media/index.d.ts +1 -0
  53. package/dist/media/index.js +1 -0
  54. package/dist/media/newApi.d.ts +126 -0
  55. package/dist/media/newApi.js +387 -0
  56. package/dist/media/utils.d.ts +1 -0
  57. package/dist/media/utils.js +4 -0
  58. package/dist/utils.d.ts +28 -0
  59. package/dist/utils.js +54 -0
  60. package/package.json +69 -0
  61. package/src/client/GatewayEvents.ts +41 -0
  62. package/src/client/GatewayOpCodes.ts +40 -0
  63. package/src/client/Streamer.ts +279 -0
  64. package/src/client/encryptor/TransportEncryptor.ts +62 -0
  65. package/src/client/index.ts +4 -0
  66. package/src/client/packet/AudioPacketizer.ts +28 -0
  67. package/src/client/packet/BaseMediaPacketizer.ts +307 -0
  68. package/src/client/packet/VideoPacketizerAnnexB.ts +263 -0
  69. package/src/client/packet/VideoPacketizerVP8.ts +73 -0
  70. package/src/client/packet/index.ts +4 -0
  71. package/src/client/processing/AnnexBHelper.ts +142 -0
  72. package/src/client/voice/BaseMediaConnection.ts +407 -0
  73. package/src/client/voice/MediaUdp.ts +171 -0
  74. package/src/client/voice/StreamConnection.ts +33 -0
  75. package/src/client/voice/VoiceConnection.ts +15 -0
  76. package/src/client/voice/VoiceMessageTypes.ts +164 -0
  77. package/src/client/voice/VoiceOpCodes.ts +21 -0
  78. package/src/client/voice/index.ts +5 -0
  79. package/src/index.ts +5 -0
  80. package/src/media/AudioStream.ts +81 -0
  81. package/src/media/BaseMediaStream.ts +173 -0
  82. package/src/media/LibavCodecId.ts +566 -0
  83. package/src/media/LibavDecoder.ts +82 -0
  84. package/src/media/LibavDemuxer.ts +348 -0
  85. package/src/media/VideoStream.ts +15 -0
  86. package/src/media/index.ts +1 -0
  87. package/src/media/newApi.ts +618 -0
  88. package/src/media/utils.ts +6 -0
  89. package/src/utils.ts +77 -0
@@ -0,0 +1,23 @@
1
+ import { type CodecParameters } from "@lng2004/libav.js-variant-webcodecs-avf-with-decoders";
2
+ import { AVCodecID } from "./LibavCodecId.js";
3
+ import type { Readable } from "node:stream";
4
+ export declare function demux(input: Readable, cancelSignal?: AbortSignal): Promise<{
5
+ video: {
6
+ stream: Readable;
7
+ index: number;
8
+ codec: AVCodecID;
9
+ codecpar: CodecParameters;
10
+ width: number;
11
+ height: number;
12
+ framerate_num: number;
13
+ framerate_den: number;
14
+ extradata?: unknown;
15
+ } | undefined;
16
+ audio: {
17
+ stream: Readable;
18
+ index: number;
19
+ codec: AVCodecID;
20
+ codecpar: CodecParameters;
21
+ sample_rate: number;
22
+ } | undefined;
23
+ }>;
@@ -0,0 +1,295 @@
1
+ import pDebounce from "p-debounce";
2
+ import LibAV 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 { H264Helpers, H264NalUnitTypes, H265Helpers, H265NalUnitTypes, splitNalu, mergeNalu } from "../client/processing/AnnexBHelper.js";
7
+ import { PassThrough } from "node:stream";
8
+ const allowedVideoCodec = new Set([
9
+ AVCodecID.AV_CODEC_ID_H264,
10
+ AVCodecID.AV_CODEC_ID_H265,
11
+ AVCodecID.AV_CODEC_ID_VP8,
12
+ AVCodecID.AV_CODEC_ID_VP9,
13
+ AVCodecID.AV_CODEC_ID_AV1
14
+ ]);
15
+ const allowedAudioCodec = new Set([
16
+ AVCodecID.AV_CODEC_ID_OPUS
17
+ ]);
18
+ // Parse the avcC atom, which contains SPS and PPS
19
+ function parseavcC(input) {
20
+ let buf = input;
21
+ if (buf[0] !== 1)
22
+ throw new Error("Only configurationVersion 1 is supported");
23
+ // Skip a bunch of stuff we don't care about
24
+ buf = buf.subarray(5);
25
+ const sps = [];
26
+ const pps = [];
27
+ // Read the SPS
28
+ const spsCount = buf[0] & 0b11111;
29
+ buf = buf.subarray(1);
30
+ for (let i = 0; i < spsCount; ++i) {
31
+ const spsLength = buf.readUInt16BE();
32
+ buf = buf.subarray(2);
33
+ sps.push(buf.subarray(0, spsLength));
34
+ buf = buf.subarray(spsLength);
35
+ }
36
+ // Read the PPS
37
+ const ppsCount = buf[0];
38
+ buf = buf.subarray(1);
39
+ for (let i = 0; i < ppsCount; ++i) {
40
+ const ppsLength = buf.readUInt16BE();
41
+ buf = buf.subarray(2);
42
+ pps.push(buf.subarray(0, ppsLength));
43
+ buf = buf.subarray(ppsLength);
44
+ }
45
+ return { sps, pps };
46
+ }
47
+ // Parse the hvcC atom, which contains VPS, SPS, PPS
48
+ function parsehvcC(input) {
49
+ let buf = input;
50
+ if (buf[0] !== 1)
51
+ throw new Error("Only configurationVersion 1 is supported");
52
+ // Skip a bunch of stuff we don't care about
53
+ buf = buf.subarray(22);
54
+ const vps = [];
55
+ const sps = [];
56
+ const pps = [];
57
+ const numOfArrays = buf[0];
58
+ buf = buf.subarray(1);
59
+ for (let i = 0; i < numOfArrays; ++i) {
60
+ const naluType = buf[0] & 0b111111;
61
+ buf = buf.subarray(1);
62
+ const naluCount = buf.readUInt16BE();
63
+ buf = buf.subarray(2);
64
+ for (let j = 0; j < naluCount; ++j) {
65
+ const naluLength = buf.readUInt16BE();
66
+ buf = buf.subarray(2);
67
+ const nalu = buf.subarray(0, naluLength);
68
+ buf = buf.subarray(naluLength);
69
+ if (naluType === H265NalUnitTypes.VPS_NUT)
70
+ vps.push(nalu);
71
+ else if (naluType === H265NalUnitTypes.SPS_NUT)
72
+ sps.push(nalu);
73
+ else if (naluType === H265NalUnitTypes.PPS_NUT)
74
+ pps.push(nalu);
75
+ }
76
+ }
77
+ return { vps, sps, pps };
78
+ }
79
+ function h264AddParamSets(frame, paramSets) {
80
+ const { sps, pps } = paramSets;
81
+ const nalus = splitNalu(frame);
82
+ // Technically non-IDR I frames exist ("open GOP"), but they're exceedingly
83
+ // rare in the wild, and no encoder produces it by default
84
+ let isIDR = false;
85
+ let hasSPS = false;
86
+ let hasPPS = false;
87
+ for (const nalu of nalus) {
88
+ const naluType = H264Helpers.getUnitType(nalu);
89
+ if (naluType === H264NalUnitTypes.CodedSliceIdr)
90
+ isIDR = true;
91
+ else if (naluType === H264NalUnitTypes.SPS)
92
+ hasSPS = true;
93
+ else if (naluType === H264NalUnitTypes.PPS)
94
+ hasPPS = true;
95
+ }
96
+ if (!isIDR) {
97
+ // Not an IDR, return as is
98
+ return frame;
99
+ }
100
+ const chunks = [];
101
+ if (!hasPPS)
102
+ chunks.push(...sps);
103
+ if (!hasSPS)
104
+ chunks.push(...pps);
105
+ return mergeNalu([...chunks, ...nalus]);
106
+ }
107
+ function h265AddParamSets(frame, paramSets) {
108
+ const { vps, sps, pps } = paramSets;
109
+ const nalus = splitNalu(frame);
110
+ // Technically non-IDR I frames exist ("open GOP"), but they're exceedingly
111
+ // rare in the wild, and no encoder produces it by default
112
+ let isIDR = false;
113
+ let hasVPS = false;
114
+ let hasSPS = false;
115
+ let hasPPS = false;
116
+ for (const nalu of nalus) {
117
+ const naluType = H265Helpers.getUnitType(nalu);
118
+ if (naluType === H265NalUnitTypes.IDR_N_LP || naluType === H265NalUnitTypes.IDR_W_RADL)
119
+ isIDR = true;
120
+ else if (naluType === H265NalUnitTypes.VPS_NUT)
121
+ hasVPS = true;
122
+ else if (naluType === H265NalUnitTypes.SPS_NUT)
123
+ hasSPS = true;
124
+ else if (naluType === H265NalUnitTypes.PPS_NUT)
125
+ hasPPS = true;
126
+ }
127
+ if (!isIDR) {
128
+ // Not an IDR, return as is
129
+ return frame;
130
+ }
131
+ const chunks = [];
132
+ if (!hasVPS)
133
+ chunks.push(...vps);
134
+ if (!hasPPS)
135
+ chunks.push(...sps);
136
+ if (!hasSPS)
137
+ chunks.push(...pps);
138
+ return mergeNalu([...chunks, ...nalus]);
139
+ }
140
+ const idToStream = new Map();
141
+ const libavInstance = LibAV.LibAV();
142
+ libavInstance.then((libav) => {
143
+ libav.onread = (id) => {
144
+ idToStream.get(id)?.resume();
145
+ };
146
+ });
147
+ export async function demux(input, cancelSignal) {
148
+ const loggerInput = new Log("demux:input");
149
+ const loggerFormat = new Log("demux:format");
150
+ const loggerFrameCommon = new Log("demux:frame:common");
151
+ const loggerFrameVideo = new Log("demux:frame:video");
152
+ const loggerFrameAudio = new Log("demux:frame:audio");
153
+ const libav = await libavInstance;
154
+ const filename = uid();
155
+ await libav.mkreaderdev(filename);
156
+ idToStream.set(filename, input);
157
+ const ondata = (chunk) => {
158
+ loggerInput.trace(`Received ${chunk.length} bytes of data for input ${filename}`);
159
+ libav.ff_reader_dev_send(filename, chunk);
160
+ };
161
+ const onend = () => {
162
+ loggerInput.trace(`Reached the end of input ${filename}`);
163
+ libav.ff_reader_dev_send(filename, null);
164
+ };
165
+ input.on("data", ondata);
166
+ input.on("end", onend);
167
+ const [fmt_ctx, streams] = await libav.ff_init_demuxer_file(filename, "matroska");
168
+ const pkt = await libav.av_packet_alloc();
169
+ const cleanup = () => {
170
+ vPipe.off("drain", readFrame);
171
+ aPipe.off("drain", readFrame);
172
+ input.off("data", ondata);
173
+ input.off("end", onend);
174
+ idToStream.delete(filename);
175
+ libav.avformat_close_input_js(fmt_ctx);
176
+ libav.av_packet_free(pkt);
177
+ libav.unlink(filename);
178
+ };
179
+ const vStream = streams.find((stream) => stream.codec_type === libav.AVMEDIA_TYPE_VIDEO);
180
+ const aStream = streams.find((stream) => stream.codec_type === libav.AVMEDIA_TYPE_AUDIO);
181
+ let vInfo;
182
+ let aInfo;
183
+ const vPipe = new PassThrough({ objectMode: true, writableHighWaterMark: 128 });
184
+ const aPipe = new PassThrough({ objectMode: true, writableHighWaterMark: 128 });
185
+ if (vStream) {
186
+ if (!allowedVideoCodec.has(vStream.codec_id)) {
187
+ const codecName = await libav.avcodec_get_name(vStream.codec_id);
188
+ cleanup();
189
+ throw new Error(`Video codec ${codecName} is not allowed`);
190
+ }
191
+ const codecpar = await libav.ff_copyout_codecpar(vStream.codecpar);
192
+ vInfo = {
193
+ index: vStream.index,
194
+ codec: vStream.codec_id,
195
+ codecpar,
196
+ width: codecpar.width ?? 0,
197
+ height: codecpar.height ?? 0,
198
+ framerate_num: await libav.AVCodecParameters_framerate_num(vStream.codecpar),
199
+ framerate_den: await libav.AVCodecParameters_framerate_den(vStream.codecpar),
200
+ };
201
+ if (vStream.codec_id === AVCodecID.AV_CODEC_ID_H264) {
202
+ const { extradata } = codecpar;
203
+ vInfo = {
204
+ ...vInfo,
205
+ // biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
206
+ extradata: parseavcC(Buffer.from(extradata))
207
+ };
208
+ }
209
+ else if (vStream.codec_id === AVCodecID.AV_CODEC_ID_H265) {
210
+ const { extradata } = codecpar;
211
+ vInfo = {
212
+ ...vInfo,
213
+ // biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
214
+ extradata: parsehvcC(Buffer.from(extradata))
215
+ };
216
+ }
217
+ loggerFormat.info({
218
+ info: vInfo
219
+ }, `Found video stream in input ${filename}`);
220
+ }
221
+ if (aStream) {
222
+ if (!allowedAudioCodec.has(aStream.codec_id)) {
223
+ const codecName = await libav.avcodec_get_name(aStream.codec_id);
224
+ cleanup();
225
+ throw new Error(`Audio codec ${codecName} is not allowed`);
226
+ }
227
+ const codecpar = await libav.ff_copyout_codecpar(aStream.codecpar);
228
+ aInfo = {
229
+ index: aStream.index,
230
+ codec: aStream.codec_id,
231
+ codecpar,
232
+ sample_rate: codecpar.sample_rate ?? 0
233
+ };
234
+ loggerFormat.info({
235
+ info: aInfo
236
+ }, `Found audio stream in input ${filename}`);
237
+ }
238
+ const readFrame = pDebounce.promise(async () => {
239
+ let resume = true;
240
+ while (resume) {
241
+ const [status, streams] = await libav.ff_read_frame_multi(fmt_ctx, pkt, {
242
+ limit: 1,
243
+ unify: true
244
+ });
245
+ for (const packet of streams[0] ?? []) {
246
+ if (vInfo && vInfo.index === packet.stream_index) {
247
+ if (vInfo.codec === AVCodecID.AV_CODEC_ID_H264) {
248
+ packet.data = h264AddParamSets(Buffer.from(packet.data),
249
+ // biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
250
+ vInfo.extradata);
251
+ }
252
+ else if (vInfo.codec === AVCodecID.AV_CODEC_ID_H265) {
253
+ packet.data = h265AddParamSets(Buffer.from(packet.data),
254
+ // biome-ignore lint/style/noNonNullAssertion: will always be non-null for our use case
255
+ vInfo.extradata);
256
+ }
257
+ resume &&= vPipe.write(packet);
258
+ loggerFrameVideo.trace("Pushed a frame into the video pipe");
259
+ }
260
+ else if (aInfo && aInfo.index === packet.stream_index) {
261
+ resume &&= aPipe.write(packet);
262
+ loggerFrameAudio.trace("Pushed a frame into the audio pipe");
263
+ }
264
+ }
265
+ if (status < 0 && status !== -libav.EAGAIN) {
266
+ // End of file, or some error happened
267
+ cleanup();
268
+ vPipe.end();
269
+ aPipe.end();
270
+ if (status === LibAV.AVERROR_EOF)
271
+ loggerFrameCommon.info("Reached end of stream. Stopping");
272
+ else
273
+ loggerFrameCommon.info({ status }, "Received an error during frame extraction. Stopping");
274
+ return;
275
+ }
276
+ if (!resume) {
277
+ input.pause();
278
+ loggerInput.trace("Input stream paused");
279
+ }
280
+ }
281
+ });
282
+ vPipe.on("drain", () => {
283
+ loggerFrameVideo.trace("Video pipe drained");
284
+ readFrame();
285
+ });
286
+ aPipe.on("drain", () => {
287
+ loggerFrameAudio.trace("Audio pipe drained");
288
+ readFrame();
289
+ });
290
+ readFrame();
291
+ return {
292
+ video: vInfo ? { ...vInfo, stream: vPipe } : undefined,
293
+ audio: aInfo ? { ...aInfo, stream: aPipe } : undefined
294
+ };
295
+ }
@@ -0,0 +1,7 @@
1
+ import type { MediaUdp } from "../client/voice/MediaUdp.js";
2
+ import { BaseMediaStream } from "./BaseMediaStream.js";
3
+ export declare class VideoStream extends BaseMediaStream {
4
+ udp: MediaUdp;
5
+ constructor(udp: MediaUdp, noSleep?: boolean);
6
+ protected _sendFrame(frame: Buffer, frametime: number): Promise<void>;
7
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseMediaStream } from "./BaseMediaStream.js";
2
+ export class VideoStream extends BaseMediaStream {
3
+ constructor(udp, noSleep = false) {
4
+ super("video", noSleep);
5
+ this.udp = udp;
6
+ }
7
+ async _sendFrame(frame, frametime) {
8
+ await this.udp.sendVideoFrame(frame, frametime);
9
+ }
10
+ }
@@ -0,0 +1 @@
1
+ export * from './LibavDemuxer.js';
@@ -0,0 +1 @@
1
+ export * from './LibavDemuxer.js';
@@ -0,0 +1,126 @@
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import { PassThrough, type Readable } from "node:stream";
3
+ import type { SupportedVideoCodec } from '../utils.js';
4
+ import type { MediaUdp, Streamer } from '../client/index.js';
5
+ export type EncoderOptions = {
6
+ /**
7
+ * Disable video transcoding
8
+ * If enabled, all video related settings have no effects, and the input
9
+ * video stream is used as-is.
10
+ *
11
+ * You need to ensure that the video stream has the right properties
12
+ * (keyframe every 1s, B-frames disabled). Failure to do so will result in
13
+ * a glitchy stream, or degraded performance
14
+ */
15
+ noTranscoding: boolean;
16
+ /**
17
+ * Video width
18
+ */
19
+ width: number;
20
+ /**
21
+ * Video height
22
+ */
23
+ height: number;
24
+ /**
25
+ * Video frame rate
26
+ */
27
+ frameRate?: number;
28
+ /**
29
+ * Video codec
30
+ */
31
+ videoCodec: SupportedVideoCodec;
32
+ /**
33
+ * Video average bitrate in kbps
34
+ */
35
+ bitrateVideo: number;
36
+ /**
37
+ * Video max bitrate in kbps
38
+ */
39
+ bitrateVideoMax: number;
40
+ /**
41
+ * Audio bitrate in kbps
42
+ */
43
+ bitrateAudio: number;
44
+ /**
45
+ * Enable audio output
46
+ */
47
+ includeAudio: boolean;
48
+ /**
49
+ * Enable hardware accelerated decoding
50
+ */
51
+ hardwareAcceleratedDecoding: boolean;
52
+ /**
53
+ * Add some options to minimize latency
54
+ */
55
+ minimizeLatency: boolean;
56
+ /**
57
+ * Preset for x264 and x265
58
+ */
59
+ h26xPreset: "ultrafast" | "superfast" | "veryfast" | "faster" | "fast" | "medium" | "slow" | "slower" | "veryslow" | "placebo";
60
+ /**
61
+ * Custom headers for HTTP requests
62
+ */
63
+ customHeaders: Record<string, string>;
64
+ /**
65
+ * Input for seeking time
66
+ */
67
+ seekTime?: number;
68
+ /**
69
+ * Custom ffmpeg flags/options to pass directly to ffmpeg
70
+ * These will be added to the command after other options
71
+ */
72
+ customFfmpegFlags: string[];
73
+ };
74
+ export interface AudioController {
75
+ mute(): void;
76
+ unmute(): void;
77
+ isMuted(): boolean;
78
+ setVolume(volume: number): void;
79
+ getVolume(): number;
80
+ }
81
+ export declare function prepareStream(input: string | Readable, options?: Partial<EncoderOptions>, cancelSignal?: AbortSignal): {
82
+ command: ffmpeg.FfmpegCommand;
83
+ output: PassThrough;
84
+ promise: Promise<void>;
85
+ };
86
+ export type PlayStreamOptions = {
87
+ /**
88
+ * Set stream type as \"Go Live\" or camera stream
89
+ */
90
+ type: "go-live" | "camera";
91
+ /**
92
+ * Override video width sent to Discord.
93
+ *
94
+ * DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
95
+ */
96
+ width: number;
97
+ /**
98
+ * Override video height sent to Discord.
99
+ *
100
+ * DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
101
+ */
102
+ height: number;
103
+ /**
104
+ * Override video frame rate sent to Discord.
105
+ *
106
+ * DO NOT SPECIFY UNLESS YOU KNOW WHAT YOU'RE DOING!
107
+ */
108
+ frameRate: number;
109
+ /**
110
+ * Same as ffmpeg's `readrate_initial_burst` command line flag
111
+ *
112
+ * See https://ffmpeg.org/ffmpeg.html#:~:text=%2Dreadrate_initial_burst
113
+ */
114
+ readrateInitialBurst: number | undefined;
115
+ /**
116
+ * Enable stream preview from input stream (experimental)
117
+ */
118
+ streamPreview: boolean;
119
+ seekTime?: number;
120
+ customHeaders?: Record<string, string>;
121
+ initialMuted?: boolean;
122
+ };
123
+ export declare function playStream(input: Readable, streamer: Streamer, options?: Partial<PlayStreamOptions>, cancelSignal?: AbortSignal, existingUdp?: MediaUdp | null): Promise<{
124
+ audioController?: AudioController;
125
+ done: Promise<void>;
126
+ }>;