@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,387 @@
1
+ import ffmpeg from 'fluent-ffmpeg';
2
+ import pDebounce from 'p-debounce';
3
+ import sharp from 'sharp';
4
+ import Log from 'debug-level';
5
+ import { PassThrough } from "node:stream";
6
+ import { demux } from './LibavDemuxer.js';
7
+ import { setTimeout as delay } from 'node:timers/promises';
8
+ import { VideoStream } from './VideoStream.js';
9
+ import { AudioStream } from './AudioStream.js';
10
+ import { isFiniteNonZero } from '../utils.js';
11
+ import { AVCodecID } from './LibavCodecId.js';
12
+ import { createDecoder } from './LibavDecoder.js';
13
+ import LibAV from '@lng2004/libav.js-variant-webcodecs-avf-with-decoders';
14
+ export function prepareStream(input, options = {}, cancelSignal) {
15
+ cancelSignal?.throwIfAborted();
16
+ const defaultOptions = {
17
+ noTranscoding: false,
18
+ // negative values = resize by aspect ratio, see https://trac.ffmpeg.org/wiki/Scaling
19
+ width: -2,
20
+ height: -2,
21
+ frameRate: undefined,
22
+ videoCodec: "H264",
23
+ bitrateVideo: 5000,
24
+ bitrateVideoMax: 7000,
25
+ bitrateAudio: 128,
26
+ includeAudio: true,
27
+ hardwareAcceleratedDecoding: false,
28
+ minimizeLatency: false,
29
+ h26xPreset: "ultrafast",
30
+ customHeaders: {
31
+ "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/107.0.0.0 Safari/537.3",
32
+ "Connection": "keep-alive",
33
+ },
34
+ seekTime: 0,
35
+ customFfmpegFlags: []
36
+ };
37
+ function mergeOptions(opts) {
38
+ return {
39
+ noTranscoding: opts.noTranscoding ?? defaultOptions.noTranscoding,
40
+ width: isFiniteNonZero(opts.width) ? Math.round(opts.width) : defaultOptions.width,
41
+ height: isFiniteNonZero(opts.height) ? Math.round(opts.height) : defaultOptions.height,
42
+ frameRate: isFiniteNonZero(opts.frameRate) && opts.frameRate > 0
43
+ ? opts.frameRate
44
+ : defaultOptions.frameRate,
45
+ videoCodec: opts.videoCodec ?? defaultOptions.videoCodec,
46
+ bitrateVideo: isFiniteNonZero(opts.bitrateVideo) && opts.bitrateVideo > 0
47
+ ? Math.round(opts.bitrateVideo)
48
+ : defaultOptions.bitrateVideo,
49
+ bitrateVideoMax: isFiniteNonZero(opts.bitrateVideoMax) && opts.bitrateVideoMax > 0
50
+ ? Math.round(opts.bitrateVideoMax)
51
+ : defaultOptions.bitrateVideoMax,
52
+ bitrateAudio: isFiniteNonZero(opts.bitrateAudio) && opts.bitrateAudio > 0
53
+ ? Math.round(opts.bitrateAudio)
54
+ : defaultOptions.bitrateAudio,
55
+ includeAudio: opts.includeAudio ?? defaultOptions.includeAudio,
56
+ hardwareAcceleratedDecoding: opts.hardwareAcceleratedDecoding ?? defaultOptions.hardwareAcceleratedDecoding,
57
+ minimizeLatency: opts.minimizeLatency ?? defaultOptions.minimizeLatency,
58
+ h26xPreset: opts.h26xPreset ?? defaultOptions.h26xPreset,
59
+ customHeaders: {
60
+ ...defaultOptions.customHeaders, ...opts.customHeaders
61
+ },
62
+ seekTime: opts.seekTime ?? defaultOptions.seekTime,
63
+ customFfmpegFlags: opts.customFfmpegFlags ?? defaultOptions.customFfmpegFlags
64
+ };
65
+ }
66
+ const mergedOptions = mergeOptions(options);
67
+ let isHttpUrl = false;
68
+ let isHls = false;
69
+ if (typeof input === "string") {
70
+ isHttpUrl = input.startsWith('http') || input.startsWith('https');
71
+ isHls = input.includes('m3u');
72
+ }
73
+ const output = new PassThrough();
74
+ // command creation
75
+ const command = ffmpeg();
76
+ // Seeking if applicable (Must be applied before input for fast seek)
77
+ if (mergedOptions.seekTime && mergedOptions.seekTime > 0) {
78
+ command.inputOption('-ss', String(mergedOptions.seekTime));
79
+ }
80
+ command.input(input)
81
+ .addOption('-loglevel', 'info');
82
+ // input options
83
+ const { hardwareAcceleratedDecoding, minimizeLatency, customHeaders } = mergedOptions;
84
+ if (hardwareAcceleratedDecoding)
85
+ command.inputOption('-hwaccel', 'auto');
86
+ if (minimizeLatency) {
87
+ command.addOptions([
88
+ '-fflags nobuffer',
89
+ '-analyzeduration 0'
90
+ ]);
91
+ }
92
+ if (isHttpUrl) {
93
+ command.inputOption('-headers', Object.entries(customHeaders).map(([k, v]) => `${k}: ${v}`).join("\\r\\n") // Corrected escape sequence
94
+ );
95
+ if (!isHls) {
96
+ command.inputOptions([
97
+ '-reconnect 1',
98
+ '-reconnect_at_eof 1',
99
+ '-reconnect_streamed 1',
100
+ '-reconnect_delay_max 4294'
101
+ ]);
102
+ }
103
+ }
104
+ // general output options
105
+ command
106
+ .output(output)
107
+ .outputFormat("matroska");
108
+ // video setup
109
+ const { noTranscoding, width, height, frameRate, bitrateVideo, bitrateVideoMax, videoCodec, h26xPreset } = mergedOptions;
110
+ command.addOutputOption("-map 0:v");
111
+ if (noTranscoding) {
112
+ command.videoCodec("copy");
113
+ }
114
+ else {
115
+ command.videoFilter(`scale=${width}:${height}`);
116
+ if (frameRate)
117
+ command.fpsOutput(frameRate);
118
+ command.addOutputOption([
119
+ "-b:v", `${bitrateVideo}k`,
120
+ "-maxrate:v", `${bitrateVideoMax}k`,
121
+ "-bf", "0",
122
+ "-pix_fmt", "yuv420p",
123
+ "-force_key_frames", "expr:gte(t,n_forced*1)"
124
+ ]);
125
+ switch (videoCodec) {
126
+ case 'AV1':
127
+ command
128
+ .videoCodec("libsvtav1");
129
+ break;
130
+ case 'VP8':
131
+ command
132
+ .videoCodec("libvpx")
133
+ .outputOption('-deadline', 'realtime');
134
+ break;
135
+ case 'VP9':
136
+ command
137
+ .videoCodec("libvpx-vp9")
138
+ .outputOption('-deadline', 'realtime');
139
+ break;
140
+ case 'H264':
141
+ command
142
+ .videoCodec("libx264")
143
+ .outputOptions([
144
+ '-tune zerolatency',
145
+ `-preset ${h26xPreset}`,
146
+ '-profile:v baseline',
147
+ ]);
148
+ break;
149
+ case 'H265':
150
+ command
151
+ .videoCodec("libx265")
152
+ .outputOptions([
153
+ '-tune zerolatency',
154
+ `-preset ${h26xPreset}`,
155
+ '-profile:v main',
156
+ ]);
157
+ break;
158
+ }
159
+ }
160
+ // audio setup
161
+ const { includeAudio, bitrateAudio } = mergedOptions;
162
+ if (includeAudio)
163
+ command
164
+ .addOutputOption("-map 0:a?")
165
+ .audioChannels(2)
166
+ /*
167
+ * I don't have much surround sound material to test this with,
168
+ * if you do and you have better settings for this, feel free to
169
+ * contribute!
170
+ */
171
+ .addOutputOption("-lfe_mix_level 1")
172
+ .audioFrequency(48000)
173
+ .audioCodec("libopus")
174
+ .audioBitrate(`${bitrateAudio}k`)
175
+ .audioFilters("volume@internal_lib=1.0");
176
+ // Add custom ffmpeg flags
177
+ if (mergedOptions.customFfmpegFlags && mergedOptions.customFfmpegFlags.length > 0) {
178
+ command.addOptions(mergedOptions.customFfmpegFlags);
179
+ }
180
+ // exit handling
181
+ const promise = new Promise((resolve, reject) => {
182
+ command.on("error", (err) => {
183
+ if (cancelSignal?.aborted)
184
+ /**
185
+ * fluent-ffmpeg might throw an error when SIGTERM is sent to
186
+ * the process, so we check if the abort signal is triggered
187
+ * and throw that instead
188
+ */
189
+ reject(cancelSignal.reason);
190
+ else
191
+ reject(err);
192
+ });
193
+ command.on("end", () => resolve());
194
+ });
195
+ promise.catch(() => { });
196
+ cancelSignal?.addEventListener("abort", () => command.kill("SIGTERM"), { once: true });
197
+ command.run();
198
+ return { command, output, promise };
199
+ }
200
+ export async function playStream(input, streamer, options = {}, cancelSignal, existingUdp = null) {
201
+ const logger = new Log("playStream");
202
+ cancelSignal?.throwIfAborted();
203
+ if (!streamer.voiceConnection)
204
+ throw new Error("Bot is not connected to a voice channel");
205
+ logger.debug("Initializing demuxer");
206
+ const { video, audio } = await demux(input);
207
+ cancelSignal?.throwIfAborted();
208
+ if (!video)
209
+ throw new Error("No video stream in media");
210
+ const cleanupFuncs = [];
211
+ const videoCodecMap = {
212
+ [AVCodecID.AV_CODEC_ID_H264]: "H264",
213
+ [AVCodecID.AV_CODEC_ID_H265]: "H265",
214
+ [AVCodecID.AV_CODEC_ID_VP8]: "VP8",
215
+ [AVCodecID.AV_CODEC_ID_VP9]: "VP9",
216
+ [AVCodecID.AV_CODEC_ID_AV1]: "AV1"
217
+ };
218
+ const defaultOptions = {
219
+ type: "go-live",
220
+ width: video.width,
221
+ height: video.height,
222
+ frameRate: video.framerate_num / video.framerate_den,
223
+ readrateInitialBurst: undefined,
224
+ streamPreview: false,
225
+ seekTime: 0,
226
+ customHeaders: {},
227
+ initialMuted: false,
228
+ };
229
+ function mergeOptions(opts) {
230
+ return {
231
+ type: opts.type ?? defaultOptions.type,
232
+ width: isFiniteNonZero(opts.width) && opts.width > 0
233
+ ? Math.round(opts.width)
234
+ : defaultOptions.width,
235
+ height: isFiniteNonZero(opts.height) && opts.height > 0
236
+ ? Math.round(opts.height)
237
+ : defaultOptions.height,
238
+ frameRate: Math.round(isFiniteNonZero(opts.frameRate) && opts.frameRate > 0
239
+ ? Math.round(opts.frameRate)
240
+ : defaultOptions.frameRate),
241
+ readrateInitialBurst: isFiniteNonZero(opts.readrateInitialBurst) && opts.readrateInitialBurst > 0
242
+ ? opts.readrateInitialBurst
243
+ : defaultOptions.readrateInitialBurst,
244
+ streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
245
+ seekTime: opts.seekTime ?? defaultOptions.seekTime,
246
+ customHeaders: {
247
+ ...defaultOptions.customHeaders, ...opts.customHeaders
248
+ },
249
+ initialMuted: opts.initialMuted ?? defaultOptions.initialMuted,
250
+ };
251
+ }
252
+ const mergedOptions = mergeOptions(options);
253
+ logger.debug({ options: mergedOptions }, "Merged options");
254
+ let udp;
255
+ let stopStream;
256
+ if (!existingUdp) {
257
+ udp = await streamer.createStream();
258
+ }
259
+ else {
260
+ udp = existingUdp;
261
+ udp.mediaConnection.setSpeaking(false);
262
+ udp.mediaConnection.setVideoAttributes(false);
263
+ await delay(250);
264
+ }
265
+ // stopStream = () => streamer.stopStream();
266
+ stopStream = () => console.log("Aborted");
267
+ udp.setPacketizer(videoCodecMap[video.codec]);
268
+ udp.mediaConnection.setSpeaking(true);
269
+ udp.mediaConnection.setVideoAttributes(true, {
270
+ width: mergedOptions.width,
271
+ height: mergedOptions.height,
272
+ fps: mergedOptions.frameRate
273
+ });
274
+ const vStream = new VideoStream(udp);
275
+ video.stream.pipe(vStream);
276
+ let audioStreamInstance;
277
+ if (audio) {
278
+ audioStreamInstance = new AudioStream(udp, false, mergedOptions.initialMuted);
279
+ audio.stream.pipe(audioStreamInstance);
280
+ vStream.syncStream = audioStreamInstance;
281
+ audioStreamInstance.syncStream = vStream;
282
+ const burstTime = mergedOptions.readrateInitialBurst;
283
+ if (typeof burstTime === "number") {
284
+ vStream.sync = false;
285
+ vStream.noSleep = audioStreamInstance.noSleep = true;
286
+ const stopBurst = (pts) => {
287
+ if (pts < burstTime * 1000)
288
+ return;
289
+ vStream.sync = true;
290
+ if (audioStreamInstance) {
291
+ vStream.noSleep = audioStreamInstance.noSleep = false;
292
+ }
293
+ vStream.off("pts", stopBurst);
294
+ };
295
+ vStream.on("pts", stopBurst);
296
+ }
297
+ }
298
+ if (mergedOptions.streamPreview && mergedOptions.type === "go-live") {
299
+ (async () => {
300
+ const logger = new Log("playStream:preview");
301
+ logger.debug("Initializing decoder for stream preview");
302
+ const decoder = await createDecoder(video.codec, video.codecpar);
303
+ if (!decoder) {
304
+ logger.warn("Failed to initialize decoder. Stream preview will be disabled");
305
+ return;
306
+ }
307
+ cleanupFuncs.push(() => {
308
+ logger.debug("Freeing decoder");
309
+ decoder.free();
310
+ });
311
+ const updatePreview = pDebounce.promise(async (packet) => {
312
+ if (!(packet.flags !== undefined && packet.flags & LibAV.AV_PKT_FLAG_KEY))
313
+ return;
314
+ const decodeStart = performance.now();
315
+ const [frame] = await decoder.decode([packet]).catch(() => []);
316
+ if (!frame)
317
+ return;
318
+ const decodeEnd = performance.now();
319
+ logger.debug(`Decoding a frame took ${decodeEnd - decodeStart}ms`);
320
+ return sharp(frame.data, {
321
+ raw: {
322
+ width: frame.width ?? 0,
323
+ height: frame.height ?? 0,
324
+ channels: 4
325
+ }
326
+ })
327
+ .resize(1024, 576, { fit: "inside" })
328
+ .jpeg()
329
+ .toBuffer()
330
+ .then(image => streamer.setStreamPreview(image))
331
+ .catch(() => { });
332
+ });
333
+ video.stream.on("data", updatePreview);
334
+ cleanupFuncs.push(() => video.stream.off("data", updatePreview));
335
+ })();
336
+ }
337
+ const streamPromise = new Promise((resolve, reject) => {
338
+ cleanupFuncs.push(() => {
339
+ stopStream();
340
+ udp.mediaConnection.setSpeaking(false);
341
+ udp.mediaConnection.setVideoAttributes(false);
342
+ });
343
+ let cleanedUp = false;
344
+ const cleanup = () => {
345
+ if (cleanedUp)
346
+ return;
347
+ cleanedUp = true;
348
+ for (const f of cleanupFuncs)
349
+ f();
350
+ };
351
+ cancelSignal?.addEventListener("abort", () => {
352
+ cleanup();
353
+ reject(cancelSignal.reason);
354
+ }, { once: true });
355
+ vStream.once("finish", () => {
356
+ if (cancelSignal?.aborted)
357
+ return;
358
+ cleanup();
359
+ resolve();
360
+ });
361
+ vStream.once("error", (err) => {
362
+ if (cancelSignal?.aborted)
363
+ return;
364
+ cleanup();
365
+ reject(err);
366
+ });
367
+ if (audio) {
368
+ audio.stream.once("error", (err) => {
369
+ if (cancelSignal?.aborted)
370
+ return;
371
+ cleanup();
372
+ reject(err);
373
+ });
374
+ }
375
+ }).catch((err) => {
376
+ if (!cancelSignal?.aborted) {
377
+ logger.error("Error during playStream:", err);
378
+ }
379
+ if (err !== cancelSignal?.reason) {
380
+ throw err;
381
+ }
382
+ });
383
+ return {
384
+ audioController: audioStreamInstance,
385
+ done: streamPromise,
386
+ };
387
+ }
@@ -0,0 +1 @@
1
+ export declare function combineLoHi(hi: number, lo: number): number;
@@ -0,0 +1,4 @@
1
+ import LibAV from "@lng2004/libav.js-variant-webcodecs-avf-with-decoders";
2
+ export function combineLoHi(hi, lo) {
3
+ return LibAV.i64tof64(lo, hi);
4
+ }
@@ -0,0 +1,28 @@
1
+ import type { AnyChannel, DMChannel, GroupDMChannel, VoiceBasedChannel } from "discord.js-selfbot-v13";
2
+ export declare function normalizeVideoCodec(codec: string): "H264" | "H265" | "VP8" | "VP9" | "AV1";
3
+ export declare const STREAMS_SIMULCAST: {
4
+ type: string;
5
+ rid: string;
6
+ quality: number;
7
+ }[];
8
+ export declare enum SupportedEncryptionModes {
9
+ AES256 = "aead_aes256_gcm_rtpsize",
10
+ XCHACHA20 = "aead_xchacha20_poly1305_rtpsize"
11
+ }
12
+ export type SupportedVideoCodec = "H264" | "H265" | "VP8" | "VP9" | "AV1";
13
+ export declare const extensions: {
14
+ id: number;
15
+ len: number;
16
+ val: number;
17
+ }[];
18
+ export declare const max_int16bit: number;
19
+ export declare const max_int32bit: number;
20
+ export declare function isFiniteNonZero(n: number | undefined): n is number;
21
+ export declare function parseStreamKey(streamKey: string): {
22
+ type: "guild" | "call";
23
+ channelId: string;
24
+ guildId: string | null;
25
+ userId: string;
26
+ };
27
+ export declare function generateStreamKey(type: "guild" | "call", guildId: string | null, channelId: string, userId: string): string;
28
+ export declare function isVoiceChannel(channel: AnyChannel): channel is DMChannel | GroupDMChannel | VoiceBasedChannel;
package/dist/utils.js ADDED
@@ -0,0 +1,54 @@
1
+ export function normalizeVideoCodec(codec) {
2
+ if (/H\.?264|AVC/i.test(codec))
3
+ return "H264";
4
+ if (/H\.?265|HEVC/i.test(codec))
5
+ return "H265";
6
+ if (/VP(8|9)/i.test(codec))
7
+ return codec.toUpperCase();
8
+ if (/AV1/i.test(codec))
9
+ return "AV1";
10
+ throw new Error(`Unknown codec: ${codec}`);
11
+ }
12
+ // The available video streams are sent by client on connection to voice gateway using OpCode Identify (0)
13
+ // The server then replies with the ssrc and rtxssrc for each available stream using OpCode Ready (2)
14
+ // RID is used specifically to distinguish between different simulcast streams of the same video source,
15
+ // but we don't really care about sending multiple quality streams, so we hardcode a single one
16
+ export const STREAMS_SIMULCAST = [{ type: "screen", rid: "100", quality: 100 }];
17
+ export var SupportedEncryptionModes;
18
+ (function (SupportedEncryptionModes) {
19
+ SupportedEncryptionModes["AES256"] = "aead_aes256_gcm_rtpsize";
20
+ SupportedEncryptionModes["XCHACHA20"] = "aead_xchacha20_poly1305_rtpsize";
21
+ })(SupportedEncryptionModes || (SupportedEncryptionModes = {}));
22
+ // RTP extensions
23
+ export const extensions = [{ id: 5, len: 2, val: 0 }];
24
+ export const max_int16bit = 2 ** 16;
25
+ export const max_int32bit = 2 ** 32;
26
+ export function isFiniteNonZero(n) {
27
+ return !!n && Number.isFinite(n);
28
+ }
29
+ export function parseStreamKey(streamKey) {
30
+ const streamKeyArray = streamKey.split(":");
31
+ const type = streamKeyArray.shift();
32
+ if (type !== "guild" && type !== "call") {
33
+ throw new Error(`Invalid stream key type: ${type}`);
34
+ }
35
+ if ((type === "guild" && streamKeyArray.length < 3) || (type === "call" && streamKey.length < 2))
36
+ throw new Error(`Invalid stream key: ${streamKey}`); // invalid stream key
37
+ let guildId = null;
38
+ if (type === "guild") {
39
+ guildId = streamKeyArray.shift() ?? null;
40
+ }
41
+ const channelId = streamKeyArray.shift();
42
+ const userId = streamKeyArray.shift();
43
+ if (!channelId || !userId) {
44
+ throw new Error(`Invalid stream key: ${streamKey}`);
45
+ }
46
+ return { type, channelId, guildId, userId };
47
+ }
48
+ export function generateStreamKey(type, guildId, channelId, userId) {
49
+ const streamKey = `${type}${type === "guild" ? `:${guildId}` : ""}:${channelId}:${userId}`;
50
+ return streamKey;
51
+ }
52
+ export function isVoiceChannel(channel) {
53
+ return (channel.type === "DM" || channel.type === "GROUP_DM" || channel.type === "GUILD_STAGE_VOICE" || channel.type === "GUILD_VOICE");
54
+ }
package/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@m7mdxzx1/discord-video-strem",
3
+ "version": "0.0.1",
4
+ "description": "A fork from dank to use self-bot to do video share",
5
+ "exports": "./dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "type": "module",
8
+ "files": [
9
+ "dist",
10
+ "src"
11
+ ],
12
+ "publishConfig": {
13
+ "access": "public"
14
+ },
15
+ "dependencies": {
16
+ "@lng2004/libav.js-variant-webcodecs-avf-with-decoders": "6.5.7-o3",
17
+ "debug-level": "^3.2.1",
18
+ "fluent-ffmpeg": "^2.1.3",
19
+ "p-debounce": "^4.0.0",
20
+ "sharp": "^0.33.5",
21
+ "sodium-plus": "^0.9.0",
22
+ "uid": "^2.0.2",
23
+ "ws": "^8.18.0",
24
+ "zeromq": "^6.4.2"
25
+ },
26
+ "devDependencies": {
27
+ "@biomejs/biome": "^1.9.4",
28
+ "@types/fluent-ffmpeg": "^2.1.27",
29
+ "@types/node": "^20.17.1",
30
+ "@types/ws": "^8.5.12",
31
+ "discord.js-selfbot-v13": "^3.6.0",
32
+ "pkg-pr-new": "^0.0.39",
33
+ "typed-emitter": "^2.1.0",
34
+ "typescript": "^5.6.3"
35
+ },
36
+ "peerDependencies": {
37
+ "discord.js-selfbot-v13": "^3.6.0"
38
+ },
39
+ "engines": {
40
+ "node": ">=21.0.0"
41
+ },
42
+ "scripts": {
43
+ "build": "tsc"
44
+ },
45
+ "keywords": [
46
+ "discord",
47
+ "video",
48
+ "voice",
49
+ "stream",
50
+ "go-live"
51
+ ],
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/ChamseddineDz/Discord-video-stream.git"
55
+ },
56
+ "contributors": [
57
+ "Long Nguyen <nguyen.long.908132@gmail.com>",
58
+ "dank074 <torresefrain10@gmail.com>",
59
+ "mrjvs <jellevs@gmail.com>",
60
+ "Elysia <71698422+aiko-chan-ai@users.noreply.github.com>",
61
+ "Fede14 <fede.ferri2001@gmail.com>",
62
+ "Malthe Morsing Larsen <57196060+malthemorsing@users.noreply.github.com>"
63
+ ],
64
+ "license": "ISC",
65
+ "bugs": {
66
+ "url": "https://github.com/ChamseddineDz/Discord-video-stream/issues"
67
+ },
68
+ "homepage": "https://github.com/ChamseddineDz/Discord-video-stream#readme"
69
+ }
@@ -0,0 +1,41 @@
1
+ type GatewayEventGeneric<Type extends string = string, Data = unknown> = {
2
+ t: Type,
3
+ d: Data
4
+ }
5
+
6
+ export namespace GatewayEvent {
7
+ export type VoiceStateUpdate = GatewayEventGeneric<
8
+ "VOICE_STATE_UPDATE", {
9
+ user_id: string,
10
+ session_id: string
11
+ }
12
+ >
13
+ export type VoiceServerUpdate = GatewayEventGeneric<
14
+ "VOICE_SERVER_UPDATE", {
15
+ guild_id: string,
16
+ channel_id?: string,
17
+ endpoint: string,
18
+ token: string
19
+ }
20
+ >
21
+ export type StreamCreate = GatewayEventGeneric<
22
+ "STREAM_CREATE", {
23
+ stream_key: string,
24
+ rtc_server_id: string
25
+ }
26
+ >
27
+ export type StreamServerUpdate = GatewayEventGeneric<
28
+ "STREAM_SERVER_UPDATE", {
29
+ stream_key: string,
30
+ endpoint: string,
31
+ token: string
32
+ }
33
+ >
34
+ }
35
+
36
+
37
+ export type GatewayEvent =
38
+ GatewayEvent.VoiceStateUpdate |
39
+ GatewayEvent.VoiceServerUpdate |
40
+ GatewayEvent.StreamCreate |
41
+ GatewayEvent.StreamServerUpdate
@@ -0,0 +1,40 @@
1
+ export enum GatewayOpCodes {
2
+ DISPATCH = 0,
3
+ HEARTBEAT = 1,
4
+ IDENTIFY = 2,
5
+ PRESENCE_UPDATE = 3,
6
+ VOICE_STATE_UPDATE = 4,
7
+ VOICE_SERVER_PING = 5,
8
+ RESUME = 6,
9
+ RECONNECT = 7,
10
+ REQUEST_GUILD_MEMBERS = 8,
11
+ INVALID_SESSION = 9,
12
+ HELLO = 10,
13
+ HEARTBEAT_ACK = 11,
14
+ CALL_CONNECT = 13,
15
+ GUILD_SUBSCRIPTIONS = 14,
16
+ LOBBY_CONNECT = 15,
17
+ LOBBY_DISCONNECT = 16,
18
+ LOBBY_VOICE_STATES_UPDATE = 17,
19
+ STREAM_CREATE = 18,
20
+ STREAM_DELETE = 19,
21
+ STREAM_WATCH = 20,
22
+ STREAM_PING = 21,
23
+ STREAM_SET_PAUSED = 22,
24
+ REQUEST_GUILD_APPLICATION_COMMANDS = 24,
25
+ EMBEDDED_ACTIVITY_LAUNCH = 25,
26
+ EMBEDDED_ACTIVITY_CLOSE = 26,
27
+ EMBEDDED_ACTIVITY_UPDATE = 27,
28
+ REQUEST_FORUM_UNREADS = 28,
29
+ REMOTE_COMMAND = 29,
30
+ GET_DELETED_ENTITY_IDS_NOT_MATCHING_HASH = 30,
31
+ REQUEST_SOUNDBOARD_SOUNDS = 31,
32
+ SPEED_TEST_CREATE = 32,
33
+ SPEED_TEST_DELETE = 33,
34
+ REQUEST_LAST_MESSAGES = 34,
35
+ SEARCH_RECENT_MEMBERS = 35,
36
+ REQUEST_CHANNEL_STATUSES = 36,
37
+ GUILD_SUBSCRIPTIONS_BULK = 37,
38
+ GUILD_CHANNELS_RESYNC = 38,
39
+ REQUEST_CHANNEL_MEMBER_COUNT = 39,
40
+ }