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.
Files changed (123) hide show
  1. package/README.md +124 -2
  2. package/dist/client/GatewayEvents.d.ts +30 -0
  3. package/dist/client/GatewayEvents.js +2 -0
  4. package/dist/client/GatewayEvents.js.map +1 -0
  5. package/dist/client/GatewayOpCodes.d.ts +40 -0
  6. package/dist/client/GatewayOpCodes.js +42 -0
  7. package/dist/client/GatewayOpCodes.js.map +1 -0
  8. package/dist/client/Streamer.d.ts +30 -0
  9. package/dist/client/Streamer.js +188 -0
  10. package/dist/client/Streamer.js.map +1 -0
  11. package/dist/client/index.d.ts +3 -0
  12. package/dist/client/index.js +4 -0
  13. package/dist/client/index.js.map +1 -0
  14. package/dist/client/processing/AnnexBBitstreamReaderWriter.d.ts +23 -0
  15. package/dist/client/processing/AnnexBBitstreamReaderWriter.js +149 -0
  16. package/dist/client/processing/AnnexBBitstreamReaderWriter.js.map +1 -0
  17. package/dist/client/processing/AnnexBHelper.d.ts +93 -0
  18. package/dist/client/processing/AnnexBHelper.js +127 -0
  19. package/dist/client/processing/AnnexBHelper.js.map +1 -0
  20. package/dist/client/processing/SPSVUIRewriter.d.ts +1 -0
  21. package/dist/client/processing/SPSVUIRewriter.js +297 -0
  22. package/dist/client/processing/SPSVUIRewriter.js.map +1 -0
  23. package/dist/client/voice/BaseMediaConnection.d.ts +80 -0
  24. package/dist/client/voice/BaseMediaConnection.js +552 -0
  25. package/dist/client/voice/BaseMediaConnection.js.map +1 -0
  26. package/dist/client/voice/CodecPayloadType.d.ts +59 -0
  27. package/dist/client/voice/CodecPayloadType.js +60 -0
  28. package/dist/client/voice/CodecPayloadType.js.map +1 -0
  29. package/dist/client/voice/StreamConnection.d.ts +11 -0
  30. package/dist/client/voice/StreamConnection.js +34 -0
  31. package/dist/client/voice/StreamConnection.js.map +1 -0
  32. package/dist/client/voice/VoiceConnection.d.ts +8 -0
  33. package/dist/client/voice/VoiceConnection.js +15 -0
  34. package/dist/client/voice/VoiceConnection.js.map +1 -0
  35. package/dist/client/voice/VoiceMessageTypes.d.ts +178 -0
  36. package/dist/client/voice/VoiceMessageTypes.js +2 -0
  37. package/dist/client/voice/VoiceMessageTypes.js.map +1 -0
  38. package/dist/client/voice/VoiceOpCodes.d.ts +35 -0
  39. package/dist/client/voice/VoiceOpCodes.js +38 -0
  40. package/dist/client/voice/VoiceOpCodes.js.map +1 -0
  41. package/dist/client/voice/WebRtcWrapper.d.ts +23 -0
  42. package/dist/client/voice/WebRtcWrapper.js +159 -0
  43. package/dist/client/voice/WebRtcWrapper.js.map +1 -0
  44. package/dist/client/voice/index.d.ts +4 -0
  45. package/dist/client/voice/index.js +6 -0
  46. package/dist/client/voice/index.js.map +1 -0
  47. package/dist/index.d.ts +3 -0
  48. package/dist/index.js +4 -0
  49. package/dist/index.js.map +1 -0
  50. package/dist/media/AudioStream.d.ts +7 -0
  51. package/dist/media/AudioStream.js +12 -0
  52. package/dist/media/AudioStream.js.map +1 -0
  53. package/dist/media/BaseMediaStream.d.ts +31 -0
  54. package/dist/media/BaseMediaStream.js +164 -0
  55. package/dist/media/BaseMediaStream.js.map +1 -0
  56. package/dist/media/LibavCodecId.d.ts +541 -0
  57. package/dist/media/LibavCodecId.js +553 -0
  58. package/dist/media/LibavCodecId.js.map +1 -0
  59. package/dist/media/LibavDecoder.d.ts +5 -0
  60. package/dist/media/LibavDecoder.js +43 -0
  61. package/dist/media/LibavDecoder.js.map +1 -0
  62. package/dist/media/LibavDemuxer.d.ts +44 -0
  63. package/dist/media/LibavDemuxer.js +231 -0
  64. package/dist/media/LibavDemuxer.js.map +1 -0
  65. package/dist/media/VideoStream.d.ts +7 -0
  66. package/dist/media/VideoStream.js +12 -0
  67. package/dist/media/VideoStream.js.map +1 -0
  68. package/dist/media/encoders/index.d.ts +26 -0
  69. package/dist/media/encoders/index.js +12 -0
  70. package/dist/media/encoders/index.js.map +1 -0
  71. package/dist/media/encoders/merge.d.ts +3 -0
  72. package/dist/media/encoders/merge.js +10 -0
  73. package/dist/media/encoders/merge.js.map +1 -0
  74. package/dist/media/encoders/nvenc.d.ts +10 -0
  75. package/dist/media/encoders/nvenc.js +23 -0
  76. package/dist/media/encoders/nvenc.js.map +1 -0
  77. package/dist/media/encoders/software.d.ts +17 -0
  78. package/dist/media/encoders/software.js +31 -0
  79. package/dist/media/encoders/software.js.map +1 -0
  80. package/dist/media/encoders/vaapi.d.ts +6 -0
  81. package/dist/media/encoders/vaapi.js +22 -0
  82. package/dist/media/encoders/vaapi.js.map +1 -0
  83. package/dist/media/index.d.ts +4 -0
  84. package/dist/media/index.js +5 -0
  85. package/dist/media/index.js.map +1 -0
  86. package/dist/media/newApi.d.ts +130 -0
  87. package/dist/media/newApi.js +409 -0
  88. package/dist/media/newApi.js.map +1 -0
  89. package/dist/utils.d.ts +25 -0
  90. package/dist/utils.js +65 -0
  91. package/dist/utils.js.map +1 -0
  92. package/package.json +82 -5
  93. package/src/client/GatewayEvents.ts +48 -0
  94. package/src/client/GatewayOpCodes.ts +40 -0
  95. package/src/client/Streamer.ts +259 -0
  96. package/src/client/index.ts +3 -0
  97. package/src/client/processing/AnnexBBitstreamReaderWriter.ts +148 -0
  98. package/src/client/processing/AnnexBHelper.ts +134 -0
  99. package/src/client/processing/SPSVUIRewriter.ts +332 -0
  100. package/src/client/voice/BaseMediaConnection.ts +639 -0
  101. package/src/client/voice/CodecPayloadType.ts +59 -0
  102. package/src/client/voice/MediaUdp.ts.bak +171 -0
  103. package/src/client/voice/StreamConnection.ts +38 -0
  104. package/src/client/voice/VoiceConnection.ts +19 -0
  105. package/src/client/voice/VoiceMessageTypes.ts +263 -0
  106. package/src/client/voice/VoiceOpCodes.ts +36 -0
  107. package/src/client/voice/WebRtcWrapper.ts +218 -0
  108. package/src/client/voice/index.ts +5 -0
  109. package/src/index.ts +3 -0
  110. package/src/media/AudioStream.ts +18 -0
  111. package/src/media/BaseMediaStream.ts +198 -0
  112. package/src/media/LibavCodecId.ts +564 -0
  113. package/src/media/LibavDecoder.ts +47 -0
  114. package/src/media/LibavDemuxer.ts +293 -0
  115. package/src/media/VideoStream.ts +17 -0
  116. package/src/media/encoders/index.ts +27 -0
  117. package/src/media/encoders/merge.ts +14 -0
  118. package/src/media/encoders/nvenc.ts +38 -0
  119. package/src/media/encoders/software.ts +76 -0
  120. package/src/media/encoders/vaapi.ts +29 -0
  121. package/src/media/index.ts +4 -0
  122. package/src/media/newApi.ts +642 -0
  123. package/src/utils.ts +101 -0
package/package.json CHANGED
@@ -1,6 +1,83 @@
1
- {
2
- "name": "piixishstream",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
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,3 @@
1
+ export * from "./voice/index.js";
2
+ export * from "./GatewayOpCodes.js";
3
+ export * from "./Streamer.js";
@@ -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
+ }