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