@afosecure/meetingsdk 1.4.4 → 1.4.6
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/index.d.mts +19 -23
- package/dist/index.d.ts +19 -23
- package/dist/index.js +338 -594
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +338 -594
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.mjs
CHANGED
|
@@ -6,8 +6,8 @@ import { createContext, useContext, useMemo, useRef } from "react";
|
|
|
6
6
|
|
|
7
7
|
// src/config/ws.ts
|
|
8
8
|
var SDK_CONFIG = {
|
|
9
|
-
wsUrl: "wss://
|
|
10
|
-
baseUrl: "https://
|
|
9
|
+
wsUrl: "wss://localhost:8080/ws",
|
|
10
|
+
baseUrl: "https://localhost:8080"
|
|
11
11
|
};
|
|
12
12
|
|
|
13
13
|
// src/core/MeetingState.ts
|
|
@@ -164,10 +164,11 @@ var VideoSDKCore = class {
|
|
|
164
164
|
this.events = events;
|
|
165
165
|
this.url = url;
|
|
166
166
|
this.ws = null;
|
|
167
|
-
this.
|
|
168
|
-
this.
|
|
169
|
-
this.
|
|
167
|
+
this.pubPC = null;
|
|
168
|
+
this.subPC = null;
|
|
169
|
+
this.pendingTracks = [];
|
|
170
170
|
this.iceServers = [];
|
|
171
|
+
this.lastPong = Date.now();
|
|
171
172
|
this.intentionalDisconnect = false;
|
|
172
173
|
this.room = {
|
|
173
174
|
id: null,
|
|
@@ -175,14 +176,11 @@ var VideoSDKCore = class {
|
|
|
175
176
|
};
|
|
176
177
|
this.localStream = null;
|
|
177
178
|
this.screenStream = null;
|
|
179
|
+
this.screenSender = null;
|
|
178
180
|
this.isScreenSharing = false;
|
|
179
|
-
this.screenSenders = {};
|
|
180
181
|
this.pingInterval = null;
|
|
181
|
-
this.pendingIceCandidates = {};
|
|
182
|
-
this.pendingOffers = {};
|
|
183
182
|
this.reconnectAttempts = 0;
|
|
184
183
|
this.participantName = "";
|
|
185
|
-
// Track if we're in the waiting room (pending approval)
|
|
186
184
|
this.isWaitingForApproval = false;
|
|
187
185
|
this.pendingRequestId = null;
|
|
188
186
|
this.iceTransportPolicy = "all";
|
|
@@ -192,29 +190,12 @@ var VideoSDKCore = class {
|
|
|
192
190
|
this.myId = localStorage.getItem("vsdk_id") || crypto.randomUUID();
|
|
193
191
|
localStorage.setItem("vsdk_id", this.myId);
|
|
194
192
|
}
|
|
195
|
-
|
|
196
|
-
const err = {
|
|
197
|
-
code,
|
|
198
|
-
message,
|
|
199
|
-
raw,
|
|
200
|
-
roomId: this.room.id,
|
|
201
|
-
userId: this.myId,
|
|
202
|
-
recoverable
|
|
203
|
-
};
|
|
204
|
-
this.events.onError?.(err);
|
|
205
|
-
this.joinRejecter?.(err);
|
|
206
|
-
this.joinRejecter = void 0;
|
|
207
|
-
console.error("[MeetingSDK Error]", err);
|
|
208
|
-
}
|
|
209
|
-
// ---------------- STREAM ----------------
|
|
193
|
+
// ---------------- MEDIA SETUP ----------------
|
|
210
194
|
async initLocal(video, name) {
|
|
211
195
|
this.participantName = name;
|
|
212
196
|
try {
|
|
213
197
|
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
214
|
-
video: {
|
|
215
|
-
width: { ideal: 1280 },
|
|
216
|
-
height: { ideal: 720 }
|
|
217
|
-
},
|
|
198
|
+
video: { width: { ideal: 1280 }, height: { ideal: 720 } },
|
|
218
199
|
audio: {
|
|
219
200
|
echoCancellation: true,
|
|
220
201
|
noiseSuppression: true,
|
|
@@ -232,6 +213,8 @@ var VideoSDKCore = class {
|
|
|
232
213
|
name: this.participantName,
|
|
233
214
|
media: {
|
|
234
215
|
stream: this.localStream,
|
|
216
|
+
cameraTrack: this.localStream.getVideoTracks()[0],
|
|
217
|
+
audioTrack: this.localStream.getAudioTracks()[0],
|
|
235
218
|
micEnabled: true,
|
|
236
219
|
camEnabled: true,
|
|
237
220
|
isScreenSharing: false
|
|
@@ -243,7 +226,109 @@ var VideoSDKCore = class {
|
|
|
243
226
|
throw err;
|
|
244
227
|
}
|
|
245
228
|
}
|
|
246
|
-
|
|
229
|
+
async joinMeeting(config) {
|
|
230
|
+
const { roomId, name, audioMuted = false, videoMuted = false } = config;
|
|
231
|
+
if (!roomId || !name) {
|
|
232
|
+
throw new Error("roomId and name are required to join meeting");
|
|
233
|
+
}
|
|
234
|
+
this.participantName = name;
|
|
235
|
+
if (!this.localStream) {
|
|
236
|
+
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
237
|
+
video: true,
|
|
238
|
+
audio: true
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
this.localStream.getAudioTracks().forEach((t) => t.enabled = !audioMuted);
|
|
242
|
+
this.localStream.getVideoTracks().forEach((t) => t.enabled = !videoMuted);
|
|
243
|
+
this.state.updateLocalParticipant({
|
|
244
|
+
id: this.myId,
|
|
245
|
+
name: this.participantName,
|
|
246
|
+
media: {
|
|
247
|
+
stream: this.localStream,
|
|
248
|
+
cameraTrack: this.localStream.getVideoTracks()[0],
|
|
249
|
+
audioTrack: this.localStream.getAudioTracks()[0],
|
|
250
|
+
micEnabled: !audioMuted,
|
|
251
|
+
camEnabled: !videoMuted,
|
|
252
|
+
isScreenSharing: false
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
this.state.localStream = this.localStream;
|
|
256
|
+
await this.connect(roomId, name);
|
|
257
|
+
}
|
|
258
|
+
// ---------------- SFU PEER CONNECTION CREATION ----------------
|
|
259
|
+
setupPublisherPC() {
|
|
260
|
+
if (!this.localStream) return;
|
|
261
|
+
this.pubPC = new RTCPeerConnection({
|
|
262
|
+
iceServers: this.iceServers,
|
|
263
|
+
iceTransportPolicy: this.iceTransportPolicy
|
|
264
|
+
});
|
|
265
|
+
this.localStream.getTracks().forEach((track) => {
|
|
266
|
+
this.pubPC?.addTrack(track, this.localStream);
|
|
267
|
+
});
|
|
268
|
+
this.pubPC.onicecandidate = (e) => {
|
|
269
|
+
if (e.candidate) {
|
|
270
|
+
this.send({
|
|
271
|
+
type: "PUB_ICE",
|
|
272
|
+
payload: JSON.stringify(e.candidate),
|
|
273
|
+
user_id: this.myId
|
|
274
|
+
});
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
this.pubPC.onconnectionstatechange = () => {
|
|
278
|
+
console.log(`[SFU Publisher PC State]`, this.pubPC?.connectionState);
|
|
279
|
+
if (this.pubPC?.connectionState === "failed") {
|
|
280
|
+
this.restartPublisherIce();
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
setupSubscriberPC() {
|
|
285
|
+
this.subPC = new RTCPeerConnection({
|
|
286
|
+
iceServers: this.iceServers,
|
|
287
|
+
iceTransportPolicy: this.iceTransportPolicy
|
|
288
|
+
});
|
|
289
|
+
this.subPC.onicecandidate = (e) => {
|
|
290
|
+
if (e.candidate) {
|
|
291
|
+
this.send({
|
|
292
|
+
type: "SUB_ICE",
|
|
293
|
+
payload: JSON.stringify(e.candidate),
|
|
294
|
+
user_id: this.myId
|
|
295
|
+
});
|
|
296
|
+
}
|
|
297
|
+
};
|
|
298
|
+
this.subPC.ontrack = (event) => {
|
|
299
|
+
const descriptor = this.pendingTracks.shift();
|
|
300
|
+
if (!descriptor) {
|
|
301
|
+
console.warn("Unknown incoming track");
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const stream = event.streams[0] || new MediaStream([event.track]);
|
|
305
|
+
switch (descriptor.source) {
|
|
306
|
+
case "camera":
|
|
307
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
308
|
+
stream,
|
|
309
|
+
cameraTrack: event.track
|
|
310
|
+
});
|
|
311
|
+
break;
|
|
312
|
+
case "screen":
|
|
313
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
314
|
+
screenStream: stream,
|
|
315
|
+
screenTrack: event.track,
|
|
316
|
+
isScreenSharing: true
|
|
317
|
+
});
|
|
318
|
+
break;
|
|
319
|
+
case "audio":
|
|
320
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
321
|
+
stream,
|
|
322
|
+
audioTrack: event.track
|
|
323
|
+
});
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
};
|
|
327
|
+
this.subPC.onconnectionstatechange = () => {
|
|
328
|
+
console.log(`[SFU Subscriber PC State]`, this.subPC?.connectionState);
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
// ---------------- WEBSOCKET CONNECTION & SIGNALING ----------------
|
|
247
332
|
async connect(roomId, name) {
|
|
248
333
|
this.room.id = roomId;
|
|
249
334
|
this.reset();
|
|
@@ -252,15 +337,17 @@ var VideoSDKCore = class {
|
|
|
252
337
|
this.joinRejecter = reject;
|
|
253
338
|
this.ws = new WebSocket(this.url);
|
|
254
339
|
this.ws.onopen = () => {
|
|
255
|
-
console.log("WebSocket connected, sending JOIN...");
|
|
340
|
+
console.log("WebSocket connected to SFU, sending JOIN...");
|
|
341
|
+
const micEnabled = !!this.state.localParticipant?.media?.micEnabled;
|
|
342
|
+
const camEnabled = !!this.state.localParticipant?.media?.camEnabled;
|
|
256
343
|
this.send({
|
|
257
344
|
type: "JOIN",
|
|
258
345
|
room_id: roomId,
|
|
259
346
|
user_id: this.myId,
|
|
260
347
|
sender_name: name,
|
|
261
348
|
camera_stream_id: this.localStream?.id.replace(/[{}]/g, ""),
|
|
262
|
-
audio_muted:
|
|
263
|
-
video_muted:
|
|
349
|
+
audio_muted: !micEnabled,
|
|
350
|
+
video_muted: !camEnabled
|
|
264
351
|
});
|
|
265
352
|
};
|
|
266
353
|
this.ws.onerror = (err) => {
|
|
@@ -273,13 +360,7 @@ var VideoSDKCore = class {
|
|
|
273
360
|
raw: e
|
|
274
361
|
});
|
|
275
362
|
this.joinRejecter = void 0;
|
|
276
|
-
if (this.intentionalDisconnect) {
|
|
277
|
-
return;
|
|
278
|
-
}
|
|
279
|
-
if (e.code === 1e3 || e.code === 1001) {
|
|
280
|
-
return;
|
|
281
|
-
}
|
|
282
|
-
if (this.isWaitingForApproval) {
|
|
363
|
+
if (this.intentionalDisconnect || e.code === 1e3 || e.code === 1001 || this.isWaitingForApproval) {
|
|
283
364
|
return;
|
|
284
365
|
}
|
|
285
366
|
this.scheduleReconnect();
|
|
@@ -289,203 +370,71 @@ var VideoSDKCore = class {
|
|
|
289
370
|
};
|
|
290
371
|
});
|
|
291
372
|
}
|
|
292
|
-
async joinMeeting(config) {
|
|
293
|
-
const { roomId, name, audioMuted = false, videoMuted = false } = config;
|
|
294
|
-
if (!roomId || !name) {
|
|
295
|
-
throw new Error("roomId and name are required to join meeting");
|
|
296
|
-
}
|
|
297
|
-
this.participantName = name;
|
|
298
|
-
if (!this.localStream) {
|
|
299
|
-
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
300
|
-
video: true,
|
|
301
|
-
audio: true
|
|
302
|
-
});
|
|
303
|
-
}
|
|
304
|
-
this.localStream.getAudioTracks().forEach((t) => {
|
|
305
|
-
t.enabled = !audioMuted;
|
|
306
|
-
});
|
|
307
|
-
this.localStream.getVideoTracks().forEach((t) => {
|
|
308
|
-
t.enabled = !videoMuted;
|
|
309
|
-
});
|
|
310
|
-
this.state.updateLocalParticipant({
|
|
311
|
-
id: this.myId,
|
|
312
|
-
name: this.participantName,
|
|
313
|
-
media: {
|
|
314
|
-
stream: this.localStream,
|
|
315
|
-
micEnabled: !audioMuted,
|
|
316
|
-
camEnabled: !videoMuted,
|
|
317
|
-
isScreenSharing: false
|
|
318
|
-
}
|
|
319
|
-
});
|
|
320
|
-
this.state.localStream = this.localStream;
|
|
321
|
-
await this.connect(roomId, name);
|
|
322
|
-
}
|
|
323
|
-
getMeeting() {
|
|
324
|
-
return this.room;
|
|
325
|
-
}
|
|
326
|
-
toggleMic() {
|
|
327
|
-
const mediaState = this.state.localParticipant?.media;
|
|
328
|
-
if (!mediaState) return;
|
|
329
|
-
const nextEnabled = !mediaState.micEnabled;
|
|
330
|
-
this.localStream?.getAudioTracks().forEach((t) => t.enabled = nextEnabled);
|
|
331
|
-
this.state.updateLocalParticipant({
|
|
332
|
-
id: this.myId,
|
|
333
|
-
name: this.participantName,
|
|
334
|
-
media: {
|
|
335
|
-
...mediaState,
|
|
336
|
-
micEnabled: nextEnabled
|
|
337
|
-
}
|
|
338
|
-
});
|
|
339
|
-
this.send({
|
|
340
|
-
type: "MEDIA_STATE",
|
|
341
|
-
kind: "audio",
|
|
342
|
-
enabled: nextEnabled
|
|
343
|
-
});
|
|
344
|
-
}
|
|
345
|
-
toggleCam() {
|
|
346
|
-
const mediaState = this.state.localParticipant?.media;
|
|
347
|
-
if (!mediaState) return;
|
|
348
|
-
const nextEnabled = !mediaState.camEnabled;
|
|
349
|
-
this.localStream?.getVideoTracks().forEach((t) => t.enabled = nextEnabled);
|
|
350
|
-
this.state.updateLocalParticipant({
|
|
351
|
-
id: this.myId,
|
|
352
|
-
name: this.participantName,
|
|
353
|
-
media: {
|
|
354
|
-
...mediaState,
|
|
355
|
-
camEnabled: nextEnabled
|
|
356
|
-
}
|
|
357
|
-
});
|
|
358
|
-
this.send({
|
|
359
|
-
type: "MEDIA_STATE",
|
|
360
|
-
kind: "video",
|
|
361
|
-
enabled: nextEnabled
|
|
362
|
-
});
|
|
363
|
-
}
|
|
364
|
-
scheduleReconnect() {
|
|
365
|
-
if (!this.room.id) return;
|
|
366
|
-
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
367
|
-
clearTimeout(this.reconnectTimer);
|
|
368
|
-
this.reconnectTimer = window.setTimeout(async () => {
|
|
369
|
-
try {
|
|
370
|
-
await this.connect(this.room.id, this.participantName);
|
|
371
|
-
this.reconnectAttempts = 0;
|
|
372
|
-
} catch {
|
|
373
|
-
this.reconnectAttempts++;
|
|
374
|
-
this.scheduleReconnect();
|
|
375
|
-
}
|
|
376
|
-
}, delay);
|
|
377
|
-
}
|
|
378
|
-
startHeartbeat() {
|
|
379
|
-
this.stopHeartbeat();
|
|
380
|
-
this.pingInterval = setInterval(() => {
|
|
381
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN) return;
|
|
382
|
-
this.send({
|
|
383
|
-
type: "PING",
|
|
384
|
-
client_ts: Date.now()
|
|
385
|
-
});
|
|
386
|
-
}, 2e4);
|
|
387
|
-
}
|
|
388
|
-
stopHeartbeat() {
|
|
389
|
-
if (this.pingInterval) {
|
|
390
|
-
clearInterval(this.pingInterval);
|
|
391
|
-
this.pingInterval = null;
|
|
392
|
-
}
|
|
393
|
-
}
|
|
394
|
-
// ---------------- RESET ----------------
|
|
395
|
-
reset() {
|
|
396
|
-
Object.values(this.peers).forEach((pc) => pc.close());
|
|
397
|
-
this.peers = {};
|
|
398
|
-
this.initiators.clear();
|
|
399
|
-
this.pendingIceCandidates = {};
|
|
400
|
-
this.state.resetRemoteState();
|
|
401
|
-
}
|
|
402
|
-
async handleJoinApproved(msg) {
|
|
403
|
-
console.log("JOIN_APPROVED received, sending new JOIN...");
|
|
404
|
-
this.events.onEntryResponded?.({
|
|
405
|
-
participantId: msg.user_id,
|
|
406
|
-
decision: "approved"
|
|
407
|
-
});
|
|
408
|
-
this.isWaitingForApproval = false;
|
|
409
|
-
this.pendingRequestId = null;
|
|
410
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
411
|
-
this.send({
|
|
412
|
-
type: "JOIN",
|
|
413
|
-
room_id: this.room.id,
|
|
414
|
-
user_id: this.myId,
|
|
415
|
-
sender_name: this.participantName
|
|
416
|
-
});
|
|
417
|
-
console.log("Sent new JOIN after approval");
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
373
|
async handle(msg) {
|
|
421
|
-
var _a, _b, _c, _d;
|
|
422
374
|
if (msg.sender === this.myId) return;
|
|
423
375
|
switch (msg.type) {
|
|
424
376
|
case "PONG":
|
|
425
377
|
this.lastPong = Date.now();
|
|
426
378
|
break;
|
|
427
|
-
case "
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
});
|
|
431
|
-
await this.handleOffer(msg.payload, msg.sender);
|
|
432
|
-
break;
|
|
433
|
-
case "ANSWER": {
|
|
434
|
-
const pc = this.peers[msg.sender];
|
|
435
|
-
console.log("[Answer] Received from", msg.sender, {
|
|
436
|
-
signalingState: pc?.signalingState,
|
|
437
|
-
iceConnectionState: pc?.iceConnectionState,
|
|
438
|
-
connectionState: pc?.connectionState
|
|
439
|
-
});
|
|
440
|
-
if (!pc) return;
|
|
441
|
-
if (pc.signalingState !== "have-local-offer") {
|
|
442
|
-
console.warn(
|
|
443
|
-
`[Signaling] Unexpected ANSWER in state "${pc.signalingState}", ignoring`
|
|
444
|
-
);
|
|
445
|
-
return;
|
|
379
|
+
case "JOINED": {
|
|
380
|
+
if (msg.iceServers) {
|
|
381
|
+
this.iceServers = msg.iceServers;
|
|
446
382
|
}
|
|
447
|
-
|
|
448
|
-
|
|
383
|
+
this.room.name = msg.room_name;
|
|
384
|
+
this.isWaitingForApproval = false;
|
|
385
|
+
this.pendingRequestId = null;
|
|
386
|
+
this.intentionalDisconnect = false;
|
|
387
|
+
this.reconnectAttempts = 0;
|
|
388
|
+
this.setupPublisherPC();
|
|
389
|
+
this.setupSubscriberPC();
|
|
390
|
+
await this.createPublisherOffer();
|
|
391
|
+
this.startHeartbeat();
|
|
392
|
+
this.joinResolver?.();
|
|
393
|
+
this.joinResolver = void 0;
|
|
394
|
+
this.joinRejecter = void 0;
|
|
395
|
+
break;
|
|
396
|
+
}
|
|
397
|
+
case "PUB_ANSWER": {
|
|
398
|
+
if (this.pubPC) {
|
|
399
|
+
await this.pubPC.setRemoteDescription({
|
|
449
400
|
type: "answer",
|
|
450
401
|
sdp: msg.payload
|
|
451
402
|
});
|
|
452
|
-
await this.flushIce(msg.sender, pc);
|
|
453
|
-
} catch (err) {
|
|
454
|
-
console.error("[Signaling] Failed to apply answer:", err);
|
|
455
|
-
this.emitError(
|
|
456
|
-
"ANSWER_FAILED",
|
|
457
|
-
`Failed to apply answer from ${msg.sender}`,
|
|
458
|
-
err,
|
|
459
|
-
true
|
|
460
|
-
);
|
|
461
403
|
}
|
|
462
404
|
break;
|
|
463
405
|
}
|
|
464
|
-
case "
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
406
|
+
case "SUB_OFFER": {
|
|
407
|
+
this.pendingTracks.push(msg.track);
|
|
408
|
+
if (this.subPC) {
|
|
409
|
+
await this.subPC.setRemoteDescription({
|
|
410
|
+
type: "offer",
|
|
411
|
+
sdp: msg.payload
|
|
412
|
+
});
|
|
413
|
+
const answer = await this.subPC.createAnswer();
|
|
414
|
+
await this.subPC.setLocalDescription(answer);
|
|
415
|
+
this.send({
|
|
416
|
+
type: "SUB_ANSWER",
|
|
417
|
+
payload: answer.sdp,
|
|
418
|
+
user_id: this.myId
|
|
419
|
+
});
|
|
471
420
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
421
|
+
break;
|
|
422
|
+
}
|
|
423
|
+
case "PUB_ICE": {
|
|
424
|
+
if (this.pubPC && msg.payload) {
|
|
425
|
+
await this.pubPC.addIceCandidate(JSON.parse(msg.payload)).catch(console.warn);
|
|
476
426
|
}
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
427
|
+
break;
|
|
428
|
+
}
|
|
429
|
+
case "SUB_ICE": {
|
|
430
|
+
if (this.subPC && msg.payload) {
|
|
431
|
+
await this.subPC.addIceCandidate(JSON.parse(msg.payload)).catch(console.warn);
|
|
481
432
|
}
|
|
482
433
|
break;
|
|
483
434
|
}
|
|
484
|
-
case "EXISTING_USERS":
|
|
435
|
+
case "EXISTING_USERS": {
|
|
485
436
|
if (msg.presenterId) {
|
|
486
437
|
this.state.setPresenterId(msg.presenterId);
|
|
487
|
-
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
488
|
-
this.state.setPresenterId(msg.presenterId);
|
|
489
438
|
}
|
|
490
439
|
for (const p of msg.participants || []) {
|
|
491
440
|
if (!p?.id || p.id === this.myId) continue;
|
|
@@ -497,9 +446,6 @@ var VideoSDKCore = class {
|
|
|
497
446
|
media: {
|
|
498
447
|
stream: null,
|
|
499
448
|
screenStream: void 0,
|
|
500
|
-
cameraTrack: void 0,
|
|
501
|
-
screenTrack: void 0,
|
|
502
|
-
audioTrack: void 0,
|
|
503
449
|
micEnabled: p.micEnabled ?? true,
|
|
504
450
|
camEnabled: p.camEnabled ?? true,
|
|
505
451
|
isScreenSharing: p.isScreenSharing ?? false,
|
|
@@ -509,118 +455,53 @@ var VideoSDKCore = class {
|
|
|
509
455
|
};
|
|
510
456
|
this.state.addParticipant(structuredParticipant);
|
|
511
457
|
this.events.onUserJoined?.(structuredParticipant);
|
|
512
|
-
if (p.isScreenSharing && p.remoteScreenStreamId) {
|
|
513
|
-
this.state.setPresenterId(p.id);
|
|
514
|
-
this.state.updateParticipantMedia(p.id, {
|
|
515
|
-
isScreenSharing: true,
|
|
516
|
-
remoteScreenStreamId: p.remoteScreenStreamId,
|
|
517
|
-
cameraStreamId: p.cameraId || null
|
|
518
|
-
});
|
|
519
|
-
}
|
|
520
|
-
if (this.shouldInitiate(p.id)) {
|
|
521
|
-
await this.createOffer(p.id);
|
|
522
|
-
}
|
|
523
458
|
}
|
|
524
459
|
break;
|
|
525
|
-
case "JOINED": {
|
|
526
|
-
if (msg.iceServers) {
|
|
527
|
-
this.iceServers = msg.iceServers;
|
|
528
|
-
}
|
|
529
|
-
this.room.name = msg.room_name;
|
|
530
|
-
this.isWaitingForApproval = false;
|
|
531
|
-
this.pendingRequestId = null;
|
|
532
|
-
this.intentionalDisconnect = false;
|
|
533
|
-
this.reconnectAttempts = 0;
|
|
534
|
-
if (Object.keys(this.pendingOffers).length > 0) {
|
|
535
|
-
console.log(
|
|
536
|
-
"Processing",
|
|
537
|
-
Object.keys(this.pendingOffers).length,
|
|
538
|
-
"pending offers"
|
|
539
|
-
);
|
|
540
|
-
for (const [peerId2, sdp] of Object.entries(this.pendingOffers)) {
|
|
541
|
-
await this.handleOffer(sdp, peerId2);
|
|
542
|
-
}
|
|
543
|
-
this.pendingOffers = {};
|
|
544
|
-
}
|
|
545
|
-
const media = this.state.localParticipant?.media;
|
|
546
|
-
if (media) {
|
|
547
|
-
this.send({
|
|
548
|
-
type: "MEDIA_STATE",
|
|
549
|
-
kind: "audio",
|
|
550
|
-
enabled: !!media.micEnabled
|
|
551
|
-
});
|
|
552
|
-
this.send({
|
|
553
|
-
type: "MEDIA_STATE",
|
|
554
|
-
kind: "video",
|
|
555
|
-
enabled: !!media.camEnabled
|
|
556
|
-
});
|
|
557
|
-
}
|
|
558
|
-
this.startHeartbeat();
|
|
559
|
-
this.joinResolver?.();
|
|
560
|
-
this.joinResolver = void 0;
|
|
561
|
-
this.joinRejecter = void 0;
|
|
562
|
-
break;
|
|
563
460
|
}
|
|
564
461
|
case "USER_JOINED": {
|
|
565
462
|
const p = msg.participant;
|
|
566
463
|
if (!p?.id || p.id === this.myId) return;
|
|
567
464
|
this.state.addParticipant(p);
|
|
568
465
|
this.events.onUserJoined?.(p);
|
|
569
|
-
if (this.shouldInitiate(p.id)) {
|
|
570
|
-
await this.createOffer(p.id);
|
|
571
|
-
}
|
|
572
|
-
break;
|
|
573
|
-
}
|
|
574
|
-
case "JOIN_PENDING": {
|
|
575
|
-
const req = msg.request;
|
|
576
|
-
this.isWaitingForApproval = true;
|
|
577
|
-
this.pendingRequestId = req.request_id;
|
|
578
|
-
this.events.onEntryRequested?.({
|
|
579
|
-
requestId: req.request_id,
|
|
580
|
-
userId: req.user_id,
|
|
581
|
-
name: req.name
|
|
582
|
-
});
|
|
583
|
-
break;
|
|
584
|
-
}
|
|
585
|
-
case "JOIN_REQUEST": {
|
|
586
|
-
const req = msg.request;
|
|
587
|
-
this.events.onEntryRequested?.({
|
|
588
|
-
requestId: req.id,
|
|
589
|
-
userId: req.user_id,
|
|
590
|
-
name: req.name
|
|
591
|
-
});
|
|
592
|
-
break;
|
|
593
|
-
}
|
|
594
|
-
case "JOIN_APPROVED": {
|
|
595
|
-
await this.handleJoinApproved(msg);
|
|
596
|
-
break;
|
|
597
|
-
}
|
|
598
|
-
case "JOIN_REJECTED": {
|
|
599
|
-
const decision = "rejected";
|
|
600
|
-
this.isWaitingForApproval = false;
|
|
601
|
-
this.pendingRequestId = null;
|
|
602
|
-
this.events.onEntryResponded?.({
|
|
603
|
-
participantId: msg.user_id,
|
|
604
|
-
decision
|
|
605
|
-
});
|
|
606
466
|
break;
|
|
607
467
|
}
|
|
608
|
-
case "USER_LEFT":
|
|
468
|
+
case "USER_LEFT": {
|
|
609
469
|
const peerId = msg.participant.id;
|
|
610
|
-
this.closePeer(peerId);
|
|
611
470
|
this.state.removeParticipant(peerId);
|
|
612
471
|
this.events.onUserLeft?.(peerId);
|
|
613
472
|
break;
|
|
473
|
+
}
|
|
614
474
|
case "MEDIA_STATE_CHANGE": {
|
|
615
|
-
const
|
|
475
|
+
const peerId = msg.peerId;
|
|
616
476
|
const { kind, enabled } = msg;
|
|
617
477
|
if (kind === "audio") {
|
|
618
|
-
this.state.updateParticipantMedia(
|
|
619
|
-
this.events.onMicToggled?.(
|
|
478
|
+
this.state.updateParticipantMedia(peerId, { micEnabled: enabled });
|
|
479
|
+
this.events.onMicToggled?.(peerId, enabled);
|
|
620
480
|
} else if (kind === "video") {
|
|
621
|
-
this.state.updateParticipantMedia(
|
|
622
|
-
this.events.onCamToggled?.(
|
|
481
|
+
this.state.updateParticipantMedia(peerId, { camEnabled: enabled });
|
|
482
|
+
this.events.onCamToggled?.(peerId, enabled);
|
|
483
|
+
}
|
|
484
|
+
break;
|
|
485
|
+
}
|
|
486
|
+
case "SCREEN_SHARE_START": {
|
|
487
|
+
const peerId = msg.peerId;
|
|
488
|
+
this.state.updateParticipantMedia(peerId, {
|
|
489
|
+
isScreenSharing: true,
|
|
490
|
+
remoteScreenStreamId: msg.stream_id,
|
|
491
|
+
cameraStreamId: msg?.camera_stream_id
|
|
492
|
+
});
|
|
493
|
+
if (!this.state.presenterId) {
|
|
494
|
+
this.state.setPresenterId(peerId);
|
|
495
|
+
}
|
|
496
|
+
break;
|
|
497
|
+
}
|
|
498
|
+
case "SCREEN_SHARE_STOP": {
|
|
499
|
+
const peerId = msg.peerId;
|
|
500
|
+
this.state.updateParticipantMedia(peerId, { isScreenSharing: false });
|
|
501
|
+
if (this.state.presenterId === peerId) {
|
|
502
|
+
this.state.setPresenterId(null);
|
|
623
503
|
}
|
|
504
|
+
this.events.onScreenShareStopped?.(peerId);
|
|
624
505
|
break;
|
|
625
506
|
}
|
|
626
507
|
case "CHAT_MESSAGE": {
|
|
@@ -637,27 +518,37 @@ var VideoSDKCore = class {
|
|
|
637
518
|
this.events.onChatMessage?.(msg);
|
|
638
519
|
break;
|
|
639
520
|
}
|
|
640
|
-
case "
|
|
641
|
-
const
|
|
642
|
-
this.
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
521
|
+
case "JOIN_PENDING": {
|
|
522
|
+
const req = msg.request;
|
|
523
|
+
this.isWaitingForApproval = true;
|
|
524
|
+
this.pendingRequestId = req.request_id;
|
|
525
|
+
this.events.onEntryRequested?.({
|
|
526
|
+
requestId: req.request_id,
|
|
527
|
+
userId: req.user_id,
|
|
528
|
+
name: req.name
|
|
646
529
|
});
|
|
647
|
-
if (!this.state.presenterId) {
|
|
648
|
-
this.state.setPresenterId(peerId2);
|
|
649
|
-
}
|
|
650
|
-
const screenStream = this.state.getParticipant(peerId2)?.media?.screenStream;
|
|
651
|
-
this.events.onScreenShareStarted?.(peerId2, screenStream || null);
|
|
652
530
|
break;
|
|
653
531
|
}
|
|
654
|
-
case "
|
|
655
|
-
|
|
656
|
-
this.
|
|
657
|
-
if (this.
|
|
658
|
-
this.
|
|
532
|
+
case "JOIN_APPROVED": {
|
|
533
|
+
this.isWaitingForApproval = false;
|
|
534
|
+
this.pendingRequestId = null;
|
|
535
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
536
|
+
this.send({
|
|
537
|
+
type: "JOIN",
|
|
538
|
+
room_id: this.room.id,
|
|
539
|
+
user_id: this.myId,
|
|
540
|
+
sender_name: this.participantName
|
|
541
|
+
});
|
|
659
542
|
}
|
|
660
|
-
|
|
543
|
+
break;
|
|
544
|
+
}
|
|
545
|
+
case "JOIN_REJECTED": {
|
|
546
|
+
this.isWaitingForApproval = false;
|
|
547
|
+
this.pendingRequestId = null;
|
|
548
|
+
this.events.onEntryResponded?.({
|
|
549
|
+
participantId: msg.user_id,
|
|
550
|
+
decision: "rejected"
|
|
551
|
+
});
|
|
661
552
|
break;
|
|
662
553
|
}
|
|
663
554
|
case "ERROR": {
|
|
@@ -668,271 +559,92 @@ var VideoSDKCore = class {
|
|
|
668
559
|
msg,
|
|
669
560
|
!fatal
|
|
670
561
|
);
|
|
671
|
-
if (fatal)
|
|
672
|
-
this.disconnect();
|
|
673
|
-
}
|
|
562
|
+
if (fatal) this.disconnect();
|
|
674
563
|
return;
|
|
675
564
|
}
|
|
676
565
|
}
|
|
677
566
|
}
|
|
678
|
-
// ----------------
|
|
679
|
-
|
|
680
|
-
if (!this.
|
|
681
|
-
if (!this.iceServers || this.iceServers.length === 0) {
|
|
682
|
-
throw new Error(
|
|
683
|
-
"ICE Servers not configured. Backend must provide iceServers on JOIN."
|
|
684
|
-
);
|
|
685
|
-
}
|
|
686
|
-
const pc = new RTCPeerConnection({
|
|
687
|
-
iceServers: this.iceServers,
|
|
688
|
-
iceTransportPolicy: this.iceTransportPolicy
|
|
689
|
-
});
|
|
690
|
-
pc.ontrack = (event) => {
|
|
691
|
-
const incomingStream = event.streams?.[0] || new MediaStream([event.track]);
|
|
692
|
-
const streamId = incomingStream?.id?.replace(/[{}]/g, "");
|
|
693
|
-
const participant = this.state.getParticipant(id);
|
|
694
|
-
const isCameraStream = streamId && participant?.media?.cameraStreamId && streamId === participant?.media?.cameraStreamId;
|
|
695
|
-
const isScreenStream = streamId && participant?.media?.remoteScreenStreamId && streamId === participant?.media?.remoteScreenStreamId;
|
|
696
|
-
if (event.track.muted) {
|
|
697
|
-
event.track.onunmute = () => {
|
|
698
|
-
console.log(`${event.track.kind} track unmuted for ${id}`);
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
if (isScreenStream) {
|
|
702
|
-
const videoTrack = event.track.kind === "video" ? event.track : incomingStream.getVideoTracks()[0] || participant?.media?.screenTrack;
|
|
703
|
-
this.state.updateParticipantMedia(id, {
|
|
704
|
-
screenStream: incomingStream,
|
|
705
|
-
screenTrack: videoTrack,
|
|
706
|
-
remoteScreenStreamId: incomingStream.id,
|
|
707
|
-
isScreenSharing: true
|
|
708
|
-
});
|
|
709
|
-
if (!this.state.presenterId) {
|
|
710
|
-
this.state.setPresenterId(id);
|
|
711
|
-
}
|
|
712
|
-
this.events.onScreenShareStarted?.(id, incomingStream);
|
|
713
|
-
console.log(`Screen share stream detected for ${id}`);
|
|
714
|
-
} else if (isCameraStream) {
|
|
715
|
-
this.state.updateParticipantMedia(id, {
|
|
716
|
-
stream: incomingStream,
|
|
717
|
-
cameraTrack: incomingStream.getVideoTracks()[0],
|
|
718
|
-
audioTrack: incomingStream.getAudioTracks()[0]
|
|
719
|
-
});
|
|
720
|
-
this.events.onTrack?.(incomingStream, id);
|
|
721
|
-
} else {
|
|
722
|
-
this.state.updateParticipantMedia(id, {
|
|
723
|
-
stream: incomingStream,
|
|
724
|
-
cameraTrack: incomingStream.getVideoTracks()[0],
|
|
725
|
-
audioTrack: incomingStream.getAudioTracks()[0]
|
|
726
|
-
});
|
|
727
|
-
this.events.onTrack?.(incomingStream, id);
|
|
728
|
-
}
|
|
729
|
-
};
|
|
730
|
-
pc.onicecandidate = (e) => {
|
|
731
|
-
if (!e.candidate) return;
|
|
732
|
-
console.log("LOCAL ICE:", e.candidate.candidate);
|
|
733
|
-
this.send({
|
|
734
|
-
type: "ICE",
|
|
735
|
-
payload: JSON.stringify(e.candidate),
|
|
736
|
-
sender: this.myId,
|
|
737
|
-
target: id
|
|
738
|
-
});
|
|
739
|
-
};
|
|
740
|
-
pc.oniceconnectionstatechange = async () => {
|
|
741
|
-
console.log("================================");
|
|
742
|
-
console.log(`Peer: ${id}`);
|
|
743
|
-
console.log(`ICE: ${pc.iceConnectionState}`);
|
|
744
|
-
console.log(`Connection: ${pc.connectionState}`);
|
|
745
|
-
console.log(`Signaling: ${pc.signalingState}`);
|
|
746
|
-
if (pc.iceConnectionState === "disconnected") {
|
|
747
|
-
const stats = await pc.getStats();
|
|
748
|
-
stats.forEach((report) => {
|
|
749
|
-
if (report.type === "transport") {
|
|
750
|
-
console.log(JSON.stringify(report, null, 2));
|
|
751
|
-
}
|
|
752
|
-
});
|
|
753
|
-
}
|
|
754
|
-
};
|
|
755
|
-
pc.onconnectionstatechange = () => {
|
|
756
|
-
if (pc.connectionState === "failed") {
|
|
757
|
-
try {
|
|
758
|
-
pc.restartIce();
|
|
759
|
-
} catch {
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
};
|
|
763
|
-
this.localStream.getTracks().forEach((track) => {
|
|
764
|
-
pc.addTrack(track, this.localStream);
|
|
765
|
-
});
|
|
766
|
-
if (this.isScreenSharing && this.screenStream) {
|
|
767
|
-
this.screenSenders[id] = [];
|
|
768
|
-
this.screenStream.getTracks().forEach((track) => {
|
|
769
|
-
const sender = pc.addTrack(track, this.screenStream);
|
|
770
|
-
this.screenSenders[id].push(sender);
|
|
771
|
-
});
|
|
772
|
-
}
|
|
773
|
-
return pc;
|
|
774
|
-
}
|
|
775
|
-
// ---------------- OFFER ----------------
|
|
776
|
-
async createOffer(id, isRenegotiation = false) {
|
|
777
|
-
if (!isRenegotiation && !this.shouldInitiate(id)) {
|
|
778
|
-
console.debug(
|
|
779
|
-
`[Offer] ${id} should initiate (${id} > ${this.myId}), skipping`
|
|
780
|
-
);
|
|
781
|
-
return;
|
|
782
|
-
}
|
|
783
|
-
if (!isRenegotiation && this.initiators.has(id)) {
|
|
784
|
-
console.debug(
|
|
785
|
-
`[Offer] Already initiating with ${id}, skipping duplicate`
|
|
786
|
-
);
|
|
787
|
-
return;
|
|
788
|
-
}
|
|
789
|
-
if (!isRenegotiation) {
|
|
790
|
-
this.initiators.add(id);
|
|
791
|
-
}
|
|
792
|
-
if (!this.peers[id]) {
|
|
793
|
-
this.peers[id] = this.createPeer(id);
|
|
794
|
-
}
|
|
795
|
-
const pc = this.peers[id];
|
|
567
|
+
// ---------------- PUBLISHER RENEGOTIATION ----------------
|
|
568
|
+
async createPublisherOffer() {
|
|
569
|
+
if (!this.pubPC) return;
|
|
796
570
|
try {
|
|
797
|
-
const offer = await
|
|
798
|
-
await
|
|
571
|
+
const offer = await this.pubPC.createOffer();
|
|
572
|
+
await this.pubPC.setLocalDescription(offer);
|
|
799
573
|
this.send({
|
|
800
|
-
type: "
|
|
574
|
+
type: "PUB_OFFER",
|
|
801
575
|
payload: offer.sdp,
|
|
802
|
-
|
|
803
|
-
|
|
576
|
+
user_id: this.myId,
|
|
577
|
+
room_id: this.room.id
|
|
804
578
|
});
|
|
805
|
-
console.debug(`[Offer] Sent to ${id}`);
|
|
806
579
|
} catch (err) {
|
|
807
|
-
console.error(
|
|
580
|
+
console.error("[SFU Publisher Offer Error]", err);
|
|
808
581
|
}
|
|
809
582
|
}
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
this.
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
}
|
|
823
|
-
const pc = this.peers[id];
|
|
824
|
-
try {
|
|
825
|
-
if (pc.signalingState === "have-local-offer") {
|
|
826
|
-
if (this.shouldInitiate(id)) {
|
|
827
|
-
console.warn(
|
|
828
|
-
`[Glare] Both sent OFFERs, we win (${this.myId} < ${id}), keeping our OFFER`
|
|
829
|
-
);
|
|
830
|
-
return;
|
|
831
|
-
} else {
|
|
832
|
-
console.warn(
|
|
833
|
-
`[Glare] Both sent OFFERs, they win (${id} < ${this.myId}), rolling back`
|
|
834
|
-
);
|
|
835
|
-
pc.close();
|
|
836
|
-
delete this.peers[id];
|
|
837
|
-
this.initiators.delete(id);
|
|
838
|
-
this.peers[id] = this.createPeer(id);
|
|
839
|
-
}
|
|
840
|
-
}
|
|
841
|
-
if (this.peers[id].signalingState !== "stable" && this.peers[id].signalingState !== "have-local-offer") {
|
|
842
|
-
console.warn(
|
|
843
|
-
`[Signaling] Cannot accept OFFER in state "${this.peers[id].signalingState}"`
|
|
844
|
-
);
|
|
845
|
-
return;
|
|
846
|
-
}
|
|
847
|
-
await this.peers[id].setRemoteDescription({
|
|
848
|
-
type: "offer",
|
|
849
|
-
sdp
|
|
850
|
-
});
|
|
851
|
-
const pending = this.pendingIceCandidates[id] || [];
|
|
852
|
-
for (const candidate of pending) {
|
|
853
|
-
try {
|
|
854
|
-
await this.peers[id].addIceCandidate(candidate);
|
|
855
|
-
} catch (err) {
|
|
856
|
-
console.warn("[ICE] Failed to add candidate:", err);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
delete this.pendingIceCandidates[id];
|
|
860
|
-
const answer = await this.peers[id].createAnswer();
|
|
861
|
-
await this.peers[id].setLocalDescription(answer);
|
|
862
|
-
await this.flushIce(id, this.peers[id]);
|
|
863
|
-
this.send({
|
|
864
|
-
type: "ANSWER",
|
|
865
|
-
payload: answer.sdp,
|
|
866
|
-
sender: this.myId,
|
|
867
|
-
target: id
|
|
868
|
-
});
|
|
869
|
-
console.debug(`[Answer] Sent to ${id}`);
|
|
870
|
-
} catch (err) {
|
|
871
|
-
console.error(`[Signaling] Failed to handle OFFER from ${id}:`, err);
|
|
872
|
-
this.emitError(
|
|
873
|
-
"OFFER_HANDLING_FAILED",
|
|
874
|
-
`Failed to handle offer from ${id}`,
|
|
875
|
-
err,
|
|
876
|
-
true
|
|
877
|
-
);
|
|
878
|
-
}
|
|
583
|
+
// ---------------- MEDIA TOGGLES ----------------
|
|
584
|
+
toggleMic() {
|
|
585
|
+
const mediaState = this.state.localParticipant?.media;
|
|
586
|
+
if (!mediaState) return;
|
|
587
|
+
const nextEnabled = !mediaState.micEnabled;
|
|
588
|
+
this.localStream?.getAudioTracks().forEach((t) => t.enabled = nextEnabled);
|
|
589
|
+
this.state.updateLocalParticipant({
|
|
590
|
+
id: this.myId,
|
|
591
|
+
name: this.participantName,
|
|
592
|
+
media: { ...mediaState, micEnabled: nextEnabled }
|
|
593
|
+
});
|
|
594
|
+
this.send({ type: "MEDIA_STATE", kind: "audio", enabled: nextEnabled });
|
|
879
595
|
}
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
this.
|
|
596
|
+
toggleCam() {
|
|
597
|
+
const mediaState = this.state.localParticipant?.media;
|
|
598
|
+
if (!mediaState) return;
|
|
599
|
+
const nextEnabled = !mediaState.camEnabled;
|
|
600
|
+
this.localStream?.getVideoTracks().forEach((t) => t.enabled = nextEnabled);
|
|
601
|
+
this.state.updateLocalParticipant({
|
|
602
|
+
id: this.myId,
|
|
603
|
+
name: this.participantName,
|
|
604
|
+
media: { ...mediaState, camEnabled: nextEnabled }
|
|
605
|
+
});
|
|
606
|
+
this.send({ type: "MEDIA_STATE", kind: "video", enabled: nextEnabled });
|
|
891
607
|
}
|
|
608
|
+
// ---------------- SCREEN SHARING (SFU) ----------------
|
|
892
609
|
async startScreenShare() {
|
|
893
610
|
try {
|
|
894
611
|
if (this.state.presenterId && this.state.presenterId !== this.myId) {
|
|
895
612
|
throw new Error("Another user is already sharing their screen.");
|
|
896
613
|
}
|
|
897
|
-
if (!navigator.mediaDevices?.getDisplayMedia) {
|
|
898
|
-
throw new Error("Screen sharing not supported on this device");
|
|
899
|
-
}
|
|
900
614
|
this.screenStream = await navigator.mediaDevices.getDisplayMedia({
|
|
901
615
|
video: true
|
|
902
|
-
// audio: true,
|
|
903
616
|
});
|
|
904
617
|
this.isScreenSharing = true;
|
|
618
|
+
const screenTrack = this.screenStream.getVideoTracks()[0];
|
|
619
|
+
Object.defineProperty(screenTrack, "contentHint", {
|
|
620
|
+
value: "detail"
|
|
621
|
+
});
|
|
622
|
+
if (this.pubPC) {
|
|
623
|
+
this.screenSender = this.pubPC.addTrack(screenTrack, this.screenStream);
|
|
624
|
+
await this.createPublisherOffer();
|
|
625
|
+
}
|
|
905
626
|
this.state.updateLocalParticipant({
|
|
906
627
|
media: {
|
|
907
628
|
isScreenSharing: true,
|
|
908
629
|
screenStream: this.screenStream,
|
|
909
|
-
screenTrack
|
|
630
|
+
screenTrack
|
|
910
631
|
}
|
|
911
632
|
});
|
|
912
633
|
this.state.setPresenterId(this.myId);
|
|
913
|
-
|
|
634
|
+
screenTrack.onended = () => {
|
|
914
635
|
this.stopScreenShare();
|
|
915
636
|
};
|
|
916
|
-
Object.entries(this.peers).forEach(([peerId, pc]) => {
|
|
917
|
-
this.screenSenders[peerId] = [];
|
|
918
|
-
this.screenStream.getTracks().forEach((track) => {
|
|
919
|
-
const sender = pc.addTrack(track, this.screenStream);
|
|
920
|
-
this.screenSenders[peerId].push(sender);
|
|
921
|
-
});
|
|
922
|
-
this.createOffer(peerId, true);
|
|
923
|
-
});
|
|
924
637
|
this.send({
|
|
925
638
|
type: "SCREEN_SHARE_START",
|
|
926
639
|
sender: this.myId,
|
|
927
640
|
room_id: this.room.id,
|
|
928
|
-
camera_id: this.localStream?.id.replace(/[{}]/g, ""),
|
|
929
641
|
stream_id: this.screenStream.id.replace(/[{}]/g, "")
|
|
930
642
|
});
|
|
931
643
|
return this.screenStream;
|
|
932
644
|
} catch (err) {
|
|
933
645
|
this.emitError(
|
|
934
646
|
"SCREEN_SHARE_FAILED",
|
|
935
|
-
err?.message || "Failed
|
|
647
|
+
err?.message || "Failed screen share",
|
|
936
648
|
err,
|
|
937
649
|
true
|
|
938
650
|
);
|
|
@@ -941,21 +653,14 @@ var VideoSDKCore = class {
|
|
|
941
653
|
throw err;
|
|
942
654
|
}
|
|
943
655
|
}
|
|
944
|
-
stopScreenShare() {
|
|
656
|
+
async stopScreenShare() {
|
|
945
657
|
if (!this.screenStream) return;
|
|
946
658
|
this.screenStream.getTracks().forEach((t) => t.stop());
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
} catch (err) {
|
|
953
|
-
console.warn(err);
|
|
954
|
-
}
|
|
955
|
-
});
|
|
956
|
-
delete this.screenSenders[peerId];
|
|
957
|
-
this.createOffer(peerId, true);
|
|
958
|
-
});
|
|
659
|
+
if (this.pubPC && this.screenSender) {
|
|
660
|
+
this.pubPC.removeTrack(this.screenSender);
|
|
661
|
+
this.screenSender = null;
|
|
662
|
+
await this.createPublisherOffer();
|
|
663
|
+
}
|
|
959
664
|
this.screenStream = null;
|
|
960
665
|
this.isScreenSharing = false;
|
|
961
666
|
this.state.updateLocalParticipant({
|
|
@@ -974,15 +679,10 @@ var VideoSDKCore = class {
|
|
|
974
679
|
room_id: this.room.id
|
|
975
680
|
});
|
|
976
681
|
}
|
|
682
|
+
// ---------------- CHAT & RECONNECT ----------------
|
|
977
683
|
sendChatMessage(payload) {
|
|
978
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
979
|
-
console.warn("WS not connected");
|
|
980
|
-
return;
|
|
981
|
-
}
|
|
982
|
-
if (!this.room.id) {
|
|
983
|
-
console.warn("No roomId set");
|
|
684
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.room.id)
|
|
984
685
|
return;
|
|
985
|
-
}
|
|
986
686
|
const isPrivate = !!payload?.target;
|
|
987
687
|
const senderName = this.state.localParticipant?.name || "Anonymous";
|
|
988
688
|
const msg = {
|
|
@@ -1006,12 +706,48 @@ var VideoSDKCore = class {
|
|
|
1006
706
|
client_ts: Date.now()
|
|
1007
707
|
});
|
|
1008
708
|
}
|
|
709
|
+
scheduleReconnect() {
|
|
710
|
+
if (!this.room.id) return;
|
|
711
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
712
|
+
window.clearTimeout(this.reconnectTimer);
|
|
713
|
+
this.reconnectTimer = window.setTimeout(async () => {
|
|
714
|
+
try {
|
|
715
|
+
await this.connect(this.room.id, this.participantName);
|
|
716
|
+
this.reconnectAttempts = 0;
|
|
717
|
+
} catch {
|
|
718
|
+
this.reconnectAttempts++;
|
|
719
|
+
this.scheduleReconnect();
|
|
720
|
+
}
|
|
721
|
+
}, delay);
|
|
722
|
+
}
|
|
723
|
+
startHeartbeat() {
|
|
724
|
+
this.stopHeartbeat();
|
|
725
|
+
this.pingInterval = setInterval(() => {
|
|
726
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
727
|
+
this.send({ type: "PING", client_ts: Date.now() });
|
|
728
|
+
}
|
|
729
|
+
}, 2e4);
|
|
730
|
+
}
|
|
731
|
+
stopHeartbeat() {
|
|
732
|
+
if (this.pingInterval) {
|
|
733
|
+
clearInterval(this.pingInterval);
|
|
734
|
+
this.pingInterval = null;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
reset() {
|
|
738
|
+
this.pubPC?.close();
|
|
739
|
+
this.subPC?.close();
|
|
740
|
+
this.pubPC = null;
|
|
741
|
+
this.subPC = null;
|
|
742
|
+
this.state.resetRemoteState();
|
|
743
|
+
}
|
|
1009
744
|
disconnect() {
|
|
1010
745
|
this.intentionalDisconnect = true;
|
|
1011
746
|
this.stopScreenShare();
|
|
1012
|
-
|
|
1013
|
-
this.
|
|
1014
|
-
this.
|
|
747
|
+
this.pubPC?.close();
|
|
748
|
+
this.subPC?.close();
|
|
749
|
+
this.pubPC = null;
|
|
750
|
+
this.subPC = null;
|
|
1015
751
|
this.stopHeartbeat();
|
|
1016
752
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1017
753
|
this.send({
|
|
@@ -1038,32 +774,40 @@ var VideoSDKCore = class {
|
|
|
1038
774
|
this.state.clearChat();
|
|
1039
775
|
this.state.setPresenterId(null);
|
|
1040
776
|
}
|
|
1041
|
-
async
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
console.warn("ICE flush error", e);
|
|
1049
|
-
}
|
|
777
|
+
async restartPublisherIce() {
|
|
778
|
+
if (!this.pubPC) return;
|
|
779
|
+
try {
|
|
780
|
+
this.pubPC.restartIce();
|
|
781
|
+
await this.createPublisherOffer();
|
|
782
|
+
} catch (err) {
|
|
783
|
+
console.error("[Publisher ICE Restart Failed]", err);
|
|
1050
784
|
}
|
|
1051
|
-
|
|
785
|
+
}
|
|
786
|
+
emitError(code, message, raw, recoverable = true) {
|
|
787
|
+
const err = {
|
|
788
|
+
code,
|
|
789
|
+
message,
|
|
790
|
+
raw,
|
|
791
|
+
roomId: this.room.id,
|
|
792
|
+
userId: this.myId,
|
|
793
|
+
recoverable
|
|
794
|
+
};
|
|
795
|
+
this.events.onError?.(err);
|
|
796
|
+
this.joinRejecter?.(err);
|
|
797
|
+
this.joinRejecter = void 0;
|
|
798
|
+
console.error("[MeetingSDK Error]", err);
|
|
1052
799
|
}
|
|
1053
800
|
send(msg) {
|
|
1054
801
|
this.ws?.send(JSON.stringify(msg));
|
|
1055
802
|
}
|
|
1056
803
|
approveJoinRequest(requestId) {
|
|
1057
|
-
this.send({
|
|
1058
|
-
type: "JOIN_APPROVE",
|
|
1059
|
-
request_id: requestId
|
|
1060
|
-
});
|
|
804
|
+
this.send({ type: "JOIN_APPROVE", request_id: requestId });
|
|
1061
805
|
}
|
|
1062
806
|
rejectJoinRequest(requestId) {
|
|
1063
|
-
this.send({
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
807
|
+
this.send({ type: "JOIN_REJECT", request_id: requestId });
|
|
808
|
+
}
|
|
809
|
+
getMeeting() {
|
|
810
|
+
return this.room;
|
|
1067
811
|
}
|
|
1068
812
|
};
|
|
1069
813
|
|