vani-meeting-server 1.0.0

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/dump.rdb ADDED
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "vani-meeting-server",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "main": "src/index.js",
6
+ "scripts": {
7
+ "build": "rimraf dist && tsc",
8
+ "preserve": "npm run build",
9
+ "serve": "cross-env NODE_ENV=development concurrently \"tsc --watch\" \"nodemon -q dist/index.js\"",
10
+ "prestart": "npm run build",
11
+ "start": "cross-env NODE_ENV=production node dist/index.js",
12
+ "test": "echo \"Error: no test specified\" && exit 1",
13
+ "pm2": "tsc && pm2 start pm2.config.js"
14
+ },
15
+ "author": "",
16
+ "license": "ISC",
17
+ "dependencies": {
18
+ "@types/axios": "^0.14.0",
19
+ "@types/body-parser": "^1.19.2",
20
+ "@types/express": "^4.17.13",
21
+ "@types/node": "^18.7.16",
22
+ "public-ip": "4.0.3",
23
+ "@types/redis": "^4.0.11",
24
+ "@types/ws": "^8.5.3",
25
+ "axios": "^0.27.2",
26
+ "body-parser": "^1.20.0",
27
+ "cross-env": "^7.0.3",
28
+ "dotenv": "^16.0.2",
29
+ "express": "^4.18.1",
30
+ "helmet": "^6.0.0",
31
+ "mediasoup": "^3.10.6",
32
+ "rimraf": "^3.0.2",
33
+ "typescript": "^4.8.3",
34
+ "uuid": "^9.0.0"
35
+ },
36
+ "devDependencies": {
37
+ "@types/uuid": "^8.3.4",
38
+ "concurrently": "^7.4.0",
39
+ "nodemon": "^2.0.19"
40
+ }
41
+ }
package/pm2.config.js ADDED
@@ -0,0 +1,8 @@
1
+ module.exports = {
2
+ apps: [
3
+ {
4
+ name: 'Vani server v2',
5
+ script: './dist/index.js',
6
+ },
7
+ ],
8
+ };
@@ -0,0 +1,32 @@
1
+ import { RedisHandler } from "./lib/redis/RedisHandler";
2
+ import { WebSocketServerStartRequest } from "./models/WebSocketServerStartRequest";
3
+ import { SFUHandler } from "./sfu/SFUHandler";
4
+ import { WebSocketHandler } from "./websocket/WebSocketHandler";
5
+
6
+ export class ServerHandler {
7
+
8
+ serverStartRequest: WebSocketServerStartRequest = new WebSocketServerStartRequest();
9
+ rediHandler?: RedisHandler;
10
+ webSocketHandler?: WebSocketHandler
11
+ sfuHandler?: SFUHandler
12
+
13
+ static instance: ServerHandler = new ServerHandler()
14
+ static getInstance(): ServerHandler {
15
+ return ServerHandler.instance;
16
+ }
17
+ async initWithServerStartRequest(serverStartRequest: WebSocketServerStartRequest) {
18
+ this.serverStartRequest = serverStartRequest
19
+
20
+ this.rediHandler = RedisHandler.getInstance()
21
+ await this.rediHandler.connectDB()
22
+ await this.rediHandler.cleanUp()
23
+
24
+ console.info("On Redis Connected")
25
+
26
+ this.webSocketHandler = new WebSocketHandler()
27
+ this.webSocketHandler.connect()
28
+
29
+ this.sfuHandler = SFUHandler.getInstance()
30
+ this.sfuHandler.init()
31
+ }
32
+ }
@@ -0,0 +1,20 @@
1
+ import { RedisHandler } from "../lib/redis/RedisHandler"
2
+ import { Participant } from "../models/Participant"
3
+ import { ClientMessageBody, WebSocketEvents, WebSocketMessageBody } from "../websocket/EachSocketConnectionHandler"
4
+
5
+ export class BaseSFUWebsocket{
6
+ protected async redisBroadcastMessageToTopic(topic: string, data: WebSocketMessageBody | ClientMessageBody) {
7
+ RedisHandler.getInstance().redisPublisher.publish(topic, JSON.stringify(data))
8
+ }
9
+
10
+
11
+ //Utilility function
12
+
13
+
14
+ protected preapreClientMessageBody(shouldSendToSelf: boolean, senderParticipant: Participant, broadcastMessage: WebSocketMessageBody): ClientMessageBody {
15
+ return { shouldSendToSelf, senderParticipant, broadcastMessage, interfaceName: 'ClientMessageBody' }
16
+ }
17
+ protected preapreWebSocketMessageBody(type: WebSocketEvents, data: any): WebSocketMessageBody {
18
+ return { type, data, interfaceName: 'WebSocketMessageBody' }
19
+ }
20
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './models/Event'
2
+ export * from './models/WebSocketServerStartRequest'
3
+ export * from './ServerHandler'
@@ -0,0 +1,150 @@
1
+ import { log } from "console";
2
+ import { createClient, RedisClientType } from "redis";
3
+ import { MessagePayload } from "../../models/MessagePayload";
4
+ import { Participant } from "../../models/Participant";
5
+ import { ServerHandler } from "../../ServerHandler";
6
+
7
+ export enum RedisKeyType {
8
+ Messages = "messages",
9
+ MeetingTime = "meetingTime",
10
+ Participants = "participants",
11
+ }
12
+ export class RedisHandler {
13
+
14
+ private static redisHandler: RedisHandler = new RedisHandler();
15
+ private redisClient: RedisClientType;
16
+ // private redisRoomModelHash = new Map<string, RedisRoomModel>;
17
+ public redisPublisher: RedisClientType;
18
+ public redisSubscriber: RedisClientType;
19
+ constructor() {
20
+ this.redisClient = createClient({ url: ServerHandler.getInstance().serverStartRequest.redisUrl, database: ServerHandler.getInstance().serverStartRequest.redisDBIndex })
21
+ this.redisSubscriber = createClient({ url: ServerHandler.getInstance().serverStartRequest.redisUrl })
22
+ this.redisPublisher = createClient({ url: ServerHandler.getInstance().serverStartRequest.redisUrl })
23
+ }
24
+
25
+
26
+ public static getInstance() {
27
+ return this.redisHandler
28
+ }
29
+
30
+ public async connectDB() {
31
+ this.redisClient.on("connect", (data) => {
32
+ console.info('Redis has been connected successfully.', data)
33
+
34
+ })
35
+ this.redisClient.on('end', (err) => {
36
+ console.error('Redis connection has been closed.', err);
37
+ });
38
+ this.redisClient.on('error', (err) => {
39
+ console.error('Unknown exception occurred at Redis', err);
40
+ });
41
+ await this.redisClient.connect()
42
+ await this.redisSubscriber.connect()
43
+ await this.redisPublisher.connect()
44
+ // await this.redisClient.hSet("rooms","123","HSETObject")
45
+ // console.log(await this.redisClient.hGet("rooms","123"))
46
+ }
47
+
48
+ public async cleanUp() {
49
+ return await this.redisClient.flushDb()
50
+ }
51
+
52
+
53
+ //Clean Room Id
54
+
55
+ public async cleanUpRoomId(roomId: string) {
56
+ await this.redisClient.del(roomId)
57
+ }
58
+
59
+
60
+ //Messages
61
+ public async storeMesagesForRoom(roomId: string, messagePayload: MessagePayload) {
62
+ const messages = await this.fetchMessagesForRoom(roomId)
63
+ messages.push(messagePayload)
64
+ await this.redisClient.hSet(roomId, RedisKeyType.Messages, JSON.stringify(messages))
65
+ this.updateRoomCleanupTimeOut(roomId)
66
+ }
67
+
68
+ public async fetchMessagesForRoom(roomId: string): Promise<MessagePayload[]> {
69
+ const messages = await this.redisClient.hGet(roomId, RedisKeyType.Messages)
70
+ if (messages) {
71
+ return JSON.parse(messages)
72
+ }
73
+ else {
74
+ return []
75
+ }
76
+ }
77
+
78
+ //Meeting Time
79
+
80
+ public async storeMeetingTimeForRoom(roomId: string, time?: number) {
81
+
82
+ await this.redisClient.hSet(roomId, RedisKeyType.MeetingTime, time ? time : new Date().getTime())
83
+ this.updateRoomCleanupTimeOut(roomId)
84
+ }
85
+
86
+ public async fetchMeetingTimeForRoom(roomId: string): Promise<number | undefined> {
87
+ const meetingTime = await this.redisClient.hGet(roomId, RedisKeyType.MeetingTime)
88
+ if (meetingTime) {
89
+ return +meetingTime;
90
+ }
91
+ else {
92
+ return undefined
93
+ }
94
+ }
95
+
96
+
97
+ //Users
98
+ public async removeParticipantForRoom(roomId: string, participant: Participant) {
99
+ const participantKey = RedisKeyType.Participants + ":" + participant.userId
100
+ await this.redisClient.hDel(roomId, participantKey)
101
+ this.updateRoomCleanupTimeOut(roomId)
102
+ }
103
+ public async addUpdateParticipantForRoom(roomId: string, participant: Participant) {
104
+ const participantKey = RedisKeyType.Participants + ":" + participant.userId
105
+ await this.redisClient.hSet(roomId, participantKey, JSON.stringify(participant))
106
+ this.updateRoomCleanupTimeOut(roomId)
107
+ }
108
+
109
+ public async getParticipantByUserId(roomId: string, userId: string): Promise<Participant | undefined> {
110
+ const participantKey = RedisKeyType.Participants + ":" + userId
111
+ const particpantString = await this.redisClient.hGet(roomId, participantKey)
112
+ if (particpantString) {
113
+ return JSON.parse(particpantString)
114
+ }
115
+ return undefined
116
+ }
117
+
118
+ public async getAllParticipants(roomId: string): Promise<Participant[]> {
119
+ const particpantsInStringify = await this.redisClient.hGetAll(roomId)
120
+ const participants: Participant[] = []
121
+ if (particpantsInStringify) {
122
+ Object.entries(particpantsInStringify).forEach(([key, value]) => {
123
+ if (key.includes(RedisKeyType.Participants)) {
124
+ participants.push(Object.assign(new Participant(), JSON.parse(value)))
125
+ }
126
+
127
+ })
128
+
129
+ }
130
+ return participants
131
+ }
132
+
133
+
134
+ // private getRediRoomModelForRoomId(roomId: string): RedisRoomModel {
135
+ // if (this.redisRoomModelHash.has(roomId)) {
136
+ // return this.redisRoomModelHash.get(roomId)!
137
+ // }
138
+ // else {
139
+ // const redisRoomModel = new RedisRoomModel(roomId)
140
+ // this.redisRoomModelHash.set(roomId, redisRoomModel)
141
+ // return redisRoomModel
142
+ // }
143
+ // }
144
+
145
+ private updateRoomCleanupTimeOut(roomId: string) {
146
+ this.redisClient.expire(roomId, ServerHandler.getInstance().serverStartRequest.redisRoomDestoryTimeOutInSec)
147
+ }
148
+
149
+
150
+ }
@@ -0,0 +1,36 @@
1
+ import { Participant } from "./Participant";
2
+
3
+
4
+ export enum VaniEvent {
5
+
6
+ OnNewMeetingStarted = "onNewMeetingStarted",
7
+ OnNewMeetingEnded = "onNewMeetingEnded",
8
+ OnUserJoined = "onUserJoined",
9
+ OnUserLeft = "onUserLeft",
10
+ OnServerStarted = "onServerStarted"
11
+
12
+
13
+ }
14
+
15
+ interface VaniConnectionEvents {
16
+ [VaniEvent.OnNewMeetingStarted]: (roomId : string) => any;
17
+ [VaniEvent.OnNewMeetingEnded]: (roomId : string) => any;
18
+ [VaniEvent.OnUserJoined]: (participant : Participant) => any;
19
+ [VaniEvent.OnUserLeft]: (participant : Participant) => any;
20
+ [VaniEvent.OnServerStarted]: () => any;
21
+
22
+ // [VaniEvent.OnSocketError]: (error: any) => any;
23
+ }
24
+
25
+ export declare interface VaniEventListener {
26
+ on<U extends keyof VaniConnectionEvents>(event: U, listener: VaniConnectionEvents[U]): this;
27
+ off<U extends keyof VaniConnectionEvents>(event: U, listener: VaniConnectionEvents[U]): this;
28
+ emit<U extends keyof VaniConnectionEvents>(event: U, ...args: Parameters<VaniConnectionEvents[U]>): boolean;
29
+ }
30
+
31
+
32
+
33
+ export interface Device {
34
+ id: string,
35
+ label: string
36
+ }
@@ -0,0 +1,35 @@
1
+
2
+ export enum ChatMessageType{
3
+ Chat = "chat",
4
+ File = "file",
5
+ Info = "info",
6
+ }
7
+ export enum ChatSendVia{
8
+ WebSocket = "WebSocket",
9
+ DataChannel = "DataChannel",
10
+ }
11
+
12
+ export class MessagePayload {
13
+ public message?: string;
14
+ public to : string = "all";
15
+ public extraData : any = {}
16
+ public type? : ChatMessageType | string ;
17
+ public senderUserId? : string;
18
+ // public sender? :Participant;
19
+ public shouldPresist : boolean = false
20
+ public time : number = new Date().getTime();
21
+ public chatSendVia : ChatSendVia = ChatSendVia.WebSocket
22
+
23
+ // constructor( _message : string)
24
+ // constructor( _message : string , _to: string )
25
+ // constructor(_message : string , _to: string = "all" , _sender ? : Participant , _type : ChatMessageType | string = ChatMessageType.Chat) {
26
+
27
+ // this.message = _message;
28
+ // this.to = _to;
29
+ // this.sender = _sender;
30
+ // this.type = _type;
31
+
32
+ // }
33
+
34
+
35
+ }
@@ -0,0 +1,24 @@
1
+
2
+ export class Participant {
3
+ public userId?: string;
4
+ public userData: any;
5
+ public isAdmin: boolean = false;
6
+ public isAudioBlockedByAdmin: boolean = false;
7
+ public isVideoBlockedByAdmin: boolean = false;
8
+ public isMessageBlockedByAdmin: boolean = false;
9
+ public isWhiteboardBlockedByAdmin: boolean = true;
10
+ public isScreenshareBlockedByAdmin: boolean = false;
11
+ public roomId?: string;
12
+ public isVideoEnable: boolean = false;
13
+ public isAudioEnable: boolean = false;
14
+ public isStartMeetingCalled: boolean = false;
15
+ public isRecordingUser: boolean = false;
16
+
17
+
18
+ // constructor(_userId: string, _roomId: string, _userData: any = {}, _isAdmin: boolean = false) {
19
+ // this.userId = _userId;
20
+ // this.roomId = _roomId;
21
+ // this.userData = _userData;
22
+ // this.isAdmin = _isAdmin;
23
+ // }
24
+ }
@@ -0,0 +1,14 @@
1
+
2
+
3
+ export class WebSocketServerStartRequest {
4
+
5
+ port: number = 4010
6
+ redisUrl: string = "redis://127.0.0.1:6378";
7
+ redisDBIndex: number = 5
8
+ redisRoomDestoryTimeOutInSec = 1800
9
+ rtcMinPort = 40000
10
+ rtcMaxPort = 59999
11
+ isTCPConnection: boolean = false
12
+
13
+
14
+ }
@@ -0,0 +1,265 @@
1
+ import { log } from "console";
2
+ import { ActiveSpeakerObserver } from "mediasoup/node/lib/ActiveSpeakerObserver";
3
+ import { AudioLevelObserver } from "mediasoup/node/lib/AudioLevelObserver";
4
+ import { Consumer } from "mediasoup/node/lib/Consumer";
5
+ import { Producer } from "mediasoup/node/lib/Producer";
6
+ import { Router } from "mediasoup/node/lib/Router";
7
+ import { Worker } from "mediasoup/node/lib/Worker";
8
+ import { BaseSFUWebsocket } from "../base/BaseSFUWebsocket";
9
+ import { RedisHandler } from "../lib/redis/RedisHandler";
10
+ import { VaniEvent } from "../models/Event";
11
+ import { Participant } from "../models/Participant";
12
+ import { mediaCodecs } from "../utility/Constant";
13
+ import { EventEmitterHandler } from "../utility/EventEmitterHandler";
14
+ import { ClientMessageBody, SFUMessageType, WebSocketBasicEvents, WebSocketMessageBody } from "../websocket/EachSocketConnectionHandler";
15
+ import { SFUEachRoomUserHandler } from "./SFUEachRoomUserHandler";
16
+
17
+ export interface SFUEachRoomParticipantInterface {
18
+ participant: Participant
19
+ }
20
+
21
+ export interface SFUEachRoomProducer extends SFUEachRoomParticipantInterface {
22
+ producer: Producer
23
+ }
24
+ export interface SFUEachRoomConsumer extends SFUEachRoomParticipantInterface {
25
+ consumer: Consumer
26
+ }
27
+
28
+ export interface SFUMessageBody {
29
+ to: string,
30
+ type: SFUMessageType,
31
+ message: any
32
+ }
33
+
34
+ export interface SFUEachRoomHandlerInterface {
35
+ getAllProducerForRoom(): SFUEachRoomProducer[];
36
+ getProducerById(producerId: string): SFUEachRoomProducer | undefined
37
+ getAllConsumerForRoom(): SFUEachRoomConsumer[];
38
+ getAllRecvRouterForRoom(): Router[];
39
+ getSendRouterForRoom(): Router;
40
+ addAudioObserverForProducer(audioProducer: Producer): void;
41
+ pipeToRoute(producer: Producer): Promise<void>;
42
+ }
43
+
44
+ export class SFUEachRoomHandler extends BaseSFUWebsocket implements SFUEachRoomHandlerInterface {
45
+
46
+ private recvRouter: Router[] = []
47
+ private sendRouter?: Router;
48
+ private roomId: string;
49
+ public roomPaticipants = new Map<string, SFUEachRoomUserHandler>()
50
+
51
+ private workers?: Worker[]
52
+ private sendWorkerIndex: number = -1
53
+ private audioObserver?: ActiveSpeakerObserver;
54
+ constructor(roomId: string, workers: Worker[], sendWorkerIndex: number) {
55
+ super()
56
+ this.roomId = roomId;
57
+ this.workers = workers
58
+ this.sendWorkerIndex = sendWorkerIndex
59
+ this.cleanUp = this.cleanUp.bind(this)
60
+ }
61
+
62
+
63
+
64
+
65
+
66
+ public onNewMessage(payload: SFUMessageBody, participant: Participant) {
67
+ // console.log(payload)
68
+ if (payload.type === SFUMessageType.GetRouterRtpCapabilities) {
69
+ this.onGetRTPCapabilities(participant)
70
+ }
71
+ else {
72
+ const sfuEachRoomUser = this.getEachRoomUserFromParticipant(participant)
73
+ if (sfuEachRoomUser) {
74
+ this.getEachRoomUserFromParticipant(participant)?.onNewMessage(payload)
75
+ }
76
+
77
+ }
78
+ }
79
+
80
+
81
+
82
+
83
+ private getEachRoomUserFromParticipant(participant: Participant): SFUEachRoomUserHandler | undefined {
84
+
85
+ if (this.sendRouter && this.recvRouter.length > 0) {
86
+ if (this.roomPaticipants.has(participant.userId!)) {
87
+ return this.roomPaticipants.get(participant.userId!)
88
+ }
89
+ else {
90
+ const indexForRecRouter = (this.roomPaticipants.size % this.recvRouter.length)
91
+ const roomUser = new SFUEachRoomUserHandler(participant, this.sendRouter!, this.recvRouter[indexForRecRouter], this)
92
+ this.roomPaticipants.set(participant.userId!, roomUser)
93
+ return roomUser;
94
+ }
95
+ }
96
+
97
+ }
98
+
99
+ private async onGetRTPCapabilities(participant: Participant) {
100
+ if (this.workers) {
101
+ await this.setUpForRoomId(this.workers, this.sendWorkerIndex)
102
+ }
103
+
104
+ if (this.sendRouter) {
105
+ const rtpCapablities = this.sendRouter.rtpCapabilities;
106
+ if (rtpCapablities) {
107
+ this.redisBroadcastMessageToTopic(participant.userId!, this.preapreClientMessageBody(true, participant, this.preapreWebSocketMessageBody(SFUMessageType.OnRouterRtpCapabilities, { rtpCapabilities: rtpCapablities })));
108
+ }
109
+ }
110
+ else {
111
+ console.error("onGetRTPCapabilities no sende router found")
112
+ }
113
+ }
114
+
115
+
116
+
117
+ private async setUpForRoomId(workers: Worker[], sendWorkerIndex: number) {
118
+ let currentWorkerIndexForRouter = sendWorkerIndex
119
+ let sendWorkerPID = undefined
120
+ if (!this.sendRouter) {
121
+ const worker = workers[currentWorkerIndexForRouter]
122
+ if (worker) {
123
+ this.sendRouter = await worker.createRouter({ mediaCodecs })
124
+ sendWorkerPID = worker.pid
125
+ currentWorkerIndexForRouter = this.getWorkerIndexForCreatingRouter(currentWorkerIndexForRouter, workers.length)
126
+ this.addAudioObserver()
127
+ }
128
+ }
129
+
130
+ while (this.recvRouter.length < (workers.length - 1)) { // Dont create on send worker
131
+ const worker = workers[currentWorkerIndexForRouter]
132
+ currentWorkerIndexForRouter = this.getWorkerIndexForCreatingRouter(currentWorkerIndexForRouter, workers.length)
133
+ if (worker.pid === sendWorkerPID) {
134
+ continue;
135
+ }
136
+ this.recvRouter.push(await worker.createRouter({ mediaCodecs }))
137
+ }
138
+ this.workers = undefined
139
+ this.sendWorkerIndex = -1
140
+ }
141
+
142
+ private async addAudioObserver() {
143
+
144
+ if (this.sendRouter && !this.audioObserver) {
145
+ this.audioObserver = await this.sendRouter.createActiveSpeakerObserver({
146
+ interval: 100
147
+ })
148
+ this.audioObserver.on("dominantspeaker", (speakerProducer) => {
149
+
150
+ const speakerUserId: string = speakerProducer.producer.appData.userId as string;
151
+ if (this.roomPaticipants.size < 2) {
152
+ return;
153
+ }
154
+ else {
155
+ const participant = RedisHandler.getInstance().getParticipantByUserId(this.roomId, speakerUserId);
156
+ if (participant !== undefined) {
157
+ this.redisBroadcastMessageToTopic(this.roomId, this.preapreClientMessageBody(true, participant as unknown as Participant, this.preapreWebSocketMessageBody(SFUMessageType.OnSpeakerChanged, { speakerUserId: speakerUserId })));
158
+ console.log( "dominantspeaker" , speakerUserId)
159
+ }
160
+ }
161
+
162
+
163
+ })
164
+
165
+
166
+ }
167
+ }
168
+
169
+ private getWorkerIndexForCreatingRouter(currentIndexOfWorker: number, totalNumberOfWorker: number) {
170
+ if (currentIndexOfWorker < (totalNumberOfWorker - 1)) {
171
+ return (currentIndexOfWorker + 1);
172
+ }
173
+ return 0
174
+ }
175
+
176
+
177
+
178
+ //Interface methods
179
+ getProducerById(producerId: string): SFUEachRoomProducer | undefined {
180
+
181
+ const allProducers: SFUEachRoomProducer[] = this.getAllProducerForRoom()
182
+ const producer = allProducers.find((eachProducer) => eachProducer.producer.id === producerId)
183
+ return producer;
184
+ }
185
+
186
+ getAllProducerForRoom(): SFUEachRoomProducer[] {
187
+ const allProducers: SFUEachRoomProducer[] = []
188
+ this.roomPaticipants.forEach((roomPaticipant, userId) => {
189
+ roomPaticipant.producers.forEach(producer => {
190
+ if (producer.closed === false) {
191
+ allProducers.push({ participant: roomPaticipant.selfParticipant, producer })
192
+ }
193
+ else {
194
+ console.log("Producer is Closed ", producer.id, roomPaticipant.selfParticipant.userData.name)
195
+ }
196
+ });
197
+ })
198
+ return allProducers
199
+ }
200
+ getAllConsumerForRoom(): SFUEachRoomConsumer[] {
201
+ return []
202
+ }
203
+ getAllRecvRouterForRoom(): Router[] {
204
+ return this.recvRouter;
205
+ }
206
+ getSendRouterForRoom(): Router {
207
+ return this.sendRouter!;
208
+ }
209
+ async pipeToRoute(producer: Producer) {
210
+ if (!this.sendRouter) {
211
+ console.error("pipeToRoute - no send router found")
212
+ }
213
+
214
+
215
+ for (const eachRecvRouter of this.recvRouter) {
216
+ try {
217
+ await this.sendRouter?.pipeToRouter({ producerId: producer.id, router: eachRecvRouter })
218
+ } catch (err) {
219
+ console.error("pipeToRouter Error")
220
+ console.error(err)
221
+ }
222
+
223
+
224
+ }
225
+
226
+ }
227
+
228
+ async addAudioObserverForProducer(audioProducer: Producer) {
229
+ if (this.audioObserver) {
230
+ console.log("addAudioObserverForProducer" , audioProducer.appData.userId)
231
+ this.audioObserver?.addProducer({ producerId: audioProducer.id })
232
+ }
233
+ }
234
+
235
+ public onUserLeft(participant: Participant) {
236
+ if (this.roomPaticipants.has(participant.userId!)) {
237
+ this.roomPaticipants.get(participant.userId!)?.cleanUp()
238
+ this.roomPaticipants.delete(participant.userId!)
239
+ }
240
+ }
241
+
242
+ public cleanUp() {
243
+ console.debug("Clean Up Room Id ", this.roomId)
244
+ RedisHandler.getInstance().cleanUpRoomId(this.roomId)
245
+ EventEmitterHandler.getInstance().vaniEventEmitter.emit(VaniEvent.OnNewMeetingEnded, this.roomId)
246
+ this.roomPaticipants.forEach((sfuEachRoomUser: SFUEachRoomUserHandler) => {
247
+ this.onUserLeft(sfuEachRoomUser.selfParticipant)
248
+ })
249
+ this.recvRouter.forEach((eachRouter) => {
250
+ eachRouter.close()
251
+ })
252
+ if (this.audioObserver) {
253
+ this.audioObserver.close()
254
+ this.audioObserver = undefined
255
+ }
256
+ this.recvRouter = []
257
+ if (this.sendRouter) {
258
+ this.sendRouter.close()
259
+ }
260
+ this.sendRouter = undefined;
261
+ this.workers = []
262
+ }
263
+
264
+
265
+ }