@whereby.com/core 0.2.0 → 0.4.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 +50 -8
- package/dist/index.js +1185 -1144
- package/package.json +4 -3
package/dist/index.js
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
import { createAsyncThunk, createListenerMiddleware, addListener, createSlice, createAction, createSelector, isAnyOf, combineReducers, configureStore } from '@reduxjs/toolkit';
|
|
2
2
|
import { io } from 'socket.io-client';
|
|
3
|
-
import
|
|
3
|
+
import adapterRaw from 'webrtc-adapter';
|
|
4
4
|
import EventEmitter, { EventEmitter as EventEmitter$1 } from 'events';
|
|
5
5
|
import { d as debounce } from './debounce-B-cWYxqK.js';
|
|
6
|
+
import { v4 as v4$1 } from 'uuid';
|
|
6
7
|
import SDPUtils from 'sdp';
|
|
7
8
|
import * as sdpTransform from 'sdp-transform';
|
|
8
|
-
import { v4 as v4$1 } from 'uuid';
|
|
9
9
|
import { Address6 } from 'ip-address';
|
|
10
10
|
import checkIp from 'check-ip';
|
|
11
11
|
import validate from 'uuid-validate';
|
|
12
12
|
import { detectDevice, Device } from 'mediasoup-client';
|
|
13
|
+
import { Chrome111 } from 'mediasoup-client/lib/handlers/Chrome111.js';
|
|
13
14
|
import nodeBtoa from 'btoa';
|
|
14
15
|
import axios from 'axios';
|
|
15
16
|
|
|
@@ -42,6 +43,7 @@ const createReactor = (selectors, callback) => {
|
|
|
42
43
|
};
|
|
43
44
|
|
|
44
45
|
const initialState$c = {
|
|
46
|
+
isNodeSdk: false,
|
|
45
47
|
wantsToJoin: false,
|
|
46
48
|
roomName: null,
|
|
47
49
|
roomKey: null,
|
|
@@ -75,6 +77,7 @@ const selectAppRoomKey = (state) => state.app.roomKey;
|
|
|
75
77
|
const selectAppDisplayName = (state) => state.app.displayName;
|
|
76
78
|
const selectAppSdkVersion = (state) => state.app.sdkVersion;
|
|
77
79
|
const selectAppExternalId = (state) => state.app.externalId;
|
|
80
|
+
const selectAppIsNodeSdk = (state) => state.app.isNodeSdk;
|
|
78
81
|
|
|
79
82
|
function createSignalEventAction(name) {
|
|
80
83
|
return createAction(`signalConnection/event/${name}`);
|
|
@@ -83,6 +86,7 @@ const signalEvents = {
|
|
|
83
86
|
audioEnabled: createSignalEventAction("audioEnabled"),
|
|
84
87
|
chatMessage: createSignalEventAction("chatMessage"),
|
|
85
88
|
clientLeft: createSignalEventAction("clientLeft"),
|
|
89
|
+
clientKicked: createSignalEventAction("clientKicked"),
|
|
86
90
|
clientMetadataReceived: createSignalEventAction("clientMetadataReceived"),
|
|
87
91
|
cloudRecordingStarted: createSignalEventAction("cloudRecordingStarted"),
|
|
88
92
|
cloudRecordingStopped: createSignalEventAction("cloudRecordingStopped"),
|
|
@@ -385,7 +389,7 @@ const PROTOCOL_EVENTS = {
|
|
|
385
389
|
MEDIA_QUALITY_CHANGED: "media_quality_changed",
|
|
386
390
|
};
|
|
387
391
|
|
|
388
|
-
const logger$
|
|
392
|
+
const logger$8 = new Logger();
|
|
389
393
|
|
|
390
394
|
class ReconnectManager extends EventEmitter {
|
|
391
395
|
constructor(socket) {
|
|
@@ -500,7 +504,7 @@ class ReconnectManager extends EventEmitter {
|
|
|
500
504
|
|
|
501
505
|
client.mergeWithOldClientState = true;
|
|
502
506
|
} catch (error) {
|
|
503
|
-
logger$
|
|
507
|
+
logger$8.error("Failed to evaluate if we should merge client state %o", error);
|
|
504
508
|
this.metrics.evaluationFailed++;
|
|
505
509
|
}
|
|
506
510
|
});
|
|
@@ -537,7 +541,7 @@ class ReconnectManager extends EventEmitter {
|
|
|
537
541
|
const client = this._clients[clientId];
|
|
538
542
|
|
|
539
543
|
if (!client) {
|
|
540
|
-
logger$
|
|
544
|
+
logger$8.warn(`client ${clientId} not found`);
|
|
541
545
|
return;
|
|
542
546
|
}
|
|
543
547
|
|
|
@@ -707,6 +711,8 @@ class ReconnectManager extends EventEmitter {
|
|
|
707
711
|
}
|
|
708
712
|
}
|
|
709
713
|
|
|
714
|
+
const adapter$6 = adapterRaw.default ?? adapterRaw;
|
|
715
|
+
|
|
710
716
|
const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
|
|
711
717
|
|
|
712
718
|
const NOOP_KEEPALIVE_INTERVAL = 2000;
|
|
@@ -736,7 +742,7 @@ class ServerSocket {
|
|
|
736
742
|
// safari doesn't support cross doamin cookies making load-balancer stickiness not work
|
|
737
743
|
// and if socket.io reconnects to another signal instance with polling it will fail
|
|
738
744
|
// remove if we move signal to a whereby.com subdomain
|
|
739
|
-
if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
|
|
745
|
+
if (adapter$6.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
|
|
740
746
|
} else {
|
|
741
747
|
this._socket.io.opts.transports = ["websocket", "polling"];
|
|
742
748
|
}
|
|
@@ -894,6 +900,7 @@ function forwardSocketEvents(socket, dispatch) {
|
|
|
894
900
|
socket.on("room_joined", (payload) => dispatch(signalEvents.roomJoined(payload)));
|
|
895
901
|
socket.on("new_client", (payload) => dispatch(signalEvents.newClient(payload)));
|
|
896
902
|
socket.on("client_left", (payload) => dispatch(signalEvents.clientLeft(payload)));
|
|
903
|
+
socket.on("client_kicked", (payload) => dispatch(signalEvents.clientKicked(payload)));
|
|
897
904
|
socket.on("audio_enabled", (payload) => dispatch(signalEvents.audioEnabled(payload)));
|
|
898
905
|
socket.on("video_enabled", (payload) => dispatch(signalEvents.videoEnabled(payload)));
|
|
899
906
|
socket.on("client_metadata_received", (payload) => dispatch(signalEvents.clientMetadataReceived(payload)));
|
|
@@ -1106,7 +1113,9 @@ const selectCloudRecordingStartedAt = (state) => state.cloudRecording.startedAt;
|
|
|
1106
1113
|
const selectCloudRecordingError = (state) => state.cloudRecording.error;
|
|
1107
1114
|
const selectIsCloudRecording = (state) => state.cloudRecording.isRecording;
|
|
1108
1115
|
|
|
1109
|
-
const
|
|
1116
|
+
const adapter$5 = adapterRaw.default ?? adapterRaw;
|
|
1117
|
+
|
|
1118
|
+
const isSafari = adapter$5.browserDetails.browser === "safari";
|
|
1110
1119
|
|
|
1111
1120
|
// Expects format 640x360@25, returns [width, height, fps]
|
|
1112
1121
|
const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10));
|
|
@@ -1117,7 +1126,6 @@ const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10))
|
|
|
1117
1126
|
function getMediaConstraints({
|
|
1118
1127
|
disableAEC,
|
|
1119
1128
|
disableAGC,
|
|
1120
|
-
fps24,
|
|
1121
1129
|
hd,
|
|
1122
1130
|
lax,
|
|
1123
1131
|
lowDataMode,
|
|
@@ -1128,7 +1136,6 @@ function getMediaConstraints({
|
|
|
1128
1136
|
}) {
|
|
1129
1137
|
let HIGH_HEIGHT = 480;
|
|
1130
1138
|
let LOW_HEIGHT = 240;
|
|
1131
|
-
let NON_STANDARD_FPS = 0;
|
|
1132
1139
|
|
|
1133
1140
|
if (hd) {
|
|
1134
1141
|
// respect user choice, but default to HD for pro, and SD for free
|
|
@@ -1143,18 +1150,14 @@ function getMediaConstraints({
|
|
|
1143
1150
|
}
|
|
1144
1151
|
}
|
|
1145
1152
|
|
|
1146
|
-
// Set framerate to 24 to increase quality/bandwidth
|
|
1147
|
-
if (fps24) NON_STANDARD_FPS = 24;
|
|
1148
|
-
|
|
1149
|
-
// Set framerate for low data, but only for non-simulcast
|
|
1150
|
-
if (lowDataMode && !simulcast) NON_STANDARD_FPS = 15;
|
|
1151
|
-
|
|
1152
1153
|
const constraints = {
|
|
1153
1154
|
audio: { ...(preferredDeviceIds.audioId && { deviceId: preferredDeviceIds.audioId }) },
|
|
1154
1155
|
video: {
|
|
1155
1156
|
...(preferredDeviceIds.videoId ? { deviceId: preferredDeviceIds.videoId } : { facingMode: "user" }),
|
|
1156
1157
|
height: lowDataMode ? LOW_HEIGHT : HIGH_HEIGHT,
|
|
1157
|
-
|
|
1158
|
+
// Set a lower frame rate (15fps) for low data, but only for non-simulcast.
|
|
1159
|
+
// Otherwise use 24fps to increase quality/bandwidth.
|
|
1160
|
+
frameRate: lowDataMode && !simulcast ? 15 : 24,
|
|
1158
1161
|
},
|
|
1159
1162
|
};
|
|
1160
1163
|
if (lax) {
|
|
@@ -1287,7 +1290,7 @@ var assert_1 = assert;
|
|
|
1287
1290
|
|
|
1288
1291
|
var assert$1 = /*@__PURE__*/getDefaultExportFromCjs(assert_1);
|
|
1289
1292
|
|
|
1290
|
-
const logger$
|
|
1293
|
+
const logger$7 = new Logger();
|
|
1291
1294
|
|
|
1292
1295
|
const isMobile = /mobi/i.test(navigator.userAgent);
|
|
1293
1296
|
|
|
@@ -1308,7 +1311,7 @@ function getUserMedia(constraints) {
|
|
|
1308
1311
|
|
|
1309
1312
|
return navigator.mediaDevices.getUserMedia(constraints).catch((error) => {
|
|
1310
1313
|
const message = `${error}, ${JSON.stringify(constraints, null, 2)}`;
|
|
1311
|
-
logger$
|
|
1314
|
+
logger$7.error(`getUserMedia ${message}`);
|
|
1312
1315
|
throw error;
|
|
1313
1316
|
});
|
|
1314
1317
|
}
|
|
@@ -1494,7 +1497,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
1494
1497
|
getConstraints({ ...constraintOpt, options: { ...constraintOpt.options, lax: true } })
|
|
1495
1498
|
);
|
|
1496
1499
|
} catch (e2) {
|
|
1497
|
-
logger$
|
|
1500
|
+
logger$7.warn(`Tried getting stream again with laxer constraints, but failed: ${"" + e2}`);
|
|
1498
1501
|
}
|
|
1499
1502
|
// Message often hints at which was the problem, let's use that
|
|
1500
1503
|
const errMsg = ("" + e).toLowerCase();
|
|
@@ -1503,7 +1506,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
1503
1506
|
try {
|
|
1504
1507
|
stream = await getUserMedia(getConstraints({ ...constraintOpt, [problemWith]: null }));
|
|
1505
1508
|
} catch (e2) {
|
|
1506
|
-
logger$
|
|
1509
|
+
logger$7.warn(`Re-tried ${problemWith} with no constraints, but failed: ${"" + e2}`);
|
|
1507
1510
|
}
|
|
1508
1511
|
}
|
|
1509
1512
|
if (!stream) {
|
|
@@ -1514,7 +1517,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
|
|
|
1514
1517
|
try {
|
|
1515
1518
|
stream = await getUserMedia(getConstraints({ ...constraintOpt, [kind]: false }));
|
|
1516
1519
|
} catch (e2) {
|
|
1517
|
-
logger$
|
|
1520
|
+
logger$7.warn(`Re-tried without ${kind}, but failed: ${"" + e2}`);
|
|
1518
1521
|
}
|
|
1519
1522
|
if (stream) break;
|
|
1520
1523
|
}
|
|
@@ -1970,8 +1973,8 @@ const selectIsLocalMediaStarting = createSelector(selectLocalMediaStatus, (statu
|
|
|
1970
1973
|
const selectCameraDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "videoinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
|
|
1971
1974
|
const selectMicrophoneDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "audioinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
|
|
1972
1975
|
const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) => devices.filter((d) => d.kind === "audiooutput"));
|
|
1973
|
-
const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
|
|
1974
|
-
if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
|
|
1976
|
+
const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, selectAppIsNodeSdk, (appWantsToJoin, localMediaStatus, localMediaOptions, isNodeSdk) => {
|
|
1977
|
+
if (appWantsToJoin && localMediaStatus === "" && !isNodeSdk && localMediaOptions) {
|
|
1975
1978
|
return localMediaOptions;
|
|
1976
1979
|
}
|
|
1977
1980
|
});
|
|
@@ -2505,6 +2508,9 @@ const roomConnectionSlice = createSlice({
|
|
|
2505
2508
|
}
|
|
2506
2509
|
return Object.assign(Object.assign({}, state), { session: null });
|
|
2507
2510
|
});
|
|
2511
|
+
builder.addCase(signalEvents.clientKicked, (state) => {
|
|
2512
|
+
return Object.assign(Object.assign({}, state), { status: "kicked" });
|
|
2513
|
+
});
|
|
2508
2514
|
builder.addCase(socketReconnecting, (state) => {
|
|
2509
2515
|
return Object.assign(Object.assign({}, state), { status: "reconnect" });
|
|
2510
2516
|
});
|
|
@@ -2576,8 +2582,14 @@ const selectRoomConnectionRaw = (state) => state.roomConnection;
|
|
|
2576
2582
|
const selectRoomConnectionSession = (state) => state.roomConnection.session;
|
|
2577
2583
|
const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
|
|
2578
2584
|
const selectRoomConnectionStatus = (state) => state.roomConnection.status;
|
|
2579
|
-
const selectShouldConnectRoom = createSelector([
|
|
2580
|
-
|
|
2585
|
+
const selectShouldConnectRoom = createSelector([
|
|
2586
|
+
selectOrganizationId,
|
|
2587
|
+
selectRoomConnectionStatus,
|
|
2588
|
+
selectSignalConnectionDeviceIdentified,
|
|
2589
|
+
selectLocalMediaStatus,
|
|
2590
|
+
selectAppIsNodeSdk,
|
|
2591
|
+
], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus, isNodeSdk) => {
|
|
2592
|
+
if ((localMediaStatus === "started" || isNodeSdk) &&
|
|
2581
2593
|
signalConnectionDeviceIdentified &&
|
|
2582
2594
|
!!hasOrganizationIdFetched &&
|
|
2583
2595
|
["initializing", "reconnect"].includes(roomConnectionStatus)) {
|
|
@@ -2609,161 +2621,594 @@ startAppListening({
|
|
|
2609
2621
|
},
|
|
2610
2622
|
});
|
|
2611
2623
|
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
};
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
CONNECTING: "connecting",
|
|
2625
|
-
CONNECTION_FAILED: "connection_failed",
|
|
2626
|
-
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
2627
|
-
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
2628
|
-
};
|
|
2629
|
-
|
|
2630
|
-
const CAMERA_STREAM_ID$2 = "0";
|
|
2631
|
-
|
|
2632
|
-
const STREAM_TYPES = {
|
|
2633
|
-
CAMERA: "camera",
|
|
2634
|
-
SCREEN_SHARE: "screen_share",
|
|
2635
|
-
};
|
|
2636
|
-
|
|
2637
|
-
class RtcStream {
|
|
2638
|
-
constructor(id, type) {
|
|
2639
|
-
assert$1.notEqual(id, undefined, "id is required");
|
|
2640
|
-
assert$1.notEqual(type, undefined, "type is required");
|
|
2641
|
-
|
|
2642
|
-
this.id = "" + id;
|
|
2643
|
-
this.type = type;
|
|
2644
|
-
|
|
2645
|
-
this.isEnabled = true;
|
|
2646
|
-
this.hasSupportForAutoSuperSize = false;
|
|
2647
|
-
this.isAudioEnabled = true;
|
|
2648
|
-
this.isVideoEnabled = true;
|
|
2649
|
-
this.status = TYPES.CONNECTING;
|
|
2650
|
-
}
|
|
2624
|
+
// transforms a maplike to an object. Mostly for getStats +
|
|
2625
|
+
// JSON.parse(JSON.stringify())
|
|
2626
|
+
function map2obj(m) {
|
|
2627
|
+
if (!m.entries) {
|
|
2628
|
+
return m;
|
|
2629
|
+
}
|
|
2630
|
+
var o = {};
|
|
2631
|
+
m.forEach(function(v, k) {
|
|
2632
|
+
o[k] = v;
|
|
2633
|
+
});
|
|
2634
|
+
return o;
|
|
2635
|
+
}
|
|
2651
2636
|
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2637
|
+
// apply a delta compression to the stats report. Reduces size by ~90%.
|
|
2638
|
+
// To reduce further, report keys could be compressed.
|
|
2639
|
+
function deltaCompression(oldStats, newStats) {
|
|
2640
|
+
newStats = JSON.parse(JSON.stringify(newStats));
|
|
2641
|
+
Object.keys(newStats).forEach(function(id) {
|
|
2642
|
+
var report = newStats[id];
|
|
2643
|
+
delete report.id;
|
|
2644
|
+
if (!oldStats[id]) {
|
|
2645
|
+
return;
|
|
2658
2646
|
}
|
|
2647
|
+
Object.keys(report).forEach(function(name) {
|
|
2648
|
+
if (report[name] === oldStats[id][name]) {
|
|
2649
|
+
delete newStats[id][name];
|
|
2650
|
+
}
|
|
2651
|
+
if (Object.keys(report).length === 0) {
|
|
2652
|
+
delete newStats[id];
|
|
2653
|
+
} else if (Object.keys(report).length === 1 && report.timestamp) {
|
|
2654
|
+
delete newStats[id];
|
|
2655
|
+
}
|
|
2656
|
+
});
|
|
2657
|
+
});
|
|
2659
2658
|
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2659
|
+
var timestamp = -Infinity;
|
|
2660
|
+
Object.keys(newStats).forEach(function(id) {
|
|
2661
|
+
var report = newStats[id];
|
|
2662
|
+
if (report.timestamp > timestamp) {
|
|
2663
|
+
timestamp = report.timestamp;
|
|
2663
2664
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
}
|
|
2670
|
-
this.stream.getVideoTracks().forEach((track) => {
|
|
2671
|
-
track.enabled = isEnabled;
|
|
2672
|
-
});
|
|
2665
|
+
});
|
|
2666
|
+
Object.keys(newStats).forEach(function(id) {
|
|
2667
|
+
var report = newStats[id];
|
|
2668
|
+
if (report.timestamp === timestamp) {
|
|
2669
|
+
report.timestamp = 0;
|
|
2673
2670
|
}
|
|
2671
|
+
});
|
|
2672
|
+
newStats.timestamp = timestamp;
|
|
2673
|
+
return newStats;
|
|
2674
|
+
}
|
|
2674
2675
|
|
|
2675
|
-
|
|
2676
|
-
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
|
|
2681
|
-
|
|
2682
|
-
|
|
2683
|
-
|
|
2676
|
+
function dumpStream(stream) {
|
|
2677
|
+
return {
|
|
2678
|
+
id: stream.id,
|
|
2679
|
+
tracks: stream.getTracks().map(function(track) {
|
|
2680
|
+
return {
|
|
2681
|
+
id: track.id, // unique identifier (GUID) for the track
|
|
2682
|
+
kind: track.kind, // `audio` or `video`
|
|
2683
|
+
label: track.label, // identified the track source
|
|
2684
|
+
enabled: track.enabled, // application can control it
|
|
2685
|
+
muted: track.muted, // application cannot control it (read-only)
|
|
2686
|
+
readyState: track.readyState, // `live` or `ended`
|
|
2687
|
+
};
|
|
2688
|
+
}),
|
|
2689
|
+
};
|
|
2690
|
+
}
|
|
2684
2691
|
|
|
2685
|
-
|
|
2686
|
-
|
|
2692
|
+
/*
|
|
2693
|
+
function filterBoringStats(results) {
|
|
2694
|
+
Object.keys(results).forEach(function(id) {
|
|
2695
|
+
switch (results[id].type) {
|
|
2696
|
+
case 'certificate':
|
|
2697
|
+
case 'codec':
|
|
2698
|
+
delete results[id];
|
|
2699
|
+
break;
|
|
2700
|
+
default:
|
|
2701
|
+
// noop
|
|
2687
2702
|
}
|
|
2703
|
+
});
|
|
2704
|
+
return results;
|
|
2705
|
+
}
|
|
2688
2706
|
|
|
2689
|
-
|
|
2690
|
-
|
|
2691
|
-
|
|
2692
|
-
|
|
2693
|
-
|
|
2694
|
-
|
|
2707
|
+
function removeTimestamps(results) {
|
|
2708
|
+
// FIXME: does not work in FF since the timestamp can't be deleted.
|
|
2709
|
+
Object.keys(results).forEach(function(id) {
|
|
2710
|
+
delete results[id].timestamp;
|
|
2711
|
+
});
|
|
2712
|
+
return results;
|
|
2695
2713
|
}
|
|
2714
|
+
*/
|
|
2696
2715
|
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
if (
|
|
2707
|
-
adapter.browserDetails.browser !== "chrome" ||
|
|
2708
|
-
adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
|
|
2709
|
-
pc.signalingState === "closed"
|
|
2710
|
-
) {
|
|
2711
|
-
return Promise.resolve(false);
|
|
2716
|
+
var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
|
|
2717
|
+
var peerconnectioncounter = 0;
|
|
2718
|
+
var isFirefox = !!window.mozRTCPeerConnection;
|
|
2719
|
+
var isEdge = !!window.RTCIceGatherer;
|
|
2720
|
+
var prevById = {};
|
|
2721
|
+
|
|
2722
|
+
prefixesToWrap.forEach(function(prefix) {
|
|
2723
|
+
if (!window[prefix + 'RTCPeerConnection']) {
|
|
2724
|
+
return;
|
|
2712
2725
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
|
|
2716
|
-
|
|
2717
|
-
|
|
2718
|
-
|
|
2719
|
-
|
|
2720
|
-
|
|
2721
|
-
|
|
2722
|
-
) {
|
|
2723
|
-
if (report.bytesSent === 0) {
|
|
2724
|
-
microphoneFailed = "outbound";
|
|
2725
|
-
}
|
|
2726
|
-
} else if (
|
|
2727
|
-
report.type === "inbound-rtp" &&
|
|
2728
|
-
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
2729
|
-
receivingAudio
|
|
2730
|
-
) {
|
|
2731
|
-
if (report.bytesReceived === 0) {
|
|
2732
|
-
microphoneFailed = "inbound";
|
|
2733
|
-
}
|
|
2734
|
-
}
|
|
2735
|
-
});
|
|
2736
|
-
return microphoneFailed;
|
|
2737
|
-
});
|
|
2738
|
-
}
|
|
2726
|
+
if (prefix === 'webkit' && isEdge) {
|
|
2727
|
+
// dont wrap webkitRTCPeerconnection in Edge.
|
|
2728
|
+
return;
|
|
2729
|
+
}
|
|
2730
|
+
var origPeerConnection = window[prefix + 'RTCPeerConnection'];
|
|
2731
|
+
var peerconnection = function(config, constraints) {
|
|
2732
|
+
var pc = new origPeerConnection(config, constraints);
|
|
2733
|
+
var id = 'PC_' + peerconnectioncounter++;
|
|
2734
|
+
pc.__rtcStatsId = id;
|
|
2739
2735
|
|
|
2740
|
-
|
|
2741
|
-
|
|
2742
|
-
|
|
2743
|
-
ICE_IPV6_SEEN: "ice_ipv6_seen",
|
|
2744
|
-
ICE_MDNS_SEEN: "ice_mdns_seen",
|
|
2745
|
-
ICE_NO_PUBLIC_IP_GATHERED: "ice_no_public_ip_gathered",
|
|
2746
|
-
ICE_NO_PUBLIC_IP_GATHERED_3SEC: "ice_no_public_ip_gathered_3sec",
|
|
2747
|
-
ICE_RESTART: "ice_restart",
|
|
2748
|
-
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
2749
|
-
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
2750
|
-
NEW_PC: "new_pc",
|
|
2751
|
-
SFU_CONNECTION_OPEN: "sfu_connection_open",
|
|
2752
|
-
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
2753
|
-
COLOCATION_SPEAKER: "colocation_speaker",
|
|
2754
|
-
DOMINANT_SPEAKER: "dominant_speaker",
|
|
2755
|
-
};
|
|
2736
|
+
if (!config) {
|
|
2737
|
+
config = { nullConfig: true };
|
|
2738
|
+
}
|
|
2756
2739
|
|
|
2757
|
-
|
|
2740
|
+
config = JSON.parse(JSON.stringify(config)); // deepcopy
|
|
2741
|
+
// don't log credentials
|
|
2742
|
+
((config && config.iceServers) || []).forEach(function(server) {
|
|
2743
|
+
delete server.credential;
|
|
2744
|
+
});
|
|
2758
2745
|
|
|
2759
|
-
|
|
2760
|
-
|
|
2746
|
+
if (isFirefox) {
|
|
2747
|
+
config.browserType = 'moz';
|
|
2748
|
+
} else if (isEdge) {
|
|
2749
|
+
config.browserType = 'edge';
|
|
2750
|
+
} else {
|
|
2751
|
+
config.browserType = 'webkit';
|
|
2752
|
+
}
|
|
2761
2753
|
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
|
|
2765
|
-
|
|
2766
|
-
|
|
2754
|
+
trace('create', id, config);
|
|
2755
|
+
// TODO: do we want to log constraints here? They are chrome-proprietary.
|
|
2756
|
+
// http://stackoverflow.com/questions/31003928/what-do-each-of-these-experimental-goog-rtcpeerconnectionconstraints-do
|
|
2757
|
+
if (constraints) {
|
|
2758
|
+
trace('constraints', id, constraints);
|
|
2759
|
+
}
|
|
2760
|
+
|
|
2761
|
+
pc.addEventListener('icecandidate', function(e) {
|
|
2762
|
+
trace('onicecandidate', id, e.candidate);
|
|
2763
|
+
});
|
|
2764
|
+
pc.addEventListener('addstream', function(e) {
|
|
2765
|
+
trace('onaddstream', id, e.stream.id + ' ' + e.stream.getTracks().map(function(t) { return t.kind + ':' + t.id; }));
|
|
2766
|
+
});
|
|
2767
|
+
pc.addEventListener('track', function(e) {
|
|
2768
|
+
trace('ontrack', id, e.track.kind + ':' + e.track.id + ' ' + e.streams.map(function(stream) { return 'stream:' + stream.id; }));
|
|
2769
|
+
});
|
|
2770
|
+
pc.addEventListener('removestream', function(e) {
|
|
2771
|
+
trace('onremovestream', id, e.stream.id + ' ' + e.stream.getTracks().map(function(t) { return t.kind + ':' + t.id; }));
|
|
2772
|
+
});
|
|
2773
|
+
pc.addEventListener('signalingstatechange', function() {
|
|
2774
|
+
trace('onsignalingstatechange', id, pc.signalingState);
|
|
2775
|
+
});
|
|
2776
|
+
pc.addEventListener('iceconnectionstatechange', function() {
|
|
2777
|
+
trace('oniceconnectionstatechange', id, pc.iceConnectionState);
|
|
2778
|
+
});
|
|
2779
|
+
pc.addEventListener('icegatheringstatechange', function() {
|
|
2780
|
+
trace('onicegatheringstatechange', id, pc.iceGatheringState);
|
|
2781
|
+
});
|
|
2782
|
+
pc.addEventListener('connectionstatechange', function() {
|
|
2783
|
+
trace('onconnectionstatechange', id, pc.connectionState);
|
|
2784
|
+
});
|
|
2785
|
+
pc.addEventListener('negotiationneeded', function() {
|
|
2786
|
+
trace('onnegotiationneeded', id, undefined);
|
|
2787
|
+
});
|
|
2788
|
+
pc.addEventListener('datachannel', function(event) {
|
|
2789
|
+
trace('ondatachannel', id, [event.channel.id, event.channel.label]);
|
|
2790
|
+
});
|
|
2791
|
+
|
|
2792
|
+
var getStats = function() {
|
|
2793
|
+
pc.getStats(null).then(function(res) {
|
|
2794
|
+
var now = map2obj(res);
|
|
2795
|
+
var base = JSON.parse(JSON.stringify(now)); // our new prev
|
|
2796
|
+
trace('getstats', id, deltaCompression(prevById[id] || {}, now));
|
|
2797
|
+
prevById[id] = base;
|
|
2798
|
+
});
|
|
2799
|
+
};
|
|
2800
|
+
// TODO: do we want one big interval and all peerconnections
|
|
2801
|
+
// queried in that or one setInterval per PC?
|
|
2802
|
+
// we have to collect results anyway so...
|
|
2803
|
+
if (!isEdge && getStatsInterval) {
|
|
2804
|
+
var interval = window.setInterval(function() {
|
|
2805
|
+
if (pc.signalingState === 'closed') {
|
|
2806
|
+
window.clearInterval(interval);
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
getStats();
|
|
2810
|
+
}, getStatsInterval);
|
|
2811
|
+
}
|
|
2812
|
+
if (!isEdge) {
|
|
2813
|
+
pc.addEventListener('connectionstatechange', function() {
|
|
2814
|
+
if (pc.connectionState === 'connected' || pc.connectionState === 'failed') {
|
|
2815
|
+
getStats();
|
|
2816
|
+
}
|
|
2817
|
+
});
|
|
2818
|
+
}
|
|
2819
|
+
return pc;
|
|
2820
|
+
};
|
|
2821
|
+
|
|
2822
|
+
['createDataChannel', 'close'].forEach(function(method) {
|
|
2823
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2824
|
+
if (nativeMethod) {
|
|
2825
|
+
origPeerConnection.prototype[method] = function() {
|
|
2826
|
+
trace(method, this.__rtcStatsId, arguments);
|
|
2827
|
+
return nativeMethod.apply(this, arguments);
|
|
2828
|
+
};
|
|
2829
|
+
}
|
|
2830
|
+
});
|
|
2831
|
+
|
|
2832
|
+
['addStream', 'removeStream'].forEach(function(method) {
|
|
2833
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2834
|
+
if (nativeMethod) {
|
|
2835
|
+
origPeerConnection.prototype[method] = function() {
|
|
2836
|
+
var stream = arguments[0];
|
|
2837
|
+
var streamInfo = stream.getTracks().map(function(t) {
|
|
2838
|
+
return t.kind + ':' + t.id;
|
|
2839
|
+
}).join(',');
|
|
2840
|
+
|
|
2841
|
+
trace(method, this.__rtcStatsId, stream.id + ' ' + streamInfo);
|
|
2842
|
+
return nativeMethod.apply(this, arguments);
|
|
2843
|
+
};
|
|
2844
|
+
}
|
|
2845
|
+
});
|
|
2846
|
+
|
|
2847
|
+
['addTrack'].forEach(function(method) {
|
|
2848
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2849
|
+
if (nativeMethod) {
|
|
2850
|
+
origPeerConnection.prototype[method] = function() {
|
|
2851
|
+
var track = arguments[0];
|
|
2852
|
+
var streams = [].slice.call(arguments, 1);
|
|
2853
|
+
trace(method, this.__rtcStatsId, track.kind + ':' + track.id + ' ' + (streams.map(function(s) { return 'stream:' + s.id; }).join(';') || '-'));
|
|
2854
|
+
return nativeMethod.apply(this, arguments);
|
|
2855
|
+
};
|
|
2856
|
+
}
|
|
2857
|
+
});
|
|
2858
|
+
|
|
2859
|
+
['removeTrack'].forEach(function(method) {
|
|
2860
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2861
|
+
if (nativeMethod) {
|
|
2862
|
+
origPeerConnection.prototype[method] = function() {
|
|
2863
|
+
var track = arguments[0].track;
|
|
2864
|
+
trace(method, this.__rtcStatsId, track ? track.kind + ':' + track.id : 'null');
|
|
2865
|
+
return nativeMethod.apply(this, arguments);
|
|
2866
|
+
};
|
|
2867
|
+
}
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
['createOffer', 'createAnswer'].forEach(function(method) {
|
|
2871
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2872
|
+
if (nativeMethod) {
|
|
2873
|
+
origPeerConnection.prototype[method] = function() {
|
|
2874
|
+
var rtcStatsId = this.__rtcStatsId;
|
|
2875
|
+
var args = arguments;
|
|
2876
|
+
var opts;
|
|
2877
|
+
if (arguments.length === 1 && typeof arguments[0] === 'object') {
|
|
2878
|
+
opts = arguments[0];
|
|
2879
|
+
} else if (arguments.length === 3 && typeof arguments[2] === 'object') {
|
|
2880
|
+
opts = arguments[2];
|
|
2881
|
+
}
|
|
2882
|
+
trace(method, this.__rtcStatsId, opts);
|
|
2883
|
+
return nativeMethod.apply(this, opts ? [opts] : undefined)
|
|
2884
|
+
.then(function(description) {
|
|
2885
|
+
trace(method + 'OnSuccess', rtcStatsId, description);
|
|
2886
|
+
if (args.length > 0 && typeof args[0] === 'function') {
|
|
2887
|
+
args[0].apply(null, [description]);
|
|
2888
|
+
return undefined;
|
|
2889
|
+
}
|
|
2890
|
+
return description;
|
|
2891
|
+
}, function(err) {
|
|
2892
|
+
trace(method + 'OnFailure', rtcStatsId, err.toString());
|
|
2893
|
+
if (args.length > 1 && typeof args[1] === 'function') {
|
|
2894
|
+
args[1].apply(null, [err]);
|
|
2895
|
+
return;
|
|
2896
|
+
}
|
|
2897
|
+
throw err;
|
|
2898
|
+
});
|
|
2899
|
+
};
|
|
2900
|
+
}
|
|
2901
|
+
});
|
|
2902
|
+
|
|
2903
|
+
['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function(method) {
|
|
2904
|
+
var nativeMethod = origPeerConnection.prototype[method];
|
|
2905
|
+
if (nativeMethod) {
|
|
2906
|
+
origPeerConnection.prototype[method] = function() {
|
|
2907
|
+
var rtcStatsId = this.__rtcStatsId;
|
|
2908
|
+
var args = arguments;
|
|
2909
|
+
trace(method, this.__rtcStatsId, args[0]);
|
|
2910
|
+
return nativeMethod.apply(this, [args[0]])
|
|
2911
|
+
.then(function() {
|
|
2912
|
+
trace(method + 'OnSuccess', rtcStatsId, undefined);
|
|
2913
|
+
if (args.length >= 2 && typeof args[1] === 'function') {
|
|
2914
|
+
args[1].apply(null, []);
|
|
2915
|
+
return undefined;
|
|
2916
|
+
}
|
|
2917
|
+
return undefined;
|
|
2918
|
+
}, function(err) {
|
|
2919
|
+
trace(method + 'OnFailure', rtcStatsId, err.toString());
|
|
2920
|
+
if (args.length >= 3 && typeof args[2] === 'function') {
|
|
2921
|
+
args[2].apply(null, [err]);
|
|
2922
|
+
return undefined;
|
|
2923
|
+
}
|
|
2924
|
+
throw err;
|
|
2925
|
+
});
|
|
2926
|
+
};
|
|
2927
|
+
}
|
|
2928
|
+
});
|
|
2929
|
+
|
|
2930
|
+
// wrap static methods. Currently just generateCertificate.
|
|
2931
|
+
if (origPeerConnection.generateCertificate) {
|
|
2932
|
+
Object.defineProperty(peerconnection, 'generateCertificate', {
|
|
2933
|
+
get: function() {
|
|
2934
|
+
return arguments.length ?
|
|
2935
|
+
origPeerConnection.generateCertificate.apply(null, arguments)
|
|
2936
|
+
: origPeerConnection.generateCertificate;
|
|
2937
|
+
},
|
|
2938
|
+
});
|
|
2939
|
+
}
|
|
2940
|
+
window[prefix + 'RTCPeerConnection'] = peerconnection;
|
|
2941
|
+
window[prefix + 'RTCPeerConnection'].prototype = origPeerConnection.prototype;
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
// getUserMedia wrappers
|
|
2945
|
+
prefixesToWrap.forEach(function(prefix) {
|
|
2946
|
+
var name = prefix + (prefix.length ? 'GetUserMedia' : 'getUserMedia');
|
|
2947
|
+
if (!navigator[name]) {
|
|
2948
|
+
return;
|
|
2949
|
+
}
|
|
2950
|
+
var origGetUserMedia = navigator[name].bind(navigator);
|
|
2951
|
+
var gum = function() {
|
|
2952
|
+
trace('getUserMedia', null, arguments[0]);
|
|
2953
|
+
var cb = arguments[1];
|
|
2954
|
+
var eb = arguments[2];
|
|
2955
|
+
origGetUserMedia(arguments[0],
|
|
2956
|
+
function(stream) {
|
|
2957
|
+
// we log the stream id, track ids and tracks readystate since that is ended GUM fails
|
|
2958
|
+
// to acquire the cam (in chrome)
|
|
2959
|
+
trace('getUserMediaOnSuccess', null, dumpStream(stream));
|
|
2960
|
+
if (cb) {
|
|
2961
|
+
cb(stream);
|
|
2962
|
+
}
|
|
2963
|
+
},
|
|
2964
|
+
function(err) {
|
|
2965
|
+
trace('getUserMediaOnFailure', null, err.name);
|
|
2966
|
+
if (eb) {
|
|
2967
|
+
eb(err);
|
|
2968
|
+
}
|
|
2969
|
+
}
|
|
2970
|
+
);
|
|
2971
|
+
};
|
|
2972
|
+
navigator[name] = gum.bind(navigator);
|
|
2973
|
+
});
|
|
2974
|
+
|
|
2975
|
+
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
|
2976
|
+
var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
|
|
2977
|
+
var gum = function() {
|
|
2978
|
+
trace('navigator.mediaDevices.getUserMedia', null, arguments[0]);
|
|
2979
|
+
return origGetUserMedia.apply(navigator.mediaDevices, arguments)
|
|
2980
|
+
.then(function(stream) {
|
|
2981
|
+
trace('navigator.mediaDevices.getUserMediaOnSuccess', null, dumpStream(stream));
|
|
2982
|
+
return stream;
|
|
2983
|
+
}, function(err) {
|
|
2984
|
+
trace('navigator.mediaDevices.getUserMediaOnFailure', null, err.name);
|
|
2985
|
+
return Promise.reject(err);
|
|
2986
|
+
});
|
|
2987
|
+
};
|
|
2988
|
+
navigator.mediaDevices.getUserMedia = gum.bind(navigator.mediaDevices);
|
|
2989
|
+
}
|
|
2990
|
+
|
|
2991
|
+
// getDisplayMedia
|
|
2992
|
+
if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
|
|
2993
|
+
var origGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
|
|
2994
|
+
var gdm = function() {
|
|
2995
|
+
trace('navigator.mediaDevices.getDisplayMedia', null, arguments[0]);
|
|
2996
|
+
return origGetDisplayMedia.apply(navigator.mediaDevices, arguments)
|
|
2997
|
+
.then(function(stream) {
|
|
2998
|
+
trace('navigator.mediaDevices.getDisplayMediaOnSuccess', null, dumpStream(stream));
|
|
2999
|
+
return stream;
|
|
3000
|
+
}, function(err) {
|
|
3001
|
+
trace('navigator.mediaDevices.getDisplayMediaOnFailure', null, err.name);
|
|
3002
|
+
return Promise.reject(err);
|
|
3003
|
+
});
|
|
3004
|
+
};
|
|
3005
|
+
navigator.mediaDevices.getDisplayMedia = gdm.bind(navigator.mediaDevices);
|
|
3006
|
+
}
|
|
3007
|
+
|
|
3008
|
+
// TODO: are there events defined on MST that would allow us to listen when enabled was set?
|
|
3009
|
+
// no :-(
|
|
3010
|
+
/*
|
|
3011
|
+
Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
|
|
3012
|
+
set: function(value) {
|
|
3013
|
+
trace('MediaStreamTrackEnable', this, value);
|
|
3014
|
+
}
|
|
3015
|
+
});
|
|
3016
|
+
*/
|
|
3017
|
+
|
|
3018
|
+
return {
|
|
3019
|
+
resetDelta() {
|
|
3020
|
+
prevById = {};
|
|
3021
|
+
}
|
|
3022
|
+
}
|
|
3023
|
+
|
|
3024
|
+
};
|
|
3025
|
+
|
|
3026
|
+
var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
|
|
3027
|
+
|
|
3028
|
+
// ensure adapter is loaded first.
|
|
3029
|
+
|
|
3030
|
+
adapterRaw.default ?? adapterRaw; // eslint-disable-line no-unused-vars
|
|
3031
|
+
|
|
3032
|
+
const RTCSTATS_PROTOCOL_VERSION = "1.0";
|
|
3033
|
+
|
|
3034
|
+
// when not connected we need to buffer at least a few getstats reports
|
|
3035
|
+
// as they are delta compressed and we need the initial properties
|
|
3036
|
+
const GETSTATS_BUFFER_SIZE = 20;
|
|
3037
|
+
|
|
3038
|
+
const clientInfo = {
|
|
3039
|
+
id: v4$1(), // shared id across rtcstats reconnects
|
|
3040
|
+
connectionNumber: 0,
|
|
3041
|
+
};
|
|
3042
|
+
|
|
3043
|
+
const noop = () => {};
|
|
3044
|
+
let resetDelta = noop;
|
|
3045
|
+
|
|
3046
|
+
// Inlined version of rtcstats/trace-ws with improved disconnect handling.
|
|
3047
|
+
function rtcStatsConnection(wsURL, logger = console) {
|
|
3048
|
+
const buffer = [];
|
|
3049
|
+
let ws;
|
|
3050
|
+
let organizationId;
|
|
3051
|
+
let clientId;
|
|
3052
|
+
let displayName;
|
|
3053
|
+
let userRole;
|
|
3054
|
+
let roomSessionId;
|
|
3055
|
+
let connectionShouldBeOpen;
|
|
3056
|
+
let connectionAttempt = 0;
|
|
3057
|
+
let hasPassedOnRoomSessionId = false;
|
|
3058
|
+
let getStatsBufferUsed = 0;
|
|
3059
|
+
|
|
3060
|
+
const connection = {
|
|
3061
|
+
connected: false,
|
|
3062
|
+
trace: (...args) => {
|
|
3063
|
+
args.push(Date.now());
|
|
3064
|
+
|
|
3065
|
+
if (args[0] === "customEvent" && args[2].type === "roomSessionId") {
|
|
3066
|
+
const oldRoomSessionIdValue = roomSessionId && roomSessionId[2].value.roomSessionId;
|
|
3067
|
+
const newRoomSessionIdValue = args[2].value.roomSessionId;
|
|
3068
|
+
roomSessionId = args;
|
|
3069
|
+
|
|
3070
|
+
if (
|
|
3071
|
+
hasPassedOnRoomSessionId &&
|
|
3072
|
+
newRoomSessionIdValue &&
|
|
3073
|
+
newRoomSessionIdValue !== oldRoomSessionIdValue
|
|
3074
|
+
) {
|
|
3075
|
+
// roomSessionId was already sent. It may have been reset to null, but anything after should be part of the same
|
|
3076
|
+
// session. Now it is something else, and we force a reconnect to start a new session
|
|
3077
|
+
if (ws) {
|
|
3078
|
+
ws.close();
|
|
3079
|
+
return;
|
|
3080
|
+
}
|
|
3081
|
+
}
|
|
3082
|
+
if (newRoomSessionIdValue) hasPassedOnRoomSessionId = true;
|
|
3083
|
+
} else if (args[0] === "customEvent" && args[2].type === "clientId") {
|
|
3084
|
+
clientId = args;
|
|
3085
|
+
} else if (args[0] === "customEvent" && args[2].type === "organizationId") {
|
|
3086
|
+
organizationId = args;
|
|
3087
|
+
} else if (args[0] === "customEvent" && args[2].type === "displayName") {
|
|
3088
|
+
displayName = args;
|
|
3089
|
+
} else if (args[0] === "customEvent" && args[2].type === "userRole") {
|
|
3090
|
+
userRole = args;
|
|
3091
|
+
}
|
|
3092
|
+
|
|
3093
|
+
if (ws.readyState === WebSocket.OPEN) {
|
|
3094
|
+
connectionAttempt = 0;
|
|
3095
|
+
ws.send(JSON.stringify(args));
|
|
3096
|
+
} else if (args[0] === "getstats") {
|
|
3097
|
+
// only buffer getStats for a while
|
|
3098
|
+
// we don't want this to pile up, but we need at least the initial reports
|
|
3099
|
+
if (getStatsBufferUsed < GETSTATS_BUFFER_SIZE) {
|
|
3100
|
+
getStatsBufferUsed++;
|
|
3101
|
+
buffer.push(args);
|
|
3102
|
+
}
|
|
3103
|
+
} else if (args[0] === "customEvent" && args[2].type === "insightsStats") ; else {
|
|
3104
|
+
// buffer everything else
|
|
3105
|
+
buffer.push(args);
|
|
3106
|
+
}
|
|
3107
|
+
|
|
3108
|
+
// reconnect when closed by anything else than client
|
|
3109
|
+
if (ws.readyState === WebSocket.CLOSED && connectionShouldBeOpen) {
|
|
3110
|
+
setTimeout(() => {
|
|
3111
|
+
if (ws.readyState === WebSocket.CLOSED && connectionShouldBeOpen) {
|
|
3112
|
+
connection.connect();
|
|
3113
|
+
}
|
|
3114
|
+
}, 1000 * connectionAttempt);
|
|
3115
|
+
}
|
|
3116
|
+
},
|
|
3117
|
+
close: () => {
|
|
3118
|
+
connectionShouldBeOpen = false;
|
|
3119
|
+
if (ws) {
|
|
3120
|
+
ws.close();
|
|
3121
|
+
}
|
|
3122
|
+
},
|
|
3123
|
+
connect: () => {
|
|
3124
|
+
connectionShouldBeOpen = true;
|
|
3125
|
+
connectionAttempt += 1;
|
|
3126
|
+
if (ws) {
|
|
3127
|
+
ws.close();
|
|
3128
|
+
}
|
|
3129
|
+
connection.connected = true;
|
|
3130
|
+
ws = new WebSocket(wsURL + window.location.pathname, RTCSTATS_PROTOCOL_VERSION);
|
|
3131
|
+
|
|
3132
|
+
ws.onerror = (e) => {
|
|
3133
|
+
connection.connected = false;
|
|
3134
|
+
logger.warn(`[RTCSTATS] WebSocket error`, e);
|
|
3135
|
+
};
|
|
3136
|
+
ws.onclose = (e) => {
|
|
3137
|
+
connection.connected = false;
|
|
3138
|
+
logger.info(`[RTCSTATS] Closed ${e.code}`);
|
|
3139
|
+
resetDelta();
|
|
3140
|
+
};
|
|
3141
|
+
ws.onopen = () => {
|
|
3142
|
+
// send client info after each connection, so analysis tools can handle reconnections
|
|
3143
|
+
clientInfo.connectionNumber++;
|
|
3144
|
+
ws.send(JSON.stringify(["clientInfo", null, clientInfo]));
|
|
3145
|
+
|
|
3146
|
+
if (organizationId) {
|
|
3147
|
+
ws.send(JSON.stringify(organizationId));
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
if (clientId) {
|
|
3151
|
+
ws.send(JSON.stringify(clientId));
|
|
3152
|
+
}
|
|
3153
|
+
|
|
3154
|
+
if (roomSessionId) {
|
|
3155
|
+
ws.send(JSON.stringify(roomSessionId));
|
|
3156
|
+
}
|
|
3157
|
+
if (displayName) {
|
|
3158
|
+
ws.send(JSON.stringify(displayName));
|
|
3159
|
+
}
|
|
3160
|
+
if (userRole) {
|
|
3161
|
+
ws.send(JSON.stringify(userRole));
|
|
3162
|
+
}
|
|
3163
|
+
|
|
3164
|
+
// send buffered events
|
|
3165
|
+
while (buffer.length) {
|
|
3166
|
+
ws.send(JSON.stringify(buffer.shift()));
|
|
3167
|
+
}
|
|
3168
|
+
getStatsBufferUsed = 0;
|
|
3169
|
+
};
|
|
3170
|
+
},
|
|
3171
|
+
};
|
|
3172
|
+
connection.connect();
|
|
3173
|
+
return connection;
|
|
3174
|
+
}
|
|
3175
|
+
|
|
3176
|
+
const server = rtcStatsConnection("wss://rtcstats.srv.whereby.com" );
|
|
3177
|
+
const stats = rtcstats$1(
|
|
3178
|
+
server.trace,
|
|
3179
|
+
10000, // query once every 10 seconds.
|
|
3180
|
+
[""] // only shim unprefixed RTCPeerConnecion.
|
|
3181
|
+
);
|
|
3182
|
+
// on node clients this function can be undefined
|
|
3183
|
+
resetDelta = stats?.resetDelta || noop;
|
|
3184
|
+
|
|
3185
|
+
const rtcStats = {
|
|
3186
|
+
sendEvent: (type, value) => {
|
|
3187
|
+
server.trace("customEvent", null, {
|
|
3188
|
+
type,
|
|
3189
|
+
value,
|
|
3190
|
+
});
|
|
3191
|
+
},
|
|
3192
|
+
sendAudioMuted: (muted) => {
|
|
3193
|
+
rtcStats.sendEvent("audio_muted", { muted });
|
|
3194
|
+
},
|
|
3195
|
+
sendVideoMuted: (muted) => {
|
|
3196
|
+
rtcStats.sendEvent("video_muted", { muted });
|
|
3197
|
+
},
|
|
3198
|
+
server,
|
|
3199
|
+
};
|
|
3200
|
+
|
|
3201
|
+
const adapter$4 = adapterRaw.default ?? adapterRaw;
|
|
3202
|
+
const logger$6 = new Logger();
|
|
3203
|
+
|
|
3204
|
+
const browserName$2 = adapter$4.browserDetails.browser;
|
|
3205
|
+
const browserVersion$1 = adapter$4.browserDetails.version;
|
|
3206
|
+
|
|
3207
|
+
function setCodecPreferenceSDP(sdp, vp9On, redOn) {
|
|
3208
|
+
try {
|
|
3209
|
+
const sdpObject = sdpTransform.parse(sdp);
|
|
3210
|
+
if (Array.isArray(sdpObject?.media)) {
|
|
3211
|
+
//audio
|
|
2767
3212
|
const mediaAudio = sdpObject.media.find((m) => m.type === "audio");
|
|
2768
3213
|
if (Array.isArray(mediaAudio?.rtp)) {
|
|
2769
3214
|
const rtp = mediaAudio.rtp;
|
|
@@ -2797,7 +3242,7 @@ function setCodecPreferenceSDP(sdp, vp9On, redOn) {
|
|
|
2797
3242
|
const newSdp = sdpTransform.write(sdpObject);
|
|
2798
3243
|
return newSdp;
|
|
2799
3244
|
} catch (error) {
|
|
2800
|
-
logger$
|
|
3245
|
+
logger$6.error("setCodecPreferenceSDP error:", error);
|
|
2801
3246
|
}
|
|
2802
3247
|
}
|
|
2803
3248
|
|
|
@@ -2850,7 +3295,7 @@ function replaceSSRCs(currentDescription, newDescription) {
|
|
|
2850
3295
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1478685
|
|
2851
3296
|
// filter out the mid rtp header extension
|
|
2852
3297
|
function filterMidExtension(sdp) {
|
|
2853
|
-
if (browserName$
|
|
3298
|
+
if (browserName$2 !== "safari" && (browserName$2 !== "firefox" || browserVersion$1 >= 63 || browserVersion$1 === 60)) {
|
|
2854
3299
|
return sdp;
|
|
2855
3300
|
}
|
|
2856
3301
|
return (
|
|
@@ -2871,7 +3316,7 @@ function filterMidExtension(sdp) {
|
|
|
2871
3316
|
// https://bugzilla.mozilla.org/show_bug.cgi?id=1534673
|
|
2872
3317
|
// Filter out a:msid-semantic header
|
|
2873
3318
|
function filterMsidSemantic(sdp) {
|
|
2874
|
-
if (browserName$
|
|
3319
|
+
if (browserName$2 !== "firefox") {
|
|
2875
3320
|
return sdp;
|
|
2876
3321
|
}
|
|
2877
3322
|
return (
|
|
@@ -2881,6 +3326,52 @@ function filterMsidSemantic(sdp) {
|
|
|
2881
3326
|
);
|
|
2882
3327
|
}
|
|
2883
3328
|
|
|
3329
|
+
// add SDP RTP header extension mapping
|
|
3330
|
+
function addExtMap(sdp, extmapUri, modifyAudio = false, modifyVideo = false) {
|
|
3331
|
+
try {
|
|
3332
|
+
const sdpObj = sdpTransform.parse(sdp);
|
|
3333
|
+
|
|
3334
|
+
// in case session level extmaps we skip modification
|
|
3335
|
+
// TODO: handle it more properly
|
|
3336
|
+
if (sdpObj?.ext?.length > 0) {
|
|
3337
|
+
return sdp;
|
|
3338
|
+
}
|
|
3339
|
+
|
|
3340
|
+
// if sdp string is faulty, and lib can't parse any m= lines we return it unmodified.
|
|
3341
|
+
if (sdpObj?.media.length < 1) return sdp;
|
|
3342
|
+
|
|
3343
|
+
const allHeaderExtensions = sdpObj?.media.flatMap((section) => section.ext || []);
|
|
3344
|
+
const extmapId =
|
|
3345
|
+
allHeaderExtensions.find((ext) => ext.uri === extmapUri)?.value ||
|
|
3346
|
+
[...new Set([0, 15, ...allHeaderExtensions.map((ext) => ext.value)])]
|
|
3347
|
+
.sort((a, b) => a - b)
|
|
3348
|
+
.find((n, i, arr) => n + 1 !== arr[i + 1]) + 1;
|
|
3349
|
+
|
|
3350
|
+
sdpObj.media.forEach((mediaSection) => {
|
|
3351
|
+
if ((modifyAudio && mediaSection.type === "audio") || (modifyVideo && mediaSection.type === "video")) {
|
|
3352
|
+
if (!mediaSection.ext?.find((e) => e.uri === extmapUri)) {
|
|
3353
|
+
if (Array.isArray(mediaSection.ext)) {
|
|
3354
|
+
mediaSection["ext"].push({ value: extmapId, uri: extmapUri });
|
|
3355
|
+
} else {
|
|
3356
|
+
mediaSection["ext"] = [{ value: extmapId, uri: extmapUri }];
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
}
|
|
3360
|
+
});
|
|
3361
|
+
return sdpTransform.write(sdpObj);
|
|
3362
|
+
} catch (error) {
|
|
3363
|
+
console.error("Error during addAbsCaptureTimeExtMap: ", error);
|
|
3364
|
+
}
|
|
3365
|
+
return sdp;
|
|
3366
|
+
}
|
|
3367
|
+
|
|
3368
|
+
// Add SDP RTP header extension mapping to abs-capture-time
|
|
3369
|
+
// a=extmap:9 http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time
|
|
3370
|
+
function addAbsCaptureTimeExtMap(sdp) {
|
|
3371
|
+
const absCaptureTimeUri = "http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time";
|
|
3372
|
+
return addExtMap(sdp, absCaptureTimeUri, true, true);
|
|
3373
|
+
}
|
|
3374
|
+
|
|
2884
3375
|
function isRelayed(pc) {
|
|
2885
3376
|
return pc.getStats(null).then((result) => {
|
|
2886
3377
|
let localCandidateType;
|
|
@@ -2944,7 +3435,8 @@ const MAXIMUM_TURN_BANDWIDTH_UNLIMITED = -1; // kbps;
|
|
|
2944
3435
|
|
|
2945
3436
|
const MEDIA_JITTER_BUFFER_TARGET = 400; // milliseconds;
|
|
2946
3437
|
|
|
2947
|
-
const
|
|
3438
|
+
const adapter$3 = adapterRaw.default ?? adapterRaw;
|
|
3439
|
+
const logger$5 = new Logger();
|
|
2948
3440
|
|
|
2949
3441
|
class Session {
|
|
2950
3442
|
constructor({ peerConnectionId, bandwidth, maximumTurnBandwidth, deprioritizeH264Encoding }) {
|
|
@@ -3119,7 +3611,7 @@ class Session {
|
|
|
3119
3611
|
return setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
|
|
3120
3612
|
},
|
|
3121
3613
|
(e) => {
|
|
3122
|
-
logger$
|
|
3614
|
+
logger$5.warn("Could not set remote description from remote answer: ", e);
|
|
3123
3615
|
}
|
|
3124
3616
|
);
|
|
3125
3617
|
}
|
|
@@ -3135,12 +3627,12 @@ class Session {
|
|
|
3135
3627
|
if (this.pc.signalingState === "closed") {
|
|
3136
3628
|
return;
|
|
3137
3629
|
}
|
|
3138
|
-
if (adapter.browserDetails.browser === "safari" && candidate && candidate.candidate === "") {
|
|
3630
|
+
if (adapter$3.browserDetails.browser === "safari" && candidate && candidate.candidate === "") {
|
|
3139
3631
|
// filter due to https://github.com/webrtcHacks/adapter/issues/863
|
|
3140
3632
|
return;
|
|
3141
3633
|
}
|
|
3142
3634
|
this.pc.addIceCandidate(candidate).catch((e) => {
|
|
3143
|
-
logger$
|
|
3635
|
+
logger$5.warn("Failed to add ICE candidate ('%s'): %s", candidate ? candidate.candidate : null, e);
|
|
3144
3636
|
});
|
|
3145
3637
|
});
|
|
3146
3638
|
}
|
|
@@ -3162,7 +3654,7 @@ class Session {
|
|
|
3162
3654
|
// do not handle state change events when we close the connection explicitly
|
|
3163
3655
|
pc.close();
|
|
3164
3656
|
} catch (e) {
|
|
3165
|
-
logger$
|
|
3657
|
+
logger$5.warn("failures during close of session", e);
|
|
3166
3658
|
// we're not interested in errors from RTCPeerConnection.close()
|
|
3167
3659
|
}
|
|
3168
3660
|
}
|
|
@@ -3178,7 +3670,7 @@ class Session {
|
|
|
3178
3670
|
const senders = pc.getSenders();
|
|
3179
3671
|
function dbg(msg) {
|
|
3180
3672
|
const tr = (t) => t && `id:${t.id},kind:${t.kind},state:${t.readyState}`;
|
|
3181
|
-
logger$
|
|
3673
|
+
logger$5.warn(
|
|
3182
3674
|
`${msg}. newTrack:${tr(newTrack)}, oldTrack:${tr(oldTrack)}, sender tracks: ${JSON.stringify(
|
|
3183
3675
|
senders.map((s) => `s ${tr(s.track)}`)
|
|
3184
3676
|
)}, sender first codecs: ${JSON.stringify(senders.map((s) => (s.getParameters().codecs || [])[0]))}`
|
|
@@ -3312,687 +3804,568 @@ class Session {
|
|
|
3312
3804
|
});
|
|
3313
3805
|
}
|
|
3314
3806
|
|
|
3315
|
-
// no-signaling negotiation of bandwidth. Peer is NOT informed.
|
|
3316
|
-
// Prefers using RTCRtpSender.setParameters if possible.
|
|
3317
|
-
changeBandwidth(bandwidth) {
|
|
3318
|
-
// don't renegotiate if bandwidth is already set.
|
|
3319
|
-
if (bandwidth === this.bandwidth) {
|
|
3320
|
-
return;
|
|
3321
|
-
}
|
|
3807
|
+
// no-signaling negotiation of bandwidth. Peer is NOT informed.
|
|
3808
|
+
// Prefers using RTCRtpSender.setParameters if possible.
|
|
3809
|
+
changeBandwidth(bandwidth) {
|
|
3810
|
+
// don't renegotiate if bandwidth is already set.
|
|
3811
|
+
if (bandwidth === this.bandwidth) {
|
|
3812
|
+
return;
|
|
3813
|
+
}
|
|
3814
|
+
|
|
3815
|
+
if (!this.canModifyPeerConnection()) {
|
|
3816
|
+
this.pending.push(() => this.changeBandwidth(bandwidth));
|
|
3817
|
+
return;
|
|
3818
|
+
}
|
|
3819
|
+
|
|
3820
|
+
this.bandwidth = bandwidth;
|
|
3821
|
+
if (!this.pc.localDescription || this.pc.localDescription.type === "") {
|
|
3822
|
+
return;
|
|
3823
|
+
}
|
|
3824
|
+
|
|
3825
|
+
setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
|
|
3826
|
+
}
|
|
3827
|
+
|
|
3828
|
+
setAudioOnly(enable, excludedTrackIds = []) {
|
|
3829
|
+
this.pc
|
|
3830
|
+
.getTransceivers()
|
|
3831
|
+
.filter(
|
|
3832
|
+
(videoTransceiver) =>
|
|
3833
|
+
videoTransceiver?.direction !== "recvonly" &&
|
|
3834
|
+
videoTransceiver?.receiver?.track?.kind === "video" &&
|
|
3835
|
+
!excludedTrackIds.includes(videoTransceiver?.receiver?.track?.id) &&
|
|
3836
|
+
!excludedTrackIds.includes(videoTransceiver?.sender?.track?.id)
|
|
3837
|
+
)
|
|
3838
|
+
.forEach((videoTransceiver) => {
|
|
3839
|
+
videoTransceiver.direction = enable ? "sendonly" : "sendrecv";
|
|
3840
|
+
});
|
|
3841
|
+
}
|
|
3842
|
+
}
|
|
3843
|
+
|
|
3844
|
+
const adapter$2 = adapterRaw.default ?? adapterRaw;
|
|
3845
|
+
|
|
3846
|
+
/**
|
|
3847
|
+
* Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
|
|
3848
|
+
* frequently. A browser restart fixes this.
|
|
3849
|
+
*
|
|
3850
|
+
* Should be called after the connection has been up for a while.
|
|
3851
|
+
*
|
|
3852
|
+
* @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
|
|
3853
|
+
*/
|
|
3854
|
+
function detectMicrophoneNotWorking(pc) {
|
|
3855
|
+
if (
|
|
3856
|
+
adapter$2.browserDetails.browser !== "chrome" ||
|
|
3857
|
+
adapter$2.browserDetails.browser < 58 || // legacy getStats is no longer supported.
|
|
3858
|
+
pc.signalingState === "closed"
|
|
3859
|
+
) {
|
|
3860
|
+
return Promise.resolve(false);
|
|
3861
|
+
}
|
|
3862
|
+
const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
|
|
3863
|
+
const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
|
|
3864
|
+
return pc.getStats(null).then((result) => {
|
|
3865
|
+
let microphoneFailed = false;
|
|
3866
|
+
result.forEach((report) => {
|
|
3867
|
+
if (
|
|
3868
|
+
report.type === "outbound-rtp" &&
|
|
3869
|
+
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
3870
|
+
sendingAudio
|
|
3871
|
+
) {
|
|
3872
|
+
if (report.bytesSent === 0) {
|
|
3873
|
+
microphoneFailed = "outbound";
|
|
3874
|
+
}
|
|
3875
|
+
} else if (
|
|
3876
|
+
report.type === "inbound-rtp" &&
|
|
3877
|
+
(report.kind === "audio" || report.mediaType === "audio") &&
|
|
3878
|
+
receivingAudio
|
|
3879
|
+
) {
|
|
3880
|
+
if (report.bytesReceived === 0) {
|
|
3881
|
+
microphoneFailed = "inbound";
|
|
3882
|
+
}
|
|
3883
|
+
}
|
|
3884
|
+
});
|
|
3885
|
+
return microphoneFailed;
|
|
3886
|
+
});
|
|
3887
|
+
}
|
|
3888
|
+
|
|
3889
|
+
const EVENTS = {
|
|
3890
|
+
CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
|
|
3891
|
+
STREAM_ADDED: "stream_added",
|
|
3892
|
+
RTC_MANAGER_CREATED: "rtc_manager_created",
|
|
3893
|
+
RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
|
|
3894
|
+
LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
|
|
3895
|
+
LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
|
|
3896
|
+
REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
|
|
3897
|
+
REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
|
|
3898
|
+
};
|
|
3899
|
+
|
|
3900
|
+
const TYPES = {
|
|
3901
|
+
CONNECTING: "connecting",
|
|
3902
|
+
CONNECTION_FAILED: "connection_failed",
|
|
3903
|
+
CONNECTION_SUCCESSFUL: "connection_successful",
|
|
3904
|
+
CONNECTION_DISCONNECTED: "connection_disconnected",
|
|
3905
|
+
};
|
|
3906
|
+
|
|
3907
|
+
const CAMERA_STREAM_ID$1 = "0";
|
|
3908
|
+
|
|
3909
|
+
const STREAM_TYPES = {
|
|
3910
|
+
CAMERA: "camera",
|
|
3911
|
+
SCREEN_SHARE: "screen_share",
|
|
3912
|
+
};
|
|
3913
|
+
|
|
3914
|
+
class RtcStream {
|
|
3915
|
+
constructor(id, type) {
|
|
3916
|
+
assert$1.notEqual(id, undefined, "id is required");
|
|
3917
|
+
assert$1.notEqual(type, undefined, "type is required");
|
|
3918
|
+
|
|
3919
|
+
this.id = "" + id;
|
|
3920
|
+
this.type = type;
|
|
3921
|
+
|
|
3922
|
+
this.isEnabled = true;
|
|
3923
|
+
this.hasSupportForAutoSuperSize = false;
|
|
3924
|
+
this.isAudioEnabled = true;
|
|
3925
|
+
this.isVideoEnabled = true;
|
|
3926
|
+
this.status = TYPES.CONNECTING;
|
|
3927
|
+
}
|
|
3928
|
+
|
|
3929
|
+
setup(stream) {
|
|
3930
|
+
this.stream = stream;
|
|
3931
|
+
this.streamId = stream.id;
|
|
3932
|
+
this.setVideoEnabled(this.isVideoEnabled && stream.getVideoTracks().length > 0);
|
|
3933
|
+
this.setAudioEnabled(this.isAudioEnabled && stream.getAudioTracks().length > 0);
|
|
3934
|
+
return this;
|
|
3935
|
+
}
|
|
3936
|
+
|
|
3937
|
+
setStatus(status) {
|
|
3938
|
+
this.status = status;
|
|
3939
|
+
return this;
|
|
3940
|
+
}
|
|
3322
3941
|
|
|
3323
|
-
|
|
3324
|
-
|
|
3942
|
+
setVideoEnabled(isEnabled) {
|
|
3943
|
+
this.isVideoEnabled = isEnabled;
|
|
3944
|
+
if (!this.stream) {
|
|
3325
3945
|
return;
|
|
3326
3946
|
}
|
|
3947
|
+
this.stream.getVideoTracks().forEach((track) => {
|
|
3948
|
+
track.enabled = isEnabled;
|
|
3949
|
+
});
|
|
3950
|
+
}
|
|
3327
3951
|
|
|
3328
|
-
|
|
3329
|
-
|
|
3952
|
+
setAudioEnabled(isEnabled) {
|
|
3953
|
+
this.isAudioEnabled = isEnabled;
|
|
3954
|
+
if (!this.stream) {
|
|
3330
3955
|
return;
|
|
3331
3956
|
}
|
|
3957
|
+
this.stream.getAudioTracks().forEach((track) => {
|
|
3958
|
+
track.enabled = isEnabled;
|
|
3959
|
+
});
|
|
3960
|
+
}
|
|
3332
3961
|
|
|
3333
|
-
|
|
3962
|
+
static getCameraId() {
|
|
3963
|
+
return CAMERA_STREAM_ID$1;
|
|
3334
3964
|
}
|
|
3335
3965
|
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3341
|
-
videoTransceiver?.direction !== "recvonly" &&
|
|
3342
|
-
videoTransceiver?.receiver?.track?.kind === "video" &&
|
|
3343
|
-
!excludedTrackIds.includes(videoTransceiver?.receiver?.track?.id) &&
|
|
3344
|
-
!excludedTrackIds.includes(videoTransceiver?.sender?.track?.id)
|
|
3345
|
-
)
|
|
3346
|
-
.forEach((videoTransceiver) => {
|
|
3347
|
-
videoTransceiver.direction = enable ? "sendonly" : "sendrecv";
|
|
3348
|
-
});
|
|
3966
|
+
static getTypeFromId(id) {
|
|
3967
|
+
assert$1.notEqual(id, undefined, "id is required");
|
|
3968
|
+
|
|
3969
|
+
const streamId = "" + id;
|
|
3970
|
+
return streamId === CAMERA_STREAM_ID$1 ? STREAM_TYPES.CAMERA : STREAM_TYPES.SCREEN_SHARE;
|
|
3349
3971
|
}
|
|
3350
3972
|
}
|
|
3351
3973
|
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
|
|
3355
|
-
|
|
3356
|
-
|
|
3357
|
-
}
|
|
3358
|
-
var o = {};
|
|
3359
|
-
m.forEach(function(v, k) {
|
|
3360
|
-
o[k] = v;
|
|
3361
|
-
});
|
|
3362
|
-
return o;
|
|
3363
|
-
}
|
|
3974
|
+
const lowPixelCount = 320 * 180;
|
|
3975
|
+
const lowBitratePerPixel = 150000 / lowPixelCount;
|
|
3976
|
+
const highPixelCount = 1280 * 720;
|
|
3977
|
+
const highBitratePerPixel = 1000000 / highPixelCount;
|
|
3978
|
+
const bitrateChangePerPixel = (highBitratePerPixel - lowBitratePerPixel) / (highPixelCount - lowPixelCount);
|
|
3364
3979
|
|
|
3365
|
-
//
|
|
3366
|
-
|
|
3367
|
-
|
|
3368
|
-
|
|
3369
|
-
|
|
3370
|
-
var report = newStats[id];
|
|
3371
|
-
delete report.id;
|
|
3372
|
-
if (!oldStats[id]) {
|
|
3373
|
-
return;
|
|
3374
|
-
}
|
|
3375
|
-
Object.keys(report).forEach(function(name) {
|
|
3376
|
-
if (report[name] === oldStats[id][name]) {
|
|
3377
|
-
delete newStats[id][name];
|
|
3378
|
-
}
|
|
3379
|
-
if (Object.keys(report).length === 0) {
|
|
3380
|
-
delete newStats[id];
|
|
3381
|
-
} else if (Object.keys(report).length === 1 && report.timestamp) {
|
|
3382
|
-
delete newStats[id];
|
|
3383
|
-
}
|
|
3384
|
-
});
|
|
3385
|
-
});
|
|
3980
|
+
// calculates a bitrate for a given resolution+frameRate
|
|
3981
|
+
function getOptimalBitrate(width, height, frameRate) {
|
|
3982
|
+
let targetPixelCount = width * height;
|
|
3983
|
+
if (targetPixelCount < lowPixelCount) targetPixelCount = lowPixelCount;
|
|
3984
|
+
if (targetPixelCount > highPixelCount) targetPixelCount = highPixelCount;
|
|
3386
3985
|
|
|
3387
|
-
|
|
3388
|
-
|
|
3389
|
-
|
|
3390
|
-
|
|
3391
|
-
timestamp = report.timestamp;
|
|
3392
|
-
}
|
|
3393
|
-
});
|
|
3394
|
-
Object.keys(newStats).forEach(function(id) {
|
|
3395
|
-
var report = newStats[id];
|
|
3396
|
-
if (report.timestamp === timestamp) {
|
|
3397
|
-
report.timestamp = 0;
|
|
3986
|
+
let targetBitratePerPixel = lowBitratePerPixel;
|
|
3987
|
+
if (targetPixelCount > highPixelCount) targetBitratePerPixel = highBitratePerPixel;
|
|
3988
|
+
else if (targetPixelCount > lowPixelCount) {
|
|
3989
|
+
targetBitratePerPixel += (targetPixelCount - lowPixelCount) * bitrateChangePerPixel;
|
|
3398
3990
|
}
|
|
3399
|
-
});
|
|
3400
|
-
newStats.timestamp = timestamp;
|
|
3401
|
-
return newStats;
|
|
3402
|
-
}
|
|
3403
3991
|
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
id: stream.id,
|
|
3407
|
-
tracks: stream.getTracks().map(function(track) {
|
|
3408
|
-
return {
|
|
3409
|
-
id: track.id, // unique identifier (GUID) for the track
|
|
3410
|
-
kind: track.kind, // `audio` or `video`
|
|
3411
|
-
label: track.label, // identified the track source
|
|
3412
|
-
enabled: track.enabled, // application can control it
|
|
3413
|
-
muted: track.muted, // application cannot control it (read-only)
|
|
3414
|
-
readyState: track.readyState, // `live` or `ended`
|
|
3415
|
-
};
|
|
3416
|
-
}),
|
|
3417
|
-
};
|
|
3418
|
-
}
|
|
3992
|
+
// we use the actual resolution for the target bitrate
|
|
3993
|
+
let targetBitrate = width * height * targetBitratePerPixel;
|
|
3419
3994
|
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
switch (results[id].type) {
|
|
3424
|
-
case 'certificate':
|
|
3425
|
-
case 'codec':
|
|
3426
|
-
delete results[id];
|
|
3427
|
-
break;
|
|
3428
|
-
default:
|
|
3429
|
-
// noop
|
|
3430
|
-
}
|
|
3431
|
-
});
|
|
3432
|
-
return results;
|
|
3433
|
-
}
|
|
3995
|
+
// adjust bitrate down a bit if reduced framerate
|
|
3996
|
+
if (frameRate <= 15) targetBitrate = targetBitrate * 0.7;
|
|
3997
|
+
else if (frameRate <= 24) targetBitrate = targetBitrate * 0.9;
|
|
3434
3998
|
|
|
3435
|
-
|
|
3436
|
-
// FIXME: does not work in FF since the timestamp can't be deleted.
|
|
3437
|
-
Object.keys(results).forEach(function(id) {
|
|
3438
|
-
delete results[id].timestamp;
|
|
3439
|
-
});
|
|
3440
|
-
return results;
|
|
3999
|
+
return targetBitrate;
|
|
3441
4000
|
}
|
|
3442
|
-
*/
|
|
3443
4001
|
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
var isEdge = !!window.RTCIceGatherer;
|
|
3448
|
-
var prevById = {};
|
|
3449
|
-
|
|
3450
|
-
prefixesToWrap.forEach(function(prefix) {
|
|
3451
|
-
if (!window[prefix + 'RTCPeerConnection']) {
|
|
3452
|
-
return;
|
|
3453
|
-
}
|
|
3454
|
-
if (prefix === 'webkit' && isEdge) {
|
|
3455
|
-
// dont wrap webkitRTCPeerconnection in Edge.
|
|
3456
|
-
return;
|
|
3457
|
-
}
|
|
3458
|
-
var origPeerConnection = window[prefix + 'RTCPeerConnection'];
|
|
3459
|
-
var peerconnection = function(config, constraints) {
|
|
3460
|
-
var pc = new origPeerConnection(config, constraints);
|
|
3461
|
-
var id = 'PC_' + peerconnectioncounter++;
|
|
3462
|
-
pc.__rtcStatsId = id;
|
|
4002
|
+
// taken from https://github.com/sindresorhus/ip-regex ^5.0.0
|
|
4003
|
+
// inlined because it's import caused errors in browser-sdk when running tests
|
|
4004
|
+
const word = "[a-fA-F\\d:]";
|
|
3463
4005
|
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
}
|
|
4006
|
+
const boundry = (options) =>
|
|
4007
|
+
options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
|
|
3467
4008
|
|
|
3468
|
-
|
|
3469
|
-
// don't log credentials
|
|
3470
|
-
((config && config.iceServers) || []).forEach(function(server) {
|
|
3471
|
-
delete server.credential;
|
|
3472
|
-
});
|
|
4009
|
+
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}";
|
|
3473
4010
|
|
|
3474
|
-
|
|
3475
|
-
config.browserType = 'moz';
|
|
3476
|
-
} else if (isEdge) {
|
|
3477
|
-
config.browserType = 'edge';
|
|
3478
|
-
} else {
|
|
3479
|
-
config.browserType = 'webkit';
|
|
3480
|
-
}
|
|
4011
|
+
const v6segment = "[a-fA-F\\d]{1,4}";
|
|
3481
4012
|
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
4013
|
+
const v6 = `
|
|
4014
|
+
(?:
|
|
4015
|
+
(?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
|
|
4016
|
+
(?:${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
|
|
4017
|
+
(?:${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
|
|
4018
|
+
(?:${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
|
|
4019
|
+
(?:${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
|
|
4020
|
+
(?:${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
|
|
4021
|
+
(?:${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
|
|
4022
|
+
(?::(?:(?::${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
|
|
4023
|
+
)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
|
|
4024
|
+
`
|
|
4025
|
+
.replace(/\s*\/\/.*$/gm, "")
|
|
4026
|
+
.replace(/\n/g, "")
|
|
4027
|
+
.trim();
|
|
3488
4028
|
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3500
|
-
|
|
3501
|
-
pc.addEventListener('signalingstatechange', function() {
|
|
3502
|
-
trace('onsignalingstatechange', id, pc.signalingState);
|
|
3503
|
-
});
|
|
3504
|
-
pc.addEventListener('iceconnectionstatechange', function() {
|
|
3505
|
-
trace('oniceconnectionstatechange', id, pc.iceConnectionState);
|
|
3506
|
-
});
|
|
3507
|
-
pc.addEventListener('icegatheringstatechange', function() {
|
|
3508
|
-
trace('onicegatheringstatechange', id, pc.iceGatheringState);
|
|
3509
|
-
});
|
|
3510
|
-
pc.addEventListener('connectionstatechange', function() {
|
|
3511
|
-
trace('onconnectionstatechange', id, pc.connectionState);
|
|
3512
|
-
});
|
|
3513
|
-
pc.addEventListener('negotiationneeded', function() {
|
|
3514
|
-
trace('onnegotiationneeded', id, undefined);
|
|
3515
|
-
});
|
|
3516
|
-
pc.addEventListener('datachannel', function(event) {
|
|
3517
|
-
trace('ondatachannel', id, [event.channel.id, event.channel.label]);
|
|
3518
|
-
});
|
|
4029
|
+
// Pre-compile only the exact regexes because adding a global flag make regexes stateful
|
|
4030
|
+
const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
|
|
4031
|
+
const v4exact = new RegExp(`^${v4}$`);
|
|
4032
|
+
const v6exact = new RegExp(`^${v6}$`);
|
|
4033
|
+
|
|
4034
|
+
const ipRegex = (options) =>
|
|
4035
|
+
options && options.exact
|
|
4036
|
+
? v46Exact
|
|
4037
|
+
: new RegExp(
|
|
4038
|
+
`(?:${boundry(options)}${v4}${boundry(options)})|(?:${boundry(options)}${v6}${boundry(options)})`,
|
|
4039
|
+
"g"
|
|
4040
|
+
);
|
|
3519
4041
|
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3524
|
-
trace('getstats', id, deltaCompression(prevById[id] || {}, now));
|
|
3525
|
-
prevById[id] = base;
|
|
3526
|
-
});
|
|
3527
|
-
};
|
|
3528
|
-
// TODO: do we want one big interval and all peerconnections
|
|
3529
|
-
// queried in that or one setInterval per PC?
|
|
3530
|
-
// we have to collect results anyway so...
|
|
3531
|
-
if (!isEdge && getStatsInterval) {
|
|
3532
|
-
var interval = window.setInterval(function() {
|
|
3533
|
-
if (pc.signalingState === 'closed') {
|
|
3534
|
-
window.clearInterval(interval);
|
|
3535
|
-
return;
|
|
3536
|
-
}
|
|
3537
|
-
getStats();
|
|
3538
|
-
}, getStatsInterval);
|
|
3539
|
-
}
|
|
3540
|
-
if (!isEdge) {
|
|
3541
|
-
pc.addEventListener('connectionstatechange', function() {
|
|
3542
|
-
if (pc.connectionState === 'connected' || pc.connectionState === 'failed') {
|
|
3543
|
-
getStats();
|
|
3544
|
-
}
|
|
3545
|
-
});
|
|
3546
|
-
}
|
|
3547
|
-
return pc;
|
|
3548
|
-
};
|
|
4042
|
+
ipRegex.v4 = (options) =>
|
|
4043
|
+
options && options.exact ? v4exact : new RegExp(`${boundry(options)}${v4}${boundry(options)}`, "g");
|
|
4044
|
+
ipRegex.v6 = (options) =>
|
|
4045
|
+
options && options.exact ? v6exact : new RegExp(`${boundry(options)}${v6}${boundry(options)}`, "g");
|
|
3549
4046
|
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
|
|
3555
|
-
|
|
3556
|
-
|
|
3557
|
-
|
|
3558
|
-
|
|
4047
|
+
var rtcManagerEvents = {
|
|
4048
|
+
CAMERA_NOT_WORKING: "camera_not_working",
|
|
4049
|
+
CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
|
|
4050
|
+
ICE_IPV6_SEEN: "ice_ipv6_seen",
|
|
4051
|
+
ICE_MDNS_SEEN: "ice_mdns_seen",
|
|
4052
|
+
ICE_NO_PUBLIC_IP_GATHERED: "ice_no_public_ip_gathered",
|
|
4053
|
+
ICE_NO_PUBLIC_IP_GATHERED_3SEC: "ice_no_public_ip_gathered_3sec",
|
|
4054
|
+
ICE_RESTART: "ice_restart",
|
|
4055
|
+
MICROPHONE_NOT_WORKING: "microphone_not_working",
|
|
4056
|
+
MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
|
|
4057
|
+
NEW_PC: "new_pc",
|
|
4058
|
+
SFU_CONNECTION_OPEN: "sfu_connection_open",
|
|
4059
|
+
SFU_CONNECTION_CLOSED: "sfu_connection_closed",
|
|
4060
|
+
COLOCATION_SPEAKER: "colocation_speaker",
|
|
4061
|
+
DOMINANT_SPEAKER: "dominant_speaker",
|
|
4062
|
+
};
|
|
3559
4063
|
|
|
3560
|
-
|
|
3561
|
-
|
|
3562
|
-
if (nativeMethod) {
|
|
3563
|
-
origPeerConnection.prototype[method] = function() {
|
|
3564
|
-
var stream = arguments[0];
|
|
3565
|
-
var streamInfo = stream.getTracks().map(function(t) {
|
|
3566
|
-
return t.kind + ':' + t.id;
|
|
3567
|
-
}).join(',');
|
|
4064
|
+
const adapter$1 = adapterRaw.default ?? adapterRaw;
|
|
4065
|
+
const logger$4 = new Logger();
|
|
3568
4066
|
|
|
3569
|
-
|
|
3570
|
-
|
|
3571
|
-
|
|
3572
|
-
|
|
3573
|
-
});
|
|
4067
|
+
const ICE_PUBLIC_IP_GATHERING_TIMEOUT = 3 * 1000;
|
|
4068
|
+
const CAMERA_STREAM_ID = RtcStream.getCameraId();
|
|
4069
|
+
const browserName$1 = adapter$1.browserDetails.browser;
|
|
4070
|
+
const browserVersion = adapter$1.browserDetails.version;
|
|
3574
4071
|
|
|
3575
|
-
|
|
3576
|
-
|
|
3577
|
-
|
|
3578
|
-
origPeerConnection.prototype[method] = function() {
|
|
3579
|
-
var track = arguments[0];
|
|
3580
|
-
var streams = [].slice.call(arguments, 1);
|
|
3581
|
-
trace(method, this.__rtcStatsId, track.kind + ':' + track.id + ' ' + (streams.map(function(s) { return 'stream:' + s.id; }).join(';') || '-'));
|
|
3582
|
-
return nativeMethod.apply(this, arguments);
|
|
3583
|
-
};
|
|
3584
|
-
}
|
|
3585
|
-
});
|
|
4072
|
+
if (browserName$1 === "firefox") {
|
|
4073
|
+
adapter$1.browserShim.shimGetDisplayMedia(window, "screen");
|
|
4074
|
+
}
|
|
3586
4075
|
|
|
3587
|
-
|
|
3588
|
-
|
|
3589
|
-
|
|
3590
|
-
|
|
3591
|
-
var track = arguments[0].track;
|
|
3592
|
-
trace(method, this.__rtcStatsId, track ? track.kind + ':' + track.id : 'null');
|
|
3593
|
-
return nativeMethod.apply(this, arguments);
|
|
3594
|
-
};
|
|
3595
|
-
}
|
|
4076
|
+
let unloading$1 = false;
|
|
4077
|
+
if (browserName$1 === "chrome") {
|
|
4078
|
+
window.document.addEventListener("beforeunload", () => {
|
|
4079
|
+
unloading$1 = true;
|
|
3596
4080
|
});
|
|
4081
|
+
}
|
|
3597
4082
|
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
|
|
3605
|
-
if (arguments.length === 1 && typeof arguments[0] === 'object') {
|
|
3606
|
-
opts = arguments[0];
|
|
3607
|
-
} else if (arguments.length === 3 && typeof arguments[2] === 'object') {
|
|
3608
|
-
opts = arguments[2];
|
|
3609
|
-
}
|
|
3610
|
-
trace(method, this.__rtcStatsId, opts);
|
|
3611
|
-
return nativeMethod.apply(this, opts ? [opts] : undefined)
|
|
3612
|
-
.then(function(description) {
|
|
3613
|
-
trace(method + 'OnSuccess', rtcStatsId, description);
|
|
3614
|
-
if (args.length > 0 && typeof args[0] === 'function') {
|
|
3615
|
-
args[0].apply(null, [description]);
|
|
3616
|
-
return undefined;
|
|
3617
|
-
}
|
|
3618
|
-
return description;
|
|
3619
|
-
}, function(err) {
|
|
3620
|
-
trace(method + 'OnFailure', rtcStatsId, err.toString());
|
|
3621
|
-
if (args.length > 1 && typeof args[1] === 'function') {
|
|
3622
|
-
args[1].apply(null, [err]);
|
|
3623
|
-
return;
|
|
3624
|
-
}
|
|
3625
|
-
throw err;
|
|
3626
|
-
});
|
|
3627
|
-
};
|
|
3628
|
-
}
|
|
3629
|
-
});
|
|
4083
|
+
class P2pRtcManager {
|
|
4084
|
+
constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features }) {
|
|
4085
|
+
assert$1.ok(selfId, "selfId is required");
|
|
4086
|
+
assert$1.ok(room, "room is required");
|
|
4087
|
+
assert$1.ok(emitter && emitter.emit, "emitter is required");
|
|
4088
|
+
assert$1.ok(serverSocket instanceof ServerSocket, "serverSocket is required");
|
|
4089
|
+
assert$1.ok(webrtcProvider, "webrtcProvider is required");
|
|
3630
4090
|
|
|
3631
|
-
|
|
3632
|
-
var nativeMethod = origPeerConnection.prototype[method];
|
|
3633
|
-
if (nativeMethod) {
|
|
3634
|
-
origPeerConnection.prototype[method] = function() {
|
|
3635
|
-
var rtcStatsId = this.__rtcStatsId;
|
|
3636
|
-
var args = arguments;
|
|
3637
|
-
trace(method, this.__rtcStatsId, args[0]);
|
|
3638
|
-
return nativeMethod.apply(this, [args[0]])
|
|
3639
|
-
.then(function() {
|
|
3640
|
-
trace(method + 'OnSuccess', rtcStatsId, undefined);
|
|
3641
|
-
if (args.length >= 2 && typeof args[1] === 'function') {
|
|
3642
|
-
args[1].apply(null, []);
|
|
3643
|
-
return undefined;
|
|
3644
|
-
}
|
|
3645
|
-
return undefined;
|
|
3646
|
-
}, function(err) {
|
|
3647
|
-
trace(method + 'OnFailure', rtcStatsId, err.toString());
|
|
3648
|
-
if (args.length >= 3 && typeof args[2] === 'function') {
|
|
3649
|
-
args[2].apply(null, [err]);
|
|
3650
|
-
return undefined;
|
|
3651
|
-
}
|
|
3652
|
-
throw err;
|
|
3653
|
-
});
|
|
3654
|
-
};
|
|
3655
|
-
}
|
|
3656
|
-
});
|
|
4091
|
+
const { name, session, iceServers, sfuServer, mediaserverConfigTtlSeconds } = room;
|
|
3657
4092
|
|
|
3658
|
-
|
|
3659
|
-
|
|
3660
|
-
|
|
3661
|
-
|
|
3662
|
-
|
|
3663
|
-
|
|
3664
|
-
|
|
3665
|
-
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
3670
|
-
|
|
4093
|
+
this._selfId = selfId;
|
|
4094
|
+
this._roomName = name;
|
|
4095
|
+
this._roomSessionId = session && session.id;
|
|
4096
|
+
this.peerConnections = {};
|
|
4097
|
+
this.localStreams = {};
|
|
4098
|
+
this.enabledLocalStreamIds = [];
|
|
4099
|
+
this._screenshareVideoTrackIds = [];
|
|
4100
|
+
this._socketListenerDeregisterFunctions = [];
|
|
4101
|
+
this._localStreamDeregisterFunction = null;
|
|
4102
|
+
this._emitter = emitter;
|
|
4103
|
+
this._serverSocket = serverSocket;
|
|
4104
|
+
this._webrtcProvider = webrtcProvider;
|
|
4105
|
+
this._features = features || {};
|
|
4106
|
+
this._isAudioOnlyMode = false;
|
|
3671
4107
|
|
|
3672
|
-
|
|
3673
|
-
|
|
3674
|
-
var name = prefix + (prefix.length ? 'GetUserMedia' : 'getUserMedia');
|
|
3675
|
-
if (!navigator[name]) {
|
|
3676
|
-
return;
|
|
3677
|
-
}
|
|
3678
|
-
var origGetUserMedia = navigator[name].bind(navigator);
|
|
3679
|
-
var gum = function() {
|
|
3680
|
-
trace('getUserMedia', null, arguments[0]);
|
|
3681
|
-
var cb = arguments[1];
|
|
3682
|
-
var eb = arguments[2];
|
|
3683
|
-
origGetUserMedia(arguments[0],
|
|
3684
|
-
function(stream) {
|
|
3685
|
-
// we log the stream id, track ids and tracks readystate since that is ended GUM fails
|
|
3686
|
-
// to acquire the cam (in chrome)
|
|
3687
|
-
trace('getUserMediaOnSuccess', null, dumpStream(stream));
|
|
3688
|
-
if (cb) {
|
|
3689
|
-
cb(stream);
|
|
3690
|
-
}
|
|
3691
|
-
},
|
|
3692
|
-
function(err) {
|
|
3693
|
-
trace('getUserMediaOnFailure', null, err.name);
|
|
3694
|
-
if (eb) {
|
|
3695
|
-
eb(err);
|
|
3696
|
-
}
|
|
3697
|
-
}
|
|
3698
|
-
);
|
|
3699
|
-
};
|
|
3700
|
-
navigator[name] = gum.bind(navigator);
|
|
3701
|
-
});
|
|
4108
|
+
this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
|
|
4109
|
+
this._pendingActionsForConnectedPeerConnections = [];
|
|
3702
4110
|
|
|
3703
|
-
|
|
3704
|
-
|
|
3705
|
-
|
|
3706
|
-
|
|
3707
|
-
|
|
3708
|
-
|
|
3709
|
-
|
|
3710
|
-
|
|
3711
|
-
|
|
3712
|
-
|
|
3713
|
-
|
|
3714
|
-
|
|
3715
|
-
|
|
3716
|
-
|
|
3717
|
-
}
|
|
4111
|
+
this._audioTrackOnEnded = () => {
|
|
4112
|
+
// There are a couple of reasons the microphone could stop working.
|
|
4113
|
+
// One of them is getting unplugged. The other is the Chrome audio
|
|
4114
|
+
// process crashing. The third is the tab being closed.
|
|
4115
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=1050008
|
|
4116
|
+
rtcStats.sendEvent("audio_ended", { unloading: unloading$1 });
|
|
4117
|
+
this._emit(rtcManagerEvents.MICROPHONE_STOPPED_WORKING, {});
|
|
4118
|
+
};
|
|
4119
|
+
|
|
4120
|
+
this._updateAndScheduleMediaServersRefresh({
|
|
4121
|
+
sfuServer,
|
|
4122
|
+
iceServers: iceServers.iceServers || [],
|
|
4123
|
+
mediaserverConfigTtlSeconds,
|
|
4124
|
+
});
|
|
3718
4125
|
|
|
3719
|
-
|
|
3720
|
-
|
|
3721
|
-
var origGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
|
|
3722
|
-
var gdm = function() {
|
|
3723
|
-
trace('navigator.mediaDevices.getDisplayMedia', null, arguments[0]);
|
|
3724
|
-
return origGetDisplayMedia.apply(navigator.mediaDevices, arguments)
|
|
3725
|
-
.then(function(stream) {
|
|
3726
|
-
trace('navigator.mediaDevices.getDisplayMediaOnSuccess', null, dumpStream(stream));
|
|
3727
|
-
return stream;
|
|
3728
|
-
}, function(err) {
|
|
3729
|
-
trace('navigator.mediaDevices.getDisplayMediaOnFailure', null, err.name);
|
|
3730
|
-
return Promise.reject(err);
|
|
3731
|
-
});
|
|
3732
|
-
};
|
|
3733
|
-
navigator.mediaDevices.getDisplayMedia = gdm.bind(navigator.mediaDevices);
|
|
3734
|
-
}
|
|
4126
|
+
this.totalSessionsCreated = 0;
|
|
4127
|
+
}
|
|
3735
4128
|
|
|
3736
|
-
|
|
3737
|
-
|
|
3738
|
-
/*
|
|
3739
|
-
Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
|
|
3740
|
-
set: function(value) {
|
|
3741
|
-
trace('MediaStreamTrackEnable', this, value);
|
|
4129
|
+
numberOfPeerconnections() {
|
|
4130
|
+
return Object.keys(this.peerConnections).length;
|
|
3742
4131
|
}
|
|
3743
|
-
});
|
|
3744
|
-
*/
|
|
3745
4132
|
|
|
3746
|
-
|
|
3747
|
-
|
|
3748
|
-
prevById = {};
|
|
4133
|
+
isInitializedWith({ selfId, roomName, isSfu }) {
|
|
4134
|
+
return this._selfId === selfId && this._roomName === roomName && isSfu === !!this._sfuServer;
|
|
3749
4135
|
}
|
|
3750
|
-
}
|
|
3751
4136
|
|
|
3752
|
-
|
|
4137
|
+
supportsScreenShareAudio() {
|
|
4138
|
+
return true;
|
|
4139
|
+
}
|
|
3753
4140
|
|
|
3754
|
-
|
|
4141
|
+
maybeRestrictRelayBandwidth(session) {
|
|
4142
|
+
session.maybeRestrictRelayBandwidth();
|
|
4143
|
+
}
|
|
3755
4144
|
|
|
3756
|
-
|
|
4145
|
+
addNewStream(streamId, stream) {
|
|
4146
|
+
if (stream === this.localStreams[streamId]) {
|
|
4147
|
+
// this can happen after reconnect. We do not want to add the stream to the
|
|
4148
|
+
// peerconnection again.
|
|
4149
|
+
return;
|
|
4150
|
+
}
|
|
3757
4151
|
|
|
3758
|
-
|
|
4152
|
+
this._addLocalStream(streamId, stream);
|
|
3759
4153
|
|
|
3760
|
-
|
|
3761
|
-
|
|
3762
|
-
const
|
|
4154
|
+
if (streamId === CAMERA_STREAM_ID) {
|
|
4155
|
+
this._addStreamToPeerConnections(stream);
|
|
4156
|
+
const [audioTrack] = stream.getAudioTracks();
|
|
4157
|
+
if (audioTrack) {
|
|
4158
|
+
this._startMonitoringAudioTrack(audioTrack);
|
|
4159
|
+
}
|
|
3763
4160
|
|
|
3764
|
-
|
|
3765
|
-
|
|
3766
|
-
|
|
3767
|
-
|
|
4161
|
+
// This should not be needed, but checking nonetheless
|
|
4162
|
+
if (this._localStreamDeregisterFunction) {
|
|
4163
|
+
this._localStreamDeregisterFunction();
|
|
4164
|
+
this._localStreamDeregisterFunction = null;
|
|
4165
|
+
}
|
|
3768
4166
|
|
|
3769
|
-
const
|
|
3770
|
-
|
|
4167
|
+
const localStreamHandler = (e) => {
|
|
4168
|
+
const { enable, track } = e.detail;
|
|
4169
|
+
this._handleStopOrResumeVideo({ enable, track });
|
|
4170
|
+
};
|
|
3771
4171
|
|
|
3772
|
-
|
|
3773
|
-
|
|
3774
|
-
|
|
3775
|
-
|
|
3776
|
-
let organizationId;
|
|
3777
|
-
let clientId;
|
|
3778
|
-
let displayName;
|
|
3779
|
-
let userRole;
|
|
3780
|
-
let roomSessionId;
|
|
3781
|
-
let connectionShouldBeOpen;
|
|
3782
|
-
let connectionAttempt = 0;
|
|
3783
|
-
let hasPassedOnRoomSessionId = false;
|
|
3784
|
-
let getStatsBufferUsed = 0;
|
|
4172
|
+
stream.addEventListener("stopresumevideo", localStreamHandler);
|
|
4173
|
+
this._localStreamDeregisterFunction = () => {
|
|
4174
|
+
stream.removeEventListener("stopresumevideo", localStreamHandler);
|
|
4175
|
+
};
|
|
3785
4176
|
|
|
3786
|
-
|
|
3787
|
-
|
|
3788
|
-
trace: (...args) => {
|
|
3789
|
-
args.push(Date.now());
|
|
4177
|
+
return;
|
|
4178
|
+
}
|
|
3790
4179
|
|
|
3791
|
-
|
|
3792
|
-
|
|
3793
|
-
|
|
3794
|
-
|
|
4180
|
+
// at this point it is clearly a screensharing stream.
|
|
4181
|
+
this._screenshareVideoTrackIds.push(stream.getVideoTracks()[0].id);
|
|
4182
|
+
this._shareScreen(streamId, stream);
|
|
4183
|
+
return;
|
|
4184
|
+
}
|
|
3795
4185
|
|
|
3796
|
-
|
|
3797
|
-
|
|
3798
|
-
|
|
3799
|
-
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
return;
|
|
3806
|
-
}
|
|
3807
|
-
}
|
|
3808
|
-
if (newRoomSessionIdValue) hasPassedOnRoomSessionId = true;
|
|
3809
|
-
} else if (args[0] === "customEvent" && args[2].type === "clientId") {
|
|
3810
|
-
clientId = args;
|
|
3811
|
-
} else if (args[0] === "customEvent" && args[2].type === "organizationId") {
|
|
3812
|
-
organizationId = args;
|
|
3813
|
-
} else if (args[0] === "customEvent" && args[2].type === "displayName") {
|
|
3814
|
-
displayName = args;
|
|
3815
|
-
} else if (args[0] === "customEvent" && args[2].type === "userRole") {
|
|
3816
|
-
userRole = args;
|
|
3817
|
-
}
|
|
4186
|
+
replaceTrack(oldTrack, newTrack) {
|
|
4187
|
+
if (oldTrack && oldTrack.kind === "audio") {
|
|
4188
|
+
this._stopMonitoringAudioTrack(oldTrack);
|
|
4189
|
+
}
|
|
4190
|
+
if (newTrack && newTrack.kind === "audio") {
|
|
4191
|
+
this._startMonitoringAudioTrack(newTrack);
|
|
4192
|
+
}
|
|
4193
|
+
return this._replaceTrackToPeerConnections(oldTrack, newTrack);
|
|
4194
|
+
}
|
|
3818
4195
|
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
} else if (args[0] === "getstats") {
|
|
3823
|
-
// only buffer getStats for a while
|
|
3824
|
-
// we don't want this to pile up, but we need at least the initial reports
|
|
3825
|
-
if (getStatsBufferUsed < GETSTATS_BUFFER_SIZE) {
|
|
3826
|
-
getStatsBufferUsed++;
|
|
3827
|
-
buffer.push(args);
|
|
3828
|
-
}
|
|
3829
|
-
} else if (args[0] === "customEvent" && args[2].type === "insightsStats") ; else {
|
|
3830
|
-
// buffer everything else
|
|
3831
|
-
buffer.push(args);
|
|
3832
|
-
}
|
|
4196
|
+
accept({ clientId, shouldAddLocalVideo }) {
|
|
4197
|
+
return this.acceptNewStream({ streamId: clientId, clientId, shouldAddLocalVideo });
|
|
4198
|
+
}
|
|
3833
4199
|
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
|
|
3842
|
-
|
|
3843
|
-
close: () => {
|
|
3844
|
-
connectionShouldBeOpen = false;
|
|
3845
|
-
if (ws) {
|
|
3846
|
-
ws.close();
|
|
3847
|
-
}
|
|
3848
|
-
},
|
|
3849
|
-
connect: () => {
|
|
3850
|
-
connectionShouldBeOpen = true;
|
|
3851
|
-
connectionAttempt += 1;
|
|
3852
|
-
if (ws) {
|
|
3853
|
-
ws.close();
|
|
3854
|
-
}
|
|
3855
|
-
connection.connected = true;
|
|
3856
|
-
ws = new WebSocket(wsURL + window.location.pathname, RTCSTATS_PROTOCOL_VERSION);
|
|
4200
|
+
disconnectAll() {
|
|
4201
|
+
Object.keys(this.peerConnections).forEach((peerConnectionId) => {
|
|
4202
|
+
this.disconnect(peerConnectionId);
|
|
4203
|
+
});
|
|
4204
|
+
this.peerConnections = {};
|
|
4205
|
+
this._socketListenerDeregisterFunctions.forEach((func) => {
|
|
4206
|
+
func();
|
|
4207
|
+
});
|
|
4208
|
+
this._socketListenerDeregisterFunctions = [];
|
|
3857
4209
|
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
connection.connected = false;
|
|
3864
|
-
logger.info(`[RTCSTATS] Closed ${e.code}`);
|
|
3865
|
-
resetDelta();
|
|
3866
|
-
};
|
|
3867
|
-
ws.onopen = () => {
|
|
3868
|
-
// send client info after each connection, so analysis tools can handle reconnections
|
|
3869
|
-
clientInfo.connectionNumber++;
|
|
3870
|
-
ws.send(JSON.stringify(["clientInfo", null, clientInfo]));
|
|
4210
|
+
if (this._localStreamDeregisterFunction) {
|
|
4211
|
+
this._localStreamDeregisterFunction();
|
|
4212
|
+
this._localStreamDeregisterFunction = null;
|
|
4213
|
+
}
|
|
4214
|
+
}
|
|
3871
4215
|
|
|
3872
|
-
|
|
3873
|
-
|
|
4216
|
+
/* the Chrome audio process crashed (probably?)
|
|
4217
|
+
* try to fix it. Constraints are the audio device constraints
|
|
4218
|
+
* used in getUserMedia.
|
|
4219
|
+
*/
|
|
4220
|
+
fixChromeAudio(constraints) {
|
|
4221
|
+
if (browserName$1 !== "chrome") {
|
|
4222
|
+
return;
|
|
4223
|
+
}
|
|
4224
|
+
const localStream = this._getLocalCameraStream();
|
|
4225
|
+
const audioTrack = localStream.getAudioTracks()[0];
|
|
4226
|
+
if (!audioTrack || audioTrack.readyState !== "ended") {
|
|
4227
|
+
return;
|
|
4228
|
+
}
|
|
4229
|
+
return navigator.mediaDevices.getUserMedia({ audio: constraints }).then((stream) => {
|
|
4230
|
+
const track = stream.getAudioTracks()[0];
|
|
4231
|
+
track.enabled = audioTrack.enabled; // retain mute state and don't accidentally unmute.
|
|
4232
|
+
localStream.removeTrack(audioTrack); // remove the old track.
|
|
4233
|
+
localStream.addTrack(track); // add the new track.
|
|
4234
|
+
return this.replaceTrack(audioTrack, track);
|
|
4235
|
+
});
|
|
4236
|
+
}
|
|
4237
|
+
|
|
4238
|
+
setupSocketListeners() {
|
|
4239
|
+
this._socketListenerDeregisterFunctions = [
|
|
4240
|
+
() => this._clearMediaServersRefresh(),
|
|
4241
|
+
|
|
4242
|
+
this._serverSocket.on(PROTOCOL_RESPONSES.MEDIASERVER_CONFIG, (data) => {
|
|
4243
|
+
if (data.error) {
|
|
4244
|
+
logger$4.warn("FETCH_MEDIASERVER_CONFIG failed:", data.error);
|
|
4245
|
+
return;
|
|
3874
4246
|
}
|
|
4247
|
+
this._updateAndScheduleMediaServersRefresh(data);
|
|
4248
|
+
}),
|
|
3875
4249
|
|
|
3876
|
-
|
|
3877
|
-
|
|
4250
|
+
this._serverSocket.on(RELAY_MESSAGES.READY_TO_RECEIVE_OFFER, (data) => {
|
|
4251
|
+
this._connect(data.clientId);
|
|
4252
|
+
}),
|
|
4253
|
+
|
|
4254
|
+
this._serverSocket.on(RELAY_MESSAGES.ICE_CANDIDATE, (data) => {
|
|
4255
|
+
const session = this._getSession(data.clientId);
|
|
4256
|
+
if (!session) {
|
|
4257
|
+
logger$4.warn("No RTCPeerConnection on ICE_CANDIDATE", data);
|
|
4258
|
+
return;
|
|
4259
|
+
}
|
|
4260
|
+
session.addIceCandidate(data.message);
|
|
4261
|
+
}),
|
|
4262
|
+
|
|
4263
|
+
this._serverSocket.on(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, (data) => {
|
|
4264
|
+
const session = this._getSession(data.clientId);
|
|
4265
|
+
if (!session) {
|
|
4266
|
+
logger$4.warn("No RTCPeerConnection on ICE_END_OF_CANDIDATES", data);
|
|
4267
|
+
return;
|
|
3878
4268
|
}
|
|
4269
|
+
session.addIceCandidate(null);
|
|
4270
|
+
}),
|
|
3879
4271
|
|
|
3880
|
-
|
|
3881
|
-
|
|
3882
|
-
|
|
3883
|
-
if (
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
if (userRole) {
|
|
3887
|
-
ws.send(JSON.stringify(userRole));
|
|
4272
|
+
// when a new SDP offer is received from another client
|
|
4273
|
+
this._serverSocket.on(RELAY_MESSAGES.SDP_OFFER, (data) => {
|
|
4274
|
+
const session = this._getSession(data.clientId);
|
|
4275
|
+
if (!session) {
|
|
4276
|
+
logger$4.warn("No RTCPeerConnection on SDP_OFFER", data);
|
|
4277
|
+
return;
|
|
3888
4278
|
}
|
|
4279
|
+
const offer = this._transformIncomingSdp(data.message, session.pc);
|
|
4280
|
+
session.handleOffer(offer).then((answer) => {
|
|
4281
|
+
this._emitServerEvent(RELAY_MESSAGES.SDP_ANSWER, {
|
|
4282
|
+
receiverId: data.clientId,
|
|
4283
|
+
message: this._transformOutgoingSdp(answer),
|
|
4284
|
+
});
|
|
4285
|
+
});
|
|
4286
|
+
}),
|
|
3889
4287
|
|
|
3890
|
-
|
|
3891
|
-
|
|
3892
|
-
|
|
4288
|
+
// when a new SDP answer is received from another client
|
|
4289
|
+
this._serverSocket.on(RELAY_MESSAGES.SDP_ANSWER, (data) => {
|
|
4290
|
+
const session = this._getSession(data.clientId);
|
|
4291
|
+
if (!session) {
|
|
4292
|
+
logger$4.warn("No RTCPeerConnection on SDP_ANSWER", data);
|
|
4293
|
+
return;
|
|
3893
4294
|
}
|
|
3894
|
-
|
|
3895
|
-
|
|
3896
|
-
|
|
3897
|
-
};
|
|
3898
|
-
connection.connect();
|
|
3899
|
-
return connection;
|
|
3900
|
-
}
|
|
4295
|
+
const answer = this._transformIncomingSdp(data.message, session.pc);
|
|
4296
|
+
session.handleAnswer(answer);
|
|
4297
|
+
}),
|
|
3901
4298
|
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
10000, // query once every 10 seconds.
|
|
3906
|
-
[""] // only shim unprefixed RTCPeerConnecion.
|
|
3907
|
-
);
|
|
3908
|
-
// on node clients this function can be undefined
|
|
3909
|
-
resetDelta = stats?.resetDelta || noop;
|
|
4299
|
+
// if this is a reconnect to signal-server during screen-share we must let signal-server know
|
|
4300
|
+
this._serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room: { sfuServer: isSfu } }) => {
|
|
4301
|
+
if (isSfu || !this._wasScreenSharing) return;
|
|
3910
4302
|
|
|
3911
|
-
const
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
value,
|
|
3916
|
-
});
|
|
3917
|
-
},
|
|
3918
|
-
sendAudioMuted: (muted) => {
|
|
3919
|
-
rtcStats.sendEvent("audio_muted", { muted });
|
|
3920
|
-
},
|
|
3921
|
-
sendVideoMuted: (muted) => {
|
|
3922
|
-
rtcStats.sendEvent("video_muted", { muted });
|
|
3923
|
-
},
|
|
3924
|
-
server,
|
|
3925
|
-
};
|
|
4303
|
+
const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID);
|
|
4304
|
+
if (!screenShareStreamId) {
|
|
4305
|
+
return;
|
|
4306
|
+
}
|
|
3926
4307
|
|
|
3927
|
-
const
|
|
4308
|
+
const screenshareStream = this.localStreams[screenShareStreamId];
|
|
4309
|
+
if (!screenshareStream) {
|
|
4310
|
+
logger$4.warn("screenshare stream %s not found", screenShareStreamId);
|
|
4311
|
+
return;
|
|
4312
|
+
}
|
|
3928
4313
|
|
|
3929
|
-
const
|
|
3930
|
-
const browserName$2 = adapter.browserDetails.browser;
|
|
3931
|
-
const browserVersion = adapter.browserDetails.version;
|
|
4314
|
+
const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
|
|
3932
4315
|
|
|
3933
|
-
|
|
3934
|
-
|
|
3935
|
-
|
|
4316
|
+
this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
|
|
4317
|
+
streamId: screenShareStreamId,
|
|
4318
|
+
hasAudioTrack,
|
|
4319
|
+
});
|
|
4320
|
+
}),
|
|
4321
|
+
];
|
|
4322
|
+
}
|
|
3936
4323
|
|
|
3937
|
-
|
|
3938
|
-
|
|
3939
|
-
|
|
3940
|
-
unloading$1 = true;
|
|
3941
|
-
});
|
|
3942
|
-
}
|
|
4324
|
+
sendAudioMutedStats(muted) {
|
|
4325
|
+
rtcStats.sendEvent("audio_muted", { muted });
|
|
4326
|
+
}
|
|
3943
4327
|
|
|
3944
|
-
|
|
3945
|
-
|
|
3946
|
-
|
|
3947
|
-
assert$1.ok(room, "room is required");
|
|
3948
|
-
assert$1.ok(emitter && emitter.emit, "emitter is required");
|
|
3949
|
-
assert$1.ok(serverSocket instanceof ServerSocket, "serverSocket is required");
|
|
3950
|
-
assert$1.ok(webrtcProvider, "webrtcProvider is required");
|
|
4328
|
+
sendVideoMutedStats(muted) {
|
|
4329
|
+
rtcStats.sendEvent("video_muted", { muted });
|
|
4330
|
+
}
|
|
3951
4331
|
|
|
3952
|
-
|
|
4332
|
+
sendStatsCustomEvent(eventName, data) {
|
|
4333
|
+
rtcStats.sendEvent(eventName, data);
|
|
4334
|
+
}
|
|
3953
4335
|
|
|
3954
|
-
|
|
3955
|
-
|
|
3956
|
-
|
|
3957
|
-
this.peerConnections = {};
|
|
3958
|
-
this.localStreams = {};
|
|
3959
|
-
this.enabledLocalStreamIds = [];
|
|
3960
|
-
this._screenshareVideoTrackIds = [];
|
|
3961
|
-
this._socketListenerDeregisterFunctions = [];
|
|
3962
|
-
this._localStreamDeregisterFunction = null;
|
|
3963
|
-
this._emitter = emitter;
|
|
3964
|
-
this._serverSocket = serverSocket;
|
|
3965
|
-
this._webrtcProvider = webrtcProvider;
|
|
3966
|
-
this._features = features || {};
|
|
3967
|
-
this._isAudioOnlyMode = false;
|
|
4336
|
+
rtcStatsDisconnect() {
|
|
4337
|
+
rtcStats.server.close();
|
|
4338
|
+
}
|
|
3968
4339
|
|
|
3969
|
-
|
|
3970
|
-
|
|
4340
|
+
rtcStatsReconnect() {
|
|
4341
|
+
if (!rtcStats.server.connected) {
|
|
4342
|
+
rtcStats.server.connect();
|
|
4343
|
+
}
|
|
4344
|
+
}
|
|
3971
4345
|
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
// One of them is getting unplugged. The other is the Chrome audio
|
|
3975
|
-
// process crashing. The third is the tab being closed.
|
|
3976
|
-
// https://bugs.chromium.org/p/chromium/issues/detail?id=1050008
|
|
3977
|
-
rtcStats.sendEvent("audio_ended", { unloading: unloading$1 });
|
|
3978
|
-
this._emit(rtcManagerEvents.MICROPHONE_STOPPED_WORKING, {});
|
|
3979
|
-
};
|
|
4346
|
+
setAudioOnly(audioOnly) {
|
|
4347
|
+
this._isAudioOnlyMode = audioOnly;
|
|
3980
4348
|
|
|
3981
|
-
this.
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
4349
|
+
this._forEachPeerConnection((session) => {
|
|
4350
|
+
if (session.hasConnectedPeerConnection()) {
|
|
4351
|
+
this._withForcedRenegotiation(session, () =>
|
|
4352
|
+
session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
|
|
4353
|
+
);
|
|
4354
|
+
}
|
|
3985
4355
|
});
|
|
3986
|
-
|
|
3987
|
-
this.totalSessionsCreated = 0;
|
|
3988
4356
|
}
|
|
3989
4357
|
|
|
3990
|
-
|
|
3991
|
-
|
|
4358
|
+
setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
|
|
4359
|
+
const localScreenshareStream = this._getFirstLocalNonCameraStream();
|
|
4360
|
+
|
|
4361
|
+
this._screenshareVideoTrackIds = [
|
|
4362
|
+
...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
|
|
4363
|
+
...remoteScreenshareVideoTrackIds,
|
|
4364
|
+
];
|
|
3992
4365
|
}
|
|
3993
4366
|
|
|
3994
|
-
|
|
3995
|
-
|
|
4367
|
+
setRoomSessionId(roomSessionId) {
|
|
4368
|
+
this._roomSessionId = roomSessionId;
|
|
3996
4369
|
}
|
|
3997
4370
|
|
|
3998
4371
|
_setConnectionStatus(session, newStatus, clientId) {
|
|
@@ -4035,7 +4408,7 @@ class BaseRtcManager {
|
|
|
4035
4408
|
receiver.playoutDelayHint = MEDIA_JITTER_BUFFER_TARGET / 1000; // seconds
|
|
4036
4409
|
});
|
|
4037
4410
|
} catch (error) {
|
|
4038
|
-
logger$
|
|
4411
|
+
logger$4.error("Error during setting jitter buffer target:", error);
|
|
4039
4412
|
}
|
|
4040
4413
|
}
|
|
4041
4414
|
|
|
@@ -4047,14 +4420,6 @@ class BaseRtcManager {
|
|
|
4047
4420
|
this._emitter.emit(eventName, data);
|
|
4048
4421
|
}
|
|
4049
4422
|
|
|
4050
|
-
isInitializedWith({ selfId, roomName, isSfu }) {
|
|
4051
|
-
return this._selfId === selfId && this._roomName === roomName && isSfu === !!this._sfuServer;
|
|
4052
|
-
}
|
|
4053
|
-
|
|
4054
|
-
supportsScreenShareAudio() {
|
|
4055
|
-
return true;
|
|
4056
|
-
}
|
|
4057
|
-
|
|
4058
4423
|
_addEnabledLocalStreamId(streamId) {
|
|
4059
4424
|
this.enabledLocalStreamIds.push(streamId);
|
|
4060
4425
|
}
|
|
@@ -4080,7 +4445,7 @@ class BaseRtcManager {
|
|
|
4080
4445
|
// Some macs + ios devices have troubles using h264 encoder since safari 14
|
|
4081
4446
|
// this will make them encode VP8 instead if available
|
|
4082
4447
|
const deprioritizeH264Encoding =
|
|
4083
|
-
browserName$
|
|
4448
|
+
browserName$1 === "safari" && browserVersion >= 14 && this._features.deprioritizeH264OnSafari;
|
|
4084
4449
|
|
|
4085
4450
|
this.peerConnections[peerConnectionId] = session = new Session({
|
|
4086
4451
|
peerConnectionId,
|
|
@@ -4097,11 +4462,11 @@ class BaseRtcManager {
|
|
|
4097
4462
|
}
|
|
4098
4463
|
|
|
4099
4464
|
_getLocalCameraStream() {
|
|
4100
|
-
return this.localStreams[CAMERA_STREAM_ID
|
|
4465
|
+
return this.localStreams[CAMERA_STREAM_ID];
|
|
4101
4466
|
}
|
|
4102
4467
|
|
|
4103
4468
|
_getNonLocalCameraStreamIds() {
|
|
4104
|
-
return Object.keys(this.localStreams).filter((streamId) => streamId !== CAMERA_STREAM_ID
|
|
4469
|
+
return Object.keys(this.localStreams).filter((streamId) => streamId !== CAMERA_STREAM_ID);
|
|
4105
4470
|
}
|
|
4106
4471
|
|
|
4107
4472
|
_isScreensharingLocally() {
|
|
@@ -4130,7 +4495,7 @@ class BaseRtcManager {
|
|
|
4130
4495
|
}
|
|
4131
4496
|
const session = this._getOrCreateSession(peerConnectionId, initialBandwidth);
|
|
4132
4497
|
const constraints = { optional: [] };
|
|
4133
|
-
if (browserName$
|
|
4498
|
+
if (browserName$1 === "chrome") {
|
|
4134
4499
|
constraints.optional.push({
|
|
4135
4500
|
googCpuOveruseDetection: true,
|
|
4136
4501
|
});
|
|
@@ -4174,7 +4539,7 @@ class BaseRtcManager {
|
|
|
4174
4539
|
}
|
|
4175
4540
|
}
|
|
4176
4541
|
|
|
4177
|
-
if (browserName$
|
|
4542
|
+
if (browserName$1 === "chrome") {
|
|
4178
4543
|
peerConnectionConfig.sdpSemantics = "unified-plan";
|
|
4179
4544
|
}
|
|
4180
4545
|
|
|
@@ -4241,7 +4606,7 @@ class BaseRtcManager {
|
|
|
4241
4606
|
if (
|
|
4242
4607
|
!session.wasEverConnected &&
|
|
4243
4608
|
(pc.iceConnectionState.match(/connected|completed/) ||
|
|
4244
|
-
(browserName$
|
|
4609
|
+
(browserName$1 === "chrome" && pc.localDescription && pc.localDescription.type === "answer"))
|
|
4245
4610
|
) {
|
|
4246
4611
|
session.wasEverConnected = true;
|
|
4247
4612
|
if (this._features.bandwidth !== "false") {
|
|
@@ -4319,7 +4684,7 @@ class BaseRtcManager {
|
|
|
4319
4684
|
}
|
|
4320
4685
|
};
|
|
4321
4686
|
|
|
4322
|
-
const localCameraStream = this.localStreams[CAMERA_STREAM_ID
|
|
4687
|
+
const localCameraStream = this.localStreams[CAMERA_STREAM_ID];
|
|
4323
4688
|
if (shouldAddLocalVideo && localCameraStream) {
|
|
4324
4689
|
session.addStream(localCameraStream);
|
|
4325
4690
|
}
|
|
@@ -4328,7 +4693,7 @@ class BaseRtcManager {
|
|
|
4328
4693
|
// added in a separate session/peerConnection by SfuV2RtcManager
|
|
4329
4694
|
if (shouldAddLocalVideo) {
|
|
4330
4695
|
Object.keys(this.localStreams).forEach((id) => {
|
|
4331
|
-
if (id === CAMERA_STREAM_ID
|
|
4696
|
+
if (id === CAMERA_STREAM_ID) {
|
|
4332
4697
|
return;
|
|
4333
4698
|
}
|
|
4334
4699
|
const screenshareStream = this.localStreams[id];
|
|
@@ -4356,7 +4721,7 @@ class BaseRtcManager {
|
|
|
4356
4721
|
_cleanup(peerConnectionId) {
|
|
4357
4722
|
const session = this._getSession(peerConnectionId);
|
|
4358
4723
|
if (!session) {
|
|
4359
|
-
logger$
|
|
4724
|
+
logger$4.warn("No RTCPeerConnection in RTCManager.disconnect()", peerConnectionId);
|
|
4360
4725
|
return;
|
|
4361
4726
|
}
|
|
4362
4727
|
session.close();
|
|
@@ -4364,8 +4729,7 @@ class BaseRtcManager {
|
|
|
4364
4729
|
}
|
|
4365
4730
|
|
|
4366
4731
|
_forEachPeerConnection(func) {
|
|
4367
|
-
Object.
|
|
4368
|
-
const peerConnection = this.peerConnections[peerConnectionId];
|
|
4732
|
+
Object.values(this.peerConnections).forEach((peerConnection) => {
|
|
4369
4733
|
func(peerConnection);
|
|
4370
4734
|
});
|
|
4371
4735
|
}
|
|
@@ -4386,10 +4750,10 @@ class BaseRtcManager {
|
|
|
4386
4750
|
const promises = [];
|
|
4387
4751
|
this._forEachPeerConnection((session) => {
|
|
4388
4752
|
if (!session.hasConnectedPeerConnection()) {
|
|
4389
|
-
logger$
|
|
4753
|
+
logger$4.info("Session doesn't have a connected PeerConnection, adding pending action!");
|
|
4390
4754
|
const pendingActions = this._pendingActionsForConnectedPeerConnections;
|
|
4391
4755
|
if (!pendingActions) {
|
|
4392
|
-
logger$
|
|
4756
|
+
logger$4.warn(
|
|
4393
4757
|
`No pending action is created to repalce track, because the pending actions array is null`
|
|
4394
4758
|
);
|
|
4395
4759
|
return;
|
|
@@ -4398,7 +4762,7 @@ class BaseRtcManager {
|
|
|
4398
4762
|
const action = () => {
|
|
4399
4763
|
const replacedTrackPromise = session.replaceTrack(oldTrack, newTrack);
|
|
4400
4764
|
if (!replacedTrackPromise) {
|
|
4401
|
-
logger$
|
|
4765
|
+
logger$4.error("replaceTrack returned false!");
|
|
4402
4766
|
reject(`ReplaceTrack returned false`);
|
|
4403
4767
|
return;
|
|
4404
4768
|
}
|
|
@@ -4411,7 +4775,7 @@ class BaseRtcManager {
|
|
|
4411
4775
|
}
|
|
4412
4776
|
const replacedTrackResult = session.replaceTrack(oldTrack, newTrack);
|
|
4413
4777
|
if (!replacedTrackResult) {
|
|
4414
|
-
logger$
|
|
4778
|
+
logger$4.error("replaceTrack returned false!");
|
|
4415
4779
|
return;
|
|
4416
4780
|
}
|
|
4417
4781
|
promises.push(replacedTrackResult);
|
|
@@ -4419,138 +4783,26 @@ class BaseRtcManager {
|
|
|
4419
4783
|
return Promise.all(promises);
|
|
4420
4784
|
}
|
|
4421
4785
|
|
|
4422
|
-
_removeStreamFromPeerConnections(stream) {
|
|
4423
|
-
this._forEachPeerConnection((session) => {
|
|
4424
|
-
this._withForcedRenegotiation(session, () => session.removeStream(stream));
|
|
4425
|
-
});
|
|
4426
|
-
}
|
|
4427
|
-
|
|
4428
|
-
_removeTrackFromPeerConnections(track) {
|
|
4429
|
-
this._forEachPeerConnection((session) => {
|
|
4430
|
-
this._withForcedRenegotiation(session, () => session.removeTrack(track));
|
|
4431
|
-
});
|
|
4432
|
-
}
|
|
4433
|
-
|
|
4434
|
-
_addLocalStream(streamId, stream) {
|
|
4435
|
-
this._addEnabledLocalStreamId(streamId);
|
|
4436
|
-
this.localStreams[streamId] = stream;
|
|
4437
|
-
}
|
|
4438
|
-
|
|
4439
|
-
_removeLocalStream(streamId) {
|
|
4440
|
-
delete this.localStreams[streamId];
|
|
4441
|
-
this._deleteEnabledLocalStreamId(streamId);
|
|
4442
|
-
}
|
|
4443
|
-
|
|
4444
|
-
maybeRestrictRelayBandwidth(session) {
|
|
4445
|
-
session.maybeRestrictRelayBandwidth();
|
|
4446
|
-
}
|
|
4447
|
-
|
|
4448
|
-
addNewStream(streamId, stream) {
|
|
4449
|
-
if (stream === this.localStreams[streamId]) {
|
|
4450
|
-
// this can happen after reconnect. We do not want to add the stream to the
|
|
4451
|
-
// peerconnection again.
|
|
4452
|
-
return;
|
|
4453
|
-
}
|
|
4454
|
-
|
|
4455
|
-
this._addLocalStream(streamId, stream);
|
|
4456
|
-
|
|
4457
|
-
if (streamId === CAMERA_STREAM_ID$1) {
|
|
4458
|
-
this._addStreamToPeerConnections(stream);
|
|
4459
|
-
const [audioTrack] = stream.getAudioTracks();
|
|
4460
|
-
if (audioTrack) {
|
|
4461
|
-
this._startMonitoringAudioTrack(audioTrack);
|
|
4462
|
-
}
|
|
4463
|
-
|
|
4464
|
-
// This should not be needed, but checking nonetheless
|
|
4465
|
-
if (this._localStreamDeregisterFunction) {
|
|
4466
|
-
this._localStreamDeregisterFunction();
|
|
4467
|
-
this._localStreamDeregisterFunction = null;
|
|
4468
|
-
}
|
|
4469
|
-
|
|
4470
|
-
const localStreamHandler = (e) => {
|
|
4471
|
-
const { enable, track } = e.detail;
|
|
4472
|
-
this._handleStopOrResumeVideo({ enable, track });
|
|
4473
|
-
};
|
|
4474
|
-
|
|
4475
|
-
stream.addEventListener("stopresumevideo", localStreamHandler);
|
|
4476
|
-
this._localStreamDeregisterFunction = () => {
|
|
4477
|
-
stream.removeEventListener("stopresumevideo", localStreamHandler);
|
|
4478
|
-
};
|
|
4479
|
-
|
|
4480
|
-
return;
|
|
4481
|
-
}
|
|
4482
|
-
|
|
4483
|
-
// at this point it is clearly a screensharing stream.
|
|
4484
|
-
this._screenshareVideoTrackIds.push(stream.getVideoTracks()[0].id);
|
|
4485
|
-
this._shareScreen(streamId, stream);
|
|
4486
|
-
return;
|
|
4487
|
-
}
|
|
4488
|
-
|
|
4489
|
-
removeStream(streamId, stream) {
|
|
4490
|
-
// essentially we only ever remove screensharing streams.
|
|
4491
|
-
// SFU and P2P provide their own ways of doing this.
|
|
4492
|
-
this._removeLocalStream(streamId, stream);
|
|
4493
|
-
}
|
|
4494
|
-
|
|
4495
|
-
replaceTrack(oldTrack, newTrack) {
|
|
4496
|
-
if (oldTrack && oldTrack.kind === "audio") {
|
|
4497
|
-
this._stopMonitoringAudioTrack(oldTrack);
|
|
4498
|
-
}
|
|
4499
|
-
if (newTrack && newTrack.kind === "audio") {
|
|
4500
|
-
this._startMonitoringAudioTrack(newTrack);
|
|
4501
|
-
}
|
|
4502
|
-
return this._replaceTrackToPeerConnections(oldTrack, newTrack);
|
|
4503
|
-
}
|
|
4504
|
-
|
|
4505
|
-
accept({ clientId, shouldAddLocalVideo }) {
|
|
4506
|
-
return this.acceptNewStream({ streamId: clientId, clientId, shouldAddLocalVideo });
|
|
4507
|
-
}
|
|
4508
|
-
|
|
4509
|
-
disconnectAll() {
|
|
4510
|
-
Object.keys(this.peerConnections).forEach((peerConnectionId) => {
|
|
4511
|
-
this.disconnect(peerConnectionId);
|
|
4512
|
-
});
|
|
4513
|
-
this.peerConnections = {};
|
|
4514
|
-
this._socketListenerDeregisterFunctions.forEach((func) => {
|
|
4515
|
-
func();
|
|
4786
|
+
_removeStreamFromPeerConnections(stream) {
|
|
4787
|
+
this._forEachPeerConnection((session) => {
|
|
4788
|
+
this._withForcedRenegotiation(session, () => session.removeStream(stream));
|
|
4516
4789
|
});
|
|
4517
|
-
this._socketListenerDeregisterFunctions = [];
|
|
4518
|
-
|
|
4519
|
-
if (this._localStreamDeregisterFunction) {
|
|
4520
|
-
this._localStreamDeregisterFunction();
|
|
4521
|
-
this._localStreamDeregisterFunction = null;
|
|
4522
|
-
}
|
|
4523
4790
|
}
|
|
4524
4791
|
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4792
|
+
_removeTrackFromPeerConnections(track) {
|
|
4793
|
+
this._forEachPeerConnection((session) => {
|
|
4794
|
+
this._withForcedRenegotiation(session, () => session.removeTrack(track));
|
|
4795
|
+
});
|
|
4796
|
+
}
|
|
4530
4797
|
|
|
4531
|
-
|
|
4532
|
-
|
|
4798
|
+
_addLocalStream(streamId, stream) {
|
|
4799
|
+
this._addEnabledLocalStreamId(streamId);
|
|
4800
|
+
this.localStreams[streamId] = stream;
|
|
4801
|
+
}
|
|
4533
4802
|
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
*/
|
|
4538
|
-
fixChromeAudio(constraints) {
|
|
4539
|
-
if (browserName$2 !== "chrome") {
|
|
4540
|
-
return;
|
|
4541
|
-
}
|
|
4542
|
-
const localStream = this._getLocalCameraStream();
|
|
4543
|
-
const audioTrack = localStream.getAudioTracks()[0];
|
|
4544
|
-
if (!audioTrack || audioTrack.readyState !== "ended") {
|
|
4545
|
-
return;
|
|
4546
|
-
}
|
|
4547
|
-
return navigator.mediaDevices.getUserMedia({ audio: constraints }).then((stream) => {
|
|
4548
|
-
const track = stream.getAudioTracks()[0];
|
|
4549
|
-
track.enabled = audioTrack.enabled; // retain mute state and don't accidentally unmute.
|
|
4550
|
-
localStream.removeTrack(audioTrack); // remove the old track.
|
|
4551
|
-
localStream.addTrack(track); // add the new track.
|
|
4552
|
-
return this.replaceTrack(audioTrack, track);
|
|
4553
|
-
});
|
|
4803
|
+
_removeLocalStream(streamId) {
|
|
4804
|
+
delete this.localStreams[streamId];
|
|
4805
|
+
this._deleteEnabledLocalStreamId(streamId);
|
|
4554
4806
|
}
|
|
4555
4807
|
|
|
4556
4808
|
_updateAndScheduleMediaServersRefresh({ iceServers, sfuServer, mediaserverConfigTtlSeconds }) {
|
|
@@ -4574,114 +4826,6 @@ class BaseRtcManager {
|
|
|
4574
4826
|
this._fetchMediaServersTimer = null;
|
|
4575
4827
|
}
|
|
4576
4828
|
|
|
4577
|
-
setupSocketListeners() {
|
|
4578
|
-
this._socketListenerDeregisterFunctions = [
|
|
4579
|
-
() => this._clearMediaServersRefresh(),
|
|
4580
|
-
|
|
4581
|
-
this._serverSocket.on(PROTOCOL_RESPONSES.MEDIASERVER_CONFIG, (data) => {
|
|
4582
|
-
if (data.error) {
|
|
4583
|
-
logger$5.warn("FETCH_MEDIASERVER_CONFIG failed:", data.error);
|
|
4584
|
-
return;
|
|
4585
|
-
}
|
|
4586
|
-
this._updateAndScheduleMediaServersRefresh(data);
|
|
4587
|
-
}),
|
|
4588
|
-
|
|
4589
|
-
this._serverSocket.on(RELAY_MESSAGES.READY_TO_RECEIVE_OFFER, (data) => {
|
|
4590
|
-
this._connect(data.clientId);
|
|
4591
|
-
}),
|
|
4592
|
-
|
|
4593
|
-
this._serverSocket.on(RELAY_MESSAGES.ICE_CANDIDATE, (data) => {
|
|
4594
|
-
const session = this._getSession(data.clientId);
|
|
4595
|
-
if (!session) {
|
|
4596
|
-
logger$5.warn("No RTCPeerConnection on ICE_CANDIDATE", data);
|
|
4597
|
-
return;
|
|
4598
|
-
}
|
|
4599
|
-
session.addIceCandidate(data.message);
|
|
4600
|
-
}),
|
|
4601
|
-
|
|
4602
|
-
this._serverSocket.on(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, (data) => {
|
|
4603
|
-
const session = this._getSession(data.clientId);
|
|
4604
|
-
if (!session) {
|
|
4605
|
-
logger$5.warn("No RTCPeerConnection on ICE_END_OF_CANDIDATES", data);
|
|
4606
|
-
return;
|
|
4607
|
-
}
|
|
4608
|
-
session.addIceCandidate(null);
|
|
4609
|
-
}),
|
|
4610
|
-
|
|
4611
|
-
// when a new SDP offer is received from another client
|
|
4612
|
-
this._serverSocket.on(RELAY_MESSAGES.SDP_OFFER, (data) => {
|
|
4613
|
-
const session = this._getSession(data.clientId);
|
|
4614
|
-
if (!session) {
|
|
4615
|
-
logger$5.warn("No RTCPeerConnection on SDP_OFFER", data);
|
|
4616
|
-
return;
|
|
4617
|
-
}
|
|
4618
|
-
const offer = this._transformIncomingSdp(data.message, session.pc);
|
|
4619
|
-
session.handleOffer(offer).then((answer) => {
|
|
4620
|
-
this._emitServerEvent(RELAY_MESSAGES.SDP_ANSWER, {
|
|
4621
|
-
receiverId: data.clientId,
|
|
4622
|
-
message: this._transformOutgoingSdp(answer),
|
|
4623
|
-
});
|
|
4624
|
-
});
|
|
4625
|
-
}),
|
|
4626
|
-
|
|
4627
|
-
// when a new SDP answer is received from another client
|
|
4628
|
-
this._serverSocket.on(RELAY_MESSAGES.SDP_ANSWER, (data) => {
|
|
4629
|
-
const session = this._getSession(data.clientId);
|
|
4630
|
-
if (!session) {
|
|
4631
|
-
logger$5.warn("No RTCPeerConnection on SDP_ANSWER", data);
|
|
4632
|
-
return;
|
|
4633
|
-
}
|
|
4634
|
-
const answer = this._transformIncomingSdp(data.message, session.pc);
|
|
4635
|
-
session.handleAnswer(answer);
|
|
4636
|
-
}),
|
|
4637
|
-
|
|
4638
|
-
// if this is a reconnect to signal-server during screen-share we must let signal-server know
|
|
4639
|
-
this._serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room: { sfuServer: isSfu } }) => {
|
|
4640
|
-
if (isSfu || !this._wasScreenSharing) return;
|
|
4641
|
-
|
|
4642
|
-
const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID$1);
|
|
4643
|
-
if (!screenShareStreamId) {
|
|
4644
|
-
return;
|
|
4645
|
-
}
|
|
4646
|
-
|
|
4647
|
-
const screenshareStream = this.localStreams[screenShareStreamId];
|
|
4648
|
-
if (!screenshareStream) {
|
|
4649
|
-
logger$5.warn("screenshare stream %s not found", screenShareStreamId);
|
|
4650
|
-
return;
|
|
4651
|
-
}
|
|
4652
|
-
|
|
4653
|
-
const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
|
|
4654
|
-
|
|
4655
|
-
this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
|
|
4656
|
-
streamId: screenShareStreamId,
|
|
4657
|
-
hasAudioTrack,
|
|
4658
|
-
});
|
|
4659
|
-
}),
|
|
4660
|
-
];
|
|
4661
|
-
}
|
|
4662
|
-
|
|
4663
|
-
sendAudioMutedStats(muted) {
|
|
4664
|
-
rtcStats.sendEvent("audio_muted", { muted });
|
|
4665
|
-
}
|
|
4666
|
-
|
|
4667
|
-
sendVideoMutedStats(muted) {
|
|
4668
|
-
rtcStats.sendEvent("video_muted", { muted });
|
|
4669
|
-
}
|
|
4670
|
-
|
|
4671
|
-
sendStatsCustomEvent(eventName, data) {
|
|
4672
|
-
rtcStats.sendEvent(eventName, data);
|
|
4673
|
-
}
|
|
4674
|
-
|
|
4675
|
-
rtcStatsDisconnect() {
|
|
4676
|
-
rtcStats.server.close();
|
|
4677
|
-
}
|
|
4678
|
-
|
|
4679
|
-
rtcStatsReconnect() {
|
|
4680
|
-
if (!rtcStats.server.connected) {
|
|
4681
|
-
rtcStats.server.connect();
|
|
4682
|
-
}
|
|
4683
|
-
}
|
|
4684
|
-
|
|
4685
4829
|
_startMonitoringAudioTrack(track) {
|
|
4686
4830
|
track.addEventListener("ended", this._audioTrackOnEnded);
|
|
4687
4831
|
}
|
|
@@ -4689,113 +4833,6 @@ class BaseRtcManager {
|
|
|
4689
4833
|
_stopMonitoringAudioTrack(track) {
|
|
4690
4834
|
track.removeEventListener("ended", this._audioTrackOnEnded);
|
|
4691
4835
|
}
|
|
4692
|
-
|
|
4693
|
-
setAudioOnly(audioOnly) {
|
|
4694
|
-
this._isAudioOnlyMode = audioOnly;
|
|
4695
|
-
|
|
4696
|
-
this._forEachPeerConnection((session) => {
|
|
4697
|
-
if (session.hasConnectedPeerConnection()) {
|
|
4698
|
-
this._withForcedRenegotiation(session, () =>
|
|
4699
|
-
session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
|
|
4700
|
-
);
|
|
4701
|
-
}
|
|
4702
|
-
});
|
|
4703
|
-
}
|
|
4704
|
-
|
|
4705
|
-
setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
|
|
4706
|
-
const localScreenshareStream = this._getFirstLocalNonCameraStream();
|
|
4707
|
-
|
|
4708
|
-
this._screenshareVideoTrackIds = [
|
|
4709
|
-
...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
|
|
4710
|
-
...remoteScreenshareVideoTrackIds,
|
|
4711
|
-
];
|
|
4712
|
-
}
|
|
4713
|
-
|
|
4714
|
-
setRoomSessionId(roomSessionId) {
|
|
4715
|
-
this._roomSessionId = roomSessionId;
|
|
4716
|
-
}
|
|
4717
|
-
}
|
|
4718
|
-
|
|
4719
|
-
const lowPixelCount = 320 * 180;
|
|
4720
|
-
const lowBitratePerPixel = 150000 / lowPixelCount;
|
|
4721
|
-
const highPixelCount = 1280 * 720;
|
|
4722
|
-
const highBitratePerPixel = 1000000 / highPixelCount;
|
|
4723
|
-
const bitrateChangePerPixel = (highBitratePerPixel - lowBitratePerPixel) / (highPixelCount - lowPixelCount);
|
|
4724
|
-
|
|
4725
|
-
// calculates a bitrate for a given resolution+frameRate
|
|
4726
|
-
function getOptimalBitrate(width, height, frameRate) {
|
|
4727
|
-
let targetPixelCount = width * height;
|
|
4728
|
-
if (targetPixelCount < lowPixelCount) targetPixelCount = lowPixelCount;
|
|
4729
|
-
if (targetPixelCount > highPixelCount) targetPixelCount = highPixelCount;
|
|
4730
|
-
|
|
4731
|
-
let targetBitratePerPixel = lowBitratePerPixel;
|
|
4732
|
-
if (targetPixelCount > highPixelCount) targetBitratePerPixel = highBitratePerPixel;
|
|
4733
|
-
else if (targetPixelCount > lowPixelCount) {
|
|
4734
|
-
targetBitratePerPixel += (targetPixelCount - lowPixelCount) * bitrateChangePerPixel;
|
|
4735
|
-
}
|
|
4736
|
-
|
|
4737
|
-
// we use the actual resolution for the target bitrate
|
|
4738
|
-
let targetBitrate = width * height * targetBitratePerPixel;
|
|
4739
|
-
|
|
4740
|
-
// adjust bitrate down a bit if reduced framerate
|
|
4741
|
-
if (frameRate <= 15) targetBitrate = targetBitrate * 0.7;
|
|
4742
|
-
else if (frameRate <= 24) targetBitrate = targetBitrate * 0.9;
|
|
4743
|
-
|
|
4744
|
-
return targetBitrate;
|
|
4745
|
-
}
|
|
4746
|
-
|
|
4747
|
-
// taken from https://github.com/sindresorhus/ip-regex ^5.0.0
|
|
4748
|
-
// inlined because it's import caused errors in browser-sdk when running tests
|
|
4749
|
-
const word = "[a-fA-F\\d:]";
|
|
4750
|
-
|
|
4751
|
-
const boundry = (options) =>
|
|
4752
|
-
options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
|
|
4753
|
-
|
|
4754
|
-
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}";
|
|
4755
|
-
|
|
4756
|
-
const v6segment = "[a-fA-F\\d]{1,4}";
|
|
4757
|
-
|
|
4758
|
-
const v6 = `
|
|
4759
|
-
(?:
|
|
4760
|
-
(?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
|
|
4761
|
-
(?:${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
|
|
4762
|
-
(?:${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
|
|
4763
|
-
(?:${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
|
|
4764
|
-
(?:${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
|
|
4765
|
-
(?:${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
|
|
4766
|
-
(?:${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
|
|
4767
|
-
(?::(?:(?::${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
|
|
4768
|
-
)(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
|
|
4769
|
-
`
|
|
4770
|
-
.replace(/\s*\/\/.*$/gm, "")
|
|
4771
|
-
.replace(/\n/g, "")
|
|
4772
|
-
.trim();
|
|
4773
|
-
|
|
4774
|
-
// Pre-compile only the exact regexes because adding a global flag make regexes stateful
|
|
4775
|
-
const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
|
|
4776
|
-
const v4exact = new RegExp(`^${v4}$`);
|
|
4777
|
-
const v6exact = new RegExp(`^${v6}$`);
|
|
4778
|
-
|
|
4779
|
-
const ipRegex = (options) =>
|
|
4780
|
-
options && options.exact
|
|
4781
|
-
? v46Exact
|
|
4782
|
-
: new RegExp(
|
|
4783
|
-
`(?:${boundry(options)}${v4}${boundry(options)})|(?:${boundry(options)}${v6}${boundry(options)})`,
|
|
4784
|
-
"g"
|
|
4785
|
-
);
|
|
4786
|
-
|
|
4787
|
-
ipRegex.v4 = (options) =>
|
|
4788
|
-
options && options.exact ? v4exact : new RegExp(`${boundry(options)}${v4}${boundry(options)}`, "g");
|
|
4789
|
-
ipRegex.v6 = (options) =>
|
|
4790
|
-
options && options.exact ? v6exact : new RegExp(`${boundry(options)}${v6}${boundry(options)}`, "g");
|
|
4791
|
-
|
|
4792
|
-
const logger$4 = new Logger();
|
|
4793
|
-
|
|
4794
|
-
const ICE_PUBLIC_IP_GATHERING_TIMEOUT = 3 * 1000;
|
|
4795
|
-
const CAMERA_STREAM_ID = RtcStream.getCameraId();
|
|
4796
|
-
const browserName$1 = adapter.browserDetails.browser;
|
|
4797
|
-
|
|
4798
|
-
class P2pRtcManager extends BaseRtcManager {
|
|
4799
4836
|
_connect(clientId) {
|
|
4800
4837
|
this.rtcStatsReconnect();
|
|
4801
4838
|
const shouldAddLocalVideo = true;
|
|
@@ -4903,15 +4940,16 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
4903
4940
|
}
|
|
4904
4941
|
session.isOperationPending = true;
|
|
4905
4942
|
|
|
4906
|
-
const { vp9On, av1On, redOn } = this._features;
|
|
4943
|
+
const { vp9On, av1On, redOn, rtpAbsCaptureTimeOn } = this._features;
|
|
4907
4944
|
|
|
4908
4945
|
// Set codec preferences to video transceivers
|
|
4909
4946
|
if (vp9On || av1On || redOn) {
|
|
4910
4947
|
this._setCodecPreferences(pc, vp9On, av1On, redOn);
|
|
4911
4948
|
}
|
|
4912
|
-
|
|
4913
4949
|
pc.createOffer(constraints || this.offerOptions)
|
|
4914
4950
|
.then((offer) => {
|
|
4951
|
+
// Add https://webrtc.googlesource.com/src/+/refs/heads/main/docs/native-code/rtp-hdrext/abs-capture-time
|
|
4952
|
+
if (rtpAbsCaptureTimeOn) offer.sdp = addAbsCaptureTimeExtMap(offer.sdp);
|
|
4915
4953
|
// SDP munging workaround for Firefox, because it doesn't support setCodecPreferences()
|
|
4916
4954
|
// Only vp9 because FF does not support AV1 yet
|
|
4917
4955
|
if ((vp9On || redOn) && browserName$1 === "firefox") {
|
|
@@ -5270,7 +5308,7 @@ class P2pRtcManager extends BaseRtcManager {
|
|
|
5270
5308
|
}
|
|
5271
5309
|
|
|
5272
5310
|
removeStream(streamId, stream, requestedByClientId) {
|
|
5273
|
-
|
|
5311
|
+
this._removeLocalStream(streamId, stream);
|
|
5274
5312
|
this._removeStreamFromPeerConnections(stream);
|
|
5275
5313
|
this._wasScreenSharing = false;
|
|
5276
5314
|
this._emitServerEvent(PROTOCOL_REQUESTS.STOP_SCREENSHARE, { streamId, requestedByClientId });
|
|
@@ -6168,6 +6206,7 @@ class VegaMediaQualityMonitor extends EventEmitter {
|
|
|
6168
6206
|
}
|
|
6169
6207
|
}
|
|
6170
6208
|
|
|
6209
|
+
const adapter = adapterRaw.default ?? adapterRaw;
|
|
6171
6210
|
const logger = new Logger();
|
|
6172
6211
|
|
|
6173
6212
|
const browserName = adapter.browserDetails.browser;
|
|
@@ -7978,6 +8017,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
|
7978
8017
|
const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
|
|
7979
8018
|
const isCameraEnabled = selectIsCameraEnabled(state);
|
|
7980
8019
|
const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
|
|
8020
|
+
const isNodeSdk = selectAppIsNodeSdk(state);
|
|
7981
8021
|
if (dispatcher) {
|
|
7982
8022
|
return;
|
|
7983
8023
|
}
|
|
@@ -8003,6 +8043,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
|
|
|
8003
8043
|
vp9On: false,
|
|
8004
8044
|
h264On: false,
|
|
8005
8045
|
simulcastScreenshareOn: false,
|
|
8046
|
+
deviceHandlerFactory: isNodeSdk ? Chrome111.createFactory() : undefined,
|
|
8006
8047
|
},
|
|
8007
8048
|
});
|
|
8008
8049
|
dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
|
|
@@ -9459,7 +9500,7 @@ class LocalParticipant extends RoomParticipant {
|
|
|
9459
9500
|
}
|
|
9460
9501
|
}
|
|
9461
9502
|
|
|
9462
|
-
const sdkVersion = "0.
|
|
9503
|
+
const sdkVersion = "0.4.0";
|
|
9463
9504
|
|
|
9464
9505
|
const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
|
|
9465
9506
|
const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
|
|
@@ -9531,4 +9572,4 @@ function createServices() {
|
|
|
9531
9572
|
};
|
|
9532
9573
|
}
|
|
9533
9574
|
|
|
9534
|
-
export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, appLeft, appSlice, chatSlice, cloudRecordingSlice, createAppAsyncThunk, createAppThunk, createReactor, createServices, createStore, createWebRtcEmitter, deviceBusy, deviceCredentialsSlice, deviceIdentified, deviceIdentifying, doAcceptWaitingParticipant, doAppJoin, doConnectRoom, doConnectRtc, doDisconnectRtc, doEnableAudio, doEnableVideo, doGetDeviceCredentials, doHandleAcceptStreams, doHandleStreamingStarted, doHandleStreamingStopped, doKnockRoom, doOrganizationFetch, doRejectWaitingParticipant, doRtcAnalyticsCustomEventsInitialize, doRtcManagerCreated, doRtcManagerInitialize, doRtcReportStreamResolution, doSendChatMessage, doSetDevice, doSetDisplayName, doSetLocalParticipant, doSignalDisconnect, doSignalIdentifyDevice, doSignalReconnect, doSignalSocketConnect, doStartCloudRecording, doStartLocalMedia, doStartScreenshare, doStopCloudRecording, doStopLocalMedia, doStopScreenshare, doSwitchLocalStream, doToggleCamera, doUpdateDeviceList, initialCloudRecordingState, initialLocalMediaState, isAcceptingStreams, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, observeStore, organizationSlice, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, resolutionReported, roomConnectionSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, sdkVersion, selectAppDisplayName, selectAppExternalId, selectAppRaw, selectAppRoomKey, selectAppRoomName, selectAppRoomUrl, selectAppSdkVersion, selectAppWantsToJoin, selectBusyDeviceIds, selectCameraDeviceError, selectCameraDevices, selectChatMessages, selectChatRaw, selectCloudRecordingError, selectCloudRecordingRaw, selectCloudRecordingStartedAt, selectCloudRecordingStatus, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectDeviceCredentialsRaw, selectDeviceId, selectHasFetchedDeviceCredentials, selectIsAcceptingStreams, selectIsCameraEnabled, selectIsCloudRecording, selectIsLocalMediaStarting, selectIsMicrophoneEnabled, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsToggleCamera, selectLocalMediaConstraintsOptions, selectLocalMediaDevices, selectLocalMediaIsSwitchingStream, selectLocalMediaOptions, selectLocalMediaOwnsStream, selectLocalMediaRaw, selectLocalMediaShouldStartWithOptions, selectLocalMediaShouldStop, selectLocalMediaStartError, selectLocalMediaStatus, selectLocalMediaStream, selectLocalParticipantIsScreenSharing, selectLocalParticipantRaw, selectLocalParticipantRole, selectLocalScreenshareRaw, selectLocalScreenshareStatus, selectLocalScreenshareStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectOrganizationId, selectOrganizationRaw, selectRemoteParticipants, selectRemoteParticipantsRaw, selectRoomConnectionRaw, selectRoomConnectionSession, selectRoomConnectionSessionId, selectRoomConnectionStatus, selectRtcConnectionRaw, selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectRtcManager, selectRtcManagerInitialized, selectRtcStatus, selectScreenshares, selectSelfId, selectShouldConnectRoom, selectShouldConnectRtc, selectShouldConnectSignal, selectShouldDisconnectRtc, selectShouldFetchDeviceCredentials, selectShouldFetchOrganization, selectShouldIdentifyDevice, selectShouldInitializeRtc, selectSignalConnectionDeviceIdentified, selectSignalConnectionRaw, selectSignalConnectionSocket, selectSignalIsIdentifyingDevice, selectSignalStatus, selectSpeakerDevices, selectStreamingRaw, selectStreamsToAccept, selectWaitingParticipants, selectWaitingParticipantsRaw, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setLocalMediaOptions, setLocalMediaStream, setRoomKey, signalConnectionSlice, socketConnected, socketConnecting, socketDisconnected, socketReconnecting, startAppListening, stopScreenshare, streamStatusUpdated, streamingSlice, toggleCameraEnabled, toggleMicrophoneEnabled, updateReportedValues, waitingParticipantsSlice };
|
|
9575
|
+
export { ApiClient, Credentials, CredentialsService, LocalParticipant, OrganizationApiClient, OrganizationService, OrganizationServiceCache, RoomService, addAppListener, appLeft, appSlice, chatSlice, cloudRecordingSlice, createAppAsyncThunk, createAppThunk, createReactor, createServices, createStore, createWebRtcEmitter, deviceBusy, deviceCredentialsSlice, deviceIdentified, deviceIdentifying, doAcceptWaitingParticipant, doAppJoin, doConnectRoom, doConnectRtc, doDisconnectRtc, doEnableAudio, doEnableVideo, doGetDeviceCredentials, doHandleAcceptStreams, doHandleStreamingStarted, doHandleStreamingStopped, doKnockRoom, doOrganizationFetch, doRejectWaitingParticipant, doRtcAnalyticsCustomEventsInitialize, doRtcManagerCreated, doRtcManagerInitialize, doRtcReportStreamResolution, doSendChatMessage, doSetDevice, doSetDisplayName, doSetLocalParticipant, doSignalDisconnect, doSignalIdentifyDevice, doSignalReconnect, doSignalSocketConnect, doStartCloudRecording, doStartLocalMedia, doStartScreenshare, doStopCloudRecording, doStopLocalMedia, doStopScreenshare, doSwitchLocalStream, doToggleCamera, doUpdateDeviceList, initialCloudRecordingState, initialLocalMediaState, isAcceptingStreams, listenerMiddleware, localMediaSlice, localMediaStopped, localParticipantSlice, localScreenshareSlice, localStreamMetadataUpdated, observeStore, organizationSlice, participantStreamAdded, participantStreamIdAdded, recordingRequestStarted, remoteParticipantsSlice, resolutionReported, roomConnectionSlice, rootReducer, rtcAnalyticsCustomEvents, rtcAnalyticsSlice, rtcConnectionSlice, rtcDisconnected, rtcDispatcherCreated, rtcManagerCreated, rtcManagerDestroyed, rtcManagerInitialized, sdkVersion, selectAppDisplayName, selectAppExternalId, selectAppIsNodeSdk, selectAppRaw, selectAppRoomKey, selectAppRoomName, selectAppRoomUrl, selectAppSdkVersion, selectAppWantsToJoin, selectBusyDeviceIds, selectCameraDeviceError, selectCameraDevices, selectChatMessages, selectChatRaw, selectCloudRecordingError, selectCloudRecordingRaw, selectCloudRecordingStartedAt, selectCloudRecordingStatus, selectCurrentCameraDeviceId, selectCurrentMicrophoneDeviceId, selectDeviceCredentialsRaw, selectDeviceId, selectHasFetchedDeviceCredentials, selectIsAcceptingStreams, selectIsCameraEnabled, selectIsCloudRecording, selectIsLocalMediaStarting, selectIsMicrophoneEnabled, selectIsSettingCameraDevice, selectIsSettingMicrophoneDevice, selectIsToggleCamera, selectLocalMediaConstraintsOptions, selectLocalMediaDevices, selectLocalMediaIsSwitchingStream, selectLocalMediaOptions, selectLocalMediaOwnsStream, selectLocalMediaRaw, selectLocalMediaShouldStartWithOptions, selectLocalMediaShouldStop, selectLocalMediaStartError, selectLocalMediaStatus, selectLocalMediaStream, selectLocalParticipantIsScreenSharing, selectLocalParticipantRaw, selectLocalParticipantRole, selectLocalScreenshareRaw, selectLocalScreenshareStatus, selectLocalScreenshareStream, selectMicrophoneDeviceError, selectMicrophoneDevices, selectOrganizationId, selectOrganizationRaw, selectRemoteParticipants, selectRemoteParticipantsRaw, selectRoomConnectionRaw, selectRoomConnectionSession, selectRoomConnectionSessionId, selectRoomConnectionStatus, selectRtcConnectionRaw, selectRtcDispatcherCreated, selectRtcIsCreatingDispatcher, selectRtcManager, selectRtcManagerInitialized, selectRtcStatus, selectScreenshares, selectSelfId, selectShouldConnectRoom, selectShouldConnectRtc, selectShouldConnectSignal, selectShouldDisconnectRtc, selectShouldFetchDeviceCredentials, selectShouldFetchOrganization, selectShouldIdentifyDevice, selectShouldInitializeRtc, selectSignalConnectionDeviceIdentified, selectSignalConnectionRaw, selectSignalConnectionSocket, selectSignalIsIdentifyingDevice, selectSignalStatus, selectSpeakerDevices, selectStreamingRaw, selectStreamsToAccept, selectWaitingParticipants, selectWaitingParticipantsRaw, setCurrentCameraDeviceId, setCurrentMicrophoneDeviceId, setLocalMediaOptions, setLocalMediaStream, setRoomKey, signalConnectionSlice, socketConnected, socketConnecting, socketDisconnected, socketReconnecting, startAppListening, stopScreenshare, streamStatusUpdated, streamingSlice, toggleCameraEnabled, toggleMicrophoneEnabled, updateReportedValues, waitingParticipantsSlice };
|