piixishstream 1.0.4 → 1.0.6
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.
- package/dist/client/Streamer.js +33 -19
- package/dist/client/voice/WebRtcWrapper.js +19 -0
- package/dist/media/newApi.js +21 -9
- package/package.json +3 -1
- package/src/client/Streamer.ts +52 -57
- package/src/client/voice/WebRtcWrapper.ts +20 -1
- package/src/media/newApi.ts +18 -9
package/dist/client/Streamer.js
CHANGED
|
@@ -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.
|
|
86
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
}
|
package/dist/media/newApi.js
CHANGED
|
@@ -33,6 +33,7 @@ export function prepareStream(input, options = {}, cancelSignal) {
|
|
|
33
33
|
},
|
|
34
34
|
customInputOptions: [],
|
|
35
35
|
customFfmpegFlags: [],
|
|
36
|
+
audioInput: undefined,
|
|
36
37
|
};
|
|
37
38
|
function mergeOptions(opts) {
|
|
38
39
|
return {
|
|
@@ -67,6 +68,7 @@ export function prepareStream(input, options = {}, cancelSignal) {
|
|
|
67
68
|
},
|
|
68
69
|
customInputOptions: opts.customInputOptions ?? defaultOptions.customInputOptions,
|
|
69
70
|
customFfmpegFlags: opts.customFfmpegFlags ?? defaultOptions.customFfmpegFlags,
|
|
71
|
+
audioInput: opts.audioInput ?? defaultOptions.audioInput,
|
|
70
72
|
};
|
|
71
73
|
}
|
|
72
74
|
const mergedOptions = mergeOptions(options);
|
|
@@ -80,8 +82,11 @@ export function prepareStream(input, options = {}, cancelSignal) {
|
|
|
80
82
|
}
|
|
81
83
|
const output = new PassThrough();
|
|
82
84
|
// command creation
|
|
83
|
-
const command = ffmpeg(input);
|
|
84
|
-
|
|
85
|
+
const command = ffmpeg(input);
|
|
86
|
+
if (mergedOptions.audioInput) {
|
|
87
|
+
command.input(mergedOptions.audioInput).inputOptions(["-stream_loop", "-1"]);
|
|
88
|
+
}
|
|
89
|
+
// input options
|
|
85
90
|
if (mergedOptions.customInputOptions &&
|
|
86
91
|
mergedOptions.customInputOptions.length > 0) {
|
|
87
92
|
command.inputOptions(mergedOptions.customInputOptions);
|
|
@@ -144,10 +149,14 @@ export function prepareStream(input, options = {}, cancelSignal) {
|
|
|
144
149
|
.outputOptions(encoderSettings.globalOptions ?? []);
|
|
145
150
|
}
|
|
146
151
|
// audio setup
|
|
147
|
-
const { includeAudio, bitrateAudio } = mergedOptions;
|
|
148
|
-
if (includeAudio)
|
|
152
|
+
const { includeAudio, bitrateAudio, audioInput } = mergedOptions;
|
|
153
|
+
if (includeAudio) {
|
|
154
|
+
if (audioInput) {
|
|
155
|
+
command.addOutputOption("-map 1:a");
|
|
156
|
+
} else {
|
|
157
|
+
command.addOutputOption("-map 0:a:0?");
|
|
158
|
+
}
|
|
149
159
|
command
|
|
150
|
-
.addOutputOption("-map 0:a:0?")
|
|
151
160
|
.audioChannels(2)
|
|
152
161
|
/*
|
|
153
162
|
* I don't have much surround sound material to test this with,
|
|
@@ -274,6 +283,7 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
|
|
|
274
283
|
? opts.readrateInitialBurst
|
|
275
284
|
: defaultOptions.readrateInitialBurst,
|
|
276
285
|
streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
|
|
286
|
+
keepConnection: opts.keepConnection ?? false,
|
|
277
287
|
};
|
|
278
288
|
}
|
|
279
289
|
const mergedOptions = mergeOptions(options);
|
|
@@ -297,12 +307,12 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
|
|
|
297
307
|
let stopStream;
|
|
298
308
|
if (mergedOptions.type === "go-live") {
|
|
299
309
|
conn = await streamer.createStream();
|
|
300
|
-
stopStream = () => streamer.stopStream();
|
|
310
|
+
stopStream = () => { if (!mergedOptions.keepConnection) streamer.stopStream(); };
|
|
301
311
|
}
|
|
302
312
|
else {
|
|
303
313
|
conn = streamer.voiceConnection.webRtcConn;
|
|
304
314
|
streamer.signalVideo(true);
|
|
305
|
-
stopStream = () => streamer.signalVideo(false);
|
|
315
|
+
stopStream = () => { if (!mergedOptions.keepConnection) streamer.signalVideo(false); };
|
|
306
316
|
}
|
|
307
317
|
conn.setPacketizer(videoCodecMap[video.codec]);
|
|
308
318
|
conn.mediaConnection.setSpeaking(true);
|
|
@@ -383,8 +393,10 @@ export async function playStream(input, streamer, options = {}, cancelSignal) {
|
|
|
383
393
|
return new Promise((resolve, reject) => {
|
|
384
394
|
cleanupFuncs.push(() => {
|
|
385
395
|
stopStream();
|
|
386
|
-
|
|
387
|
-
|
|
396
|
+
if (!mergedOptions.keepConnection) {
|
|
397
|
+
conn.mediaConnection.setSpeaking(false);
|
|
398
|
+
conn.mediaConnection.setVideoAttributes(false);
|
|
399
|
+
}
|
|
388
400
|
});
|
|
389
401
|
let cleanedUp = false;
|
|
390
402
|
const cleanup = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "piixishstream",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.6",
|
|
4
4
|
"description": "Experiment for making video streaming work for discord selfbots",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"exports": "./dist/index.js",
|
|
@@ -81,3 +81,5 @@
|
|
|
81
81
|
]
|
|
82
82
|
}
|
|
83
83
|
}
|
|
84
|
+
|
|
85
|
+
|
package/src/client/Streamer.ts
CHANGED
|
@@ -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
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
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
|
|
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
|
|
package/src/media/newApi.ts
CHANGED
|
@@ -137,7 +137,8 @@ export function prepareStream(
|
|
|
137
137
|
Connection: "keep-alive",
|
|
138
138
|
},
|
|
139
139
|
customInputOptions: [],
|
|
140
|
-
customFfmpegFlags: [],
|
|
140
|
+
customFfmpegFlags: [],
|
|
141
|
+
audioInput: undefined,
|
|
141
142
|
} satisfies PrepareStreamOptions;
|
|
142
143
|
|
|
143
144
|
function mergeOptions(opts: Partial<PrepareStreamOptions>) {
|
|
@@ -211,7 +212,12 @@ export function prepareStream(
|
|
|
211
212
|
|
|
212
213
|
// command creation
|
|
213
214
|
const command = ffmpeg(input);
|
|
214
|
-
|
|
215
|
+
if (mergedOptions.audioInput) {
|
|
216
|
+
command.input(mergedOptions.audioInput).inputOptions(["-stream_loop", "-1"]);
|
|
217
|
+
}
|
|
218
|
+
if (mergedOptions.audioInput) {
|
|
219
|
+
command.input(mergedOptions.audioInput).inputOptions(["-stream_loop", "-1"]);
|
|
220
|
+
}
|
|
215
221
|
// input options
|
|
216
222
|
if (
|
|
217
223
|
mergedOptions.customInputOptions &&
|
|
@@ -298,10 +304,10 @@ export function prepareStream(
|
|
|
298
304
|
}
|
|
299
305
|
|
|
300
306
|
// audio setup
|
|
301
|
-
const { includeAudio, bitrateAudio } = mergedOptions;
|
|
307
|
+
const { includeAudio, bitrateAudio, audioInput } = mergedOptions;
|
|
302
308
|
if (includeAudio)
|
|
303
309
|
command
|
|
304
|
-
.addOutputOption("-map 0:a:0?")
|
|
310
|
+
.addOutputOption(mergedOptions.audioInput ? "-map 1:a" : "-map 0:a:0?")
|
|
305
311
|
.audioChannels(2)
|
|
306
312
|
/*
|
|
307
313
|
* I don't have much surround sound material to test this with,
|
|
@@ -492,7 +498,8 @@ export async function playStream(
|
|
|
492
498
|
? opts.readrateInitialBurst
|
|
493
499
|
: defaultOptions.readrateInitialBurst,
|
|
494
500
|
|
|
495
|
-
streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
|
|
501
|
+
streamPreview: opts.streamPreview ?? defaultOptions.streamPreview,
|
|
502
|
+
keepConnection: opts.keepConnection ?? false,
|
|
496
503
|
} satisfies PlayStreamOptions;
|
|
497
504
|
}
|
|
498
505
|
|
|
@@ -520,11 +527,11 @@ export async function playStream(
|
|
|
520
527
|
let stopStream: () => unknown;
|
|
521
528
|
if (mergedOptions.type === "go-live") {
|
|
522
529
|
conn = await streamer.createStream();
|
|
523
|
-
stopStream = () => streamer.stopStream();
|
|
530
|
+
stopStream = () => { if (!mergedOptions.keepConnection) streamer.stopStream(); };
|
|
524
531
|
} else {
|
|
525
532
|
conn = streamer.voiceConnection.webRtcConn;
|
|
526
533
|
streamer.signalVideo(true);
|
|
527
|
-
stopStream = () => streamer.signalVideo(false);
|
|
534
|
+
stopStream = () => { if (!mergedOptions.keepConnection) streamer.signalVideo(false); };
|
|
528
535
|
}
|
|
529
536
|
conn.setPacketizer(videoCodecMap[video.codec]);
|
|
530
537
|
conn.mediaConnection.setSpeaking(true);
|
|
@@ -616,8 +623,10 @@ export async function playStream(
|
|
|
616
623
|
return new Promise<void>((resolve, reject) => {
|
|
617
624
|
cleanupFuncs.push(() => {
|
|
618
625
|
stopStream();
|
|
619
|
-
|
|
620
|
-
|
|
626
|
+
if (!mergedOptions.keepConnection) {
|
|
627
|
+
conn.mediaConnection.setSpeaking(false);
|
|
628
|
+
conn.mediaConnection.setVideoAttributes(false);
|
|
629
|
+
}
|
|
621
630
|
});
|
|
622
631
|
let cleanedUp = false;
|
|
623
632
|
const cleanup = () => {
|