@whereby.com/core 0.3.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.
Files changed (3) hide show
  1. package/dist/index.d.ts +49 -7
  2. package/dist/index.js +1181 -1145
  3. 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 adapter from 'webrtc-adapter';
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}`);
@@ -386,7 +389,7 @@ const PROTOCOL_EVENTS = {
386
389
  MEDIA_QUALITY_CHANGED: "media_quality_changed",
387
390
  };
388
391
 
389
- const logger$9 = new Logger();
392
+ const logger$8 = new Logger();
390
393
 
391
394
  class ReconnectManager extends EventEmitter {
392
395
  constructor(socket) {
@@ -501,7 +504,7 @@ class ReconnectManager extends EventEmitter {
501
504
 
502
505
  client.mergeWithOldClientState = true;
503
506
  } catch (error) {
504
- logger$9.error("Failed to evaluate if we should merge client state %o", error);
507
+ logger$8.error("Failed to evaluate if we should merge client state %o", error);
505
508
  this.metrics.evaluationFailed++;
506
509
  }
507
510
  });
@@ -538,7 +541,7 @@ class ReconnectManager extends EventEmitter {
538
541
  const client = this._clients[clientId];
539
542
 
540
543
  if (!client) {
541
- logger$9.warn(`client ${clientId} not found`);
544
+ logger$8.warn(`client ${clientId} not found`);
542
545
  return;
543
546
  }
544
547
 
@@ -708,6 +711,8 @@ class ReconnectManager extends EventEmitter {
708
711
  }
709
712
  }
710
713
 
714
+ const adapter$6 = adapterRaw.default ?? adapterRaw;
715
+
711
716
  const DEFAULT_SOCKET_PATH = "/protocol/socket.io/v4";
712
717
 
713
718
  const NOOP_KEEPALIVE_INTERVAL = 2000;
@@ -737,7 +742,7 @@ class ServerSocket {
737
742
  // safari doesn't support cross doamin cookies making load-balancer stickiness not work
738
743
  // and if socket.io reconnects to another signal instance with polling it will fail
739
744
  // remove if we move signal to a whereby.com subdomain
740
- if (adapter.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
745
+ if (adapter$6.browserDetails.browser !== "safari") delete this._wasConnectedUsingWebsocket;
741
746
  } else {
742
747
  this._socket.io.opts.transports = ["websocket", "polling"];
743
748
  }
@@ -1108,7 +1113,9 @@ const selectCloudRecordingStartedAt = (state) => state.cloudRecording.startedAt;
1108
1113
  const selectCloudRecordingError = (state) => state.cloudRecording.error;
1109
1114
  const selectIsCloudRecording = (state) => state.cloudRecording.isRecording;
1110
1115
 
1111
- const isSafari = adapter.browserDetails.browser === "safari";
1116
+ const adapter$5 = adapterRaw.default ?? adapterRaw;
1117
+
1118
+ const isSafari = adapter$5.browserDetails.browser === "safari";
1112
1119
 
1113
1120
  // Expects format 640x360@25, returns [width, height, fps]
1114
1121
  const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10));
@@ -1119,7 +1126,6 @@ const parseResolution = (res) => res.split(/[^\d]/g).map((n) => parseInt(n, 10))
1119
1126
  function getMediaConstraints({
1120
1127
  disableAEC,
1121
1128
  disableAGC,
1122
- fps24,
1123
1129
  hd,
1124
1130
  lax,
1125
1131
  lowDataMode,
@@ -1130,7 +1136,6 @@ function getMediaConstraints({
1130
1136
  }) {
1131
1137
  let HIGH_HEIGHT = 480;
1132
1138
  let LOW_HEIGHT = 240;
1133
- let NON_STANDARD_FPS = 0;
1134
1139
 
1135
1140
  if (hd) {
1136
1141
  // respect user choice, but default to HD for pro, and SD for free
@@ -1145,18 +1150,14 @@ function getMediaConstraints({
1145
1150
  }
1146
1151
  }
1147
1152
 
1148
- // Set framerate to 24 to increase quality/bandwidth
1149
- if (fps24) NON_STANDARD_FPS = 24;
1150
-
1151
- // Set framerate for low data, but only for non-simulcast
1152
- if (lowDataMode && !simulcast) NON_STANDARD_FPS = 15;
1153
-
1154
1153
  const constraints = {
1155
1154
  audio: { ...(preferredDeviceIds.audioId && { deviceId: preferredDeviceIds.audioId }) },
1156
1155
  video: {
1157
1156
  ...(preferredDeviceIds.videoId ? { deviceId: preferredDeviceIds.videoId } : { facingMode: "user" }),
1158
1157
  height: lowDataMode ? LOW_HEIGHT : HIGH_HEIGHT,
1159
- ...(NON_STANDARD_FPS && { frameRate: NON_STANDARD_FPS }),
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,
1160
1161
  },
1161
1162
  };
1162
1163
  if (lax) {
@@ -1289,7 +1290,7 @@ var assert_1 = assert;
1289
1290
 
1290
1291
  var assert$1 = /*@__PURE__*/getDefaultExportFromCjs(assert_1);
1291
1292
 
1292
- const logger$8 = new Logger();
1293
+ const logger$7 = new Logger();
1293
1294
 
1294
1295
  const isMobile = /mobi/i.test(navigator.userAgent);
1295
1296
 
@@ -1310,7 +1311,7 @@ function getUserMedia(constraints) {
1310
1311
 
1311
1312
  return navigator.mediaDevices.getUserMedia(constraints).catch((error) => {
1312
1313
  const message = `${error}, ${JSON.stringify(constraints, null, 2)}`;
1313
- logger$8.error(`getUserMedia ${message}`);
1314
+ logger$7.error(`getUserMedia ${message}`);
1314
1315
  throw error;
1315
1316
  });
1316
1317
  }
@@ -1496,7 +1497,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
1496
1497
  getConstraints({ ...constraintOpt, options: { ...constraintOpt.options, lax: true } })
1497
1498
  );
1498
1499
  } catch (e2) {
1499
- logger$8.warn(`Tried getting stream again with laxer constraints, but failed: ${"" + e2}`);
1500
+ logger$7.warn(`Tried getting stream again with laxer constraints, but failed: ${"" + e2}`);
1500
1501
  }
1501
1502
  // Message often hints at which was the problem, let's use that
1502
1503
  const errMsg = ("" + e).toLowerCase();
@@ -1505,7 +1506,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
1505
1506
  try {
1506
1507
  stream = await getUserMedia(getConstraints({ ...constraintOpt, [problemWith]: null }));
1507
1508
  } catch (e2) {
1508
- logger$8.warn(`Re-tried ${problemWith} with no constraints, but failed: ${"" + e2}`);
1509
+ logger$7.warn(`Re-tried ${problemWith} with no constraints, but failed: ${"" + e2}`);
1509
1510
  }
1510
1511
  }
1511
1512
  if (!stream) {
@@ -1516,7 +1517,7 @@ async function getStream(constraintOpt, { replaceStream, fallback = true } = {})
1516
1517
  try {
1517
1518
  stream = await getUserMedia(getConstraints({ ...constraintOpt, [kind]: false }));
1518
1519
  } catch (e2) {
1519
- logger$8.warn(`Re-tried without ${kind}, but failed: ${"" + e2}`);
1520
+ logger$7.warn(`Re-tried without ${kind}, but failed: ${"" + e2}`);
1520
1521
  }
1521
1522
  if (stream) break;
1522
1523
  }
@@ -1972,8 +1973,8 @@ const selectIsLocalMediaStarting = createSelector(selectLocalMediaStatus, (statu
1972
1973
  const selectCameraDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "videoinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1973
1974
  const selectMicrophoneDevices = createSelector(selectLocalMediaDevices, selectBusyDeviceIds, (devices, busyDeviceIds) => devices.filter((d) => d.kind === "audioinput").filter((d) => !busyDeviceIds.includes(d.deviceId)));
1974
1975
  const selectSpeakerDevices = createSelector(selectLocalMediaDevices, (devices) => devices.filter((d) => d.kind === "audiooutput"));
1975
- const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, (appWantsToJoin, localMediaStatus, localMediaOptions) => {
1976
- if (appWantsToJoin && localMediaStatus === "" && localMediaOptions) {
1976
+ const selectLocalMediaShouldStartWithOptions = createSelector(selectAppWantsToJoin, selectLocalMediaStatus, selectLocalMediaOptions, selectAppIsNodeSdk, (appWantsToJoin, localMediaStatus, localMediaOptions, isNodeSdk) => {
1977
+ if (appWantsToJoin && localMediaStatus === "" && !isNodeSdk && localMediaOptions) {
1977
1978
  return localMediaOptions;
1978
1979
  }
1979
1980
  });
@@ -2581,8 +2582,14 @@ const selectRoomConnectionRaw = (state) => state.roomConnection;
2581
2582
  const selectRoomConnectionSession = (state) => state.roomConnection.session;
2582
2583
  const selectRoomConnectionSessionId = (state) => { var _a; return (_a = state.roomConnection.session) === null || _a === void 0 ? void 0 : _a.id; };
2583
2584
  const selectRoomConnectionStatus = (state) => state.roomConnection.status;
2584
- const selectShouldConnectRoom = createSelector([selectOrganizationId, selectRoomConnectionStatus, selectSignalConnectionDeviceIdentified, selectLocalMediaStatus], (hasOrganizationIdFetched, roomConnectionStatus, signalConnectionDeviceIdentified, localMediaStatus) => {
2585
- if (localMediaStatus === "started" &&
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) &&
2586
2593
  signalConnectionDeviceIdentified &&
2587
2594
  !!hasOrganizationIdFetched &&
2588
2595
  ["initializing", "reconnect"].includes(roomConnectionStatus)) {
@@ -2614,162 +2621,595 @@ startAppListening({
2614
2621
  },
2615
2622
  });
2616
2623
 
2617
- const EVENTS = {
2618
- CLIENT_CONNECTION_STATUS_CHANGED: "client_connection_status_changed",
2619
- STREAM_ADDED: "stream_added",
2620
- RTC_MANAGER_CREATED: "rtc_manager_created",
2621
- RTC_MANAGER_DESTROYED: "rtc_manager_destroyed",
2622
- LOCAL_STREAM_TRACK_ADDED: "local_stream_track_added",
2623
- LOCAL_STREAM_TRACK_REMOVED: "local_stream_track_removed",
2624
- REMOTE_STREAM_TRACK_ADDED: "remote_stream_track_added",
2625
- REMOTE_STREAM_TRACK_REMOVED: "remote_stream_track_removed",
2626
- };
2627
-
2628
- const TYPES = {
2629
- CONNECTING: "connecting",
2630
- CONNECTION_FAILED: "connection_failed",
2631
- CONNECTION_SUCCESSFUL: "connection_successful",
2632
- CONNECTION_DISCONNECTED: "connection_disconnected",
2633
- };
2634
-
2635
- const CAMERA_STREAM_ID$2 = "0";
2636
-
2637
- const STREAM_TYPES = {
2638
- CAMERA: "camera",
2639
- SCREEN_SHARE: "screen_share",
2640
- };
2641
-
2642
- class RtcStream {
2643
- constructor(id, type) {
2644
- assert$1.notEqual(id, undefined, "id is required");
2645
- assert$1.notEqual(type, undefined, "type is required");
2646
-
2647
- this.id = "" + id;
2648
- this.type = type;
2649
-
2650
- this.isEnabled = true;
2651
- this.hasSupportForAutoSuperSize = false;
2652
- this.isAudioEnabled = true;
2653
- this.isVideoEnabled = true;
2654
- this.status = TYPES.CONNECTING;
2655
- }
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
+ }
2656
2636
 
2657
- setup(stream) {
2658
- this.stream = stream;
2659
- this.streamId = stream.id;
2660
- this.setVideoEnabled(this.isVideoEnabled && stream.getVideoTracks().length > 0);
2661
- this.setAudioEnabled(this.isAudioEnabled && stream.getAudioTracks().length > 0);
2662
- return this;
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;
2663
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
+ });
2664
2658
 
2665
- setStatus(status) {
2666
- this.status = status;
2667
- return this;
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;
2668
2664
  }
2669
-
2670
- setVideoEnabled(isEnabled) {
2671
- this.isVideoEnabled = isEnabled;
2672
- if (!this.stream) {
2673
- return;
2674
- }
2675
- this.stream.getVideoTracks().forEach((track) => {
2676
- track.enabled = isEnabled;
2677
- });
2665
+ });
2666
+ Object.keys(newStats).forEach(function(id) {
2667
+ var report = newStats[id];
2668
+ if (report.timestamp === timestamp) {
2669
+ report.timestamp = 0;
2678
2670
  }
2671
+ });
2672
+ newStats.timestamp = timestamp;
2673
+ return newStats;
2674
+ }
2679
2675
 
2680
- setAudioEnabled(isEnabled) {
2681
- this.isAudioEnabled = isEnabled;
2682
- if (!this.stream) {
2683
- return;
2684
- }
2685
- this.stream.getAudioTracks().forEach((track) => {
2686
- track.enabled = isEnabled;
2687
- });
2688
- }
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
+ }
2689
2691
 
2690
- static getCameraId() {
2691
- return CAMERA_STREAM_ID$2;
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
2692
2702
  }
2703
+ });
2704
+ return results;
2705
+ }
2693
2706
 
2694
- static getTypeFromId(id) {
2695
- assert$1.notEqual(id, undefined, "id is required");
2696
-
2697
- const streamId = "" + id;
2698
- return streamId === CAMERA_STREAM_ID$2 ? STREAM_TYPES.CAMERA : STREAM_TYPES.SCREEN_SHARE;
2699
- }
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;
2700
2713
  }
2714
+ */
2701
2715
 
2702
- /**
2703
- * Detect mic issue which seems to happen on OSX when the computer is woken up and sleeping
2704
- * frequently. A browser restart fixes this.
2705
- *
2706
- * Should be called after the connection has been up for a while.
2707
- *
2708
- * @see Bug report {@link https://bugs.chromium.org/p/webrtc/issues/detail?id=4799}
2709
- */
2710
- function detectMicrophoneNotWorking(pc) {
2711
- if (
2712
- adapter.browserDetails.browser !== "chrome" ||
2713
- adapter.browserDetails.browser < 58 || // legacy getStats is no longer supported.
2714
- pc.signalingState === "closed"
2715
- ) {
2716
- 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;
2717
2725
  }
2718
- const sendingAudio = pc.getSenders().some((sender) => sender.track && sender.track.kind === "audio");
2719
- const receivingAudio = pc.getReceivers().some((receiver) => receiver.track && receiver.track.kind === "audio");
2720
- return pc.getStats(null).then((result) => {
2721
- let microphoneFailed = false;
2722
- result.forEach((report) => {
2723
- if (
2724
- report.type === "outbound-rtp" &&
2725
- (report.kind === "audio" || report.mediaType === "audio") &&
2726
- sendingAudio
2727
- ) {
2728
- if (report.bytesSent === 0) {
2729
- microphoneFailed = "outbound";
2730
- }
2731
- } else if (
2732
- report.type === "inbound-rtp" &&
2733
- (report.kind === "audio" || report.mediaType === "audio") &&
2734
- receivingAudio
2735
- ) {
2736
- if (report.bytesReceived === 0) {
2737
- microphoneFailed = "inbound";
2738
- }
2739
- }
2740
- });
2741
- return microphoneFailed;
2742
- });
2743
- }
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;
2744
2735
 
2745
- var rtcManagerEvents = {
2746
- CAMERA_NOT_WORKING: "camera_not_working",
2747
- CONNECTION_BLOCKED_BY_NETWORK: "connection_blocked_by_network",
2748
- ICE_IPV6_SEEN: "ice_ipv6_seen",
2749
- ICE_MDNS_SEEN: "ice_mdns_seen",
2750
- ICE_NO_PUBLIC_IP_GATHERED: "ice_no_public_ip_gathered",
2751
- ICE_NO_PUBLIC_IP_GATHERED_3SEC: "ice_no_public_ip_gathered_3sec",
2752
- ICE_RESTART: "ice_restart",
2753
- MICROPHONE_NOT_WORKING: "microphone_not_working",
2754
- MICROPHONE_STOPPED_WORKING: "microphone_stopped_working",
2755
- NEW_PC: "new_pc",
2756
- SFU_CONNECTION_OPEN: "sfu_connection_open",
2757
- SFU_CONNECTION_CLOSED: "sfu_connection_closed",
2758
- COLOCATION_SPEAKER: "colocation_speaker",
2759
- DOMINANT_SPEAKER: "dominant_speaker",
2760
- };
2736
+ if (!config) {
2737
+ config = { nullConfig: true };
2738
+ }
2761
2739
 
2762
- const logger$7 = new Logger();
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
+ });
2763
2745
 
2764
- const browserName$3 = adapter.browserDetails.browser;
2765
- const browserVersion$1 = adapter.browserDetails.version;
2746
+ if (isFirefox) {
2747
+ config.browserType = 'moz';
2748
+ } else if (isEdge) {
2749
+ config.browserType = 'edge';
2750
+ } else {
2751
+ config.browserType = 'webkit';
2752
+ }
2766
2753
 
2767
- function setCodecPreferenceSDP(sdp, vp9On, redOn) {
2768
- try {
2769
- const sdpObject = sdpTransform.parse(sdp);
2770
- if (Array.isArray(sdpObject?.media)) {
2771
- //audio
2772
- const mediaAudio = sdpObject.media.find((m) => m.type === "audio");
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
3212
+ const mediaAudio = sdpObject.media.find((m) => m.type === "audio");
2773
3213
  if (Array.isArray(mediaAudio?.rtp)) {
2774
3214
  const rtp = mediaAudio.rtp;
2775
3215
  for (let i = 0; i < rtp.length; i++) {
@@ -2802,7 +3242,7 @@ function setCodecPreferenceSDP(sdp, vp9On, redOn) {
2802
3242
  const newSdp = sdpTransform.write(sdpObject);
2803
3243
  return newSdp;
2804
3244
  } catch (error) {
2805
- logger$7.error("setCodecPreferenceSDP error:", error);
3245
+ logger$6.error("setCodecPreferenceSDP error:", error);
2806
3246
  }
2807
3247
  }
2808
3248
 
@@ -2855,7 +3295,7 @@ function replaceSSRCs(currentDescription, newDescription) {
2855
3295
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1478685
2856
3296
  // filter out the mid rtp header extension
2857
3297
  function filterMidExtension(sdp) {
2858
- if (browserName$3 !== "safari" && (browserName$3 !== "firefox" || browserVersion$1 >= 63 || browserVersion$1 === 60)) {
3298
+ if (browserName$2 !== "safari" && (browserName$2 !== "firefox" || browserVersion$1 >= 63 || browserVersion$1 === 60)) {
2859
3299
  return sdp;
2860
3300
  }
2861
3301
  return (
@@ -2876,7 +3316,7 @@ function filterMidExtension(sdp) {
2876
3316
  // https://bugzilla.mozilla.org/show_bug.cgi?id=1534673
2877
3317
  // Filter out a:msid-semantic header
2878
3318
  function filterMsidSemantic(sdp) {
2879
- if (browserName$3 !== "firefox") {
3319
+ if (browserName$2 !== "firefox") {
2880
3320
  return sdp;
2881
3321
  }
2882
3322
  return (
@@ -2886,6 +3326,52 @@ function filterMsidSemantic(sdp) {
2886
3326
  );
2887
3327
  }
2888
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
+
2889
3375
  function isRelayed(pc) {
2890
3376
  return pc.getStats(null).then((result) => {
2891
3377
  let localCandidateType;
@@ -2949,7 +3435,8 @@ const MAXIMUM_TURN_BANDWIDTH_UNLIMITED = -1; // kbps;
2949
3435
 
2950
3436
  const MEDIA_JITTER_BUFFER_TARGET = 400; // milliseconds;
2951
3437
 
2952
- const logger$6 = new Logger();
3438
+ const adapter$3 = adapterRaw.default ?? adapterRaw;
3439
+ const logger$5 = new Logger();
2953
3440
 
2954
3441
  class Session {
2955
3442
  constructor({ peerConnectionId, bandwidth, maximumTurnBandwidth, deprioritizeH264Encoding }) {
@@ -3124,7 +3611,7 @@ class Session {
3124
3611
  return setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
3125
3612
  },
3126
3613
  (e) => {
3127
- logger$6.warn("Could not set remote description from remote answer: ", e);
3614
+ logger$5.warn("Could not set remote description from remote answer: ", e);
3128
3615
  }
3129
3616
  );
3130
3617
  }
@@ -3140,12 +3627,12 @@ class Session {
3140
3627
  if (this.pc.signalingState === "closed") {
3141
3628
  return;
3142
3629
  }
3143
- if (adapter.browserDetails.browser === "safari" && candidate && candidate.candidate === "") {
3630
+ if (adapter$3.browserDetails.browser === "safari" && candidate && candidate.candidate === "") {
3144
3631
  // filter due to https://github.com/webrtcHacks/adapter/issues/863
3145
3632
  return;
3146
3633
  }
3147
3634
  this.pc.addIceCandidate(candidate).catch((e) => {
3148
- logger$6.warn("Failed to add ICE candidate ('%s'): %s", candidate ? candidate.candidate : null, e);
3635
+ logger$5.warn("Failed to add ICE candidate ('%s'): %s", candidate ? candidate.candidate : null, e);
3149
3636
  });
3150
3637
  });
3151
3638
  }
@@ -3167,7 +3654,7 @@ class Session {
3167
3654
  // do not handle state change events when we close the connection explicitly
3168
3655
  pc.close();
3169
3656
  } catch (e) {
3170
- logger$6.warn("failures during close of session", e);
3657
+ logger$5.warn("failures during close of session", e);
3171
3658
  // we're not interested in errors from RTCPeerConnection.close()
3172
3659
  }
3173
3660
  }
@@ -3183,7 +3670,7 @@ class Session {
3183
3670
  const senders = pc.getSenders();
3184
3671
  function dbg(msg) {
3185
3672
  const tr = (t) => t && `id:${t.id},kind:${t.kind},state:${t.readyState}`;
3186
- logger$6.warn(
3673
+ logger$5.warn(
3187
3674
  `${msg}. newTrack:${tr(newTrack)}, oldTrack:${tr(oldTrack)}, sender tracks: ${JSON.stringify(
3188
3675
  senders.map((s) => `s ${tr(s.track)}`)
3189
3676
  )}, sender first codecs: ${JSON.stringify(senders.map((s) => (s.getParameters().codecs || [])[0]))}`
@@ -3317,687 +3804,568 @@ class Session {
3317
3804
  });
3318
3805
  }
3319
3806
 
3320
- // no-signaling negotiation of bandwidth. Peer is NOT informed.
3321
- // Prefers using RTCRtpSender.setParameters if possible.
3322
- changeBandwidth(bandwidth) {
3323
- // don't renegotiate if bandwidth is already set.
3324
- if (bandwidth === this.bandwidth) {
3325
- return;
3326
- }
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
+ }
3327
3941
 
3328
- if (!this.canModifyPeerConnection()) {
3329
- this.pending.push(() => this.changeBandwidth(bandwidth));
3942
+ setVideoEnabled(isEnabled) {
3943
+ this.isVideoEnabled = isEnabled;
3944
+ if (!this.stream) {
3330
3945
  return;
3331
3946
  }
3947
+ this.stream.getVideoTracks().forEach((track) => {
3948
+ track.enabled = isEnabled;
3949
+ });
3950
+ }
3332
3951
 
3333
- this.bandwidth = bandwidth;
3334
- if (!this.pc.localDescription || this.pc.localDescription.type === "") {
3952
+ setAudioEnabled(isEnabled) {
3953
+ this.isAudioEnabled = isEnabled;
3954
+ if (!this.stream) {
3335
3955
  return;
3336
3956
  }
3957
+ this.stream.getAudioTracks().forEach((track) => {
3958
+ track.enabled = isEnabled;
3959
+ });
3960
+ }
3337
3961
 
3338
- setVideoBandwidthUsingSetParameters(this.pc, this.bandwidth);
3962
+ static getCameraId() {
3963
+ return CAMERA_STREAM_ID$1;
3339
3964
  }
3340
3965
 
3341
- setAudioOnly(enable, excludedTrackIds = []) {
3342
- this.pc
3343
- .getTransceivers()
3344
- .filter(
3345
- (videoTransceiver) =>
3346
- videoTransceiver?.direction !== "recvonly" &&
3347
- videoTransceiver?.receiver?.track?.kind === "video" &&
3348
- !excludedTrackIds.includes(videoTransceiver?.receiver?.track?.id) &&
3349
- !excludedTrackIds.includes(videoTransceiver?.sender?.track?.id)
3350
- )
3351
- .forEach((videoTransceiver) => {
3352
- videoTransceiver.direction = enable ? "sendonly" : "sendrecv";
3353
- });
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;
3354
3971
  }
3355
3972
  }
3356
3973
 
3357
- // transforms a maplike to an object. Mostly for getStats +
3358
- // JSON.parse(JSON.stringify())
3359
- function map2obj(m) {
3360
- if (!m.entries) {
3361
- return m;
3362
- }
3363
- var o = {};
3364
- m.forEach(function(v, k) {
3365
- o[k] = v;
3366
- });
3367
- return o;
3368
- }
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);
3369
3979
 
3370
- // apply a delta compression to the stats report. Reduces size by ~90%.
3371
- // To reduce further, report keys could be compressed.
3372
- function deltaCompression(oldStats, newStats) {
3373
- newStats = JSON.parse(JSON.stringify(newStats));
3374
- Object.keys(newStats).forEach(function(id) {
3375
- var report = newStats[id];
3376
- delete report.id;
3377
- if (!oldStats[id]) {
3378
- return;
3379
- }
3380
- Object.keys(report).forEach(function(name) {
3381
- if (report[name] === oldStats[id][name]) {
3382
- delete newStats[id][name];
3383
- }
3384
- if (Object.keys(report).length === 0) {
3385
- delete newStats[id];
3386
- } else if (Object.keys(report).length === 1 && report.timestamp) {
3387
- delete newStats[id];
3388
- }
3389
- });
3390
- });
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;
3391
3985
 
3392
- var timestamp = -Infinity;
3393
- Object.keys(newStats).forEach(function(id) {
3394
- var report = newStats[id];
3395
- if (report.timestamp > timestamp) {
3396
- timestamp = report.timestamp;
3397
- }
3398
- });
3399
- Object.keys(newStats).forEach(function(id) {
3400
- var report = newStats[id];
3401
- if (report.timestamp === timestamp) {
3402
- report.timestamp = 0;
3986
+ let targetBitratePerPixel = lowBitratePerPixel;
3987
+ if (targetPixelCount > highPixelCount) targetBitratePerPixel = highBitratePerPixel;
3988
+ else if (targetPixelCount > lowPixelCount) {
3989
+ targetBitratePerPixel += (targetPixelCount - lowPixelCount) * bitrateChangePerPixel;
3403
3990
  }
3404
- });
3405
- newStats.timestamp = timestamp;
3406
- return newStats;
3407
- }
3408
3991
 
3409
- function dumpStream(stream) {
3410
- return {
3411
- id: stream.id,
3412
- tracks: stream.getTracks().map(function(track) {
3413
- return {
3414
- id: track.id, // unique identifier (GUID) for the track
3415
- kind: track.kind, // `audio` or `video`
3416
- label: track.label, // identified the track source
3417
- enabled: track.enabled, // application can control it
3418
- muted: track.muted, // application cannot control it (read-only)
3419
- readyState: track.readyState, // `live` or `ended`
3420
- };
3421
- }),
3422
- };
3423
- }
3992
+ // we use the actual resolution for the target bitrate
3993
+ let targetBitrate = width * height * targetBitratePerPixel;
3424
3994
 
3425
- /*
3426
- function filterBoringStats(results) {
3427
- Object.keys(results).forEach(function(id) {
3428
- switch (results[id].type) {
3429
- case 'certificate':
3430
- case 'codec':
3431
- delete results[id];
3432
- break;
3433
- default:
3434
- // noop
3435
- }
3436
- });
3437
- return results;
3438
- }
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;
3439
3998
 
3440
- function removeTimestamps(results) {
3441
- // FIXME: does not work in FF since the timestamp can't be deleted.
3442
- Object.keys(results).forEach(function(id) {
3443
- delete results[id].timestamp;
3444
- });
3445
- return results;
3999
+ return targetBitrate;
3446
4000
  }
3447
- */
3448
4001
 
3449
- var rtcstats = function(trace, getStatsInterval, prefixesToWrap) {
3450
- var peerconnectioncounter = 0;
3451
- var isFirefox = !!window.mozRTCPeerConnection;
3452
- var isEdge = !!window.RTCIceGatherer;
3453
- var prevById = {};
3454
-
3455
- prefixesToWrap.forEach(function(prefix) {
3456
- if (!window[prefix + 'RTCPeerConnection']) {
3457
- return;
3458
- }
3459
- if (prefix === 'webkit' && isEdge) {
3460
- // dont wrap webkitRTCPeerconnection in Edge.
3461
- return;
3462
- }
3463
- var origPeerConnection = window[prefix + 'RTCPeerConnection'];
3464
- var peerconnection = function(config, constraints) {
3465
- var pc = new origPeerConnection(config, constraints);
3466
- var id = 'PC_' + peerconnectioncounter++;
3467
- 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:]";
3468
4005
 
3469
- if (!config) {
3470
- config = { nullConfig: true };
3471
- }
4006
+ const boundry = (options) =>
4007
+ options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
3472
4008
 
3473
- config = JSON.parse(JSON.stringify(config)); // deepcopy
3474
- // don't log credentials
3475
- ((config && config.iceServers) || []).forEach(function(server) {
3476
- delete server.credential;
3477
- });
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}";
3478
4010
 
3479
- if (isFirefox) {
3480
- config.browserType = 'moz';
3481
- } else if (isEdge) {
3482
- config.browserType = 'edge';
3483
- } else {
3484
- config.browserType = 'webkit';
3485
- }
4011
+ const v6segment = "[a-fA-F\\d]{1,4}";
3486
4012
 
3487
- trace('create', id, config);
3488
- // TODO: do we want to log constraints here? They are chrome-proprietary.
3489
- // http://stackoverflow.com/questions/31003928/what-do-each-of-these-experimental-goog-rtcpeerconnectionconstraints-do
3490
- if (constraints) {
3491
- trace('constraints', id, constraints);
3492
- }
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();
3493
4028
 
3494
- pc.addEventListener('icecandidate', function(e) {
3495
- trace('onicecandidate', id, e.candidate);
3496
- });
3497
- pc.addEventListener('addstream', function(e) {
3498
- trace('onaddstream', id, e.stream.id + ' ' + e.stream.getTracks().map(function(t) { return t.kind + ':' + t.id; }));
3499
- });
3500
- pc.addEventListener('track', function(e) {
3501
- trace('ontrack', id, e.track.kind + ':' + e.track.id + ' ' + e.streams.map(function(stream) { return 'stream:' + stream.id; }));
3502
- });
3503
- pc.addEventListener('removestream', function(e) {
3504
- trace('onremovestream', id, e.stream.id + ' ' + e.stream.getTracks().map(function(t) { return t.kind + ':' + t.id; }));
3505
- });
3506
- pc.addEventListener('signalingstatechange', function() {
3507
- trace('onsignalingstatechange', id, pc.signalingState);
3508
- });
3509
- pc.addEventListener('iceconnectionstatechange', function() {
3510
- trace('oniceconnectionstatechange', id, pc.iceConnectionState);
3511
- });
3512
- pc.addEventListener('icegatheringstatechange', function() {
3513
- trace('onicegatheringstatechange', id, pc.iceGatheringState);
3514
- });
3515
- pc.addEventListener('connectionstatechange', function() {
3516
- trace('onconnectionstatechange', id, pc.connectionState);
3517
- });
3518
- pc.addEventListener('negotiationneeded', function() {
3519
- trace('onnegotiationneeded', id, undefined);
3520
- });
3521
- pc.addEventListener('datachannel', function(event) {
3522
- trace('ondatachannel', id, [event.channel.id, event.channel.label]);
3523
- });
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
+ );
3524
4041
 
3525
- var getStats = function() {
3526
- pc.getStats(null).then(function(res) {
3527
- var now = map2obj(res);
3528
- var base = JSON.parse(JSON.stringify(now)); // our new prev
3529
- trace('getstats', id, deltaCompression(prevById[id] || {}, now));
3530
- prevById[id] = base;
3531
- });
3532
- };
3533
- // TODO: do we want one big interval and all peerconnections
3534
- // queried in that or one setInterval per PC?
3535
- // we have to collect results anyway so...
3536
- if (!isEdge && getStatsInterval) {
3537
- var interval = window.setInterval(function() {
3538
- if (pc.signalingState === 'closed') {
3539
- window.clearInterval(interval);
3540
- return;
3541
- }
3542
- getStats();
3543
- }, getStatsInterval);
3544
- }
3545
- if (!isEdge) {
3546
- pc.addEventListener('connectionstatechange', function() {
3547
- if (pc.connectionState === 'connected' || pc.connectionState === 'failed') {
3548
- getStats();
3549
- }
3550
- });
3551
- }
3552
- return pc;
3553
- };
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");
3554
4046
 
3555
- ['createDataChannel', 'close'].forEach(function(method) {
3556
- var nativeMethod = origPeerConnection.prototype[method];
3557
- if (nativeMethod) {
3558
- origPeerConnection.prototype[method] = function() {
3559
- trace(method, this.__rtcStatsId, arguments);
3560
- return nativeMethod.apply(this, arguments);
3561
- };
3562
- }
3563
- });
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
+ };
3564
4063
 
3565
- ['addStream', 'removeStream'].forEach(function(method) {
3566
- var nativeMethod = origPeerConnection.prototype[method];
3567
- if (nativeMethod) {
3568
- origPeerConnection.prototype[method] = function() {
3569
- var stream = arguments[0];
3570
- var streamInfo = stream.getTracks().map(function(t) {
3571
- return t.kind + ':' + t.id;
3572
- }).join(',');
4064
+ const adapter$1 = adapterRaw.default ?? adapterRaw;
4065
+ const logger$4 = new Logger();
3573
4066
 
3574
- trace(method, this.__rtcStatsId, stream.id + ' ' + streamInfo);
3575
- return nativeMethod.apply(this, arguments);
3576
- };
3577
- }
3578
- });
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;
3579
4071
 
3580
- ['addTrack'].forEach(function(method) {
3581
- var nativeMethod = origPeerConnection.prototype[method];
3582
- if (nativeMethod) {
3583
- origPeerConnection.prototype[method] = function() {
3584
- var track = arguments[0];
3585
- var streams = [].slice.call(arguments, 1);
3586
- trace(method, this.__rtcStatsId, track.kind + ':' + track.id + ' ' + (streams.map(function(s) { return 'stream:' + s.id; }).join(';') || '-'));
3587
- return nativeMethod.apply(this, arguments);
3588
- };
3589
- }
3590
- });
4072
+ if (browserName$1 === "firefox") {
4073
+ adapter$1.browserShim.shimGetDisplayMedia(window, "screen");
4074
+ }
3591
4075
 
3592
- ['removeTrack'].forEach(function(method) {
3593
- var nativeMethod = origPeerConnection.prototype[method];
3594
- if (nativeMethod) {
3595
- origPeerConnection.prototype[method] = function() {
3596
- var track = arguments[0].track;
3597
- trace(method, this.__rtcStatsId, track ? track.kind + ':' + track.id : 'null');
3598
- return nativeMethod.apply(this, arguments);
3599
- };
3600
- }
4076
+ let unloading$1 = false;
4077
+ if (browserName$1 === "chrome") {
4078
+ window.document.addEventListener("beforeunload", () => {
4079
+ unloading$1 = true;
3601
4080
  });
4081
+ }
3602
4082
 
3603
- ['createOffer', 'createAnswer'].forEach(function(method) {
3604
- var nativeMethod = origPeerConnection.prototype[method];
3605
- if (nativeMethod) {
3606
- origPeerConnection.prototype[method] = function() {
3607
- var rtcStatsId = this.__rtcStatsId;
3608
- var args = arguments;
3609
- var opts;
3610
- if (arguments.length === 1 && typeof arguments[0] === 'object') {
3611
- opts = arguments[0];
3612
- } else if (arguments.length === 3 && typeof arguments[2] === 'object') {
3613
- opts = arguments[2];
3614
- }
3615
- trace(method, this.__rtcStatsId, opts);
3616
- return nativeMethod.apply(this, opts ? [opts] : undefined)
3617
- .then(function(description) {
3618
- trace(method + 'OnSuccess', rtcStatsId, description);
3619
- if (args.length > 0 && typeof args[0] === 'function') {
3620
- args[0].apply(null, [description]);
3621
- return undefined;
3622
- }
3623
- return description;
3624
- }, function(err) {
3625
- trace(method + 'OnFailure', rtcStatsId, err.toString());
3626
- if (args.length > 1 && typeof args[1] === 'function') {
3627
- args[1].apply(null, [err]);
3628
- return;
3629
- }
3630
- throw err;
3631
- });
3632
- };
3633
- }
3634
- });
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");
3635
4090
 
3636
- ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function(method) {
3637
- var nativeMethod = origPeerConnection.prototype[method];
3638
- if (nativeMethod) {
3639
- origPeerConnection.prototype[method] = function() {
3640
- var rtcStatsId = this.__rtcStatsId;
3641
- var args = arguments;
3642
- trace(method, this.__rtcStatsId, args[0]);
3643
- return nativeMethod.apply(this, [args[0]])
3644
- .then(function() {
3645
- trace(method + 'OnSuccess', rtcStatsId, undefined);
3646
- if (args.length >= 2 && typeof args[1] === 'function') {
3647
- args[1].apply(null, []);
3648
- return undefined;
3649
- }
3650
- return undefined;
3651
- }, function(err) {
3652
- trace(method + 'OnFailure', rtcStatsId, err.toString());
3653
- if (args.length >= 3 && typeof args[2] === 'function') {
3654
- args[2].apply(null, [err]);
3655
- return undefined;
3656
- }
3657
- throw err;
3658
- });
3659
- };
3660
- }
3661
- });
4091
+ const { name, session, iceServers, sfuServer, mediaserverConfigTtlSeconds } = room;
3662
4092
 
3663
- // wrap static methods. Currently just generateCertificate.
3664
- if (origPeerConnection.generateCertificate) {
3665
- Object.defineProperty(peerconnection, 'generateCertificate', {
3666
- get: function() {
3667
- return arguments.length ?
3668
- origPeerConnection.generateCertificate.apply(null, arguments)
3669
- : origPeerConnection.generateCertificate;
3670
- },
3671
- });
3672
- }
3673
- window[prefix + 'RTCPeerConnection'] = peerconnection;
3674
- window[prefix + 'RTCPeerConnection'].prototype = origPeerConnection.prototype;
3675
- });
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;
3676
4107
 
3677
- // getUserMedia wrappers
3678
- prefixesToWrap.forEach(function(prefix) {
3679
- var name = prefix + (prefix.length ? 'GetUserMedia' : 'getUserMedia');
3680
- if (!navigator[name]) {
3681
- return;
3682
- }
3683
- var origGetUserMedia = navigator[name].bind(navigator);
3684
- var gum = function() {
3685
- trace('getUserMedia', null, arguments[0]);
3686
- var cb = arguments[1];
3687
- var eb = arguments[2];
3688
- origGetUserMedia(arguments[0],
3689
- function(stream) {
3690
- // we log the stream id, track ids and tracks readystate since that is ended GUM fails
3691
- // to acquire the cam (in chrome)
3692
- trace('getUserMediaOnSuccess', null, dumpStream(stream));
3693
- if (cb) {
3694
- cb(stream);
3695
- }
3696
- },
3697
- function(err) {
3698
- trace('getUserMediaOnFailure', null, err.name);
3699
- if (eb) {
3700
- eb(err);
3701
- }
3702
- }
3703
- );
3704
- };
3705
- navigator[name] = gum.bind(navigator);
3706
- });
4108
+ this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
4109
+ this._pendingActionsForConnectedPeerConnections = [];
3707
4110
 
3708
- if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
3709
- var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices);
3710
- var gum = function() {
3711
- trace('navigator.mediaDevices.getUserMedia', null, arguments[0]);
3712
- return origGetUserMedia.apply(navigator.mediaDevices, arguments)
3713
- .then(function(stream) {
3714
- trace('navigator.mediaDevices.getUserMediaOnSuccess', null, dumpStream(stream));
3715
- return stream;
3716
- }, function(err) {
3717
- trace('navigator.mediaDevices.getUserMediaOnFailure', null, err.name);
3718
- return Promise.reject(err);
3719
- });
3720
- };
3721
- navigator.mediaDevices.getUserMedia = gum.bind(navigator.mediaDevices);
3722
- }
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
+ });
3723
4125
 
3724
- // getDisplayMedia
3725
- if (navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) {
3726
- var origGetDisplayMedia = navigator.mediaDevices.getDisplayMedia.bind(navigator.mediaDevices);
3727
- var gdm = function() {
3728
- trace('navigator.mediaDevices.getDisplayMedia', null, arguments[0]);
3729
- return origGetDisplayMedia.apply(navigator.mediaDevices, arguments)
3730
- .then(function(stream) {
3731
- trace('navigator.mediaDevices.getDisplayMediaOnSuccess', null, dumpStream(stream));
3732
- return stream;
3733
- }, function(err) {
3734
- trace('navigator.mediaDevices.getDisplayMediaOnFailure', null, err.name);
3735
- return Promise.reject(err);
3736
- });
3737
- };
3738
- navigator.mediaDevices.getDisplayMedia = gdm.bind(navigator.mediaDevices);
3739
- }
4126
+ this.totalSessionsCreated = 0;
4127
+ }
3740
4128
 
3741
- // TODO: are there events defined on MST that would allow us to listen when enabled was set?
3742
- // no :-(
3743
- /*
3744
- Object.defineProperty(MediaStreamTrack.prototype, 'enabled', {
3745
- set: function(value) {
3746
- trace('MediaStreamTrackEnable', this, value);
4129
+ numberOfPeerconnections() {
4130
+ return Object.keys(this.peerConnections).length;
3747
4131
  }
3748
- });
3749
- */
3750
4132
 
3751
- return {
3752
- resetDelta() {
3753
- prevById = {};
4133
+ isInitializedWith({ selfId, roomName, isSfu }) {
4134
+ return this._selfId === selfId && this._roomName === roomName && isSfu === !!this._sfuServer;
3754
4135
  }
3755
- }
3756
4136
 
3757
- };
4137
+ supportsScreenShareAudio() {
4138
+ return true;
4139
+ }
3758
4140
 
3759
- var rtcstats$1 = /*@__PURE__*/getDefaultExportFromCjs(rtcstats);
4141
+ maybeRestrictRelayBandwidth(session) {
4142
+ session.maybeRestrictRelayBandwidth();
4143
+ }
3760
4144
 
3761
- // ensure adapter is loaded first.
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
+ }
3762
4151
 
3763
- const RTCSTATS_PROTOCOL_VERSION = "1.0";
4152
+ this._addLocalStream(streamId, stream);
3764
4153
 
3765
- // when not connected we need to buffer at least a few getstats reports
3766
- // as they are delta compressed and we need the initial properties
3767
- const GETSTATS_BUFFER_SIZE = 20;
4154
+ if (streamId === CAMERA_STREAM_ID) {
4155
+ this._addStreamToPeerConnections(stream);
4156
+ const [audioTrack] = stream.getAudioTracks();
4157
+ if (audioTrack) {
4158
+ this._startMonitoringAudioTrack(audioTrack);
4159
+ }
3768
4160
 
3769
- const clientInfo = {
3770
- id: v4$1(), // shared id across rtcstats reconnects
3771
- connectionNumber: 0,
3772
- };
4161
+ // This should not be needed, but checking nonetheless
4162
+ if (this._localStreamDeregisterFunction) {
4163
+ this._localStreamDeregisterFunction();
4164
+ this._localStreamDeregisterFunction = null;
4165
+ }
3773
4166
 
3774
- const noop = () => {};
3775
- let resetDelta = noop;
4167
+ const localStreamHandler = (e) => {
4168
+ const { enable, track } = e.detail;
4169
+ this._handleStopOrResumeVideo({ enable, track });
4170
+ };
3776
4171
 
3777
- // Inlined version of rtcstats/trace-ws with improved disconnect handling.
3778
- function rtcStatsConnection(wsURL, logger = console) {
3779
- const buffer = [];
3780
- let ws;
3781
- let organizationId;
3782
- let clientId;
3783
- let displayName;
3784
- let userRole;
3785
- let roomSessionId;
3786
- let connectionShouldBeOpen;
3787
- let connectionAttempt = 0;
3788
- let hasPassedOnRoomSessionId = false;
3789
- let getStatsBufferUsed = 0;
4172
+ stream.addEventListener("stopresumevideo", localStreamHandler);
4173
+ this._localStreamDeregisterFunction = () => {
4174
+ stream.removeEventListener("stopresumevideo", localStreamHandler);
4175
+ };
3790
4176
 
3791
- const connection = {
3792
- connected: false,
3793
- trace: (...args) => {
3794
- args.push(Date.now());
4177
+ return;
4178
+ }
3795
4179
 
3796
- if (args[0] === "customEvent" && args[2].type === "roomSessionId") {
3797
- const oldRoomSessionIdValue = roomSessionId && roomSessionId[2].value.roomSessionId;
3798
- const newRoomSessionIdValue = args[2].value.roomSessionId;
3799
- roomSessionId = args;
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
+ }
3800
4185
 
3801
- if (
3802
- hasPassedOnRoomSessionId &&
3803
- newRoomSessionIdValue &&
3804
- newRoomSessionIdValue !== oldRoomSessionIdValue
3805
- ) {
3806
- // roomSessionId was already sent. It may have been reset to null, but anything after should be part of the same
3807
- // session. Now it is something else, and we force a reconnect to start a new session
3808
- if (ws) {
3809
- ws.close();
3810
- return;
3811
- }
3812
- }
3813
- if (newRoomSessionIdValue) hasPassedOnRoomSessionId = true;
3814
- } else if (args[0] === "customEvent" && args[2].type === "clientId") {
3815
- clientId = args;
3816
- } else if (args[0] === "customEvent" && args[2].type === "organizationId") {
3817
- organizationId = args;
3818
- } else if (args[0] === "customEvent" && args[2].type === "displayName") {
3819
- displayName = args;
3820
- } else if (args[0] === "customEvent" && args[2].type === "userRole") {
3821
- userRole = args;
3822
- }
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
+ }
3823
4195
 
3824
- if (ws.readyState === WebSocket.OPEN) {
3825
- connectionAttempt = 0;
3826
- ws.send(JSON.stringify(args));
3827
- } else if (args[0] === "getstats") {
3828
- // only buffer getStats for a while
3829
- // we don't want this to pile up, but we need at least the initial reports
3830
- if (getStatsBufferUsed < GETSTATS_BUFFER_SIZE) {
3831
- getStatsBufferUsed++;
3832
- buffer.push(args);
3833
- }
3834
- } else if (args[0] === "customEvent" && args[2].type === "insightsStats") ; else {
3835
- // buffer everything else
3836
- buffer.push(args);
3837
- }
4196
+ accept({ clientId, shouldAddLocalVideo }) {
4197
+ return this.acceptNewStream({ streamId: clientId, clientId, shouldAddLocalVideo });
4198
+ }
3838
4199
 
3839
- // reconnect when closed by anything else than client
3840
- if (ws.readyState === WebSocket.CLOSED && connectionShouldBeOpen) {
3841
- setTimeout(() => {
3842
- if (ws.readyState === WebSocket.CLOSED && connectionShouldBeOpen) {
3843
- connection.connect();
3844
- }
3845
- }, 1000 * connectionAttempt);
3846
- }
3847
- },
3848
- close: () => {
3849
- connectionShouldBeOpen = false;
3850
- if (ws) {
3851
- ws.close();
3852
- }
3853
- },
3854
- connect: () => {
3855
- connectionShouldBeOpen = true;
3856
- connectionAttempt += 1;
3857
- if (ws) {
3858
- ws.close();
3859
- }
3860
- connection.connected = true;
3861
- 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 = [];
3862
4209
 
3863
- ws.onerror = (e) => {
3864
- connection.connected = false;
3865
- logger.warn(`[RTCSTATS] WebSocket error`, e);
3866
- };
3867
- ws.onclose = (e) => {
3868
- connection.connected = false;
3869
- logger.info(`[RTCSTATS] Closed ${e.code}`);
3870
- resetDelta();
3871
- };
3872
- ws.onopen = () => {
3873
- // send client info after each connection, so analysis tools can handle reconnections
3874
- clientInfo.connectionNumber++;
3875
- ws.send(JSON.stringify(["clientInfo", null, clientInfo]));
4210
+ if (this._localStreamDeregisterFunction) {
4211
+ this._localStreamDeregisterFunction();
4212
+ this._localStreamDeregisterFunction = null;
4213
+ }
4214
+ }
3876
4215
 
3877
- if (organizationId) {
3878
- ws.send(JSON.stringify(organizationId));
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;
3879
4246
  }
4247
+ this._updateAndScheduleMediaServersRefresh(data);
4248
+ }),
3880
4249
 
3881
- if (clientId) {
3882
- ws.send(JSON.stringify(clientId));
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;
3883
4268
  }
4269
+ session.addIceCandidate(null);
4270
+ }),
3884
4271
 
3885
- if (roomSessionId) {
3886
- ws.send(JSON.stringify(roomSessionId));
3887
- }
3888
- if (displayName) {
3889
- ws.send(JSON.stringify(displayName));
3890
- }
3891
- if (userRole) {
3892
- 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;
3893
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
+ }),
3894
4287
 
3895
- // send buffered events
3896
- while (buffer.length) {
3897
- ws.send(JSON.stringify(buffer.shift()));
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;
3898
4294
  }
3899
- getStatsBufferUsed = 0;
3900
- };
3901
- },
3902
- };
3903
- connection.connect();
3904
- return connection;
3905
- }
4295
+ const answer = this._transformIncomingSdp(data.message, session.pc);
4296
+ session.handleAnswer(answer);
4297
+ }),
3906
4298
 
3907
- const server = rtcStatsConnection("wss://rtcstats.srv.whereby.com" );
3908
- const stats = rtcstats$1(
3909
- server.trace,
3910
- 10000, // query once every 10 seconds.
3911
- [""] // only shim unprefixed RTCPeerConnecion.
3912
- );
3913
- // on node clients this function can be undefined
3914
- 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;
3915
4302
 
3916
- const rtcStats = {
3917
- sendEvent: (type, value) => {
3918
- server.trace("customEvent", null, {
3919
- type,
3920
- value,
3921
- });
3922
- },
3923
- sendAudioMuted: (muted) => {
3924
- rtcStats.sendEvent("audio_muted", { muted });
3925
- },
3926
- sendVideoMuted: (muted) => {
3927
- rtcStats.sendEvent("video_muted", { muted });
3928
- },
3929
- server,
3930
- };
4303
+ const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID);
4304
+ if (!screenShareStreamId) {
4305
+ return;
4306
+ }
3931
4307
 
3932
- const logger$5 = new Logger();
4308
+ const screenshareStream = this.localStreams[screenShareStreamId];
4309
+ if (!screenshareStream) {
4310
+ logger$4.warn("screenshare stream %s not found", screenShareStreamId);
4311
+ return;
4312
+ }
3933
4313
 
3934
- const CAMERA_STREAM_ID$1 = RtcStream.getCameraId();
3935
- const browserName$2 = adapter.browserDetails.browser;
3936
- const browserVersion = adapter.browserDetails.version;
4314
+ const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
3937
4315
 
3938
- if (browserName$2 === "firefox") {
3939
- adapter.browserShim.shimGetDisplayMedia(window, "screen");
3940
- }
4316
+ this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
4317
+ streamId: screenShareStreamId,
4318
+ hasAudioTrack,
4319
+ });
4320
+ }),
4321
+ ];
4322
+ }
3941
4323
 
3942
- let unloading$1 = false;
3943
- if (browserName$2 === "chrome") {
3944
- window.document.addEventListener("beforeunload", () => {
3945
- unloading$1 = true;
3946
- });
3947
- }
4324
+ sendAudioMutedStats(muted) {
4325
+ rtcStats.sendEvent("audio_muted", { muted });
4326
+ }
3948
4327
 
3949
- class BaseRtcManager {
3950
- constructor({ selfId, room, emitter, serverSocket, webrtcProvider, features }) {
3951
- assert$1.ok(selfId, "selfId is required");
3952
- assert$1.ok(room, "room is required");
3953
- assert$1.ok(emitter && emitter.emit, "emitter is required");
3954
- assert$1.ok(serverSocket instanceof ServerSocket, "serverSocket is required");
3955
- assert$1.ok(webrtcProvider, "webrtcProvider is required");
4328
+ sendVideoMutedStats(muted) {
4329
+ rtcStats.sendEvent("video_muted", { muted });
4330
+ }
3956
4331
 
3957
- const { name, session, iceServers, sfuServer, mediaserverConfigTtlSeconds } = room;
4332
+ sendStatsCustomEvent(eventName, data) {
4333
+ rtcStats.sendEvent(eventName, data);
4334
+ }
3958
4335
 
3959
- this._selfId = selfId;
3960
- this._roomName = name;
3961
- this._roomSessionId = session && session.id;
3962
- this.peerConnections = {};
3963
- this.localStreams = {};
3964
- this.enabledLocalStreamIds = [];
3965
- this._screenshareVideoTrackIds = [];
3966
- this._socketListenerDeregisterFunctions = [];
3967
- this._localStreamDeregisterFunction = null;
3968
- this._emitter = emitter;
3969
- this._serverSocket = serverSocket;
3970
- this._webrtcProvider = webrtcProvider;
3971
- this._features = features || {};
3972
- this._isAudioOnlyMode = false;
4336
+ rtcStatsDisconnect() {
4337
+ rtcStats.server.close();
4338
+ }
3973
4339
 
3974
- this.offerOptions = { offerToReceiveAudio: true, offerToReceiveVideo: true };
3975
- this._pendingActionsForConnectedPeerConnections = [];
4340
+ rtcStatsReconnect() {
4341
+ if (!rtcStats.server.connected) {
4342
+ rtcStats.server.connect();
4343
+ }
4344
+ }
3976
4345
 
3977
- this._audioTrackOnEnded = () => {
3978
- // There are a couple of reasons the microphone could stop working.
3979
- // One of them is getting unplugged. The other is the Chrome audio
3980
- // process crashing. The third is the tab being closed.
3981
- // https://bugs.chromium.org/p/chromium/issues/detail?id=1050008
3982
- rtcStats.sendEvent("audio_ended", { unloading: unloading$1 });
3983
- this._emit(rtcManagerEvents.MICROPHONE_STOPPED_WORKING, {});
3984
- };
4346
+ setAudioOnly(audioOnly) {
4347
+ this._isAudioOnlyMode = audioOnly;
3985
4348
 
3986
- this._updateAndScheduleMediaServersRefresh({
3987
- sfuServer,
3988
- iceServers: iceServers.iceServers || [],
3989
- mediaserverConfigTtlSeconds,
4349
+ this._forEachPeerConnection((session) => {
4350
+ if (session.hasConnectedPeerConnection()) {
4351
+ this._withForcedRenegotiation(session, () =>
4352
+ session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
4353
+ );
4354
+ }
3990
4355
  });
3991
-
3992
- this.totalSessionsCreated = 0;
3993
4356
  }
3994
4357
 
3995
- numberOfPeerconnections() {
3996
- return Object.keys(this.peerConnections).length;
4358
+ setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
4359
+ const localScreenshareStream = this._getFirstLocalNonCameraStream();
4360
+
4361
+ this._screenshareVideoTrackIds = [
4362
+ ...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
4363
+ ...remoteScreenshareVideoTrackIds,
4364
+ ];
3997
4365
  }
3998
4366
 
3999
- numberOfRemotePeers() {
4000
- return Object.values(this.peerConnections).filter((session) => session.clientId !== this._selfId).length;
4367
+ setRoomSessionId(roomSessionId) {
4368
+ this._roomSessionId = roomSessionId;
4001
4369
  }
4002
4370
 
4003
4371
  _setConnectionStatus(session, newStatus, clientId) {
@@ -4040,7 +4408,7 @@ class BaseRtcManager {
4040
4408
  receiver.playoutDelayHint = MEDIA_JITTER_BUFFER_TARGET / 1000; // seconds
4041
4409
  });
4042
4410
  } catch (error) {
4043
- logger$5.error("Error during setting jitter buffer target:", error);
4411
+ logger$4.error("Error during setting jitter buffer target:", error);
4044
4412
  }
4045
4413
  }
4046
4414
 
@@ -4052,14 +4420,6 @@ class BaseRtcManager {
4052
4420
  this._emitter.emit(eventName, data);
4053
4421
  }
4054
4422
 
4055
- isInitializedWith({ selfId, roomName, isSfu }) {
4056
- return this._selfId === selfId && this._roomName === roomName && isSfu === !!this._sfuServer;
4057
- }
4058
-
4059
- supportsScreenShareAudio() {
4060
- return true;
4061
- }
4062
-
4063
4423
  _addEnabledLocalStreamId(streamId) {
4064
4424
  this.enabledLocalStreamIds.push(streamId);
4065
4425
  }
@@ -4085,7 +4445,7 @@ class BaseRtcManager {
4085
4445
  // Some macs + ios devices have troubles using h264 encoder since safari 14
4086
4446
  // this will make them encode VP8 instead if available
4087
4447
  const deprioritizeH264Encoding =
4088
- browserName$2 === "safari" && browserVersion >= 14 && this._features.deprioritizeH264OnSafari;
4448
+ browserName$1 === "safari" && browserVersion >= 14 && this._features.deprioritizeH264OnSafari;
4089
4449
 
4090
4450
  this.peerConnections[peerConnectionId] = session = new Session({
4091
4451
  peerConnectionId,
@@ -4102,11 +4462,11 @@ class BaseRtcManager {
4102
4462
  }
4103
4463
 
4104
4464
  _getLocalCameraStream() {
4105
- return this.localStreams[CAMERA_STREAM_ID$1];
4465
+ return this.localStreams[CAMERA_STREAM_ID];
4106
4466
  }
4107
4467
 
4108
4468
  _getNonLocalCameraStreamIds() {
4109
- return Object.keys(this.localStreams).filter((streamId) => streamId !== CAMERA_STREAM_ID$1);
4469
+ return Object.keys(this.localStreams).filter((streamId) => streamId !== CAMERA_STREAM_ID);
4110
4470
  }
4111
4471
 
4112
4472
  _isScreensharingLocally() {
@@ -4135,7 +4495,7 @@ class BaseRtcManager {
4135
4495
  }
4136
4496
  const session = this._getOrCreateSession(peerConnectionId, initialBandwidth);
4137
4497
  const constraints = { optional: [] };
4138
- if (browserName$2 === "chrome") {
4498
+ if (browserName$1 === "chrome") {
4139
4499
  constraints.optional.push({
4140
4500
  googCpuOveruseDetection: true,
4141
4501
  });
@@ -4179,7 +4539,7 @@ class BaseRtcManager {
4179
4539
  }
4180
4540
  }
4181
4541
 
4182
- if (browserName$2 === "chrome") {
4542
+ if (browserName$1 === "chrome") {
4183
4543
  peerConnectionConfig.sdpSemantics = "unified-plan";
4184
4544
  }
4185
4545
 
@@ -4246,7 +4606,7 @@ class BaseRtcManager {
4246
4606
  if (
4247
4607
  !session.wasEverConnected &&
4248
4608
  (pc.iceConnectionState.match(/connected|completed/) ||
4249
- (browserName$2 === "chrome" && pc.localDescription && pc.localDescription.type === "answer"))
4609
+ (browserName$1 === "chrome" && pc.localDescription && pc.localDescription.type === "answer"))
4250
4610
  ) {
4251
4611
  session.wasEverConnected = true;
4252
4612
  if (this._features.bandwidth !== "false") {
@@ -4324,7 +4684,7 @@ class BaseRtcManager {
4324
4684
  }
4325
4685
  };
4326
4686
 
4327
- const localCameraStream = this.localStreams[CAMERA_STREAM_ID$1];
4687
+ const localCameraStream = this.localStreams[CAMERA_STREAM_ID];
4328
4688
  if (shouldAddLocalVideo && localCameraStream) {
4329
4689
  session.addStream(localCameraStream);
4330
4690
  }
@@ -4333,7 +4693,7 @@ class BaseRtcManager {
4333
4693
  // added in a separate session/peerConnection by SfuV2RtcManager
4334
4694
  if (shouldAddLocalVideo) {
4335
4695
  Object.keys(this.localStreams).forEach((id) => {
4336
- if (id === CAMERA_STREAM_ID$1) {
4696
+ if (id === CAMERA_STREAM_ID) {
4337
4697
  return;
4338
4698
  }
4339
4699
  const screenshareStream = this.localStreams[id];
@@ -4361,7 +4721,7 @@ class BaseRtcManager {
4361
4721
  _cleanup(peerConnectionId) {
4362
4722
  const session = this._getSession(peerConnectionId);
4363
4723
  if (!session) {
4364
- logger$5.warn("No RTCPeerConnection in RTCManager.disconnect()", peerConnectionId);
4724
+ logger$4.warn("No RTCPeerConnection in RTCManager.disconnect()", peerConnectionId);
4365
4725
  return;
4366
4726
  }
4367
4727
  session.close();
@@ -4369,8 +4729,7 @@ class BaseRtcManager {
4369
4729
  }
4370
4730
 
4371
4731
  _forEachPeerConnection(func) {
4372
- Object.keys(this.peerConnections).forEach((peerConnectionId) => {
4373
- const peerConnection = this.peerConnections[peerConnectionId];
4732
+ Object.values(this.peerConnections).forEach((peerConnection) => {
4374
4733
  func(peerConnection);
4375
4734
  });
4376
4735
  }
@@ -4391,10 +4750,10 @@ class BaseRtcManager {
4391
4750
  const promises = [];
4392
4751
  this._forEachPeerConnection((session) => {
4393
4752
  if (!session.hasConnectedPeerConnection()) {
4394
- logger$5.info("Session doesn't have a connected PeerConnection, adding pending action!");
4753
+ logger$4.info("Session doesn't have a connected PeerConnection, adding pending action!");
4395
4754
  const pendingActions = this._pendingActionsForConnectedPeerConnections;
4396
4755
  if (!pendingActions) {
4397
- logger$5.warn(
4756
+ logger$4.warn(
4398
4757
  `No pending action is created to repalce track, because the pending actions array is null`
4399
4758
  );
4400
4759
  return;
@@ -4403,7 +4762,7 @@ class BaseRtcManager {
4403
4762
  const action = () => {
4404
4763
  const replacedTrackPromise = session.replaceTrack(oldTrack, newTrack);
4405
4764
  if (!replacedTrackPromise) {
4406
- logger$5.error("replaceTrack returned false!");
4765
+ logger$4.error("replaceTrack returned false!");
4407
4766
  reject(`ReplaceTrack returned false`);
4408
4767
  return;
4409
4768
  }
@@ -4416,7 +4775,7 @@ class BaseRtcManager {
4416
4775
  }
4417
4776
  const replacedTrackResult = session.replaceTrack(oldTrack, newTrack);
4418
4777
  if (!replacedTrackResult) {
4419
- logger$5.error("replaceTrack returned false!");
4778
+ logger$4.error("replaceTrack returned false!");
4420
4779
  return;
4421
4780
  }
4422
4781
  promises.push(replacedTrackResult);
@@ -4424,138 +4783,26 @@ class BaseRtcManager {
4424
4783
  return Promise.all(promises);
4425
4784
  }
4426
4785
 
4427
- _removeStreamFromPeerConnections(stream) {
4428
- this._forEachPeerConnection((session) => {
4429
- this._withForcedRenegotiation(session, () => session.removeStream(stream));
4430
- });
4431
- }
4432
-
4433
- _removeTrackFromPeerConnections(track) {
4434
- this._forEachPeerConnection((session) => {
4435
- this._withForcedRenegotiation(session, () => session.removeTrack(track));
4436
- });
4437
- }
4438
-
4439
- _addLocalStream(streamId, stream) {
4440
- this._addEnabledLocalStreamId(streamId);
4441
- this.localStreams[streamId] = stream;
4442
- }
4443
-
4444
- _removeLocalStream(streamId) {
4445
- delete this.localStreams[streamId];
4446
- this._deleteEnabledLocalStreamId(streamId);
4447
- }
4448
-
4449
- maybeRestrictRelayBandwidth(session) {
4450
- session.maybeRestrictRelayBandwidth();
4451
- }
4452
-
4453
- addNewStream(streamId, stream) {
4454
- if (stream === this.localStreams[streamId]) {
4455
- // this can happen after reconnect. We do not want to add the stream to the
4456
- // peerconnection again.
4457
- return;
4458
- }
4459
-
4460
- this._addLocalStream(streamId, stream);
4461
-
4462
- if (streamId === CAMERA_STREAM_ID$1) {
4463
- this._addStreamToPeerConnections(stream);
4464
- const [audioTrack] = stream.getAudioTracks();
4465
- if (audioTrack) {
4466
- this._startMonitoringAudioTrack(audioTrack);
4467
- }
4468
-
4469
- // This should not be needed, but checking nonetheless
4470
- if (this._localStreamDeregisterFunction) {
4471
- this._localStreamDeregisterFunction();
4472
- this._localStreamDeregisterFunction = null;
4473
- }
4474
-
4475
- const localStreamHandler = (e) => {
4476
- const { enable, track } = e.detail;
4477
- this._handleStopOrResumeVideo({ enable, track });
4478
- };
4479
-
4480
- stream.addEventListener("stopresumevideo", localStreamHandler);
4481
- this._localStreamDeregisterFunction = () => {
4482
- stream.removeEventListener("stopresumevideo", localStreamHandler);
4483
- };
4484
-
4485
- return;
4486
- }
4487
-
4488
- // at this point it is clearly a screensharing stream.
4489
- this._screenshareVideoTrackIds.push(stream.getVideoTracks()[0].id);
4490
- this._shareScreen(streamId, stream);
4491
- return;
4492
- }
4493
-
4494
- removeStream(streamId, stream) {
4495
- // essentially we only ever remove screensharing streams.
4496
- // SFU and P2P provide their own ways of doing this.
4497
- this._removeLocalStream(streamId, stream);
4498
- }
4499
-
4500
- replaceTrack(oldTrack, newTrack) {
4501
- if (oldTrack && oldTrack.kind === "audio") {
4502
- this._stopMonitoringAudioTrack(oldTrack);
4503
- }
4504
- if (newTrack && newTrack.kind === "audio") {
4505
- this._startMonitoringAudioTrack(newTrack);
4506
- }
4507
- return this._replaceTrackToPeerConnections(oldTrack, newTrack);
4508
- }
4509
-
4510
- accept({ clientId, shouldAddLocalVideo }) {
4511
- return this.acceptNewStream({ streamId: clientId, clientId, shouldAddLocalVideo });
4512
- }
4513
-
4514
- disconnectAll() {
4515
- Object.keys(this.peerConnections).forEach((peerConnectionId) => {
4516
- this.disconnect(peerConnectionId);
4517
- });
4518
- this.peerConnections = {};
4519
- this._socketListenerDeregisterFunctions.forEach((func) => {
4520
- func();
4786
+ _removeStreamFromPeerConnections(stream) {
4787
+ this._forEachPeerConnection((session) => {
4788
+ this._withForcedRenegotiation(session, () => session.removeStream(stream));
4521
4789
  });
4522
- this._socketListenerDeregisterFunctions = [];
4523
-
4524
- if (this._localStreamDeregisterFunction) {
4525
- this._localStreamDeregisterFunction();
4526
- this._localStreamDeregisterFunction = null;
4527
- }
4528
4790
  }
4529
4791
 
4530
- // the user has muted/unmuted the audio track on the local stream.
4531
- stopOrResumeAudio(/*localStream, enable*/) {}
4532
-
4533
- // the user has muted/unmuted the video track on the local stream.
4534
- stopOrResumeVideo(/*localStream, enable*/) {}
4792
+ _removeTrackFromPeerConnections(track) {
4793
+ this._forEachPeerConnection((session) => {
4794
+ this._withForcedRenegotiation(session, () => session.removeTrack(track));
4795
+ });
4796
+ }
4535
4797
 
4536
- // handle the rtc side-effects of ^
4537
- _handleStopOrResumeVideo(/* { localStream, enable, track } */) {}
4798
+ _addLocalStream(streamId, stream) {
4799
+ this._addEnabledLocalStreamId(streamId);
4800
+ this.localStreams[streamId] = stream;
4801
+ }
4538
4802
 
4539
- /* the Chrome audio process crashed (probably?)
4540
- * try to fix it. Constraints are the audio device constraints
4541
- * used in getUserMedia.
4542
- */
4543
- fixChromeAudio(constraints) {
4544
- if (browserName$2 !== "chrome") {
4545
- return;
4546
- }
4547
- const localStream = this._getLocalCameraStream();
4548
- const audioTrack = localStream.getAudioTracks()[0];
4549
- if (!audioTrack || audioTrack.readyState !== "ended") {
4550
- return;
4551
- }
4552
- return navigator.mediaDevices.getUserMedia({ audio: constraints }).then((stream) => {
4553
- const track = stream.getAudioTracks()[0];
4554
- track.enabled = audioTrack.enabled; // retain mute state and don't accidentally unmute.
4555
- localStream.removeTrack(audioTrack); // remove the old track.
4556
- localStream.addTrack(track); // add the new track.
4557
- return this.replaceTrack(audioTrack, track);
4558
- });
4803
+ _removeLocalStream(streamId) {
4804
+ delete this.localStreams[streamId];
4805
+ this._deleteEnabledLocalStreamId(streamId);
4559
4806
  }
4560
4807
 
4561
4808
  _updateAndScheduleMediaServersRefresh({ iceServers, sfuServer, mediaserverConfigTtlSeconds }) {
@@ -4579,114 +4826,6 @@ class BaseRtcManager {
4579
4826
  this._fetchMediaServersTimer = null;
4580
4827
  }
4581
4828
 
4582
- setupSocketListeners() {
4583
- this._socketListenerDeregisterFunctions = [
4584
- () => this._clearMediaServersRefresh(),
4585
-
4586
- this._serverSocket.on(PROTOCOL_RESPONSES.MEDIASERVER_CONFIG, (data) => {
4587
- if (data.error) {
4588
- logger$5.warn("FETCH_MEDIASERVER_CONFIG failed:", data.error);
4589
- return;
4590
- }
4591
- this._updateAndScheduleMediaServersRefresh(data);
4592
- }),
4593
-
4594
- this._serverSocket.on(RELAY_MESSAGES.READY_TO_RECEIVE_OFFER, (data) => {
4595
- this._connect(data.clientId);
4596
- }),
4597
-
4598
- this._serverSocket.on(RELAY_MESSAGES.ICE_CANDIDATE, (data) => {
4599
- const session = this._getSession(data.clientId);
4600
- if (!session) {
4601
- logger$5.warn("No RTCPeerConnection on ICE_CANDIDATE", data);
4602
- return;
4603
- }
4604
- session.addIceCandidate(data.message);
4605
- }),
4606
-
4607
- this._serverSocket.on(RELAY_MESSAGES.ICE_END_OF_CANDIDATES, (data) => {
4608
- const session = this._getSession(data.clientId);
4609
- if (!session) {
4610
- logger$5.warn("No RTCPeerConnection on ICE_END_OF_CANDIDATES", data);
4611
- return;
4612
- }
4613
- session.addIceCandidate(null);
4614
- }),
4615
-
4616
- // when a new SDP offer is received from another client
4617
- this._serverSocket.on(RELAY_MESSAGES.SDP_OFFER, (data) => {
4618
- const session = this._getSession(data.clientId);
4619
- if (!session) {
4620
- logger$5.warn("No RTCPeerConnection on SDP_OFFER", data);
4621
- return;
4622
- }
4623
- const offer = this._transformIncomingSdp(data.message, session.pc);
4624
- session.handleOffer(offer).then((answer) => {
4625
- this._emitServerEvent(RELAY_MESSAGES.SDP_ANSWER, {
4626
- receiverId: data.clientId,
4627
- message: this._transformOutgoingSdp(answer),
4628
- });
4629
- });
4630
- }),
4631
-
4632
- // when a new SDP answer is received from another client
4633
- this._serverSocket.on(RELAY_MESSAGES.SDP_ANSWER, (data) => {
4634
- const session = this._getSession(data.clientId);
4635
- if (!session) {
4636
- logger$5.warn("No RTCPeerConnection on SDP_ANSWER", data);
4637
- return;
4638
- }
4639
- const answer = this._transformIncomingSdp(data.message, session.pc);
4640
- session.handleAnswer(answer);
4641
- }),
4642
-
4643
- // if this is a reconnect to signal-server during screen-share we must let signal-server know
4644
- this._serverSocket.on(PROTOCOL_RESPONSES.ROOM_JOINED, ({ room: { sfuServer: isSfu } }) => {
4645
- if (isSfu || !this._wasScreenSharing) return;
4646
-
4647
- const screenShareStreamId = Object.keys(this.localStreams).find((id) => id !== CAMERA_STREAM_ID$1);
4648
- if (!screenShareStreamId) {
4649
- return;
4650
- }
4651
-
4652
- const screenshareStream = this.localStreams[screenShareStreamId];
4653
- if (!screenshareStream) {
4654
- logger$5.warn("screenshare stream %s not found", screenShareStreamId);
4655
- return;
4656
- }
4657
-
4658
- const hasAudioTrack = screenshareStream.getAudioTracks().length > 0;
4659
-
4660
- this._emitServerEvent(PROTOCOL_REQUESTS.START_SCREENSHARE, {
4661
- streamId: screenShareStreamId,
4662
- hasAudioTrack,
4663
- });
4664
- }),
4665
- ];
4666
- }
4667
-
4668
- sendAudioMutedStats(muted) {
4669
- rtcStats.sendEvent("audio_muted", { muted });
4670
- }
4671
-
4672
- sendVideoMutedStats(muted) {
4673
- rtcStats.sendEvent("video_muted", { muted });
4674
- }
4675
-
4676
- sendStatsCustomEvent(eventName, data) {
4677
- rtcStats.sendEvent(eventName, data);
4678
- }
4679
-
4680
- rtcStatsDisconnect() {
4681
- rtcStats.server.close();
4682
- }
4683
-
4684
- rtcStatsReconnect() {
4685
- if (!rtcStats.server.connected) {
4686
- rtcStats.server.connect();
4687
- }
4688
- }
4689
-
4690
4829
  _startMonitoringAudioTrack(track) {
4691
4830
  track.addEventListener("ended", this._audioTrackOnEnded);
4692
4831
  }
@@ -4694,113 +4833,6 @@ class BaseRtcManager {
4694
4833
  _stopMonitoringAudioTrack(track) {
4695
4834
  track.removeEventListener("ended", this._audioTrackOnEnded);
4696
4835
  }
4697
-
4698
- setAudioOnly(audioOnly) {
4699
- this._isAudioOnlyMode = audioOnly;
4700
-
4701
- this._forEachPeerConnection((session) => {
4702
- if (session.hasConnectedPeerConnection()) {
4703
- this._withForcedRenegotiation(session, () =>
4704
- session.setAudioOnly(this._isAudioOnlyMode, this._screenshareVideoTrackIds)
4705
- );
4706
- }
4707
- });
4708
- }
4709
-
4710
- setRemoteScreenshareVideoTrackIds(remoteScreenshareVideoTrackIds = []) {
4711
- const localScreenshareStream = this._getFirstLocalNonCameraStream();
4712
-
4713
- this._screenshareVideoTrackIds = [
4714
- ...(localScreenshareStream?.track ? [localScreenshareStream.track.id] : []),
4715
- ...remoteScreenshareVideoTrackIds,
4716
- ];
4717
- }
4718
-
4719
- setRoomSessionId(roomSessionId) {
4720
- this._roomSessionId = roomSessionId;
4721
- }
4722
- }
4723
-
4724
- const lowPixelCount = 320 * 180;
4725
- const lowBitratePerPixel = 150000 / lowPixelCount;
4726
- const highPixelCount = 1280 * 720;
4727
- const highBitratePerPixel = 1000000 / highPixelCount;
4728
- const bitrateChangePerPixel = (highBitratePerPixel - lowBitratePerPixel) / (highPixelCount - lowPixelCount);
4729
-
4730
- // calculates a bitrate for a given resolution+frameRate
4731
- function getOptimalBitrate(width, height, frameRate) {
4732
- let targetPixelCount = width * height;
4733
- if (targetPixelCount < lowPixelCount) targetPixelCount = lowPixelCount;
4734
- if (targetPixelCount > highPixelCount) targetPixelCount = highPixelCount;
4735
-
4736
- let targetBitratePerPixel = lowBitratePerPixel;
4737
- if (targetPixelCount > highPixelCount) targetBitratePerPixel = highBitratePerPixel;
4738
- else if (targetPixelCount > lowPixelCount) {
4739
- targetBitratePerPixel += (targetPixelCount - lowPixelCount) * bitrateChangePerPixel;
4740
- }
4741
-
4742
- // we use the actual resolution for the target bitrate
4743
- let targetBitrate = width * height * targetBitratePerPixel;
4744
-
4745
- // adjust bitrate down a bit if reduced framerate
4746
- if (frameRate <= 15) targetBitrate = targetBitrate * 0.7;
4747
- else if (frameRate <= 24) targetBitrate = targetBitrate * 0.9;
4748
-
4749
- return targetBitrate;
4750
- }
4751
-
4752
- // taken from https://github.com/sindresorhus/ip-regex ^5.0.0
4753
- // inlined because it's import caused errors in browser-sdk when running tests
4754
- const word = "[a-fA-F\\d:]";
4755
-
4756
- const boundry = (options) =>
4757
- options && options.includeBoundaries ? `(?:(?<=\\s|^)(?=${word})|(?<=${word})(?=\\s|$))` : "";
4758
-
4759
- const v4 = "(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)(?:\\.(?:25[0-5]|2[0-4]\\d|1\\d\\d|[1-9]\\d|\\d)){3}";
4760
-
4761
- const v6segment = "[a-fA-F\\d]{1,4}";
4762
-
4763
- const v6 = `
4764
- (?:
4765
- (?:${v6segment}:){7}(?:${v6segment}|:)| // 1:2:3:4:5:6:7:: 1:2:3:4:5:6:7:8
4766
- (?:${v6segment}:){6}(?:${v4}|:${v6segment}|:)| // 1:2:3:4:5:6:: 1:2:3:4:5:6::8 1:2:3:4:5:6::8 1:2:3:4:5:6::1.2.3.4
4767
- (?:${v6segment}:){5}(?::${v4}|(?::${v6segment}){1,2}|:)| // 1:2:3:4:5:: 1:2:3:4:5::7:8 1:2:3:4:5::8 1:2:3:4:5::7:1.2.3.4
4768
- (?:${v6segment}:){4}(?:(?::${v6segment}){0,1}:${v4}|(?::${v6segment}){1,3}|:)| // 1:2:3:4:: 1:2:3:4::6:7:8 1:2:3:4::8 1:2:3:4::6:7:1.2.3.4
4769
- (?:${v6segment}:){3}(?:(?::${v6segment}){0,2}:${v4}|(?::${v6segment}){1,4}|:)| // 1:2:3:: 1:2:3::5:6:7:8 1:2:3::8 1:2:3::5:6:7:1.2.3.4
4770
- (?:${v6segment}:){2}(?:(?::${v6segment}){0,3}:${v4}|(?::${v6segment}){1,5}|:)| // 1:2:: 1:2::4:5:6:7:8 1:2::8 1:2::4:5:6:7:1.2.3.4
4771
- (?:${v6segment}:){1}(?:(?::${v6segment}){0,4}:${v4}|(?::${v6segment}){1,6}|:)| // 1:: 1::3:4:5:6:7:8 1::8 1::3:4:5:6:7:1.2.3.4
4772
- (?::(?:(?::${v6segment}){0,5}:${v4}|(?::${v6segment}){1,7}|:)) // ::2:3:4:5:6:7:8 ::2:3:4:5:6:7:8 ::8 ::1.2.3.4
4773
- )(?:%[0-9a-zA-Z]{1,})? // %eth0 %1
4774
- `
4775
- .replace(/\s*\/\/.*$/gm, "")
4776
- .replace(/\n/g, "")
4777
- .trim();
4778
-
4779
- // Pre-compile only the exact regexes because adding a global flag make regexes stateful
4780
- const v46Exact = new RegExp(`(?:^${v4}$)|(?:^${v6}$)`);
4781
- const v4exact = new RegExp(`^${v4}$`);
4782
- const v6exact = new RegExp(`^${v6}$`);
4783
-
4784
- const ipRegex = (options) =>
4785
- options && options.exact
4786
- ? v46Exact
4787
- : new RegExp(
4788
- `(?:${boundry(options)}${v4}${boundry(options)})|(?:${boundry(options)}${v6}${boundry(options)})`,
4789
- "g"
4790
- );
4791
-
4792
- ipRegex.v4 = (options) =>
4793
- options && options.exact ? v4exact : new RegExp(`${boundry(options)}${v4}${boundry(options)}`, "g");
4794
- ipRegex.v6 = (options) =>
4795
- options && options.exact ? v6exact : new RegExp(`${boundry(options)}${v6}${boundry(options)}`, "g");
4796
-
4797
- const logger$4 = new Logger();
4798
-
4799
- const ICE_PUBLIC_IP_GATHERING_TIMEOUT = 3 * 1000;
4800
- const CAMERA_STREAM_ID = RtcStream.getCameraId();
4801
- const browserName$1 = adapter.browserDetails.browser;
4802
-
4803
- class P2pRtcManager extends BaseRtcManager {
4804
4836
  _connect(clientId) {
4805
4837
  this.rtcStatsReconnect();
4806
4838
  const shouldAddLocalVideo = true;
@@ -4908,15 +4940,16 @@ class P2pRtcManager extends BaseRtcManager {
4908
4940
  }
4909
4941
  session.isOperationPending = true;
4910
4942
 
4911
- const { vp9On, av1On, redOn } = this._features;
4943
+ const { vp9On, av1On, redOn, rtpAbsCaptureTimeOn } = this._features;
4912
4944
 
4913
4945
  // Set codec preferences to video transceivers
4914
4946
  if (vp9On || av1On || redOn) {
4915
4947
  this._setCodecPreferences(pc, vp9On, av1On, redOn);
4916
4948
  }
4917
-
4918
4949
  pc.createOffer(constraints || this.offerOptions)
4919
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);
4920
4953
  // SDP munging workaround for Firefox, because it doesn't support setCodecPreferences()
4921
4954
  // Only vp9 because FF does not support AV1 yet
4922
4955
  if ((vp9On || redOn) && browserName$1 === "firefox") {
@@ -5275,7 +5308,7 @@ class P2pRtcManager extends BaseRtcManager {
5275
5308
  }
5276
5309
 
5277
5310
  removeStream(streamId, stream, requestedByClientId) {
5278
- super.removeStream(streamId, stream);
5311
+ this._removeLocalStream(streamId, stream);
5279
5312
  this._removeStreamFromPeerConnections(stream);
5280
5313
  this._wasScreenSharing = false;
5281
5314
  this._emitServerEvent(PROTOCOL_REQUESTS.STOP_SCREENSHARE, { streamId, requestedByClientId });
@@ -6173,6 +6206,7 @@ class VegaMediaQualityMonitor extends EventEmitter {
6173
6206
  }
6174
6207
  }
6175
6208
 
6209
+ const adapter = adapterRaw.default ?? adapterRaw;
6176
6210
  const logger = new Logger();
6177
6211
 
6178
6212
  const browserName = adapter.browserDetails.browser;
@@ -7983,6 +8017,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
7983
8017
  const dispatcher = selectRtcConnectionRaw(state).rtcManagerDispatcher;
7984
8018
  const isCameraEnabled = selectIsCameraEnabled(state);
7985
8019
  const isMicrophoneEnabled = selectIsMicrophoneEnabled(state);
8020
+ const isNodeSdk = selectAppIsNodeSdk(state);
7986
8021
  if (dispatcher) {
7987
8022
  return;
7988
8023
  }
@@ -8008,6 +8043,7 @@ const doConnectRtc = createAppThunk(() => (dispatch, getState) => {
8008
8043
  vp9On: false,
8009
8044
  h264On: false,
8010
8045
  simulcastScreenshareOn: false,
8046
+ deviceHandlerFactory: isNodeSdk ? Chrome111.createFactory() : undefined,
8011
8047
  },
8012
8048
  });
8013
8049
  dispatch(rtcDispatcherCreated(rtcManagerDispatcher));
@@ -9464,7 +9500,7 @@ class LocalParticipant extends RoomParticipant {
9464
9500
  }
9465
9501
  }
9466
9502
 
9467
- const sdkVersion = "0.3.0";
9503
+ const sdkVersion = "0.4.0";
9468
9504
 
9469
9505
  const defaultSubdomainPattern = /^(?:([^.]+)[.])?((:?[^.]+[.]){1,}[^.]+)$/;
9470
9506
  const localstackPattern = /^(?:([^.]+)-)?(ip-[^.]*[.](?:hereby[.]dev|rfc1918[.]disappear[.]at)(?::\d+|))$/;
@@ -9536,4 +9572,4 @@ function createServices() {
9536
9572
  };
9537
9573
  }
9538
9574
 
9539
- 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 };