@webex/web-client-media-engine 1.39.0 → 1.40.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/esm/index.js CHANGED
@@ -974,6 +974,10 @@ var LocalTrackEvents;
974
974
  * Fires when there has been a change in the underlying track.
975
975
  */
976
976
  LocalTrackEvents["UnderlyingTrackChange"] = "underlying-track-change";
977
+ /**
978
+ * Fires when the applyConstraints() has been called for the track.
979
+ */
980
+ LocalTrackEvents["TrackConstraintsChange"] = "track-constraints-change";
977
981
  })(LocalTrackEvents || (LocalTrackEvents = {}));
978
982
  // TBD: Fix this once types are published separately
979
983
  // export type TrackEffect = BaseMicrophoneEffect | BaseCameraEffect;
@@ -1147,7 +1151,7 @@ class LocalTrack extends EventEmitter$2 {
1147
1151
  return effect;
1148
1152
  }
1149
1153
  /**
1150
- * Cleanup local microphone track.
1154
+ * Cleanup the local microphone track.
1151
1155
  */
1152
1156
  disposeEffects() {
1153
1157
  if (this.effects.size > 0) {
@@ -1157,6 +1161,59 @@ class LocalTrack extends EventEmitter$2 {
1157
1161
  this.emit(LocalTrackEvents.UnderlyingTrackChange);
1158
1162
  }
1159
1163
  }
1164
+ /**
1165
+ * Apply constraints to the track.
1166
+ *
1167
+ * @param constraints - The constraints to apply to the track.
1168
+ * @returns A promise which resolves when the constraints have been successfully applied.
1169
+ */
1170
+ applyConstraints(constraints) {
1171
+ return __awaiter$1(this, void 0, void 0, function* () {
1172
+ logger$3.log(`Applying constraints to local track:`, constraints);
1173
+ const ret = this.underlyingTrack.applyConstraints(constraints).then(() => {
1174
+ this.emit(LocalTrackEvents.TrackConstraintsChange);
1175
+ });
1176
+ return ret;
1177
+ });
1178
+ }
1179
+ /**
1180
+ * Get the current constraints of the track.
1181
+ *
1182
+ * @returns The constraints of the track.
1183
+ */
1184
+ getConstraints() {
1185
+ return this.underlyingTrack.getConstraints();
1186
+ }
1187
+ /**
1188
+ * Get the current settings of the track.
1189
+ *
1190
+ * @returns The settings of the track.
1191
+ */
1192
+ getSettings() {
1193
+ return this.underlyingTrack.getSettings();
1194
+ }
1195
+ /**
1196
+ * Check the resolution and then return how many layers will be active.
1197
+ *
1198
+ * @returns The active layers count.
1199
+ */
1200
+ getNumActiveSimulcastLayers() {
1201
+ let activeSimulcastLayersNumber = 0;
1202
+ if (this.trackState.type === 'audio') {
1203
+ return activeSimulcastLayersNumber;
1204
+ }
1205
+ const videoHeight = this.underlyingTrack.getSettings().height;
1206
+ if (videoHeight <= 180) {
1207
+ activeSimulcastLayersNumber = 1;
1208
+ }
1209
+ else if (videoHeight <= 360) {
1210
+ activeSimulcastLayersNumber = 2;
1211
+ }
1212
+ else {
1213
+ activeSimulcastLayersNumber = 3;
1214
+ }
1215
+ return activeSimulcastLayersNumber;
1216
+ }
1160
1217
  }
1161
1218
  LocalTrack.Events = LocalTrackEvents;
1162
1219
 
@@ -1200,7 +1257,10 @@ class WcmeError {
1200
1257
  }
1201
1258
  }
1202
1259
  /**
1203
- * Creates a camera video track.
1260
+ * Creates a camera video track. Please note that the constraint params in second getUserMedia call would NOT take effect when:
1261
+ *
1262
+ * 1. Previous captured video track from the same device is not stopped .
1263
+ * 2. Previous createCameraTrack() call for the same device is in progress.
1204
1264
  *
1205
1265
  * @param constraints - Video device constraints.
1206
1266
  * @returns A LocalTrack object or an error.
@@ -4991,6 +5051,44 @@ class PeerConnection extends EventEmitter$2 {
4991
5051
  get iceGatheringState() {
4992
5052
  return this.pc.iceGatheringState;
4993
5053
  }
5054
+ /**
5055
+ * Returns the type of a connection that has been established.
5056
+ *
5057
+ * @returns The connection type which would be `ConnectionType`.
5058
+ */
5059
+ getCurrentConnectionType() {
5060
+ var _a;
5061
+ return __awaiter$1(this, void 0, void 0, function* () {
5062
+ // make sure this method only can be called when the ice connection is established;
5063
+ const isIceConnected = this.pc.iceConnectionState === 'connected' || this.pc.iceConnectionState === 'completed';
5064
+ if (!isIceConnected) {
5065
+ throw new Error('Ice connection is not established');
5066
+ }
5067
+ const succeededLocalCandidateIds = new Set();
5068
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
5069
+ const localCandidateStatsReports = [];
5070
+ (yield this.pc.getStats()).forEach((report) => {
5071
+ var _a;
5072
+ // collect all local candidate ids from `candidate-pair` stats reports with `succeeded` state.
5073
+ if (report.type === 'candidate-pair' && ((_a = report.state) === null || _a === void 0 ? void 0 : _a.toLowerCase()) === 'succeeded') {
5074
+ succeededLocalCandidateIds.add(report.localCandidateId);
5075
+ }
5076
+ // collect all `local-candidate` stats.
5077
+ if (report.type === 'local-candidate') {
5078
+ localCandidateStatsReports.push(report);
5079
+ }
5080
+ });
5081
+ // find the `local-candidate` stats which report id contains in `succeededLocalCandidateIds`.
5082
+ const localCandidate = localCandidateStatsReports.find((report) => succeededLocalCandidateIds.has(report.id));
5083
+ if (!localCandidate) {
5084
+ return 'unknown';
5085
+ }
5086
+ if (localCandidate.relayProtocol) {
5087
+ return `TURN-${localCandidate.relayProtocol.toUpperCase()}`;
5088
+ }
5089
+ return (_a = localCandidate.protocol) === null || _a === void 0 ? void 0 : _a.toUpperCase();
5090
+ });
5091
+ }
4994
5092
  }
4995
5093
  PeerConnection.Events = PeerConnectionEvents;
4996
5094
 
@@ -8877,6 +8975,9 @@ function injectJmpAttributes(parsedSdp, csiMap, streamSignalingMode) {
8877
8975
  mLine.addLine(new JmpStreamIdModeLine(streamSignalingMode));
8878
8976
  }
8879
8977
  });
8978
+ }
8979
+ function hasSimulcast(av) {
8980
+ return !!av.simulcast || av.ssrcGroups.map((sg) => sg.semantics).some((sem) => sem === 'SIM');
8880
8981
  }
8881
8982
 
8882
8983
  class SendOnlyTransceiver extends Transceiver {
@@ -9600,6 +9701,10 @@ const defaultMultistreamConnectionOptions = {
9600
9701
  enableMainAudio: true,
9601
9702
  enableMainVideo: true,
9602
9703
  };
9704
+ const defaultVideoCodecParameters = {
9705
+ 'max-mbps': `${defaultMaxVideoEncodeMbps}`,
9706
+ 'max-fs': `${defaultMaxVideoEncodeFrameSize}`,
9707
+ };
9603
9708
  class MultistreamConnection extends EventEmitter {
9604
9709
  constructor(userOptions = {}) {
9605
9710
  var _a, _b;
@@ -9610,6 +9715,7 @@ class MultistreamConnection extends EventEmitter {
9610
9715
  this.pendingJmpTasks = [];
9611
9716
  this.metricsCallback = () => { };
9612
9717
  this.overuseUpdateCallback = () => { };
9718
+ this.customCodecParameters = new Map();
9613
9719
  this.midMap = new Map();
9614
9720
  this.currentMid = 0;
9615
9721
  this.options = Object.assign(Object.assign({}, defaultMultistreamConnectionOptions), userOptions);
@@ -9622,6 +9728,7 @@ class MultistreamConnection extends EventEmitter {
9622
9728
  const mainSceneId = generateSceneId();
9623
9729
  const videoMainEncodingOptions = this.getVideoEncodingOptions(MediaContent.Main);
9624
9730
  this.createSendTransceiver(MediaType.VideoMain, mainSceneId, videoMainEncodingOptions);
9731
+ this.setCodecParameters(MediaType.VideoMain, defaultVideoCodecParameters);
9625
9732
  this.createSendTransceiver(MediaType.AudioMain, mainSceneId);
9626
9733
  (_a = this.sendTransceivers.get(MediaType.VideoMain)) === null || _a === void 0 ? void 0 : _a.setActive(this.options.enableMainVideo);
9627
9734
  (_b = this.sendTransceivers.get(MediaType.AudioMain)) === null || _b === void 0 ? void 0 : _b.setActive(this.options.enableMainAudio);
@@ -9629,6 +9736,7 @@ class MultistreamConnection extends EventEmitter {
9629
9736
  const videoPresentationEncodingOptions = this.getVideoEncodingOptions(MediaContent.Slides);
9630
9737
  const contentSceneId = generateSceneId();
9631
9738
  this.createSendTransceiver(MediaType.VideoSlides, contentSceneId, videoPresentationEncodingOptions);
9739
+ this.setCodecParameters(MediaType.VideoSlides, defaultVideoCodecParameters);
9632
9740
  this.createSendTransceiver(MediaType.AudioSlides, contentSceneId);
9633
9741
  }
9634
9742
  }
@@ -9993,20 +10101,7 @@ class MultistreamConnection extends EventEmitter {
9993
10101
  return ingressSignaler.getReceiverId();
9994
10102
  });
9995
10103
  if (this.pc.getRemoteDescription()) {
9996
- yield this.pc
9997
- .createOffer()
9998
- .then((offer) => {
9999
- if (!offer.sdp) {
10000
- throw new Error('No SDP offer');
10001
- }
10002
- offer.sdp = this.preProcessLocalOffer(offer.sdp);
10003
- return this.pc.setLocalDescription(offer);
10004
- })
10005
- .then(() => {
10006
- var _a;
10007
- const answer = this.preProcessRemoteAnswer((_a = this.pc.getRemoteDescription()) === null || _a === void 0 ? void 0 : _a.sdp);
10008
- return this.pc.setRemoteDescription({ type: 'answer', sdp: answer });
10009
- });
10104
+ yield this.doLocalOfferAnswer();
10010
10105
  }
10011
10106
  this.recvTransceivers.set(mediaType, [
10012
10107
  ...(this.recvTransceivers.get(mediaType) || []),
@@ -10072,8 +10167,26 @@ class MultistreamConnection extends EventEmitter {
10072
10167
  }
10073
10168
  setAnswer(answer) {
10074
10169
  return __awaiter(this, void 0, void 0, function* () {
10170
+ const isInitialAnswer = !this.pc.getRemoteDescription();
10075
10171
  const sdp = this.preProcessRemoteAnswer(answer);
10076
- return this.pc.setRemoteDescription({ type: 'answer', sdp });
10172
+ return this.pc.setRemoteDescription({ type: 'answer', sdp }).then(() => {
10173
+ if (isInitialAnswer && this.customCodecParameters.size > 0) {
10174
+ this.doLocalOfferAnswer();
10175
+ }
10176
+ });
10177
+ });
10178
+ }
10179
+ doLocalOfferAnswer() {
10180
+ var _a;
10181
+ return __awaiter(this, void 0, void 0, function* () {
10182
+ const offer = yield this.pc.createOffer();
10183
+ if (!offer.sdp) {
10184
+ throw new Error('No SDP offer');
10185
+ }
10186
+ offer.sdp = this.preProcessLocalOffer(offer.sdp);
10187
+ yield this.pc.setLocalDescription(offer);
10188
+ const answer = this.preProcessRemoteAnswer((_a = this.pc.getRemoteDescription()) === null || _a === void 0 ? void 0 : _a.sdp);
10189
+ return this.pc.setRemoteDescription({ type: 'answer', sdp: answer });
10077
10190
  });
10078
10191
  }
10079
10192
  enableMultistreamAudio(enabled) {
@@ -10097,15 +10210,23 @@ class MultistreamConnection extends EventEmitter {
10097
10210
  .filter((av) => av.direction === 'sendrecv')
10098
10211
  .forEach((av) => {
10099
10212
  const egressSignaler = this.streamSignalerManager.getOrCreateEgressStreamSignaler(av.mid);
10100
- const simulcastEnabled = !!av.simulcast;
10213
+ const simulcastEnabled = hasSimulcast(av);
10101
10214
  const rtxEnabled = av.type === 'video';
10102
10215
  egressSignaler.signalStreams(simulcastEnabled, rtxEnabled, av);
10103
- if (av.type === 'video') {
10216
+ const mediaType = [...this.sendTransceivers.keys()].find((key) => { var _a; return ((_a = this.sendTransceivers.get(key)) === null || _a === void 0 ? void 0 : _a.mid) === av.mid; });
10217
+ if (mediaType && this.customCodecParameters.has(mediaType)) {
10104
10218
  [...av.codecs.values()]
10105
- .filter((ci) => ci.name === 'H264')
10219
+ .filter((ci) => ci.name === (av.type === 'audio' ? 'opus' : 'H264'))
10106
10220
  .forEach((ci) => {
10107
- ci.fmtParams.set('max-mbps', `${defaultMaxVideoEncodeMbps}`);
10108
- ci.fmtParams.set('max-fs', `${defaultMaxVideoEncodeFrameSize}`);
10221
+ var _a;
10222
+ (_a = this.customCodecParameters.get(mediaType)) === null || _a === void 0 ? void 0 : _a.forEach((value, param) => {
10223
+ if (value === null) {
10224
+ ci.fmtParams.delete(param);
10225
+ }
10226
+ else {
10227
+ ci.fmtParams.set(param, `${value}`);
10228
+ }
10229
+ });
10109
10230
  });
10110
10231
  }
10111
10232
  });
@@ -10161,6 +10282,30 @@ class MultistreamConnection extends EventEmitter {
10161
10282
  .map((transceiver) => transceiver.publishedTrack)
10162
10283
  .filter(Boolean);
10163
10284
  }
10285
+ setCodecParameters(mediaType, parameters) {
10286
+ return __awaiter(this, void 0, void 0, function* () {
10287
+ const currentParams = this.customCodecParameters.get(mediaType) || new Map();
10288
+ Object.entries(parameters).forEach(([param, value]) => {
10289
+ currentParams.set(param, value);
10290
+ });
10291
+ this.customCodecParameters.set(mediaType, currentParams);
10292
+ if (this.pc.getRemoteDescription()) {
10293
+ yield this.doLocalOfferAnswer();
10294
+ }
10295
+ });
10296
+ }
10297
+ deleteCodecParameters(mediaType, parameters) {
10298
+ return __awaiter(this, void 0, void 0, function* () {
10299
+ const currentParams = this.customCodecParameters.get(mediaType) || new Map();
10300
+ parameters.forEach((param) => {
10301
+ currentParams.set(param, null);
10302
+ });
10303
+ this.customCodecParameters.set(mediaType, currentParams);
10304
+ if (this.pc.getRemoteDescription()) {
10305
+ yield this.doLocalOfferAnswer();
10306
+ }
10307
+ });
10308
+ }
10164
10309
  requestMedia(mediaType, mediaRequests) {
10165
10310
  var _a;
10166
10311
  const task = () => {