piixishstream 1.0.3 → 1.0.5

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.
@@ -82,33 +82,47 @@ export class Streamer {
82
82
  reject("cannot start stream without first joining voice channel");
83
83
  return;
84
84
  }
85
- this.signalStream();
86
- const { guildId: clientGuildId, channelId: clientChannelId, session_id, } = this.voiceConnection;
85
+ if (this.voiceConnection.streamConnection?.webRtcConn?.ready) {
86
+ resolve(this.voiceConnection.streamConnection.webRtcConn);
87
+ return;
88
+ }
89
+ const { guildId: clientGuildId, channelId: clientChannelId, session_id } = this.voiceConnection;
87
90
  const { id: clientUserId } = this.client.user;
88
- if (!session_id)
89
- throw new Error("Session doesn't exist yet");
90
- const streamConn = new StreamConnection(this, clientGuildId, clientUserId, clientChannelId, (conn) => {
91
- resolve(conn);
92
- });
93
- this.voiceConnection.streamConnection = streamConn;
94
- this._gatewayEmitter.on("STREAM_CREATE", (d) => {
91
+ if (!session_id) throw new Error("Session doesn't exist yet");
92
+
93
+ let done = false;
94
+ const onStreamCreate = (d) => {
95
95
  const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
96
- if (clientGuildId !== guildId ||
97
- clientChannelId !== channelId ||
98
- clientUserId !== userId)
99
- return;
96
+ if (clientGuildId !== guildId || clientChannelId !== channelId || clientUserId !== userId) return;
100
97
  streamConn.serverId = d.rtc_server_id;
101
98
  streamConn.streamKey = d.stream_key;
102
99
  streamConn.setSession(session_id);
103
- });
104
- this._gatewayEmitter.on("STREAM_SERVER_UPDATE", (d) => {
100
+ };
101
+ const onStreamServerUpdate = (d) => {
105
102
  const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
106
- if (clientGuildId !== guildId ||
107
- clientChannelId !== channelId ||
108
- clientUserId !== userId)
109
- return;
103
+ if (clientGuildId !== guildId || clientChannelId !== channelId || clientUserId !== userId) return;
110
104
  streamConn.setTokens(d.endpoint, d.token);
105
+ };
106
+ const cleanup = () => {
107
+ if (done) return;
108
+ done = true;
109
+ clearTimeout(timer);
110
+ this._gatewayEmitter.off("STREAM_CREATE", onStreamCreate);
111
+ this._gatewayEmitter.off("STREAM_SERVER_UPDATE", onStreamServerUpdate);
112
+ };
113
+ const timer = setTimeout(() => {
114
+ cleanup();
115
+ reject(new Error("Timeout en attente de la passerelle Discord Go-Live"));
116
+ }, 10000);
117
+
118
+ this.signalStream();
119
+ const streamConn = new StreamConnection(this, clientGuildId, clientUserId, clientChannelId, (conn) => {
120
+ cleanup();
121
+ resolve(conn);
111
122
  });
123
+ this.voiceConnection.streamConnection = streamConn;
124
+ this._gatewayEmitter.on("STREAM_CREATE", onStreamCreate);
125
+ this._gatewayEmitter.on("STREAM_SERVER_UPDATE", onStreamServerUpdate);
112
126
  });
113
127
  }
114
128
  async setStreamPreview(image) {
@@ -60,6 +60,25 @@ export class WebRtcConnWrapper {
60
60
  get webRtcConn() {
61
61
  return this._webRtcConn;
62
62
  }
63
+ waitUntilReady(timeoutMs = 5000) {
64
+ if (this.ready) return Promise.resolve();
65
+ return new Promise((resolve) => {
66
+ let done = false;
67
+ const finish = () => {
68
+ if (!done) {
69
+ done = true;
70
+ clearTimeout(timer);
71
+ resolve();
72
+ }
73
+ };
74
+ const timer = setTimeout(finish, timeoutMs);
75
+ this._webRtcConn?.onStateChange((state) => {
76
+ if (state === "connected") {
77
+ finish();
78
+ }
79
+ });
80
+ });
81
+ }
63
82
  get ready() {
64
83
  return this._webRtcConn?.state() === "connected";
65
84
  }
@@ -75,7 +94,7 @@ export class WebRtcConnWrapper {
75
94
  const { clockRate } = rtpConfig;
76
95
  if (this.mediaConnection.daveReady)
77
96
  frame = this.mediaConnection.daveSession.encryptOpus(frame);
78
- this._audioTrack?.sendMessageBinary(frame);
97
+ try { this._audioTrack?.sendMessageBinary(frame); } catch (e) { /* ignore SRTP/WebRTC drop errors */ }
79
98
  rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
80
99
  }
81
100
  sendVideoFrame(frame, frametime) {
@@ -118,7 +137,7 @@ export class WebRtcConnWrapper {
118
137
  }
119
138
  frame = this.mediaConnection.daveSession.encrypt(1 /* MediaType.VIDEO */, daveCodec, frame);
120
139
  }
121
- this._videoTrack?.sendMessageBinary(frame);
140
+ try { this._videoTrack?.sendMessageBinary(frame); } catch (e) { /* ignore SRTP/WebRTC drop errors */ }
122
141
  rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
123
142
  }
124
143
  setPacketizer(videoCodec) {
@@ -274,6 +274,7 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
274
274
  ? opts.readrateInitialBurst
275
275
  : defaultOptions.readrateInitialBurst,
276
276
  streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
277
+ keepConnection: opts.keepConnection ?? false,
277
278
  };
278
279
  }
279
280
  const mergedOptions = mergeOptions(options);
@@ -297,12 +298,12 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
297
298
  let stopStream;
298
299
  if (mergedOptions.type === "go-live") {
299
300
  conn = await streamer.createStream();
300
- stopStream = () => streamer.stopStream();
301
+ stopStream = () => { if (!mergedOptions.keepConnection) streamer.stopStream(); };
301
302
  }
302
303
  else {
303
304
  conn = streamer.voiceConnection.webRtcConn;
304
305
  streamer.signalVideo(true);
305
- stopStream = () => streamer.signalVideo(false);
306
+ stopStream = () => { if (!mergedOptions.keepConnection) streamer.signalVideo(false); };
306
307
  }
307
308
  conn.setPacketizer(videoCodecMap[video.codec]);
308
309
  conn.mediaConnection.setSpeaking(true);
@@ -383,8 +384,10 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
383
384
  return new Promise((resolve, reject) => {
384
385
  cleanupFuncs.push(() => {
385
386
  stopStream();
386
- conn.mediaConnection.setSpeaking(false);
387
- conn.mediaConnection.setVideoAttributes(false);
387
+ if (!mergedOptions.keepConnection) {
388
+ conn.mediaConnection.setSpeaking(false);
389
+ conn.mediaConnection.setVideoAttributes(false);
390
+ }
388
391
  });
389
392
  let cleanedUp = false;
390
393
  const cleanup = () => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "piixishstream",
3
- "version": "1.0.3",
3
+ "version": "1.0.5",
4
4
  "description": "Experiment for making video streaming work for discord selfbots",
5
5
  "main": "./dist/index.js",
6
6
  "exports": "./dist/index.js",
@@ -80,4 +80,5 @@
80
80
  "node-datachannel"
81
81
  ]
82
82
  }
83
- }
83
+ }
84
+
@@ -104,63 +104,58 @@ export class Streamer {
104
104
  });
105
105
  }
106
106
 
107
- public createStream(): Promise<WebRtcConnWrapper> {
108
- return new Promise<WebRtcConnWrapper>((resolve, reject) => {
109
- if (!this.client.user) {
110
- reject("Client not logged in");
111
- return;
112
- }
113
- if (!this.voiceConnection) {
114
- reject("cannot start stream without first joining voice channel");
115
- return;
116
- }
117
-
118
- this.signalStream();
119
- const {
120
- guildId: clientGuildId,
121
- channelId: clientChannelId,
122
- session_id,
123
- } = this.voiceConnection;
124
- const { id: clientUserId } = this.client.user;
125
-
126
- if (!session_id) throw new Error("Session doesn't exist yet");
127
- const streamConn = new StreamConnection(
128
- this,
129
- clientGuildId,
130
- clientUserId,
131
- clientChannelId,
132
- (conn) => {
133
- resolve(conn);
134
- },
135
- );
136
- this.voiceConnection.streamConnection = streamConn;
137
- this._gatewayEmitter.on("STREAM_CREATE", (d) => {
138
- const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
139
-
140
- if (
141
- clientGuildId !== guildId ||
142
- clientChannelId !== channelId ||
143
- clientUserId !== userId
144
- )
145
- return;
146
-
147
- streamConn.serverId = d.rtc_server_id;
148
- streamConn.streamKey = d.stream_key;
149
- streamConn.setSession(session_id);
150
- });
151
- this._gatewayEmitter.on("STREAM_SERVER_UPDATE", (d) => {
152
- const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
153
-
154
- if (
155
- clientGuildId !== guildId ||
156
- clientChannelId !== channelId ||
157
- clientUserId !== userId
158
- )
159
- return;
160
-
161
- streamConn.setTokens(d.endpoint, d.token);
162
- });
163
- });
107
+ public createStream(): Promise<WebRtcConnWrapper> {
108
+ return new Promise<WebRtcConnWrapper>((resolve, reject) => {
109
+ if (!this.client.user) {
110
+ reject("Client not logged in");
111
+ return;
112
+ }
113
+ if (!this.voiceConnection) {
114
+ reject("cannot start stream without first joining voice channel");
115
+ return;
116
+ }
117
+ if (this.voiceConnection.streamConnection?.webRtcConn?.ready) {
118
+ resolve(this.voiceConnection.streamConnection.webRtcConn);
119
+ return;
120
+ }
121
+ const { guildId: clientGuildId, channelId: clientChannelId, session_id } = this.voiceConnection;
122
+ const { id: clientUserId } = this.client.user;
123
+ if (!session_id) throw new Error("Session doesn't exist yet");
124
+
125
+ let done = false;
126
+ const onStreamCreate = (d: any) => {
127
+ const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
128
+ if (clientGuildId !== guildId || clientChannelId !== channelId || clientUserId !== userId) return;
129
+ streamConn.serverId = d.rtc_server_id;
130
+ streamConn.streamKey = d.stream_key;
131
+ streamConn.setSession(session_id);
132
+ };
133
+ const onStreamServerUpdate = (d: any) => {
134
+ const { channelId, guildId, userId } = parseStreamKey(d.stream_key);
135
+ if (clientGuildId !== guildId || clientChannelId !== channelId || clientUserId !== userId) return;
136
+ streamConn.setTokens(d.endpoint, d.token);
137
+ };
138
+ const cleanup = () => {
139
+ if (done) return;
140
+ done = true;
141
+ clearTimeout(timer);
142
+ this._gatewayEmitter.off("STREAM_CREATE", onStreamCreate);
143
+ this._gatewayEmitter.off("STREAM_SERVER_UPDATE", onStreamServerUpdate);
144
+ };
145
+ const timer = setTimeout(() => {
146
+ cleanup();
147
+ reject(new Error("Timeout en attente de la passerelle Discord Go-Live"));
148
+ }, 10000);
149
+
150
+ this.signalStream();
151
+ const streamConn = new StreamConnection(this, clientGuildId, clientUserId, clientChannelId, (conn) => {
152
+ cleanup();
153
+ resolve(conn);
154
+ });
155
+ this.voiceConnection.streamConnection = streamConn;
156
+ this._gatewayEmitter.on("STREAM_CREATE", onStreamCreate);
157
+ this._gatewayEmitter.on("STREAM_SERVER_UPDATE", onStreamServerUpdate);
158
+ });
164
159
  }
165
160
 
166
161
  public async setStreamPreview(image: Buffer): Promise<void> {
@@ -93,7 +93,26 @@ export class WebRtcConnWrapper {
93
93
  return this._webRtcConn;
94
94
  }
95
95
 
96
- public get ready() {
96
+ public waitUntilReady(timeoutMs = 5000) {
97
+ if (this.ready) return Promise.resolve();
98
+ return new Promise((resolve) => {
99
+ let done = false;
100
+ const finish = () => {
101
+ if (!done) {
102
+ done = true;
103
+ clearTimeout(timer);
104
+ resolve();
105
+ }
106
+ };
107
+ const timer = setTimeout(finish, timeoutMs);
108
+ this._webRtcConn?.onStateChange((state) => {
109
+ if (state === "connected") {
110
+ finish();
111
+ }
112
+ });
113
+ });
114
+ }
115
+ get ready() {
97
116
  return this._webRtcConn?.state() === "connected";
98
117
  }
99
118
 
@@ -108,7 +127,7 @@ export class WebRtcConnWrapper {
108
127
  const { clockRate } = rtpConfig;
109
128
  if (this.mediaConnection.daveReady)
110
129
  frame = this.mediaConnection.daveSession!.encryptOpus(frame);
111
- this._audioTrack?.sendMessageBinary(frame);
130
+ try { this._audioTrack?.sendMessageBinary(frame); } catch (e) { /* ignore SRTP/WebRTC drop errors */ }
112
131
  rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
113
132
  }
114
133
 
@@ -154,7 +173,7 @@ export class WebRtcConnWrapper {
154
173
  frame,
155
174
  );
156
175
  }
157
- this._videoTrack?.sendMessageBinary(frame);
176
+ try { this._videoTrack?.sendMessageBinary(frame); } catch (e) { /* ignore SRTP/WebRTC drop errors */ }
158
177
  rtpConfig.timestamp += Math.round((frametime * clockRate) / 1000);
159
178
  }
160
179
 
@@ -492,7 +492,8 @@ export async function playStream(
492
492
  ? opts.readrateInitialBurst
493
493
  : defaultOptions.readrateInitialBurst,
494
494
 
495
- streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
495
+ streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
496
+ keepConnection: opts.keepConnection ?? false,
496
497
  } satisfies PlayStreamOptions;
497
498
  }
498
499
 
@@ -520,11 +521,11 @@ export async function playStream(
520
521
  let stopStream: () => unknown;
521
522
  if (mergedOptions.type === "go-live") {
522
523
  conn = await streamer.createStream();
523
- stopStream = () => streamer.stopStream();
524
+ stopStream = () => { if (!mergedOptions.keepConnection) streamer.stopStream(); };
524
525
  } else {
525
526
  conn = streamer.voiceConnection.webRtcConn;
526
527
  streamer.signalVideo(true);
527
- stopStream = () => streamer.signalVideo(false);
528
+ stopStream = () => { if (!mergedOptions.keepConnection) streamer.signalVideo(false); };
528
529
  }
529
530
  conn.setPacketizer(videoCodecMap[video.codec]);
530
531
  conn.mediaConnection.setSpeaking(true);