@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.
- package/dist/common/errors/webex-errors.js +5 -29
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/constants.js +15 -74
- package/dist/constants.js.map +1 -1
- package/dist/media/index.js +68 -213
- package/dist/media/index.js.map +1 -1
- package/dist/media/internal-media-core-wrapper.js +22 -0
- package/dist/media/internal-media-core-wrapper.js.map +1 -0
- package/dist/media/properties.js +20 -25
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +0 -27
- package/dist/media/util.js.map +1 -1
- package/dist/meeting/index.js +694 -432
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/request.js +1 -0
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/util.js +3 -44
- package/dist/meeting/util.js.map +1 -1
- package/dist/meetings/index.js +64 -5
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/util.js +24 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/members/index.js +68 -0
- package/dist/members/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +132 -0
- package/dist/multistream/mediaRequestManager.js.map +1 -0
- package/dist/multistream/multistreamMedia.js +116 -0
- package/dist/multistream/multistreamMedia.js.map +1 -0
- package/dist/multistream/receiveSlot.js +209 -0
- package/dist/multistream/receiveSlot.js.map +1 -0
- package/dist/multistream/receiveSlotManager.js +195 -0
- package/dist/multistream/receiveSlotManager.js.map +1 -0
- package/dist/multistream/remoteMedia.js +284 -0
- package/dist/multistream/remoteMedia.js.map +1 -0
- package/dist/multistream/remoteMediaGroup.js +243 -0
- package/dist/multistream/remoteMediaGroup.js.map +1 -0
- package/dist/multistream/remoteMediaManager.js +1113 -0
- package/dist/multistream/remoteMediaManager.js.map +1 -0
- package/dist/reconnection-manager/index.js +109 -130
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/roap/index.js +57 -240
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +2 -114
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +11 -5
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +2 -0
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +39 -36
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/package.json +20 -19
- package/src/common/errors/webex-errors.js +0 -18
- package/src/constants.ts +139 -180
- package/src/media/index.js +60 -194
- package/src/media/internal-media-core-wrapper.ts +9 -0
- package/src/media/properties.js +19 -25
- package/src/media/util.js +0 -22
- package/src/meeting/index.js +565 -320
- package/src/meeting/request.js +1 -0
- package/src/meeting/util.js +3 -46
- package/src/meetings/index.js +30 -1
- package/src/meetings/util.js +23 -2
- package/src/members/index.js +48 -0
- package/src/multistream/mediaRequestManager.ts +164 -0
- package/src/multistream/multistreamMedia.ts +92 -0
- package/src/multistream/receiveSlot.ts +141 -0
- package/src/multistream/receiveSlotManager.ts +142 -0
- package/src/multistream/remoteMedia.ts +219 -0
- package/src/multistream/remoteMediaGroup.ts +224 -0
- package/src/multistream/remoteMediaManager.ts +911 -0
- package/src/reconnection-manager/index.js +40 -53
- package/src/roap/index.js +47 -207
- package/src/roap/request.js +1 -72
- package/src/roap/turnDiscovery.ts +12 -6
- package/src/statsAnalyzer/global.js +2 -0
- package/src/statsAnalyzer/index.js +32 -46
- package/test/integration/spec/journey.js +1 -1
- package/test/unit/spec/media/index.ts +223 -0
- package/test/unit/spec/media/properties.ts +73 -82
- package/test/unit/spec/meeting/effectsState.js +1 -3
- package/test/unit/spec/meeting/index.js +420 -228
- package/test/unit/spec/meeting/muteState.js +7 -0
- package/test/unit/spec/meeting/utils.js +61 -2
- package/test/unit/spec/meetings/index.js +0 -4
- package/test/unit/spec/members/index.js +164 -2
- package/test/unit/spec/multistream/mediaRequestManager.ts +511 -0
- package/test/unit/spec/multistream/receiveSlot.ts +104 -0
- package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
- package/test/unit/spec/multistream/remoteMedia.ts +217 -0
- package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
- package/test/unit/spec/multistream/remoteMediaManager.ts +1251 -0
- package/test/unit/spec/roap/index.ts +63 -35
- package/test/unit/spec/stats-analyzer/index.js +19 -22
- package/dist/peer-connection-manager/index.js +0 -794
- package/dist/peer-connection-manager/index.js.map +0 -1
- package/dist/roap/collection.js +0 -73
- package/dist/roap/collection.js.map +0 -1
- package/dist/roap/handler.js +0 -337
- package/dist/roap/handler.js.map +0 -1
- package/dist/roap/state.js +0 -164
- package/dist/roap/state.js.map +0 -1
- package/dist/roap/util.js +0 -102
- package/dist/roap/util.js.map +0 -1
- package/src/peer-connection-manager/index.js +0 -723
- package/src/roap/collection.js +0 -63
- package/src/roap/handler.js +0 -252
- package/src/roap/state.js +0 -149
- package/src/roap/util.js +0 -93
- package/test/unit/spec/peerconnection-manager/index.js +0 -188
- package/test/unit/spec/peerconnection-manager/utils.js +0 -48
- package/test/unit/spec/roap/util.js +0 -30
package/src/meeting/index.js
CHANGED
|
@@ -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
|
|
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
|
|
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({
|
|
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.
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
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
|
-
|
|
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 {
|
|
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.
|
|
4246
|
-
|
|
4247
|
-
|
|
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
|
|
4561
|
+
return mc.initiateOffer();
|
|
4250
4562
|
})
|
|
4251
|
-
.then(() =>
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
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((
|
|
4569
|
+
.then(() => {
|
|
4264
4570
|
this.handleMediaLogging(this.mediaProperties);
|
|
4265
|
-
LoggerProxy.logger.info(`${LOG_HEADER}
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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(() =>
|
|
4515
|
-
|
|
4516
|
-
|
|
4517
|
-
|
|
4518
|
-
|
|
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((
|
|
4521
|
-
LoggerProxy.logger.info(`${LOG_HEADER}
|
|
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
|
-
|
|
4543
|
-
|
|
4544
|
-
|
|
4545
|
-
|
|
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
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
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(() =>
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
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) =>
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
|
|
4746
|
-
|
|
4747
|
-
|
|
4748
|
-
|
|
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
|