@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,164 @@
1
+ import type { VoiceOpCodes } from "./VoiceOpCodes.js"
2
+ import type { SupportedEncryptionModes } from "../../utils.js"
3
+
4
+ type StreamInfo = {
5
+ active: boolean,
6
+ quality: number,
7
+ rid: string,
8
+ ssrc: number,
9
+ rtx_ssrc: number,
10
+ /**
11
+ * always "video" from what I observed
12
+ */
13
+ type: string
14
+ }
15
+
16
+ type SimulcastInfo = {
17
+ type: string,
18
+ rid: string,
19
+ quality: number
20
+ }
21
+
22
+ type CodecPayloadType = {
23
+ name: string,
24
+ type: "audio",
25
+ priority: number,
26
+ payload_type: number
27
+ } | {
28
+ name: string,
29
+ type: "video",
30
+ priority: number,
31
+ payload_type: number,
32
+ rtx_payload_type: number,
33
+ encode: boolean,
34
+ decode: boolean
35
+ }
36
+
37
+ export namespace Message {
38
+ // Request messages
39
+ export type Identify = {
40
+ server_id: string,
41
+ user_id: string,
42
+ session_id: string,
43
+ token: string,
44
+ video: boolean,
45
+ streams: SimulcastInfo[]
46
+ }
47
+
48
+ export type Resume = {
49
+ server_id: string,
50
+ session_id: string,
51
+ token: string,
52
+ seq_ack: number
53
+ }
54
+
55
+ export type Heartbeat = {
56
+ t: number,
57
+ seq_ack?: number
58
+ }
59
+
60
+ export type SelectProtocol = {
61
+ protocol: string,
62
+ codecs: CodecPayloadType[],
63
+ data: {
64
+ address: string,
65
+ port: number,
66
+ mode: SupportedEncryptionModes
67
+ }
68
+ }
69
+
70
+ export type Video = {
71
+ audio_ssrc: number,
72
+ video_ssrc: number,
73
+ rtx_ssrc: number,
74
+ streams: {
75
+ type: "video",
76
+ rid: string,
77
+ ssrc: number,
78
+ active: boolean,
79
+ quality: number,
80
+ rtx_ssrc: number,
81
+ max_bitrate: number,
82
+ max_framerate: number,
83
+ max_resolution: {
84
+ type: "fixed",
85
+ width: number,
86
+ height: number
87
+ }
88
+ }[]
89
+ }
90
+
91
+ // Response messages
92
+ export type Hello = {
93
+ heartbeat_interval: number
94
+ }
95
+
96
+ export type Ready = {
97
+ ssrc: number,
98
+ ip: string,
99
+ port: number,
100
+ modes: SupportedEncryptionModes[],
101
+ experiments: string[],
102
+ streams: StreamInfo[]
103
+ }
104
+
105
+ export type Speaking = {
106
+ speaking: 0 | 1 | 2,
107
+ delay: number,
108
+ ssrc: number
109
+ }
110
+
111
+ export type SelectProtocolAck = {
112
+ secret_key: number[],
113
+ audio_codec: string,
114
+ video_codec: string,
115
+ mode: string,
116
+ }
117
+
118
+ export type HeartbeatAck = {
119
+ t: number
120
+ }
121
+ }
122
+
123
+ export namespace GatewayResponse {
124
+ type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
125
+ op: Op,
126
+ d: T,
127
+ seq?: number
128
+ }
129
+ export type Hello = Generic<VoiceOpCodes.HELLO, Message.Hello>
130
+ export type Ready = Generic<VoiceOpCodes.READY, Message.Ready>
131
+ export type Resumed = Generic<VoiceOpCodes.RESUMED, null>
132
+ export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>
133
+ export type SelectProtocolAck = Generic<VoiceOpCodes.SELECT_PROTOCOL_ACK, Message.SelectProtocolAck>
134
+ export type HeartbeatAck = Generic<VoiceOpCodes.HEARTBEAT_ACK, Message.HeartbeatAck>
135
+ }
136
+
137
+ export type GatewayResponse =
138
+ GatewayResponse.Hello |
139
+ GatewayResponse.Ready |
140
+ GatewayResponse.Resumed |
141
+ GatewayResponse.Speaking |
142
+ GatewayResponse.SelectProtocolAck |
143
+ GatewayResponse.HeartbeatAck
144
+
145
+ export namespace GatewayRequest {
146
+ type Generic<Op extends VoiceOpCodes, T extends Record<string, unknown> | null> = {
147
+ op: Op,
148
+ d: T
149
+ }
150
+ export type Identify = Generic<VoiceOpCodes.IDENTIFY, Message.Identify>
151
+ export type Resume = Generic<VoiceOpCodes.RESUME, Message.Resume>
152
+ export type Heartbeat = Generic<VoiceOpCodes.HEARTBEAT, Message.Heartbeat>
153
+ export type SelectProtocol = Generic<VoiceOpCodes.SELECT_PROTOCOL, Message.SelectProtocol>
154
+ export type Video = Generic<VoiceOpCodes.VIDEO, Message.Video>
155
+ export type Speaking = Generic<VoiceOpCodes.SPEAKING, Message.Speaking>
156
+ }
157
+
158
+ export type GatewayRequest =
159
+ GatewayRequest.Identify |
160
+ GatewayRequest.Resume |
161
+ GatewayRequest.Heartbeat |
162
+ GatewayRequest.SelectProtocol |
163
+ GatewayRequest.Video |
164
+ GatewayRequest.Speaking
@@ -0,0 +1,21 @@
1
+ export enum VoiceOpCodes {
2
+ IDENTIFY = 0,
3
+ SELECT_PROTOCOL = 1,
4
+ READY = 2,
5
+ HEARTBEAT = 3,
6
+ SELECT_PROTOCOL_ACK = 4,
7
+ SPEAKING = 5,
8
+ HEARTBEAT_ACK = 6,
9
+ RESUME = 7,
10
+ HELLO = 8,
11
+ RESUMED = 9,
12
+ VIDEO = 12,
13
+ CLIENT_DISCONNECT = 13,
14
+ SESSION_UPDATE = 14,
15
+ MEDIA_SINK_WANTS = 15,
16
+ VOICE_BACKEND_VERSION = 16,
17
+ CHANNEL_OPTIONS_UPDATE = 17,
18
+ FLAGS = 18,
19
+ SPEED_TEST = 19,
20
+ PLATFORM = 20
21
+ }
@@ -0,0 +1,5 @@
1
+ export * from './VoiceConnection.js';
2
+ export * from './VoiceOpCodes.js';
3
+ export * from './MediaUdp.js';
4
+ export * from './StreamConnection.js';
5
+ export * from './BaseMediaConnection.js';
package/src/index.ts ADDED
@@ -0,0 +1,5 @@
1
+ export * from './client/index.js';
2
+ export * from './media/index.js';
3
+ export * from './media/newApi.js';
4
+ export * as NewApi from './media/newApi.js';
5
+ export * as Utils from './utils.js';
@@ -0,0 +1,81 @@
1
+ import OpusScript from "opusscript";
2
+ import type { MediaUdp } from "../client/voice/MediaUdp.js";
3
+ import { BaseMediaStream } from "./BaseMediaStream.js";
4
+
5
+ export class AudioStream extends BaseMediaStream {
6
+ public udp: MediaUdp;
7
+ private _isMuted: boolean; // Internal state for muting
8
+ private _volume: number = 1.0;
9
+ private _opusEncoder: OpusScript;
10
+
11
+
12
+
13
+ constructor(udp: MediaUdp, noSleep = false, initialMuted: boolean = false) {
14
+ super("audio", noSleep);
15
+ this.udp = udp;
16
+ this._isMuted = initialMuted; // Initialize mute state
17
+ this._opusEncoder = new OpusScript(48000, 2, 2048);
18
+ }
19
+
20
+ /**
21
+ * Mutes the audio stream. No audio frames will be sent.
22
+ */
23
+ public mute(): void {
24
+ this._isMuted = true;
25
+ }
26
+
27
+ /**
28
+ * Unmutes the audio stream. Audio frames will resume sending.
29
+ */
30
+ public unmute(): void {
31
+ this._isMuted = false;
32
+ }
33
+
34
+ /**
35
+ * Checks if the audio stream is currently muted.
36
+ * @returns True if muted, false otherwise.
37
+ */
38
+ public isMuted(): boolean {
39
+ return this._isMuted;
40
+ }
41
+
42
+ public setVolume(volume: number): void {
43
+ if (volume < 0) {
44
+ this._volume = 0;
45
+ } else {
46
+ this._volume = Math.min(volume, 2.0);
47
+ }
48
+ }
49
+
50
+ public getVolume(): number {
51
+ return this._volume;
52
+ }
53
+
54
+ protected override async _sendFrame(frame: Buffer, frametime: number): Promise<void> {
55
+ if (this._isMuted) {
56
+ return;
57
+ }
58
+
59
+ let processedFrame = frame;
60
+
61
+ if (this._volume !== 1.0) {
62
+ try {
63
+ const pcmData = this._opusEncoder.decode(frame);
64
+
65
+ for (let i = 0; i < pcmData.length; i += 2) {
66
+ const sample = pcmData.readInt16LE(i);
67
+ const newSample = Math.max(-32768, Math.min(32767, Math.floor(sample * this._volume)));
68
+ pcmData.writeInt16LE(newSample, i);
69
+ }
70
+
71
+ processedFrame = this._opusEncoder.encode(pcmData, 960);
72
+ } catch (error) {
73
+ console.error("[AudioStream] Error processing audio frame:", error);
74
+ processedFrame = frame;
75
+ }
76
+ }
77
+
78
+ await this.udp.sendAudioFrame(processedFrame, frametime);
79
+ }
80
+
81
+ }
@@ -0,0 +1,173 @@
1
+ import { Log } from "debug-level";
2
+ import { setTimeout } from "node:timers/promises";
3
+ import { Writable } from "node:stream";
4
+ import { combineLoHi } from "./utils.js";
5
+ import type { Packet } from "@lng2004/libav.js-variant-webcodecs-avf-with-decoders";
6
+
7
+ export class BaseMediaStream extends Writable {
8
+ private _pts?: number;
9
+ private _syncTolerance = 20;
10
+ private _loggerSend: Log;
11
+ private _loggerSync: Log;
12
+ private _loggerSleep: Log;
13
+
14
+ private _noSleep: boolean;
15
+ private _startTime?: number;
16
+ private _startPts?: number;
17
+ private _sync = true;
18
+ private _syncStream?: BaseMediaStream;
19
+
20
+ constructor(type: string, noSleep = false) {
21
+ super({ objectMode: true, highWaterMark: 0 });
22
+ this._loggerSend = new Log(`stream:${type}:send`);
23
+ this._loggerSync = new Log(`stream:${type}:sync`);
24
+ this._loggerSleep = new Log(`stream:${type}:sleep`);
25
+ this._noSleep = noSleep;
26
+ }
27
+
28
+ get sync(): boolean {
29
+ return this._sync;
30
+ }
31
+ set sync(val: boolean) {
32
+ this._sync = val;
33
+ if (val)
34
+ this._loggerSync.debug("Sync enabled");
35
+ else
36
+ this._loggerSync.debug("Sync disabled");
37
+ }
38
+ get syncStream() {
39
+ return this._syncStream;
40
+ }
41
+ set syncStream(stream: BaseMediaStream | undefined)
42
+ {
43
+ if (stream === this)
44
+ throw new Error("A stream cannot sync with itself");
45
+ this._syncStream = stream;
46
+ }
47
+ get noSleep(): boolean {
48
+ return this._noSleep;
49
+ }
50
+ set noSleep(val: boolean) {
51
+ this._noSleep = val;
52
+ if (!val)
53
+ this.resetTimingCompensation();
54
+ }
55
+ get pts(): number | undefined {
56
+ return this._pts;
57
+ }
58
+ get syncTolerance() {
59
+ return this._syncTolerance;
60
+ }
61
+ set syncTolerance(n: number) {
62
+ if (n < 0)
63
+ return;
64
+ this._syncTolerance = n;
65
+ }
66
+ protected async _sendFrame(frame: Buffer, frametime: number): Promise<void>
67
+ {
68
+ throw new Error("Not implemented");
69
+ }
70
+ private ptsDelta() {
71
+ if (this.pts !== undefined && this.syncStream?.pts !== undefined)
72
+ return this.pts - this.syncStream.pts;
73
+ return undefined;
74
+ }
75
+ private isAhead() {
76
+ const delta = this.ptsDelta();
77
+ return this.syncStream?.writableEnded === false && delta !== undefined && delta > this.syncTolerance;
78
+ }
79
+ private isBehind() {
80
+ const delta = this.ptsDelta();
81
+ return this.syncStream?.writableEnded === false && delta !== undefined && delta < -this.syncTolerance;
82
+ }
83
+ private resetTimingCompensation() {
84
+ this._startTime = this._startPts = undefined;
85
+ }
86
+ async _write(frame: Packet, _: BufferEncoding, callback: (error?: Error | null) => void) {
87
+ const { data, ptshi, pts, durationhi, duration, time_base_num, time_base_den } = frame;
88
+ // biome-ignore lint/style/noNonNullAssertion: this will never happen with our media stream
89
+ const frametime = combineLoHi(durationhi!, duration!) / time_base_den! * time_base_num! * 1000;
90
+
91
+ const start_sendFrame = performance.now();
92
+ await this._sendFrame(Buffer.from(data), frametime);
93
+ const end_sendFrame = performance.now();
94
+
95
+ // biome-ignore lint/style/noNonNullAssertion: this will never happen with our media stream
96
+ this._pts = combineLoHi(ptshi!, pts!) / time_base_den! * time_base_num! * 1000;
97
+ this.emit("pts", this._pts);
98
+
99
+ const sendTime = end_sendFrame - start_sendFrame;
100
+ const ratio = sendTime / frametime;
101
+ this._loggerSend.debug({
102
+ stats: {
103
+ pts: this._pts,
104
+ frame_size: data.length,
105
+ duration: sendTime,
106
+ frametime
107
+ }
108
+ }, `Frame sent in ${sendTime.toFixed(2)}ms (${(ratio * 100).toFixed(2)}% frametime)`);
109
+ if (ratio > 1)
110
+ {
111
+ this._loggerSend.warn({
112
+ frame_size: data.length,
113
+ duration: sendTime,
114
+ frametime
115
+ }, `Frame takes too long to send (${(ratio * 100).toFixed(2)}% frametime)`)
116
+ }
117
+
118
+ this._startTime ??= start_sendFrame;
119
+ this._startPts ??= this._pts;
120
+ const sleep = Math.max(
121
+ 0, this._pts - this._startPts + frametime - (end_sendFrame - this._startTime)
122
+ );
123
+ if (this._noSleep || sleep === 0)
124
+ {
125
+ callback(null);
126
+ }
127
+ else if (this.sync && this.isBehind())
128
+ {
129
+ this._loggerSync.debug({
130
+ stats: {
131
+ pts: this.pts,
132
+ pts_other: this.syncStream?.pts
133
+ }
134
+ }, "Stream is behind. Not sleeping for this frame");
135
+ this.resetTimingCompensation();
136
+ callback(null);
137
+ }
138
+ else if (this.sync && this.isAhead())
139
+ {
140
+ do
141
+ {
142
+ this._loggerSync.debug({
143
+ stats: {
144
+ pts: this.pts,
145
+ pts_other: this.syncStream?.pts,
146
+ frametime
147
+ }
148
+ }, `Stream is ahead. Waiting for ${frametime}ms`);
149
+ await setTimeout(frametime);
150
+ }
151
+ while (this.sync && this.isAhead());
152
+ this.resetTimingCompensation();
153
+ callback(null);
154
+ }
155
+ else
156
+ {
157
+ this._loggerSleep.debug({
158
+ stats: {
159
+ pts: this._pts,
160
+ startPts: this._startPts,
161
+ time: end_sendFrame,
162
+ startTime: this._startTime,
163
+ frametime
164
+ }
165
+ }, `Sleeping for ${sleep}ms`);
166
+ setTimeout(sleep).then(() => callback(null));
167
+ }
168
+ }
169
+ _destroy(error: Error | null, callback: (error?: Error | null) => void): void {
170
+ super._destroy(error, callback);
171
+ this.syncStream = undefined;
172
+ }
173
+ }