@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.js
CHANGED
|
@@ -40,8 +40,8 @@ var import_react2 = require("react");
|
|
|
40
40
|
|
|
41
41
|
// src/config/ws.ts
|
|
42
42
|
var SDK_CONFIG = {
|
|
43
|
-
wsUrl: "wss://
|
|
44
|
-
baseUrl: "https://
|
|
43
|
+
wsUrl: "wss://localhost:8080/ws",
|
|
44
|
+
baseUrl: "https://localhost:8080"
|
|
45
45
|
};
|
|
46
46
|
|
|
47
47
|
// src/core/MeetingState.ts
|
|
@@ -198,10 +198,11 @@ var VideoSDKCore = class {
|
|
|
198
198
|
this.events = events;
|
|
199
199
|
this.url = url;
|
|
200
200
|
this.ws = null;
|
|
201
|
-
this.
|
|
202
|
-
this.
|
|
203
|
-
this.
|
|
201
|
+
this.pubPC = null;
|
|
202
|
+
this.subPC = null;
|
|
203
|
+
this.pendingTracks = [];
|
|
204
204
|
this.iceServers = [];
|
|
205
|
+
this.lastPong = Date.now();
|
|
205
206
|
this.intentionalDisconnect = false;
|
|
206
207
|
this.room = {
|
|
207
208
|
id: null,
|
|
@@ -209,14 +210,11 @@ var VideoSDKCore = class {
|
|
|
209
210
|
};
|
|
210
211
|
this.localStream = null;
|
|
211
212
|
this.screenStream = null;
|
|
213
|
+
this.screenSender = null;
|
|
212
214
|
this.isScreenSharing = false;
|
|
213
|
-
this.screenSenders = {};
|
|
214
215
|
this.pingInterval = null;
|
|
215
|
-
this.pendingIceCandidates = {};
|
|
216
|
-
this.pendingOffers = {};
|
|
217
216
|
this.reconnectAttempts = 0;
|
|
218
217
|
this.participantName = "";
|
|
219
|
-
// Track if we're in the waiting room (pending approval)
|
|
220
218
|
this.isWaitingForApproval = false;
|
|
221
219
|
this.pendingRequestId = null;
|
|
222
220
|
this.iceTransportPolicy = "all";
|
|
@@ -226,29 +224,12 @@ var VideoSDKCore = class {
|
|
|
226
224
|
this.myId = localStorage.getItem("vsdk_id") || crypto.randomUUID();
|
|
227
225
|
localStorage.setItem("vsdk_id", this.myId);
|
|
228
226
|
}
|
|
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 ----------------
|
|
227
|
+
// ---------------- MEDIA SETUP ----------------
|
|
244
228
|
async initLocal(video, name) {
|
|
245
229
|
this.participantName = name;
|
|
246
230
|
try {
|
|
247
231
|
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
248
|
-
video: {
|
|
249
|
-
width: { ideal: 1280 },
|
|
250
|
-
height: { ideal: 720 }
|
|
251
|
-
},
|
|
232
|
+
video: { width: { ideal: 1280 }, height: { ideal: 720 } },
|
|
252
233
|
audio: {
|
|
253
234
|
echoCancellation: true,
|
|
254
235
|
noiseSuppression: true,
|
|
@@ -266,6 +247,8 @@ var VideoSDKCore = class {
|
|
|
266
247
|
name: this.participantName,
|
|
267
248
|
media: {
|
|
268
249
|
stream: this.localStream,
|
|
250
|
+
cameraTrack: this.localStream.getVideoTracks()[0],
|
|
251
|
+
audioTrack: this.localStream.getAudioTracks()[0],
|
|
269
252
|
micEnabled: true,
|
|
270
253
|
camEnabled: true,
|
|
271
254
|
isScreenSharing: false
|
|
@@ -277,7 +260,109 @@ var VideoSDKCore = class {
|
|
|
277
260
|
throw err;
|
|
278
261
|
}
|
|
279
262
|
}
|
|
280
|
-
|
|
263
|
+
async joinMeeting(config) {
|
|
264
|
+
const { roomId, name, audioMuted = false, videoMuted = false } = config;
|
|
265
|
+
if (!roomId || !name) {
|
|
266
|
+
throw new Error("roomId and name are required to join meeting");
|
|
267
|
+
}
|
|
268
|
+
this.participantName = name;
|
|
269
|
+
if (!this.localStream) {
|
|
270
|
+
this.localStream = await navigator.mediaDevices.getUserMedia({
|
|
271
|
+
video: true,
|
|
272
|
+
audio: true
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
this.localStream.getAudioTracks().forEach((t) => t.enabled = !audioMuted);
|
|
276
|
+
this.localStream.getVideoTracks().forEach((t) => t.enabled = !videoMuted);
|
|
277
|
+
this.state.updateLocalParticipant({
|
|
278
|
+
id: this.myId,
|
|
279
|
+
name: this.participantName,
|
|
280
|
+
media: {
|
|
281
|
+
stream: this.localStream,
|
|
282
|
+
cameraTrack: this.localStream.getVideoTracks()[0],
|
|
283
|
+
audioTrack: this.localStream.getAudioTracks()[0],
|
|
284
|
+
micEnabled: !audioMuted,
|
|
285
|
+
camEnabled: !videoMuted,
|
|
286
|
+
isScreenSharing: false
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
this.state.localStream = this.localStream;
|
|
290
|
+
await this.connect(roomId, name);
|
|
291
|
+
}
|
|
292
|
+
// ---------------- SFU PEER CONNECTION CREATION ----------------
|
|
293
|
+
setupPublisherPC() {
|
|
294
|
+
if (!this.localStream) return;
|
|
295
|
+
this.pubPC = new RTCPeerConnection({
|
|
296
|
+
iceServers: this.iceServers,
|
|
297
|
+
iceTransportPolicy: this.iceTransportPolicy
|
|
298
|
+
});
|
|
299
|
+
this.localStream.getTracks().forEach((track) => {
|
|
300
|
+
this.pubPC?.addTrack(track, this.localStream);
|
|
301
|
+
});
|
|
302
|
+
this.pubPC.onicecandidate = (e) => {
|
|
303
|
+
if (e.candidate) {
|
|
304
|
+
this.send({
|
|
305
|
+
type: "PUB_ICE",
|
|
306
|
+
payload: JSON.stringify(e.candidate),
|
|
307
|
+
user_id: this.myId
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
this.pubPC.onconnectionstatechange = () => {
|
|
312
|
+
console.log(`[SFU Publisher PC State]`, this.pubPC?.connectionState);
|
|
313
|
+
if (this.pubPC?.connectionState === "failed") {
|
|
314
|
+
this.restartPublisherIce();
|
|
315
|
+
}
|
|
316
|
+
};
|
|
317
|
+
}
|
|
318
|
+
setupSubscriberPC() {
|
|
319
|
+
this.subPC = new RTCPeerConnection({
|
|
320
|
+
iceServers: this.iceServers,
|
|
321
|
+
iceTransportPolicy: this.iceTransportPolicy
|
|
322
|
+
});
|
|
323
|
+
this.subPC.onicecandidate = (e) => {
|
|
324
|
+
if (e.candidate) {
|
|
325
|
+
this.send({
|
|
326
|
+
type: "SUB_ICE",
|
|
327
|
+
payload: JSON.stringify(e.candidate),
|
|
328
|
+
user_id: this.myId
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
};
|
|
332
|
+
this.subPC.ontrack = (event) => {
|
|
333
|
+
const descriptor = this.pendingTracks.shift();
|
|
334
|
+
if (!descriptor) {
|
|
335
|
+
console.warn("Unknown incoming track");
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
const stream = event.streams[0] || new MediaStream([event.track]);
|
|
339
|
+
switch (descriptor.source) {
|
|
340
|
+
case "camera":
|
|
341
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
342
|
+
stream,
|
|
343
|
+
cameraTrack: event.track
|
|
344
|
+
});
|
|
345
|
+
break;
|
|
346
|
+
case "screen":
|
|
347
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
348
|
+
screenStream: stream,
|
|
349
|
+
screenTrack: event.track,
|
|
350
|
+
isScreenSharing: true
|
|
351
|
+
});
|
|
352
|
+
break;
|
|
353
|
+
case "audio":
|
|
354
|
+
this.state.updateParticipantMedia(descriptor.publisher_id, {
|
|
355
|
+
stream,
|
|
356
|
+
audioTrack: event.track
|
|
357
|
+
});
|
|
358
|
+
break;
|
|
359
|
+
}
|
|
360
|
+
};
|
|
361
|
+
this.subPC.onconnectionstatechange = () => {
|
|
362
|
+
console.log(`[SFU Subscriber PC State]`, this.subPC?.connectionState);
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
// ---------------- WEBSOCKET CONNECTION & SIGNALING ----------------
|
|
281
366
|
async connect(roomId, name) {
|
|
282
367
|
this.room.id = roomId;
|
|
283
368
|
this.reset();
|
|
@@ -286,15 +371,17 @@ var VideoSDKCore = class {
|
|
|
286
371
|
this.joinRejecter = reject;
|
|
287
372
|
this.ws = new WebSocket(this.url);
|
|
288
373
|
this.ws.onopen = () => {
|
|
289
|
-
console.log("WebSocket connected, sending JOIN...");
|
|
374
|
+
console.log("WebSocket connected to SFU, sending JOIN...");
|
|
375
|
+
const micEnabled = !!this.state.localParticipant?.media?.micEnabled;
|
|
376
|
+
const camEnabled = !!this.state.localParticipant?.media?.camEnabled;
|
|
290
377
|
this.send({
|
|
291
378
|
type: "JOIN",
|
|
292
379
|
room_id: roomId,
|
|
293
380
|
user_id: this.myId,
|
|
294
381
|
sender_name: name,
|
|
295
382
|
camera_stream_id: this.localStream?.id.replace(/[{}]/g, ""),
|
|
296
|
-
audio_muted:
|
|
297
|
-
video_muted:
|
|
383
|
+
audio_muted: !micEnabled,
|
|
384
|
+
video_muted: !camEnabled
|
|
298
385
|
});
|
|
299
386
|
};
|
|
300
387
|
this.ws.onerror = (err) => {
|
|
@@ -307,13 +394,7 @@ var VideoSDKCore = class {
|
|
|
307
394
|
raw: e
|
|
308
395
|
});
|
|
309
396
|
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) {
|
|
397
|
+
if (this.intentionalDisconnect || e.code === 1e3 || e.code === 1001 || this.isWaitingForApproval) {
|
|
317
398
|
return;
|
|
318
399
|
}
|
|
319
400
|
this.scheduleReconnect();
|
|
@@ -323,203 +404,71 @@ var VideoSDKCore = class {
|
|
|
323
404
|
};
|
|
324
405
|
});
|
|
325
406
|
}
|
|
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
407
|
async handle(msg) {
|
|
455
|
-
var _a, _b, _c, _d;
|
|
456
408
|
if (msg.sender === this.myId) return;
|
|
457
409
|
switch (msg.type) {
|
|
458
410
|
case "PONG":
|
|
459
411
|
this.lastPong = Date.now();
|
|
460
412
|
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;
|
|
413
|
+
case "JOINED": {
|
|
414
|
+
if (msg.iceServers) {
|
|
415
|
+
this.iceServers = msg.iceServers;
|
|
480
416
|
}
|
|
481
|
-
|
|
482
|
-
|
|
417
|
+
this.room.name = msg.room_name;
|
|
418
|
+
this.isWaitingForApproval = false;
|
|
419
|
+
this.pendingRequestId = null;
|
|
420
|
+
this.intentionalDisconnect = false;
|
|
421
|
+
this.reconnectAttempts = 0;
|
|
422
|
+
this.setupPublisherPC();
|
|
423
|
+
this.setupSubscriberPC();
|
|
424
|
+
await this.createPublisherOffer();
|
|
425
|
+
this.startHeartbeat();
|
|
426
|
+
this.joinResolver?.();
|
|
427
|
+
this.joinResolver = void 0;
|
|
428
|
+
this.joinRejecter = void 0;
|
|
429
|
+
break;
|
|
430
|
+
}
|
|
431
|
+
case "PUB_ANSWER": {
|
|
432
|
+
if (this.pubPC) {
|
|
433
|
+
await this.pubPC.setRemoteDescription({
|
|
483
434
|
type: "answer",
|
|
484
435
|
sdp: msg.payload
|
|
485
436
|
});
|
|
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
437
|
}
|
|
496
438
|
break;
|
|
497
439
|
}
|
|
498
|
-
case "
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
440
|
+
case "SUB_OFFER": {
|
|
441
|
+
this.pendingTracks.push(msg.track);
|
|
442
|
+
if (this.subPC) {
|
|
443
|
+
await this.subPC.setRemoteDescription({
|
|
444
|
+
type: "offer",
|
|
445
|
+
sdp: msg.payload
|
|
446
|
+
});
|
|
447
|
+
const answer = await this.subPC.createAnswer();
|
|
448
|
+
await this.subPC.setLocalDescription(answer);
|
|
449
|
+
this.send({
|
|
450
|
+
type: "SUB_ANSWER",
|
|
451
|
+
payload: answer.sdp,
|
|
452
|
+
user_id: this.myId
|
|
453
|
+
});
|
|
505
454
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
455
|
+
break;
|
|
456
|
+
}
|
|
457
|
+
case "PUB_ICE": {
|
|
458
|
+
if (this.pubPC && msg.payload) {
|
|
459
|
+
await this.pubPC.addIceCandidate(JSON.parse(msg.payload)).catch(console.warn);
|
|
510
460
|
}
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
461
|
+
break;
|
|
462
|
+
}
|
|
463
|
+
case "SUB_ICE": {
|
|
464
|
+
if (this.subPC && msg.payload) {
|
|
465
|
+
await this.subPC.addIceCandidate(JSON.parse(msg.payload)).catch(console.warn);
|
|
515
466
|
}
|
|
516
467
|
break;
|
|
517
468
|
}
|
|
518
|
-
case "EXISTING_USERS":
|
|
469
|
+
case "EXISTING_USERS": {
|
|
519
470
|
if (msg.presenterId) {
|
|
520
471
|
this.state.setPresenterId(msg.presenterId);
|
|
521
|
-
this.events.onScreenShareStarted?.(msg.presenterId, null);
|
|
522
|
-
this.state.setPresenterId(msg.presenterId);
|
|
523
472
|
}
|
|
524
473
|
for (const p of msg.participants || []) {
|
|
525
474
|
if (!p?.id || p.id === this.myId) continue;
|
|
@@ -531,9 +480,6 @@ var VideoSDKCore = class {
|
|
|
531
480
|
media: {
|
|
532
481
|
stream: null,
|
|
533
482
|
screenStream: void 0,
|
|
534
|
-
cameraTrack: void 0,
|
|
535
|
-
screenTrack: void 0,
|
|
536
|
-
audioTrack: void 0,
|
|
537
483
|
micEnabled: p.micEnabled ?? true,
|
|
538
484
|
camEnabled: p.camEnabled ?? true,
|
|
539
485
|
isScreenSharing: p.isScreenSharing ?? false,
|
|
@@ -543,118 +489,53 @@ var VideoSDKCore = class {
|
|
|
543
489
|
};
|
|
544
490
|
this.state.addParticipant(structuredParticipant);
|
|
545
491
|
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
492
|
}
|
|
558
493
|
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
494
|
}
|
|
598
495
|
case "USER_JOINED": {
|
|
599
496
|
const p = msg.participant;
|
|
600
497
|
if (!p?.id || p.id === this.myId) return;
|
|
601
498
|
this.state.addParticipant(p);
|
|
602
499
|
this.events.onUserJoined?.(p);
|
|
603
|
-
if (this.shouldInitiate(p.id)) {
|
|
604
|
-
await this.createOffer(p.id);
|
|
605
|
-
}
|
|
606
|
-
break;
|
|
607
|
-
}
|
|
608
|
-
case "JOIN_PENDING": {
|
|
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
500
|
break;
|
|
641
501
|
}
|
|
642
|
-
case "USER_LEFT":
|
|
502
|
+
case "USER_LEFT": {
|
|
643
503
|
const peerId = msg.participant.id;
|
|
644
|
-
this.closePeer(peerId);
|
|
645
504
|
this.state.removeParticipant(peerId);
|
|
646
505
|
this.events.onUserLeft?.(peerId);
|
|
647
506
|
break;
|
|
507
|
+
}
|
|
648
508
|
case "MEDIA_STATE_CHANGE": {
|
|
649
|
-
const
|
|
509
|
+
const peerId = msg.peerId;
|
|
650
510
|
const { kind, enabled } = msg;
|
|
651
511
|
if (kind === "audio") {
|
|
652
|
-
this.state.updateParticipantMedia(
|
|
653
|
-
this.events.onMicToggled?.(
|
|
512
|
+
this.state.updateParticipantMedia(peerId, { micEnabled: enabled });
|
|
513
|
+
this.events.onMicToggled?.(peerId, enabled);
|
|
654
514
|
} else if (kind === "video") {
|
|
655
|
-
this.state.updateParticipantMedia(
|
|
656
|
-
this.events.onCamToggled?.(
|
|
515
|
+
this.state.updateParticipantMedia(peerId, { camEnabled: enabled });
|
|
516
|
+
this.events.onCamToggled?.(peerId, enabled);
|
|
517
|
+
}
|
|
518
|
+
break;
|
|
519
|
+
}
|
|
520
|
+
case "SCREEN_SHARE_START": {
|
|
521
|
+
const peerId = msg.peerId;
|
|
522
|
+
this.state.updateParticipantMedia(peerId, {
|
|
523
|
+
isScreenSharing: true,
|
|
524
|
+
remoteScreenStreamId: msg.stream_id,
|
|
525
|
+
cameraStreamId: msg?.camera_stream_id
|
|
526
|
+
});
|
|
527
|
+
if (!this.state.presenterId) {
|
|
528
|
+
this.state.setPresenterId(peerId);
|
|
529
|
+
}
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
case "SCREEN_SHARE_STOP": {
|
|
533
|
+
const peerId = msg.peerId;
|
|
534
|
+
this.state.updateParticipantMedia(peerId, { isScreenSharing: false });
|
|
535
|
+
if (this.state.presenterId === peerId) {
|
|
536
|
+
this.state.setPresenterId(null);
|
|
657
537
|
}
|
|
538
|
+
this.events.onScreenShareStopped?.(peerId);
|
|
658
539
|
break;
|
|
659
540
|
}
|
|
660
541
|
case "CHAT_MESSAGE": {
|
|
@@ -671,27 +552,37 @@ var VideoSDKCore = class {
|
|
|
671
552
|
this.events.onChatMessage?.(msg);
|
|
672
553
|
break;
|
|
673
554
|
}
|
|
674
|
-
case "
|
|
675
|
-
const
|
|
676
|
-
this.
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
555
|
+
case "JOIN_PENDING": {
|
|
556
|
+
const req = msg.request;
|
|
557
|
+
this.isWaitingForApproval = true;
|
|
558
|
+
this.pendingRequestId = req.request_id;
|
|
559
|
+
this.events.onEntryRequested?.({
|
|
560
|
+
requestId: req.request_id,
|
|
561
|
+
userId: req.user_id,
|
|
562
|
+
name: req.name
|
|
680
563
|
});
|
|
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
564
|
break;
|
|
687
565
|
}
|
|
688
|
-
case "
|
|
689
|
-
|
|
690
|
-
this.
|
|
691
|
-
if (this.
|
|
692
|
-
this.
|
|
566
|
+
case "JOIN_APPROVED": {
|
|
567
|
+
this.isWaitingForApproval = false;
|
|
568
|
+
this.pendingRequestId = null;
|
|
569
|
+
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
570
|
+
this.send({
|
|
571
|
+
type: "JOIN",
|
|
572
|
+
room_id: this.room.id,
|
|
573
|
+
user_id: this.myId,
|
|
574
|
+
sender_name: this.participantName
|
|
575
|
+
});
|
|
693
576
|
}
|
|
694
|
-
|
|
577
|
+
break;
|
|
578
|
+
}
|
|
579
|
+
case "JOIN_REJECTED": {
|
|
580
|
+
this.isWaitingForApproval = false;
|
|
581
|
+
this.pendingRequestId = null;
|
|
582
|
+
this.events.onEntryResponded?.({
|
|
583
|
+
participantId: msg.user_id,
|
|
584
|
+
decision: "rejected"
|
|
585
|
+
});
|
|
695
586
|
break;
|
|
696
587
|
}
|
|
697
588
|
case "ERROR": {
|
|
@@ -702,271 +593,92 @@ var VideoSDKCore = class {
|
|
|
702
593
|
msg,
|
|
703
594
|
!fatal
|
|
704
595
|
);
|
|
705
|
-
if (fatal)
|
|
706
|
-
this.disconnect();
|
|
707
|
-
}
|
|
596
|
+
if (fatal) this.disconnect();
|
|
708
597
|
return;
|
|
709
598
|
}
|
|
710
599
|
}
|
|
711
600
|
}
|
|
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];
|
|
601
|
+
// ---------------- PUBLISHER RENEGOTIATION ----------------
|
|
602
|
+
async createPublisherOffer() {
|
|
603
|
+
if (!this.pubPC) return;
|
|
830
604
|
try {
|
|
831
|
-
const offer = await
|
|
832
|
-
await
|
|
605
|
+
const offer = await this.pubPC.createOffer();
|
|
606
|
+
await this.pubPC.setLocalDescription(offer);
|
|
833
607
|
this.send({
|
|
834
|
-
type: "
|
|
608
|
+
type: "PUB_OFFER",
|
|
835
609
|
payload: offer.sdp,
|
|
836
|
-
|
|
837
|
-
|
|
610
|
+
user_id: this.myId,
|
|
611
|
+
room_id: this.room.id
|
|
838
612
|
});
|
|
839
|
-
console.debug(`[Offer] Sent to ${id}`);
|
|
840
613
|
} catch (err) {
|
|
841
|
-
console.error(
|
|
614
|
+
console.error("[SFU Publisher Offer Error]", err);
|
|
842
615
|
}
|
|
843
616
|
}
|
|
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
|
-
}
|
|
617
|
+
// ---------------- MEDIA TOGGLES ----------------
|
|
618
|
+
toggleMic() {
|
|
619
|
+
const mediaState = this.state.localParticipant?.media;
|
|
620
|
+
if (!mediaState) return;
|
|
621
|
+
const nextEnabled = !mediaState.micEnabled;
|
|
622
|
+
this.localStream?.getAudioTracks().forEach((t) => t.enabled = nextEnabled);
|
|
623
|
+
this.state.updateLocalParticipant({
|
|
624
|
+
id: this.myId,
|
|
625
|
+
name: this.participantName,
|
|
626
|
+
media: { ...mediaState, micEnabled: nextEnabled }
|
|
627
|
+
});
|
|
628
|
+
this.send({ type: "MEDIA_STATE", kind: "audio", enabled: nextEnabled });
|
|
913
629
|
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
this.
|
|
630
|
+
toggleCam() {
|
|
631
|
+
const mediaState = this.state.localParticipant?.media;
|
|
632
|
+
if (!mediaState) return;
|
|
633
|
+
const nextEnabled = !mediaState.camEnabled;
|
|
634
|
+
this.localStream?.getVideoTracks().forEach((t) => t.enabled = nextEnabled);
|
|
635
|
+
this.state.updateLocalParticipant({
|
|
636
|
+
id: this.myId,
|
|
637
|
+
name: this.participantName,
|
|
638
|
+
media: { ...mediaState, camEnabled: nextEnabled }
|
|
639
|
+
});
|
|
640
|
+
this.send({ type: "MEDIA_STATE", kind: "video", enabled: nextEnabled });
|
|
925
641
|
}
|
|
642
|
+
// ---------------- SCREEN SHARING (SFU) ----------------
|
|
926
643
|
async startScreenShare() {
|
|
927
644
|
try {
|
|
928
645
|
if (this.state.presenterId && this.state.presenterId !== this.myId) {
|
|
929
646
|
throw new Error("Another user is already sharing their screen.");
|
|
930
647
|
}
|
|
931
|
-
if (!navigator.mediaDevices?.getDisplayMedia) {
|
|
932
|
-
throw new Error("Screen sharing not supported on this device");
|
|
933
|
-
}
|
|
934
648
|
this.screenStream = await navigator.mediaDevices.getDisplayMedia({
|
|
935
649
|
video: true
|
|
936
|
-
// audio: true,
|
|
937
650
|
});
|
|
938
651
|
this.isScreenSharing = true;
|
|
652
|
+
const screenTrack = this.screenStream.getVideoTracks()[0];
|
|
653
|
+
Object.defineProperty(screenTrack, "contentHint", {
|
|
654
|
+
value: "detail"
|
|
655
|
+
});
|
|
656
|
+
if (this.pubPC) {
|
|
657
|
+
this.screenSender = this.pubPC.addTrack(screenTrack, this.screenStream);
|
|
658
|
+
await this.createPublisherOffer();
|
|
659
|
+
}
|
|
939
660
|
this.state.updateLocalParticipant({
|
|
940
661
|
media: {
|
|
941
662
|
isScreenSharing: true,
|
|
942
663
|
screenStream: this.screenStream,
|
|
943
|
-
screenTrack
|
|
664
|
+
screenTrack
|
|
944
665
|
}
|
|
945
666
|
});
|
|
946
667
|
this.state.setPresenterId(this.myId);
|
|
947
|
-
|
|
668
|
+
screenTrack.onended = () => {
|
|
948
669
|
this.stopScreenShare();
|
|
949
670
|
};
|
|
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
671
|
this.send({
|
|
959
672
|
type: "SCREEN_SHARE_START",
|
|
960
673
|
sender: this.myId,
|
|
961
674
|
room_id: this.room.id,
|
|
962
|
-
camera_id: this.localStream?.id.replace(/[{}]/g, ""),
|
|
963
675
|
stream_id: this.screenStream.id.replace(/[{}]/g, "")
|
|
964
676
|
});
|
|
965
677
|
return this.screenStream;
|
|
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
|
);
|
|
@@ -975,21 +687,14 @@ var VideoSDKCore = class {
|
|
|
975
687
|
throw err;
|
|
976
688
|
}
|
|
977
689
|
}
|
|
978
|
-
stopScreenShare() {
|
|
690
|
+
async stopScreenShare() {
|
|
979
691
|
if (!this.screenStream) return;
|
|
980
692
|
this.screenStream.getTracks().forEach((t) => t.stop());
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
} catch (err) {
|
|
987
|
-
console.warn(err);
|
|
988
|
-
}
|
|
989
|
-
});
|
|
990
|
-
delete this.screenSenders[peerId];
|
|
991
|
-
this.createOffer(peerId, true);
|
|
992
|
-
});
|
|
693
|
+
if (this.pubPC && this.screenSender) {
|
|
694
|
+
this.pubPC.removeTrack(this.screenSender);
|
|
695
|
+
this.screenSender = null;
|
|
696
|
+
await this.createPublisherOffer();
|
|
697
|
+
}
|
|
993
698
|
this.screenStream = null;
|
|
994
699
|
this.isScreenSharing = false;
|
|
995
700
|
this.state.updateLocalParticipant({
|
|
@@ -1008,15 +713,10 @@ var VideoSDKCore = class {
|
|
|
1008
713
|
room_id: this.room.id
|
|
1009
714
|
});
|
|
1010
715
|
}
|
|
716
|
+
// ---------------- CHAT & RECONNECT ----------------
|
|
1011
717
|
sendChatMessage(payload) {
|
|
1012
|
-
if (!this.ws || this.ws.readyState !== WebSocket.OPEN)
|
|
1013
|
-
console.warn("WS not connected");
|
|
1014
|
-
return;
|
|
1015
|
-
}
|
|
1016
|
-
if (!this.room.id) {
|
|
1017
|
-
console.warn("No roomId set");
|
|
718
|
+
if (!this.ws || this.ws.readyState !== WebSocket.OPEN || !this.room.id)
|
|
1018
719
|
return;
|
|
1019
|
-
}
|
|
1020
720
|
const isPrivate = !!payload?.target;
|
|
1021
721
|
const senderName = this.state.localParticipant?.name || "Anonymous";
|
|
1022
722
|
const msg = {
|
|
@@ -1040,12 +740,48 @@ var VideoSDKCore = class {
|
|
|
1040
740
|
client_ts: Date.now()
|
|
1041
741
|
});
|
|
1042
742
|
}
|
|
743
|
+
scheduleReconnect() {
|
|
744
|
+
if (!this.room.id) return;
|
|
745
|
+
const delay = Math.min(1e3 * Math.pow(2, this.reconnectAttempts), 3e4);
|
|
746
|
+
window.clearTimeout(this.reconnectTimer);
|
|
747
|
+
this.reconnectTimer = window.setTimeout(async () => {
|
|
748
|
+
try {
|
|
749
|
+
await this.connect(this.room.id, this.participantName);
|
|
750
|
+
this.reconnectAttempts = 0;
|
|
751
|
+
} catch {
|
|
752
|
+
this.reconnectAttempts++;
|
|
753
|
+
this.scheduleReconnect();
|
|
754
|
+
}
|
|
755
|
+
}, delay);
|
|
756
|
+
}
|
|
757
|
+
startHeartbeat() {
|
|
758
|
+
this.stopHeartbeat();
|
|
759
|
+
this.pingInterval = setInterval(() => {
|
|
760
|
+
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
761
|
+
this.send({ type: "PING", client_ts: Date.now() });
|
|
762
|
+
}
|
|
763
|
+
}, 2e4);
|
|
764
|
+
}
|
|
765
|
+
stopHeartbeat() {
|
|
766
|
+
if (this.pingInterval) {
|
|
767
|
+
clearInterval(this.pingInterval);
|
|
768
|
+
this.pingInterval = null;
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
reset() {
|
|
772
|
+
this.pubPC?.close();
|
|
773
|
+
this.subPC?.close();
|
|
774
|
+
this.pubPC = null;
|
|
775
|
+
this.subPC = null;
|
|
776
|
+
this.state.resetRemoteState();
|
|
777
|
+
}
|
|
1043
778
|
disconnect() {
|
|
1044
779
|
this.intentionalDisconnect = true;
|
|
1045
780
|
this.stopScreenShare();
|
|
1046
|
-
|
|
1047
|
-
this.
|
|
1048
|
-
this.
|
|
781
|
+
this.pubPC?.close();
|
|
782
|
+
this.subPC?.close();
|
|
783
|
+
this.pubPC = null;
|
|
784
|
+
this.subPC = null;
|
|
1049
785
|
this.stopHeartbeat();
|
|
1050
786
|
if (this.ws?.readyState === WebSocket.OPEN) {
|
|
1051
787
|
this.send({
|
|
@@ -1072,32 +808,40 @@ var VideoSDKCore = class {
|
|
|
1072
808
|
this.state.clearChat();
|
|
1073
809
|
this.state.setPresenterId(null);
|
|
1074
810
|
}
|
|
1075
|
-
async
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
console.warn("ICE flush error", e);
|
|
1083
|
-
}
|
|
811
|
+
async restartPublisherIce() {
|
|
812
|
+
if (!this.pubPC) return;
|
|
813
|
+
try {
|
|
814
|
+
this.pubPC.restartIce();
|
|
815
|
+
await this.createPublisherOffer();
|
|
816
|
+
} catch (err) {
|
|
817
|
+
console.error("[Publisher ICE Restart Failed]", err);
|
|
1084
818
|
}
|
|
1085
|
-
|
|
819
|
+
}
|
|
820
|
+
emitError(code, message, raw, recoverable = true) {
|
|
821
|
+
const err = {
|
|
822
|
+
code,
|
|
823
|
+
message,
|
|
824
|
+
raw,
|
|
825
|
+
roomId: this.room.id,
|
|
826
|
+
userId: this.myId,
|
|
827
|
+
recoverable
|
|
828
|
+
};
|
|
829
|
+
this.events.onError?.(err);
|
|
830
|
+
this.joinRejecter?.(err);
|
|
831
|
+
this.joinRejecter = void 0;
|
|
832
|
+
console.error("[MeetingSDK Error]", err);
|
|
1086
833
|
}
|
|
1087
834
|
send(msg) {
|
|
1088
835
|
this.ws?.send(JSON.stringify(msg));
|
|
1089
836
|
}
|
|
1090
837
|
approveJoinRequest(requestId) {
|
|
1091
|
-
this.send({
|
|
1092
|
-
type: "JOIN_APPROVE",
|
|
1093
|
-
request_id: requestId
|
|
1094
|
-
});
|
|
838
|
+
this.send({ type: "JOIN_APPROVE", request_id: requestId });
|
|
1095
839
|
}
|
|
1096
840
|
rejectJoinRequest(requestId) {
|
|
1097
|
-
this.send({
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
841
|
+
this.send({ type: "JOIN_REJECT", request_id: requestId });
|
|
842
|
+
}
|
|
843
|
+
getMeeting() {
|
|
844
|
+
return this.room;
|
|
1101
845
|
}
|
|
1102
846
|
};
|
|
1103
847
|
|