@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.
- package/dist/IpcClient.d.ts +31 -0
- package/dist/IpcClient.d.ts.map +1 -0
- package/dist/IpcClient.js +136 -0
- package/dist/IpcClient.js.map +1 -0
- package/dist/PionSignalingDelegate.d.ts +27 -0
- package/dist/PionSignalingDelegate.d.ts.map +1 -0
- package/dist/PionSignalingDelegate.js +152 -0
- package/dist/PionSignalingDelegate.js.map +1 -0
- package/dist/PionWebRtcClient.d.ts +26 -0
- package/dist/PionWebRtcClient.d.ts.map +1 -0
- package/dist/PionWebRtcClient.js +115 -0
- package/dist/PionWebRtcClient.js.map +1 -0
- package/dist/VoiceRoom.d.ts +19 -0
- package/dist/VoiceRoom.d.ts.map +1 -0
- package/dist/VoiceRoom.js +58 -0
- package/dist/VoiceRoom.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/tsconfig.tsbuildinfo +1 -0
- package/package.json +39 -0
- package/src/IpcClient.ts +183 -0
- package/src/PionSignalingDelegate.ts +256 -0
- package/src/PionWebRtcClient.ts +133 -0
- package/src/VoiceRoom.ts +75 -0
- package/src/index.ts +3 -0
|
@@ -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
|
+
}
|
package/src/VoiceRoom.ts
ADDED
|
@@ -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