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 +0 -0
- package/package.json +41 -0
- package/pm2.config.js +8 -0
- package/src/ServerHandler.ts +32 -0
- package/src/base/BaseSFUWebsocket.ts +20 -0
- package/src/index.ts +3 -0
- package/src/lib/redis/RedisHandler.ts +150 -0
- package/src/models/Event.ts +36 -0
- package/src/models/MessagePayload.ts +35 -0
- package/src/models/Participant.ts +24 -0
- package/src/models/WebSocketServerStartRequest.ts +14 -0
- package/src/sfu/SFUEachRoomHandler.ts +265 -0
- package/src/sfu/SFUEachRoomUserHandler.ts +468 -0
- package/src/sfu/SFUHandler.ts +67 -0
- package/src/utility/Constant.ts +63 -0
- package/src/utility/EventEmitterHandler.ts +10 -0
- package/src/utility/VaniEventListener.ts +7 -0
- package/src/websocket/EachSocketConnectionHandler.ts +451 -0
- package/src/websocket/WebSocketHandler.ts +30 -0
- package/tsconfig.json +12 -0
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,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,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
|
+
}
|