piixishstream 0.0.0-stage → 1.0.2
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/README.md +124 -2
- package/dist/client/GatewayEvents.d.ts +30 -0
- package/dist/client/GatewayEvents.js +2 -0
- package/dist/client/GatewayEvents.js.map +1 -0
- package/dist/client/GatewayOpCodes.d.ts +40 -0
- package/dist/client/GatewayOpCodes.js +42 -0
- package/dist/client/GatewayOpCodes.js.map +1 -0
- package/dist/client/Streamer.d.ts +30 -0
- package/dist/client/Streamer.js +188 -0
- package/dist/client/Streamer.js.map +1 -0
- package/dist/client/index.d.ts +3 -0
- package/dist/client/index.js +4 -0
- package/dist/client/index.js.map +1 -0
- package/dist/client/processing/AnnexBBitstreamReaderWriter.d.ts +23 -0
- package/dist/client/processing/AnnexBBitstreamReaderWriter.js +149 -0
- package/dist/client/processing/AnnexBBitstreamReaderWriter.js.map +1 -0
- package/dist/client/processing/AnnexBHelper.d.ts +93 -0
- package/dist/client/processing/AnnexBHelper.js +127 -0
- package/dist/client/processing/AnnexBHelper.js.map +1 -0
- package/dist/client/processing/SPSVUIRewriter.d.ts +1 -0
- package/dist/client/processing/SPSVUIRewriter.js +297 -0
- package/dist/client/processing/SPSVUIRewriter.js.map +1 -0
- package/dist/client/voice/BaseMediaConnection.d.ts +80 -0
- package/dist/client/voice/BaseMediaConnection.js +552 -0
- package/dist/client/voice/BaseMediaConnection.js.map +1 -0
- package/dist/client/voice/CodecPayloadType.d.ts +59 -0
- package/dist/client/voice/CodecPayloadType.js +60 -0
- package/dist/client/voice/CodecPayloadType.js.map +1 -0
- package/dist/client/voice/StreamConnection.d.ts +11 -0
- package/dist/client/voice/StreamConnection.js +34 -0
- package/dist/client/voice/StreamConnection.js.map +1 -0
- package/dist/client/voice/VoiceConnection.d.ts +8 -0
- package/dist/client/voice/VoiceConnection.js +15 -0
- package/dist/client/voice/VoiceConnection.js.map +1 -0
- package/dist/client/voice/VoiceMessageTypes.d.ts +178 -0
- package/dist/client/voice/VoiceMessageTypes.js +2 -0
- package/dist/client/voice/VoiceMessageTypes.js.map +1 -0
- package/dist/client/voice/VoiceOpCodes.d.ts +35 -0
- package/dist/client/voice/VoiceOpCodes.js +38 -0
- package/dist/client/voice/VoiceOpCodes.js.map +1 -0
- package/dist/client/voice/WebRtcWrapper.d.ts +23 -0
- package/dist/client/voice/WebRtcWrapper.js +159 -0
- package/dist/client/voice/WebRtcWrapper.js.map +1 -0
- package/dist/client/voice/index.d.ts +4 -0
- package/dist/client/voice/index.js +6 -0
- package/dist/client/voice/index.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/media/AudioStream.d.ts +7 -0
- package/dist/media/AudioStream.js +12 -0
- package/dist/media/AudioStream.js.map +1 -0
- package/dist/media/BaseMediaStream.d.ts +31 -0
- package/dist/media/BaseMediaStream.js +164 -0
- package/dist/media/BaseMediaStream.js.map +1 -0
- package/dist/media/LibavCodecId.d.ts +541 -0
- package/dist/media/LibavCodecId.js +553 -0
- package/dist/media/LibavCodecId.js.map +1 -0
- package/dist/media/LibavDecoder.d.ts +5 -0
- package/dist/media/LibavDecoder.js +43 -0
- package/dist/media/LibavDecoder.js.map +1 -0
- package/dist/media/LibavDemuxer.d.ts +44 -0
- package/dist/media/LibavDemuxer.js +231 -0
- package/dist/media/LibavDemuxer.js.map +1 -0
- package/dist/media/VideoStream.d.ts +7 -0
- package/dist/media/VideoStream.js +12 -0
- package/dist/media/VideoStream.js.map +1 -0
- package/dist/media/encoders/index.d.ts +26 -0
- package/dist/media/encoders/index.js +12 -0
- package/dist/media/encoders/index.js.map +1 -0
- package/dist/media/encoders/merge.d.ts +3 -0
- package/dist/media/encoders/merge.js +10 -0
- package/dist/media/encoders/merge.js.map +1 -0
- package/dist/media/encoders/nvenc.d.ts +10 -0
- package/dist/media/encoders/nvenc.js +23 -0
- package/dist/media/encoders/nvenc.js.map +1 -0
- package/dist/media/encoders/software.d.ts +17 -0
- package/dist/media/encoders/software.js +31 -0
- package/dist/media/encoders/software.js.map +1 -0
- package/dist/media/encoders/vaapi.d.ts +6 -0
- package/dist/media/encoders/vaapi.js +22 -0
- package/dist/media/encoders/vaapi.js.map +1 -0
- package/dist/media/index.d.ts +4 -0
- package/dist/media/index.js +5 -0
- package/dist/media/index.js.map +1 -0
- package/dist/media/newApi.d.ts +130 -0
- package/dist/media/newApi.js +409 -0
- package/dist/media/newApi.js.map +1 -0
- package/dist/utils.d.ts +25 -0
- package/dist/utils.js +65 -0
- package/dist/utils.js.map +1 -0
- package/package.json +82 -5
- package/src/client/GatewayEvents.ts +48 -0
- package/src/client/GatewayOpCodes.ts +40 -0
- package/src/client/Streamer.ts +259 -0
- package/src/client/index.ts +3 -0
- package/src/client/processing/AnnexBBitstreamReaderWriter.ts +148 -0
- package/src/client/processing/AnnexBHelper.ts +134 -0
- package/src/client/processing/SPSVUIRewriter.ts +332 -0
- package/src/client/voice/BaseMediaConnection.ts +639 -0
- package/src/client/voice/CodecPayloadType.ts +59 -0
- package/src/client/voice/MediaUdp.ts.bak +171 -0
- package/src/client/voice/StreamConnection.ts +38 -0
- package/src/client/voice/VoiceConnection.ts +19 -0
- package/src/client/voice/VoiceMessageTypes.ts +263 -0
- package/src/client/voice/VoiceOpCodes.ts +36 -0
- package/src/client/voice/WebRtcWrapper.ts +218 -0
- package/src/client/voice/index.ts +5 -0
- package/src/index.ts +3 -0
- package/src/media/AudioStream.ts +18 -0
- package/src/media/BaseMediaStream.ts +198 -0
- package/src/media/LibavCodecId.ts +564 -0
- package/src/media/LibavDecoder.ts +47 -0
- package/src/media/LibavDemuxer.ts +293 -0
- package/src/media/VideoStream.ts +17 -0
- package/src/media/encoders/index.ts +27 -0
- package/src/media/encoders/merge.ts +14 -0
- package/src/media/encoders/nvenc.ts +38 -0
- package/src/media/encoders/software.ts +76 -0
- package/src/media/encoders/vaapi.ts +29 -0
- package/src/media/index.ts +4 -0
- package/src/media/newApi.ts +642 -0
- package/src/utils.ts +101 -0
package/package.json
CHANGED
|
@@ -1,6 +1,83 @@
|
|
|
1
|
-
{
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "piixishstream",
|
|
3
|
+
"version": "1.0.2",
|
|
4
|
+
"description": "Experiment for making video streaming work for discord selfbots",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"exports": "./dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"type": "module",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@lng2004/node-datachannel": "0.32.0-20260202",
|
|
18
|
+
"@snazzah/davey": "^0.1.8",
|
|
19
|
+
"debug-level": "^4.1.1",
|
|
20
|
+
"fluent-ffmpeg": "^2.1.3",
|
|
21
|
+
"node-av": "^5.2.2",
|
|
22
|
+
"p-debounce": "^5.1.0",
|
|
23
|
+
"sharp": "^0.34.5",
|
|
24
|
+
"zeromq": "^6.5.0"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@biomejs/biome": "2.3.8",
|
|
28
|
+
"@types/fluent-ffmpeg": "^2.1.28",
|
|
29
|
+
"@types/node": "^25.0.1",
|
|
30
|
+
"discord.js-selfbot-v13": "^3.7.1",
|
|
31
|
+
"pkg-pr-new": "^0.0.62",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
|
+
},
|
|
34
|
+
"peerDependencies": {
|
|
35
|
+
"discord.js-selfbot-v13": "^3.6.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=22.4.0"
|
|
39
|
+
},
|
|
40
|
+
"scripts": {
|
|
41
|
+
"build": "tsc",
|
|
42
|
+
"lint": "biome lint --error-on-warnings .",
|
|
43
|
+
"lint:fix": "biome lint --write .",
|
|
44
|
+
"check": "biome check .",
|
|
45
|
+
"check:fix": "biome check --write ."
|
|
46
|
+
},
|
|
47
|
+
"keywords": [
|
|
48
|
+
"discord",
|
|
49
|
+
"video",
|
|
50
|
+
"voice",
|
|
51
|
+
"stream",
|
|
52
|
+
"go-live",
|
|
53
|
+
"webrtc",
|
|
54
|
+
"dave",
|
|
55
|
+
"selfbot",
|
|
56
|
+
"performance",
|
|
57
|
+
"60fps"
|
|
58
|
+
],
|
|
59
|
+
"repository": {
|
|
60
|
+
"type": "git",
|
|
61
|
+
"url": "git+https://github.com/dank074/Discord-video-stream.git"
|
|
62
|
+
},
|
|
63
|
+
"contributors": [
|
|
64
|
+
"Piixish",
|
|
65
|
+
"Long Nguyen <nguyen.long.908132@gmail.com>",
|
|
66
|
+
"s074 <torresefrain10@gmail.com>",
|
|
67
|
+
"mrjvs <jellevs@gmail.com>",
|
|
68
|
+
"Elysia <71698422+aiko-chan-ai@users.noreply.github.com>",
|
|
69
|
+
"Fede14 <fede.ferri2001@gmail.com>",
|
|
70
|
+
"Malthe Morsing Larsen <57196060+malthemorsing@users.noreply.github.com>"
|
|
71
|
+
],
|
|
72
|
+
"license": "ISC",
|
|
73
|
+
"bugs": {
|
|
74
|
+
"url": "https://github.com/dank074/Discord-video-stream/issues"
|
|
75
|
+
},
|
|
76
|
+
"homepage": "https://github.com/dank074/Discord-video-stream#readme",
|
|
77
|
+
"pnpm": {
|
|
78
|
+
"onlyBuiltDependencies": [
|
|
79
|
+
"@lng2004/node-datachannel",
|
|
80
|
+
"node-datachannel"
|
|
81
|
+
]
|
|
82
|
+
}
|
|
6
83
|
}
|
|
@@ -0,0 +1,48 @@
|
|
|
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
|
+
{
|
|
10
|
+
user_id: string;
|
|
11
|
+
session_id: string;
|
|
12
|
+
}
|
|
13
|
+
>;
|
|
14
|
+
export type VoiceServerUpdate = GatewayEventGeneric<
|
|
15
|
+
"VOICE_SERVER_UPDATE",
|
|
16
|
+
{
|
|
17
|
+
guild_id: string;
|
|
18
|
+
channel_id?: string;
|
|
19
|
+
endpoint: string;
|
|
20
|
+
token: string;
|
|
21
|
+
}
|
|
22
|
+
>;
|
|
23
|
+
export type StreamCreate = GatewayEventGeneric<
|
|
24
|
+
"STREAM_CREATE",
|
|
25
|
+
{
|
|
26
|
+
stream_key: string;
|
|
27
|
+
rtc_server_id: string;
|
|
28
|
+
}
|
|
29
|
+
>;
|
|
30
|
+
export type StreamServerUpdate = GatewayEventGeneric<
|
|
31
|
+
"STREAM_SERVER_UPDATE",
|
|
32
|
+
{
|
|
33
|
+
stream_key: string;
|
|
34
|
+
endpoint: string;
|
|
35
|
+
token: string;
|
|
36
|
+
}
|
|
37
|
+
>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export type GatewayEvent =
|
|
41
|
+
| GatewayEvent.VoiceStateUpdate
|
|
42
|
+
| GatewayEvent.VoiceServerUpdate
|
|
43
|
+
| GatewayEvent.StreamCreate
|
|
44
|
+
| GatewayEvent.StreamServerUpdate;
|
|
45
|
+
|
|
46
|
+
export type GatewayEventMap = {
|
|
47
|
+
[E in GatewayEvent as E["t"]]: [E["d"]];
|
|
48
|
+
};
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { EventEmitter } from "node:events";
|
|
2
|
+
import { VoiceConnection } from "./voice/VoiceConnection.js";
|
|
3
|
+
import { StreamConnection } from "./voice/StreamConnection.js";
|
|
4
|
+
import { GatewayOpCodes } from "./GatewayOpCodes.js";
|
|
5
|
+
import type {
|
|
6
|
+
Client,
|
|
7
|
+
DMChannel,
|
|
8
|
+
GroupDMChannel,
|
|
9
|
+
VoiceBasedChannel,
|
|
10
|
+
} from "discord.js-selfbot-v13";
|
|
11
|
+
import type { GatewayEvent, GatewayEventMap } from "./GatewayEvents.js";
|
|
12
|
+
import type { WebRtcConnWrapper } from "./voice/WebRtcWrapper.js";
|
|
13
|
+
import { generateStreamKey, parseStreamKey } from "../utils.js";
|
|
14
|
+
|
|
15
|
+
export class Streamer {
|
|
16
|
+
private _voiceConnection?: VoiceConnection;
|
|
17
|
+
private _client: Client;
|
|
18
|
+
private _gatewayEmitter = new EventEmitter<GatewayEventMap>();
|
|
19
|
+
|
|
20
|
+
constructor(client: Client) {
|
|
21
|
+
this._client = client;
|
|
22
|
+
|
|
23
|
+
//listen for messages
|
|
24
|
+
this.client.on("raw", (packet: GatewayEvent) => {
|
|
25
|
+
// @ts-expect-error I don't know how to make this work with TypeScript, so whatever
|
|
26
|
+
this._gatewayEmitter.emit(packet.t, packet.d);
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
public get client(): Client {
|
|
31
|
+
return this._client;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
public get opts() {
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
public get voiceConnection(): VoiceConnection | undefined {
|
|
39
|
+
return this._voiceConnection;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
public sendOpcode(code: number, data: unknown): void {
|
|
43
|
+
this.client.ws.broadcast({
|
|
44
|
+
op: code,
|
|
45
|
+
d: data,
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
public joinVoiceChannel(
|
|
50
|
+
channel: DMChannel | GroupDMChannel | VoiceBasedChannel,
|
|
51
|
+
): Promise<WebRtcConnWrapper> {
|
|
52
|
+
let guildId: string | null = null;
|
|
53
|
+
|
|
54
|
+
if (
|
|
55
|
+
channel.type === "GUILD_STAGE_VOICE" ||
|
|
56
|
+
channel.type === "GUILD_VOICE"
|
|
57
|
+
) {
|
|
58
|
+
guildId = channel.guildId;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
return this.joinVoice(guildId, channel.id);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Joins a voice channel and returns a WebRtcConnWrapper object.
|
|
66
|
+
* @param guild_id the guild id of the voice channel. If null, it will join a DM voice channel.
|
|
67
|
+
* @param channel_id the channel id of the voice channel
|
|
68
|
+
* @returns the WebRtcConnWrapper object
|
|
69
|
+
* @throws Error if the client is not logged in
|
|
70
|
+
*/
|
|
71
|
+
public joinVoice(
|
|
72
|
+
guild_id: string | null,
|
|
73
|
+
channel_id: string,
|
|
74
|
+
): Promise<WebRtcConnWrapper> {
|
|
75
|
+
return new Promise<WebRtcConnWrapper>((resolve, reject) => {
|
|
76
|
+
if (!this.client.user) {
|
|
77
|
+
reject("Client not logged in");
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
const user_id = this.client.user.id;
|
|
81
|
+
const voiceConn = new VoiceConnection(
|
|
82
|
+
this,
|
|
83
|
+
guild_id,
|
|
84
|
+
user_id,
|
|
85
|
+
channel_id,
|
|
86
|
+
(conn) => {
|
|
87
|
+
resolve(conn);
|
|
88
|
+
},
|
|
89
|
+
);
|
|
90
|
+
this._voiceConnection = voiceConn;
|
|
91
|
+
this._gatewayEmitter.on("VOICE_STATE_UPDATE", (d) => {
|
|
92
|
+
if (user_id !== d.user_id) return;
|
|
93
|
+
voiceConn.setSession(d.session_id);
|
|
94
|
+
});
|
|
95
|
+
this._gatewayEmitter.on("VOICE_SERVER_UPDATE", (d) => {
|
|
96
|
+
if (guild_id !== d.guild_id) return;
|
|
97
|
+
|
|
98
|
+
// channel_id is not set for guild voice calls
|
|
99
|
+
if (d.channel_id && channel_id !== d.channel_id) return;
|
|
100
|
+
|
|
101
|
+
voiceConn.setTokens(d.endpoint, d.token);
|
|
102
|
+
});
|
|
103
|
+
this.signalVideo(false);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
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
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
public async setStreamPreview(image: Buffer): Promise<void> {
|
|
167
|
+
if (!this.client.token) throw new Error("Please login :)");
|
|
168
|
+
if (!this.voiceConnection?.streamConnection?.guildId) return;
|
|
169
|
+
const data = `data:image/jpeg;base64,${image.toString("base64")}`;
|
|
170
|
+
const { guildId } = this.voiceConnection.streamConnection;
|
|
171
|
+
const server = await this.client.guilds.fetch(guildId);
|
|
172
|
+
await server.members.me?.voice.postPreview(data);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
public stopStream(): void {
|
|
176
|
+
const stream = this.voiceConnection?.streamConnection;
|
|
177
|
+
|
|
178
|
+
if (!stream) return;
|
|
179
|
+
|
|
180
|
+
stream.stop();
|
|
181
|
+
|
|
182
|
+
this.signalStopStream();
|
|
183
|
+
|
|
184
|
+
this.voiceConnection.streamConnection = undefined;
|
|
185
|
+
this._gatewayEmitter.removeAllListeners("STREAM_CREATE");
|
|
186
|
+
this._gatewayEmitter.removeAllListeners("STREAM_SERVER_UPDATE");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
public leaveVoice(): void {
|
|
190
|
+
this.voiceConnection?.stop();
|
|
191
|
+
|
|
192
|
+
this.signalLeaveVoice();
|
|
193
|
+
|
|
194
|
+
this._voiceConnection = undefined;
|
|
195
|
+
this._gatewayEmitter.removeAllListeners("VOICE_STATE_UPDATE");
|
|
196
|
+
this._gatewayEmitter.removeAllListeners("VOICE_SERVER_UPDATE");
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
public signalVideo(video_enabled: boolean): void {
|
|
200
|
+
if (!this.voiceConnection) return;
|
|
201
|
+
const { guildId: guild_id, channelId: channel_id } = this.voiceConnection;
|
|
202
|
+
this.sendOpcode(GatewayOpCodes.VOICE_STATE_UPDATE, {
|
|
203
|
+
guild_id: guild_id,
|
|
204
|
+
channel_id,
|
|
205
|
+
self_mute: false,
|
|
206
|
+
self_deaf: true,
|
|
207
|
+
self_video: video_enabled,
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
public signalStream(): void {
|
|
212
|
+
if (!this.voiceConnection) return;
|
|
213
|
+
const {
|
|
214
|
+
type,
|
|
215
|
+
guildId: guild_id,
|
|
216
|
+
channelId: channel_id,
|
|
217
|
+
botId: user_id,
|
|
218
|
+
} = this.voiceConnection;
|
|
219
|
+
|
|
220
|
+
const streamKey = generateStreamKey(type, guild_id, channel_id, user_id);
|
|
221
|
+
|
|
222
|
+
this.sendOpcode(GatewayOpCodes.STREAM_CREATE, {
|
|
223
|
+
type,
|
|
224
|
+
guild_id,
|
|
225
|
+
channel_id,
|
|
226
|
+
preferred_region: null,
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
this.sendOpcode(GatewayOpCodes.STREAM_SET_PAUSED, {
|
|
230
|
+
stream_key: streamKey,
|
|
231
|
+
paused: false,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
public signalStopStream(): void {
|
|
236
|
+
if (!this.voiceConnection) return;
|
|
237
|
+
const {
|
|
238
|
+
type,
|
|
239
|
+
guildId: guild_id,
|
|
240
|
+
channelId: channel_id,
|
|
241
|
+
botId: user_id,
|
|
242
|
+
} = this.voiceConnection;
|
|
243
|
+
|
|
244
|
+
const streamKey = generateStreamKey(type, guild_id, channel_id, user_id);
|
|
245
|
+
this.sendOpcode(GatewayOpCodes.STREAM_DELETE, {
|
|
246
|
+
stream_key: streamKey,
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
public signalLeaveVoice(): void {
|
|
251
|
+
this.sendOpcode(GatewayOpCodes.VOICE_STATE_UPDATE, {
|
|
252
|
+
guild_id: null,
|
|
253
|
+
channel_id: null,
|
|
254
|
+
self_mute: true,
|
|
255
|
+
self_deaf: false,
|
|
256
|
+
self_video: false,
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
}
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
export class AnnexBBitstreamReader {
|
|
2
|
+
private _buffer: Buffer;
|
|
3
|
+
private _byteOffset = 0;
|
|
4
|
+
private _bitOffset = 0;
|
|
5
|
+
|
|
6
|
+
constructor(buffer: Buffer) {
|
|
7
|
+
this._buffer = buffer;
|
|
8
|
+
}
|
|
9
|
+
public readBits(count: number) {
|
|
10
|
+
if (count === 0) return 0;
|
|
11
|
+
let result = 0;
|
|
12
|
+
while (count > 0) {
|
|
13
|
+
if (this._byteOffset >= this._buffer.length)
|
|
14
|
+
throw new Error("Bad byte offset");
|
|
15
|
+
if (
|
|
16
|
+
this._bitOffset === 0 &&
|
|
17
|
+
this._byteOffset >= 2 &&
|
|
18
|
+
this._buffer[this._byteOffset - 2] === 0 &&
|
|
19
|
+
this._buffer[this._byteOffset - 1] === 0 &&
|
|
20
|
+
this._buffer[this._byteOffset] === 3
|
|
21
|
+
) {
|
|
22
|
+
// Skip over emulation prevention
|
|
23
|
+
this._byteOffset++;
|
|
24
|
+
}
|
|
25
|
+
if (this._bitOffset === 0 && count >= 8) {
|
|
26
|
+
// We're byte aligned, read whole bytes and push in
|
|
27
|
+
result = (result << 8) | this._buffer[this._byteOffset++];
|
|
28
|
+
count -= 8;
|
|
29
|
+
} else {
|
|
30
|
+
// Read just enough to get us to the next byte
|
|
31
|
+
const numBitsToRead = Math.min(count, 8 - this._bitOffset);
|
|
32
|
+
const mask = (1 << numBitsToRead) - 1;
|
|
33
|
+
const newBits =
|
|
34
|
+
(this._buffer[this._byteOffset] >>
|
|
35
|
+
(8 - this._bitOffset - numBitsToRead)) &
|
|
36
|
+
mask;
|
|
37
|
+
result = (result << numBitsToRead) | newBits;
|
|
38
|
+
count -= numBitsToRead;
|
|
39
|
+
this._bitOffset += numBitsToRead;
|
|
40
|
+
if (this._bitOffset === 8) {
|
|
41
|
+
this._bitOffset = 0;
|
|
42
|
+
this._byteOffset++;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return result;
|
|
47
|
+
}
|
|
48
|
+
public readUnsigned(bits: number) {
|
|
49
|
+
return this.readBits(bits);
|
|
50
|
+
}
|
|
51
|
+
public readSigned(bits: number) {
|
|
52
|
+
const unsigned = this.readUnsigned(bits);
|
|
53
|
+
if (unsigned & (1 << (bits - 1))) return unsigned - (1 << bits);
|
|
54
|
+
return unsigned;
|
|
55
|
+
}
|
|
56
|
+
public readUnsignedExpGolomb() {
|
|
57
|
+
let leading0 = 0;
|
|
58
|
+
while (this.readBits(1) === 0) leading0++;
|
|
59
|
+
|
|
60
|
+
return (1 << leading0) + this.readBits(leading0) - 1;
|
|
61
|
+
}
|
|
62
|
+
public readSignedExpGolomb() {
|
|
63
|
+
// Mapping: x <= 0 => -2x, x > 0 => 2x - 1
|
|
64
|
+
const unsigned = this.readUnsignedExpGolomb();
|
|
65
|
+
if (unsigned % 2 === 0) return unsigned / -2;
|
|
66
|
+
return (unsigned + 1) / 2;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export class AnnexBBitstreamWriter {
|
|
71
|
+
private _arr: number[] = [];
|
|
72
|
+
private _pendingByte = 0;
|
|
73
|
+
private _bitOffset = 0;
|
|
74
|
+
|
|
75
|
+
public toBuffer() {
|
|
76
|
+
return Buffer.from(this._arr);
|
|
77
|
+
}
|
|
78
|
+
public flush() {
|
|
79
|
+
// Write the pending byte into the array and reset, taking care of emulation prevention
|
|
80
|
+
if (
|
|
81
|
+
this._pendingByte <= 3 &&
|
|
82
|
+
this._arr.at(-1) === 0 &&
|
|
83
|
+
this._arr.at(-2) === 0
|
|
84
|
+
)
|
|
85
|
+
this._arr.push(3);
|
|
86
|
+
this._arr.push(this._pendingByte);
|
|
87
|
+
this._pendingByte = 0;
|
|
88
|
+
this._bitOffset = 0;
|
|
89
|
+
}
|
|
90
|
+
public writeBits(bits: number, count: number) {
|
|
91
|
+
while (count > 0) {
|
|
92
|
+
if (this._bitOffset === 0) {
|
|
93
|
+
if (count >= 8) {
|
|
94
|
+
// We're byte aligned and has more than 1 byte left to write, write a whole byte
|
|
95
|
+
this._pendingByte = (bits >> (count - 8)) & 0xff;
|
|
96
|
+
count -= 8;
|
|
97
|
+
this.flush();
|
|
98
|
+
} else {
|
|
99
|
+
// We have less than 1 byte, write the rest in
|
|
100
|
+
const mask = (1 << count) - 1;
|
|
101
|
+
this._pendingByte |= (bits & mask) << (8 - count);
|
|
102
|
+
this._bitOffset = count;
|
|
103
|
+
count = 0;
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
// Write the minimum number of bits to get us byte aligned again
|
|
107
|
+
const numBitsToWrite = Math.min(8 - this._bitOffset, count);
|
|
108
|
+
const bitsToWrite =
|
|
109
|
+
(bits >> (count - numBitsToWrite)) & ((1 << numBitsToWrite) - 1);
|
|
110
|
+
this._pendingByte |=
|
|
111
|
+
bitsToWrite << (8 - this._bitOffset - numBitsToWrite);
|
|
112
|
+
count -= numBitsToWrite;
|
|
113
|
+
this._bitOffset += numBitsToWrite;
|
|
114
|
+
if (this._bitOffset === 8) {
|
|
115
|
+
this._bitOffset = 0;
|
|
116
|
+
this.flush();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
public writeUnsigned(num: number, count: number) {
|
|
122
|
+
if (num < 0) throw new Error("Expected a non-negative number");
|
|
123
|
+
this.writeBits(num, count);
|
|
124
|
+
}
|
|
125
|
+
public writeSigned(num: number, count: number) {
|
|
126
|
+
if (count <= 0) return;
|
|
127
|
+
if (count > 32) throw new Error("writeSigned supports up to 32 bits");
|
|
128
|
+
|
|
129
|
+
// Build mask for `count` bits. Handle 32-bit as a special case.
|
|
130
|
+
const mask =
|
|
131
|
+
count === 32 ? 0xffffffff >>> 0 : (((1 << count) >>> 0) - 1) >>> 0;
|
|
132
|
+
|
|
133
|
+
// Convert to two's-complement unsigned representation and write
|
|
134
|
+
const unsigned = (num & mask) >>> 0;
|
|
135
|
+
this.writeBits(unsigned, count);
|
|
136
|
+
}
|
|
137
|
+
public writeUnsignedExpGolomb(num: number) {
|
|
138
|
+
if (num < 0) throw new Error("Expected a non-negative number");
|
|
139
|
+
num++;
|
|
140
|
+
const bitCount = 32 - Math.clz32(num >>> 0);
|
|
141
|
+
this.writeBits(0, bitCount - 1);
|
|
142
|
+
this.writeBits(num, bitCount);
|
|
143
|
+
}
|
|
144
|
+
public writeSignedExpGolomb(num: number) {
|
|
145
|
+
if (num < 0) this.writeUnsignedExpGolomb(-2 * num);
|
|
146
|
+
else this.writeUnsignedExpGolomb(2 * num - 1);
|
|
147
|
+
}
|
|
148
|
+
}
|