@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,319 @@
1
+ import { VoiceOpCodes } from "./VoiceOpCodes.js";
2
+ import { MediaUdp } from "./MediaUdp.js";
3
+ import { AES256TransportEncryptor, Chacha20TransportEncryptor } from "../encryptor/TransportEncryptor.js";
4
+ import { STREAMS_SIMULCAST, SupportedEncryptionModes } from "../../utils.js";
5
+ import WebSocket from 'ws';
6
+ import EventEmitter from "node:events";
7
+ export const CodecPayloadType = {
8
+ "opus": {
9
+ name: "opus", type: "audio", priority: 1000, payload_type: 120
10
+ },
11
+ "H264": {
12
+ name: "H264", type: "video", priority: 1000, payload_type: 101, rtx_payload_type: 102, encode: true, decode: true
13
+ },
14
+ "H265": {
15
+ name: "H265", type: "video", priority: 1000, payload_type: 103, rtx_payload_type: 104, encode: true, decode: true
16
+ },
17
+ "VP8": {
18
+ name: "VP8", type: "video", priority: 1000, payload_type: 105, rtx_payload_type: 106, encode: true, decode: true
19
+ },
20
+ "VP9": {
21
+ name: "VP9", type: "video", priority: 1000, payload_type: 107, rtx_payload_type: 108, encode: true, decode: true
22
+ },
23
+ "AV1": {
24
+ name: "AV1", type: "video", priority: 1000, payload_type: 109, rtx_payload_type: 110, encode: true, decode: true
25
+ }
26
+ };
27
+ export class BaseMediaConnection extends EventEmitter {
28
+ constructor(streamer, guildId, botId, channelId, callback) {
29
+ super();
30
+ this.interval = null;
31
+ this.guildId = null;
32
+ this.ws = null;
33
+ this.server = null; //websocket url
34
+ this.token = null;
35
+ this.session_id = null;
36
+ this.webRtcParams = null;
37
+ this._sequenceNumber = -1;
38
+ this._streamer = streamer;
39
+ this.status = {
40
+ hasSession: false,
41
+ hasToken: false,
42
+ started: false,
43
+ resuming: false
44
+ };
45
+ // make udp client
46
+ this.udp = new MediaUdp(this);
47
+ this.guildId = guildId;
48
+ this.channelId = channelId;
49
+ this.botId = botId;
50
+ this.ready = callback;
51
+ }
52
+ get type() {
53
+ return this.guildId ? "guild" : "call";
54
+ }
55
+ get transportEncryptor() {
56
+ return this._transportEncryptor;
57
+ }
58
+ get streamer() {
59
+ return this._streamer;
60
+ }
61
+ stop() {
62
+ this.interval && clearInterval(this.interval);
63
+ this.status.started = false;
64
+ this.ws?.close();
65
+ this.udp?.stop();
66
+ }
67
+ setSession(session_id) {
68
+ this.session_id = session_id;
69
+ this.status.hasSession = true;
70
+ this.start();
71
+ }
72
+ setTokens(server, token) {
73
+ this.token = token;
74
+ this.server = server;
75
+ this.status.hasToken = true;
76
+ this.start();
77
+ }
78
+ start() {
79
+ /*
80
+ ** Connection can only start once both
81
+ ** session description and tokens have been gathered
82
+ */
83
+ if (this.status.hasSession && this.status.hasToken) {
84
+ if (this.status.started)
85
+ return;
86
+ this.status.started = true;
87
+ this.ws = new WebSocket(`wss://${this.server}/?v=8`, {
88
+ followRedirects: true
89
+ });
90
+ this.ws.on("open", () => {
91
+ if (this.status.resuming) {
92
+ this.status.resuming = false;
93
+ this.resume();
94
+ }
95
+ else {
96
+ this.identify();
97
+ }
98
+ });
99
+ this.ws.on("error", (err) => {
100
+ console.error(err);
101
+ });
102
+ this.ws.on("close", (code) => {
103
+ const wasStarted = this.status.started;
104
+ this.status.started = false;
105
+ this.udp.ready = false;
106
+ const canResume = code === 4_015 || code < 4_000;
107
+ if (canResume && wasStarted) {
108
+ this.status.resuming = true;
109
+ this.start();
110
+ }
111
+ });
112
+ this.setupEvents();
113
+ }
114
+ }
115
+ handleReady(d) {
116
+ // we hardcoded the STREAMS_SIMULCAST, which will always be array of 1
117
+ const stream = d.streams[0];
118
+ this.webRtcParams = {
119
+ address: d.ip,
120
+ port: d.port,
121
+ audioSsrc: d.ssrc,
122
+ videoSsrc: stream.ssrc,
123
+ rtxSsrc: stream.rtx_ssrc,
124
+ supportedEncryptionModes: d.modes
125
+ };
126
+ }
127
+ handleProtocolAck(d) {
128
+ const secretKey = Buffer.from(d.secret_key);
129
+ switch (d.mode) {
130
+ case SupportedEncryptionModes.AES256:
131
+ this._transportEncryptor = new AES256TransportEncryptor(secretKey);
132
+ break;
133
+ case SupportedEncryptionModes.XCHACHA20:
134
+ this._transportEncryptor = new Chacha20TransportEncryptor(secretKey);
135
+ break;
136
+ }
137
+ this.emit("select_protocol_ack");
138
+ }
139
+ setupEvents() {
140
+ this.ws?.on('message', (data, isBinary) => {
141
+ if (isBinary)
142
+ return;
143
+ const { op, d, seq } = JSON.parse(data.toString());
144
+ if (seq)
145
+ this._sequenceNumber = seq;
146
+ if (op === VoiceOpCodes.READY) { // ready
147
+ this.handleReady(d);
148
+ this.sendVoice().then(() => this.ready(this.udp));
149
+ this.setVideoAttributes(false);
150
+ }
151
+ else if (op >= 4000) {
152
+ console.error(`Error ${this.constructor.name} connection`, d);
153
+ }
154
+ else if (op === VoiceOpCodes.HELLO) {
155
+ this.setupHeartbeat(d.heartbeat_interval);
156
+ }
157
+ else if (op === VoiceOpCodes.SELECT_PROTOCOL_ACK) { // session description
158
+ this.handleProtocolAck(d);
159
+ }
160
+ else if (op === VoiceOpCodes.SPEAKING) {
161
+ // ignore speaking updates
162
+ }
163
+ else if (op === VoiceOpCodes.HEARTBEAT_ACK) {
164
+ // ignore heartbeat acknowledgements
165
+ }
166
+ else if (op === VoiceOpCodes.RESUMED) {
167
+ this.status.started = true;
168
+ this.udp.ready = true;
169
+ }
170
+ else {
171
+ //console.log("unhandled voice event", {op, d});
172
+ }
173
+ });
174
+ }
175
+ setupHeartbeat(interval) {
176
+ if (this.interval) {
177
+ clearInterval(this.interval);
178
+ }
179
+ this.interval = setInterval(() => {
180
+ this.sendOpcode(VoiceOpCodes.HEARTBEAT, {
181
+ t: Date.now(),
182
+ seq_ack: this._sequenceNumber
183
+ });
184
+ }, interval);
185
+ }
186
+ sendOpcode(code, data) {
187
+ this.ws?.send(JSON.stringify({
188
+ op: code,
189
+ d: data
190
+ }));
191
+ }
192
+ /*
193
+ ** identifies with media server with credentials
194
+ */
195
+ identify() {
196
+ if (!this.serverId)
197
+ throw new Error("Server ID is null or empty");
198
+ if (!this.session_id)
199
+ throw new Error("Session ID is null or empty");
200
+ if (!this.token)
201
+ throw new Error("Token is null or empty");
202
+ this.sendOpcode(VoiceOpCodes.IDENTIFY, {
203
+ server_id: this.serverId,
204
+ user_id: this.botId,
205
+ session_id: this.session_id,
206
+ token: this.token,
207
+ video: true,
208
+ streams: STREAMS_SIMULCAST
209
+ });
210
+ }
211
+ resume() {
212
+ if (!this.serverId)
213
+ throw new Error("Server ID is null or empty");
214
+ if (!this.session_id)
215
+ throw new Error("Session ID is null or empty");
216
+ if (!this.token)
217
+ throw new Error("Token is null or empty");
218
+ this.sendOpcode(VoiceOpCodes.RESUME, {
219
+ server_id: this.serverId,
220
+ session_id: this.session_id,
221
+ token: this.token,
222
+ seq_ack: this._sequenceNumber
223
+ });
224
+ }
225
+ /*
226
+ ** Sets protocols and ip data used for video and audio.
227
+ ** Uses vp8 for video
228
+ ** Uses opus for audio
229
+ */
230
+ setProtocols() {
231
+ const { ip, port } = this.udp;
232
+ if (!ip || !port)
233
+ throw new Error("IP or port is undefined (this shouldn't happen!!!)");
234
+ // select encryption mode
235
+ // From Discord docs:
236
+ // You must support aead_xchacha20_poly1305_rtpsize. You should prefer to use aead_aes256_gcm_rtpsize when it is available.
237
+ let encryptionMode;
238
+ if (!this.webRtcParams)
239
+ throw new Error("WebRTC connection not ready");
240
+ if (this.webRtcParams.supportedEncryptionModes.includes(SupportedEncryptionModes.AES256) &&
241
+ !this._streamer.opts.forceChacha20Encryption) {
242
+ encryptionMode = SupportedEncryptionModes.AES256;
243
+ }
244
+ else {
245
+ encryptionMode = SupportedEncryptionModes.XCHACHA20;
246
+ }
247
+ return new Promise((resolve) => {
248
+ this.sendOpcode(VoiceOpCodes.SELECT_PROTOCOL, {
249
+ protocol: "udp",
250
+ codecs: Object.values(CodecPayloadType),
251
+ data: {
252
+ address: ip,
253
+ port: port,
254
+ mode: encryptionMode
255
+ }
256
+ });
257
+ this.once("select_protocol_ack", () => resolve());
258
+ });
259
+ }
260
+ setVideoAttributes(enabled, attr) {
261
+ if (!this.webRtcParams)
262
+ throw new Error("WebRTC connection not ready");
263
+ const { audioSsrc, videoSsrc, rtxSsrc } = this.webRtcParams;
264
+ if (!enabled) {
265
+ this.sendOpcode(VoiceOpCodes.VIDEO, {
266
+ audio_ssrc: audioSsrc,
267
+ video_ssrc: 0,
268
+ rtx_ssrc: 0,
269
+ streams: []
270
+ });
271
+ }
272
+ else {
273
+ if (!attr)
274
+ throw new Error("Need to specify video attributes");
275
+ this.sendOpcode(VoiceOpCodes.VIDEO, {
276
+ audio_ssrc: audioSsrc,
277
+ video_ssrc: videoSsrc,
278
+ rtx_ssrc: rtxSsrc,
279
+ streams: [
280
+ {
281
+ type: "video",
282
+ rid: "100",
283
+ ssrc: videoSsrc,
284
+ active: true,
285
+ quality: 100,
286
+ rtx_ssrc: rtxSsrc,
287
+ // hardcode the max bitrate because we don't really know anyway
288
+ max_bitrate: 10000 * 1000,
289
+ max_framerate: enabled ? attr.fps : 0,
290
+ max_resolution: {
291
+ type: "fixed",
292
+ width: attr.width,
293
+ height: attr.height
294
+ }
295
+ }
296
+ ]
297
+ });
298
+ }
299
+ }
300
+ /*
301
+ ** Set speaking status
302
+ ** speaking -> speaking status on or off
303
+ */
304
+ setSpeaking(speaking) {
305
+ if (!this.webRtcParams)
306
+ throw new Error("WebRTC connection not ready");
307
+ this.sendOpcode(VoiceOpCodes.SPEAKING, {
308
+ delay: 0,
309
+ speaking: speaking ? 1 : 0,
310
+ ssrc: this.webRtcParams.audioSsrc
311
+ });
312
+ }
313
+ /*
314
+ ** Start media connection
315
+ */
316
+ sendVoice() {
317
+ return this.udp.createUdp().then(() => this.setProtocols());
318
+ }
319
+ }
@@ -0,0 +1,26 @@
1
+ import type { BaseMediaPacketizer } from '../packet/BaseMediaPacketizer.js';
2
+ import type { BaseMediaConnection } from './BaseMediaConnection.js';
3
+ export declare class MediaUdp {
4
+ private _mediaConnection;
5
+ private _socket;
6
+ private _ready;
7
+ private _audioPacketizer?;
8
+ private _videoPacketizer?;
9
+ private _ip?;
10
+ private _port?;
11
+ constructor(voiceConnection: BaseMediaConnection);
12
+ get audioPacketizer(): BaseMediaPacketizer | undefined;
13
+ get videoPacketizer(): BaseMediaPacketizer | undefined;
14
+ get mediaConnection(): BaseMediaConnection;
15
+ get ip(): string | undefined;
16
+ get port(): number | undefined;
17
+ sendAudioFrame(frame: Buffer, frametime: number): Promise<void>;
18
+ sendVideoFrame(frame: Buffer, frametime: number): Promise<void>;
19
+ setPacketizer(videoCodec: string): void;
20
+ sendPacket(packet: Buffer): Promise<void>;
21
+ handleIncoming(buf: unknown): void;
22
+ get ready(): boolean;
23
+ set ready(val: boolean);
24
+ stop(): void;
25
+ createUdp(): Promise<void>;
26
+ }
@@ -0,0 +1,140 @@
1
+ import udpCon from 'node:dgram';
2
+ import { isIP } from 'node:net';
3
+ import { AudioPacketizer } from '../packet/AudioPacketizer.js';
4
+ import { VideoPacketizerH264, VideoPacketizerH265 } from '../packet/VideoPacketizerAnnexB.js';
5
+ import { VideoPacketizerVP8 } from '../packet/VideoPacketizerVP8.js';
6
+ import { normalizeVideoCodec } from '../../utils.js';
7
+ // credit to discord.js
8
+ function parseLocalPacket(message) {
9
+ const packet = Buffer.from(message);
10
+ const ip = packet.subarray(8, packet.indexOf(0, 8)).toString('utf8');
11
+ if (!isIP(ip)) {
12
+ throw new Error('Malformed IP address');
13
+ }
14
+ const port = packet.readUInt16BE(packet.length - 2);
15
+ return { ip, port };
16
+ }
17
+ export class MediaUdp {
18
+ constructor(voiceConnection) {
19
+ this._socket = null;
20
+ this._ready = false;
21
+ this._mediaConnection = voiceConnection;
22
+ }
23
+ get audioPacketizer() {
24
+ return this._audioPacketizer;
25
+ }
26
+ get videoPacketizer() {
27
+ // This will never be undefined anyway, so it's safe
28
+ return this._videoPacketizer;
29
+ }
30
+ get mediaConnection() {
31
+ return this._mediaConnection;
32
+ }
33
+ get ip() {
34
+ return this._ip;
35
+ }
36
+ get port() {
37
+ return this._port;
38
+ }
39
+ async sendAudioFrame(frame, frametime) {
40
+ if (!this.ready)
41
+ return;
42
+ await this.audioPacketizer?.sendFrame(frame, frametime);
43
+ }
44
+ async sendVideoFrame(frame, frametime) {
45
+ if (!this.ready)
46
+ return;
47
+ await this.videoPacketizer?.sendFrame(frame, frametime);
48
+ }
49
+ setPacketizer(videoCodec) {
50
+ if (!this.mediaConnection.webRtcParams)
51
+ throw new Error("WebRTC connection not ready");
52
+ const { audioSsrc, videoSsrc } = this.mediaConnection.webRtcParams;
53
+ this._audioPacketizer = new AudioPacketizer(this, audioSsrc);
54
+ switch (normalizeVideoCodec(videoCodec)) {
55
+ case "H264":
56
+ this._videoPacketizer = new VideoPacketizerH264(this, videoSsrc);
57
+ break;
58
+ case "H265":
59
+ this._videoPacketizer = new VideoPacketizerH265(this, videoSsrc);
60
+ break;
61
+ case "VP8":
62
+ this._videoPacketizer = new VideoPacketizerVP8(this, videoSsrc);
63
+ break;
64
+ default:
65
+ throw new Error(`Packetizer not implemented for ${videoCodec}`);
66
+ }
67
+ }
68
+ sendPacket(packet) {
69
+ if (!this.mediaConnection.webRtcParams)
70
+ throw new Error("WebRTC connection not ready");
71
+ const { address, port } = this.mediaConnection.webRtcParams;
72
+ return new Promise((resolve, reject) => {
73
+ try {
74
+ this._socket?.send(packet, 0, packet.length, port, address, (error, bytes) => {
75
+ if (error) {
76
+ console.log("ERROR", error);
77
+ reject(error);
78
+ }
79
+ resolve();
80
+ });
81
+ }
82
+ catch (e) {
83
+ reject(e);
84
+ }
85
+ });
86
+ }
87
+ handleIncoming(buf) {
88
+ //console.log("RECEIVED PACKET", buf);
89
+ }
90
+ get ready() {
91
+ return this._ready;
92
+ }
93
+ set ready(val) {
94
+ this._ready = val;
95
+ }
96
+ stop() {
97
+ try {
98
+ this.ready = false;
99
+ this._socket?.disconnect();
100
+ }
101
+ catch (e) { }
102
+ }
103
+ createUdp() {
104
+ if (!this.mediaConnection.webRtcParams)
105
+ throw new Error("WebRTC connection not ready");
106
+ const { audioSsrc, address, port } = this.mediaConnection.webRtcParams;
107
+ return new Promise((resolve, reject) => {
108
+ this._socket = udpCon.createSocket('udp4');
109
+ this._socket.on('error', (error) => {
110
+ console.error("Error connecting to media udp server", error);
111
+ reject(error);
112
+ });
113
+ this._socket.once('message', (message) => {
114
+ if (message.readUInt16BE(0) !== 2) {
115
+ reject('wrong handshake packet for udp');
116
+ }
117
+ try {
118
+ const packet = parseLocalPacket(message);
119
+ this._ip = packet.ip;
120
+ this._port = packet.port;
121
+ this._ready = true;
122
+ }
123
+ catch (e) {
124
+ reject(e);
125
+ }
126
+ resolve();
127
+ this._socket?.on('message', this.handleIncoming);
128
+ });
129
+ const blank = Buffer.alloc(74);
130
+ blank.writeUInt16BE(1, 0);
131
+ blank.writeUInt16BE(70, 2);
132
+ blank.writeUInt32BE(audioSsrc, 4);
133
+ this._socket.send(blank, 0, blank.length, port, address, (error, bytes) => {
134
+ if (error) {
135
+ reject(error);
136
+ }
137
+ });
138
+ });
139
+ }
140
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseMediaConnection } from "./BaseMediaConnection.js";
2
+ export declare class StreamConnection extends BaseMediaConnection {
3
+ private _streamKey;
4
+ private _serverId;
5
+ setSpeaking(speaking: boolean): void;
6
+ get serverId(): string | null;
7
+ set serverId(id: string);
8
+ get streamKey(): string | null;
9
+ set streamKey(value: string);
10
+ }
@@ -0,0 +1,30 @@
1
+ import { VoiceOpCodes } from "../voice/VoiceOpCodes.js";
2
+ import { BaseMediaConnection } from "./BaseMediaConnection.js";
3
+ export class StreamConnection extends BaseMediaConnection {
4
+ constructor() {
5
+ super(...arguments);
6
+ this._streamKey = null;
7
+ this._serverId = null;
8
+ }
9
+ setSpeaking(speaking) {
10
+ if (!this.webRtcParams)
11
+ throw new Error("WebRTC connection not ready");
12
+ this.sendOpcode(VoiceOpCodes.SPEAKING, {
13
+ delay: 0,
14
+ speaking: speaking ? 2 : 0,
15
+ ssrc: this.webRtcParams.audioSsrc
16
+ });
17
+ }
18
+ get serverId() {
19
+ return this._serverId;
20
+ }
21
+ set serverId(id) {
22
+ this._serverId = id;
23
+ }
24
+ get streamKey() {
25
+ return this._streamKey;
26
+ }
27
+ set streamKey(value) {
28
+ this._streamKey = value;
29
+ }
30
+ }
@@ -0,0 +1,7 @@
1
+ import type { StreamConnection } from './StreamConnection.js';
2
+ import { BaseMediaConnection } from './BaseMediaConnection.js';
3
+ export declare class VoiceConnection extends BaseMediaConnection {
4
+ streamConnection?: StreamConnection;
5
+ get serverId(): string;
6
+ stop(): void;
7
+ }
@@ -0,0 +1,10 @@
1
+ import { BaseMediaConnection } from './BaseMediaConnection.js';
2
+ export class VoiceConnection extends BaseMediaConnection {
3
+ get serverId() {
4
+ return this.guildId ?? this.channelId; // for guild vc it is the guild id, for dm voice it is the channel id
5
+ }
6
+ stop() {
7
+ super.stop();
8
+ this.streamConnection?.stop();
9
+ }
10
+ }
@@ -0,0 +1,136 @@
1
+ import type { VoiceOpCodes } from "./VoiceOpCodes.js";
2
+ import type { SupportedEncryptionModes } from "../../utils.js";
3
+ type StreamInfo = {
4
+ active: boolean;
5
+ quality: number;
6
+ rid: string;
7
+ ssrc: number;
8
+ rtx_ssrc: number;
9
+ /**
10
+ * always "video" from what I observed
11
+ */
12
+ type: string;
13
+ };
14
+ type SimulcastInfo = {
15
+ type: string;
16
+ rid: string;
17
+ quality: number;
18
+ };
19
+ type CodecPayloadType = {
20
+ name: string;
21
+ type: "audio";
22
+ priority: number;
23
+ payload_type: number;
24
+ } | {
25
+ name: string;
26
+ type: "video";
27
+ priority: number;
28
+ payload_type: number;
29
+ rtx_payload_type: number;
30
+ encode: boolean;
31
+ decode: boolean;
32
+ };
33
+ export declare namespace Message {
34
+ type Identify = {
35
+ server_id: string;
36
+ user_id: string;
37
+ session_id: string;
38
+ token: string;
39
+ video: boolean;
40
+ streams: SimulcastInfo[];
41
+ };
42
+ type Resume = {
43
+ server_id: string;
44
+ session_id: string;
45
+ token: string;
46
+ seq_ack: number;
47
+ };
48
+ type Heartbeat = {
49
+ t: number;
50
+ seq_ack?: number;
51
+ };
52
+ type SelectProtocol = {
53
+ protocol: string;
54
+ codecs: CodecPayloadType[];
55
+ data: {
56
+ address: string;
57
+ port: number;
58
+ mode: SupportedEncryptionModes;
59
+ };
60
+ };
61
+ type Video = {
62
+ audio_ssrc: number;
63
+ video_ssrc: number;
64
+ rtx_ssrc: number;
65
+ streams: {
66
+ type: "video";
67
+ rid: string;
68
+ ssrc: number;
69
+ active: boolean;
70
+ quality: number;
71
+ rtx_ssrc: number;
72
+ max_bitrate: number;
73
+ max_framerate: number;
74
+ max_resolution: {
75
+ type: "fixed";
76
+ width: number;
77
+ height: number;
78
+ };
79
+ }[];
80
+ };
81
+ type Hello = {
82
+ heartbeat_interval: number;
83
+ };
84
+ type Ready = {
85
+ ssrc: number;
86
+ ip: string;
87
+ port: number;
88
+ modes: SupportedEncryptionModes[];
89
+ experiments: string[];
90
+ streams: StreamInfo[];
91
+ };
92
+ type Speaking = {
93
+ speaking: 0 | 1 | 2;
94
+ delay: number;
95
+ ssrc: number;
96
+ };
97
+ type SelectProtocolAck = {
98
+ secret_key: number[];
99
+ audio_codec: string;
100
+ video_codec: string;
101
+ mode: string;
102
+ };
103
+ type HeartbeatAck = {
104
+ t: number;
105
+ };
106
+ }
107
+ export declare namespace GatewayResponse {
108
+ type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
109
+ op: Op;
110
+ d: T;
111
+ seq?: number;
112
+ };
113
+ export type Hello = Generic<VoiceOpCodes.HELLO, Message.Hello>;
114
+ export type Ready = Generic<VoiceOpCodes.READY, Message.Ready>;
115
+ export type Resumed = Generic<VoiceOpCodes.RESUMED, null>;
116
+ export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>;
117
+ export type SelectProtocolAck = Generic<VoiceOpCodes.SELECT_PROTOCOL_ACK, Message.SelectProtocolAck>;
118
+ export type HeartbeatAck = Generic<VoiceOpCodes.HEARTBEAT_ACK, Message.HeartbeatAck>;
119
+ export {};
120
+ }
121
+ export type GatewayResponse = GatewayResponse.Hello | GatewayResponse.Ready | GatewayResponse.Resumed | GatewayResponse.Speaking | GatewayResponse.SelectProtocolAck | GatewayResponse.HeartbeatAck;
122
+ export declare namespace GatewayRequest {
123
+ type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
124
+ op: Op;
125
+ d: T;
126
+ };
127
+ export type Identify = Generic<VoiceOpCodes.IDENTIFY, Message.Identify>;
128
+ export type Resume = Generic<VoiceOpCodes.RESUME, Message.Resume>;
129
+ export type Heartbeat = Generic<VoiceOpCodes.HEARTBEAT, Message.Heartbeat>;
130
+ export type SelectProtocol = Generic<VoiceOpCodes.SELECT_PROTOCOL, Message.SelectProtocol>;
131
+ export type Video = Generic<VoiceOpCodes.VIDEO, Message.Video>;
132
+ export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>;
133
+ export {};
134
+ }
135
+ export type GatewayRequest = GatewayRequest.Identify | GatewayRequest.Resume | GatewayRequest.Heartbeat | GatewayRequest.SelectProtocol | GatewayRequest.Video | GatewayRequest.Speaking;
136
+ export {};
@@ -0,0 +1 @@
1
+ export {};