@whereby.com/core 0.2.0-beta.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1307 -275
- package/dist/utils.js +1 -1
- package/package.json +5 -5
- /package/dist/{debounce-IlgXge5U.js → debounce-B-cWYxqK.js} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
import { createAsyncThunk, createListenerMiddleware, addListener, createSlice, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
2
2
|
import { io } from 'socket.io-client';
|
|
3
3
|
import adapter from 'webrtc-adapter';
|
|
4
|
-
import {
|
|
4
|
+
import EventEmitter, { EventEmitter as EventEmitter$1 } from 'events';
|
|
5
|
+
import { d as debounce } from './debounce-B-cWYxqK.js';
|
|
5
6
|
import SDPUtils from 'sdp';
|
|
6
7
|
import * as sdpTransform from 'sdp-transform';
|
|
7
|
-
import { v4 } from 'uuid';
|
|
8
|
+
import { v4 as v4$1 } from 'uuid';
|
|
9
|
+
import { Address6 } from 'ip-address';
|
|
10
|
+
import checkIp from 'check-ip';
|
|
11
|
+
import validate from 'uuid-validate';
|
|
8
12
|
import { detectDevice, Device } from 'mediasoup-client';
|
|
9
|
-
import EventEmitter$1, { EventEmitter } from 'events';
|
|
10
13
|
import nodeBtoa from 'btoa';
|
|
11
14
|
import axios from 'axios';
|
|
12
15
|
|
|
@@ -80,6 +83,7 @@ const signalEvents = {
|
|
|
80
83
|
audioEnabled: createSignalEventAction("audioEnabled"),
|
|
81
84
|
chatMessage: createSignalEventAction("chatMessage"),
|
|
82
85
|
clientLeft: createSignalEventAction("clientLeft"),
|
|
86
|
+
clientKicked: createSignalEventAction("clientKicked"),
|
|
83
87
|
clientMetadataReceived: createSignalEventAction("clientMetadataReceived"),
|
|
84
88
|
cloudRecordingStarted: createSignalEventAction("cloudRecordingStarted"),
|
|
85
89
|
cloudRecordingStopped: createSignalEventAction("cloudRecordingStopped"),
|
|
@@ -184,13 +188,535 @@ createReactor([selectShouldFetchDeviceCredentials], ({ dispatch }, shouldFetchDe
|
|
|
184
188
|
}
|
|
185
189
|
});
|
|
186
190
|
|
|
191
|
+
let peerConnections = [];
|
|
192
|
+
let peerConnectionCounter = 0;
|
|
193
|
+
const peerConnectionData = new WeakMap();
|
|
194
|
+
|
|
195
|
+
const removePeerConnection = (pc) => {
|
|
196
|
+
peerConnections = peerConnections.filter((old) => old !== pc);
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
if (window.RTCPeerConnection) {
|
|
200
|
+
const OriginalRTCPeerConnection = window.RTCPeerConnection;
|
|
201
|
+
function PatchedRTCPeerConnection(rtcConfig) {
|
|
202
|
+
const pc = new OriginalRTCPeerConnection(rtcConfig);
|
|
203
|
+
peerConnections.push(pc);
|
|
204
|
+
peerConnectionData.set(pc, { index: peerConnectionCounter++ });
|
|
205
|
+
const onConnectionStateChange = () => {
|
|
206
|
+
if (pc.connectionState === "closed") {
|
|
207
|
+
removePeerConnection(pc);
|
|
208
|
+
pc.removeEventListener("connectionstatechange", onConnectionStateChange);
|
|
209
|
+
}
|
|
210
|
+
};
|
|
211
|
+
pc.addEventListener("connectionstatechange", onConnectionStateChange);
|
|
212
|
+
return pc;
|
|
213
|
+
}
|
|
214
|
+
PatchedRTCPeerConnection.prototype = OriginalRTCPeerConnection.prototype;
|
|
215
|
+
window.RTCPeerConnection = PatchedRTCPeerConnection;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const debugOn = new URLSearchParams(window.location.search).has("debug");
|
|
219
|
+
|
|
220
|
+
class Logger {
|
|
221
|
+
constructor() {
|
|
222
|
+
this._isEnabled = debugOn;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
isEnabled() {
|
|
226
|
+
return this._isEnabled;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
enable() {
|
|
230
|
+
this._isEnabled = true;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
disable() {
|
|
234
|
+
this._isEnabled = false;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
info(...params) {
|
|
238
|
+
if (!this._isEnabled) {
|
|
239
|
+
return;
|
|
240
|
+
}
|
|
241
|
+
// eslint-disable-next-line no-console
|
|
242
|
+
return console.info(...params);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
warn(...params) {
|
|
246
|
+
if (!this._isEnabled) {
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
return console.warn(...params);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
error(...params) {
|
|
253
|
+
if (!this._isEnabled) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
return console.error(...params);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
withDebugLogger(myDebugger = null) {
|
|
260
|
+
this._debugger = myDebugger;
|
|
261
|
+
return this;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
debug(...params) {
|
|
265
|
+
if (!this._isEnabled || !this._debugger) {
|
|
266
|
+
return;
|
|
267
|
+
}
|
|
268
|
+
const suppliedParams = [];
|
|
269
|
+
params.forEach((param) => {
|
|
270
|
+
if (typeof param === "function") {
|
|
271
|
+
const suppliedParam = param();
|
|
272
|
+
suppliedParams.push(suppliedParam);
|
|
273
|
+
} else {
|
|
274
|
+
suppliedParams.push(param);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
this._debugger.print(...suppliedParams);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
let currentMonitor = null;
|
|
282
|
+
|
|
283
|
+
const getUpdatedStats = () => currentMonitor?.getUpdatedStats();
|
|
284
|
+
|
|
285
|
+
// Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
|
|
286
|
+
|
|
287
|
+
// Requests: messages from the client to the server
|
|
288
|
+
const PROTOCOL_REQUESTS = {
|
|
289
|
+
BLOCK_CLIENT: "block_client",
|
|
290
|
+
CLAIM_ROOM: "claim_room",
|
|
291
|
+
CLEAR_CHAT_HISTORY: "clear_chat_history",
|
|
292
|
+
ENABLE_AUDIO: "enable_audio",
|
|
293
|
+
ENABLE_VIDEO: "enable_video",
|
|
294
|
+
END_STREAM: "end_stream",
|
|
295
|
+
FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
|
|
296
|
+
HANDLE_KNOCK: "handle_knock",
|
|
297
|
+
IDENTIFY_DEVICE: "identify_device",
|
|
298
|
+
INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
|
|
299
|
+
JOIN_ROOM: "join_room",
|
|
300
|
+
KICK_CLIENT: "kick_client",
|
|
301
|
+
KNOCK_ROOM: "knock_room",
|
|
302
|
+
LEAVE_ROOM: "leave_room",
|
|
303
|
+
SEND_CLIENT_METADATA: "send_client_metadata",
|
|
304
|
+
SET_LOCK: "set_lock",
|
|
305
|
+
SHARE_MEDIA: "share_media",
|
|
306
|
+
START_NEW_STREAM: "start_new_stream",
|
|
307
|
+
START_SCREENSHARE: "start_screenshare",
|
|
308
|
+
STOP_SCREENSHARE: "stop_screenshare",
|
|
309
|
+
START_URL_EMBED: "start_url_embed",
|
|
310
|
+
STOP_URL_EMBED: "stop_url_embed",
|
|
311
|
+
START_RECORDING: "start_recording",
|
|
312
|
+
STOP_RECORDING: "stop_recording",
|
|
313
|
+
SFU_TOKEN: "sfu_token",
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
// Responses: messages from the server to the client, in response to requests
|
|
317
|
+
const PROTOCOL_RESPONSES = {
|
|
318
|
+
AUDIO_ENABLED: "audio_enabled",
|
|
319
|
+
BACKGROUND_IMAGE_CHANGED: "background_image_changed",
|
|
320
|
+
BLOCK_ADDED: "block_added",
|
|
321
|
+
BLOCK_REMOVED: "block_removed",
|
|
322
|
+
CHAT_HISTORY_CLEARED: "chat_history_cleared",
|
|
323
|
+
CLIENT_BLOCKED: "client_blocked",
|
|
324
|
+
CLIENT_INVITED_AS_MEMBER: "client_invited_as_member",
|
|
325
|
+
CLIENT_KICKED: "client_kicked",
|
|
326
|
+
CLIENT_LEFT: "client_left",
|
|
327
|
+
CLIENT_METADATA_RECEIVED: "client_metadata_received",
|
|
328
|
+
CLIENT_READY: "client_ready",
|
|
329
|
+
CLIENT_ROLE_CHANGED: "client_role_changed",
|
|
330
|
+
CLIENT_USER_ID_CHANGED: "client_user_id_changed",
|
|
331
|
+
CONTACTS_UPDATED: "contacts_updated",
|
|
332
|
+
DEVICE_IDENTIFIED: "device_identified",
|
|
333
|
+
ROOM_ROLES_UPDATED: "room_roles_updated",
|
|
334
|
+
KNOCK_HANDLED: "knock_handled",
|
|
335
|
+
KNOCK_PAGE_BACKGROUND_CHANGED: "knock_page_background_changed",
|
|
336
|
+
KNOCKER_LEFT: "knocker_left",
|
|
337
|
+
MEDIASERVER_CONFIG: "mediaserver_config",
|
|
338
|
+
MEDIA_SHARED: "media_shared",
|
|
339
|
+
MEMBER_INVITE: "member_invite",
|
|
340
|
+
NEW_CLIENT: "new_client",
|
|
341
|
+
NEW_STREAM_STARTED: "new_stream_started",
|
|
342
|
+
SCREENSHARE_STARTED: "screenshare_started",
|
|
343
|
+
SCREENSHARE_STOPPED: "screenshare_stopped",
|
|
344
|
+
OWNER_NOTIFIED: "owner_notified",
|
|
345
|
+
OWNERS_CHANGED: "owners_changed",
|
|
346
|
+
PLAY_CLIENT_STICKER: "play_client_sticker",
|
|
347
|
+
ROOM_INTEGRATION_ENABLED: "room_integration_enabled",
|
|
348
|
+
ROOM_INTEGRATION_DISABLED: "room_integration_disabled",
|
|
349
|
+
ROOM_JOINED: "room_joined",
|
|
350
|
+
ROOM_KNOCKED: "room_knocked",
|
|
351
|
+
ROOM_LEFT: "room_left",
|
|
352
|
+
ROOM_LOCKED: "room_locked",
|
|
353
|
+
ROOM_PERMISSIONS_CHANGED: "room_permissions_changed",
|
|
354
|
+
ROOM_LOGO_CHANGED: "room_logo_changed",
|
|
355
|
+
ROOM_TYPE_CHANGED: "room_type_changed",
|
|
356
|
+
ROOM_MODE_CHANGED: "room_mode_changed",
|
|
357
|
+
SOCKET_USER_ID_CHANGED: "socket_user_id_changed",
|
|
358
|
+
STICKERS_UNLOCKED: "stickers_unlocked",
|
|
359
|
+
STREAM_ENDED: "stream_ended",
|
|
360
|
+
URL_EMBED_STARTED: "url_embed_started",
|
|
361
|
+
URL_EMBED_STOPPED: "url_embed_stopped",
|
|
362
|
+
RECORDING_STARTED: "recording_started",
|
|
363
|
+
RECORDING_STOPPED: "recording_stopped",
|
|
364
|
+
USER_NOTIFIED: "user_notified",
|
|
365
|
+
VIDEO_ENABLED: "video_enabled",
|
|
366
|
+
CLIENT_UNABLE_TO_JOIN: "client_unable_to_join",
|
|
367
|
+
};
|
|
368
|
+
|
|
369
|
+
// Relays: messages between clients, relayed through the server
|
|
370
|
+
const RELAY_MESSAGES = {
|
|
371
|
+
CHAT_MESSAGE: "chat_message",
|
|
372
|
+
CHAT_READ_STATE: "chat_read_state",
|
|
373
|
+
CHAT_STATE: "chat_state",
|
|
374
|
+
ICE_CANDIDATE: "ice_candidate",
|
|
375
|
+
ICE_END_OF_CANDIDATES: "ice_endofcandidates",
|
|
376
|
+
READY_TO_RECEIVE_OFFER: "ready_to_receive_offer",
|
|
377
|
+
REMOTE_CLIENT_MEDIA_REQUEST: "remote_client_media_request",
|
|
378
|
+
SDP_ANSWER: "sdp_answer",
|
|
379
|
+
SDP_OFFER: "sdp_offer",
|
|
380
|
+
VIDEO_STICKER: "video_sticker",
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// Events: something happened that we want to let the client know about
|
|
384
|
+
const PROTOCOL_EVENTS = {
|
|
385
|
+
PENDING_CLIENT_LEFT: "pending_client_left",
|
|
386
|
+
MEDIA_QUALITY_CHANGED: "media_quality_changed",
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
const logger$9 = new Logger();
|
|
390
|
+
|
|
391
|
+
class ReconnectManager extends EventEmitter {
|
|
392
|
+
constructor(socket) {
|
|
393
|
+
super();
|
|
394
|
+
this._socket = socket;
|
|
395
|
+
this._clients = {};
|
|
396
|
+
this._signalDisconnectTime = undefined;
|
|
397
|
+
this.rtcManager = undefined;
|
|
398
|
+
this.metrics = {
|
|
399
|
+
roomJoinedLate: 0,
|
|
400
|
+
pendingClientCanceled: 0,
|
|
401
|
+
evaluationFailed: 0,
|
|
402
|
+
roomJoined: 0,
|
|
403
|
+
};
|
|
404
|
+
|
|
405
|
+
socket.on("disconnect", () => {
|
|
406
|
+
this._signalDisconnectTime = Date.now();
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
// We intercept these events and take responsiblity for forwarding them
|
|
410
|
+
socket.on(PROTOCOL_RESPONSES.ROOM_JOINED, (payload) => this._onRoomJoined(payload));
|
|
411
|
+
socket.on(PROTOCOL_RESPONSES.NEW_CLIENT, (payload) => this._onNewClient(payload));
|
|
412
|
+
socket.on(PROTOCOL_RESPONSES.CLIENT_LEFT, (payload) => this._onClientLeft(payload));
|
|
413
|
+
|
|
414
|
+
// We intercept these events and handle them without forwarding them
|
|
415
|
+
socket.on(PROTOCOL_EVENTS.PENDING_CLIENT_LEFT, (payload) => this._onPendingClientLeft(payload));
|
|
416
|
+
|
|
417
|
+
// We gather information from these events but they will also be forwarded
|
|
418
|
+
socket.on(PROTOCOL_RESPONSES.AUDIO_ENABLED, (payload) => this._onAudioEnabled(payload));
|
|
419
|
+
socket.on(PROTOCOL_RESPONSES.VIDEO_ENABLED, (payload) => this._onVideoEnabled(payload));
|
|
420
|
+
socket.on(PROTOCOL_RESPONSES.SCREENSHARE_STARTED, (payload) => this._onScreenshareChanged(payload, true));
|
|
421
|
+
socket.on(PROTOCOL_RESPONSES.SCREENSHARE_STOPPED, (payload) => this._onScreenshareChanged(payload, false));
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async _onRoomJoined(payload) {
|
|
425
|
+
// We might have gotten an error
|
|
426
|
+
if (!payload.room?.clients) {
|
|
427
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
428
|
+
return;
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (!payload.selfId) {
|
|
432
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
433
|
+
return;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
const myDeviceId = payload.room.clients.find((c) => payload.selfId === c.id)?.deviceId;
|
|
437
|
+
if (!myDeviceId) {
|
|
438
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
439
|
+
return;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Try to remove our own pending client if this is a page reload
|
|
443
|
+
// Could also be a first normal room_joined which can never be glitch-free
|
|
444
|
+
if (!this._signalDisconnectTime) {
|
|
445
|
+
this._resetClientState(payload);
|
|
446
|
+
payload.room.clients = payload.room.clients.filter(
|
|
447
|
+
(c) => !(c.deviceId === myDeviceId && c.isPendingToLeave)
|
|
448
|
+
);
|
|
449
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// The threshold for trying glitch-free reconnect should be less than server-side configuration
|
|
454
|
+
const RECONNECT_THRESHOLD = payload.disconnectTimeout * 0.8;
|
|
455
|
+
const timeSinceDisconnect = Date.now() - this._signalDisconnectTime;
|
|
456
|
+
if (timeSinceDisconnect > RECONNECT_THRESHOLD) {
|
|
457
|
+
this._resetClientState(payload);
|
|
458
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
459
|
+
this.metrics.roomJoinedLate++;
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
// At this point we want to try to attempt glitch-free reconnection experience
|
|
464
|
+
|
|
465
|
+
// Filter out our own pending client after page reload
|
|
466
|
+
payload.room.clients = payload.room.clients.filter((c) => !(c.deviceId === myDeviceId && c.isPendingToLeave));
|
|
467
|
+
|
|
468
|
+
const allStats = await getUpdatedStats();
|
|
469
|
+
payload.room.clients.forEach((client) => {
|
|
470
|
+
try {
|
|
471
|
+
if (client.id === payload.selfId) return;
|
|
472
|
+
|
|
473
|
+
// Maybe add client to state
|
|
474
|
+
if (!this._clients[client.id]) {
|
|
475
|
+
this._addClientToState(client);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
// Verify that rtcManager knows about the client
|
|
479
|
+
if (!this.rtcManager?.hasClient(client.id)) {
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Verify that the client state hasn't changed
|
|
484
|
+
if (
|
|
485
|
+
this._hasClientStateChanged({
|
|
486
|
+
clientId: client.id,
|
|
487
|
+
webcam: client.isVideoEnabled,
|
|
488
|
+
mic: client.isAudioEnabled,
|
|
489
|
+
screenShare: client.streams.length > 1,
|
|
490
|
+
})
|
|
491
|
+
) {
|
|
492
|
+
return;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
if (this._wasClientSendingMedia(client.id)) {
|
|
496
|
+
// Verify the client media is still flowing (not stopped from other end)
|
|
497
|
+
if (!this._isClientMediaActive(allStats, client.id)) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
client.mergeWithOldClientState = true;
|
|
503
|
+
} catch (error) {
|
|
504
|
+
logger$9.error("Failed to evaluate if we should merge client state %o", error);
|
|
505
|
+
this.metrics.evaluationFailed++;
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
// We will try to remove any remote client pending to leave
|
|
510
|
+
payload.room.clients.forEach((c) => {
|
|
511
|
+
if (c.isPendingToLeave) {
|
|
512
|
+
this._onPendingClientLeft({ clientId: c.id });
|
|
513
|
+
}
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
this.metrics.roomJoined++;
|
|
517
|
+
this.emit(PROTOCOL_RESPONSES.ROOM_JOINED, payload);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
_onClientLeft(payload) {
|
|
521
|
+
const { clientId } = payload;
|
|
522
|
+
const client = this._clients[clientId];
|
|
523
|
+
|
|
524
|
+
// Remove client from state and clear timeout if client was pending to leave
|
|
525
|
+
if (client) {
|
|
526
|
+
clearTimeout(client.timeout);
|
|
527
|
+
delete this._clients[clientId];
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// Old RTCManager only takes one argument, so rest is ignored.
|
|
531
|
+
this.rtcManager?.disconnect(clientId, /* activeBreakout */ null, payload.eventClaim);
|
|
532
|
+
|
|
533
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, payload);
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
_onPendingClientLeft(payload) {
|
|
537
|
+
const { clientId } = payload;
|
|
538
|
+
const client = this._clients[clientId];
|
|
539
|
+
|
|
540
|
+
if (!client) {
|
|
541
|
+
logger$9.warn(`client ${clientId} not found`);
|
|
542
|
+
return;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// We've already started the check below, don't do it again
|
|
546
|
+
if (client.isPendingToLeave) {
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
client.isPendingToLeave = true;
|
|
551
|
+
if (this._wasClientSendingMedia(clientId)) {
|
|
552
|
+
client.checkActiveMediaAttempts = 0;
|
|
553
|
+
this._abortIfNotActive(payload);
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
_onNewClient(payload) {
|
|
558
|
+
const {
|
|
559
|
+
client: { id: clientId, deviceId },
|
|
560
|
+
} = payload;
|
|
561
|
+
|
|
562
|
+
const client = this._clients[clientId];
|
|
563
|
+
if (client && client.isPendingToLeave) {
|
|
564
|
+
clearTimeout(client.timeoutHandler);
|
|
565
|
+
client.isPendingToLeave = false;
|
|
566
|
+
this.metrics.pendingClientCanceled++;
|
|
567
|
+
return;
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
this._getPendingClientsByDeviceId(deviceId).forEach((client) => {
|
|
571
|
+
clearTimeout(client.timeoutHandler);
|
|
572
|
+
client.isPendingToLeave = undefined;
|
|
573
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, { clientId: client.clientId });
|
|
574
|
+
});
|
|
575
|
+
|
|
576
|
+
this._addClientToState(payload.client);
|
|
577
|
+
this.emit(PROTOCOL_RESPONSES.NEW_CLIENT, payload);
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// Evaluate if we should send send client_left before getting it from signal-server
|
|
581
|
+
async _abortIfNotActive(payload) {
|
|
582
|
+
const { clientId } = payload;
|
|
583
|
+
|
|
584
|
+
let client = this._clients[clientId];
|
|
585
|
+
if (!client?.isPendingToLeave) return;
|
|
586
|
+
|
|
587
|
+
client.checkActiveMediaAttempts++;
|
|
588
|
+
if (client.checkActiveMediaAttempts > 3) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
const stillActive = await this._checkIsActive(clientId);
|
|
593
|
+
if (stillActive) {
|
|
594
|
+
client.timeoutHandler = setTimeout(() => this._abortIfNotActive(payload), 500);
|
|
595
|
+
return;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
client = this._clients[clientId];
|
|
599
|
+
if (client?.isPendingToLeave) {
|
|
600
|
+
clearTimeout(client.timeoutHandler);
|
|
601
|
+
delete this._clients[clientId];
|
|
602
|
+
this.emit(PROTOCOL_RESPONSES.CLIENT_LEFT, payload);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
// Check if client is active
|
|
607
|
+
async _checkIsActive(clientId) {
|
|
608
|
+
const allStats = await getUpdatedStats();
|
|
609
|
+
return this._isClientMediaActive(allStats, clientId);
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
// Check if client has bitrates for all tracks
|
|
613
|
+
_isClientMediaActive(stats, clientId) {
|
|
614
|
+
const clientStats = stats?.[clientId];
|
|
615
|
+
let isActive = false;
|
|
616
|
+
if (clientStats) {
|
|
617
|
+
Object.entries(clientStats.tracks).forEach(([trackId, trackStats]) => {
|
|
618
|
+
if (trackId !== "probator")
|
|
619
|
+
Object.values(trackStats.ssrcs).forEach((ssrcStats) => {
|
|
620
|
+
if ((ssrcStats.bitrate || 0) > 0) isActive = true;
|
|
621
|
+
});
|
|
622
|
+
});
|
|
623
|
+
}
|
|
624
|
+
return isActive;
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
_onAudioEnabled(payload) {
|
|
628
|
+
const { clientId, isAudioEnabled } = payload;
|
|
629
|
+
this._clients[clientId] = {
|
|
630
|
+
...(this._clients[clientId] || {}),
|
|
631
|
+
isAudioEnabled,
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
_onVideoEnabled(payload) {
|
|
636
|
+
const { clientId, isVideoEnabled } = payload;
|
|
637
|
+
this._clients[clientId] = {
|
|
638
|
+
...(this._clients[clientId] || {}),
|
|
639
|
+
isVideoEnabled,
|
|
640
|
+
};
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
_onScreenshareChanged(payload, action) {
|
|
644
|
+
const { clientId } = payload;
|
|
645
|
+
this._clients[clientId] = {
|
|
646
|
+
...(this._clients[clientId] || {}),
|
|
647
|
+
isScreenshareEnabled: action,
|
|
648
|
+
};
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
_hasClientStateChanged({ clientId, webcam, mic, screenShare }) {
|
|
652
|
+
const state = this._clients[clientId];
|
|
653
|
+
|
|
654
|
+
if (!state) {
|
|
655
|
+
throw new Error(`Client ${clientId} not found in ReconnectManager state`);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
if (webcam !== state.isVideoEnabled) {
|
|
659
|
+
return true;
|
|
660
|
+
}
|
|
661
|
+
if (mic !== state.isAudioEnabled) {
|
|
662
|
+
return true;
|
|
663
|
+
}
|
|
664
|
+
if (screenShare !== state.isScreenshareEnabled) {
|
|
665
|
+
return true;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
|
|
671
|
+
_addClientToState(newClient) {
|
|
672
|
+
this._clients[newClient.id] = {
|
|
673
|
+
...(this._clients[newClient.id] || {}),
|
|
674
|
+
isAudioEnabled: newClient.isAudioEnabled,
|
|
675
|
+
isVideoEnabled: newClient.isVideoEnabled,
|
|
676
|
+
isScreenshareEnabled: newClient.streams.length > 1,
|
|
677
|
+
deviceId: newClient.deviceId,
|
|
678
|
+
isPendingToLeave: newClient.isPendingToLeave,
|
|
679
|
+
clientId: newClient.id,
|
|
680
|
+
};
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
_wasClientSendingMedia(clientId) {
|
|
684
|
+
const client = this._clients[clientId];
|
|
685
|
+
|
|
686
|
+
if (!client) {
|
|
687
|
+
throw new Error(`Client ${clientId} not found in ReconnectManager state`);
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return client.isAudioEnabled || client.isVideoEnabled || client.isScreenshareEnabled;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
_getPendingClientsByDeviceId(deviceId) {
|
|
694
|
+
return Object.values(this._clients).filter((clientState) => {
|
|
695
|
+
return clientState.deviceId === deviceId && clientState.isPendingToLeave;
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
_resetClientState(payload) {
|
|
700
|
+
this._clients = {};
|
|
701
|
+
payload.room.clients.forEach((client) => {
|
|
702
|
+
if (client.id === payload.selfId) {
|
|
703
|
+
return;
|
|
704
|
+
} else {
|
|
705
|
+
this._addClientToState(client);
|
|
706
|
+
}
|
|
707
|
+
});
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
187
711
|
const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
|
|
188
712
|
|
|
713
|
+
const NOOP_KEEPALIVE_INTERVAL = 2000;
|
|
714
|
+
|
|
189
715
|
/**
|
|
190
716
|
* Wrapper class that extends the Socket.IO client library.
|
|
191
717
|
*/
|
|
192
718
|
class ServerSocket {
|
|
193
|
-
constructor(hostName, optionsOverrides) {
|
|
719
|
+
constructor(hostName, optionsOverrides, glitchFree) {
|
|
194
720
|
this._socket = io(hostName, {
|
|
195
721
|
path: DEFAULT_SOCKET_PATH,
|
|
196
722
|
randomizationFactor: 0.5,
|
|
@@ -216,14 +742,41 @@ class ServerSocket {
|
|
|
216
742
|
this._socket.io.opts.transports = ["websocket", "polling"];
|
|
217
743
|
}
|
|
218
744
|
});
|
|
745
|
+
|
|
746
|
+
if (glitchFree) this._reconnectManager = new ReconnectManager(this._socket);
|
|
747
|
+
|
|
219
748
|
this._socket.on("connect", () => {
|
|
220
749
|
const transport = this.getTransport();
|
|
221
750
|
if (transport === "websocket") {
|
|
222
751
|
this._wasConnectedUsingWebsocket = true;
|
|
752
|
+
|
|
753
|
+
// start noop keepalive loop to detect client side disconnects fast
|
|
754
|
+
if (!this.noopKeepaliveInterval)
|
|
755
|
+
this.noopKeepaliveInterval = setInterval(() => {
|
|
756
|
+
try {
|
|
757
|
+
// send a noop message if it thinks it is connected (might not be)
|
|
758
|
+
if (this._socket.connected) {
|
|
759
|
+
this._socket.io.engine.sendPacket("noop");
|
|
760
|
+
}
|
|
761
|
+
} catch (ex) {}
|
|
762
|
+
}, NOOP_KEEPALIVE_INTERVAL);
|
|
763
|
+
}
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
this._socket.on("disconnect", () => {
|
|
767
|
+
if (this.noopKeepaliveInterval) {
|
|
768
|
+
clearInterval(this.noopKeepaliveInterval);
|
|
769
|
+
this.noopKeepaliveInterval = null;
|
|
223
770
|
}
|
|
224
771
|
});
|
|
225
772
|
}
|
|
226
773
|
|
|
774
|
+
setRtcManager(rtcManager) {
|
|
775
|
+
if (this._reconnectManager) {
|
|
776
|
+
this._reconnectManager.rtcManager = rtcManager;
|
|
777
|
+
}
|
|
778
|
+
}
|
|
779
|
+
|
|
227
780
|
connect() {
|
|
228
781
|
if (this.isConnected() || this.isConnecting()) {
|
|
229
782
|
return;
|
|
@@ -282,6 +835,17 @@ class ServerSocket {
|
|
|
282
835
|
* @returns {function} Function to deregister the listener.
|
|
283
836
|
*/
|
|
284
837
|
on(eventName, handler) {
|
|
838
|
+
const relayableEvents = [
|
|
839
|
+
PROTOCOL_RESPONSES.ROOM_JOINED,
|
|
840
|
+
PROTOCOL_RESPONSES.CLIENT_LEFT,
|
|
841
|
+
PROTOCOL_RESPONSES.NEW_CLIENT,
|
|
842
|
+
];
|
|
843
|
+
|
|
844
|
+
// Intercept certain events if glitch-free is enabled.
|
|
845
|
+
if (this._reconnectManager && relayableEvents.includes(eventName)) {
|
|
846
|
+
return this._interceptEvent(eventName, handler);
|
|
847
|
+
}
|
|
848
|
+
|
|
285
849
|
this._socket.on(eventName, handler);
|
|
286
850
|
|
|
287
851
|
return () => {
|
|
@@ -308,12 +872,30 @@ class ServerSocket {
|
|
|
308
872
|
off(eventName, handler) {
|
|
309
873
|
this._socket.off(eventName, handler);
|
|
310
874
|
}
|
|
875
|
+
|
|
876
|
+
/**
|
|
877
|
+
* Intercept event and let ReconnectManager handle them.
|
|
878
|
+
*/
|
|
879
|
+
_interceptEvent(eventName, handler) {
|
|
880
|
+
if (this._reconnectManager) {
|
|
881
|
+
this._reconnectManager.on(eventName, handler);
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return () => {
|
|
885
|
+
if (this._reconnectManager) this._reconnectManager.removeListener(eventName, handler);
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
|
|
889
|
+
getGlitchFreeMetrics() {
|
|
890
|
+
return this._reconnectManager?.metrics;
|
|
891
|
+
}
|
|
311
892
|
}
|
|
312
893
|
|
|
313
894
|
function forwardSocketEvents(socket, dispatch) {
|
|
314
895
|
socket.on("room_joined", (payload) => dispatch(signalEvents.roomJoined(payload)));
|
|
315
896
|
socket.on("new_client", (payload) => dispatch(signalEvents.newClient(payload)));
|
|
316
897
|
socket.on("client_left", (payload) => dispatch(signalEvents.clientLeft(payload)));
|
|
898
|
+
socket.on("client_kicked", (payload) => dispatch(signalEvents.clientKicked(payload)));
|
|
317
899
|
socket.on("audio_enabled", (payload) => dispatch(signalEvents.audioEnabled(payload)));
|
|
318
900
|
socket.on("video_enabled", (payload) => dispatch(signalEvents.videoEnabled(payload)));
|
|
319
901
|
socket.on("client_metadata_received", (payload) => dispatch(signalEvents.clientMetadataReceived(payload)));
|
|
@@ -537,6 +1119,7 @@ const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10))
|
|
|
537
1119
|
function getMediaConstraints({
|
|
538
1120
|
disableAEC,
|
|
539
1121
|
disableAGC,
|
|
1122
|
+
fps24,
|
|
540
1123
|
hd,
|
|
541
1124
|
lax,
|
|
542
1125
|
lowDataMode,
|
|
@@ -547,7 +1130,7 @@ function getMediaConstraints({
|
|
|
547
1130
|
}) {
|
|
548
1131
|
let HIGH_HEIGHT = 480;
|
|
549
1132
|
let LOW_HEIGHT = 240;
|
|
550
|
-
let
|
|
1133
|
+
let NON_STANDARD_FPS = 0;
|
|
551
1134
|
|
|
552
1135
|
if (hd) {
|
|
553
1136
|
// respect user choice, but default to HD for pro, and SD for free
|
|
@@ -560,15 +1143,20 @@ function getMediaConstraints({
|
|
|
560
1143
|
} else {
|
|
561
1144
|
LOW_HEIGHT = 360;
|
|
562
1145
|
}
|
|
563
|
-
LOW_FPS = 30; // we still use 30fps because of assumptions about temporal layers
|
|
564
1146
|
}
|
|
565
1147
|
|
|
1148
|
+
// Set framerate to 24 to increase quality/bandwidth
|
|
1149
|
+
if (fps24) NON_STANDARD_FPS = 24;
|
|
1150
|
+
|
|
1151
|
+
// Set framerate for low data, but only for non-simulcast
|
|
1152
|
+
if (lowDataMode && !simulcast) NON_STANDARD_FPS = 15;
|
|
1153
|
+
|
|
566
1154
|
const constraints = {
|
|
567
1155
|
audio: { ...(preferredDeviceIds.audioId && { deviceId: preferredDeviceIds.audioId }) },
|
|
568
1156
|
video: {
|
|
569
1157
|
...(preferredDeviceIds.videoId ? { deviceId: preferredDeviceIds.videoId } : { facingMode: "user" }),
|
|
570
1158
|
height: lowDataMode ? LOW_HEIGHT : HIGH_HEIGHT,
|
|
571
|
-
...(
|
|
1159
|
+
...(NON_STANDARD_FPS && { frameRate: NON_STANDARD_FPS }),
|
|
572
1160
|
},
|
|
573
1161
|
};
|
|
574
1162
|
if (lax) {
|
|
@@ -701,6 +1289,8 @@ var assert_1 = assert;
|
|
|
701
1289
|
|
|
702
1290
|
var assert$1 = /*@__PURE__*/getDefaultExportFromCjs(assert_1);
|
|
703
1291
|
|
|
1292
|
+
const logger$8 = new Logger();
|
|
1293
|
+
|
|
704
1294
|
const isMobile = /mobi/i.test(navigator.userAgent);
|
|
705
1295
|
|
|
706
1296
|
class NoDevicesError extends Error {
|
|
@@ -720,7 +1310,7 @@ function getUserMedia(constraints) {
|
|
|
720
1310
|
|
|
721
1311
|
return navigator.mediaDevices.getUserMedia(constraints).catch((error) => {
|
|
722
1312
|
const message = `${error}, ${JSON.stringify(constraints, null, 2)}`;
|
|
723
|
-
|
|
1313
|
+
logger$8.error(`getUserMedia ${message}`);
|
|
724
1314
|
throw error;
|
|
725
1315
|
});
|
|
726
1316
|
}
|
|
@@ -906,7 +1496,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
906
1496
|
getConstraints({ ...constraintOpt, options: { ...constraintOpt.options, lax: true } })
|
|
907
1497
|
);
|
|
908
1498
|
} catch (e2) {
|
|
909
|
-
|
|
1499
|
+
logger$8.warn(`Tried getting stream again with laxer constraints, but failed: ${"" + e2}`);
|
|
910
1500
|
}
|
|
911
1501
|
// Message often hints at which was the problem, let's use that
|
|
912
1502
|
const errMsg = ("" + e).toLowerCase();
|
|
@@ -915,7 +1505,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
915
1505
|
try {
|
|
916
1506
|
stream = await getUserMedia(getConstraints({ ...constraintOpt, [problemWith]: null }));
|
|
917
1507
|
} catch (e2) {
|
|
918
|
-
|
|
1508
|
+
logger$8.warn(`Re-tried ${problemWith} with no constraints, but failed: ${"" + e2}`);
|
|
919
1509
|
}
|
|
920
1510
|
}
|
|
921
1511
|
if (!stream) {
|
|
@@ -926,7 +1516,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
926
1516
|
try {
|
|
927
1517
|
stream = await getUserMedia(getConstraints({ ...constraintOpt, [kind]: false }));
|
|
928
1518
|
} catch (e2) {
|
|
929
|
-
|
|
1519
|
+
logger$8.warn(`Re-tried without ${kind}, but failed: ${"" + e2}`);
|
|
930
1520
|
}
|
|
931
1521
|
if (stream) break;
|
|
932
1522
|
}
|
|
@@ -1917,6 +2507,9 @@ const roomConnectionSlice = createSlice({
|
|
|
1917
2507
|
}
|
|
1918
2508
|
return Object.assign(Object.assign({}, state), { session: null });
|
|
1919
2509
|
});
|
|
2510
|
+
builder.addCase(signalEvents.clientKicked, (state) => {
|
|
2511
|
+
return Object.assign(Object.assign({}, state), { status: "kicked" });
|
|
2512
|
+
});
|
|
1920
2513
|
builder.addCase(socketReconnecting, (state) => {
|
|
1921
2514
|
return Object.assign(Object.assign({}, state), { status: "reconnect" });
|
|
1922
2515
|
});
|
|
@@ -1978,163 +2571,65 @@ const doConnectRoom = createAppThunk(() => (dispatch, getState) => {
|
|
|
1978
2571
|
organizationId,
|
|
1979
2572
|
roomKey,
|
|
1980
2573
|
roomName,
|
|
1981
|
-
selfId,
|
|
1982
|
-
userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
|
|
1983
|
-
externalId,
|
|
1984
|
-
});
|
|
1985
|
-
dispatch(connectionStatusChanged("connecting"));
|
|
1986
|
-
});
|
|
1987
|
-
const selectRoomConnectionRaw = (state) => state.roomConnection;
|
|
1988
|
-
const selectRoomConnectionSession = (state) => state.roomConnection.session;
|
|
1989
|
-
const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
|
|
1990
|
-
const selectRoomConnectionStatus = (state) => state.roomConnection.status;
|
|
1991
|
-
const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
|
|
1992
|
-
if (localMediaStatus === "started" &&
|
|
1993
|
-
signalConnectionDeviceIdentified &&
|
|
1994
|
-
!!hasOrganizationIdFetched &&
|
|
1995
|
-
["initializing", "reconnect"].includes(roomConnectionStatus)) {
|
|
1996
|
-
return true;
|
|
1997
|
-
}
|
|
1998
|
-
return false;
|
|
1999
|
-
});
|
|
2000
|
-
createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
|
|
2001
|
-
if (shouldConnectRoom) {
|
|
2002
|
-
dispatch(doConnectRoom());
|
|
2003
|
-
}
|
|
2004
|
-
});
|
|
2005
|
-
startAppListening({
|
|
2006
|
-
actionCreator: signalEvents.knockHandled,
|
|
2007
|
-
effect: ({ payload }, { dispatch, getState }) => {
|
|
2008
|
-
const { clientId, resolution } = payload;
|
|
2009
|
-
const state = getState();
|
|
2010
|
-
const selfId = selectSelfId(state);
|
|
2011
|
-
if (clientId !== selfId) {
|
|
2012
|
-
return;
|
|
2013
|
-
}
|
|
2014
|
-
if (resolution === "accepted") {
|
|
2015
|
-
dispatch(setRoomKey(payload.metadata.roomKey));
|
|
2016
|
-
dispatch(doConnectRoom());
|
|
2017
|
-
}
|
|
2018
|
-
else if (resolution === "rejected") {
|
|
2019
|
-
dispatch(connectionStatusChanged("knock_rejected"));
|
|
2020
|
-
}
|
|
2021
|
-
},
|
|
2022
|
-
});
|
|
2023
|
-
|
|
2024
|
-
const EVENTS = {
|
|
2025
|
-
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
2026
|
-
STREAM_ADDED: "stream_added",
|
|
2027
|
-
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
2028
|
-
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
2029
|
-
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
2030
|
-
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
2031
|
-
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
2032
|
-
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
2033
|
-
};
|
|
2034
|
-
|
|
2035
|
-
const TYPES = {
|
|
2036
|
-
CONNECTING: "connecting",
|
|
2037
|
-
CONNECTION_FAILED: "connection_failed",
|
|
2038
|
-
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2039
|
-
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2040
|
-
};
|
|
2041
|
-
|
|
2042
|
-
// Protocol enum used for the CLIENT (Make sure to keep it in sync with its server counterpart)
|
|
2043
|
-
|
|
2044
|
-
// Requests: messages from the client to the server
|
|
2045
|
-
const PROTOCOL_REQUESTS = {
|
|
2046
|
-
BLOCK_CLIENT: "block_client",
|
|
2047
|
-
CLAIM_ROOM: "claim_room",
|
|
2048
|
-
CLEAR_CHAT_HISTORY: "clear_chat_history",
|
|
2049
|
-
ENABLE_AUDIO: "enable_audio",
|
|
2050
|
-
ENABLE_VIDEO: "enable_video",
|
|
2051
|
-
END_STREAM: "end_stream",
|
|
2052
|
-
FETCH_MEDIASERVER_CONFIG: "fetch_mediaserver_config",
|
|
2053
|
-
HANDLE_KNOCK: "handle_knock",
|
|
2054
|
-
IDENTIFY_DEVICE: "identify_device",
|
|
2055
|
-
INVITE_CLIENT_AS_MEMBER: "invite_client_as_member",
|
|
2056
|
-
JOIN_ROOM: "join_room",
|
|
2057
|
-
KICK_CLIENT: "kick_client",
|
|
2058
|
-
KNOCK_ROOM: "knock_room",
|
|
2059
|
-
LEAVE_ROOM: "leave_room",
|
|
2060
|
-
SEND_CLIENT_METADATA: "send_client_metadata",
|
|
2061
|
-
SET_LOCK: "set_lock",
|
|
2062
|
-
SHARE_MEDIA: "share_media",
|
|
2063
|
-
START_NEW_STREAM: "start_new_stream",
|
|
2064
|
-
START_SCREENSHARE: "start_screenshare",
|
|
2065
|
-
STOP_SCREENSHARE: "stop_screenshare",
|
|
2066
|
-
START_URL_EMBED: "start_url_embed",
|
|
2067
|
-
STOP_URL_EMBED: "stop_url_embed",
|
|
2068
|
-
START_RECORDING: "start_recording",
|
|
2069
|
-
STOP_RECORDING: "stop_recording",
|
|
2070
|
-
SFU_TOKEN: "sfu_token",
|
|
2071
|
-
};
|
|
2072
|
-
|
|
2073
|
-
// Responses: messages from the server to the client, in response to requests
|
|
2074
|
-
const PROTOCOL_RESPONSES = {
|
|
2075
|
-
AUDIO_ENABLED: "audio_enabled",
|
|
2076
|
-
BACKGROUND_IMAGE_CHANGED: "background_image_changed",
|
|
2077
|
-
BLOCK_ADDED: "block_added",
|
|
2078
|
-
BLOCK_REMOVED: "block_removed",
|
|
2079
|
-
CHAT_HISTORY_CLEARED: "chat_history_cleared",
|
|
2080
|
-
CLIENT_BLOCKED: "client_blocked",
|
|
2081
|
-
CLIENT_INVITED_AS_MEMBER: "client_invited_as_member",
|
|
2082
|
-
CLIENT_KICKED: "client_kicked",
|
|
2083
|
-
CLIENT_LEFT: "client_left",
|
|
2084
|
-
CLIENT_METADATA_RECEIVED: "client_metadata_received",
|
|
2085
|
-
CLIENT_READY: "client_ready",
|
|
2086
|
-
CLIENT_ROLE_CHANGED: "client_role_changed",
|
|
2087
|
-
CLIENT_USER_ID_CHANGED: "client_user_id_changed",
|
|
2088
|
-
CONTACTS_UPDATED: "contacts_updated",
|
|
2089
|
-
DEVICE_IDENTIFIED: "device_identified",
|
|
2090
|
-
ROOM_ROLES_UPDATED: "room_roles_updated",
|
|
2091
|
-
KNOCK_HANDLED: "knock_handled",
|
|
2092
|
-
KNOCK_PAGE_BACKGROUND_CHANGED: "knock_page_background_changed",
|
|
2093
|
-
KNOCKER_LEFT: "knocker_left",
|
|
2094
|
-
MEDIASERVER_CONFIG: "mediaserver_config",
|
|
2095
|
-
MEDIA_SHARED: "media_shared",
|
|
2096
|
-
MEMBER_INVITE: "member_invite",
|
|
2097
|
-
NEW_CLIENT: "new_client",
|
|
2098
|
-
NEW_STREAM_STARTED: "new_stream_started",
|
|
2099
|
-
SCREENSHARE_STARTED: "screenshare_started",
|
|
2100
|
-
SCREENSHARE_STOPPED: "screenshare_stopped",
|
|
2101
|
-
OWNER_NOTIFIED: "owner_notified",
|
|
2102
|
-
OWNERS_CHANGED: "owners_changed",
|
|
2103
|
-
PLAY_CLIENT_STICKER: "play_client_sticker",
|
|
2104
|
-
ROOM_INTEGRATION_ENABLED: "room_integration_enabled",
|
|
2105
|
-
ROOM_INTEGRATION_DISABLED: "room_integration_disabled",
|
|
2106
|
-
ROOM_JOINED: "room_joined",
|
|
2107
|
-
ROOM_KNOCKED: "room_knocked",
|
|
2108
|
-
ROOM_LEFT: "room_left",
|
|
2109
|
-
ROOM_LOCKED: "room_locked",
|
|
2110
|
-
ROOM_PERMISSIONS_CHANGED: "room_permissions_changed",
|
|
2111
|
-
ROOM_LOGO_CHANGED: "room_logo_changed",
|
|
2112
|
-
ROOM_TYPE_CHANGED: "room_type_changed",
|
|
2113
|
-
ROOM_MODE_CHANGED: "room_mode_changed",
|
|
2114
|
-
SOCKET_USER_ID_CHANGED: "socket_user_id_changed",
|
|
2115
|
-
STICKERS_UNLOCKED: "stickers_unlocked",
|
|
2116
|
-
STREAM_ENDED: "stream_ended",
|
|
2117
|
-
URL_EMBED_STARTED: "url_embed_started",
|
|
2118
|
-
URL_EMBED_STOPPED: "url_embed_stopped",
|
|
2119
|
-
RECORDING_STARTED: "recording_started",
|
|
2120
|
-
RECORDING_STOPPED: "recording_stopped",
|
|
2121
|
-
USER_NOTIFIED: "user_notified",
|
|
2122
|
-
VIDEO_ENABLED: "video_enabled",
|
|
2123
|
-
CLIENT_UNABLE_TO_JOIN: "client_unable_to_join",
|
|
2574
|
+
selfId,
|
|
2575
|
+
userAgent: `browser-sdk:${sdkVersion || "unknown"}`,
|
|
2576
|
+
externalId,
|
|
2577
|
+
});
|
|
2578
|
+
dispatch(connectionStatusChanged("connecting"));
|
|
2579
|
+
});
|
|
2580
|
+
const selectRoomConnectionRaw = (state) => state.roomConnection;
|
|
2581
|
+
const selectRoomConnectionSession = (state) => state.roomConnection.session;
|
|
2582
|
+
const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
|
|
2583
|
+
const selectRoomConnectionStatus = (state) => state.roomConnection.status;
|
|
2584
|
+
const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
|
|
2585
|
+
if (localMediaStatus === "started" &&
|
|
2586
|
+
signalConnectionDeviceIdentified &&
|
|
2587
|
+
!!hasOrganizationIdFetched &&
|
|
2588
|
+
["initializing", "reconnect"].includes(roomConnectionStatus)) {
|
|
2589
|
+
return true;
|
|
2590
|
+
}
|
|
2591
|
+
return false;
|
|
2592
|
+
});
|
|
2593
|
+
createReactor([selectShouldConnectRoom], ({ dispatch }, shouldConnectRoom) => {
|
|
2594
|
+
if (shouldConnectRoom) {
|
|
2595
|
+
dispatch(doConnectRoom());
|
|
2596
|
+
}
|
|
2597
|
+
});
|
|
2598
|
+
startAppListening({
|
|
2599
|
+
actionCreator: signalEvents.knockHandled,
|
|
2600
|
+
effect: ({ payload }, { dispatch, getState }) => {
|
|
2601
|
+
const { clientId, resolution } = payload;
|
|
2602
|
+
const state = getState();
|
|
2603
|
+
const selfId = selectSelfId(state);
|
|
2604
|
+
if (clientId !== selfId) {
|
|
2605
|
+
return;
|
|
2606
|
+
}
|
|
2607
|
+
if (resolution === "accepted") {
|
|
2608
|
+
dispatch(setRoomKey(payload.metadata.roomKey));
|
|
2609
|
+
dispatch(doConnectRoom());
|
|
2610
|
+
}
|
|
2611
|
+
else if (resolution === "rejected") {
|
|
2612
|
+
dispatch(connectionStatusChanged("knock_rejected"));
|
|
2613
|
+
}
|
|
2614
|
+
},
|
|
2615
|
+
});
|
|
2616
|
+
|
|
2617
|
+
const EVENTS = {
|
|
2618
|
+
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
2619
|
+
STREAM_ADDED: "stream_added",
|
|
2620
|
+
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
2621
|
+
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
2622
|
+
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
2623
|
+
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
2624
|
+
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
2625
|
+
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
2124
2626
|
};
|
|
2125
2627
|
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
ICE_CANDIDATE: "ice_candidate",
|
|
2132
|
-
ICE_END_OF_CANDIDATES: "ice_endofcandidates",
|
|
2133
|
-
READY_TO_RECEIVE_OFFER: "ready_to_receive_offer",
|
|
2134
|
-
REMOTE_CLIENT_MEDIA_REQUEST: "remote_client_media_request",
|
|
2135
|
-
SDP_ANSWER: "sdp_answer",
|
|
2136
|
-
SDP_OFFER: "sdp_offer",
|
|
2137
|
-
VIDEO_STICKER: "video_sticker",
|
|
2628
|
+
const TYPES = {
|
|
2629
|
+
CONNECTING: "connecting",
|
|
2630
|
+
CONNECTION_FAILED: "connection_failed",
|
|
2631
|
+
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2632
|
+
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2138
2633
|
};
|
|
2139
2634
|
|
|
2140
2635
|
const CAMERA_STREAM_ID$2 = "0";
|
|
@@ -2250,13 +2745,22 @@ function detectMicrophoneNotWorking(pc) {
|
|
|
2250
2745
|
var rtcManagerEvents = {
|
|
2251
2746
|
CAMERA_NOT_WORKING: "camera_not_working",
|
|
2252
2747
|
CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
|
|
2748
|
+
ICE_IPV6_SEEN: "ice_ipv6_seen",
|
|
2749
|
+
ICE_MDNS_SEEN: "ice_mdns_seen",
|
|
2750
|
+
ICE_NO_PUBLIC_IP_GATHERED: "ice_no_public_ip_gathered",
|
|
2751
|
+
ICE_NO_PUBLIC_IP_GATHERED_3SEC: "ice_no_public_ip_gathered_3sec",
|
|
2752
|
+
ICE_RESTART: "ice_restart",
|
|
2253
2753
|
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
2254
2754
|
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
2755
|
+
NEW_PC: "new_pc",
|
|
2756
|
+
SFU_CONNECTION_OPEN: "sfu_connection_open",
|
|
2255
2757
|
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
2256
2758
|
COLOCATION_SPEAKER: "colocation_speaker",
|
|
2257
2759
|
DOMINANT_SPEAKER: "dominant_speaker",
|
|
2258
2760
|
};
|
|
2259
2761
|
|
|
2762
|
+
const logger$7 = new Logger();
|
|
2763
|
+
|
|
2260
2764
|
const browserName$3 = adapter.browserDetails.browser;
|
|
2261
2765
|
const browserVersion$1 = adapter.browserDetails.version;
|
|
2262
2766
|
|
|
@@ -2298,8 +2802,7 @@ function setCodecPreferenceSDP(sdp, vp9On, redOn) {
|
|
|
2298
2802
|
const newSdp = sdpTransform.write(sdpObject);
|
|
2299
2803
|
return newSdp;
|
|
2300
2804
|
} catch (error) {
|
|
2301
|
-
|
|
2302
|
-
console.log("setCodecPreferenceSDP error:", error);
|
|
2805
|
+
logger$7.error("setCodecPreferenceSDP error:", error);
|
|
2303
2806
|
}
|
|
2304
2807
|
}
|
|
2305
2808
|
|
|
@@ -2446,10 +2949,19 @@ const MAXIMUM_TURN_BANDWIDTH_UNLIMITED = -1; // kbps;
|
|
|
2446
2949
|
|
|
2447
2950
|
const MEDIA_JITTER_BUFFER_TARGET = 400; // milliseconds;
|
|
2448
2951
|
|
|
2952
|
+
const logger$6 = new Logger();
|
|
2953
|
+
|
|
2449
2954
|
class Session {
|
|
2450
|
-
constructor({ peerConnectionId, bandwidth, maximumTurnBandwidth, deprioritizeH264Encoding
|
|
2955
|
+
constructor({ peerConnectionId, bandwidth, maximumTurnBandwidth, deprioritizeH264Encoding }) {
|
|
2451
2956
|
this.peerConnectionId = peerConnectionId;
|
|
2452
2957
|
this.relayCandidateSeen = false;
|
|
2958
|
+
this.serverReflexiveCandidateSeen = false;
|
|
2959
|
+
this.publicHostCandidateSeen = false;
|
|
2960
|
+
this.ipv6HostCandidateSeen = false;
|
|
2961
|
+
this.ipv6HostCandidateTeredoSeen = false;
|
|
2962
|
+
this.ipv6HostCandidate6to4Seen = false;
|
|
2963
|
+
this.mdnsHostCandidateSeen = false;
|
|
2964
|
+
|
|
2453
2965
|
this.pc = null;
|
|
2454
2966
|
this.wasEverConnected = false;
|
|
2455
2967
|
this.connectionStatus = null;
|
|
@@ -2469,7 +2981,6 @@ class Session {
|
|
|
2469
2981
|
});
|
|
2470
2982
|
this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
|
|
2471
2983
|
this._deprioritizeH264Encoding = deprioritizeH264Encoding;
|
|
2472
|
-
this._logger = logger;
|
|
2473
2984
|
}
|
|
2474
2985
|
|
|
2475
2986
|
setAndGetPeerConnection({ clientId, constraints, peerConnectionConfig, shouldAddLocalVideo }) {
|
|
@@ -2613,7 +3124,7 @@ class Session {
|
|
|
2613
3124
|
return setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
|
|
2614
3125
|
},
|
|
2615
3126
|
(e) => {
|
|
2616
|
-
|
|
3127
|
+
logger$6.warn("Could not set remote description from remote answer: ", e);
|
|
2617
3128
|
}
|
|
2618
3129
|
);
|
|
2619
3130
|
}
|
|
@@ -2634,7 +3145,7 @@ class Session {
|
|
|
2634
3145
|
return;
|
|
2635
3146
|
}
|
|
2636
3147
|
this.pc.addIceCandidate(candidate).catch((e) => {
|
|
2637
|
-
|
|
3148
|
+
logger$6.warn("Failed to add ICE candidate ('%s'): %s", candidate ? candidate.candidate : null, e);
|
|
2638
3149
|
});
|
|
2639
3150
|
});
|
|
2640
3151
|
}
|
|
@@ -2656,7 +3167,7 @@ class Session {
|
|
|
2656
3167
|
// do not handle state change events when we close the connection explicitly
|
|
2657
3168
|
pc.close();
|
|
2658
3169
|
} catch (e) {
|
|
2659
|
-
|
|
3170
|
+
logger$6.warn("failures during close of session", e);
|
|
2660
3171
|
// we're not interested in errors from RTCPeerConnection.close()
|
|
2661
3172
|
}
|
|
2662
3173
|
}
|
|
@@ -2672,7 +3183,7 @@ class Session {
|
|
|
2672
3183
|
const senders = pc.getSenders();
|
|
2673
3184
|
function dbg(msg) {
|
|
2674
3185
|
const tr = (t) => t && `id:${t.id},kind:${t.kind},state:${t.readyState}`;
|
|
2675
|
-
|
|
3186
|
+
logger$6.warn(
|
|
2676
3187
|
`${msg}. newTrack:${tr(newTrack)}, oldTrack:${tr(oldTrack)}, sender tracks: ${JSON.stringify(
|
|
2677
3188
|
senders.map((s) => `s ${tr(s.track)}`)
|
|
2678
3189
|
)}, sender first codecs: ${JSON.stringify(senders.map((s) => (s.getParameters().codecs || [])[0]))}`
|
|
@@ -2826,6 +3337,21 @@ class Session {
|
|
|
2826
3337
|
|
|
2827
3338
|
setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
|
|
2828
3339
|
}
|
|
3340
|
+
|
|
3341
|
+
setAudioOnly(enable, excludedTrackIds = []) {
|
|
3342
|
+
this.pc
|
|
3343
|
+
.getTransceivers()
|
|
3344
|
+
.filter(
|
|
3345
|
+
(videoTransceiver) =>
|
|
3346
|
+
videoTransceiver?.direction !== "recvonly" &&
|
|
3347
|
+
videoTransceiver?.receiver?.track?.kind === "video" &&
|
|
3348
|
+
!excludedTrackIds.includes(videoTransceiver?.receiver?.track?.id) &&
|
|
3349
|
+
!excludedTrackIds.includes(videoTransceiver?.sender?.track?.id)
|
|
3350
|
+
)
|
|
3351
|
+
.forEach((videoTransceiver) => {
|
|
3352
|
+
videoTransceiver.direction = enable ? "sendonly" : "sendrecv";
|
|
3353
|
+
});
|
|
3354
|
+
}
|
|
2829
3355
|
}
|
|
2830
3356
|
|
|
2831
3357
|
// transforms a maplike to an object. Mostly for getStats +
|
|
@@ -2924,6 +3450,8 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
2924
3450
|
var peerconnectioncounter = 0;
|
|
2925
3451
|
var isFirefox = !!window.mozRTCPeerConnection;
|
|
2926
3452
|
var isEdge = !!window.RTCIceGatherer;
|
|
3453
|
+
var prevById = {};
|
|
3454
|
+
|
|
2927
3455
|
prefixesToWrap.forEach(function(prefix) {
|
|
2928
3456
|
if (!window[prefix + 'RTCPeerConnection']) {
|
|
2929
3457
|
return;
|
|
@@ -2994,13 +3522,12 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
2994
3522
|
trace('ondatachannel', id, [event.channel.id, event.channel.label]);
|
|
2995
3523
|
});
|
|
2996
3524
|
|
|
2997
|
-
var prev = {};
|
|
2998
3525
|
var getStats = function() {
|
|
2999
3526
|
pc.getStats(null).then(function(res) {
|
|
3000
3527
|
var now = map2obj(res);
|
|
3001
3528
|
var base = JSON.parse(JSON.stringify(now)); // our new prev
|
|
3002
|
-
trace('getstats', id, deltaCompression(
|
|
3003
|
-
|
|
3529
|
+
trace('getstats', id, deltaCompression(prevById[id] || {}, now));
|
|
3530
|
+
prevById[id] = base;
|
|
3004
3531
|
});
|
|
3005
3532
|
};
|
|
3006
3533
|
// TODO: do we want one big interval and all peerconnections
|
|
@@ -3220,6 +3747,13 @@ var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
|
3220
3747
|
}
|
|
3221
3748
|
});
|
|
3222
3749
|
*/
|
|
3750
|
+
|
|
3751
|
+
return {
|
|
3752
|
+
resetDelta() {
|
|
3753
|
+
prevById = {};
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
|
|
3223
3757
|
};
|
|
3224
3758
|
|
|
3225
3759
|
var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
|
|
@@ -3228,11 +3762,18 @@ var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
|
|
|
3228
3762
|
|
|
3229
3763
|
const RTCSTATS_PROTOCOL_VERSION = "1.0";
|
|
3230
3764
|
|
|
3765
|
+
// when not connected we need to buffer at least a few getstats reports
|
|
3766
|
+
// as they are delta compressed and we need the initial properties
|
|
3767
|
+
const GETSTATS_BUFFER_SIZE = 20;
|
|
3768
|
+
|
|
3231
3769
|
const clientInfo = {
|
|
3232
|
-
id: v4(), // shared id across rtcstats reconnects
|
|
3770
|
+
id: v4$1(), // shared id across rtcstats reconnects
|
|
3233
3771
|
connectionNumber: 0,
|
|
3234
3772
|
};
|
|
3235
3773
|
|
|
3774
|
+
const noop = () => {};
|
|
3775
|
+
let resetDelta = noop;
|
|
3776
|
+
|
|
3236
3777
|
// Inlined version of rtcstats/trace-ws with improved disconnect handling.
|
|
3237
3778
|
function rtcStatsConnection(wsURL, logger = console) {
|
|
3238
3779
|
const buffer = [];
|
|
@@ -3245,6 +3786,7 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3245
3786
|
let connectionShouldBeOpen;
|
|
3246
3787
|
let connectionAttempt = 0;
|
|
3247
3788
|
let hasPassedOnRoomSessionId = false;
|
|
3789
|
+
let getStatsBufferUsed = 0;
|
|
3248
3790
|
|
|
3249
3791
|
const connection = {
|
|
3250
3792
|
connected: false,
|
|
@@ -3282,8 +3824,15 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3282
3824
|
if (ws.readyState === WebSocket.OPEN) {
|
|
3283
3825
|
connectionAttempt = 0;
|
|
3284
3826
|
ws.send(JSON.stringify(args));
|
|
3285
|
-
} else if (args[0]
|
|
3286
|
-
// buffer
|
|
3827
|
+
} else if (args[0] === "getstats") {
|
|
3828
|
+
// only buffer getStats for a while
|
|
3829
|
+
// we don't want this to pile up, but we need at least the initial reports
|
|
3830
|
+
if (getStatsBufferUsed < GETSTATS_BUFFER_SIZE) {
|
|
3831
|
+
getStatsBufferUsed++;
|
|
3832
|
+
buffer.push(args);
|
|
3833
|
+
}
|
|
3834
|
+
} else if (args[0] === "customEvent" && args[2].type === "insightsStats") ; else {
|
|
3835
|
+
// buffer everything else
|
|
3287
3836
|
buffer.push(args);
|
|
3288
3837
|
}
|
|
3289
3838
|
|
|
@@ -3318,6 +3867,7 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3318
3867
|
ws.onclose = (e) => {
|
|
3319
3868
|
connection.connected = false;
|
|
3320
3869
|
logger.info(`[RTCSTATS] Closed ${e.code}`);
|
|
3870
|
+
resetDelta();
|
|
3321
3871
|
};
|
|
3322
3872
|
ws.onopen = () => {
|
|
3323
3873
|
// send client info after each connection, so analysis tools can handle reconnections
|
|
@@ -3342,10 +3892,11 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3342
3892
|
ws.send(JSON.stringify(userRole));
|
|
3343
3893
|
}
|
|
3344
3894
|
|
|
3345
|
-
// send buffered events
|
|
3895
|
+
// send buffered events
|
|
3346
3896
|
while (buffer.length) {
|
|
3347
3897
|
ws.send(JSON.stringify(buffer.shift()));
|
|
3348
3898
|
}
|
|
3899
|
+
getStatsBufferUsed = 0;
|
|
3349
3900
|
};
|
|
3350
3901
|
},
|
|
3351
3902
|
};
|
|
@@ -3354,11 +3905,13 @@ function rtcStatsConnection(wsURL, logger = console) {
|
|
|
3354
3905
|
}
|
|
3355
3906
|
|
|
3356
3907
|
const server = rtcStatsConnection("wss://rtcstats.srv.whereby.com" );
|
|
3357
|
-
rtcstats$1(
|
|
3908
|
+
const stats = rtcstats$1(
|
|
3358
3909
|
server.trace,
|
|
3359
3910
|
10000, // query once every 10 seconds.
|
|
3360
3911
|
[""] // only shim unprefixed RTCPeerConnecion.
|
|
3361
3912
|
);
|
|
3913
|
+
// on node clients this function can be undefined
|
|
3914
|
+
resetDelta = stats?.resetDelta || noop;
|
|
3362
3915
|
|
|
3363
3916
|
const rtcStats = {
|
|
3364
3917
|
sendEvent: (type, value) => {
|
|
@@ -3376,6 +3929,8 @@ const rtcStats = {
|
|
|
3376
3929
|
server,
|
|
3377
3930
|
};
|
|
3378
3931
|
|
|
3932
|
+
const logger$5 = new Logger();
|
|
3933
|
+
|
|
3379
3934
|
const CAMERA_STREAM_ID$1 = RtcStream.getCameraId();
|
|
3380
3935
|
const browserName$2 = adapter.browserDetails.browser;
|
|
3381
3936
|
const browserVersion = adapter.browserDetails.version;
|
|
@@ -3392,7 +3947,7 @@ if (browserName$2 === "chrome") {
|
|
|
3392
3947
|
}
|
|
3393
3948
|
|
|
3394
3949
|
class BaseRtcManager {
|
|
3395
|
-
constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features
|
|
3950
|
+
constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features }) {
|
|
3396
3951
|
assert$1.ok(selfId, "selfId is required");
|
|
3397
3952
|
assert$1.ok(room, "room is required");
|
|
3398
3953
|
assert$1.ok(emitter && emitter.emit, "emitter is required");
|
|
@@ -3407,13 +3962,14 @@ class BaseRtcManager {
|
|
|
3407
3962
|
this.peerConnections = {};
|
|
3408
3963
|
this.localStreams = {};
|
|
3409
3964
|
this.enabledLocalStreamIds = [];
|
|
3965
|
+
this._screenshareVideoTrackIds = [];
|
|
3410
3966
|
this._socketListenerDeregisterFunctions = [];
|
|
3411
3967
|
this._localStreamDeregisterFunction = null;
|
|
3412
3968
|
this._emitter = emitter;
|
|
3413
3969
|
this._serverSocket = serverSocket;
|
|
3414
3970
|
this._webrtcProvider = webrtcProvider;
|
|
3415
3971
|
this._features = features || {};
|
|
3416
|
-
this.
|
|
3972
|
+
this._isAudioOnlyMode = false;
|
|
3417
3973
|
|
|
3418
3974
|
this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
|
|
3419
3975
|
this._pendingActionsForConnectedPeerConnections = [];
|
|
@@ -3432,6 +3988,8 @@ class BaseRtcManager {
|
|
|
3432
3988
|
iceServers: iceServers.iceServers || [],
|
|
3433
3989
|
mediaserverConfigTtlSeconds,
|
|
3434
3990
|
});
|
|
3991
|
+
|
|
3992
|
+
this.totalSessionsCreated = 0;
|
|
3435
3993
|
}
|
|
3436
3994
|
|
|
3437
3995
|
numberOfPeerconnections() {
|
|
@@ -3482,7 +4040,7 @@ class BaseRtcManager {
|
|
|
3482
4040
|
receiver.playoutDelayHint = MEDIA_JITTER_BUFFER_TARGET / 1000; // seconds
|
|
3483
4041
|
});
|
|
3484
4042
|
} catch (error) {
|
|
3485
|
-
|
|
4043
|
+
logger$5.error("Error during setting jitter buffer target:", error);
|
|
3486
4044
|
}
|
|
3487
4045
|
}
|
|
3488
4046
|
|
|
@@ -3537,6 +4095,8 @@ class BaseRtcManager {
|
|
|
3537
4095
|
: MAXIMUM_TURN_BANDWIDTH,
|
|
3538
4096
|
deprioritizeH264Encoding,
|
|
3539
4097
|
});
|
|
4098
|
+
|
|
4099
|
+
this.totalSessionsCreated++;
|
|
3540
4100
|
}
|
|
3541
4101
|
return session;
|
|
3542
4102
|
}
|
|
@@ -3630,6 +4190,8 @@ class BaseRtcManager {
|
|
|
3630
4190
|
clientId,
|
|
3631
4191
|
});
|
|
3632
4192
|
|
|
4193
|
+
setTimeout(() => this._emit(rtcManagerEvents.NEW_PC), 0);
|
|
4194
|
+
|
|
3633
4195
|
pc.ontrack = (event) => {
|
|
3634
4196
|
const stream = event.streams[0];
|
|
3635
4197
|
if (stream.id === "default" && stream.getAudioTracks().length === 0) {
|
|
@@ -3691,6 +4253,11 @@ class BaseRtcManager {
|
|
|
3691
4253
|
this.maybeRestrictRelayBandwidth(session);
|
|
3692
4254
|
}
|
|
3693
4255
|
}
|
|
4256
|
+
|
|
4257
|
+
if (this._isAudioOnlyMode) {
|
|
4258
|
+
session.setAudioOnly(true, this._screenshareVideoTrackIds);
|
|
4259
|
+
}
|
|
4260
|
+
|
|
3694
4261
|
session.registerConnected();
|
|
3695
4262
|
break;
|
|
3696
4263
|
case "disconnected":
|
|
@@ -3794,7 +4361,7 @@ class BaseRtcManager {
|
|
|
3794
4361
|
_cleanup(peerConnectionId) {
|
|
3795
4362
|
const session = this._getSession(peerConnectionId);
|
|
3796
4363
|
if (!session) {
|
|
3797
|
-
|
|
4364
|
+
logger$5.warn("No RTCPeerConnection in RTCManager.disconnect()", peerConnectionId);
|
|
3798
4365
|
return;
|
|
3799
4366
|
}
|
|
3800
4367
|
session.close();
|
|
@@ -3824,10 +4391,10 @@ class BaseRtcManager {
|
|
|
3824
4391
|
const promises = [];
|
|
3825
4392
|
this._forEachPeerConnection((session) => {
|
|
3826
4393
|
if (!session.hasConnectedPeerConnection()) {
|
|
3827
|
-
|
|
4394
|
+
logger$5.info("Session doesn't have a connected PeerConnection, adding pending action!");
|
|
3828
4395
|
const pendingActions = this._pendingActionsForConnectedPeerConnections;
|
|
3829
4396
|
if (!pendingActions) {
|
|
3830
|
-
|
|
4397
|
+
logger$5.warn(
|
|
3831
4398
|
`No pending action is created to repalce track, because the pending actions array is null`
|
|
3832
4399
|
);
|
|
3833
4400
|
return;
|
|
@@ -3836,7 +4403,7 @@ class BaseRtcManager {
|
|
|
3836
4403
|
const action = () => {
|
|
3837
4404
|
const replacedTrackPromise = session.replaceTrack(oldTrack, newTrack);
|
|
3838
4405
|
if (!replacedTrackPromise) {
|
|
3839
|
-
|
|
4406
|
+
logger$5.error("replaceTrack returned false!");
|
|
3840
4407
|
reject(`ReplaceTrack returned false`);
|
|
3841
4408
|
return;
|
|
3842
4409
|
}
|
|
@@ -3849,7 +4416,7 @@ class BaseRtcManager {
|
|
|
3849
4416
|
}
|
|
3850
4417
|
const replacedTrackResult = session.replaceTrack(oldTrack, newTrack);
|
|
3851
4418
|
if (!replacedTrackResult) {
|
|
3852
|
-
|
|
4419
|
+
logger$5.error("replaceTrack returned false!");
|
|
3853
4420
|
return;
|
|
3854
4421
|
}
|
|
3855
4422
|
promises.push(replacedTrackResult);
|
|
@@ -3919,6 +4486,7 @@ class BaseRtcManager {
|
|
|
3919
4486
|
}
|
|
3920
4487
|
|
|
3921
4488
|
// at this point it is clearly a screensharing stream.
|
|
4489
|
+
this._screenshareVideoTrackIds.push(stream.getVideoTracks()[0].id);
|
|
3922
4490
|
this._shareScreen(streamId, stream);
|
|
3923
4491
|
return;
|
|
3924
4492
|
}
|
|
@@ -4017,7 +4585,7 @@ class BaseRtcManager {
|
|
|
4017
4585
|
|
|
4018
4586
|
this._serverSocket.on(PROTOCOL_RESPONSES.MEDIASERVER_CONFIG, (data) => {
|
|
4019
4587
|
if (data.error) {
|
|
4020
|
-
|
|
4588
|
+
logger$5.warn("FETCH_MEDIASERVER_CONFIG failed:", data.error);
|
|
4021
4589
|
return;
|
|
4022
4590
|
}
|
|
4023
4591
|
this._updateAndScheduleMediaServersRefresh(data);
|
|
@@ -4030,7 +4598,7 @@ class BaseRtcManager {
|
|
|
4030
4598
|
this._serverSocket.on(RELAY_MESSAGES.ICE_CANDIDATE, (data) => {
|
|
4031
4599
|
const session = this._getSession(data.clientId);
|
|
4032
4600
|
if (!session) {
|
|
4033
|
-
|
|
4601
|
+
logger$5.warn("No RTCPeerConnection on ICE_CANDIDATE", data);
|
|
4034
4602
|
return;
|
|
4035
4603
|
}
|
|
4036
4604
|
session.addIceCandidate(data.message);
|
|
@@ -4039,7 +4607,7 @@ class BaseRtcManager {
|
|
|
4039
4607
|
this._serverSocket.on(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, (data) => {
|
|
4040
4608
|
const session = this._getSession(data.clientId);
|
|
4041
4609
|
if (!session) {
|
|
4042
|
-
|
|
4610
|
+
logger$5.warn("No RTCPeerConnection on ICE_END_OF_CANDIDATES", data);
|
|
4043
4611
|
return;
|
|
4044
4612
|
}
|
|
4045
4613
|
session.addIceCandidate(null);
|
|
@@ -4049,7 +4617,7 @@ class BaseRtcManager {
|
|
|
4049
4617
|
this._serverSocket.on(RELAY_MESSAGES.SDP_OFFER, (data) => {
|
|
4050
4618
|
const session = this._getSession(data.clientId);
|
|
4051
4619
|
if (!session) {
|
|
4052
|
-
|
|
4620
|
+
logger$5.warn("No RTCPeerConnection on SDP_OFFER", data);
|
|
4053
4621
|
return;
|
|
4054
4622
|
}
|
|
4055
4623
|
const offer = this._transformIncomingSdp(data.message, session.pc);
|
|
@@ -4065,12 +4633,35 @@ class BaseRtcManager {
|
|
|
4065
4633
|
this._serverSocket.on(RELAY_MESSAGES.SDP_ANSWER, (data) => {
|
|
4066
4634
|
const session = this._getSession(data.clientId);
|
|
4067
4635
|
if (!session) {
|
|
4068
|
-
|
|
4636
|
+
logger$5.warn("No RTCPeerConnection on SDP_ANSWER", data);
|
|
4069
4637
|
return;
|
|
4070
4638
|
}
|
|
4071
4639
|
const answer = this._transformIncomingSdp(data.message, session.pc);
|
|
4072
4640
|
session.handleAnswer(answer);
|
|
4073
4641
|
}),
|
|
4642
|
+
|
|
4643
|
+
// if this is a reconnect to signal-server during screen-share we must let signal-server know
|
|
4644
|
+
this._serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room: { sfuServer: isSfu } }) => {
|
|
4645
|
+
if (isSfu || !this._wasScreenSharing) return;
|
|
4646
|
+
|
|
4647
|
+
const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID$1);
|
|
4648
|
+
if (!screenShareStreamId) {
|
|
4649
|
+
return;
|
|
4650
|
+
}
|
|
4651
|
+
|
|
4652
|
+
const screenshareStream = this.localStreams[screenShareStreamId];
|
|
4653
|
+
if (!screenshareStream) {
|
|
4654
|
+
logger$5.warn("screenshare stream %s not found", screenShareStreamId);
|
|
4655
|
+
return;
|
|
4656
|
+
}
|
|
4657
|
+
|
|
4658
|
+
const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
|
|
4659
|
+
|
|
4660
|
+
this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
|
|
4661
|
+
streamId: screenShareStreamId,
|
|
4662
|
+
hasAudioTrack,
|
|
4663
|
+
});
|
|
4664
|
+
}),
|
|
4074
4665
|
];
|
|
4075
4666
|
}
|
|
4076
4667
|
|
|
@@ -4104,6 +4695,27 @@ class BaseRtcManager {
|
|
|
4104
4695
|
track.removeEventListener("ended", this._audioTrackOnEnded);
|
|
4105
4696
|
}
|
|
4106
4697
|
|
|
4698
|
+
setAudioOnly(audioOnly) {
|
|
4699
|
+
this._isAudioOnlyMode = audioOnly;
|
|
4700
|
+
|
|
4701
|
+
this._forEachPeerConnection((session) => {
|
|
4702
|
+
if (session.hasConnectedPeerConnection()) {
|
|
4703
|
+
this._withForcedRenegotiation(session, () =>
|
|
4704
|
+
session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
|
|
4705
|
+
);
|
|
4706
|
+
}
|
|
4707
|
+
});
|
|
4708
|
+
}
|
|
4709
|
+
|
|
4710
|
+
setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
|
|
4711
|
+
const localScreenshareStream = this._getFirstLocalNonCameraStream();
|
|
4712
|
+
|
|
4713
|
+
this._screenshareVideoTrackIds = [
|
|
4714
|
+
...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
|
|
4715
|
+
...remoteScreenshareVideoTrackIds,
|
|
4716
|
+
];
|
|
4717
|
+
}
|
|
4718
|
+
|
|
4107
4719
|
setRoomSessionId(roomSessionId) {
|
|
4108
4720
|
this._roomSessionId = roomSessionId;
|
|
4109
4721
|
}
|
|
@@ -4137,6 +4749,54 @@ function getOptimalBitrate(width, height, frameRate) {
|
|
|
4137
4749
|
return targetBitrate;
|
|
4138
4750
|
}
|
|
4139
4751
|
|
|
4752
|
+
// taken from https://github.com/sindresorhus/ip-regex ^5.0.0
|
|
4753
|
+
// inlined because it's import caused errors in browser-sdk when running tests
|
|
4754
|
+
const word = "[a-fA-F\\d:]";
|
|
4755
|
+
|
|
4756
|
+
const boundry = (options) =>
|
|
4757
|
+
options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
|
|
4758
|
+
|
|
4759
|
+
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}";
|
|
4760
|
+
|
|
4761
|
+
const v6segment = "[a-fA-F\\d]{1,4}";
|
|
4762
|
+
|
|
4763
|
+
const v6 = `
|
|
4764
|
+
(?:
|
|
4765
|
+
(?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
|
|
4766
|
+
(?:${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
|
|
4767
|
+
(?:${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
|
|
4768
|
+
(?:${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
|
|
4769
|
+
(?:${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
|
|
4770
|
+
(?:${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
|
|
4771
|
+
(?:${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
|
|
4772
|
+
(?::(?:(?::${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
|
|
4773
|
+
)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
|
|
4774
|
+
`
|
|
4775
|
+
.replace(/\s*\/\/.*$/gm, "")
|
|
4776
|
+
.replace(/\n/g, "")
|
|
4777
|
+
.trim();
|
|
4778
|
+
|
|
4779
|
+
// Pre-compile only the exact regexes because adding a global flag make regexes stateful
|
|
4780
|
+
const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
|
|
4781
|
+
const v4exact = new RegExp(`^${v4}$`);
|
|
4782
|
+
const v6exact = new RegExp(`^${v6}$`);
|
|
4783
|
+
|
|
4784
|
+
const ipRegex = (options) =>
|
|
4785
|
+
options && options.exact
|
|
4786
|
+
? v46Exact
|
|
4787
|
+
: new RegExp(
|
|
4788
|
+
`(?:${boundry(options)}${v4}${boundry(options)})|(?:${boundry(options)}${v6}${boundry(options)})`,
|
|
4789
|
+
"g"
|
|
4790
|
+
);
|
|
4791
|
+
|
|
4792
|
+
ipRegex.v4 = (options) =>
|
|
4793
|
+
options && options.exact ? v4exact : new RegExp(`${boundry(options)}${v4}${boundry(options)}`, "g");
|
|
4794
|
+
ipRegex.v6 = (options) =>
|
|
4795
|
+
options && options.exact ? v6exact : new RegExp(`${boundry(options)}${v6}${boundry(options)}`, "g");
|
|
4796
|
+
|
|
4797
|
+
const logger$4 = new Logger();
|
|
4798
|
+
|
|
4799
|
+
const ICE_PUBLIC_IP_GATHERING_TIMEOUT = 3 * 1000;
|
|
4140
4800
|
const CAMERA_STREAM_ID = RtcStream.getCameraId();
|
|
4141
4801
|
const browserName$1 = adapter.browserDetails.browser;
|
|
4142
4802
|
|
|
@@ -4147,7 +4807,7 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4147
4807
|
let session = this._getSession(clientId);
|
|
4148
4808
|
let bandwidth = (session && session.bandwidth) || 0;
|
|
4149
4809
|
if (session) {
|
|
4150
|
-
|
|
4810
|
+
logger$4.warn("Replacing peer session", clientId);
|
|
4151
4811
|
this._cleanup(clientId);
|
|
4152
4812
|
} else {
|
|
4153
4813
|
bandwidth = this._changeBandwidthForAllClients(true);
|
|
@@ -4168,6 +4828,14 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4168
4828
|
// clean up some helpers.
|
|
4169
4829
|
session.wasEverConnected = false;
|
|
4170
4830
|
session.relayCandidateSeen = false;
|
|
4831
|
+
session.serverReflexiveCandidateSeen = false;
|
|
4832
|
+
session.publicHostCandidateSeen = false;
|
|
4833
|
+
session.ipv6HostCandidateSeen = false;
|
|
4834
|
+
this.ipv6HostCandidateTeredoSeen = false;
|
|
4835
|
+
this.ipv6HostCandidate6to4Seen = false;
|
|
4836
|
+
this.mdnsHostCandidateSeen = false;
|
|
4837
|
+
|
|
4838
|
+
this._emit(rtcManagerEvents.ICE_RESTART);
|
|
4171
4839
|
|
|
4172
4840
|
this._negotiatePeerConnection(
|
|
4173
4841
|
clientId,
|
|
@@ -4222,13 +4890,13 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4222
4890
|
videoTransceiver.setCodecPreferences(capabilities.codecs);
|
|
4223
4891
|
});
|
|
4224
4892
|
} catch (error) {
|
|
4225
|
-
|
|
4893
|
+
logger$4.error("Error during setting setCodecPreferences:", error);
|
|
4226
4894
|
}
|
|
4227
4895
|
}
|
|
4228
4896
|
|
|
4229
4897
|
_negotiatePeerConnection(clientId, session, constraints) {
|
|
4230
4898
|
if (!session) {
|
|
4231
|
-
|
|
4899
|
+
logger$4.warn("No RTCPeerConnection in negotiatePeerConnection()", clientId);
|
|
4232
4900
|
return;
|
|
4233
4901
|
}
|
|
4234
4902
|
const pc = session.pc;
|
|
@@ -4270,11 +4938,24 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4270
4938
|
return;
|
|
4271
4939
|
}
|
|
4272
4940
|
pc.setLocalDescription(offer).catch((e) => {
|
|
4273
|
-
|
|
4941
|
+
logger$4.warn("RTCPeerConnection.setLocalDescription() failed with local offer", e);
|
|
4942
|
+
|
|
4943
|
+
// we failed to create a valid offer so try having the other side create it, without looping
|
|
4944
|
+
if (this._features.reverseOfferOnFailure) {
|
|
4945
|
+
if (!this._lastReverseDirectionAttemptByClientId)
|
|
4946
|
+
this._lastReverseDirectionAttemptByClientId = {};
|
|
4947
|
+
if (
|
|
4948
|
+
!this._lastReverseDirectionAttemptByClientId[clientId] ||
|
|
4949
|
+
this._lastReverseDirectionAttemptByClientId[clientId] < Date.now() - 10000
|
|
4950
|
+
) {
|
|
4951
|
+
this.acceptNewStream({ clientId, streamId: clientId, shouldAddLocalVideo: true });
|
|
4952
|
+
this._lastReverseDirectionAttemptByClientId[clientId] = Date.now();
|
|
4953
|
+
}
|
|
4954
|
+
}
|
|
4274
4955
|
});
|
|
4275
4956
|
})
|
|
4276
4957
|
.catch((e) => {
|
|
4277
|
-
|
|
4958
|
+
logger$4.warn("RTCPeerConnection.createOffer() failed to create local offer", e);
|
|
4278
4959
|
});
|
|
4279
4960
|
}
|
|
4280
4961
|
|
|
@@ -4310,7 +4991,7 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4310
4991
|
let bandwidth = this._features.bandwidth
|
|
4311
4992
|
? parseInt(this._features.bandwidth, 10)
|
|
4312
4993
|
: {
|
|
4313
|
-
1:
|
|
4994
|
+
1: 0,
|
|
4314
4995
|
2: this._features.highP2PBandwidth ? 768 : 384,
|
|
4315
4996
|
3: this._features.highP2PBandwidth ? 512 : 256,
|
|
4316
4997
|
4: 192,
|
|
@@ -4376,9 +5057,73 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4376
5057
|
pc.addTrack(this._stoppedVideoTrack, localCameraStream);
|
|
4377
5058
|
}
|
|
4378
5059
|
|
|
5060
|
+
pc.onicegatheringstatechange = (event) => {
|
|
5061
|
+
const connection = event.target;
|
|
5062
|
+
|
|
5063
|
+
switch (connection.iceGatheringState) {
|
|
5064
|
+
case "gathering":
|
|
5065
|
+
if (this.icePublicIPGatheringTimeoutID) clearTimeout(this.icePublicIPGatheringTimeoutID);
|
|
5066
|
+
this.icePublicIPGatheringTimeoutID = setTimeout(() => {
|
|
5067
|
+
if (
|
|
5068
|
+
!session.publicHostCandidateSeen &&
|
|
5069
|
+
!session.relayCandidateSeen &&
|
|
5070
|
+
!session.serverReflexiveCandidateSeen
|
|
5071
|
+
) {
|
|
5072
|
+
this._emit(rtcManagerEvents.ICE_NO_PUBLIC_IP_GATHERED_3SEC);
|
|
5073
|
+
}
|
|
5074
|
+
}, ICE_PUBLIC_IP_GATHERING_TIMEOUT);
|
|
5075
|
+
break;
|
|
5076
|
+
case "complete":
|
|
5077
|
+
if (this.icePublicIPGatheringTimeoutID) clearTimeout(this.icePublicIPGatheringTimeoutID);
|
|
5078
|
+
this.icePublicIPGatheringTimeoutID = undefined;
|
|
5079
|
+
break;
|
|
5080
|
+
}
|
|
5081
|
+
};
|
|
5082
|
+
|
|
4379
5083
|
pc.onicecandidate = (event) => {
|
|
4380
5084
|
if (event.candidate) {
|
|
4381
|
-
|
|
5085
|
+
switch (event.candidate?.type) {
|
|
5086
|
+
case "host":
|
|
5087
|
+
const address = event?.candidate?.address;
|
|
5088
|
+
try {
|
|
5089
|
+
if (ipRegex.v4({ exact: true }).test(address)) {
|
|
5090
|
+
const ipv4 = checkIp(address);
|
|
5091
|
+
if (ipv4.isPublicIp) session.publicHostCandidateSeen = true;
|
|
5092
|
+
} else if (ipRegex.v6({ exact: true }).test(address.replace(/^\[(.*)\]/, "$1"))) {
|
|
5093
|
+
const ipv6 = new Address6(address.replace(/^\[(.*)\]/, "$1"));
|
|
5094
|
+
session.ipv6HostCandidateSeen = true;
|
|
5095
|
+
|
|
5096
|
+
if (ipv6.getScope() === "Global") {
|
|
5097
|
+
session.publicHostCandidateSeen = true;
|
|
5098
|
+
}
|
|
5099
|
+
if (ipv6.isTeredo()) {
|
|
5100
|
+
session.ipv6HostCandidateTeredoSeen = true;
|
|
5101
|
+
}
|
|
5102
|
+
if (ipv6.is6to4()) {
|
|
5103
|
+
session.ipv6HostCandidate6to4Seen = true;
|
|
5104
|
+
}
|
|
5105
|
+
} else {
|
|
5106
|
+
const uuidv4 = address.replace(/.local/, "");
|
|
5107
|
+
if (uuidv4 && validate(uuidv4, 4)) {
|
|
5108
|
+
session.mdnsHostCandidateSeen = true;
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
} catch (error) {
|
|
5112
|
+
logger$4.info("Error during parsing candidates! Error: ", { error });
|
|
5113
|
+
}
|
|
5114
|
+
break;
|
|
5115
|
+
case "srflx":
|
|
5116
|
+
if (!session.serverReflexiveCandidateSeen) {
|
|
5117
|
+
session.serverReflexiveCandidateSeen = true;
|
|
5118
|
+
}
|
|
5119
|
+
break;
|
|
5120
|
+
case "relay":
|
|
5121
|
+
case "relayed":
|
|
5122
|
+
if (!session.relayCandidateSeen) {
|
|
5123
|
+
session.relayCandidateSeen = true;
|
|
5124
|
+
}
|
|
5125
|
+
break;
|
|
5126
|
+
}
|
|
4382
5127
|
this._emitServerEvent(RELAY_MESSAGES.ICE_CANDIDATE, {
|
|
4383
5128
|
receiverId: clientId,
|
|
4384
5129
|
message: event.candidate,
|
|
@@ -4387,6 +5132,20 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4387
5132
|
this._emitServerEvent(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, {
|
|
4388
5133
|
receiverId: clientId,
|
|
4389
5134
|
});
|
|
5135
|
+
if (
|
|
5136
|
+
!session.publicHostCandidateSeen &&
|
|
5137
|
+
!session.relayCandidateSeen &&
|
|
5138
|
+
!session.serverReflexiveCandidateSeen
|
|
5139
|
+
) {
|
|
5140
|
+
this._emit(rtcManagerEvents.ICE_NO_PUBLIC_IP_GATHERED);
|
|
5141
|
+
}
|
|
5142
|
+
if (session.ipv6HostCandidateSeen) {
|
|
5143
|
+
this._emit(rtcManagerEvents.ICE_IPV6_SEEN, {
|
|
5144
|
+
teredoSeen: session.ipv6HostCandidateTeredoSeen,
|
|
5145
|
+
sixtofourSeen: session.ipv6HostCandidate6to4Seen,
|
|
5146
|
+
});
|
|
5147
|
+
}
|
|
5148
|
+
if (session.mdnsHostCandidateSeen) this._emit(rtcManagerEvents.ICE_MDNS_SEEN);
|
|
4390
5149
|
}
|
|
4391
5150
|
};
|
|
4392
5151
|
|
|
@@ -4413,7 +5172,7 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4413
5172
|
if (session) {
|
|
4414
5173
|
// this will happen on a signal-server reconnect
|
|
4415
5174
|
// before we tried an ice-restart here, now we recreate the session/pc
|
|
4416
|
-
|
|
5175
|
+
logger$4.warn("Replacing peer session", clientId);
|
|
4417
5176
|
this._cleanup(clientId); // will cleanup and delete session/pc
|
|
4418
5177
|
} else {
|
|
4419
5178
|
// we adjust bandwidth based on number of sessions/pcs
|
|
@@ -4511,14 +5270,20 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4511
5270
|
streamId,
|
|
4512
5271
|
hasAudioTrack: !!stream.getAudioTracks().length,
|
|
4513
5272
|
});
|
|
5273
|
+
this._wasScreenSharing = true;
|
|
4514
5274
|
this._addStreamToPeerConnections(stream);
|
|
4515
5275
|
}
|
|
4516
5276
|
|
|
4517
5277
|
removeStream(streamId, stream, requestedByClientId) {
|
|
4518
5278
|
super.removeStream(streamId, stream);
|
|
4519
5279
|
this._removeStreamFromPeerConnections(stream);
|
|
5280
|
+
this._wasScreenSharing = false;
|
|
4520
5281
|
this._emitServerEvent(PROTOCOL_REQUESTS.STOP_SCREENSHARE, { streamId, requestedByClientId });
|
|
4521
5282
|
}
|
|
5283
|
+
|
|
5284
|
+
hasClient(clientId) {
|
|
5285
|
+
return Object.keys(this.peerConnections).includes(clientId);
|
|
5286
|
+
}
|
|
4522
5287
|
}
|
|
4523
5288
|
|
|
4524
5289
|
class SfuV2Parser {
|
|
@@ -4643,12 +5408,13 @@ class SfuV2Parser {
|
|
|
4643
5408
|
}
|
|
4644
5409
|
}
|
|
4645
5410
|
|
|
4646
|
-
|
|
4647
|
-
|
|
5411
|
+
const logger$3 = new Logger();
|
|
5412
|
+
|
|
5413
|
+
class VegaConnection extends EventEmitter$1 {
|
|
5414
|
+
constructor(wsUrl, protocol = "whereby-sfu#v4") {
|
|
4648
5415
|
super();
|
|
4649
5416
|
|
|
4650
5417
|
this.wsUrl = wsUrl;
|
|
4651
|
-
this.logger = logger;
|
|
4652
5418
|
this.protocol = protocol;
|
|
4653
5419
|
|
|
4654
5420
|
// This is the map of sent requests that are waiting for a response
|
|
@@ -4681,7 +5447,7 @@ class VegaConnection extends EventEmitter {
|
|
|
4681
5447
|
}
|
|
4682
5448
|
|
|
4683
5449
|
_onOpen() {
|
|
4684
|
-
|
|
5450
|
+
logger$3.info("Connected");
|
|
4685
5451
|
|
|
4686
5452
|
this.emit("open");
|
|
4687
5453
|
}
|
|
@@ -4689,7 +5455,7 @@ class VegaConnection extends EventEmitter {
|
|
|
4689
5455
|
_onMessage(event) {
|
|
4690
5456
|
const socketMessage = SfuV2Parser.parse(event.data);
|
|
4691
5457
|
|
|
4692
|
-
|
|
5458
|
+
logger$3.info("Received message", socketMessage);
|
|
4693
5459
|
|
|
4694
5460
|
if (socketMessage?.response) {
|
|
4695
5461
|
this._handleResponse(socketMessage);
|
|
@@ -4699,13 +5465,13 @@ class VegaConnection extends EventEmitter {
|
|
|
4699
5465
|
}
|
|
4700
5466
|
|
|
4701
5467
|
_onClose() {
|
|
4702
|
-
|
|
5468
|
+
logger$3.info("Disconnected");
|
|
4703
5469
|
|
|
4704
5470
|
this._tearDown();
|
|
4705
5471
|
}
|
|
4706
5472
|
|
|
4707
5473
|
_onError(error) {
|
|
4708
|
-
|
|
5474
|
+
logger$3.info("Error", error);
|
|
4709
5475
|
}
|
|
4710
5476
|
|
|
4711
5477
|
_handleResponse(socketMessage) {
|
|
@@ -5061,6 +5827,8 @@ const defaultParams = {
|
|
|
5061
5827
|
outFormula: "score", // the out/score sent to SFU
|
|
5062
5828
|
};
|
|
5063
5829
|
|
|
5830
|
+
const logger$2 = new Logger();
|
|
5831
|
+
|
|
5064
5832
|
function createMicAnalyser({ micTrack, params, onScoreUpdated }) {
|
|
5065
5833
|
// todo: might need to reuse existing in PWA
|
|
5066
5834
|
const audioCtx = new AudioContext();
|
|
@@ -5086,7 +5854,7 @@ function createMicAnalyser({ micTrack, params, onScoreUpdated }) {
|
|
|
5086
5854
|
track = stream.getAudioTracks()[0];
|
|
5087
5855
|
lastTrackWasOurs = true;
|
|
5088
5856
|
} catch (ex) {
|
|
5089
|
-
|
|
5857
|
+
logger$2.warn("unable to fetch new track for colocation speaker analysis, using current", ex);
|
|
5090
5858
|
}
|
|
5091
5859
|
}
|
|
5092
5860
|
|
|
@@ -5218,18 +5986,207 @@ const maybeTurnOnly = (transportConfig, features) => {
|
|
|
5218
5986
|
}
|
|
5219
5987
|
};
|
|
5220
5988
|
|
|
5989
|
+
const logger$1 = new Logger();
|
|
5990
|
+
|
|
5991
|
+
const MEDIA_QUALITY = Object.freeze({
|
|
5992
|
+
ok: "ok",
|
|
5993
|
+
warning: "warning",
|
|
5994
|
+
critical: "critical",
|
|
5995
|
+
});
|
|
5996
|
+
|
|
5997
|
+
const MONITOR_INTERVAL = 600; // ms
|
|
5998
|
+
const TREND_HORIZON = 3; // number of monitor intervals needed for quality to change
|
|
5999
|
+
const WARNING_SCORE = 9;
|
|
6000
|
+
const CRITICAL_SCORE = 7;
|
|
6001
|
+
|
|
6002
|
+
class VegaMediaQualityMonitor extends EventEmitter {
|
|
6003
|
+
constructor() {
|
|
6004
|
+
super();
|
|
6005
|
+
this._clients = {};
|
|
6006
|
+
this._producers = {};
|
|
6007
|
+
this._startMonitor();
|
|
6008
|
+
}
|
|
6009
|
+
|
|
6010
|
+
close() {
|
|
6011
|
+
clearInterval(this._intervalHandle);
|
|
6012
|
+
delete this._intervalHandle;
|
|
6013
|
+
this._producers = {};
|
|
6014
|
+
this._clients = {};
|
|
6015
|
+
}
|
|
6016
|
+
|
|
6017
|
+
_startMonitor() {
|
|
6018
|
+
this._intervalHandle = setInterval(() => {
|
|
6019
|
+
Object.entries(this._producers).forEach(([clientId, producers]) => {
|
|
6020
|
+
this._evaluateClient(clientId, producers);
|
|
6021
|
+
});
|
|
6022
|
+
}, MONITOR_INTERVAL);
|
|
6023
|
+
}
|
|
6024
|
+
|
|
6025
|
+
_evaluateClient(clientId, producers) {
|
|
6026
|
+
if (!this._clients[clientId]) {
|
|
6027
|
+
this._clients[clientId] = {
|
|
6028
|
+
audio: { currentQuality: MEDIA_QUALITY.ok, trend: [] },
|
|
6029
|
+
video: { currentQuality: MEDIA_QUALITY.ok, trend: [] },
|
|
6030
|
+
};
|
|
6031
|
+
}
|
|
6032
|
+
|
|
6033
|
+
this._evaluateProducer(
|
|
6034
|
+
clientId,
|
|
6035
|
+
Object.values(producers).filter((p) => p.kind === "audio"),
|
|
6036
|
+
"audio"
|
|
6037
|
+
);
|
|
6038
|
+
this._evaluateProducer(
|
|
6039
|
+
clientId,
|
|
6040
|
+
Object.values(producers).filter((p) => p.kind === "video"),
|
|
6041
|
+
"video"
|
|
6042
|
+
);
|
|
6043
|
+
}
|
|
6044
|
+
|
|
6045
|
+
_evaluateProducer(clientId, producers, kind) {
|
|
6046
|
+
if (producers.length === 0) {
|
|
6047
|
+
return;
|
|
6048
|
+
}
|
|
6049
|
+
|
|
6050
|
+
const avgScore = producers.reduce((prev, curr) => prev + curr.score, 0) / producers.length;
|
|
6051
|
+
const newQuality = this._evaluateScore(avgScore);
|
|
6052
|
+
const qualityChanged = this._updateTrend(newQuality, this._clients[clientId][kind]);
|
|
6053
|
+
if (qualityChanged) {
|
|
6054
|
+
this.emit(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, {
|
|
6055
|
+
clientId,
|
|
6056
|
+
kind,
|
|
6057
|
+
quality: newQuality,
|
|
6058
|
+
});
|
|
6059
|
+
}
|
|
6060
|
+
}
|
|
6061
|
+
|
|
6062
|
+
_updateTrend(newQuality, state) {
|
|
6063
|
+
state.trend.push(newQuality);
|
|
6064
|
+
if (state.trend.length > TREND_HORIZON) {
|
|
6065
|
+
state.trend.shift();
|
|
6066
|
+
}
|
|
6067
|
+
|
|
6068
|
+
if (newQuality !== state.currentQuality && state.trend.every((t) => t !== state.currentQuality)) {
|
|
6069
|
+
state.currentQuality = newQuality;
|
|
6070
|
+
return true;
|
|
6071
|
+
} else {
|
|
6072
|
+
return false;
|
|
6073
|
+
}
|
|
6074
|
+
}
|
|
6075
|
+
|
|
6076
|
+
addProducer(clientId, producerId) {
|
|
6077
|
+
if (!clientId || !producerId || !(typeof clientId === "string" && typeof producerId === "string")) {
|
|
6078
|
+
logger$1.warn("Missing clientId or producerId");
|
|
6079
|
+
return;
|
|
6080
|
+
}
|
|
6081
|
+
|
|
6082
|
+
if (!this._producers[clientId]) {
|
|
6083
|
+
this._producers[clientId] = {};
|
|
6084
|
+
}
|
|
6085
|
+
|
|
6086
|
+
this._producers[clientId][producerId] = {};
|
|
6087
|
+
}
|
|
6088
|
+
|
|
6089
|
+
removeProducer(clientId, producerId) {
|
|
6090
|
+
delete this._producers[clientId][producerId];
|
|
6091
|
+
|
|
6092
|
+
if (Object.keys(this._producers[clientId]).length === 0) {
|
|
6093
|
+
delete this._producers[clientId];
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
addConsumer(clientId, consumerId) {
|
|
6098
|
+
if (!clientId || !consumerId) {
|
|
6099
|
+
logger$1.warn("Missing clientId or consumerId");
|
|
6100
|
+
return;
|
|
6101
|
+
}
|
|
6102
|
+
|
|
6103
|
+
if (!this._producers[clientId]) {
|
|
6104
|
+
this._producers[clientId] = {};
|
|
6105
|
+
}
|
|
6106
|
+
|
|
6107
|
+
this._producers[clientId][consumerId] = {};
|
|
6108
|
+
}
|
|
6109
|
+
|
|
6110
|
+
removeConsumer(clientId, consumerId) {
|
|
6111
|
+
delete this._producers[clientId][consumerId];
|
|
6112
|
+
|
|
6113
|
+
if (Object.keys(this._producers[clientId]).length === 0) {
|
|
6114
|
+
delete this._producers[clientId];
|
|
6115
|
+
}
|
|
6116
|
+
}
|
|
6117
|
+
|
|
6118
|
+
addProducerScore(clientId, producerId, kind, score) {
|
|
6119
|
+
if (
|
|
6120
|
+
!Array.isArray(score) ||
|
|
6121
|
+
score.length === 0 ||
|
|
6122
|
+
score.some((s) => !s || !s.hasOwnProperty("score") || typeof s.score !== "number" || isNaN(s.score))
|
|
6123
|
+
) {
|
|
6124
|
+
logger$1.warn("Unexpected producer score format");
|
|
6125
|
+
return;
|
|
6126
|
+
}
|
|
6127
|
+
this._producers[clientId][producerId] = { kind, score: this._calcAvgProducerScore(score.map((s) => s.score)) };
|
|
6128
|
+
}
|
|
6129
|
+
|
|
6130
|
+
addConsumerScore(clientId, consumerId, kind, score) {
|
|
6131
|
+
if (!score || !score.hasOwnProperty("producerScores") || !Array.isArray(score.producerScores)) {
|
|
6132
|
+
logger$1.warn("Unexpected consumer score format");
|
|
6133
|
+
return;
|
|
6134
|
+
}
|
|
6135
|
+
this._producers[clientId][consumerId] = { kind, score: this._calcAvgProducerScore(score.producerScores) };
|
|
6136
|
+
}
|
|
6137
|
+
|
|
6138
|
+
_evaluateScore(score) {
|
|
6139
|
+
if (score <= WARNING_SCORE && score > CRITICAL_SCORE) {
|
|
6140
|
+
return MEDIA_QUALITY.warning;
|
|
6141
|
+
} else if (score <= CRITICAL_SCORE && score > 0) {
|
|
6142
|
+
return MEDIA_QUALITY.critical;
|
|
6143
|
+
} else {
|
|
6144
|
+
return MEDIA_QUALITY.ok;
|
|
6145
|
+
}
|
|
6146
|
+
}
|
|
6147
|
+
|
|
6148
|
+
_calcAvgProducerScore(scores) {
|
|
6149
|
+
try {
|
|
6150
|
+
if (!Array.isArray(scores) || scores.length === 0) {
|
|
6151
|
+
return 0;
|
|
6152
|
+
}
|
|
6153
|
+
|
|
6154
|
+
let totalScore = 0;
|
|
6155
|
+
let divisor = 0;
|
|
6156
|
+
|
|
6157
|
+
scores.forEach((score) => {
|
|
6158
|
+
if (score > 0) {
|
|
6159
|
+
totalScore += score;
|
|
6160
|
+
divisor++;
|
|
6161
|
+
}
|
|
6162
|
+
});
|
|
6163
|
+
|
|
6164
|
+
if (totalScore === 0 || divisor === 0) {
|
|
6165
|
+
return 0;
|
|
6166
|
+
} else {
|
|
6167
|
+
return totalScore / divisor;
|
|
6168
|
+
}
|
|
6169
|
+
} catch (error) {
|
|
6170
|
+
logger$1.error(error);
|
|
6171
|
+
return 0;
|
|
6172
|
+
}
|
|
6173
|
+
}
|
|
6174
|
+
}
|
|
6175
|
+
|
|
6176
|
+
const logger = new Logger();
|
|
6177
|
+
|
|
5221
6178
|
const browserName = adapter.browserDetails.browser;
|
|
5222
6179
|
let unloading = false;
|
|
5223
6180
|
|
|
5224
6181
|
const RESTARTICE_ERROR_RETRY_THRESHOLD_IN_MS = 3500;
|
|
5225
6182
|
const RESTARTICE_ERROR_MAX_RETRY_COUNT = 5;
|
|
5226
|
-
const OUTBOUND_CAM_OUTBOUND_STREAM_ID = v4();
|
|
5227
|
-
const OUTBOUND_SCREEN_OUTBOUND_STREAM_ID = v4();
|
|
6183
|
+
const OUTBOUND_CAM_OUTBOUND_STREAM_ID = v4$1();
|
|
6184
|
+
const OUTBOUND_SCREEN_OUTBOUND_STREAM_ID = v4$1();
|
|
5228
6185
|
|
|
5229
6186
|
if (browserName === "chrome") window.document.addEventListener("beforeunload", () => (unloading = true));
|
|
5230
6187
|
|
|
5231
6188
|
class VegaRtcManager {
|
|
5232
|
-
constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features, eventClaim,
|
|
6189
|
+
constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features, eventClaim, deviceHandlerFactory }) {
|
|
5233
6190
|
assert$1.ok(selfId, "selfId is required");
|
|
5234
6191
|
assert$1.ok(room, "room is required");
|
|
5235
6192
|
assert$1.ok(emitter && emitter.emit, "emitter is required");
|
|
@@ -5246,14 +6203,18 @@ class VegaRtcManager {
|
|
|
5246
6203
|
this._webrtcProvider = webrtcProvider;
|
|
5247
6204
|
this._features = features || {};
|
|
5248
6205
|
this._eventClaim = eventClaim;
|
|
5249
|
-
this._logger = logger;
|
|
5250
6206
|
|
|
5251
6207
|
this._vegaConnection = null;
|
|
5252
6208
|
|
|
5253
6209
|
this._micAnalyser = null;
|
|
5254
6210
|
this._micAnalyserDebugger = null;
|
|
5255
6211
|
|
|
5256
|
-
|
|
6212
|
+
if (deviceHandlerFactory) {
|
|
6213
|
+
this._mediasoupDevice = new Device({ handlerFactory: deviceHandlerFactory });
|
|
6214
|
+
} else {
|
|
6215
|
+
this._mediasoupDevice = new Device({ handlerName: getHandler() });
|
|
6216
|
+
}
|
|
6217
|
+
|
|
5257
6218
|
this._routerRtpCapabilities = null;
|
|
5258
6219
|
|
|
5259
6220
|
this._sendTransport = null;
|
|
@@ -5316,6 +6277,11 @@ class VegaRtcManager {
|
|
|
5316
6277
|
// Retry if connection closed until disconnectAll called;
|
|
5317
6278
|
this._reconnect = true;
|
|
5318
6279
|
this._reconnectTimeOut = null;
|
|
6280
|
+
|
|
6281
|
+
this._qualityMonitor = new VegaMediaQualityMonitor();
|
|
6282
|
+
this._qualityMonitor.on(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, (payload) => {
|
|
6283
|
+
this._emitToPWA(PROTOCOL_EVENTS.MEDIA_QUALITY_CHANGED, payload);
|
|
6284
|
+
});
|
|
5319
6285
|
}
|
|
5320
6286
|
|
|
5321
6287
|
_updateAndScheduleMediaServersRefresh({ iceServers, sfuServer, mediaserverConfigTtlSeconds }) {
|
|
@@ -5348,7 +6314,7 @@ class VegaRtcManager {
|
|
|
5348
6314
|
|
|
5349
6315
|
this._serverSocket.on(PROTOCOL_RESPONSES.MEDIASERVER_CONFIG, (data) => {
|
|
5350
6316
|
if (data.error) {
|
|
5351
|
-
|
|
6317
|
+
logger.warn("FETCH_MEDIASERVER_CONFIG failed:", data.error);
|
|
5352
6318
|
return;
|
|
5353
6319
|
}
|
|
5354
6320
|
this._updateAndScheduleMediaServersRefresh(data);
|
|
@@ -5380,14 +6346,14 @@ class VegaRtcManager {
|
|
|
5380
6346
|
const queryString = searchParams.toString();
|
|
5381
6347
|
const wsUrl = `wss://${host}?${queryString}`;
|
|
5382
6348
|
|
|
5383
|
-
this._vegaConnection = new VegaConnection(wsUrl
|
|
6349
|
+
this._vegaConnection = new VegaConnection(wsUrl);
|
|
5384
6350
|
this._vegaConnection.on("open", () => this._join());
|
|
5385
6351
|
this._vegaConnection.on("close", () => this._onClose());
|
|
5386
6352
|
this._vegaConnection.on("message", (message) => this._onMessage(message));
|
|
5387
6353
|
}
|
|
5388
6354
|
|
|
5389
6355
|
_onClose() {
|
|
5390
|
-
|
|
6356
|
+
logger.info("_onClose()");
|
|
5391
6357
|
|
|
5392
6358
|
// These will clean up any and all producers/consumers
|
|
5393
6359
|
this._sendTransport?.close();
|
|
@@ -5400,10 +6366,14 @@ class VegaRtcManager {
|
|
|
5400
6366
|
if (this._reconnect) {
|
|
5401
6367
|
this._reconnectTimeOut = setTimeout(() => this._connect(), 1000);
|
|
5402
6368
|
}
|
|
6369
|
+
|
|
6370
|
+
this._qualityMonitor.close();
|
|
6371
|
+
this._emitToPWA(rtcManagerEvents.SFU_CONNECTION_CLOSED);
|
|
5403
6372
|
}
|
|
5404
6373
|
|
|
5405
6374
|
async _join() {
|
|
5406
|
-
|
|
6375
|
+
logger.info("join()");
|
|
6376
|
+
this._emitToPWA(rtcManagerEvents.SFU_CONNECTION_OPEN);
|
|
5407
6377
|
|
|
5408
6378
|
try {
|
|
5409
6379
|
// We need to always do this, as this triggers the join logic on the SFU
|
|
@@ -5437,7 +6407,7 @@ class VegaRtcManager {
|
|
|
5437
6407
|
|
|
5438
6408
|
await Promise.all(mediaPromises);
|
|
5439
6409
|
} catch (error) {
|
|
5440
|
-
|
|
6410
|
+
logger.error("_join() [error:%o]", error);
|
|
5441
6411
|
// TODO: handle this error, rejoin?
|
|
5442
6412
|
}
|
|
5443
6413
|
}
|
|
@@ -5468,7 +6438,7 @@ class VegaRtcManager {
|
|
|
5468
6438
|
|
|
5469
6439
|
const transport = this._mediasoupDevice[creator](transportOptions);
|
|
5470
6440
|
const onConnectionStateListener = async (connectionState) => {
|
|
5471
|
-
|
|
6441
|
+
logger.info(`Transport ConnectionStateChanged ${connectionState}`);
|
|
5472
6442
|
if (connectionState !== "disconnected" && connectionState !== "failed") {
|
|
5473
6443
|
return;
|
|
5474
6444
|
}
|
|
@@ -5542,17 +6512,17 @@ class VegaRtcManager {
|
|
|
5542
6512
|
|
|
5543
6513
|
async _restartIce(transport, retried = 0) {
|
|
5544
6514
|
if (!transport || !("closed" in transport) || !("connectionState" in transport)) {
|
|
5545
|
-
|
|
6515
|
+
logger.info("_restartIce: No transport or property closed or connectionState!");
|
|
5546
6516
|
return;
|
|
5547
6517
|
}
|
|
5548
6518
|
|
|
5549
6519
|
if (transport.closed) {
|
|
5550
|
-
|
|
6520
|
+
logger.info("_restartIce: Transport is closed!");
|
|
5551
6521
|
return;
|
|
5552
6522
|
}
|
|
5553
6523
|
|
|
5554
6524
|
if (transport.connectionState !== "disconnected" && transport.connectionState !== "failed") {
|
|
5555
|
-
|
|
6525
|
+
logger.info("_restartIce: Connection is healthy ICE restart no loneger needed!");
|
|
5556
6526
|
return;
|
|
5557
6527
|
}
|
|
5558
6528
|
|
|
@@ -5565,24 +6535,24 @@ class VegaRtcManager {
|
|
|
5565
6535
|
transport.appData.iceRestartStarted = now;
|
|
5566
6536
|
|
|
5567
6537
|
if (RESTARTICE_ERROR_MAX_RETRY_COUNT <= retried) {
|
|
5568
|
-
|
|
6538
|
+
logger.info("_restartIce: Reached restart ICE maximum retry count!");
|
|
5569
6539
|
return;
|
|
5570
6540
|
}
|
|
5571
6541
|
|
|
5572
6542
|
if (!this._vegaConnection) {
|
|
5573
|
-
|
|
6543
|
+
logger.info(`_restartIce: Connection is undefined`);
|
|
5574
6544
|
return;
|
|
5575
6545
|
}
|
|
5576
6546
|
const { iceParameters } = await this._vegaConnection.request("restartIce", { transportId: transport.id });
|
|
5577
6547
|
|
|
5578
|
-
|
|
6548
|
+
logger.info("_restartIce: ICE restart iceParameters received from SFU: ", iceParameters);
|
|
5579
6549
|
const error = await transport
|
|
5580
6550
|
.restartIce({ iceParameters })
|
|
5581
6551
|
.then(() => null)
|
|
5582
6552
|
.catch((err) => err);
|
|
5583
6553
|
|
|
5584
6554
|
if (error) {
|
|
5585
|
-
|
|
6555
|
+
logger.error(`_restartIce: ICE restart failed: ${error}`);
|
|
5586
6556
|
switch (error.message) {
|
|
5587
6557
|
case "missing transportId":
|
|
5588
6558
|
case "no such transport":
|
|
@@ -5618,7 +6588,7 @@ class VegaRtcManager {
|
|
|
5618
6588
|
}
|
|
5619
6589
|
|
|
5620
6590
|
async _internalSendMic() {
|
|
5621
|
-
|
|
6591
|
+
logger.info("_internalSendMic()");
|
|
5622
6592
|
|
|
5623
6593
|
this._micProducerPromise = (async () => {
|
|
5624
6594
|
try {
|
|
@@ -5647,21 +6617,23 @@ class VegaRtcManager {
|
|
|
5647
6617
|
currentPaused ? producer.pause() : producer.resume();
|
|
5648
6618
|
|
|
5649
6619
|
this._micProducer = producer;
|
|
6620
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
5650
6621
|
|
|
5651
6622
|
producer.observer.once("close", () => {
|
|
5652
|
-
|
|
6623
|
+
logger.info('micProducer "close" event');
|
|
5653
6624
|
|
|
5654
6625
|
if (producer.appData.localClosed)
|
|
5655
6626
|
this._vegaConnection?.message("closeProducers", { producerIds: [producer.id] });
|
|
5656
6627
|
|
|
5657
6628
|
this._micProducer = null;
|
|
5658
6629
|
this._micProducerPromise = null;
|
|
6630
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
5659
6631
|
});
|
|
5660
6632
|
|
|
5661
6633
|
if (this._micTrack !== this._micProducer.track) await this._replaceMicTrack();
|
|
5662
6634
|
if (this._micPaused !== this._micProducer.paused) this._pauseResumeMic();
|
|
5663
6635
|
} catch (error) {
|
|
5664
|
-
|
|
6636
|
+
logger.error("micProducer failed:%o", error);
|
|
5665
6637
|
} finally {
|
|
5666
6638
|
this._micProducerPromise = null;
|
|
5667
6639
|
|
|
@@ -5675,7 +6647,7 @@ class VegaRtcManager {
|
|
|
5675
6647
|
}
|
|
5676
6648
|
|
|
5677
6649
|
async _internalSetupMicScore() {
|
|
5678
|
-
|
|
6650
|
+
logger.info("_internalSetupMicScore()");
|
|
5679
6651
|
|
|
5680
6652
|
this._micScoreProducerPromise = (async () => {
|
|
5681
6653
|
try {
|
|
@@ -5698,7 +6670,7 @@ class VegaRtcManager {
|
|
|
5698
6670
|
this._micScoreProducer = producer;
|
|
5699
6671
|
|
|
5700
6672
|
producer.observer.once("close", () => {
|
|
5701
|
-
|
|
6673
|
+
logger.info('micScoreProducer "close" event');
|
|
5702
6674
|
if (producer.appData.localClosed) {
|
|
5703
6675
|
this._vegaConnection?.message("closeDataProducers", { dataProducerIds: [producer.id] });
|
|
5704
6676
|
}
|
|
@@ -5707,7 +6679,7 @@ class VegaRtcManager {
|
|
|
5707
6679
|
this._micScoreProducerPromise = null;
|
|
5708
6680
|
});
|
|
5709
6681
|
} catch (error) {
|
|
5710
|
-
|
|
6682
|
+
logger.error("micScoreProducer failed:%o", error);
|
|
5711
6683
|
} finally {
|
|
5712
6684
|
this._micScoreProducerPromise = null;
|
|
5713
6685
|
|
|
@@ -5725,7 +6697,7 @@ class VegaRtcManager {
|
|
|
5725
6697
|
}
|
|
5726
6698
|
|
|
5727
6699
|
async _replaceMicTrack() {
|
|
5728
|
-
|
|
6700
|
+
logger.info("_replaceMicTrack()");
|
|
5729
6701
|
|
|
5730
6702
|
if (!this._micTrack || !this._micProducer || this._micProducer.closed) return;
|
|
5731
6703
|
|
|
@@ -5741,7 +6713,7 @@ class VegaRtcManager {
|
|
|
5741
6713
|
}
|
|
5742
6714
|
|
|
5743
6715
|
_pauseResumeMic() {
|
|
5744
|
-
|
|
6716
|
+
logger.info("_pauseResumeMic()");
|
|
5745
6717
|
|
|
5746
6718
|
if (!this._micProducer || this._micProducer.closed) return;
|
|
5747
6719
|
|
|
@@ -5768,7 +6740,7 @@ class VegaRtcManager {
|
|
|
5768
6740
|
}
|
|
5769
6741
|
|
|
5770
6742
|
async _sendMic(track) {
|
|
5771
|
-
|
|
6743
|
+
logger.info("_sendMic() [track:%o]", track);
|
|
5772
6744
|
|
|
5773
6745
|
this._micTrack = track;
|
|
5774
6746
|
|
|
@@ -5800,7 +6772,7 @@ class VegaRtcManager {
|
|
|
5800
6772
|
try {
|
|
5801
6773
|
this._micScoreProducer.send(JSON.stringify({ score }));
|
|
5802
6774
|
} catch (ex) {
|
|
5803
|
-
|
|
6775
|
+
logger.error("_sendMicScore failed [error:%o]", ex);
|
|
5804
6776
|
}
|
|
5805
6777
|
return;
|
|
5806
6778
|
}
|
|
@@ -5812,7 +6784,7 @@ class VegaRtcManager {
|
|
|
5812
6784
|
}
|
|
5813
6785
|
|
|
5814
6786
|
async _internalSendWebcam() {
|
|
5815
|
-
|
|
6787
|
+
logger.info("_internalSendWebcam()");
|
|
5816
6788
|
|
|
5817
6789
|
this._webcamProducerPromise = (async () => {
|
|
5818
6790
|
try {
|
|
@@ -5841,21 +6813,23 @@ class VegaRtcManager {
|
|
|
5841
6813
|
currentPaused ? producer.pause() : producer.resume();
|
|
5842
6814
|
|
|
5843
6815
|
this._webcamProducer = producer;
|
|
6816
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
5844
6817
|
producer.observer.once("close", () => {
|
|
5845
|
-
|
|
6818
|
+
logger.info('webcamProducer "close" event');
|
|
5846
6819
|
|
|
5847
6820
|
if (producer.appData.localClosed)
|
|
5848
6821
|
this._vegaConnection?.message("closeProducers", { producerIds: [producer.id] });
|
|
5849
6822
|
|
|
5850
6823
|
this._webcamProducer = null;
|
|
5851
6824
|
this._webcamProducerPromise = null;
|
|
6825
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
5852
6826
|
});
|
|
5853
6827
|
|
|
5854
6828
|
// Has someone replaced the track?
|
|
5855
6829
|
if (this._webcamTrack !== this._webcamProducer.track) await this._replaceWebcamTrack();
|
|
5856
6830
|
if (this._webcamPaused !== this._webcamProducer.paused) this._pauseResumeWebcam();
|
|
5857
6831
|
} catch (error) {
|
|
5858
|
-
|
|
6832
|
+
logger.error("webcamProducer failed:%o", error);
|
|
5859
6833
|
} finally {
|
|
5860
6834
|
this._webcamProducerPromise = null;
|
|
5861
6835
|
|
|
@@ -5869,7 +6843,7 @@ class VegaRtcManager {
|
|
|
5869
6843
|
}
|
|
5870
6844
|
|
|
5871
6845
|
async _replaceWebcamTrack() {
|
|
5872
|
-
|
|
6846
|
+
logger.info("_replaceWebcamTrack()");
|
|
5873
6847
|
|
|
5874
6848
|
if (!this._webcamTrack || !this._webcamProducer || this._webcamProducer.closed) return;
|
|
5875
6849
|
|
|
@@ -5880,7 +6854,7 @@ class VegaRtcManager {
|
|
|
5880
6854
|
}
|
|
5881
6855
|
|
|
5882
6856
|
_pauseResumeWebcam() {
|
|
5883
|
-
|
|
6857
|
+
logger.info("_pauseResumeWebcam()");
|
|
5884
6858
|
|
|
5885
6859
|
if (!this._webcamProducer || this._webcamProducer.closed) return;
|
|
5886
6860
|
|
|
@@ -5904,7 +6878,7 @@ class VegaRtcManager {
|
|
|
5904
6878
|
}
|
|
5905
6879
|
|
|
5906
6880
|
async _sendWebcam(track) {
|
|
5907
|
-
|
|
6881
|
+
logger.info("_sendWebcam() [track:%o]", track);
|
|
5908
6882
|
|
|
5909
6883
|
this._webcamTrack = track;
|
|
5910
6884
|
|
|
@@ -5918,7 +6892,7 @@ class VegaRtcManager {
|
|
|
5918
6892
|
}
|
|
5919
6893
|
|
|
5920
6894
|
async _internalSendScreenVideo() {
|
|
5921
|
-
|
|
6895
|
+
logger.info("_internalSendScreenVideo()");
|
|
5922
6896
|
|
|
5923
6897
|
this._screenVideoProducerPromise = (async () => {
|
|
5924
6898
|
try {
|
|
@@ -5943,20 +6917,22 @@ class VegaRtcManager {
|
|
|
5943
6917
|
});
|
|
5944
6918
|
|
|
5945
6919
|
this._screenVideoProducer = producer;
|
|
6920
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
5946
6921
|
producer.observer.once("close", () => {
|
|
5947
|
-
|
|
6922
|
+
logger.info('screenVideoProducer "close" event');
|
|
5948
6923
|
|
|
5949
6924
|
if (producer.appData.localClosed)
|
|
5950
6925
|
this._vegaConnection?.message("closeProducers", { producerIds: [producer.id] });
|
|
5951
6926
|
|
|
5952
6927
|
this._screenVideoProducer = null;
|
|
5953
6928
|
this._screenVideoProducerPromise = null;
|
|
6929
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
5954
6930
|
});
|
|
5955
6931
|
|
|
5956
6932
|
// Has someone replaced the track?
|
|
5957
6933
|
if (this._screenVideoTrack !== this._screenVideoProducer.track) await this._replaceScreenVideoTrack();
|
|
5958
6934
|
} catch (error) {
|
|
5959
|
-
|
|
6935
|
+
logger.error("screenVideoProducer failed:%o", error);
|
|
5960
6936
|
} finally {
|
|
5961
6937
|
this._screenVideoProducerPromise = null;
|
|
5962
6938
|
|
|
@@ -5970,7 +6946,7 @@ class VegaRtcManager {
|
|
|
5970
6946
|
}
|
|
5971
6947
|
|
|
5972
6948
|
async _replaceScreenVideoTrack() {
|
|
5973
|
-
|
|
6949
|
+
logger.info("_replaceScreenVideoTrack()");
|
|
5974
6950
|
|
|
5975
6951
|
if (!this._screenVideoTrack || !this._screenVideoProducer || this._screenVideoProducer.closed) return;
|
|
5976
6952
|
|
|
@@ -5981,7 +6957,7 @@ class VegaRtcManager {
|
|
|
5981
6957
|
}
|
|
5982
6958
|
|
|
5983
6959
|
async _sendScreenVideo(track) {
|
|
5984
|
-
|
|
6960
|
+
logger.info("_sendScreenVideo() [track:%o]", track);
|
|
5985
6961
|
|
|
5986
6962
|
this._screenVideoTrack = track;
|
|
5987
6963
|
|
|
@@ -5995,7 +6971,7 @@ class VegaRtcManager {
|
|
|
5995
6971
|
}
|
|
5996
6972
|
|
|
5997
6973
|
async _internalSendScreenAudio() {
|
|
5998
|
-
|
|
6974
|
+
logger.info("_internalSendScreenAudio()");
|
|
5999
6975
|
|
|
6000
6976
|
this._screenAudioProducerPromise = (async () => {
|
|
6001
6977
|
try {
|
|
@@ -6020,20 +6996,22 @@ class VegaRtcManager {
|
|
|
6020
6996
|
});
|
|
6021
6997
|
|
|
6022
6998
|
this._screenAudioProducer = producer;
|
|
6999
|
+
this._qualityMonitor.addProducer(this._selfId, producer.id);
|
|
6023
7000
|
producer.observer.once("close", () => {
|
|
6024
|
-
|
|
7001
|
+
logger.info('screenAudioProducer "close" event');
|
|
6025
7002
|
|
|
6026
7003
|
if (producer.appData.localClosed)
|
|
6027
7004
|
this._vegaConnection?.message("closeProducers", { producerIds: [producer.id] });
|
|
6028
7005
|
|
|
6029
7006
|
this._screenAudioProducer = null;
|
|
6030
7007
|
this._screenAudioProducerPromise = null;
|
|
7008
|
+
this._qualityMonitor.removeProducer(this._selfId, producer.id);
|
|
6031
7009
|
});
|
|
6032
7010
|
|
|
6033
7011
|
// Has someone replaced the track?
|
|
6034
7012
|
if (this._screenAudioTrack !== this._screenAudioProducer.track) await this._replaceScreenAudioTrack();
|
|
6035
7013
|
} catch (error) {
|
|
6036
|
-
|
|
7014
|
+
logger.error("screenAudioProducer failed:%o", error);
|
|
6037
7015
|
} finally {
|
|
6038
7016
|
this._screenAudioProducerPromise = null;
|
|
6039
7017
|
|
|
@@ -6047,7 +7025,7 @@ class VegaRtcManager {
|
|
|
6047
7025
|
}
|
|
6048
7026
|
|
|
6049
7027
|
async _replaceScreenAudioTrack() {
|
|
6050
|
-
|
|
7028
|
+
logger.info("_replaceScreenAudioTrack()");
|
|
6051
7029
|
|
|
6052
7030
|
if (!this._screenAudioTrack || !this._screenAudioProducer || this._screenAudioProducer.closed) return;
|
|
6053
7031
|
|
|
@@ -6058,7 +7036,7 @@ class VegaRtcManager {
|
|
|
6058
7036
|
}
|
|
6059
7037
|
|
|
6060
7038
|
async _sendScreenAudio(track) {
|
|
6061
|
-
|
|
7039
|
+
logger.info("_sendScreenAudio() [track:%o]", track);
|
|
6062
7040
|
|
|
6063
7041
|
this._screenAudioTrack = track;
|
|
6064
7042
|
|
|
@@ -6072,7 +7050,7 @@ class VegaRtcManager {
|
|
|
6072
7050
|
}
|
|
6073
7051
|
|
|
6074
7052
|
_stopProducer(producer) {
|
|
6075
|
-
|
|
7053
|
+
logger.info("_stopProducer()");
|
|
6076
7054
|
|
|
6077
7055
|
if (!producer || producer.closed) return;
|
|
6078
7056
|
|
|
@@ -6127,6 +7105,18 @@ class VegaRtcManager {
|
|
|
6127
7105
|
rtcStats.sendEvent("colocation_changed", { colocation });
|
|
6128
7106
|
}
|
|
6129
7107
|
|
|
7108
|
+
/**
|
|
7109
|
+
* This sends a signal to the SFU to pause all incoming video streams to the client.
|
|
7110
|
+
*
|
|
7111
|
+
* @param {boolean} audioOnly
|
|
7112
|
+
*/
|
|
7113
|
+
setAudioOnly(audioOnly) {
|
|
7114
|
+
this._vegaConnection?.message(audioOnly ? "enableAudioOnly" : "disableAudioOnly");
|
|
7115
|
+
}
|
|
7116
|
+
|
|
7117
|
+
// the track ids send by signal server for remote-initiated screenshares
|
|
7118
|
+
setRemoteScreenshareVideoTrackIds(/*remoteScreenshareVideoTrackIds*/) {}
|
|
7119
|
+
|
|
6130
7120
|
/**
|
|
6131
7121
|
* The unique identifier for this room session.
|
|
6132
7122
|
*
|
|
@@ -6146,7 +7136,7 @@ class VegaRtcManager {
|
|
|
6146
7136
|
* @param {string} eventClaim
|
|
6147
7137
|
*/
|
|
6148
7138
|
disconnect(clientIdOrStreamId, _activeBreakout, eventClaim) {
|
|
6149
|
-
|
|
7139
|
+
logger.info("disconnect() [clientIdOrStreamId:%s, eventClaim:%s]", clientIdOrStreamId, eventClaim);
|
|
6150
7140
|
|
|
6151
7141
|
if (this._clientStates.has(clientIdOrStreamId)) {
|
|
6152
7142
|
// In this case this is a disconnect from an actual client, not just a screen share.
|
|
@@ -6204,7 +7194,7 @@ class VegaRtcManager {
|
|
|
6204
7194
|
* @param {string} requestedByClientId
|
|
6205
7195
|
*/
|
|
6206
7196
|
removeStream(streamId, _stream, requestedByClientId) {
|
|
6207
|
-
|
|
7197
|
+
logger.info("removeStream() [streamId:%s, requestedByClientId:%s]", streamId, requestedByClientId);
|
|
6208
7198
|
|
|
6209
7199
|
this._emitToSignal(PROTOCOL_REQUESTS.STOP_SCREENSHARE, {
|
|
6210
7200
|
streamId: OUTBOUND_SCREEN_OUTBOUND_STREAM_ID,
|
|
@@ -6308,7 +7298,7 @@ class VegaRtcManager {
|
|
|
6308
7298
|
* @param {boolean} enabled
|
|
6309
7299
|
*/
|
|
6310
7300
|
stopOrResumeAudio(stream, enabled) {
|
|
6311
|
-
|
|
7301
|
+
logger.info("stopOrResumeAudio() [enabled:%s]", enabled);
|
|
6312
7302
|
|
|
6313
7303
|
this._micPaused = !enabled;
|
|
6314
7304
|
|
|
@@ -6334,7 +7324,7 @@ class VegaRtcManager {
|
|
|
6334
7324
|
* @param {boolean} enabled
|
|
6335
7325
|
*/
|
|
6336
7326
|
stopOrResumeVideo(localStream, enable) {
|
|
6337
|
-
|
|
7327
|
+
logger.info("stopOrResumeVideo() [enable:%s]", enable);
|
|
6338
7328
|
|
|
6339
7329
|
this._webcamPaused = !enable;
|
|
6340
7330
|
|
|
@@ -6397,7 +7387,7 @@ class VegaRtcManager {
|
|
|
6397
7387
|
* }} streamOptions
|
|
6398
7388
|
*/
|
|
6399
7389
|
acceptNewStream({ streamId, clientId }) {
|
|
6400
|
-
|
|
7390
|
+
logger.info("acceptNewStream()", { streamId, clientId });
|
|
6401
7391
|
|
|
6402
7392
|
// make sure we have rtcStats connection
|
|
6403
7393
|
this.rtcStatsReconnect();
|
|
@@ -6428,7 +7418,7 @@ class VegaRtcManager {
|
|
|
6428
7418
|
* }} size
|
|
6429
7419
|
*/
|
|
6430
7420
|
updateStreamResolution(streamId, _ignored, { width, height }) {
|
|
6431
|
-
|
|
7421
|
+
logger.info("updateStreamResolution()", { streamId, width, height });
|
|
6432
7422
|
|
|
6433
7423
|
const consumerId = this._streamIdToVideoConsumerId.get(streamId);
|
|
6434
7424
|
const consumer = this._consumers.get(consumerId);
|
|
@@ -6561,18 +7551,22 @@ class VegaRtcManager {
|
|
|
6561
7551
|
return this._onDataConsumerClosed(data);
|
|
6562
7552
|
case "dominantSpeaker":
|
|
6563
7553
|
return this._onDominantSpeaker(data);
|
|
7554
|
+
case "consumerScore":
|
|
7555
|
+
return this._onConsumerScore(data);
|
|
7556
|
+
case "producerScore":
|
|
7557
|
+
return this._onProducerScore(data);
|
|
6564
7558
|
default:
|
|
6565
|
-
|
|
7559
|
+
logger.info(`unknown message method "${method}"`);
|
|
6566
7560
|
return;
|
|
6567
7561
|
}
|
|
6568
7562
|
})
|
|
6569
7563
|
.catch((error) => {
|
|
6570
|
-
|
|
7564
|
+
logger.error('"message" failed [error:%o]', error);
|
|
6571
7565
|
});
|
|
6572
7566
|
}
|
|
6573
7567
|
|
|
6574
7568
|
async _onConsumerReady(options) {
|
|
6575
|
-
|
|
7569
|
+
logger.info("_onConsumerReady()", { id: options.id, producerId: options.producerId });
|
|
6576
7570
|
|
|
6577
7571
|
const consumer = await this._receiveTransport.consume(options);
|
|
6578
7572
|
|
|
@@ -6581,8 +7575,10 @@ class VegaRtcManager {
|
|
|
6581
7575
|
consumer.appData.spatialLayer = 2;
|
|
6582
7576
|
|
|
6583
7577
|
this._consumers.set(consumer.id, consumer);
|
|
7578
|
+
this._qualityMonitor.addConsumer(consumer.appData.sourceClientId, consumer.id);
|
|
6584
7579
|
consumer.observer.once("close", () => {
|
|
6585
7580
|
this._consumers.delete(consumer.id);
|
|
7581
|
+
this._qualityMonitor.removeConsumer(consumer.appData.sourceClientId, consumer.id);
|
|
6586
7582
|
|
|
6587
7583
|
this._consumerClosedCleanup(consumer);
|
|
6588
7584
|
});
|
|
@@ -6593,7 +7589,7 @@ class VegaRtcManager {
|
|
|
6593
7589
|
// Legacy Chrome API
|
|
6594
7590
|
consumer.rtpReceiver.playoutDelayHint = MEDIA_JITTER_BUFFER_TARGET / 1000; // seconds
|
|
6595
7591
|
} catch (error) {
|
|
6596
|
-
|
|
7592
|
+
logger.error("Error during setting jitter buffer target:", error);
|
|
6597
7593
|
}
|
|
6598
7594
|
}
|
|
6599
7595
|
|
|
@@ -6626,13 +7622,13 @@ class VegaRtcManager {
|
|
|
6626
7622
|
}
|
|
6627
7623
|
|
|
6628
7624
|
async _onConsumerClosed({ consumerId, reason }) {
|
|
6629
|
-
|
|
7625
|
+
logger.info("_onConsumerClosed()", { consumerId, reason });
|
|
6630
7626
|
|
|
6631
7627
|
this._consumers.get(consumerId)?.close();
|
|
6632
7628
|
}
|
|
6633
7629
|
|
|
6634
7630
|
_onConsumerPaused({ consumerId }) {
|
|
6635
|
-
|
|
7631
|
+
logger.info("_onConsumerPaused()", { consumerId });
|
|
6636
7632
|
|
|
6637
7633
|
const consumer = this._consumers.get(consumerId);
|
|
6638
7634
|
|
|
@@ -6643,7 +7639,7 @@ class VegaRtcManager {
|
|
|
6643
7639
|
}
|
|
6644
7640
|
|
|
6645
7641
|
_onConsumerResumed({ consumerId }) {
|
|
6646
|
-
|
|
7642
|
+
logger.info("_onConsumerResumed()", { consumerId });
|
|
6647
7643
|
|
|
6648
7644
|
const consumer = this._consumers.get(consumerId);
|
|
6649
7645
|
|
|
@@ -6656,8 +7652,30 @@ class VegaRtcManager {
|
|
|
6656
7652
|
}
|
|
6657
7653
|
}
|
|
6658
7654
|
|
|
7655
|
+
_onConsumerScore({ consumerId, kind, score }) {
|
|
7656
|
+
logger.info("_onConsumerScore()", { consumerId, kind, score });
|
|
7657
|
+
const {
|
|
7658
|
+
appData: { sourceClientId },
|
|
7659
|
+
} = this._consumers.get(consumerId) || { appData: {} };
|
|
7660
|
+
|
|
7661
|
+
if (sourceClientId) {
|
|
7662
|
+
this._qualityMonitor.addConsumerScore(sourceClientId, consumerId, kind, score);
|
|
7663
|
+
}
|
|
7664
|
+
}
|
|
7665
|
+
|
|
7666
|
+
_onProducerScore({ producerId, kind, score }) {
|
|
7667
|
+
logger.info("_onProducerScore()", { producerId, kind, score });
|
|
7668
|
+
[this._micProducer, this._webcamProducer, this._screenVideoProducer, this._screenAudioProducer].forEach(
|
|
7669
|
+
(producer) => {
|
|
7670
|
+
if (producer?.id === producerId) {
|
|
7671
|
+
this._qualityMonitor.addProducerScore(this._selfId, producerId, kind, score);
|
|
7672
|
+
}
|
|
7673
|
+
}
|
|
7674
|
+
);
|
|
7675
|
+
}
|
|
7676
|
+
|
|
6659
7677
|
async _onDataConsumerReady(options) {
|
|
6660
|
-
|
|
7678
|
+
logger.info("_onDataConsumerReady()", { id: options.id, producerId: options.producerId });
|
|
6661
7679
|
const consumer = await this._receiveTransport.consumeData(options);
|
|
6662
7680
|
|
|
6663
7681
|
this._dataConsumers.set(consumer.id, consumer);
|
|
@@ -6683,7 +7701,7 @@ class VegaRtcManager {
|
|
|
6683
7701
|
}
|
|
6684
7702
|
|
|
6685
7703
|
async _onDataConsumerClosed({ dataConsumerId, reason }) {
|
|
6686
|
-
|
|
7704
|
+
logger.info("_onDataConsumerClosed()", { dataConsumerId, reason });
|
|
6687
7705
|
const consumer = this._dataConsumers.get(dataConsumerId);
|
|
6688
7706
|
consumer?.close();
|
|
6689
7707
|
}
|
|
@@ -6739,7 +7757,7 @@ class VegaRtcManager {
|
|
|
6739
7757
|
} = clientState;
|
|
6740
7758
|
|
|
6741
7759
|
// Need to pause/resume any consumers that are part of a stream that has been
|
|
6742
|
-
// accepted or
|
|
7760
|
+
// accepted or disconnected by the PWA
|
|
6743
7761
|
const toPauseConsumers = [];
|
|
6744
7762
|
const toResumeConsumers = [];
|
|
6745
7763
|
|
|
@@ -6841,6 +7859,10 @@ class VegaRtcManager {
|
|
|
6841
7859
|
setMicAnalyserParams(params) {
|
|
6842
7860
|
this._micAnalyser?.setParams(params);
|
|
6843
7861
|
}
|
|
7862
|
+
|
|
7863
|
+
hasClient(clientId) {
|
|
7864
|
+
return this._clientStates.has(clientId);
|
|
7865
|
+
}
|
|
6844
7866
|
}
|
|
6845
7867
|
|
|
6846
7868
|
class RtcManagerDispatcher {
|
|
@@ -6849,7 +7871,16 @@ class RtcManagerDispatcher {
|
|
|
6849
7871
|
this.currentManager = null;
|
|
6850
7872
|
serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room, selfId, error, eventClaim }) => {
|
|
6851
7873
|
if (error) return; // ignore error responses which lack room
|
|
6852
|
-
const config = {
|
|
7874
|
+
const config = {
|
|
7875
|
+
selfId,
|
|
7876
|
+
room,
|
|
7877
|
+
emitter,
|
|
7878
|
+
serverSocket,
|
|
7879
|
+
webrtcProvider,
|
|
7880
|
+
features,
|
|
7881
|
+
eventClaim,
|
|
7882
|
+
deviceHandlerFactory: features?.deviceHandlerFactory,
|
|
7883
|
+
};
|
|
6853
7884
|
const isSfu = !!room.sfuServer;
|
|
6854
7885
|
if (this.currentManager) {
|
|
6855
7886
|
if (this.currentManager.isInitializedWith({ selfId, roomName: room.name, isSfu })) {
|
|
@@ -6870,6 +7901,7 @@ class RtcManagerDispatcher {
|
|
|
6870
7901
|
rtcManager.setupSocketListeners();
|
|
6871
7902
|
emitter.emit(EVENTS.RTC_MANAGER_CREATED, { rtcManager });
|
|
6872
7903
|
this.currentManager = rtcManager;
|
|
7904
|
+
serverSocket.setRtcManager(rtcManager);
|
|
6873
7905
|
});
|
|
6874
7906
|
}
|
|
6875
7907
|
|
|
@@ -7794,7 +8826,7 @@ var localStorage$1 = localStorage;
|
|
|
7794
8826
|
const events = {
|
|
7795
8827
|
CREDENTIALS_SAVED: "credentials_saved",
|
|
7796
8828
|
};
|
|
7797
|
-
class CredentialsService extends EventEmitter
|
|
8829
|
+
class CredentialsService extends EventEmitter {
|
|
7798
8830
|
constructor({ deviceService, credentialsStore, }) {
|
|
7799
8831
|
super();
|
|
7800
8832
|
this._deviceService = deviceService;
|
|
@@ -8432,7 +9464,7 @@ class LocalParticipant extends RoomParticipant {
|
|
|
8432
9464
|
}
|
|
8433
9465
|
}
|
|
8434
9466
|
|
|
8435
|
-
const sdkVersion = "0.
|
|
9467
|
+
const sdkVersion = "0.3.0";
|
|
8436
9468
|
|
|
8437
9469
|
const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
|
|
8438
9470
|
const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
|