@whereby.com/browser-sdk 2.1.0-beta2 → 2.1.0-beta3
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/cdn/{v2-embed-beta2.js → v2-embed-beta3.js} +1 -1
- package/dist/cdn/v2-react-beta3.js +3 -0
- package/dist/core/index.d.ts +2356 -0
- package/dist/core/index.js +62945 -0
- package/dist/embed/index.esm.js +1 -1
- package/dist/react/index.d.ts +2 -0
- package/dist/react/index.esm.js +1086 -145
- package/package.json +9 -2
- package/dist/cdn/v2-react-beta2.js +0 -3
package/dist/react/index.esm.js
CHANGED
|
@@ -3,11 +3,15 @@ import { useState, useEffect, useCallback } from 'react';
|
|
|
3
3
|
import { createListenerMiddleware, createSlice, createAsyncThunk, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
4
4
|
import { io } from 'socket.io-client';
|
|
5
5
|
import adapter from 'webrtc-adapter';
|
|
6
|
+
import EventEmitter, { EventEmitter as EventEmitter$1 } from 'events';
|
|
6
7
|
import SDPUtils from 'sdp';
|
|
7
8
|
import * as sdpTransform from 'sdp-transform';
|
|
8
|
-
import { v4 } from 'uuid';
|
|
9
|
+
import { v4 as v4$1 } from 'uuid';
|
|
10
|
+
import { Address6 } from 'ip-address';
|
|
11
|
+
import checkIp from 'check-ip';
|
|
12
|
+
import validate from 'uuid-validate';
|
|
9
13
|
import { detectDevice, Device } from 'mediasoup-client';
|
|
10
|
-
import
|
|
14
|
+
import { Chrome111 } from 'mediasoup-client/lib/handlers/Chrome111';
|
|
11
15
|
import nodeBtoa from 'btoa';
|
|
12
16
|
import axios from 'axios';
|
|
13
17
|
|
|
@@ -173,6 +177,7 @@ const initialState$g = {
|
|
|
173
177
|
displayName: null,
|
|
174
178
|
sdkVersion: null,
|
|
175
179
|
externalId: null,
|
|
180
|
+
isNodeSdk: typeof process !== "undefined" && process.release.name === "node",
|
|
176
181
|
};
|
|
177
182
|
const appSlice = createSlice({
|
|
178
183
|
name: "app",
|
|
@@ -200,7 +205,8 @@ const selectAppRoomUrl = (state) => state.app.roomUrl;
|
|
|
200
205
|
const selectAppRoomKey = (state) => state.app.roomKey;
|
|
201
206
|
const selectAppDisplayName = (state) => state.app.displayName;
|
|
202
207
|
const selectAppSdkVersion = (state) => state.app.sdkVersion;
|
|
203
|
-
const selectAppExternalId = (state) => state.app.externalId;
|
|
208
|
+
const selectAppExternalId = (state) => state.app.externalId;
|
|
209
|
+
const selectAppIsNodeSdk = (state) => state.app.isNodeSdk;
|
|
204
210
|
|
|
205
211
|
function createAppAsyncThunk(typePrefix, payloadCreator) {
|
|
206
212
|
return createAsyncThunk(typePrefix, payloadCreator);
|
|
@@ -285,13 +291,461 @@ createReactor([selectShouldFetchDeviceCredentials], ({ dispatch }, shouldFetchDe
|
|
|
285
291
|
}
|
|
286
292
|
});
|
|
287
293
|
|
|
294
|
+
let peerConnections = [];
|
|
295
|
+
let peerConnectionCounter = 0;
|
|
296
|
+
const peerConnectionData = new WeakMap();
|
|
297
|
+
|
|
298
|
+
const removePeerConnection = (pc) => {
|
|
299
|
+
peerConnections = peerConnections.filter((old) => old !== pc);
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
if (window.RTCPeerConnection) {
|
|
303
|
+
const OriginalRTCPeerConnection = window.RTCPeerConnection;
|
|
304
|
+
function PatchedRTCPeerConnection(rtcConfig) {
|
|
305
|
+
const pc = new OriginalRTCPeerConnection(rtcConfig);
|
|
306
|
+
peerConnections.push(pc);
|
|
307
|
+
peerConnectionData.set(pc, { index: peerConnectionCounter++ });
|
|
308
|
+
const onConnectionStateChange = () => {
|
|
309
|
+
if (pc.connectionState === "closed") {
|
|
310
|
+
removePeerConnection(pc);
|
|
311
|
+
pc.removeEventListener("connectionstatechange", onConnectionStateChange);
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
pc.addEventListener("connectionstatechange", onConnectionStateChange);
|
|
315
|
+
return pc;
|
|
316
|
+
}
|
|
317
|
+
PatchedRTCPeerConnection.prototype = OriginalRTCPeerConnection.prototype;
|
|
318
|
+
window.RTCPeerConnection = PatchedRTCPeerConnection;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let currentMonitor = null;
|
|
322
|
+
|
|
323
|
+
const getUpdatedStats = () => currentMonitor?.getUpdatedStats();
|
|
324
|
+
|
|
325
|
+
// Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
|
|
326
|
+
|
|
327
|
+
// Requests: messages from the client to the server
|
|
328
|
+
const PROTOCOL_REQUESTS = {
|
|
329
|
+
BLOCK_CLIENT: "block_client",
|
|
330
|
+
CLAIM_ROOM: "claim_room",
|
|
331
|
+
CLEAR_CHAT_HISTORY: "clear_chat_history",
|
|
332
|
+
ENABLE_AUDIO: "enable_audio",
|
|
333
|
+
ENABLE_VIDEO: "enable_video",
|
|
334
|
+
END_STREAM: "end_stream",
|
|
335
|
+
FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
|
|
336
|
+
HANDLE_KNOCK: "handle_knock",
|
|
337
|
+
IDENTIFY_DEVICE: "identify_device",
|
|
338
|
+
INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
|
|
339
|
+
JOIN_ROOM: "join_room",
|
|
340
|
+
KICK_CLIENT: "kick_client",
|
|
341
|
+
KNOCK_ROOM: "knock_room",
|
|
342
|
+
LEAVE_ROOM: "leave_room",
|
|
343
|
+
SEND_CLIENT_METADATA: "send_client_metadata",
|
|
344
|
+
SET_LOCK: "set_lock",
|
|
345
|
+
SHARE_MEDIA: "share_media",
|
|
346
|
+
START_NEW_STREAM: "start_new_stream",
|
|
347
|
+
START_SCREENSHARE: "start_screenshare",
|
|
348
|
+
STOP_SCREENSHARE: "stop_screenshare",
|
|
349
|
+
START_URL_EMBED: "start_url_embed",
|
|
350
|
+
STOP_URL_EMBED: "stop_url_embed",
|
|
351
|
+
START_RECORDING: "start_recording",
|
|
352
|
+
STOP_RECORDING: "stop_recording",
|
|
353
|
+
SFU_TOKEN: "sfu_token",
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// Responses: messages from the server to the client, in response to requests
|
|
357
|
+
const PROTOCOL_RESPONSES = {
|
|
358
|
+
AUDIO_ENABLED: "audio_enabled",
|
|
359
|
+
BACKGROUND_IMAGE_CHANGED: "background_image_changed",
|
|
360
|
+
BLOCK_ADDED: "block_added",
|
|
361
|
+
BLOCK_REMOVED: "block_removed",
|
|
362
|
+
CHAT_HISTORY_CLEARED: "chat_history_cleared",
|
|
363
|
+
CLIENT_BLOCKED: "client_blocked",
|
|
364
|
+
CLIENT_INVITED_AS_MEMBER: "client_invited_as_member",
|
|
365
|
+
CLIENT_KICKED: "client_kicked",
|
|
366
|
+
CLIENT_LEFT: "client_left",
|
|
367
|
+
CLIENT_METADATA_RECEIVED: "client_metadata_received",
|
|
368
|
+
CLIENT_READY: "client_ready",
|
|
369
|
+
CLIENT_ROLE_CHANGED: "client_role_changed",
|
|
370
|
+
CLIENT_USER_ID_CHANGED: "client_user_id_changed",
|
|
371
|
+
CONTACTS_UPDATED: "contacts_updated",
|
|
372
|
+
DEVICE_IDENTIFIED: "device_identified",
|
|
373
|
+
ROOM_ROLES_UPDATED: "room_roles_updated",
|
|
374
|
+
KNOCK_HANDLED: "knock_handled",
|
|
375
|
+
KNOCK_PAGE_BACKGROUND_CHANGED: "knock_page_background_changed",
|
|
376
|
+
KNOCKER_LEFT: "knocker_left",
|
|
377
|
+
MEDIASERVER_CONFIG: "mediaserver_config",
|
|
378
|
+
MEDIA_SHARED: "media_shared",
|
|
379
|
+
MEMBER_INVITE: "member_invite",
|
|
380
|
+
NEW_CLIENT: "new_client",
|
|
381
|
+
NEW_STREAM_STARTED: "new_stream_started",
|
|
382
|
+
SCREENSHARE_STARTED: "screenshare_started",
|
|
383
|
+
SCREENSHARE_STOPPED: "screenshare_stopped",
|
|
384
|
+
OWNER_NOTIFIED: "owner_notified",
|
|
385
|
+
OWNERS_CHANGED: "owners_changed",
|
|
386
|
+
PLAY_CLIENT_STICKER: "play_client_sticker",
|
|
387
|
+
ROOM_INTEGRATION_ENABLED: "room_integration_enabled",
|
|
388
|
+
ROOM_INTEGRATION_DISABLED: "room_integration_disabled",
|
|
389
|
+
ROOM_JOINED: "room_joined",
|
|
390
|
+
ROOM_KNOCKED: "room_knocked",
|
|
391
|
+
ROOM_LEFT: "room_left",
|
|
392
|
+
ROOM_LOCKED: "room_locked",
|
|
393
|
+
ROOM_PERMISSIONS_CHANGED: "room_permissions_changed",
|
|
394
|
+
ROOM_LOGO_CHANGED: "room_logo_changed",
|
|
395
|
+
ROOM_TYPE_CHANGED: "room_type_changed",
|
|
396
|
+
ROOM_MODE_CHANGED: "room_mode_changed",
|
|
397
|
+
SOCKET_USER_ID_CHANGED: "socket_user_id_changed",
|
|
398
|
+
STICKERS_UNLOCKED: "stickers_unlocked",
|
|
399
|
+
STREAM_ENDED: "stream_ended",
|
|
400
|
+
URL_EMBED_STARTED: "url_embed_started",
|
|
401
|
+
URL_EMBED_STOPPED: "url_embed_stopped",
|
|
402
|
+
RECORDING_STARTED: "recording_started",
|
|
403
|
+
RECORDING_STOPPED: "recording_stopped",
|
|
404
|
+
USER_NOTIFIED: "user_notified",
|
|
405
|
+
VIDEO_ENABLED: "video_enabled",
|
|
406
|
+
CLIENT_UNABLE_TO_JOIN: "client_unable_to_join",
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// Relays: messages between clients, relayed through the server
|
|
410
|
+
const RELAY_MESSAGES = {
|
|
411
|
+
CHAT_MESSAGE: "chat_message",
|
|
412
|
+
CHAT_READ_STATE: "chat_read_state",
|
|
413
|
+
CHAT_STATE: "chat_state",
|
|
414
|
+
ICE_CANDIDATE: "ice_candidate",
|
|
415
|
+
ICE_END_OF_CANDIDATES: "ice_endofcandidates",
|
|
416
|
+
READY_TO_RECEIVE_OFFER: "ready_to_receive_offer",
|
|
417
|
+
REMOTE_CLIENT_MEDIA_REQUEST: "remote_client_media_request",
|
|
418
|
+
SDP_ANSWER: "sdp_answer",
|
|
419
|
+
SDP_OFFER: "sdp_offer",
|
|
420
|
+
VIDEO_STICKER: "video_sticker",
|
|
421
|
+
};
|
|
422
|
+
|
|
423
|
+
// Events: something happened that we want to let the client know about
|
|
424
|
+
const PROTOCOL_EVENTS = {
|
|
425
|
+
PENDING_CLIENT_LEFT: "pending_client_left",
|
|
426
|
+
MEDIA_QUALITY_CHANGED: "media_quality_changed",
|
|
427
|
+
};
|
|
428
|
+
|
|
429
|
+
class ReconnectManager extends EventEmitter {
|
|
430
|
+
constructor(socket, logger = console) {
|
|
431
|
+
super();
|
|
432
|
+
this._socket = socket;
|
|
433
|
+
this._logger = logger;
|
|
434
|
+
this._clients = {};
|
|
435
|
+
this._signalDisconnectTime = undefined;
|
|
436
|
+
this.rtcManager = undefined;
|
|
437
|
+
|
|
438
|
+
socket.on("disconnect", () => {
|
|
439
|
+
this._signalDisconnectTime = Date.now();
|
|
440
|
+
});
|
|
441
|
+
|
|
442
|
+
// We intercept these events and take responsiblity for forwarding them
|
|
443
|
+
socket.on(PROTOCOL_RESPONSES.ROOM_JOINED, (payload) => this._onRoomJoined(payload));
|
|
444
|
+
socket.on(PROTOCOL_RESPONSES.NEW_CLIENT, (payload) => this._onNewClient(payload));
|
|
445
|
+
socket.on(PROTOCOL_RESPONSES.CLIENT_LEFT, (payload) => this._onClientLeft(payload));
|
|
446
|
+
|
|
447
|
+
// We intercept these events and handle them without forwarding them
|
|
448
|
+
socket.on(PROTOCOL_EVENTS.PENDING_CLIENT_LEFT, (payload) => this._onPendingClientLeft(payload));
|
|
449
|
+
|
|
450
|
+
// We gather information from these events but they will also be forwarded
|
|
451
|
+
socket.on(PROTOCOL_RESPONSES.AUDIO_ENABLED, (payload) => this._onAudioEnabled(payload));
|
|
452
|
+
socket.on(PROTOCOL_RESPONSES.VIDEO_ENABLED, (payload) => this._onVideoEnabled(payload));
|
|
453
|
+
socket.on(PROTOCOL_RESPONSES.SCREENSHARE_STARTED, (payload) => this._onScreenshareChanged(payload, true));
|
|
454
|
+
socket.on(PROTOCOL_RESPONSES.SCREENSHARE_STOPPED, (payload) => this._onScreenshareChanged(payload, false));
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
async _onRoomJoined(payload) {
|
|
458
|
+
// We might have gotten an error
|
|
459
|
+
if (!payload.room?.clients) {
|
|
460
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
461
|
+
return;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (!payload.selfId) {
|
|
465
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const myDeviceId = payload.room.clients.find((c) => payload.selfId === c.id)?.deviceId;
|
|
470
|
+
if (!myDeviceId) {
|
|
471
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
// Try to remove our own pending client if this is a page reload
|
|
476
|
+
// Could also be a first normal room_joined which can never be glitch-free
|
|
477
|
+
if (!this._signalDisconnectTime) {
|
|
478
|
+
this._resetClientState(payload);
|
|
479
|
+
payload.room.clients = payload.room.clients.filter(
|
|
480
|
+
(c) => !(c.deviceId === myDeviceId && c.isPendingToLeave)
|
|
481
|
+
);
|
|
482
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
483
|
+
return;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
// The threshold for trying glitch-free reconnect should be less than server-side configuration
|
|
487
|
+
const RECONNECT_THRESHOLD = payload.disconnectTimeout * 0.8;
|
|
488
|
+
const timeSinceDisconnect = Date.now() - this._signalDisconnectTime;
|
|
489
|
+
if (timeSinceDisconnect > RECONNECT_THRESHOLD) {
|
|
490
|
+
this._resetClientState(payload);
|
|
491
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
// At this point we want to try to attempt glitch-free reconnection experience
|
|
496
|
+
|
|
497
|
+
// Filter out our own pending client after page reload
|
|
498
|
+
payload.room.clients = payload.room.clients.filter((c) => !(c.deviceId === myDeviceId && c.isPendingToLeave));
|
|
499
|
+
|
|
500
|
+
const allStats = await getUpdatedStats();
|
|
501
|
+
payload.room.clients.forEach((client) => {
|
|
502
|
+
try {
|
|
503
|
+
if (client.id === payload.selfId) return;
|
|
504
|
+
|
|
505
|
+
// Maybe add client to state
|
|
506
|
+
if (!this._clients[client.id]) {
|
|
507
|
+
this._addClientToState(client);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
// Verify that rtcManager knows about the client
|
|
511
|
+
if (!this.rtcManager?.hasClient(client.id)) {
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Verify that the client state hasn't changed
|
|
516
|
+
if (
|
|
517
|
+
this._hasClientStateChanged({
|
|
518
|
+
clientId: client.id,
|
|
519
|
+
webcam: client.isVideoEnabled,
|
|
520
|
+
mic: client.isAudioEnabled,
|
|
521
|
+
screenShare: client.streams.length > 1,
|
|
522
|
+
})
|
|
523
|
+
) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (this._wasClientSendingMedia(client.id)) {
|
|
528
|
+
// Verify the client media is still flowing (not stopped from other end)
|
|
529
|
+
if (!this._isClientMediaActive(allStats, client.id)) {
|
|
530
|
+
return;
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
client.mergeWithOldClientState = true;
|
|
535
|
+
} catch (error) {
|
|
536
|
+
this._logger.error("Failed to evaluate if we should merge client state %o", error);
|
|
537
|
+
}
|
|
538
|
+
});
|
|
539
|
+
|
|
540
|
+
// We will try to remove any remote client pending to leave
|
|
541
|
+
payload.room.clients.forEach((c) => {
|
|
542
|
+
if (c.isPendingToLeave) {
|
|
543
|
+
this._onPendingClientLeft({ clientId: c.id });
|
|
544
|
+
}
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
_onClientLeft(payload) {
|
|
551
|
+
const { clientId } = payload;
|
|
552
|
+
const client = this._clients[clientId];
|
|
553
|
+
|
|
554
|
+
// Remove client from state and clear timeout if client was pending to leave
|
|
555
|
+
if (client) {
|
|
556
|
+
clearTimeout(client.timeout);
|
|
557
|
+
delete this._clients[clientId];
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// Old RTCManager only takes one argument, so rest is ignored.
|
|
561
|
+
this.rtcManager?.disconnect(clientId, /* activeBreakout */ null, payload.eventClaim);
|
|
562
|
+
|
|
563
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, payload);
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
_onPendingClientLeft(payload) {
|
|
567
|
+
const { clientId } = payload;
|
|
568
|
+
const client = this._clients[clientId];
|
|
569
|
+
|
|
570
|
+
if (!client) {
|
|
571
|
+
this._logger.warn(`client ${clientId} not found`);
|
|
572
|
+
return;
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// We've already started the check below, don't do it again
|
|
576
|
+
if (client.isPendingToLeave) {
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
client.isPendingToLeave = true;
|
|
581
|
+
if (this._wasClientSendingMedia(clientId)) {
|
|
582
|
+
client.checkActiveMediaAttempts = 0;
|
|
583
|
+
this._abortIfNotActive(payload);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
_onNewClient(payload) {
|
|
588
|
+
const {
|
|
589
|
+
client: { id: clientId, deviceId },
|
|
590
|
+
} = payload;
|
|
591
|
+
|
|
592
|
+
const client = this._clients[clientId];
|
|
593
|
+
if (client && client.isPendingToLeave) {
|
|
594
|
+
clearTimeout(client.timeoutHandler);
|
|
595
|
+
client.isPendingToLeave = false;
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
this._getPendingClientsByDeviceId(deviceId).forEach((client) => {
|
|
600
|
+
clearTimeout(client.timeoutHandler);
|
|
601
|
+
client.isPendingToLeave = undefined;
|
|
602
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, { clientId: client.clientId });
|
|
603
|
+
});
|
|
604
|
+
|
|
605
|
+
this._addClientToState(payload.client);
|
|
606
|
+
this.emit(PROTOCOL_RESPONSES.NEW_CLIENT, payload);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// Evaluate if we should send send client_left before getting it from signal-server
|
|
610
|
+
async _abortIfNotActive(payload) {
|
|
611
|
+
const { clientId } = payload;
|
|
612
|
+
|
|
613
|
+
let client = this._clients[clientId];
|
|
614
|
+
if (!client?.isPendingToLeave) return;
|
|
615
|
+
|
|
616
|
+
client.checkActiveMediaAttempts++;
|
|
617
|
+
if (client.checkActiveMediaAttempts > 3) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
const stillActive = await this._checkIsActive(clientId);
|
|
622
|
+
if (stillActive) {
|
|
623
|
+
client.timeoutHandler = setTimeout(() => this._abortIfNotActive(payload), 500);
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
client = this._clients[clientId];
|
|
628
|
+
if (client?.isPendingToLeave) {
|
|
629
|
+
clearTimeout(client.timeoutHandler);
|
|
630
|
+
delete this._clients[clientId];
|
|
631
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, payload);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// Check if client is active
|
|
636
|
+
async _checkIsActive(clientId) {
|
|
637
|
+
const allStats = await getUpdatedStats();
|
|
638
|
+
return this._isClientMediaActive(allStats, clientId);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
// Check if client has bitrates for all tracks
|
|
642
|
+
_isClientMediaActive(stats, clientId) {
|
|
643
|
+
const clientStats = stats?.[clientId];
|
|
644
|
+
let isActive = false;
|
|
645
|
+
if (clientStats) {
|
|
646
|
+
Object.entries(clientStats.tracks).forEach(([trackId, trackStats]) => {
|
|
647
|
+
if (trackId !== "probator")
|
|
648
|
+
Object.values(trackStats.ssrcs).forEach((ssrcStats) => {
|
|
649
|
+
if ((ssrcStats.bitrate || 0) > 0) isActive = true;
|
|
650
|
+
});
|
|
651
|
+
});
|
|
652
|
+
}
|
|
653
|
+
return isActive;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
_onAudioEnabled(payload) {
|
|
657
|
+
const { clientId, isAudioEnabled } = payload;
|
|
658
|
+
this._clients[clientId] = {
|
|
659
|
+
...(this._clients[clientId] || {}),
|
|
660
|
+
isAudioEnabled,
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
_onVideoEnabled(payload) {
|
|
665
|
+
const { clientId, isVideoEnabled } = payload;
|
|
666
|
+
this._clients[clientId] = {
|
|
667
|
+
...(this._clients[clientId] || {}),
|
|
668
|
+
isVideoEnabled,
|
|
669
|
+
};
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
_onScreenshareChanged(payload, action) {
|
|
673
|
+
const { clientId } = payload;
|
|
674
|
+
this._clients[clientId] = {
|
|
675
|
+
...(this._clients[clientId] || {}),
|
|
676
|
+
isScreenshareEnabled: action,
|
|
677
|
+
};
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
_hasClientStateChanged({ clientId, webcam, mic, screenShare }) {
|
|
681
|
+
const state = this._clients[clientId];
|
|
682
|
+
|
|
683
|
+
if (!state) {
|
|
684
|
+
throw new Error(`Client ${clientId} not found in ReconnectManager state`);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
if (webcam !== state.isVideoEnabled) {
|
|
688
|
+
return true;
|
|
689
|
+
}
|
|
690
|
+
if (mic !== state.isAudioEnabled) {
|
|
691
|
+
return true;
|
|
692
|
+
}
|
|
693
|
+
if (screenShare !== state.isScreenshareEnabled) {
|
|
694
|
+
return true;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
return false;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
_addClientToState(newClient) {
|
|
701
|
+
this._clients[newClient.id] = {
|
|
702
|
+
...(this._clients[newClient.id] || {}),
|
|
703
|
+
isAudioEnabled: newClient.isAudioEnabled,
|
|
704
|
+
isVideoEnabled: newClient.isVideoEnabled,
|
|
705
|
+
isScreenshareEnabled: newClient.streams.length > 1,
|
|
706
|
+
deviceId: newClient.deviceId,
|
|
707
|
+
isPendingToLeave: newClient.isPendingToLeave,
|
|
708
|
+
clientId: newClient.id,
|
|
709
|
+
};
|
|
710
|
+
}
|
|
711
|
+
|
|
712
|
+
_wasClientSendingMedia(clientId) {
|
|
713
|
+
const client = this._clients[clientId];
|
|
714
|
+
|
|
715
|
+
if (!client) {
|
|
716
|
+
throw new Error(`Client ${clientId} not found in ReconnectManager state`);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return client.isAudioEnabled || client.isVideoEnabled || client.isScreenshareEnabled;
|
|
720
|
+
}
|
|
721
|
+
|
|
722
|
+
_getPendingClientsByDeviceId(deviceId) {
|
|
723
|
+
return Object.values(this._clients).filter((clientState) => {
|
|
724
|
+
return clientState.deviceId === deviceId && clientState.isPendingToLeave;
|
|
725
|
+
});
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
_resetClientState(payload) {
|
|
729
|
+
this._clients = {};
|
|
730
|
+
payload.room.clients.forEach((client) => {
|
|
731
|
+
if (client.id === payload.selfId) {
|
|
732
|
+
return;
|
|
733
|
+
} else {
|
|
734
|
+
this._addClientToState(client);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
|
|
288
740
|
const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
|
|
289
741
|
|
|
742
|
+
const NOOP_KEEPALIVE_INTERVAL = 2000;
|
|
743
|
+
|
|
290
744
|
/**
|
|
291
745
|
* Wrapper class that extends the Socket.IO client library.
|
|
292
746
|
*/
|
|
293
747
|
class ServerSocket {
|
|
294
|
-
constructor(hostName, optionsOverrides) {
|
|
748
|
+
constructor(hostName, optionsOverrides, glitchFree) {
|
|
295
749
|
this._socket = io(hostName, {
|
|
296
750
|
path: DEFAULT_SOCKET_PATH,
|
|
297
751
|
randomizationFactor: 0.5,
|
|
@@ -317,12 +771,39 @@ class ServerSocket {
|
|
|
317
771
|
this._socket.io.opts.transports = ["websocket", "polling"];
|
|
318
772
|
}
|
|
319
773
|
});
|
|
774
|
+
|
|
775
|
+
if (glitchFree) this._reconnectManager = new ReconnectManager(this._socket);
|
|
776
|
+
|
|
320
777
|
this._socket.on("connect", () => {
|
|
321
778
|
const transport = this.getTransport();
|
|
322
779
|
if (transport === "websocket") {
|
|
323
780
|
this._wasConnectedUsingWebsocket = true;
|
|
781
|
+
|
|
782
|
+
// start noop keepalive loop to detect client side disconnects fast
|
|
783
|
+
if (!this.noopKeepaliveInterval)
|
|
784
|
+
this.noopKeepaliveInterval = setInterval(() => {
|
|
785
|
+
try {
|
|
786
|
+
// send a noop message if it thinks it is connected (might not be)
|
|
787
|
+
if (this._socket.connected) {
|
|
788
|
+
this._socket.io.engine.sendPacket("noop");
|
|
789
|
+
}
|
|
790
|
+
} catch (ex) {}
|
|
791
|
+
}, NOOP_KEEPALIVE_INTERVAL);
|
|
324
792
|
}
|
|
325
793
|
});
|
|
794
|
+
|
|
795
|
+
this._socket.on("disconnect", () => {
|
|
796
|
+
if (this.noopKeepaliveInterval) {
|
|
797
|
+
clearInterval(this.noopKeepaliveInterval);
|
|
798
|
+
this.noopKeepaliveInterval = null;
|
|
799
|
+
}
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
setRtcManager(rtcManager) {
|
|
804
|
+
if (this._reconnectManager) {
|
|
805
|
+
this._reconnectManager.rtcManager = rtcManager;
|
|
806
|
+
}
|
|
326
807
|
}
|
|
327
808
|
|
|
328
809
|
connect() {
|
|
@@ -383,6 +864,17 @@ class ServerSocket {
|
|
|
383
864
|
* @returns {function} Function to deregister the listener.
|
|
384
865
|
*/
|
|
385
866
|
on(eventName, handler) {
|
|
867
|
+
const relayableEvents = [
|
|
868
|
+
PROTOCOL_RESPONSES.ROOM_JOINED,
|
|
869
|
+
PROTOCOL_RESPONSES.CLIENT_LEFT,
|
|
870
|
+
PROTOCOL_RESPONSES.NEW_CLIENT,
|
|
871
|
+
];
|
|
872
|
+
|
|
873
|
+
// Intercept certain events if glitch-free is enabled.
|
|
874
|
+
if (this._reconnectManager && relayableEvents.includes(eventName)) {
|
|
875
|
+
return this._interceptEvent(eventName, handler);
|
|
876
|
+
}
|
|
877
|
+
|
|
386
878
|
this._socket.on(eventName, handler);
|
|
387
879
|
|
|
388
880
|
return () => {
|
|
@@ -409,6 +901,19 @@ class ServerSocket {
|
|
|
409
901
|
off(eventName, handler) {
|
|
410
902
|
this._socket.off(eventName, handler);
|
|
411
903
|
}
|
|
904
|
+
|
|
905
|
+
/**
|
|
906
|
+
* Intercept event and let ReconnectManager handle them.
|
|
907
|
+
*/
|
|
908
|
+
_interceptEvent(eventName, handler) {
|
|
909
|
+
if (this._reconnectManager) {
|
|
910
|
+
this._reconnectManager.on(eventName, handler);
|
|
911
|
+
}
|
|
912
|
+
|
|
913
|
+
return () => {
|
|
914
|
+
if (this._reconnectManager) this._reconnectManager.removeListener(eventName, handler);
|
|
915
|
+
};
|
|
916
|
+
}
|
|
412
917
|
}
|
|
413
918
|
|
|
414
919
|
function forwardSocketEvents(socket, dispatch) {
|
|
@@ -652,6 +1157,7 @@ const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10))
|
|
|
652
1157
|
function getMediaConstraints({
|
|
653
1158
|
disableAEC,
|
|
654
1159
|
disableAGC,
|
|
1160
|
+
fps24,
|
|
655
1161
|
hd,
|
|
656
1162
|
lax,
|
|
657
1163
|
lowDataMode,
|
|
@@ -662,7 +1168,7 @@ function getMediaConstraints({
|
|
|
662
1168
|
}) {
|
|
663
1169
|
let HIGH_HEIGHT = 480;
|
|
664
1170
|
let LOW_HEIGHT = 240;
|
|
665
|
-
let
|
|
1171
|
+
let NON_STANDARD_FPS = 0;
|
|
666
1172
|
|
|
667
1173
|
if (hd) {
|
|
668
1174
|
// respect user choice, but default to HD for pro, and SD for free
|
|
@@ -675,15 +1181,20 @@ function getMediaConstraints({
|
|
|
675
1181
|
} else {
|
|
676
1182
|
LOW_HEIGHT = 360;
|
|
677
1183
|
}
|
|
678
|
-
LOW_FPS = 30; // we still use 30fps because of assumptions about temporal layers
|
|
679
1184
|
}
|
|
680
1185
|
|
|
1186
|
+
// Set framerate to 24 to increase quality/bandwidth
|
|
1187
|
+
if (fps24) NON_STANDARD_FPS = 24;
|
|
1188
|
+
|
|
1189
|
+
// Set framerate for low data, but only for non-simulcast
|
|
1190
|
+
if (lowDataMode && !simulcast) NON_STANDARD_FPS = 15;
|
|
1191
|
+
|
|
681
1192
|
const constraints = {
|
|
682
1193
|
audio: { ...(preferredDeviceIds.audioId && { deviceId: preferredDeviceIds.audioId }) },
|
|
683
1194
|
video: {
|
|
684
1195
|
...(preferredDeviceIds.videoId ? { deviceId: preferredDeviceIds.videoId } : { facingMode: "user" }),
|
|
685
1196
|
height: lowDataMode ? LOW_HEIGHT : HIGH_HEIGHT,
|
|
686
|
-
...(
|
|
1197
|
+
...(NON_STANDARD_FPS && { frameRate: NON_STANDARD_FPS }),
|
|
687
1198
|
},
|
|
688
1199
|
};
|
|
689
1200
|
if (lax) {
|
|
@@ -1519,7 +2030,10 @@ const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) =
|
|
|
1519
2030
|
* Reactors
|
|
1520
2031
|
*/
|
|
1521
2032
|
// Start localMedia unless started when roomConnection is wanted
|
|
1522
|
-
const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
|
|
2033
|
+
const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, selectAppIsNodeSdk, (appWantsToJoin, localMediaStatus, localMediaOptions, isNodeSdk) => {
|
|
2034
|
+
if (isNodeSdk) {
|
|
2035
|
+
return;
|
|
2036
|
+
}
|
|
1523
2037
|
if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
|
|
1524
2038
|
return localMediaOptions;
|
|
1525
2039
|
}
|
|
@@ -2155,8 +2669,14 @@ const selectRoomConnectionStatus = (state) => state.roomConnection.status;
|
|
|
2155
2669
|
/**
|
|
2156
2670
|
* Reactors
|
|
2157
2671
|
*/
|
|
2158
|
-
const selectShouldConnectRoom = createSelector([
|
|
2159
|
-
|
|
2672
|
+
const selectShouldConnectRoom = createSelector([
|
|
2673
|
+
selectOrganizationId,
|
|
2674
|
+
selectRoomConnectionStatus,
|
|
2675
|
+
selectSignalConnectionDeviceIdentified,
|
|
2676
|
+
selectLocalMediaStatus,
|
|
2677
|
+
selectAppIsNodeSdk,
|
|
2678
|
+
], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus, isNodeSdk) => {
|
|
2679
|
+
if ((localMediaStatus === "started" || isNodeSdk) && // the node SDK doesn't use LocalMedia, so we can join without
|
|
2160
2680
|
signalConnectionDeviceIdentified &&
|
|
2161
2681
|
!!hasOrganizationIdFetched &&
|
|
2162
2682
|
["initializing", "reconnect"].includes(roomConnectionStatus)) {
|
|
@@ -2183,125 +2703,27 @@ startAppListening({
|
|
|
2183
2703
|
dispatch(doConnectRoom());
|
|
2184
2704
|
}
|
|
2185
2705
|
else if (resolution === "rejected") {
|
|
2186
|
-
dispatch(connectionStatusChanged("knock_rejected"));
|
|
2187
|
-
}
|
|
2188
|
-
},
|
|
2189
|
-
});
|
|
2190
|
-
|
|
2191
|
-
const EVENTS = {
|
|
2192
|
-
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
2193
|
-
STREAM_ADDED: "stream_added",
|
|
2194
|
-
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
2195
|
-
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
2196
|
-
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
2197
|
-
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
2198
|
-
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
2199
|
-
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
2200
|
-
};
|
|
2201
|
-
|
|
2202
|
-
const TYPES = {
|
|
2203
|
-
CONNECTING: "connecting",
|
|
2204
|
-
CONNECTION_FAILED: "connection_failed",
|
|
2205
|
-
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2206
|
-
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2207
|
-
};
|
|
2208
|
-
|
|
2209
|
-
// Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
|
|
2210
|
-
|
|
2211
|
-
// Requests: messages from the client to the server
|
|
2212
|
-
const PROTOCOL_REQUESTS = {
|
|
2213
|
-
BLOCK_CLIENT: "block_client",
|
|
2214
|
-
CLAIM_ROOM: "claim_room",
|
|
2215
|
-
CLEAR_CHAT_HISTORY: "clear_chat_history",
|
|
2216
|
-
ENABLE_AUDIO: "enable_audio",
|
|
2217
|
-
ENABLE_VIDEO: "enable_video",
|
|
2218
|
-
END_STREAM: "end_stream",
|
|
2219
|
-
FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
|
|
2220
|
-
HANDLE_KNOCK: "handle_knock",
|
|
2221
|
-
IDENTIFY_DEVICE: "identify_device",
|
|
2222
|
-
INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
|
|
2223
|
-
JOIN_ROOM: "join_room",
|
|
2224
|
-
KICK_CLIENT: "kick_client",
|
|
2225
|
-
KNOCK_ROOM: "knock_room",
|
|
2226
|
-
LEAVE_ROOM: "leave_room",
|
|
2227
|
-
SEND_CLIENT_METADATA: "send_client_metadata",
|
|
2228
|
-
SET_LOCK: "set_lock",
|
|
2229
|
-
SHARE_MEDIA: "share_media",
|
|
2230
|
-
START_NEW_STREAM: "start_new_stream",
|
|
2231
|
-
START_SCREENSHARE: "start_screenshare",
|
|
2232
|
-
STOP_SCREENSHARE: "stop_screenshare",
|
|
2233
|
-
START_URL_EMBED: "start_url_embed",
|
|
2234
|
-
STOP_URL_EMBED: "stop_url_embed",
|
|
2235
|
-
START_RECORDING: "start_recording",
|
|
2236
|
-
STOP_RECORDING: "stop_recording",
|
|
2237
|
-
SFU_TOKEN: "sfu_token",
|
|
2238
|
-
};
|
|
2706
|
+
dispatch(connectionStatusChanged("knock_rejected"));
|
|
2707
|
+
}
|
|
2708
|
+
},
|
|
2709
|
+
});
|
|
2239
2710
|
|
|
2240
|
-
|
|
2241
|
-
|
|
2242
|
-
|
|
2243
|
-
|
|
2244
|
-
|
|
2245
|
-
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
CLIENT_KICKED: "client_kicked",
|
|
2250
|
-
CLIENT_LEFT: "client_left",
|
|
2251
|
-
CLIENT_METADATA_RECEIVED: "client_metadata_received",
|
|
2252
|
-
CLIENT_READY: "client_ready",
|
|
2253
|
-
CLIENT_ROLE_CHANGED: "client_role_changed",
|
|
2254
|
-
CLIENT_USER_ID_CHANGED: "client_user_id_changed",
|
|
2255
|
-
CONTACTS_UPDATED: "contacts_updated",
|
|
2256
|
-
DEVICE_IDENTIFIED: "device_identified",
|
|
2257
|
-
ROOM_ROLES_UPDATED: "room_roles_updated",
|
|
2258
|
-
KNOCK_HANDLED: "knock_handled",
|
|
2259
|
-
KNOCK_PAGE_BACKGROUND_CHANGED: "knock_page_background_changed",
|
|
2260
|
-
KNOCKER_LEFT: "knocker_left",
|
|
2261
|
-
MEDIASERVER_CONFIG: "mediaserver_config",
|
|
2262
|
-
MEDIA_SHARED: "media_shared",
|
|
2263
|
-
MEMBER_INVITE: "member_invite",
|
|
2264
|
-
NEW_CLIENT: "new_client",
|
|
2265
|
-
NEW_STREAM_STARTED: "new_stream_started",
|
|
2266
|
-
SCREENSHARE_STARTED: "screenshare_started",
|
|
2267
|
-
SCREENSHARE_STOPPED: "screenshare_stopped",
|
|
2268
|
-
OWNER_NOTIFIED: "owner_notified",
|
|
2269
|
-
OWNERS_CHANGED: "owners_changed",
|
|
2270
|
-
PLAY_CLIENT_STICKER: "play_client_sticker",
|
|
2271
|
-
ROOM_INTEGRATION_ENABLED: "room_integration_enabled",
|
|
2272
|
-
ROOM_INTEGRATION_DISABLED: "room_integration_disabled",
|
|
2273
|
-
ROOM_JOINED: "room_joined",
|
|
2274
|
-
ROOM_KNOCKED: "room_knocked",
|
|
2275
|
-
ROOM_LEFT: "room_left",
|
|
2276
|
-
ROOM_LOCKED: "room_locked",
|
|
2277
|
-
ROOM_PERMISSIONS_CHANGED: "room_permissions_changed",
|
|
2278
|
-
ROOM_LOGO_CHANGED: "room_logo_changed",
|
|
2279
|
-
ROOM_TYPE_CHANGED: "room_type_changed",
|
|
2280
|
-
ROOM_MODE_CHANGED: "room_mode_changed",
|
|
2281
|
-
SOCKET_USER_ID_CHANGED: "socket_user_id_changed",
|
|
2282
|
-
STICKERS_UNLOCKED: "stickers_unlocked",
|
|
2283
|
-
STREAM_ENDED: "stream_ended",
|
|
2284
|
-
URL_EMBED_STARTED: "url_embed_started",
|
|
2285
|
-
URL_EMBED_STOPPED: "url_embed_stopped",
|
|
2286
|
-
RECORDING_STARTED: "recording_started",
|
|
2287
|
-
RECORDING_STOPPED: "recording_stopped",
|
|
2288
|
-
USER_NOTIFIED: "user_notified",
|
|
2289
|
-
VIDEO_ENABLED: "video_enabled",
|
|
2290
|
-
CLIENT_UNABLE_TO_JOIN: "client_unable_to_join",
|
|
2711
|
+
const EVENTS = {
|
|
2712
|
+
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
2713
|
+
STREAM_ADDED: "stream_added",
|
|
2714
|
+
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
2715
|
+
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
2716
|
+
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
2717
|
+
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
2718
|
+
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
2719
|
+
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
2291
2720
|
};
|
|
2292
2721
|
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
ICE_CANDIDATE: "ice_candidate",
|
|
2299
|
-
ICE_END_OF_CANDIDATES: "ice_endofcandidates",
|
|
2300
|
-
READY_TO_RECEIVE_OFFER: "ready_to_receive_offer",
|
|
2301
|
-
REMOTE_CLIENT_MEDIA_REQUEST: "remote_client_media_request",
|
|
2302
|
-
SDP_ANSWER: "sdp_answer",
|
|
2303
|
-
SDP_OFFER: "sdp_offer",
|
|
2304
|
-
VIDEO_STICKER: "video_sticker",
|
|
2722
|
+
const TYPES = {
|
|
2723
|
+
CONNECTING: "connecting",
|
|
2724
|
+
CONNECTION_FAILED: "connection_failed",
|
|
2725
|
+
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2726
|
+
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2305
2727
|
};
|
|
2306
2728
|
|
|
2307
2729
|
const CAMERA_STREAM_ID$2 = "0";
|
|
@@ -2417,8 +2839,14 @@ function detectMicrophoneNotWorking(pc) {
|
|
|
2417
2839
|
var rtcManagerEvents = {
|
|
2418
2840
|
CAMERA_NOT_WORKING: "camera_not_working",
|
|
2419
2841
|
CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
|
|
2842
|
+
ICE_IPV6_SEEN: "ice_ipv6_seen",
|
|
2843
|
+
ICE_MDNS_SEEN: "ice_mdns_seen",
|
|
2844
|
+
ICE_NO_PUBLIC_IP_GATHERED: "ice_no_public_ip_gathered",
|
|
2845
|
+
ICE_NO_PUBLIC_IP_GATHERED_3SEC: "ice_no_public_ip_gathered_3sec",
|
|
2846
|
+
ICE_RESTART: "ice_restart",
|
|
2420
2847
|
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
2421
2848
|
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
2849
|
+
NEW_PC: "new_pc",
|
|
2422
2850
|
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
2423
2851
|
COLOCATION_SPEAKER: "colocation_speaker",
|
|
2424
2852
|
DOMINANT_SPEAKER: "dominant_speaker",
|
|
@@ -2617,6 +3045,13 @@ class Session {
|
|
|
2617
3045
|
constructor({ peerConnectionId, bandwidth, maximumTurnBandwidth, deprioritizeH264Encoding, logger = console }) {
|
|
2618
3046
|
this.peerConnectionId = peerConnectionId;
|
|
2619
3047
|
this.relayCandidateSeen = false;
|
|
3048
|
+
this.serverReflexiveCandidateSeen = false;
|
|
3049
|
+
this.publicHostCandidateSeen = false;
|
|
3050
|
+
this.ipv6HostCandidateSeen = false;
|
|
3051
|
+
this.ipv6HostCandidateTeredoSeen = false;
|
|
3052
|
+
this.ipv6HostCandidate6to4Seen = false;
|
|
3053
|
+
this.mdnsHostCandidateSeen = false;
|
|
3054
|
+
|
|
2620
3055
|
this.pc = null;
|
|
2621
3056
|
this.wasEverConnected = false;
|
|
2622
3057
|
this.connectionStatus = null;
|
|
@@ -2993,6 +3428,21 @@ class Session {
|
|
|
2993
3428
|
|
|
2994
3429
|
setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
|
|
2995
3430
|
}
|
|
3431
|
+
|
|
3432
|
+
setAudioOnly(enable, excludedTrackIds = []) {
|
|
3433
|
+
this.pc
|
|
3434
|
+
.getTransceivers()
|
|
3435
|
+
.filter(
|
|
3436
|
+
(videoTransceiver) =>
|
|
3437
|
+
videoTransceiver?.direction !== "recvonly" &&
|
|
3438
|
+
videoTransceiver?.receiver?.track?.kind === "video" &&
|
|
3439
|
+
!excludedTrackIds.includes(videoTransceiver?.receiver?.track?.id) &&
|
|
3440
|
+
!excludedTrackIds.includes(videoTransceiver?.sender?.track?.id)
|
|
3441
|
+
)
|
|
3442
|
+
.forEach((videoTransceiver) => {
|
|
3443
|
+
videoTransceiver.direction = enable ? "sendonly" : "sendrecv";
|
|
3444
|
+
});
|
|
3445
|
+
}
|
|
2996
3446
|
}
|
|
2997
3447
|
|
|
2998
3448
|
// transforms a maplike to an object. Mostly for getStats +
|
|
@@ -3091,6 +3541,8 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
3091
3541
|
var peerconnectioncounter = 0;
|
|
3092
3542
|
var isFirefox = !!window.mozRTCPeerConnection;
|
|
3093
3543
|
var isEdge = !!window.RTCIceGatherer;
|
|
3544
|
+
var prevById = {};
|
|
3545
|
+
|
|
3094
3546
|
prefixesToWrap.forEach(function(prefix) {
|
|
3095
3547
|
if (!window[prefix + 'RTCPeerConnection']) {
|
|
3096
3548
|
return;
|
|
@@ -3161,13 +3613,12 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
3161
3613
|
trace('ondatachannel', id, [event.channel.id, event.channel.label]);
|
|
3162
3614
|
});
|
|
3163
3615
|
|
|
3164
|
-
var prev = {};
|
|
3165
3616
|
var getStats = function() {
|
|
3166
3617
|
pc.getStats(null).then(function(res) {
|
|
3167
3618
|
var now = map2obj(res);
|
|
3168
3619
|
var base = JSON.parse(JSON.stringify(now)); // our new prev
|
|
3169
|
-
trace('getstats', id, deltaCompression(
|
|
3170
|
-
|
|
3620
|
+
trace('getstats', id, deltaCompression(prevById[id] || {}, now));
|
|
3621
|
+
prevById[id] = base;
|
|
3171
3622
|
});
|
|
3172
3623
|
};
|
|
3173
3624
|
// TODO: do we want one big interval and all peerconnections
|
|
@@ -3387,6 +3838,13 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
3387
3838
|
}
|
|
3388
3839
|
});
|
|
3389
3840
|
*/
|
|
3841
|
+
|
|
3842
|
+
return {
|
|
3843
|
+
resetDelta() {
|
|
3844
|
+
prevById = {};
|
|
3845
|
+
}
|
|
3846
|
+
}
|
|
3847
|
+
|
|
3390
3848
|
};
|
|
3391
3849
|
|
|
3392
3850
|
var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
|
|
@@ -3395,11 +3853,18 @@ var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
|
|
|
3395
3853
|
|
|
3396
3854
|
const RTCSTATS_PROTOCOL_VERSION = "1.0";
|
|
3397
3855
|
|
|
3856
|
+
// when not connected we need to buffer at least a few getstats reports
|
|
3857
|
+
// as they are delta compressed and we need the initial properties
|
|
3858
|
+
const GETSTATS_BUFFER_SIZE = 20;
|
|
3859
|
+
|
|
3398
3860
|
const clientInfo = {
|
|
3399
|
-
id: v4(), // shared id across rtcstats reconnects
|
|
3861
|
+
id: v4$1(), // shared id across rtcstats reconnects
|
|
3400
3862
|
connectionNumber: 0,
|
|
3401
3863
|
};
|
|
3402
3864
|
|
|
3865
|
+
const noop = () => {};
|
|
3866
|
+
let resetDelta = noop;
|
|
3867
|
+
|
|
3403
3868
|
// Inlined version of rtcstats/trace-ws with improved disconnect handling.
|
|
3404
3869
|
function rtcStatsConnection(wsURL, logger = console) {
|
|
3405
3870
|
const buffer = [];
|
|
@@ -3412,6 +3877,7 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3412
3877
|
let connectionShouldBeOpen;
|
|
3413
3878
|
let connectionAttempt = 0;
|
|
3414
3879
|
let hasPassedOnRoomSessionId = false;
|
|
3880
|
+
let getStatsBufferUsed = 0;
|
|
3415
3881
|
|
|
3416
3882
|
const connection = {
|
|
3417
3883
|
connected: false,
|
|
@@ -3449,8 +3915,15 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3449
3915
|
if (ws.readyState === WebSocket.OPEN) {
|
|
3450
3916
|
connectionAttempt = 0;
|
|
3451
3917
|
ws.send(JSON.stringify(args));
|
|
3452
|
-
} else if (args[0]
|
|
3453
|
-
// buffer
|
|
3918
|
+
} else if (args[0] === "getstats") {
|
|
3919
|
+
// only buffer getStats for a while
|
|
3920
|
+
// we don't want this to pile up, but we need at least the initial reports
|
|
3921
|
+
if (getStatsBufferUsed < GETSTATS_BUFFER_SIZE) {
|
|
3922
|
+
getStatsBufferUsed++;
|
|
3923
|
+
buffer.push(args);
|
|
3924
|
+
}
|
|
3925
|
+
} else if (args[0] === "customEvent" && args[2].type === "insightsStats") ; else {
|
|
3926
|
+
// buffer everything else
|
|
3454
3927
|
buffer.push(args);
|
|
3455
3928
|
}
|
|
3456
3929
|
|
|
@@ -3485,6 +3958,7 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3485
3958
|
ws.onclose = (e) => {
|
|
3486
3959
|
connection.connected = false;
|
|
3487
3960
|
logger.info(`[RTCSTATS] Closed ${e.code}`);
|
|
3961
|
+
resetDelta();
|
|
3488
3962
|
};
|
|
3489
3963
|
ws.onopen = () => {
|
|
3490
3964
|
// send client info after each connection, so analysis tools can handle reconnections
|
|
@@ -3509,10 +3983,11 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3509
3983
|
ws.send(JSON.stringify(userRole));
|
|
3510
3984
|
}
|
|
3511
3985
|
|
|
3512
|
-
// send buffered events
|
|
3986
|
+
// send buffered events
|
|
3513
3987
|
while (buffer.length) {
|
|
3514
3988
|
ws.send(JSON.stringify(buffer.shift()));
|
|
3515
3989
|
}
|
|
3990
|
+
getStatsBufferUsed = 0;
|
|
3516
3991
|
};
|
|
3517
3992
|
},
|
|
3518
3993
|
};
|
|
@@ -3521,11 +3996,13 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3521
3996
|
}
|
|
3522
3997
|
|
|
3523
3998
|
const server = rtcStatsConnection("wss://rtcstats.srv.whereby.com" );
|
|
3524
|
-
rtcstats$1(
|
|
3999
|
+
const stats = rtcstats$1(
|
|
3525
4000
|
server.trace,
|
|
3526
4001
|
10000, // query once every 10 seconds.
|
|
3527
4002
|
[""] // only shim unprefixed RTCPeerConnecion.
|
|
3528
4003
|
);
|
|
4004
|
+
// on node clients this function can be undefined
|
|
4005
|
+
resetDelta = stats?.resetDelta || noop;
|
|
3529
4006
|
|
|
3530
4007
|
const rtcStats = {
|
|
3531
4008
|
sendEvent: (type, value) => {
|
|
@@ -3574,6 +4051,7 @@ class BaseRtcManager {
|
|
|
3574
4051
|
this.peerConnections = {};
|
|
3575
4052
|
this.localStreams = {};
|
|
3576
4053
|
this.enabledLocalStreamIds = [];
|
|
4054
|
+
this._screenshareVideoTrackIds = [];
|
|
3577
4055
|
this._socketListenerDeregisterFunctions = [];
|
|
3578
4056
|
this._localStreamDeregisterFunction = null;
|
|
3579
4057
|
this._emitter = emitter;
|
|
@@ -3581,6 +4059,7 @@ class BaseRtcManager {
|
|
|
3581
4059
|
this._webrtcProvider = webrtcProvider;
|
|
3582
4060
|
this._features = features || {};
|
|
3583
4061
|
this._logger = logger;
|
|
4062
|
+
this._isAudioOnlyMode = false;
|
|
3584
4063
|
|
|
3585
4064
|
this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
|
|
3586
4065
|
this._pendingActionsForConnectedPeerConnections = [];
|
|
@@ -3797,6 +4276,8 @@ class BaseRtcManager {
|
|
|
3797
4276
|
clientId,
|
|
3798
4277
|
});
|
|
3799
4278
|
|
|
4279
|
+
setTimeout(() => this._emit(rtcManagerEvents.NEW_PC), 0);
|
|
4280
|
+
|
|
3800
4281
|
pc.ontrack = (event) => {
|
|
3801
4282
|
const stream = event.streams[0];
|
|
3802
4283
|
if (stream.id === "default" && stream.getAudioTracks().length === 0) {
|
|
@@ -3858,6 +4339,11 @@ class BaseRtcManager {
|
|
|
3858
4339
|
this.maybeRestrictRelayBandwidth(session);
|
|
3859
4340
|
}
|
|
3860
4341
|
}
|
|
4342
|
+
|
|
4343
|
+
if (this._isAudioOnlyMode) {
|
|
4344
|
+
session.setAudioOnly(true, this._screenshareVideoTrackIds);
|
|
4345
|
+
}
|
|
4346
|
+
|
|
3861
4347
|
session.registerConnected();
|
|
3862
4348
|
break;
|
|
3863
4349
|
case "disconnected":
|
|
@@ -4086,6 +4572,7 @@ class BaseRtcManager {
|
|
|
4086
4572
|
}
|
|
4087
4573
|
|
|
4088
4574
|
// at this point it is clearly a screensharing stream.
|
|
4575
|
+
this._screenshareVideoTrackIds.push(stream.getVideoTracks()[0].id);
|
|
4089
4576
|
this._shareScreen(streamId, stream);
|
|
4090
4577
|
return;
|
|
4091
4578
|
}
|
|
@@ -4238,6 +4725,29 @@ class BaseRtcManager {
|
|
|
4238
4725
|
const answer = this._transformIncomingSdp(data.message, session.pc);
|
|
4239
4726
|
session.handleAnswer(answer);
|
|
4240
4727
|
}),
|
|
4728
|
+
|
|
4729
|
+
// if this is a reconnect to signal-server during screen-share we must let signal-server know
|
|
4730
|
+
this._serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room: { sfuServer: isSfu } }) => {
|
|
4731
|
+
if (isSfu || !this._wasScreenSharing) return;
|
|
4732
|
+
|
|
4733
|
+
const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID$1);
|
|
4734
|
+
if (!screenShareStreamId) {
|
|
4735
|
+
return;
|
|
4736
|
+
}
|
|
4737
|
+
|
|
4738
|
+
const screenshareStream = this.localStreams[screenShareStreamId];
|
|
4739
|
+
if (!screenshareStream) {
|
|
4740
|
+
this._logger.warn(`screenshare stream ${screenShareStreamId} not found`);
|
|
4741
|
+
return;
|
|
4742
|
+
}
|
|
4743
|
+
|
|
4744
|
+
const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
|
|
4745
|
+
|
|
4746
|
+
this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
|
|
4747
|
+
streamId: screenShareStreamId,
|
|
4748
|
+
hasAudioTrack,
|
|
4749
|
+
});
|
|
4750
|
+
}),
|
|
4241
4751
|
];
|
|
4242
4752
|
}
|
|
4243
4753
|
|
|
@@ -4271,6 +4781,27 @@ class BaseRtcManager {
|
|
|
4271
4781
|
track.removeEventListener("ended", this._audioTrackOnEnded);
|
|
4272
4782
|
}
|
|
4273
4783
|
|
|
4784
|
+
setAudioOnly(audioOnly) {
|
|
4785
|
+
this._isAudioOnlyMode = audioOnly;
|
|
4786
|
+
|
|
4787
|
+
this._forEachPeerConnection((session) => {
|
|
4788
|
+
if (session.hasConnectedPeerConnection()) {
|
|
4789
|
+
this._withForcedRenegotiation(session, () =>
|
|
4790
|
+
session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
|
|
4791
|
+
);
|
|
4792
|
+
}
|
|
4793
|
+
});
|
|
4794
|
+
}
|
|
4795
|
+
|
|
4796
|
+
setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
|
|
4797
|
+
const localScreenshareStream = this._getFirstLocalNonCameraStream();
|
|
4798
|
+
|
|
4799
|
+
this._screenshareVideoTrackIds = [
|
|
4800
|
+
...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
|
|
4801
|
+
...remoteScreenshareVideoTrackIds,
|
|
4802
|
+
];
|
|
4803
|
+
}
|
|
4804
|
+
|
|
4274
4805
|
setRoomSessionId(roomSessionId) {
|
|
4275
4806
|
this._roomSessionId = roomSessionId;
|
|
4276
4807
|
}
|
|
@@ -4304,6 +4835,52 @@ function getOptimalBitrate(width, height, frameRate) {
|
|
|
4304
4835
|
return targetBitrate;
|
|
4305
4836
|
}
|
|
4306
4837
|
|
|
4838
|
+
// taken from https://github.com/sindresorhus/ip-regex ^5.0.0
|
|
4839
|
+
// inlined because it's import caused errors in browser-sdk when running tests
|
|
4840
|
+
const word = "[a-fA-F\\d:]";
|
|
4841
|
+
|
|
4842
|
+
const boundry = (options) =>
|
|
4843
|
+
options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
|
|
4844
|
+
|
|
4845
|
+
const v4 = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}";
|
|
4846
|
+
|
|
4847
|
+
const v6segment = "[a-fA-F\\d]{1,4}";
|
|
4848
|
+
|
|
4849
|
+
const v6 = `
|
|
4850
|
+
(?:
|
|
4851
|
+
(?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
|
|
4852
|
+
(?:${v6segment}:){6}(?:${v4}|:${v6segment}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4
|
|
4853
|
+
(?:${v6segment}:){5}(?::${v4}|(?::${v6segment}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4
|
|
4854
|
+
(?:${v6segment}:){4}(?:(?::${v6segment}){0,1}:${v4}|(?::${v6segment}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4
|
|
4855
|
+
(?:${v6segment}:){3}(?:(?::${v6segment}){0,2}:${v4}|(?::${v6segment}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4
|
|
4856
|
+
(?:${v6segment}:){2}(?:(?::${v6segment}){0,3}:${v4}|(?::${v6segment}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4
|
|
4857
|
+
(?:${v6segment}:){1}(?:(?::${v6segment}){0,4}:${v4}|(?::${v6segment}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4
|
|
4858
|
+
(?::(?:(?::${v6segment}){0,5}:${v4}|(?::${v6segment}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4
|
|
4859
|
+
)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
|
|
4860
|
+
`
|
|
4861
|
+
.replace(/\s*\/\/.*$/gm, "")
|
|
4862
|
+
.replace(/\n/g, "")
|
|
4863
|
+
.trim();
|
|
4864
|
+
|
|
4865
|
+
// Pre-compile only the exact regexes because adding a global flag make regexes stateful
|
|
4866
|
+
const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
|
|
4867
|
+
const v4exact = new RegExp(`^${v4}$`);
|
|
4868
|
+
const v6exact = new RegExp(`^${v6}$`);
|
|
4869
|
+
|
|
4870
|
+
const ipRegex = (options) =>
|
|
4871
|
+
options && options.exact
|
|
4872
|
+
? v46Exact
|
|
4873
|
+
: new RegExp(
|
|
4874
|
+
`(?:${boundry(options)}${v4}${boundry(options)})|(?:${boundry(options)}${v6}${boundry(options)})`,
|
|
4875
|
+
"g"
|
|
4876
|
+
);
|
|
4877
|
+
|
|
4878
|
+
ipRegex.v4 = (options) =>
|
|
4879
|
+
options && options.exact ? v4exact : new RegExp(`${boundry(options)}${v4}${boundry(options)}`, "g");
|
|
4880
|
+
ipRegex.v6 = (options) =>
|
|
4881
|
+
options && options.exact ? v6exact : new RegExp(`${boundry(options)}${v6}${boundry(options)}`, "g");
|
|
4882
|
+
|
|
4883
|
+
const ICE_PUBLIC_IP_GATHERING_TIMEOUT = 3 * 1000;
|
|
4307
4884
|
const CAMERA_STREAM_ID = RtcStream.getCameraId();
|
|
4308
4885
|
const browserName$1 = adapter.browserDetails.browser;
|
|
4309
4886
|
|
|
@@ -4335,6 +4912,14 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4335
4912
|
// clean up some helpers.
|
|
4336
4913
|
session.wasEverConnected = false;
|
|
4337
4914
|
session.relayCandidateSeen = false;
|
|
4915
|
+
session.serverReflexiveCandidateSeen = false;
|
|
4916
|
+
session.publicHostCandidateSeen = false;
|
|
4917
|
+
session.ipv6HostCandidateSeen = false;
|
|
4918
|
+
this.ipv6HostCandidateTeredoSeen = false;
|
|
4919
|
+
this.ipv6HostCandidate6to4Seen = false;
|
|
4920
|
+
this.mdnsHostCandidateSeen = false;
|
|
4921
|
+
|
|
4922
|
+
this._emit(rtcManagerEvents.ICE_RESTART);
|
|
4338
4923
|
|
|
4339
4924
|
this._negotiatePeerConnection(
|
|
4340
4925
|
clientId,
|
|
@@ -4477,7 +5062,7 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4477
5062
|
let bandwidth = this._features.bandwidth
|
|
4478
5063
|
? parseInt(this._features.bandwidth, 10)
|
|
4479
5064
|
: {
|
|
4480
|
-
1:
|
|
5065
|
+
1: 0,
|
|
4481
5066
|
2: this._features.highP2PBandwidth ? 768 : 384,
|
|
4482
5067
|
3: this._features.highP2PBandwidth ? 512 : 256,
|
|
4483
5068
|
4: 192,
|
|
@@ -4543,9 +5128,73 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4543
5128
|
pc.addTrack(this._stoppedVideoTrack, localCameraStream);
|
|
4544
5129
|
}
|
|
4545
5130
|
|
|
5131
|
+
pc.onicegatheringstatechange = (event) => {
|
|
5132
|
+
const connection = event.target;
|
|
5133
|
+
|
|
5134
|
+
switch (connection.iceGatheringState) {
|
|
5135
|
+
case "gathering":
|
|
5136
|
+
if (this.icePublicIPGatheringTimeoutID) clearTimeout(this.icePublicIPGatheringTimeoutID);
|
|
5137
|
+
this.icePublicIPGatheringTimeoutID = setTimeout(() => {
|
|
5138
|
+
if (
|
|
5139
|
+
!session.publicHostCandidateSeen &&
|
|
5140
|
+
!session.relayCandidateSeen &&
|
|
5141
|
+
!session.serverReflexiveCandidateSeen
|
|
5142
|
+
) {
|
|
5143
|
+
this._emit(rtcManagerEvents.ICE_NO_PUBLIC_IP_GATHERED_3SEC);
|
|
5144
|
+
}
|
|
5145
|
+
}, ICE_PUBLIC_IP_GATHERING_TIMEOUT);
|
|
5146
|
+
break;
|
|
5147
|
+
case "complete":
|
|
5148
|
+
if (this.icePublicIPGatheringTimeoutID) clearTimeout(this.icePublicIPGatheringTimeoutID);
|
|
5149
|
+
this.icePublicIPGatheringTimeoutID = undefined;
|
|
5150
|
+
break;
|
|
5151
|
+
}
|
|
5152
|
+
};
|
|
5153
|
+
|
|
4546
5154
|
pc.onicecandidate = (event) => {
|
|
4547
5155
|
if (event.candidate) {
|
|
4548
|
-
|
|
5156
|
+
switch (event.candidate?.type) {
|
|
5157
|
+
case "host":
|
|
5158
|
+
const address = event?.candidate?.address;
|
|
5159
|
+
try {
|
|
5160
|
+
if (ipRegex.v4({ exact: true }).test(address)) {
|
|
5161
|
+
const ipv4 = checkIp(address);
|
|
5162
|
+
if (ipv4.isPublicIp) session.publicHostCandidateSeen = true;
|
|
5163
|
+
} else if (ipRegex.v6({ exact: true }).test(address.replace(/^\[(.*)\]/, "$1"))) {
|
|
5164
|
+
const ipv6 = new Address6(address.replace(/^\[(.*)\]/, "$1"));
|
|
5165
|
+
session.ipv6HostCandidateSeen = true;
|
|
5166
|
+
|
|
5167
|
+
if (ipv6.getScope() === "Global") {
|
|
5168
|
+
session.publicHostCandidateSeen = true;
|
|
5169
|
+
}
|
|
5170
|
+
if (ipv6.isTeredo()) {
|
|
5171
|
+
session.ipv6HostCandidateTeredoSeen = true;
|
|
5172
|
+
}
|
|
5173
|
+
if (ipv6.is6to4()) {
|
|
5174
|
+
session.ipv6HostCandidate6to4Seen = true;
|
|
5175
|
+
}
|
|
5176
|
+
} else {
|
|
5177
|
+
const uuidv4 = address.replace(/.local/, "");
|
|
5178
|
+
if (uuidv4 && validate(uuidv4, 4)) {
|
|
5179
|
+
session.mdnsHostCandidateSeen = true;
|
|
5180
|
+
}
|
|
5181
|
+
}
|
|
5182
|
+
} catch (error) {
|
|
5183
|
+
this._logger.debug("Error during parsing candidates! Error: ", { error });
|
|
5184
|
+
}
|
|
5185
|
+
break;
|
|
5186
|
+
case "srflx":
|
|
5187
|
+
if (!session.serverReflexiveCandidateSeen) {
|
|
5188
|
+
session.serverReflexiveCandidateSeen = true;
|
|
5189
|
+
}
|
|
5190
|
+
break;
|
|
5191
|
+
case "relay":
|
|
5192
|
+
case "relayed":
|
|
5193
|
+
if (!session.relayCandidateSeen) {
|
|
5194
|
+
session.relayCandidateSeen = true;
|
|
5195
|
+
}
|
|
5196
|
+
break;
|
|
5197
|
+
}
|
|
4549
5198
|
this._emitServerEvent(RELAY_MESSAGES.ICE_CANDIDATE, {
|
|
4550
5199
|
receiverId: clientId,
|
|
4551
5200
|
message: event.candidate,
|
|
@@ -4554,6 +5203,20 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4554
5203
|
this._emitServerEvent(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, {
|
|
4555
5204
|
receiverId: clientId,
|
|
4556
5205
|
});
|
|
5206
|
+
if (
|
|
5207
|
+
!session.publicHostCandidateSeen &&
|
|
5208
|
+
!session.relayCandidateSeen &&
|
|
5209
|
+
!session.serverReflexiveCandidateSeen
|
|
5210
|
+
) {
|
|
5211
|
+
this._emit(rtcManagerEvents.ICE_NO_PUBLIC_IP_GATHERED);
|
|
5212
|
+
}
|
|
5213
|
+
if (session.ipv6HostCandidateSeen) {
|
|
5214
|
+
this._emit(rtcManagerEvents.ICE_IPV6_SEEN, {
|
|
5215
|
+
teredoSeen: session.ipv6HostCandidateTeredoSeen,
|
|
5216
|
+
sixtofourSeen: session.ipv6HostCandidate6to4Seen,
|
|
5217
|
+
});
|
|
5218
|
+
}
|
|
5219
|
+
if (session.mdnsHostCandidateSeen) this._emit(rtcManagerEvents.ICE_MDNS_SEEN);
|
|
4557
5220
|
}
|
|
4558
5221
|
};
|
|
4559
5222
|
|
|
@@ -4678,14 +5341,20 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4678
5341
|
streamId,
|
|
4679
5342
|
hasAudioTrack: !!stream.getAudioTracks().length,
|
|
4680
5343
|
});
|
|
5344
|
+
this._wasScreenSharing = true;
|
|
4681
5345
|
this._addStreamToPeerConnections(stream);
|
|
4682
5346
|
}
|
|
4683
5347
|
|
|
4684
5348
|
removeStream(streamId, stream, requestedByClientId) {
|
|
4685
5349
|
super.removeStream(streamId, stream);
|
|
4686
5350
|
this._removeStreamFromPeerConnections(stream);
|
|
5351
|
+
this._wasScreenSharing = false;
|
|
4687
5352
|
this._emitServerEvent(PROTOCOL_REQUESTS.STOP_SCREENSHARE, { streamId, requestedByClientId });
|
|
4688
5353
|
}
|
|
5354
|
+
|
|
5355
|
+
hasClient(clientId) {
|
|
5356
|
+
return Object.keys(this.peerConnections).includes(clientId);
|
|
5357
|
+
}
|
|
4689
5358
|
}
|
|
4690
5359
|
|
|
4691
5360
|
class SfuV2Parser {
|
|
@@ -4810,7 +5479,7 @@ class SfuV2Parser {
|
|
|
4810
5479
|
}
|
|
4811
5480
|
}
|
|
4812
5481
|
|
|
4813
|
-
class VegaConnection extends EventEmitter {
|
|
5482
|
+
class VegaConnection extends EventEmitter$1 {
|
|
4814
5483
|
constructor(wsUrl, logger, protocol = "whereby-sfu#v4") {
|
|
4815
5484
|
super();
|
|
4816
5485
|
|
|
@@ -5385,18 +6054,214 @@ const maybeTurnOnly = (transportConfig, features) => {
|
|
|
5385
6054
|
}
|
|
5386
6055
|
};
|
|
5387
6056
|
|
|
6057
|
+
const MEDIA_QUALITY = Object.freeze({
|
|
6058
|
+
ok: "ok",
|
|
6059
|
+
warning: "warning",
|
|
6060
|
+
critical: "critical",
|
|
6061
|
+
});
|
|
6062
|
+
|
|
6063
|
+
const MONITOR_INTERVAL = 600; // ms
|
|
6064
|
+
const TREND_HORIZON = 3; // number of monitor intervals needed for quality to change
|
|
6065
|
+
const WARNING_SCORE = 9;
|
|
6066
|
+
const CRITICAL_SCORE = 7;
|
|
6067
|
+
|
|
6068
|
+
class VegaMediaQualityMonitor extends EventEmitter {
|
|
6069
|
+
constructor({ logger }) {
|
|
6070
|
+
super();
|
|
6071
|
+
this._logger = logger;
|
|
6072
|
+
this._clients = {};
|
|
6073
|
+
this._producers = {};
|
|
6074
|
+
this._startMonitor();
|
|
6075
|
+
}
|
|
6076
|
+
|
|
6077
|
+
close() {
|
|
6078
|
+
clearInterval(this._intervalHandle);
|
|
6079
|
+
delete this._intervalHandle;
|
|
6080
|
+
this._producers = {};
|
|
6081
|
+
this._clients = {};
|
|
6082
|
+
}
|
|
6083
|
+
|
|
6084
|
+
_startMonitor() {
|
|
6085
|
+
this._intervalHandle = setInterval(() => {
|
|
6086
|
+
Object.entries(this._producers).forEach(([clientId, producers]) => {
|
|
6087
|
+
this._evaluateClient(clientId, producers);
|
|
6088
|
+
});
|
|
6089
|
+
}, MONITOR_INTERVAL);
|
|
6090
|
+
}
|
|
6091
|
+
|
|
6092
|
+
_evaluateClient(clientId, producers) {
|
|
6093
|
+
if (!this._clients[clientId]) {
|
|
6094
|
+
this._clients[clientId] = {
|
|
6095
|
+
audio: { currentQuality: MEDIA_QUALITY.ok, trend: [] },
|
|
6096
|
+
video: { currentQuality: MEDIA_QUALITY.ok, trend: [] },
|
|
6097
|
+
};
|
|
6098
|
+
}
|
|
6099
|
+
|
|
6100
|
+
this._evaluateProducer(
|
|
6101
|
+
clientId,
|
|
6102
|
+
Object.values(producers).filter((p) => p.kind === "audio"),
|
|
6103
|
+
"audio"
|
|
6104
|
+
);
|
|
6105
|
+
this._evaluateProducer(
|
|
6106
|
+
clientId,
|
|
6107
|
+
Object.values(producers).filter((p) => p.kind === "video"),
|
|
6108
|
+
"video"
|
|
6109
|
+
);
|
|
6110
|
+
}
|
|
6111
|
+
|
|
6112
|
+
_evaluateProducer(clientId, producers, kind) {
|
|
6113
|
+
if (producers.length === 0) {
|
|
6114
|
+
return;
|
|
6115
|
+
}
|
|
6116
|
+
|
|
6117
|
+
const avgScore = producers.reduce((prev, curr) => prev + curr.score, 0) / producers.length;
|
|
6118
|
+
const newQuality = this._evaluateScore(avgScore);
|
|
6119
|
+
const qualityChanged = this._updateTrend(newQuality, this._clients[clientId][kind]);
|
|
6120
|
+
if (qualityChanged) {
|
|
6121
|
+
this.emit(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, {
|
|
6122
|
+
clientId,
|
|
6123
|
+
kind,
|
|
6124
|
+
quality: newQuality,
|
|
6125
|
+
});
|
|
6126
|
+
}
|
|
6127
|
+
}
|
|
6128
|
+
|
|
6129
|
+
_updateTrend(newQuality, state) {
|
|
6130
|
+
state.trend.push(newQuality);
|
|
6131
|
+
if (state.trend.length > TREND_HORIZON) {
|
|
6132
|
+
state.trend.shift();
|
|
6133
|
+
}
|
|
6134
|
+
|
|
6135
|
+
if (newQuality !== state.currentQuality && state.trend.every((t) => t !== state.currentQuality)) {
|
|
6136
|
+
state.currentQuality = newQuality;
|
|
6137
|
+
return true;
|
|
6138
|
+
} else {
|
|
6139
|
+
return false;
|
|
6140
|
+
}
|
|
6141
|
+
}
|
|
6142
|
+
|
|
6143
|
+
addProducer(clientId, producerId) {
|
|
6144
|
+
if (!clientId || !producerId || !(typeof clientId === "string" && typeof producerId === "string")) {
|
|
6145
|
+
this._logger.warn("Missing clientId or producerId");
|
|
6146
|
+
return;
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6149
|
+
if (!this._producers[clientId]) {
|
|
6150
|
+
this._producers[clientId] = {};
|
|
6151
|
+
}
|
|
6152
|
+
|
|
6153
|
+
this._producers[clientId][producerId] = {};
|
|
6154
|
+
}
|
|
6155
|
+
|
|
6156
|
+
removeProducer(clientId, producerId) {
|
|
6157
|
+
delete this._producers[clientId][producerId];
|
|
6158
|
+
|
|
6159
|
+
if (Object.keys(this._producers[clientId]).length === 0) {
|
|
6160
|
+
delete this._producers[clientId];
|
|
6161
|
+
}
|
|
6162
|
+
}
|
|
6163
|
+
|
|
6164
|
+
addConsumer(clientId, consumerId) {
|
|
6165
|
+
if (!clientId || !consumerId) {
|
|
6166
|
+
this._logger.warn("Missing clientId or consumerId");
|
|
6167
|
+
return;
|
|
6168
|
+
}
|
|
6169
|
+
|
|
6170
|
+
if (!this._producers[clientId]) {
|
|
6171
|
+
this._producers[clientId] = {};
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
this._producers[clientId][consumerId] = {};
|
|
6175
|
+
}
|
|
6176
|
+
|
|
6177
|
+
removeConsumer(clientId, consumerId) {
|
|
6178
|
+
delete this._producers[clientId][consumerId];
|
|
6179
|
+
|
|
6180
|
+
if (Object.keys(this._producers[clientId]).length === 0) {
|
|
6181
|
+
delete this._producers[clientId];
|
|
6182
|
+
}
|
|
6183
|
+
}
|
|
6184
|
+
|
|
6185
|
+
addProducerScore(clientId, producerId, kind, score) {
|
|
6186
|
+
if (
|
|
6187
|
+
!Array.isArray(score) ||
|
|
6188
|
+
score.length === 0 ||
|
|
6189
|
+
score.some((s) => !s || !s.hasOwnProperty("score") || typeof s.score !== "number" || isNaN(s.score))
|
|
6190
|
+
) {
|
|
6191
|
+
this._logger.warn("VegaMediaQualityMonitor.addProducerScore(): Unexpected producer score format");
|
|
6192
|
+
return;
|
|
6193
|
+
}
|
|
6194
|
+
this._producers[clientId][producerId] = { kind, score: this._calcAvgProducerScore(score.map((s) => s.score)) };
|
|
6195
|
+
}
|
|
6196
|
+
|
|
6197
|
+
addConsumerScore(clientId, consumerId, kind, score) {
|
|
6198
|
+
if (!score || !score.hasOwnProperty("producerScores") || !Array.isArray(score.producerScores)) {
|
|
6199
|
+
this._logger.warn("VegaMediaQualityMonitor.addConsumerScore(): Unexpected consumer score format");
|
|
6200
|
+
return;
|
|
6201
|
+
}
|
|
6202
|
+
this._producers[clientId][consumerId] = { kind, score: this._calcAvgProducerScore(score.producerScores) };
|
|
6203
|
+
}
|
|
6204
|
+
|
|
6205
|
+
_evaluateScore(score) {
|
|
6206
|
+
if (score <= WARNING_SCORE && score > CRITICAL_SCORE) {
|
|
6207
|
+
return MEDIA_QUALITY.warning;
|
|
6208
|
+
} else if (score <= CRITICAL_SCORE && score > 0) {
|
|
6209
|
+
return MEDIA_QUALITY.critical;
|
|
6210
|
+
} else {
|
|
6211
|
+
return MEDIA_QUALITY.ok;
|
|
6212
|
+
}
|
|
6213
|
+
}
|
|
6214
|
+
|
|
6215
|
+
_calcAvgProducerScore(scores) {
|
|
6216
|
+
try {
|
|
6217
|
+
if (!Array.isArray(scores) || scores.length === 0) {
|
|
6218
|
+
return 0;
|
|
6219
|
+
}
|
|
6220
|
+
|
|
6221
|
+
let totalScore = 0;
|
|
6222
|
+
let divisor = 0;
|
|
6223
|
+
|
|
6224
|
+
scores.forEach((score) => {
|
|
6225
|
+
if (score > 0) {
|
|
6226
|
+
totalScore += score;
|
|
6227
|
+
divisor++;
|
|
6228
|
+
}
|
|
6229
|
+
});
|
|
6230
|
+
|
|
6231
|
+
if (totalScore === 0 || divisor === 0) {
|
|
6232
|
+
return 0;
|
|
6233
|
+
} else {
|
|
6234
|
+
return totalScore / divisor;
|
|
6235
|
+
}
|
|
6236
|
+
} catch (error) {
|
|
6237
|
+
this._logger.error(error);
|
|
6238
|
+
return 0;
|
|
6239
|
+
}
|
|
6240
|
+
}
|
|
6241
|
+
}
|
|
6242
|
+
|
|
5388
6243
|
const browserName = adapter.browserDetails.browser;
|
|
5389
6244
|
let unloading = false;
|
|
5390
6245
|
|
|
5391
6246
|
const RESTARTICE_ERROR_RETRY_THRESHOLD_IN_MS = 3500;
|
|
5392
6247
|
const RESTARTICE_ERROR_MAX_RETRY_COUNT = 5;
|
|
5393
|
-
const OUTBOUND_CAM_OUTBOUND_STREAM_ID = v4();
|
|
5394
|
-
const OUTBOUND_SCREEN_OUTBOUND_STREAM_ID = v4();
|
|
6248
|
+
const OUTBOUND_CAM_OUTBOUND_STREAM_ID = v4$1();
|
|
6249
|
+
const OUTBOUND_SCREEN_OUTBOUND_STREAM_ID = v4$1();
|
|
5395
6250
|
|
|
5396
6251
|
if (browserName === "chrome") window.document.addEventListener("beforeunload", () => (unloading = true));
|
|
5397
6252
|
|
|
5398
6253
|
class VegaRtcManager {
|
|
5399
|
-
constructor({
|
|
6254
|
+
constructor({
|
|
6255
|
+
selfId,
|
|
6256
|
+
room,
|
|
6257
|
+
emitter,
|
|
6258
|
+
serverSocket,
|
|
6259
|
+
webrtcProvider,
|
|
6260
|
+
features,
|
|
6261
|
+
eventClaim,
|
|
6262
|
+
logger = console,
|
|
6263
|
+
deviceHandlerFactory,
|
|
6264
|
+
}) {
|
|
5400
6265
|
assert$1.ok(selfId, "selfId is required");
|
|
5401
6266
|
assert$1.ok(room, "room is required");
|
|
5402
6267
|
assert$1.ok(emitter && emitter.emit, "emitter is required");
|
|
@@ -5420,7 +6285,12 @@ class VegaRtcManager {
|
|
|
5420
6285
|
this._micAnalyser = null;
|
|
5421
6286
|
this._micAnalyserDebugger = null;
|
|
5422
6287
|
|
|
5423
|
-
|
|
6288
|
+
if (deviceHandlerFactory) {
|
|
6289
|
+
this._mediasoupDevice = new Device({ handlerFactory: deviceHandlerFactory });
|
|
6290
|
+
} else {
|
|
6291
|
+
this._mediasoupDevice = new Device({ handlerName: getHandler() });
|
|
6292
|
+
}
|
|
6293
|
+
|
|
5424
6294
|
this._routerRtpCapabilities = null;
|
|
5425
6295
|
|
|
5426
6296
|
this._sendTransport = null;
|
|
@@ -5483,6 +6353,11 @@ class VegaRtcManager {
|
|
|
5483
6353
|
// Retry if connection closed until disconnectAll called;
|
|
5484
6354
|
this._reconnect = true;
|
|
5485
6355
|
this._reconnectTimeOut = null;
|
|
6356
|
+
|
|
6357
|
+
this._qualityMonitor = new VegaMediaQualityMonitor({ logger: this._logger });
|
|
6358
|
+
this._qualityMonitor.on(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, (payload) => {
|
|
6359
|
+
this._emitToPWA(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, payload);
|
|
6360
|
+
});
|
|
5486
6361
|
}
|
|
5487
6362
|
|
|
5488
6363
|
_updateAndScheduleMediaServersRefresh({ iceServers, sfuServer, mediaserverConfigTtlSeconds }) {
|
|
@@ -5567,6 +6442,8 @@ class VegaRtcManager {
|
|
|
5567
6442
|
if (this._reconnect) {
|
|
5568
6443
|
this._reconnectTimeOut = setTimeout(() => this._connect(), 1000);
|
|
5569
6444
|
}
|
|
6445
|
+
|
|
6446
|
+
this._qualityMonitor.close();
|
|
5570
6447
|
}
|
|
5571
6448
|
|
|
5572
6449
|
async _join() {
|
|
@@ -5814,6 +6691,7 @@ class VegaRtcManager {
|
|
|
5814
6691
|
currentPaused ? producer.pause() : producer.resume();
|
|
5815
6692
|
|
|
5816
6693
|
this._micProducer = producer;
|
|
6694
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
5817
6695
|
|
|
5818
6696
|
producer.observer.once("close", () => {
|
|
5819
6697
|
this._logger.debug('micProducer "close" event');
|
|
@@ -5823,6 +6701,7 @@ class VegaRtcManager {
|
|
|
5823
6701
|
|
|
5824
6702
|
this._micProducer = null;
|
|
5825
6703
|
this._micProducerPromise = null;
|
|
6704
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
5826
6705
|
});
|
|
5827
6706
|
|
|
5828
6707
|
if (this._micTrack !== this._micProducer.track) await this._replaceMicTrack();
|
|
@@ -6008,6 +6887,7 @@ class VegaRtcManager {
|
|
|
6008
6887
|
currentPaused ? producer.pause() : producer.resume();
|
|
6009
6888
|
|
|
6010
6889
|
this._webcamProducer = producer;
|
|
6890
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
6011
6891
|
producer.observer.once("close", () => {
|
|
6012
6892
|
this._logger.debug('webcamProducer "close" event');
|
|
6013
6893
|
|
|
@@ -6016,6 +6896,7 @@ class VegaRtcManager {
|
|
|
6016
6896
|
|
|
6017
6897
|
this._webcamProducer = null;
|
|
6018
6898
|
this._webcamProducerPromise = null;
|
|
6899
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
6019
6900
|
});
|
|
6020
6901
|
|
|
6021
6902
|
// Has someone replaced the track?
|
|
@@ -6110,6 +6991,7 @@ class VegaRtcManager {
|
|
|
6110
6991
|
});
|
|
6111
6992
|
|
|
6112
6993
|
this._screenVideoProducer = producer;
|
|
6994
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
6113
6995
|
producer.observer.once("close", () => {
|
|
6114
6996
|
this._logger.debug('screenVideoProducer "close" event');
|
|
6115
6997
|
|
|
@@ -6118,6 +7000,7 @@ class VegaRtcManager {
|
|
|
6118
7000
|
|
|
6119
7001
|
this._screenVideoProducer = null;
|
|
6120
7002
|
this._screenVideoProducerPromise = null;
|
|
7003
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
6121
7004
|
});
|
|
6122
7005
|
|
|
6123
7006
|
// Has someone replaced the track?
|
|
@@ -6187,6 +7070,7 @@ class VegaRtcManager {
|
|
|
6187
7070
|
});
|
|
6188
7071
|
|
|
6189
7072
|
this._screenAudioProducer = producer;
|
|
7073
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
6190
7074
|
producer.observer.once("close", () => {
|
|
6191
7075
|
this._logger.debug('screenAudioProducer "close" event');
|
|
6192
7076
|
|
|
@@ -6195,6 +7079,7 @@ class VegaRtcManager {
|
|
|
6195
7079
|
|
|
6196
7080
|
this._screenAudioProducer = null;
|
|
6197
7081
|
this._screenAudioProducerPromise = null;
|
|
7082
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
6198
7083
|
});
|
|
6199
7084
|
|
|
6200
7085
|
// Has someone replaced the track?
|
|
@@ -6294,6 +7179,18 @@ class VegaRtcManager {
|
|
|
6294
7179
|
rtcStats.sendEvent("colocation_changed", { colocation });
|
|
6295
7180
|
}
|
|
6296
7181
|
|
|
7182
|
+
/**
|
|
7183
|
+
* This sends a signal to the SFU to pause all incoming video streams to the client.
|
|
7184
|
+
*
|
|
7185
|
+
* @param {boolean} audioOnly
|
|
7186
|
+
*/
|
|
7187
|
+
setAudioOnly(audioOnly) {
|
|
7188
|
+
this._vegaConnection?.message(audioOnly ? "enableAudioOnly" : "disableAudioOnly");
|
|
7189
|
+
}
|
|
7190
|
+
|
|
7191
|
+
// the track ids send by signal server for remote-initiated screenshares
|
|
7192
|
+
setRemoteScreenshareVideoTrackIds(/*remoteScreenshareVideoTrackIds*/) {}
|
|
7193
|
+
|
|
6297
7194
|
/**
|
|
6298
7195
|
* The unique identifier for this room session.
|
|
6299
7196
|
*
|
|
@@ -6728,6 +7625,10 @@ class VegaRtcManager {
|
|
|
6728
7625
|
return this._onDataConsumerClosed(data);
|
|
6729
7626
|
case "dominantSpeaker":
|
|
6730
7627
|
return this._onDominantSpeaker(data);
|
|
7628
|
+
case "consumerScore":
|
|
7629
|
+
return this._onConsumerScore(data);
|
|
7630
|
+
case "producerScore":
|
|
7631
|
+
return this._onProducerScore(data);
|
|
6731
7632
|
default:
|
|
6732
7633
|
this._logger.debug(`unknown message method "${method}"`);
|
|
6733
7634
|
return;
|
|
@@ -6748,8 +7649,10 @@ class VegaRtcManager {
|
|
|
6748
7649
|
consumer.appData.spatialLayer = 2;
|
|
6749
7650
|
|
|
6750
7651
|
this._consumers.set(consumer.id, consumer);
|
|
7652
|
+
this._qualityMonitor.addConsumer(consumer.appData.sourceClientId, consumer.id);
|
|
6751
7653
|
consumer.observer.once("close", () => {
|
|
6752
7654
|
this._consumers.delete(consumer.id);
|
|
7655
|
+
this._qualityMonitor.removeConsumer(consumer.appData.sourceClientId, consumer.id);
|
|
6753
7656
|
|
|
6754
7657
|
this._consumerClosedCleanup(consumer);
|
|
6755
7658
|
});
|
|
@@ -6823,6 +7726,28 @@ class VegaRtcManager {
|
|
|
6823
7726
|
}
|
|
6824
7727
|
}
|
|
6825
7728
|
|
|
7729
|
+
_onConsumerScore({ consumerId, kind, score }) {
|
|
7730
|
+
this._logger.debug("_onConsumerScore()", { consumerId, kind, score });
|
|
7731
|
+
const {
|
|
7732
|
+
appData: { sourceClientId },
|
|
7733
|
+
} = this._consumers.get(consumerId) || { appData: {} };
|
|
7734
|
+
|
|
7735
|
+
if (sourceClientId) {
|
|
7736
|
+
this._qualityMonitor.addConsumerScore(sourceClientId, consumerId, kind, score);
|
|
7737
|
+
}
|
|
7738
|
+
}
|
|
7739
|
+
|
|
7740
|
+
_onProducerScore({ producerId, kind, score }) {
|
|
7741
|
+
this._logger.debug("_onProducerScore()", { producerId, kind, score });
|
|
7742
|
+
[this._micProducer, this._webcamProducer, this._screenVideoProducer, this._screenAudioProducer].forEach(
|
|
7743
|
+
(producer) => {
|
|
7744
|
+
if (producer?.id === producerId) {
|
|
7745
|
+
this._qualityMonitor.addProducerScore(this._selfId, producerId, kind, score);
|
|
7746
|
+
}
|
|
7747
|
+
}
|
|
7748
|
+
);
|
|
7749
|
+
}
|
|
7750
|
+
|
|
6826
7751
|
async _onDataConsumerReady(options) {
|
|
6827
7752
|
this._logger.debug("_onDataConsumerReady()", { id: options.id, producerId: options.producerId });
|
|
6828
7753
|
const consumer = await this._receiveTransport.consumeData(options);
|
|
@@ -6906,7 +7831,7 @@ class VegaRtcManager {
|
|
|
6906
7831
|
} = clientState;
|
|
6907
7832
|
|
|
6908
7833
|
// Need to pause/resume any consumers that are part of a stream that has been
|
|
6909
|
-
// accepted or
|
|
7834
|
+
// accepted or disconnected by the PWA
|
|
6910
7835
|
const toPauseConsumers = [];
|
|
6911
7836
|
const toResumeConsumers = [];
|
|
6912
7837
|
|
|
@@ -7008,6 +7933,10 @@ class VegaRtcManager {
|
|
|
7008
7933
|
setMicAnalyserParams(params) {
|
|
7009
7934
|
this._micAnalyser?.setParams(params);
|
|
7010
7935
|
}
|
|
7936
|
+
|
|
7937
|
+
hasClient(clientId) {
|
|
7938
|
+
return this._clientStates.has(clientId);
|
|
7939
|
+
}
|
|
7011
7940
|
}
|
|
7012
7941
|
|
|
7013
7942
|
class RtcManagerDispatcher {
|
|
@@ -7016,7 +7945,16 @@ class RtcManagerDispatcher {
|
|
|
7016
7945
|
this.currentManager = null;
|
|
7017
7946
|
serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room, selfId, error, eventClaim }) => {
|
|
7018
7947
|
if (error) return; // ignore error responses which lack room
|
|
7019
|
-
const config = {
|
|
7948
|
+
const config = {
|
|
7949
|
+
selfId,
|
|
7950
|
+
room,
|
|
7951
|
+
emitter,
|
|
7952
|
+
serverSocket,
|
|
7953
|
+
webrtcProvider,
|
|
7954
|
+
features,
|
|
7955
|
+
eventClaim,
|
|
7956
|
+
deviceHandlerFactory: features?.deviceHandlerFactory,
|
|
7957
|
+
};
|
|
7020
7958
|
const isSfu = !!room.sfuServer;
|
|
7021
7959
|
if (this.currentManager) {
|
|
7022
7960
|
if (this.currentManager.isInitializedWith({ selfId, roomName: room.name, isSfu })) {
|
|
@@ -7037,6 +7975,7 @@ class RtcManagerDispatcher {
|
|
|
7037
7975
|
rtcManager.setupSocketListeners();
|
|
7038
7976
|
emitter.emit(EVENTS.RTC_MANAGER_CREATED, { rtcManager });
|
|
7039
7977
|
this.currentManager = rtcManager;
|
|
7978
|
+
serverSocket.setRtcManager(rtcManager);
|
|
7040
7979
|
});
|
|
7041
7980
|
}
|
|
7042
7981
|
|
|
@@ -7121,6 +8060,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
|
7121
8060
|
const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
|
|
7122
8061
|
const isCameraEnabled = selectIsCameraEnabled(state);
|
|
7123
8062
|
const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
|
|
8063
|
+
const isNodeSdk = selectAppIsNodeSdk(state);
|
|
7124
8064
|
if (dispatcher) {
|
|
7125
8065
|
return;
|
|
7126
8066
|
}
|
|
@@ -7146,6 +8086,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
|
7146
8086
|
vp9On: false,
|
|
7147
8087
|
h264On: false,
|
|
7148
8088
|
simulcastScreenshareOn: false,
|
|
8089
|
+
deviceHandlerFactory: isNodeSdk ? Chrome111.createFactory() : undefined,
|
|
7149
8090
|
},
|
|
7150
8091
|
});
|
|
7151
8092
|
dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
|
|
@@ -8200,7 +9141,7 @@ var localStorage$1 = localStorage;
|
|
|
8200
9141
|
const events = {
|
|
8201
9142
|
CREDENTIALS_SAVED: "credentials_saved",
|
|
8202
9143
|
};
|
|
8203
|
-
class CredentialsService extends EventEmitter
|
|
9144
|
+
class CredentialsService extends EventEmitter {
|
|
8204
9145
|
/**
|
|
8205
9146
|
* Service to manage Whereby's Rest API credentials.
|
|
8206
9147
|
*
|
|
@@ -8690,7 +9631,7 @@ const selectRoomConnectionState = createSelector(selectChatMessages, selectCloud
|
|
|
8690
9631
|
return state;
|
|
8691
9632
|
});
|
|
8692
9633
|
|
|
8693
|
-
const sdkVersion = "2.1.0-
|
|
9634
|
+
const sdkVersion = "2.1.0-beta3";
|
|
8694
9635
|
|
|
8695
9636
|
const initialState$1 = {
|
|
8696
9637
|
chatMessages: [],
|