@spacebarchat/pion-webrtc 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.
@@ -0,0 +1,133 @@
1
+ import { ClientEmitter, SSRCs, VideoStream, WebRtcClient } from "@spacebarchat/spacebar-webrtc-types";
2
+ import EventEmitter from "node:events";
3
+ import { VoiceRoom } from "./VoiceRoom.js";
4
+ import { randomUUID } from "node:crypto";
5
+
6
+ export class PionWebRtcClient implements WebRtcClient<any> {
7
+ websocket: any;
8
+ user_id: string;
9
+ voiceRoomId: string;
10
+ webrtcConnected: boolean;
11
+ emitter: ClientEmitter;
12
+
13
+ public room?: VoiceRoom;
14
+ public isStopped?: boolean;
15
+ public incomingSSRCS?: SSRCs;
16
+ public outgoingSSRCS?: Map<string, SSRCs>;
17
+ public uniqueId?: string;
18
+
19
+ constructor(
20
+ userId: string,
21
+ roomId: string,
22
+ websocket: any,
23
+ room: VoiceRoom,
24
+ ) {
25
+ this.user_id = userId;
26
+ this.voiceRoomId = roomId;
27
+ this.websocket = websocket;
28
+ this.room = room;
29
+ this.webrtcConnected = false;
30
+ this.incomingSSRCS = {};
31
+ this.isStopped = false;
32
+ this.outgoingSSRCS = new Map<string, SSRCs>();
33
+ this.emitter = new EventEmitter()
34
+ this.uniqueId = randomUUID();
35
+ }
36
+
37
+ public initIncomingSSRCs(ssrcs: SSRCs) {
38
+ this.incomingSSRCS = ssrcs;
39
+ }
40
+
41
+ public getIncomingStreamSSRCs(): SSRCs {
42
+ return this.incomingSSRCS || {}
43
+ }
44
+
45
+ public getOutgoingStreamSSRCsForUser(user_id: string): SSRCs {
46
+ return this.outgoingSSRCS?.get(user_id) ?? {}
47
+ }
48
+
49
+ public isProducingAudio(): boolean {
50
+ return !!this.incomingSSRCS?.audio_ssrc
51
+ }
52
+
53
+ public isProducingVideo(): boolean {
54
+ return !!this.incomingSSRCS?.video_ssrc;
55
+ }
56
+
57
+ publishTrack (type: "audio" | "video", ssrc: SSRCs): Promise<void> {
58
+ if(!this.incomingSSRCS) this.incomingSSRCS = {}
59
+
60
+ if(type === "audio") {
61
+ this.incomingSSRCS.audio_ssrc = ssrc.audio_ssrc;
62
+ } else {
63
+ this.incomingSSRCS = {...ssrc, audio_ssrc: this.incomingSSRCS.audio_ssrc}
64
+ }
65
+
66
+ this.room?.sfu.ipc.send({ type: "publish", clientId: this.uniqueId!, trackType: type})
67
+ return Promise.resolve();
68
+ }
69
+
70
+ stopPublishingTrack (type: "audio" | "video"): void {
71
+ if(type === "audio") {
72
+ if(this.incomingSSRCS) this.incomingSSRCS.audio_ssrc = undefined
73
+ }
74
+ else {
75
+ if(this.incomingSSRCS) {
76
+ this.incomingSSRCS.video_ssrc = undefined;
77
+ this.incomingSSRCS.rtx_ssrc = undefined;
78
+ }
79
+ }
80
+
81
+ this.room?.sfu.ipc.send({type: "stop-publish", clientId: this.uniqueId!, trackType: type})
82
+ }
83
+
84
+ async subscribeToTrack(user_id: string, type: "audio" | "video"): Promise<void> {
85
+ const otherClient = this.room?.getClientByUserId(user_id);
86
+ if(!otherClient || !otherClient.uniqueId) {
87
+ console.log(`Cannot subscribe: user ${user_id} not found in the current voice room`)
88
+ return;
89
+ }
90
+ const result = await this.room?.sfu.ipc.request({type: "subscribe", clientId: this.uniqueId!, publisherId: otherClient.uniqueId, trackType: type})
91
+ const sssr = this.outgoingSSRCS?.get(user_id) ?? {}
92
+ if(!result || !result.ssrc) {
93
+ console.log("unable to subscribe")
94
+ return;
95
+ }
96
+ if(type === "audio") sssr.audio_ssrc = result.ssrc;
97
+ else {
98
+ sssr.video_ssrc = result.ssrc;
99
+ sssr.rtx_ssrc = otherClient.getIncomingStreamSSRCs().rtx_ssrc;
100
+ }
101
+
102
+ console.log(`subscribed to track with ssrcs: ${result.ssrc}`)
103
+ this.outgoingSSRCS?.set(user_id, sssr)
104
+ }
105
+
106
+ unSubscribeFromTrack(user_id: string, type: "audio" | "video"): void {
107
+ const sssr = this.outgoingSSRCS?.get(user_id) ?? {}
108
+ let unsubscribedSSRC = -1;
109
+ if(type === "audio") {
110
+ unsubscribedSSRC = sssr.audio_ssrc ?? -1;
111
+ sssr.audio_ssrc = undefined;
112
+ } else {
113
+ unsubscribedSSRC = sssr.video_ssrc ?? -1;
114
+ sssr.video_ssrc = undefined
115
+ sssr.rtx_ssrc = undefined
116
+ }
117
+
118
+ const otherClient = this.room?.getClientByUserId(user_id);
119
+ if(!otherClient || !otherClient.uniqueId) {
120
+ console.log(`Cannot subscribe: user ${user_id} not found in the current voice room`)
121
+ return;
122
+ }
123
+ console.log(`unsubscribed from track ssrcs: ${unsubscribedSSRC}`)
124
+ this.room?.sfu.ipc.send({type: "unsubscribe", clientId: this.uniqueId!, trackType: type, publisherId: otherClient.uniqueId})
125
+ }
126
+
127
+ isSubscribedToTrack(user_id: string, type: "audio" | "video"): boolean {
128
+ const ssrc = this.outgoingSSRCS?.get(user_id) ?? {}
129
+
130
+ if(type === "audio") return ssrc.audio_ssrc !== undefined
131
+ else return ssrc.video_ssrc !== undefined
132
+ }
133
+ }
@@ -0,0 +1,75 @@
1
+ import { PionSignalingDelegate } from "./PionSignalingDelegate.js";
2
+ import { PionWebRtcClient } from "./PionWebRtcClient.js";
3
+
4
+ export class VoiceRoom {
5
+ private _clients: Map<string, PionWebRtcClient>;
6
+ private _id: string;
7
+ private _sfu: PionSignalingDelegate;
8
+ private _type: "guild-voice" | "dm-voice" | "stream";
9
+
10
+ constructor(
11
+ id: string,
12
+ type: "guild-voice" | "dm-voice" | "stream",
13
+ sfu: PionSignalingDelegate
14
+ ) {
15
+ this._id = id;
16
+ this._type = type;
17
+ this._clients = new Map();
18
+ this._sfu = sfu;
19
+ }
20
+
21
+ onClientJoin = (client: PionWebRtcClient) => {
22
+ // do shit here
23
+ this._clients.set(client.user_id, client);
24
+ };
25
+
26
+ onClientLeave = (client: PionWebRtcClient) => {
27
+ // stop the client
28
+ if (!client.isStopped) {
29
+ client.isStopped = true;
30
+
31
+ for (const otherClient of this.clients.values()) {
32
+ //remove outgoing track for this user
33
+ otherClient.outgoingSSRCS?.delete(client.user_id);
34
+ }
35
+ client.room = undefined;
36
+ client.emitter.removeAllListeners();
37
+
38
+ this._clients.delete(client.user_id)
39
+ }
40
+ }
41
+
42
+ public async stop(): Promise<void> {
43
+ for (const client of this.clients.values()) {
44
+ if(client.isStopped) continue;
45
+ client.isStopped = true;
46
+ client.room = undefined;
47
+ client.emitter.removeAllListeners();
48
+ this._clients.delete(client.user_id)
49
+ }
50
+ }
51
+
52
+ get clients(): Map<string, PionWebRtcClient> {
53
+ return this._clients;
54
+ }
55
+
56
+ getClientByUserId = (id: string) => {
57
+ return this._clients.get(id);
58
+ };
59
+
60
+ getClientByUniqueId = (id: string) => {
61
+ return Array.from(this._clients.entries()).find(([userId, client]) => client.uniqueId === id)?.[1];
62
+ }
63
+
64
+ get id(): string {
65
+ return this._id;
66
+ }
67
+
68
+ get type(): "guild-voice" | "dm-voice" | "stream" {
69
+ return this._type;
70
+ }
71
+
72
+ get sfu(): PionSignalingDelegate {
73
+ return this._sfu;
74
+ }
75
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ import { PionSignalingDelegate } from "./PionSignalingDelegate.js";
2
+
3
+ export default PionSignalingDelegate;