@webex/plugin-meetings 3.0.0-beta.1 → 3.0.0-beta.2

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 (111) hide show
  1. package/dist/common/errors/webex-errors.js +5 -29
  2. package/dist/common/errors/webex-errors.js.map +1 -1
  3. package/dist/constants.js +15 -74
  4. package/dist/constants.js.map +1 -1
  5. package/dist/media/index.js +68 -213
  6. package/dist/media/index.js.map +1 -1
  7. package/dist/media/internal-media-core-wrapper.js +22 -0
  8. package/dist/media/internal-media-core-wrapper.js.map +1 -0
  9. package/dist/media/properties.js +20 -25
  10. package/dist/media/properties.js.map +1 -1
  11. package/dist/media/util.js +0 -27
  12. package/dist/media/util.js.map +1 -1
  13. package/dist/meeting/index.js +694 -432
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/request.js +1 -0
  16. package/dist/meeting/request.js.map +1 -1
  17. package/dist/meeting/util.js +3 -44
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meetings/index.js +64 -5
  20. package/dist/meetings/index.js.map +1 -1
  21. package/dist/meetings/util.js +24 -1
  22. package/dist/meetings/util.js.map +1 -1
  23. package/dist/members/index.js +68 -0
  24. package/dist/members/index.js.map +1 -1
  25. package/dist/multistream/mediaRequestManager.js +132 -0
  26. package/dist/multistream/mediaRequestManager.js.map +1 -0
  27. package/dist/multistream/multistreamMedia.js +116 -0
  28. package/dist/multistream/multistreamMedia.js.map +1 -0
  29. package/dist/multistream/receiveSlot.js +209 -0
  30. package/dist/multistream/receiveSlot.js.map +1 -0
  31. package/dist/multistream/receiveSlotManager.js +195 -0
  32. package/dist/multistream/receiveSlotManager.js.map +1 -0
  33. package/dist/multistream/remoteMedia.js +284 -0
  34. package/dist/multistream/remoteMedia.js.map +1 -0
  35. package/dist/multistream/remoteMediaGroup.js +243 -0
  36. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  37. package/dist/multistream/remoteMediaManager.js +1113 -0
  38. package/dist/multistream/remoteMediaManager.js.map +1 -0
  39. package/dist/reconnection-manager/index.js +109 -130
  40. package/dist/reconnection-manager/index.js.map +1 -1
  41. package/dist/roap/index.js +57 -240
  42. package/dist/roap/index.js.map +1 -1
  43. package/dist/roap/request.js +2 -114
  44. package/dist/roap/request.js.map +1 -1
  45. package/dist/roap/turnDiscovery.js +11 -5
  46. package/dist/roap/turnDiscovery.js.map +1 -1
  47. package/dist/statsAnalyzer/global.js +2 -0
  48. package/dist/statsAnalyzer/global.js.map +1 -1
  49. package/dist/statsAnalyzer/index.js +39 -36
  50. package/dist/statsAnalyzer/index.js.map +1 -1
  51. package/package.json +20 -19
  52. package/src/common/errors/webex-errors.js +0 -18
  53. package/src/constants.ts +139 -180
  54. package/src/media/index.js +60 -194
  55. package/src/media/internal-media-core-wrapper.ts +9 -0
  56. package/src/media/properties.js +19 -25
  57. package/src/media/util.js +0 -22
  58. package/src/meeting/index.js +565 -320
  59. package/src/meeting/request.js +1 -0
  60. package/src/meeting/util.js +3 -46
  61. package/src/meetings/index.js +30 -1
  62. package/src/meetings/util.js +23 -2
  63. package/src/members/index.js +48 -0
  64. package/src/multistream/mediaRequestManager.ts +164 -0
  65. package/src/multistream/multistreamMedia.ts +92 -0
  66. package/src/multistream/receiveSlot.ts +141 -0
  67. package/src/multistream/receiveSlotManager.ts +142 -0
  68. package/src/multistream/remoteMedia.ts +219 -0
  69. package/src/multistream/remoteMediaGroup.ts +224 -0
  70. package/src/multistream/remoteMediaManager.ts +911 -0
  71. package/src/reconnection-manager/index.js +40 -53
  72. package/src/roap/index.js +47 -207
  73. package/src/roap/request.js +1 -72
  74. package/src/roap/turnDiscovery.ts +12 -6
  75. package/src/statsAnalyzer/global.js +2 -0
  76. package/src/statsAnalyzer/index.js +32 -46
  77. package/test/integration/spec/journey.js +1 -1
  78. package/test/unit/spec/media/index.ts +223 -0
  79. package/test/unit/spec/media/properties.ts +73 -82
  80. package/test/unit/spec/meeting/effectsState.js +1 -3
  81. package/test/unit/spec/meeting/index.js +420 -228
  82. package/test/unit/spec/meeting/muteState.js +7 -0
  83. package/test/unit/spec/meeting/utils.js +61 -2
  84. package/test/unit/spec/meetings/index.js +0 -4
  85. package/test/unit/spec/members/index.js +164 -2
  86. package/test/unit/spec/multistream/mediaRequestManager.ts +511 -0
  87. package/test/unit/spec/multistream/receiveSlot.ts +104 -0
  88. package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
  89. package/test/unit/spec/multistream/remoteMedia.ts +217 -0
  90. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  91. package/test/unit/spec/multistream/remoteMediaManager.ts +1251 -0
  92. package/test/unit/spec/roap/index.ts +63 -35
  93. package/test/unit/spec/stats-analyzer/index.js +19 -22
  94. package/dist/peer-connection-manager/index.js +0 -794
  95. package/dist/peer-connection-manager/index.js.map +0 -1
  96. package/dist/roap/collection.js +0 -73
  97. package/dist/roap/collection.js.map +0 -1
  98. package/dist/roap/handler.js +0 -337
  99. package/dist/roap/handler.js.map +0 -1
  100. package/dist/roap/state.js +0 -164
  101. package/dist/roap/state.js.map +0 -1
  102. package/dist/roap/util.js +0 -102
  103. package/dist/roap/util.js.map +0 -1
  104. package/src/peer-connection-manager/index.js +0 -723
  105. package/src/roap/collection.js +0 -63
  106. package/src/roap/handler.js +0 -252
  107. package/src/roap/state.js +0 -149
  108. package/src/roap/util.js +0 -93
  109. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  110. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  111. package/test/unit/spec/roap/util.js +0 -30
@@ -1,11 +1,11 @@
1
1
  import uuid from 'uuid';
2
2
  import {cloneDeep, isEqual, pick, isString} from 'lodash';
3
3
  import {StatelessWebexPlugin} from '@webex/webex-core';
4
- import {Media as WebRTCMedia} from '@webex/internal-media-core';
4
+ import {Media as WebRTCMedia, MediaConnection as MC} from '@webex/internal-media-core';
5
5
 
6
6
  import {
7
7
  MeetingNotActiveError, createMeetingsError, UserInLobbyError,
8
- NoMediaEstablishedYetError, UserNotJoinedError, InvalidSdpError
8
+ NoMediaEstablishedYetError, UserNotJoinedError
9
9
  } from '../common/errors/webex-errors';
10
10
  import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
11
11
  import NetworkQualityMonitor from '../networkQualityMonitor';
@@ -18,9 +18,8 @@ import MeetingStateMachine from '../meeting/state';
18
18
  import createMuteState from '../meeting/muteState';
19
19
  import createEffectsState from '../meeting/effectsState';
20
20
  import LocusInfo from '../locus-info';
21
- import PeerConnectionManager from '../peer-connection-manager';
22
21
  import Metrics from '../metrics';
23
- import {trigger, mediaType, eventType} from '../metrics/config';
22
+ import {trigger, mediaType, error as MetricsError, eventType} from '../metrics/config';
24
23
  import ReconnectionManager from '../reconnection-manager';
25
24
  import MeetingRequest from '../meeting/request';
26
25
  import Members from '../members/index';
@@ -44,7 +43,6 @@ import {
44
43
  FLOOR_ACTION,
45
44
  FULL_STATE,
46
45
  LAYOUT_TYPES,
47
- LIVE,
48
46
  LOCUSINFO,
49
47
  MEETING_INFO_FAILURE_REASON,
50
48
  MEETING_REMOVED_REASON,
@@ -60,9 +58,6 @@ import {
60
58
  PSTN_STATUS,
61
59
  QUALITY_LEVELS,
62
60
  RECORDING_STATE,
63
- ROAP_SEQ_PRE,
64
- SDP,
65
- SENDRECV,
66
61
  SHARE_STATUS,
67
62
  SHARE_STOPPED_REASON,
68
63
  VIDEO_RESOLUTIONS,
@@ -75,7 +70,10 @@ import ParameterError from '../common/errors/parameter';
75
70
  import MediaError from '../common/errors/media';
76
71
  import {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '../meeting-info/meeting-info-v2';
77
72
  import BrowserDetection from '../common/browser-detection';
78
- import RoapCollection from '../roap/collection';
73
+ import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
74
+ import {MediaRequestManager} from '../multistream/mediaRequestManager';
75
+ import {RemoteMediaManager, Event as RemoteMediaManagerEvent} from '../multistream/remoteMediaManager';
76
+ import {MultistreamMedia} from '../multistream/multistreamMedia';
79
77
 
80
78
  import InMeetingActions from './in-meeting-actions';
81
79
 
@@ -143,6 +141,7 @@ export const MEDIA_UPDATE_TYPE = {
143
141
  * @property {String} [meetingQuality.local]
144
142
  * @property {String} [meetingQuality.remote]
145
143
  * @property {Boolean} [rejoin]
144
+ * @property {Boolean} [enableMultistream]
146
145
  */
147
146
 
148
147
  /**
@@ -468,15 +467,6 @@ export default class Meeting extends StatelessWebexPlugin {
468
467
  * @memberof Meeting
469
468
  */
470
469
  this.deviceUrl = attrs.deviceUrl;
471
- /**
472
- * @description set you -1 as default values is 0 (used to idenfify if 1st roap request was sent)
473
- * @instance
474
- * @type {Number}
475
- * @readonly
476
- * @private
477
- * @memberof Meeting
478
- */
479
- this.roapSeq = ROAP_SEQ_PRE;
480
470
  /**
481
471
  * @instance
482
472
  * @type {Object}
@@ -486,13 +476,43 @@ export default class Meeting extends StatelessWebexPlugin {
486
476
  */
487
477
  // TODO: needs to be defined as a class
488
478
  this.meetingInfo = {};
479
+ /**
480
+ * helper class for managing receive slots (for multistream media connections)
481
+ */
482
+ this.receiveSlotManager = new ReceiveSlotManager(this);
483
+ /**
484
+ * Helper class for managing media requests for main video (for multistream media connections)
485
+ * All media requests sent out for main video for this meeting have to go through it.
486
+ */
487
+ this.mediaRequestManagers = {
488
+ audio: new MediaRequestManager((mediaRequests) => {
489
+ if (!this.mediaProperties.webrtcMediaConnection) {
490
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created');
491
+
492
+ return;
493
+ }
494
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.AudioMain, mediaRequests);
495
+ }),
496
+ video: new MediaRequestManager((mediaRequests) => {
497
+ if (!this.mediaProperties.webrtcMediaConnection) {
498
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created');
499
+
500
+ return;
501
+ }
502
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.VideoMain, mediaRequests);
503
+ })
504
+ };
489
505
  /**
490
506
  * @instance
491
507
  * @type {Members}
492
508
  * @public
493
509
  * @memberof Meeting
494
510
  */
495
- this.members = new Members({locusUrl: (attrs.locus && attrs.locus.url)}, {parent: this.webex});
511
+ this.members = new Members({
512
+ locusUrl: (attrs.locus && attrs.locus.url),
513
+ receiveSlotManager: this.receiveSlotManager,
514
+ mediaRequestManagers: this.mediaRequestManagers,
515
+ }, {parent: this.webex});
496
516
  /**
497
517
  * @instance
498
518
  * @type {Roap}
@@ -501,6 +521,16 @@ export default class Meeting extends StatelessWebexPlugin {
501
521
  * @memberof Meeting
502
522
  */
503
523
  this.roap = new Roap({}, {parent: this.webex});
524
+ /**
525
+ * indicates if an SDP exchange is happening
526
+ *
527
+ * @instance
528
+ * @type {Boolean}
529
+ * @readonly
530
+ * @private
531
+ * @memberof Meeting
532
+ */
533
+ this.isRoapInProgress = false;
504
534
  /**
505
535
  * created later
506
536
  * @instance
@@ -638,6 +668,11 @@ export default class Meeting extends StatelessWebexPlugin {
638
668
  */
639
669
  this.mediaConnections = null;
640
670
 
671
+ /**
672
+ * If true, then media is sent over multiple separate streams.
673
+ * If false, then media is transcoded by the server into a single stream.
674
+ */
675
+ this.isMultistream = false;
641
676
  /**
642
677
  * Fetching meeting info can be done randomly 2-5 mins before meeting start
643
678
  * In case it is done before the timer expires, this timeout id is reset to cancel the timer.
@@ -695,29 +730,7 @@ export default class Meeting extends StatelessWebexPlugin {
695
730
  * @memberof Meeting
696
731
  */
697
732
  this.shareStatus = SHARE_STATUS.NO_SHARE;
698
- /**
699
- * @instance
700
- * @type {Boolean}
701
- * @readonly
702
- * @private
703
- * @memberof Meeting
704
- */
705
- Object.defineProperty(this, 'isLocalShareLive', {
706
- get: () => {
707
- const {shareTransceiver} = this.mediaProperties.peerConnection;
708
- const shareDirection = shareTransceiver?.direction;
709
- const trackReadyState = shareTransceiver?.sender?.track?.readyState;
710
- const activeShare = trackReadyState === LIVE;
711
- const offersToSendData = shareDirection === SENDRECV;
712
-
713
- if (activeShare && offersToSendData) {
714
- return true;
715
- }
716
733
 
717
- return false;
718
- },
719
- configurable: true
720
- });
721
734
  /**
722
735
  * @instance
723
736
  * @type {Array}
@@ -899,6 +912,13 @@ export default class Meeting extends StatelessWebexPlugin {
899
912
  this.setUpLocusInfoListeners();
900
913
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
901
914
  this.hasJoinedOnce = false;
915
+
916
+ this.media = new MultistreamMedia(this);
917
+
918
+ /**
919
+ * helper class for managing remote streams
920
+ */
921
+ this.remoteMediaManager = null;
902
922
  }
903
923
 
904
924
  /**
@@ -2420,19 +2440,6 @@ export default class Meeting extends StatelessWebexPlugin {
2420
2440
  this.sipUri = sipUri;
2421
2441
  }
2422
2442
 
2423
- /**
2424
- * Set the roap seq on the class instance
2425
- * @param {Number} seq
2426
- * @returns {undefined}
2427
- * @private
2428
- * @memberof Meeting
2429
- */
2430
- setRoapSeq(seq) {
2431
- if (seq >= 0) {
2432
- this.roapSeq = seq;
2433
- }
2434
- }
2435
-
2436
2443
  /**
2437
2444
  * Set the locus info the class instance
2438
2445
  * @param {Object} locus
@@ -2460,111 +2467,6 @@ export default class Meeting extends StatelessWebexPlugin {
2460
2467
  this.locusInfo.initialSetup(mtgLocus);
2461
2468
  }
2462
2469
 
2463
- /**
2464
- * Sets the remote stream on the class instance and emits and
2465
- * event to developers
2466
- * @param {Object} pc The remote stream peer connection
2467
- * @returns {undefined}
2468
- * @public
2469
- * @memberof Meeting
2470
- */
2471
- setRemoteStream(pc) {
2472
- if (!pc) {
2473
- return;
2474
- }
2475
- // eslint-disable-next-line no-param-reassign
2476
- pc.ontrack = (event) => {
2477
- // eslint-disable-next-line no-warning-comments
2478
- // TODO: It's possible for media to not be present
2479
- // so we might need to either
2480
- // A) wait until we have media flowing
2481
- // B) trigger a second event when video is flowing
2482
- LoggerProxy.logger.log(`Meeting:index#setRemoteStream --> ontrack event received for peerConnection: ${event}`);
2483
-
2484
- const MEDIA_ID = {
2485
- AUDIO_TRACK: '0',
2486
- VIDEO_TRACK: '1',
2487
- SHARE_TRACK: '2'
2488
- };
2489
- let eventType = null;
2490
- const mediaTrack = event.track;
2491
- let trackMediaID = null;
2492
-
2493
-
2494
- // In case of safari some time the transceiver is not present for specific os version
2495
- // sdk tries to determine the transceive using the track id present
2496
- if (event.transceiver && event.transceiver.mid) {
2497
- trackMediaID = event.transceiver.mid;
2498
- }
2499
- else {
2500
- const {audioTransceiver, videoTransceiver, shareTransceiver} = event.target;
2501
-
2502
- // audio kind indicates its a audio stream
2503
- if (mediaTrack.id === audioTransceiver.receiver.track.id) {
2504
- trackMediaID = '0';
2505
- }
2506
- else
2507
- if (mediaTrack.id === videoTransceiver.receiver.track.id) {
2508
- trackMediaID = '1';
2509
- }
2510
- else
2511
- if (mediaTrack.id === shareTransceiver.receiver.track.id) {
2512
- trackMediaID = '2';
2513
- }
2514
- else {
2515
- trackMediaID = null;
2516
- Metrics.sendBehavioralMetric(
2517
- BEHAVIORAL_METRICS.MUTE_AUDIO_FAILURE,
2518
- {
2519
- correlation_id: this.correlationId,
2520
- locus_id: this.locusUrl.split('/').pop()
2521
- }
2522
- );
2523
- }
2524
- }
2525
-
2526
-
2527
- switch (trackMediaID) {
2528
- case MEDIA_ID.AUDIO_TRACK:
2529
- eventType = EVENT_TYPES.REMOTE_AUDIO;
2530
- this.mediaProperties.setRemoteAudioTrack(mediaTrack);
2531
- break;
2532
- case MEDIA_ID.VIDEO_TRACK:
2533
- eventType = EVENT_TYPES.REMOTE_VIDEO;
2534
- this.mediaProperties.setRemoteVideoTrack(mediaTrack);
2535
- break;
2536
- case MEDIA_ID.SHARE_TRACK:
2537
- if (event.track) {
2538
- eventType = EVENT_TYPES.REMOTE_SHARE;
2539
- this.mediaProperties.setRemoteShare(mediaTrack);
2540
- }
2541
- break;
2542
- default: {
2543
- LoggerProxy.logger.log('Meeting:index#setRemoteStream --> no matching media track id');
2544
- }
2545
- }
2546
-
2547
- // start stats here the stats are coming null if you dont receive streams
2548
-
2549
- this.statsAnalyzer.startAnalyzer(this.mediaProperties.peerConnection);
2550
-
2551
- if (eventType && mediaTrack) {
2552
- Trigger.trigger(
2553
- this,
2554
- {
2555
- file: 'meeting/index',
2556
- function: 'setRemoteStream:pc.ontrack'
2557
- },
2558
- EVENT_TRIGGERS.MEDIA_READY,
2559
- {
2560
- type: eventType,
2561
- stream: MediaUtil.createMediaStream([mediaTrack])
2562
- }
2563
- );
2564
- }
2565
- };
2566
- }
2567
-
2568
2470
  /**
2569
2471
  * Upload logs for the current meeting
2570
2472
  * @param {object} options file name and function name
@@ -2959,20 +2861,32 @@ export default class Meeting extends StatelessWebexPlugin {
2959
2861
  }
2960
2862
 
2961
2863
  /**
2962
- * Close the peer connections and remove them from the class. Triggers an event
2963
- * when each is closed.
2964
- * @returns {Promise} returns a resolved promise with an array of closed peer connections
2864
+ * Close the peer connections and remove them from the class.
2865
+ * Cleanup any media connection related things.
2866
+ *
2867
+ * @returns {Promise}
2965
2868
  * @public
2966
2869
  * @memberof Meeting
2967
2870
  */
2968
2871
  closePeerConnections() {
2969
- return PeerConnectionManager.close(this.mediaProperties.peerConnection);
2872
+ if (this.mediaProperties.webrtcMediaConnection) {
2873
+ if (this.remoteMediaManager) {
2874
+ this.remoteMediaManager.stop();
2875
+ this.remoteMediaManager = null;
2876
+ }
2877
+
2878
+ Object.values(this.mediaRequestManagers).forEach((mediaRequestManager) => mediaRequestManager.reset());
2879
+
2880
+ this.receiveSlotManager.reset();
2881
+ this.mediaProperties.webrtcMediaConnection.close();
2882
+ }
2883
+
2884
+ return Promise.resolve();
2970
2885
  }
2971
2886
 
2972
2887
  /**
2973
2888
  * Unsets the peer connections on the class
2974
2889
  * warning DO NOT CALL WITHOUT CLOSING PEER CONNECTIONS FIRST
2975
- * @param {PeerConnection} peerConnection
2976
2890
  * @returns {undefined}
2977
2891
  * @public
2978
2892
  * @memberof Meeting
@@ -3623,6 +3537,8 @@ export default class Meeting extends StatelessWebexPlugin {
3623
3537
  }
3624
3538
  }
3625
3539
 
3540
+ this.isMultistream = !!options.enableMultistream;
3541
+
3626
3542
  return MeetingUtil.joinMeetingOptions(this, options)
3627
3543
  .then((join) => {
3628
3544
  this.meetingFiniteStateMachine.join();
@@ -3974,6 +3890,9 @@ export default class Meeting extends StatelessWebexPlugin {
3974
3890
 
3975
3891
  /**
3976
3892
  * Get local media streams based on options passed
3893
+ *
3894
+ * NOTE: this method can only be used with transcoded meetings, not with multistream meetings
3895
+ *
3977
3896
  * @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
3978
3897
  * @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
3979
3898
  * @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
@@ -4109,6 +4028,344 @@ export default class Meeting extends StatelessWebexPlugin {
4109
4028
  */
4110
4029
  getDevices = () => Media.getDevices();
4111
4030
 
4031
+ /**
4032
+ * Handles ROAP_FAILURE event from the webrtc media connection
4033
+ *
4034
+ * @param {Error} error
4035
+ * @returns {void}
4036
+ */
4037
+ handleRoapFailure = (error) => {
4038
+ const sendBehavioralMetric = (metricName, error, correlationId) => {
4039
+ const data = {
4040
+ code: error.code,
4041
+ correlation_id: correlationId,
4042
+ reason: error.message,
4043
+ stack: error.stack
4044
+ };
4045
+ const metadata = {
4046
+ type: error.cause?.name || error.name
4047
+ };
4048
+
4049
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4050
+ };
4051
+
4052
+
4053
+ if (error instanceof MC.Errors.SdpOfferCreationError) {
4054
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4055
+
4056
+ Metrics.postEvent({
4057
+ event: eventType.LOCAL_SDP_GENERATED,
4058
+ meetingId: this.id,
4059
+ data: {
4060
+ canProceed: false,
4061
+ errors: [
4062
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE)]
4063
+ }
4064
+ });
4065
+ }
4066
+ else if ((error instanceof MC.Errors.SdpOfferHandlingError) || (error instanceof MC.Errors.SdpAnswerHandlingError)) {
4067
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4068
+
4069
+ Metrics.postEvent({
4070
+ event: eventType.REMOTE_SDP_RECEIVED,
4071
+ meetingId: this.id,
4072
+ data: {
4073
+ canProceed: false,
4074
+ errors: [Metrics.generateErrorPayload(2001, true, error.name.MEDIA_ENGINE)]
4075
+ }
4076
+ });
4077
+ }
4078
+ else if (error instanceof MC.Errors.SdpError) { // this covers also the case of MC.Errors.IceGatheringError which extends MC.Errors.SdpError
4079
+ sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
4080
+
4081
+ Metrics.postEvent({
4082
+ event: eventType.LOCAL_SDP_GENERATED,
4083
+ meetingId: this.id,
4084
+ data: {
4085
+ canProceed: false,
4086
+ errors: [
4087
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE)]
4088
+ }
4089
+ });
4090
+ }
4091
+ };
4092
+
4093
+ setupMediaConnectionListeners = () => {
4094
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_STARTED, () => {
4095
+ this.isRoapInProgress = true;
4096
+ });
4097
+
4098
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_DONE, () => {
4099
+ this.mediaNegotiatedEvent();
4100
+ this.isRoapInProgress = false;
4101
+ this.processNextQueuedMediaUpdate();
4102
+ });
4103
+
4104
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_FAILURE, this.handleRoapFailure);
4105
+
4106
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_MESSAGE_TO_SEND, (event) => {
4107
+ const LOG_HEADER = 'Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND -->';
4108
+
4109
+ switch (event.roapMessage.messageType) {
4110
+ case 'OK':
4111
+ Metrics.postEvent({
4112
+ event: eventType.REMOTE_SDP_RECEIVED,
4113
+ meetingId: this.id,
4114
+ });
4115
+
4116
+ logRequest(this.roap.sendRoapOK({
4117
+ seq: event.roapMessage.seq,
4118
+ mediaId: this.mediaId,
4119
+ correlationId: this.correlationId
4120
+ }), {
4121
+ header: `${LOG_HEADER} Send Roap OK`,
4122
+ success: `${LOG_HEADER} Successfully send roap OK`,
4123
+ failure: `${LOG_HEADER} Error joining the call on send roap OK, `
4124
+ });
4125
+ break;
4126
+
4127
+ case 'OFFER':
4128
+ Metrics.postEvent({
4129
+ event: eventType.LOCAL_SDP_GENERATED,
4130
+ meetingId: this.id,
4131
+ });
4132
+
4133
+ logRequest(this.roap
4134
+ .sendRoapMediaRequest({
4135
+ sdp: event.roapMessage.sdp,
4136
+ seq: event.roapMessage.seq,
4137
+ tieBreaker: event.roapMessage.tieBreaker,
4138
+ meeting: this, // or can pass meeting ID
4139
+ reconnect: this.reconnectionManager.isReconnectInProgress()
4140
+ }), {
4141
+ header: `${LOG_HEADER} Send Roap Offer`,
4142
+ success: `${LOG_HEADER} Successfully send roap offer`,
4143
+ failure: `${LOG_HEADER} Error joining the call on send roap offer, `
4144
+ });
4145
+ break;
4146
+
4147
+ case 'ANSWER':
4148
+ Metrics.postEvent({
4149
+ event: eventType.REMOTE_SDP_RECEIVED,
4150
+ meetingId: this.id,
4151
+ });
4152
+
4153
+ logRequest(this.roap.sendRoapAnswer({
4154
+ sdp: event.roapMessage.sdp,
4155
+ seq: event.roapMessage.seq,
4156
+ mediaId: this.mediaId,
4157
+ correlationId: this.correlationId
4158
+ }), {
4159
+ header: `${LOG_HEADER} Send Roap Answer.`,
4160
+ success: `${LOG_HEADER} Successfully send roap answer`,
4161
+ failure: `${LOG_HEADER} Error joining the call on send roap answer, `
4162
+ })
4163
+ .catch((error) => {
4164
+ const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
4165
+ const data = {
4166
+ correlation_id: this.correlationId,
4167
+ locus_id: this.locusUrl.split('/').pop(),
4168
+ reason: error.message,
4169
+ stack: error.stack
4170
+ };
4171
+ const metadata = {
4172
+ type: error.name
4173
+ };
4174
+
4175
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4176
+ });
4177
+ break;
4178
+
4179
+ case 'ERROR':
4180
+ if (event.roapMessage.errorType === MC.ErrorType.CONFLICT || event.roapMessage.errorType === MC.ErrorType.DOUBLECONFLICT) {
4181
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
4182
+ correlation_id: this.correlationId,
4183
+ locus_id: this.locusUrl.split('/').pop(),
4184
+ sequence: event.roapMessage.seq
4185
+ });
4186
+ }
4187
+ logRequest(this.roap.sendRoapError({
4188
+ seq: event.roapMessage.seq,
4189
+ errorType: event.roapMessage.errorType,
4190
+ mediaId: this.mediaId,
4191
+ correlationId: this.correlationId
4192
+ }), {
4193
+ header: `${LOG_HEADER} Send Roap Error.`,
4194
+ success: `${LOG_HEADER} Successfully send roap error`,
4195
+ failure: `${LOG_HEADER} Failed to send roap error, `
4196
+ });
4197
+ break;
4198
+
4199
+ default:
4200
+ LoggerProxy.logger.error(`${LOG_HEADER} Unsupported message type: ${event.roapMessage.messageType}`);
4201
+ break;
4202
+ }
4203
+ });
4204
+
4205
+ // eslint-disable-next-line no-param-reassign
4206
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.REMOTE_TRACK_ADDED, (event) => {
4207
+ LoggerProxy.logger.log(`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(event)}`);
4208
+
4209
+ const mediaTrack = event.track;
4210
+
4211
+ let eventType;
4212
+
4213
+ switch (event.type) {
4214
+ case MC.RemoteTrackType.AUDIO:
4215
+ eventType = EVENT_TYPES.REMOTE_AUDIO;
4216
+ this.mediaProperties.setRemoteAudioTrack(event.track);
4217
+ break;
4218
+ case MC.RemoteTrackType.VIDEO:
4219
+ eventType = EVENT_TYPES.REMOTE_VIDEO;
4220
+ this.mediaProperties.setRemoteVideoTrack(event.track);
4221
+ break;
4222
+ case MC.RemoteTrackType.SCREENSHARE_VIDEO:
4223
+ if (event.track) {
4224
+ eventType = EVENT_TYPES.REMOTE_SHARE;
4225
+ this.mediaProperties.setRemoteShare(event.track);
4226
+ }
4227
+ break;
4228
+ default: {
4229
+ LoggerProxy.logger.log('Meeting:index#setupMediaConnectionListeners --> unexpected track');
4230
+ }
4231
+ }
4232
+
4233
+ // start stats here the stats are coming null if you dont receive streams
4234
+
4235
+ this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
4236
+
4237
+ if (eventType && mediaTrack) {
4238
+ Trigger.trigger(
4239
+ this,
4240
+ {
4241
+ file: 'meeting/index',
4242
+ function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED'
4243
+ },
4244
+ EVENT_TRIGGERS.MEDIA_READY,
4245
+ {
4246
+ type: eventType,
4247
+ stream: MediaUtil.createMediaStream([mediaTrack])
4248
+ }
4249
+ );
4250
+ }
4251
+ });
4252
+
4253
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.CONNECTION_STATE_CHANGED, (event) => {
4254
+ const connectionFailed = () => {
4255
+ // we know the media connection failed and browser will not attempt to recover it any more
4256
+ // so reset the timer as it's not needed anymore, we want to reconnect immediately
4257
+ this.reconnectionManager.resetReconnectionTimer();
4258
+
4259
+ this.reconnect({networkDisconnect: true});
4260
+ Metrics.postEvent({
4261
+ event: eventType.ICE_END,
4262
+ meeting: this,
4263
+ data: {
4264
+ canProceed: false,
4265
+ errors: [
4266
+ Metrics.generateErrorPayload(
4267
+ 2004, false, MetricsError.name.MEDIA_ENGINE
4268
+ )]
4269
+ }
4270
+ });
4271
+
4272
+ this.uploadLogs({
4273
+ file: 'peer-connection-manager/index',
4274
+ function: 'connectionFailed'
4275
+ });
4276
+
4277
+ Metrics.sendBehavioralMetric(
4278
+ BEHAVIORAL_METRICS.CONNECTION_FAILURE,
4279
+ {
4280
+ correlation_id: this.correlationId,
4281
+ locus_id: this.locusId
4282
+ }
4283
+ );
4284
+ };
4285
+
4286
+ LoggerProxy.logger.info(`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`);
4287
+ switch (event.state) {
4288
+ case MC.ConnectionState.Connecting:
4289
+ Metrics.postEvent({event: eventType.ICE_START, meeting: this});
4290
+ break;
4291
+ case MC.ConnectionState.Connected:
4292
+ Metrics.postEvent({event: eventType.ICE_END, meeting: this});
4293
+ Metrics.sendBehavioralMetric(
4294
+ BEHAVIORAL_METRICS.CONNECTION_SUCCESS,
4295
+ {
4296
+ correlation_id: this.correlationId,
4297
+ locus_id: this.locusId
4298
+ }
4299
+ );
4300
+ this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
4301
+ this.reconnectionManager.iceReconnected();
4302
+ break;
4303
+ case MC.ConnectionState.Disconnected:
4304
+ this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
4305
+ this.reconnectionManager.waitForIceReconnect()
4306
+ .catch(() => {
4307
+ LoggerProxy.logger.info('Meeting:index#setupMediaConnectionListeners --> state DISCONNECTED, automatic reconnection timed out.');
4308
+
4309
+ connectionFailed();
4310
+ });
4311
+ break;
4312
+ case MC.ConnectionState.Failed:
4313
+ connectionFailed();
4314
+ break;
4315
+ default:
4316
+ break;
4317
+ }
4318
+ });
4319
+
4320
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ACTIVE_SPEAKERS_CHANGED,
4321
+ (msg) => {
4322
+ Trigger.trigger(
4323
+ this,
4324
+ {
4325
+ file: 'meeting/index',
4326
+ function: 'setupMediaConnectionListeners'
4327
+ },
4328
+ EVENT_TRIGGERS.ACTIVE_SPEAKER_CHANGED,
4329
+ {
4330
+ seqNum: msg.seqNum,
4331
+ memberIds: msg.csis.map((csi) => this.members.findMemberByCsi(csi)?.id).filter((item) => (item !== undefined))
4332
+ }
4333
+ );
4334
+ });
4335
+
4336
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.VIDEO_SOURCES_COUNT_CHANGED,
4337
+ (numTotalSources, numLiveSources) => {
4338
+ Trigger.trigger(
4339
+ this,
4340
+ {
4341
+ file: 'meeting/index',
4342
+ function: 'setupMediaConnectionListeners'
4343
+ },
4344
+ EVENT_TRIGGERS.REMOTE_VIDEO_SOURCE_COUNT_CHANGED,
4345
+ {
4346
+ numTotalSources,
4347
+ numLiveSources
4348
+ }
4349
+ );
4350
+ });
4351
+
4352
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.AUDIO_SOURCES_COUNT_CHANGED,
4353
+ (numTotalSources, numLiveSources) => {
4354
+ Trigger.trigger(
4355
+ this,
4356
+ {
4357
+ file: 'meeting/index',
4358
+ function: 'setupMediaConnectionListeners'
4359
+ },
4360
+ EVENT_TRIGGERS.REMOTE_AUDIO_SOURCE_COUNT_CHANGED,
4361
+ {
4362
+ numTotalSources,
4363
+ numLiveSources
4364
+ }
4365
+ );
4366
+ });
4367
+ };
4368
+
4112
4369
  /**
4113
4370
  * Registers for all required StatsAnalyzer events
4114
4371
  * @private
@@ -4178,6 +4435,43 @@ export default class Meeting extends StatelessWebexPlugin {
4178
4435
  });
4179
4436
  };
4180
4437
 
4438
+ createMediaConnection(turnServerInfo) {
4439
+ const mc = Media.createMediaConnection(this.mediaProperties, {
4440
+ isMultistream: this.isMultistream,
4441
+ meetingId: this.id,
4442
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4443
+ enableRtx: this.config.enableRtx,
4444
+ enableExtmap: this.config.enableExtmap,
4445
+ turnServerInfo
4446
+ });
4447
+
4448
+ this.mediaProperties.setMediaPeerConnection(mc);
4449
+ this.setupMediaConnectionListeners();
4450
+
4451
+ return mc;
4452
+ }
4453
+
4454
+ /**
4455
+ * Listens for an event emitted by eventEmitter and emits it from the meeting object
4456
+ *
4457
+ * @private
4458
+ * @param {*} eventEmitter object from which to forward the event
4459
+ * @param {*} eventTypeToForward which event type to listen on and to forward
4460
+ * @param {string} meetingEventType event type to be used in the event emitted from the meeting object
4461
+ * @returns {void}
4462
+ */
4463
+ forwardEvent(eventEmitter, eventTypeToForward, meetingEventType) {
4464
+ eventEmitter.on(eventTypeToForward, (data) => Trigger.trigger(
4465
+ this,
4466
+ {
4467
+ file: 'meetings',
4468
+ function: 'addMedia'
4469
+ },
4470
+ meetingEventType,
4471
+ data
4472
+ ));
4473
+ }
4474
+
4181
4475
  /**
4182
4476
  * Specify joining via audio (option: pstn), video, screenshare
4183
4477
  * @param {Object} options A configurable options object for joining a meeting
@@ -4185,6 +4479,7 @@ export default class Meeting extends StatelessWebexPlugin {
4185
4479
  * @param {MediaDirection} options.mediaSettings pass media options
4186
4480
  * @param {MediaStream} options.localStream
4187
4481
  * @param {MediaStream} options.localShare
4482
+ * @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
4188
4483
  * @returns {Promise}
4189
4484
  * @public
4190
4485
  * @memberof Meeting
@@ -4207,7 +4502,9 @@ export default class Meeting extends StatelessWebexPlugin {
4207
4502
  return Promise.reject(new UserInLobbyError());
4208
4503
  }
4209
4504
 
4210
- const {localStream, localShare, mediaSettings} = options;
4505
+ const {
4506
+ localStream, localShare, mediaSettings, remoteMediaManagerConfig
4507
+ } = options;
4211
4508
 
4212
4509
  LoggerProxy.logger.info(`${LOG_HEADER} Adding Media.`);
4213
4510
 
@@ -4242,31 +4539,38 @@ export default class Meeting extends StatelessWebexPlugin {
4242
4539
 
4243
4540
  const {turnServerInfo} = turnDiscoveryObject;
4244
4541
 
4245
- this.mediaProperties.setMediaPeerConnection(MediaUtil.createPeerConnection(turnServerInfo));
4246
- this.setMercuryListener();
4247
- PeerConnectionManager.setPeerConnectionEvents(this);
4542
+ this.preMedia(localStream, localShare, mediaSettings);
4543
+
4544
+ const mc = this.createMediaConnection(turnServerInfo);
4545
+
4546
+ if (this.isMultistream) {
4547
+ this.remoteMediaManager = new RemoteMediaManager(
4548
+ this.receiveSlotManager,
4549
+ this.mediaRequestManagers,
4550
+ remoteMediaManagerConfig
4551
+ );
4552
+
4553
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.AudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED);
4554
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.ScreenShareAudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED);
4555
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.VideoLayoutChanged, EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED);
4556
+
4557
+ return this.remoteMediaManager.start()
4558
+ .then(() => mc.initiateOffer());
4559
+ }
4248
4560
 
4249
- return this.preMedia(localStream, localShare, mediaSettings);
4561
+ return mc.initiateOffer();
4250
4562
  })
4251
- .then(() => Media.attachMedia(this.mediaProperties, {
4252
- meetingId: this.id,
4253
- remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4254
- enableRtx: this.config.enableRtx,
4255
- enableExtmap: this.config.enableExtmap,
4256
- setStartLocalSDPGenRemoteSDPRecvDelay: this.setStartLocalSDPGenRemoteSDPRecvDelay.bind(this)
4257
- }))
4258
- .then((peerConnection) => this.getDevices().then((devices) => {
4563
+ .then(() => {
4564
+ this.setMercuryListener();
4565
+ })
4566
+ .then(() => this.getDevices().then((devices) => {
4259
4567
  MeetingUtil.handleDeviceLogging(devices);
4260
-
4261
- return peerConnection;
4262
4568
  }))
4263
- .then((peerConnection) => {
4569
+ .then(() => {
4264
4570
  this.handleMediaLogging(this.mediaProperties);
4265
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection Received from attachMedia `);
4571
+ LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
4266
4572
 
4267
- this.setRemoteStream(peerConnection);
4268
4573
  if (this.config.stats.enableStatsAnalyzer) {
4269
- // TODO: ** Dont re create StatsAnalyzer on reconnect or rejoin
4270
4574
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
4271
4575
  this.statsAnalyzer = new StatsAnalyzer(this.config.stats, this.networkQualityMonitor);
4272
4576
  this.setupStatsAnalyzerEventHandlers();
@@ -4276,18 +4580,6 @@ export default class Meeting extends StatelessWebexPlugin {
4276
4580
  .catch((error) => {
4277
4581
  LoggerProxy.logger.error(`${LOG_HEADER} Error adding media , setting up peerconnection, `, error);
4278
4582
 
4279
- Metrics.sendBehavioralMetric(
4280
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
4281
- {
4282
- correlation_id: this.correlationId,
4283
- locus_id: this.locusUrl.split('/').pop(),
4284
- reason: error.message,
4285
- stack: error.stack,
4286
- turnDiscoverySkippedReason,
4287
- turnServerUsed
4288
- }
4289
- );
4290
-
4291
4583
  throw error;
4292
4584
  })
4293
4585
  .then(() => new Promise((resolve, reject) => {
@@ -4311,26 +4603,14 @@ export default class Meeting extends StatelessWebexPlugin {
4311
4603
  }
4312
4604
  }, 1000);
4313
4605
  }))
4314
- .then(() =>
4315
- logRequest(this.roap
4316
- .sendRoapMediaRequest({
4317
- sdp: this.mediaProperties.peerConnection.sdp,
4318
- roapSeq: this.roapSeq,
4319
- meeting: this // or can pass meeting ID
4320
- }), {
4321
- header: `${LOG_HEADER} Send Roap Media Request.`,
4322
- success: `${LOG_HEADER} Successfully send roap media request`,
4323
- failure: `${LOG_HEADER} Error joining the call on send roap media request, `
4324
- }))
4325
4606
  .then(
4326
- () => this.mediaProperties.waitForIceConnectedState()
4607
+ () => this.mediaProperties.waitForMediaConnectionConnected()
4327
4608
  .catch(() => {
4328
4609
  throw createMeetingsError(30202, 'Meeting connection failed');
4329
4610
  })
4330
4611
  )
4331
4612
  .then(() => {
4332
4613
  LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
4333
-
4334
4614
  if (mediaSettings && mediaSettings.sendShare && localShare) {
4335
4615
  if (this.state === MEETING_STATE.STATES.JOINED) {
4336
4616
  return this.share();
@@ -4361,7 +4641,7 @@ export default class Meeting extends StatelessWebexPlugin {
4361
4641
  .then(() => {
4362
4642
  this.statsAnalyzer = null;
4363
4643
 
4364
- if (this.mediaProperties.peerConnection) {
4644
+ if (this.mediaProperties.webrtcMediaConnection) {
4365
4645
  this.closePeerConnections();
4366
4646
  this.unsetPeerConnections();
4367
4647
  }
@@ -4392,10 +4672,7 @@ export default class Meeting extends StatelessWebexPlugin {
4392
4672
  this
4393
4673
  );
4394
4674
 
4395
- // If addMedia failes for not establishing connection then
4396
- // leave the meeting with reson connection failed as meeting anyways will end
4397
- // and cannot be connected unless network condition is checked for firewall
4398
- if (error.code === InvalidSdpError.CODE) {
4675
+ if (error instanceof MC.Errors.SdpError) {
4399
4676
  this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
4400
4677
  }
4401
4678
 
@@ -4409,7 +4686,10 @@ export default class Meeting extends StatelessWebexPlugin {
4409
4686
  * @returns {Boolean}
4410
4687
  */
4411
4688
  canUpdateMedia() {
4412
- return this.mediaProperties.peerConnection.signalingState === SDP.STABLE && !RoapCollection.isBusy(this.correlationId);
4689
+ // in theory we shouldn't need this as RoapMediaConnection handles multiple updates, glare, etc,
4690
+ // but there are some server issues, like https://jira-eng-gpk2.cisco.com/jira/browse/WEBEX-248394
4691
+ // so for now it's better to keep queuing any media updates at SDK meeting level
4692
+ return !this.isRoapInProgress;
4413
4693
  }
4414
4694
 
4415
4695
  /**
@@ -4493,7 +4773,6 @@ export default class Meeting extends StatelessWebexPlugin {
4493
4773
  * @param {MediaStream} options.localShare
4494
4774
  * @param {MediaDirection} options.mediaSettings
4495
4775
  * @returns {Promise}
4496
- * @todo fix setRemoteStream for updateMedia
4497
4776
  * @public
4498
4777
  * @memberof Meeting
4499
4778
  */
@@ -4509,20 +4788,27 @@ export default class Meeting extends StatelessWebexPlugin {
4509
4788
 
4510
4789
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4511
4790
 
4791
+ if (!this.mediaProperties.webrtcMediaConnection) {
4792
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
4793
+ }
4794
+
4512
4795
  return MeetingUtil.validateOptions(options)
4513
4796
  .then(() => this.preMedia(localStream, localShare, mediaSettings))
4514
- .then(() => Media.updateMedia(this.mediaProperties, {
4515
- meetingId: this.id,
4516
- remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4517
- enableRtx: this.config.enableRtx,
4518
- enableExtmap: this.config.enableExtmap,
4797
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
4798
+ send: {
4799
+ audio: this.mediaProperties.mediaDirection.sendAudio ? this.mediaProperties.audioTrack : null,
4800
+ video: this.mediaProperties.mediaDirection.sendVideo ? this.mediaProperties.videoTrack : null,
4801
+ screenShareVideo: this.mediaProperties.mediaDirection.sendShare ? this.mediaProperties.shareTrack : null
4802
+ },
4803
+ receive: {
4804
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
4805
+ video: this.mediaProperties.mediaDirection.receiveVideo,
4806
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
4807
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4808
+ }
4519
4809
  })
4520
- .then((peerConnection) => {
4521
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection received from updateMedia, ${peerConnection}`);
4522
- this.setRemoteStream(peerConnection);
4523
- if (mediaSettings.receiveShare || localShare) {
4524
- PeerConnectionManager.setContentSlides(peerConnection);
4525
- }
4810
+ .then(() => {
4811
+ LoggerProxy.logger.info(`${LOG_HEADER} webrtcMediaConnection.updateSendReceiveOptions done`);
4526
4812
  })
4527
4813
  .catch((error) => {
4528
4814
  LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
@@ -4539,18 +4825,10 @@ export default class Meeting extends StatelessWebexPlugin {
4539
4825
 
4540
4826
  throw error;
4541
4827
  })
4542
- .then(() =>
4543
- logRequest(this.roap
4544
- .sendRoapMediaRequest({
4545
- sdp: this.mediaProperties.peerConnection.sdp,
4546
- roapSeq: this.roapSeq,
4547
- meeting: this, // or can pass meeting ID
4548
- }),
4549
- {
4550
- header: `${LOG_HEADER} sendRoapMediaRequest being sent`,
4551
- success: `${LOG_HEADER} sendRoadMediaRequest successful`,
4552
- failure: `${LOG_HEADER} Error updateMedia on send roap media request, `
4553
- }))
4828
+ // todo: the following code used to be called always after sending the roap message with the new SDP
4829
+ // now it's called independently from the roap message (so might be before it), check if that's OK
4830
+ // if not, ensure it's called after (now it's called after roap message is sent out, but we're not
4831
+ // waiting for sendRoapMediaRequest() to be resolved)
4554
4832
  .then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
4555
4833
  .then((startShare) => {
4556
4834
  // This is a special case if we do an /floor grant followed by /media
@@ -4566,6 +4844,9 @@ export default class Meeting extends StatelessWebexPlugin {
4566
4844
 
4567
4845
  /**
4568
4846
  * Update the main audio track with new parameters
4847
+ *
4848
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
4849
+ *
4569
4850
  * @param {Object} options
4570
4851
  * @param {boolean} options.sendAudio
4571
4852
  * @param {boolean} options.receiveAudio
@@ -4578,17 +4859,17 @@ export default class Meeting extends StatelessWebexPlugin {
4578
4859
  if (!this.canUpdateMedia()) {
4579
4860
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
4580
4861
  }
4581
- const {
4582
- sendAudio, receiveAudio, stream
4583
- } = options;
4584
-
4585
- const {audioTransceiver} = this.mediaProperties.peerConnection;
4862
+ const {sendAudio, receiveAudio, stream} = options;
4586
4863
  let track = MeetingUtil.getTrack(stream).audioTrack;
4587
4864
 
4588
4865
  if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
4589
4866
  return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
4590
4867
  }
4591
4868
 
4869
+ if (!this.mediaProperties.webrtcMediaConnection) {
4870
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
4871
+ }
4872
+
4592
4873
  if (this.effects && this.effects.state) {
4593
4874
  const bnrEnabled = this.effects.state.bnr.enabled;
4594
4875
 
@@ -4600,38 +4881,18 @@ export default class Meeting extends StatelessWebexPlugin {
4600
4881
  }
4601
4882
 
4602
4883
  return MeetingUtil.validateOptions({sendAudio, localStream: stream})
4603
- .then(() => {
4604
- let previousMediaDirection = {};
4605
-
4606
- if (this.mediaProperties.mediaDirection) {
4607
- previousMediaDirection = {
4608
- sendTrack: this.mediaProperties.mediaDirection.sendAudio,
4609
- receiveTrack: this.mediaProperties.mediaDirection.receiveAudio
4610
- };
4611
- }
4612
- else {
4613
- this.mediaProperties.mediaDirection = {};
4884
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
4885
+ send: {audio: track},
4886
+ receive: {
4887
+ audio: options.receiveAudio,
4888
+ video: this.mediaProperties.mediaDirection.receiveVideo,
4889
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
4890
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4614
4891
  }
4615
-
4616
- return MeetingUtil.updateTransceiver(
4617
- {
4618
- type: 'audio',
4619
- sendTrack: options.sendAudio,
4620
- receiveTrack: options.receiveAudio,
4621
- track,
4622
- transceiver: audioTransceiver,
4623
- peerConnection: this.mediaProperties.peerConnection,
4624
- previousMediaDirection
4625
- },
4626
- {
4627
- mediaProperties: this.mediaProperties,
4628
- meeting: this,
4629
- id: this.id
4630
- }
4631
- );
4632
- })
4892
+ }))
4633
4893
  .then(() => {
4634
4894
  this.setLocalAudioTrack(track);
4895
+ // todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
4635
4896
  this.mediaProperties.mediaDirection.sendAudio = sendAudio;
4636
4897
  this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
4637
4898
 
@@ -4642,6 +4903,9 @@ export default class Meeting extends StatelessWebexPlugin {
4642
4903
 
4643
4904
  /**
4644
4905
  * Update the main video track with new parameters
4906
+ *
4907
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
4908
+ *
4645
4909
  * @param {Object} options
4646
4910
  * @param {boolean} options.sendVideo
4647
4911
  * @param {boolean} options.receiveVideo
@@ -4655,30 +4919,25 @@ export default class Meeting extends StatelessWebexPlugin {
4655
4919
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
4656
4920
  }
4657
4921
  const {sendVideo, receiveVideo, stream} = options;
4658
- const {videoTransceiver} = this.mediaProperties.peerConnection;
4659
4922
  const track = MeetingUtil.getTrack(stream).videoTrack;
4660
4923
 
4661
4924
  if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
4662
4925
  return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
4663
4926
  }
4664
4927
 
4928
+ if (!this.mediaProperties.webrtcMediaConnection) {
4929
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
4930
+ }
4931
+
4665
4932
  return MeetingUtil.validateOptions({sendVideo, localStream: stream})
4666
- .then(() => MeetingUtil.updateTransceiver({
4667
- type: 'video',
4668
- sendTrack: options.sendVideo,
4669
- receiveTrack: options.receiveVideo,
4670
- track,
4671
- transceiver: videoTransceiver,
4672
- peerConnection: this.mediaProperties.peerConnection,
4673
- previousMediaDirection: {
4674
- sendTrack: this.mediaProperties.mediaDirection.sendVideo,
4675
- receiveTrack: this.mediaProperties.mediaDirection.receiveVideo
4933
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
4934
+ send: {video: track},
4935
+ receive: {
4936
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
4937
+ video: options.receiveVideo,
4938
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
4939
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4676
4940
  }
4677
- },
4678
- {
4679
- mediaProperties: this.mediaProperties,
4680
- meeting: this,
4681
- id: this.id
4682
4941
  }))
4683
4942
  .then(() => {
4684
4943
  this.setLocalVideoTrack(track);
@@ -4715,6 +4974,9 @@ export default class Meeting extends StatelessWebexPlugin {
4715
4974
 
4716
4975
  /**
4717
4976
  * Update the share streams, can be used to start sharing
4977
+ *
4978
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
4979
+ *
4718
4980
  * @param {Object} options
4719
4981
  * @param {boolean} options.sendShare
4720
4982
  * @param {boolean} options.receiveShare
@@ -4727,34 +4989,30 @@ export default class Meeting extends StatelessWebexPlugin {
4727
4989
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
4728
4990
  }
4729
4991
  const {sendShare, receiveShare, stream} = options;
4730
- const {shareTransceiver} = this.mediaProperties.peerConnection;
4731
4992
  const track = MeetingUtil.getTrack(stream).videoTrack;
4732
4993
 
4733
4994
  if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
4734
4995
  return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
4735
4996
  }
4997
+
4998
+ if (!this.mediaProperties.webrtcMediaConnection) {
4999
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5000
+ }
5001
+
4736
5002
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4737
5003
 
4738
5004
  this.setLocalShareTrack(stream);
4739
5005
 
4740
5006
  return MeetingUtil.validateOptions({sendShare, localShare: stream})
4741
5007
  .then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
4742
- .then((startShare) => MeetingUtil.updateTransceiver({
4743
- type: 'video',
4744
- sendTrack: sendShare,
4745
- receiveTrack: receiveShare,
4746
- track,
4747
- transceiver: shareTransceiver,
4748
- peerConnection: this.mediaProperties.peerConnection,
4749
- previousMediaDirection: {
4750
- sendTrack: this.mediaProperties.mediaDirection.sendShare,
4751
- receiveTrack: this.mediaProperties.mediaDirection.receiveShare
5008
+ .then((startShare) => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5009
+ send: {screenShareVideo: track},
5010
+ receive: {
5011
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5012
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5013
+ screenShareVideo: options.receiveShare,
5014
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4752
5015
  }
4753
- },
4754
- {
4755
- mediaProperties: this.mediaProperties,
4756
- meeting: this,
4757
- id: this.id
4758
5016
  })
4759
5017
  .then(() => {
4760
5018
  if (startShare) {
@@ -4770,22 +5028,6 @@ export default class Meeting extends StatelessWebexPlugin {
4770
5028
  .catch((error) => {
4771
5029
  this.unsetLocalShareTrack(stream);
4772
5030
  throw error;
4773
- })
4774
- .finally(() => {
4775
- const delay = 1e3;
4776
- // Check to see if share was stopped natively before onended was assigned.
4777
- const sharingModeIsActive = this.mediaProperties.peerConnection.shareTransceiver.direction === SENDRECV;
4778
- const isSharingOutOfSync = sharingModeIsActive && !this.isLocalShareLive;
4779
-
4780
- if (isSharingOutOfSync) {
4781
- // Adding a delay to avoid a 409 from server
4782
- // which results in user still appearing as if sharing.
4783
- // Also delay give time for changes to peerConnection.
4784
- setTimeout(
4785
- () => this.handleShareTrackEnded(stream),
4786
- delay
4787
- );
4788
- }
4789
5031
  });
4790
5032
  }
4791
5033
 
@@ -5493,6 +5735,9 @@ export default class Meeting extends StatelessWebexPlugin {
5493
5735
  }
5494
5736
 
5495
5737
  /**
5738
+ *
5739
+ * NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
5740
+ *
5496
5741
  * @param {Object} options parameter
5497
5742
  * @param {Boolean} options.sendAudio send audio from the display share
5498
5743
  * @param {Boolean} options.sendShare send video from the display share