@webex/plugin-meetings 3.0.0-beta.0 → 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 +742 -500
- 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 +622 -398
- 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';
|
|
@@ -36,7 +35,6 @@ import {
|
|
|
36
35
|
_INCOMING_,
|
|
37
36
|
_JOIN_,
|
|
38
37
|
AUDIO,
|
|
39
|
-
CONNECTION_STATE,
|
|
40
38
|
CONTENT,
|
|
41
39
|
ENDED,
|
|
42
40
|
EVENT_TRIGGERS,
|
|
@@ -45,7 +43,6 @@ import {
|
|
|
45
43
|
FLOOR_ACTION,
|
|
46
44
|
FULL_STATE,
|
|
47
45
|
LAYOUT_TYPES,
|
|
48
|
-
LIVE,
|
|
49
46
|
LOCUSINFO,
|
|
50
47
|
MEETING_INFO_FAILURE_REASON,
|
|
51
48
|
MEETING_REMOVED_REASON,
|
|
@@ -58,13 +55,9 @@ import {
|
|
|
58
55
|
ONLINE,
|
|
59
56
|
OFFLINE,
|
|
60
57
|
PASSWORD_STATUS,
|
|
61
|
-
PC_BAIL_TIMEOUT,
|
|
62
58
|
PSTN_STATUS,
|
|
63
59
|
QUALITY_LEVELS,
|
|
64
60
|
RECORDING_STATE,
|
|
65
|
-
ROAP_SEQ_PRE,
|
|
66
|
-
SDP,
|
|
67
|
-
SENDRECV,
|
|
68
61
|
SHARE_STATUS,
|
|
69
62
|
SHARE_STOPPED_REASON,
|
|
70
63
|
VIDEO_RESOLUTIONS,
|
|
@@ -77,7 +70,10 @@ import ParameterError from '../common/errors/parameter';
|
|
|
77
70
|
import MediaError from '../common/errors/media';
|
|
78
71
|
import {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '../meeting-info/meeting-info-v2';
|
|
79
72
|
import BrowserDetection from '../common/browser-detection';
|
|
80
|
-
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';
|
|
81
77
|
|
|
82
78
|
import InMeetingActions from './in-meeting-actions';
|
|
83
79
|
|
|
@@ -145,6 +141,7 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
145
141
|
* @property {String} [meetingQuality.local]
|
|
146
142
|
* @property {String} [meetingQuality.remote]
|
|
147
143
|
* @property {Boolean} [rejoin]
|
|
144
|
+
* @property {Boolean} [enableMultistream]
|
|
148
145
|
*/
|
|
149
146
|
|
|
150
147
|
/**
|
|
@@ -470,15 +467,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
470
467
|
* @memberof Meeting
|
|
471
468
|
*/
|
|
472
469
|
this.deviceUrl = attrs.deviceUrl;
|
|
473
|
-
/**
|
|
474
|
-
* @description set you -1 as default values is 0 (used to idenfify if 1st roap request was sent)
|
|
475
|
-
* @instance
|
|
476
|
-
* @type {Number}
|
|
477
|
-
* @readonly
|
|
478
|
-
* @private
|
|
479
|
-
* @memberof Meeting
|
|
480
|
-
*/
|
|
481
|
-
this.roapSeq = ROAP_SEQ_PRE;
|
|
482
470
|
/**
|
|
483
471
|
* @instance
|
|
484
472
|
* @type {Object}
|
|
@@ -488,13 +476,43 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
488
476
|
*/
|
|
489
477
|
// TODO: needs to be defined as a class
|
|
490
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
|
+
};
|
|
491
505
|
/**
|
|
492
506
|
* @instance
|
|
493
507
|
* @type {Members}
|
|
494
508
|
* @public
|
|
495
509
|
* @memberof Meeting
|
|
496
510
|
*/
|
|
497
|
-
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});
|
|
498
516
|
/**
|
|
499
517
|
* @instance
|
|
500
518
|
* @type {Roap}
|
|
@@ -503,6 +521,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
503
521
|
* @memberof Meeting
|
|
504
522
|
*/
|
|
505
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;
|
|
506
534
|
/**
|
|
507
535
|
* created later
|
|
508
536
|
* @instance
|
|
@@ -640,6 +668,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
640
668
|
*/
|
|
641
669
|
this.mediaConnections = null;
|
|
642
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;
|
|
643
676
|
/**
|
|
644
677
|
* Fetching meeting info can be done randomly 2-5 mins before meeting start
|
|
645
678
|
* In case it is done before the timer expires, this timeout id is reset to cancel the timer.
|
|
@@ -697,29 +730,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
697
730
|
* @memberof Meeting
|
|
698
731
|
*/
|
|
699
732
|
this.shareStatus = SHARE_STATUS.NO_SHARE;
|
|
700
|
-
/**
|
|
701
|
-
* @instance
|
|
702
|
-
* @type {Boolean}
|
|
703
|
-
* @readonly
|
|
704
|
-
* @private
|
|
705
|
-
* @memberof Meeting
|
|
706
|
-
*/
|
|
707
|
-
Object.defineProperty(this, 'isLocalShareLive', {
|
|
708
|
-
get: () => {
|
|
709
|
-
const {shareTransceiver} = this.mediaProperties.peerConnection;
|
|
710
|
-
const shareDirection = shareTransceiver?.direction;
|
|
711
|
-
const trackReadyState = shareTransceiver?.sender?.track?.readyState;
|
|
712
|
-
const activeShare = trackReadyState === LIVE;
|
|
713
|
-
const offersToSendData = shareDirection === SENDRECV;
|
|
714
|
-
|
|
715
|
-
if (activeShare && offersToSendData) {
|
|
716
|
-
return true;
|
|
717
|
-
}
|
|
718
733
|
|
|
719
|
-
return false;
|
|
720
|
-
},
|
|
721
|
-
configurable: true
|
|
722
|
-
});
|
|
723
734
|
/**
|
|
724
735
|
* @instance
|
|
725
736
|
* @type {Array}
|
|
@@ -901,6 +912,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
901
912
|
this.setUpLocusInfoListeners();
|
|
902
913
|
this.locusInfo.init(attrs.locus ? attrs.locus : {});
|
|
903
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;
|
|
904
922
|
}
|
|
905
923
|
|
|
906
924
|
/**
|
|
@@ -2422,19 +2440,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2422
2440
|
this.sipUri = sipUri;
|
|
2423
2441
|
}
|
|
2424
2442
|
|
|
2425
|
-
/**
|
|
2426
|
-
* Set the roap seq on the class instance
|
|
2427
|
-
* @param {Number} seq
|
|
2428
|
-
* @returns {undefined}
|
|
2429
|
-
* @private
|
|
2430
|
-
* @memberof Meeting
|
|
2431
|
-
*/
|
|
2432
|
-
setRoapSeq(seq) {
|
|
2433
|
-
if (seq >= 0) {
|
|
2434
|
-
this.roapSeq = seq;
|
|
2435
|
-
}
|
|
2436
|
-
}
|
|
2437
|
-
|
|
2438
2443
|
/**
|
|
2439
2444
|
* Set the locus info the class instance
|
|
2440
2445
|
* @param {Object} locus
|
|
@@ -2462,111 +2467,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2462
2467
|
this.locusInfo.initialSetup(mtgLocus);
|
|
2463
2468
|
}
|
|
2464
2469
|
|
|
2465
|
-
/**
|
|
2466
|
-
* Sets the remote stream on the class instance and emits and
|
|
2467
|
-
* event to developers
|
|
2468
|
-
* @param {Object} pc The remote stream peer connection
|
|
2469
|
-
* @returns {undefined}
|
|
2470
|
-
* @public
|
|
2471
|
-
* @memberof Meeting
|
|
2472
|
-
*/
|
|
2473
|
-
setRemoteStream(pc) {
|
|
2474
|
-
if (!pc) {
|
|
2475
|
-
return;
|
|
2476
|
-
}
|
|
2477
|
-
// eslint-disable-next-line no-param-reassign
|
|
2478
|
-
pc.ontrack = (event) => {
|
|
2479
|
-
// eslint-disable-next-line no-warning-comments
|
|
2480
|
-
// TODO: It's possible for media to not be present
|
|
2481
|
-
// so we might need to either
|
|
2482
|
-
// A) wait until we have media flowing
|
|
2483
|
-
// B) trigger a second event when video is flowing
|
|
2484
|
-
LoggerProxy.logger.log(`Meeting:index#setRemoteStream --> ontrack event received for peerConnection: ${event}`);
|
|
2485
|
-
|
|
2486
|
-
const MEDIA_ID = {
|
|
2487
|
-
AUDIO_TRACK: '0',
|
|
2488
|
-
VIDEO_TRACK: '1',
|
|
2489
|
-
SHARE_TRACK: '2'
|
|
2490
|
-
};
|
|
2491
|
-
let eventType = null;
|
|
2492
|
-
const mediaTrack = event.track;
|
|
2493
|
-
let trackMediaID = null;
|
|
2494
|
-
|
|
2495
|
-
|
|
2496
|
-
// In case of safari some time the transceiver is not present for specific os version
|
|
2497
|
-
// sdk tries to determine the transceive using the track id present
|
|
2498
|
-
if (event.transceiver && event.transceiver.mid) {
|
|
2499
|
-
trackMediaID = event.transceiver.mid;
|
|
2500
|
-
}
|
|
2501
|
-
else {
|
|
2502
|
-
const {audioTransceiver, videoTransceiver, shareTransceiver} = event.target;
|
|
2503
|
-
|
|
2504
|
-
// audio kind indicates its a audio stream
|
|
2505
|
-
if (mediaTrack.id === audioTransceiver.receiver.track.id) {
|
|
2506
|
-
trackMediaID = '0';
|
|
2507
|
-
}
|
|
2508
|
-
else
|
|
2509
|
-
if (mediaTrack.id === videoTransceiver.receiver.track.id) {
|
|
2510
|
-
trackMediaID = '1';
|
|
2511
|
-
}
|
|
2512
|
-
else
|
|
2513
|
-
if (mediaTrack.id === shareTransceiver.receiver.track.id) {
|
|
2514
|
-
trackMediaID = '2';
|
|
2515
|
-
}
|
|
2516
|
-
else {
|
|
2517
|
-
trackMediaID = null;
|
|
2518
|
-
Metrics.sendBehavioralMetric(
|
|
2519
|
-
BEHAVIORAL_METRICS.MUTE_AUDIO_FAILURE,
|
|
2520
|
-
{
|
|
2521
|
-
correlation_id: this.correlationId,
|
|
2522
|
-
locus_id: this.locusUrl.split('/').pop()
|
|
2523
|
-
}
|
|
2524
|
-
);
|
|
2525
|
-
}
|
|
2526
|
-
}
|
|
2527
|
-
|
|
2528
|
-
|
|
2529
|
-
switch (trackMediaID) {
|
|
2530
|
-
case MEDIA_ID.AUDIO_TRACK:
|
|
2531
|
-
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
2532
|
-
this.mediaProperties.setRemoteAudioTrack(mediaTrack);
|
|
2533
|
-
break;
|
|
2534
|
-
case MEDIA_ID.VIDEO_TRACK:
|
|
2535
|
-
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
2536
|
-
this.mediaProperties.setRemoteVideoTrack(mediaTrack);
|
|
2537
|
-
break;
|
|
2538
|
-
case MEDIA_ID.SHARE_TRACK:
|
|
2539
|
-
if (event.track) {
|
|
2540
|
-
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
2541
|
-
this.mediaProperties.setRemoteShare(mediaTrack);
|
|
2542
|
-
}
|
|
2543
|
-
break;
|
|
2544
|
-
default: {
|
|
2545
|
-
LoggerProxy.logger.log('Meeting:index#setRemoteStream --> no matching media track id');
|
|
2546
|
-
}
|
|
2547
|
-
}
|
|
2548
|
-
|
|
2549
|
-
// start stats here the stats are coming null if you dont receive streams
|
|
2550
|
-
|
|
2551
|
-
this.statsAnalyzer.startAnalyzer(this.mediaProperties.peerConnection);
|
|
2552
|
-
|
|
2553
|
-
if (eventType && mediaTrack) {
|
|
2554
|
-
Trigger.trigger(
|
|
2555
|
-
this,
|
|
2556
|
-
{
|
|
2557
|
-
file: 'meeting/index',
|
|
2558
|
-
function: 'setRemoteStream:pc.ontrack'
|
|
2559
|
-
},
|
|
2560
|
-
EVENT_TRIGGERS.MEDIA_READY,
|
|
2561
|
-
{
|
|
2562
|
-
type: eventType,
|
|
2563
|
-
stream: MediaUtil.createMediaStream([mediaTrack])
|
|
2564
|
-
}
|
|
2565
|
-
);
|
|
2566
|
-
}
|
|
2567
|
-
};
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
2470
|
/**
|
|
2571
2471
|
* Upload logs for the current meeting
|
|
2572
2472
|
* @param {object} options file name and function name
|
|
@@ -2961,20 +2861,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2961
2861
|
}
|
|
2962
2862
|
|
|
2963
2863
|
/**
|
|
2964
|
-
* Close the peer connections and remove them from the class.
|
|
2965
|
-
*
|
|
2966
|
-
*
|
|
2864
|
+
* Close the peer connections and remove them from the class.
|
|
2865
|
+
* Cleanup any media connection related things.
|
|
2866
|
+
*
|
|
2867
|
+
* @returns {Promise}
|
|
2967
2868
|
* @public
|
|
2968
2869
|
* @memberof Meeting
|
|
2969
2870
|
*/
|
|
2970
2871
|
closePeerConnections() {
|
|
2971
|
-
|
|
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();
|
|
2972
2885
|
}
|
|
2973
2886
|
|
|
2974
2887
|
/**
|
|
2975
2888
|
* Unsets the peer connections on the class
|
|
2976
2889
|
* warning DO NOT CALL WITHOUT CLOSING PEER CONNECTIONS FIRST
|
|
2977
|
-
* @param {PeerConnection} peerConnection
|
|
2978
2890
|
* @returns {undefined}
|
|
2979
2891
|
* @public
|
|
2980
2892
|
* @memberof Meeting
|
|
@@ -3625,6 +3537,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3625
3537
|
}
|
|
3626
3538
|
}
|
|
3627
3539
|
|
|
3540
|
+
this.isMultistream = !!options.enableMultistream;
|
|
3541
|
+
|
|
3628
3542
|
return MeetingUtil.joinMeetingOptions(this, options)
|
|
3629
3543
|
.then((join) => {
|
|
3630
3544
|
this.meetingFiniteStateMachine.join();
|
|
@@ -3976,6 +3890,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3976
3890
|
|
|
3977
3891
|
/**
|
|
3978
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
|
+
*
|
|
3979
3896
|
* @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
|
|
3980
3897
|
* @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
3981
3898
|
* @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
@@ -4111,6 +4028,344 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4111
4028
|
*/
|
|
4112
4029
|
getDevices = () => Media.getDevices();
|
|
4113
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
|
+
|
|
4114
4369
|
/**
|
|
4115
4370
|
* Registers for all required StatsAnalyzer events
|
|
4116
4371
|
* @private
|
|
@@ -4180,6 +4435,43 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4180
4435
|
});
|
|
4181
4436
|
};
|
|
4182
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
|
+
|
|
4183
4475
|
/**
|
|
4184
4476
|
* Specify joining via audio (option: pstn), video, screenshare
|
|
4185
4477
|
* @param {Object} options A configurable options object for joining a meeting
|
|
@@ -4187,6 +4479,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4187
4479
|
* @param {MediaDirection} options.mediaSettings pass media options
|
|
4188
4480
|
* @param {MediaStream} options.localStream
|
|
4189
4481
|
* @param {MediaStream} options.localShare
|
|
4482
|
+
* @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
|
|
4190
4483
|
* @returns {Promise}
|
|
4191
4484
|
* @public
|
|
4192
4485
|
* @memberof Meeting
|
|
@@ -4209,7 +4502,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4209
4502
|
return Promise.reject(new UserInLobbyError());
|
|
4210
4503
|
}
|
|
4211
4504
|
|
|
4212
|
-
const {
|
|
4505
|
+
const {
|
|
4506
|
+
localStream, localShare, mediaSettings, remoteMediaManagerConfig
|
|
4507
|
+
} = options;
|
|
4213
4508
|
|
|
4214
4509
|
LoggerProxy.logger.info(`${LOG_HEADER} Adding Media.`);
|
|
4215
4510
|
|
|
@@ -4244,145 +4539,109 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4244
4539
|
|
|
4245
4540
|
const {turnServerInfo} = turnDiscoveryObject;
|
|
4246
4541
|
|
|
4247
|
-
this.
|
|
4248
|
-
this.setMercuryListener();
|
|
4249
|
-
PeerConnectionManager.setPeerConnectionEvents(this);
|
|
4542
|
+
this.preMedia(localStream, localShare, mediaSettings);
|
|
4250
4543
|
|
|
4251
|
-
|
|
4252
|
-
})
|
|
4253
|
-
.then(() => Media.attachMedia(this.mediaProperties, {
|
|
4254
|
-
meetingId: this.id,
|
|
4255
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
4256
|
-
enableRtx: this.config.enableRtx,
|
|
4257
|
-
enableExtmap: this.config.enableExtmap,
|
|
4258
|
-
setStartLocalSDPGenRemoteSDPRecvDelay: this.setStartLocalSDPGenRemoteSDPRecvDelay.bind(this)
|
|
4259
|
-
})
|
|
4260
|
-
.then((peerConnection) => this.getDevices().then((devices) => {
|
|
4261
|
-
MeetingUtil.handleDeviceLogging(devices);
|
|
4544
|
+
const mc = this.createMediaConnection(turnServerInfo);
|
|
4262
4545
|
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
|
|
4268
|
-
|
|
4269
|
-
this.setRemoteStream(peerConnection);
|
|
4270
|
-
if (this.config.stats.enableStatsAnalyzer) {
|
|
4271
|
-
// TODO: ** Dont re create StatsAnalyzer on reconnect or rejoin
|
|
4272
|
-
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
4273
|
-
this.statsAnalyzer = new StatsAnalyzer(this.config.stats, this.networkQualityMonitor);
|
|
4274
|
-
this.setupStatsAnalyzerEventHandlers();
|
|
4275
|
-
this.networkQualityMonitor.on(EVENT_TRIGGERS.NETWORK_QUALITY, this.sendNetworkQualityEvent.bind(this));
|
|
4276
|
-
}
|
|
4277
|
-
})
|
|
4278
|
-
.catch((error) => {
|
|
4279
|
-
LoggerProxy.logger.error(`${LOG_HEADER} Error adding media , setting up peerconnection, `, error);
|
|
4280
|
-
|
|
4281
|
-
Metrics.sendBehavioralMetric(
|
|
4282
|
-
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
4283
|
-
{
|
|
4284
|
-
correlation_id: this.correlationId,
|
|
4285
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
4286
|
-
reason: error.message,
|
|
4287
|
-
stack: error.stack,
|
|
4288
|
-
turnDiscoverySkippedReason,
|
|
4289
|
-
turnServerUsed
|
|
4290
|
-
}
|
|
4546
|
+
if (this.isMultistream) {
|
|
4547
|
+
this.remoteMediaManager = new RemoteMediaManager(
|
|
4548
|
+
this.receiveSlotManager,
|
|
4549
|
+
this.mediaRequestManagers,
|
|
4550
|
+
remoteMediaManagerConfig
|
|
4291
4551
|
);
|
|
4292
4552
|
|
|
4293
|
-
|
|
4294
|
-
|
|
4295
|
-
|
|
4296
|
-
let timerCount = 0;
|
|
4297
|
-
|
|
4298
|
-
// eslint-disable-next-line func-names
|
|
4299
|
-
// eslint-disable-next-line prefer-arrow-callback
|
|
4300
|
-
if (this.type === _CALL_) {
|
|
4301
|
-
resolve();
|
|
4302
|
-
}
|
|
4303
|
-
const joiningTimer = setInterval(() => {
|
|
4304
|
-
timerCount += 1;
|
|
4305
|
-
if (this.meetingState === FULL_STATE.ACTIVE) {
|
|
4306
|
-
clearInterval(joiningTimer);
|
|
4307
|
-
resolve();
|
|
4308
|
-
}
|
|
4309
|
-
|
|
4310
|
-
if (timerCount === 4) {
|
|
4311
|
-
clearInterval(joiningTimer);
|
|
4312
|
-
reject(new Error('Meeting is still not active '));
|
|
4313
|
-
}
|
|
4314
|
-
}, 1000);
|
|
4315
|
-
}))
|
|
4316
|
-
.then(() =>
|
|
4317
|
-
logRequest(this.roap
|
|
4318
|
-
.sendRoapMediaRequest({
|
|
4319
|
-
sdp: this.mediaProperties.peerConnection.sdp,
|
|
4320
|
-
roapSeq: this.roapSeq,
|
|
4321
|
-
meeting: this // or can pass meeting ID
|
|
4322
|
-
}), {
|
|
4323
|
-
header: `${LOG_HEADER} Send Roap Media Request.`,
|
|
4324
|
-
success: `${LOG_HEADER} Successfully send roap media request`,
|
|
4325
|
-
failure: `${LOG_HEADER} Error joining the call on send roap media request, `
|
|
4326
|
-
}))
|
|
4327
|
-
.then(() => {
|
|
4328
|
-
const {peerConnection} = this.mediaProperties;
|
|
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);
|
|
4329
4556
|
|
|
4330
|
-
return
|
|
4331
|
-
|
|
4332
|
-
|
|
4557
|
+
return this.remoteMediaManager.start()
|
|
4558
|
+
.then(() => mc.initiateOffer());
|
|
4559
|
+
}
|
|
4333
4560
|
|
|
4334
|
-
|
|
4561
|
+
return mc.initiateOffer();
|
|
4562
|
+
})
|
|
4563
|
+
.then(() => {
|
|
4564
|
+
this.setMercuryListener();
|
|
4565
|
+
})
|
|
4566
|
+
.then(() => this.getDevices().then((devices) => {
|
|
4567
|
+
MeetingUtil.handleDeviceLogging(devices);
|
|
4568
|
+
}))
|
|
4569
|
+
.then(() => {
|
|
4570
|
+
this.handleMediaLogging(this.mediaProperties);
|
|
4571
|
+
LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
|
|
4572
|
+
|
|
4573
|
+
if (this.config.stats.enableStatsAnalyzer) {
|
|
4574
|
+
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
4575
|
+
this.statsAnalyzer = new StatsAnalyzer(this.config.stats, this.networkQualityMonitor);
|
|
4576
|
+
this.setupStatsAnalyzerEventHandlers();
|
|
4577
|
+
this.networkQualityMonitor.on(EVENT_TRIGGERS.NETWORK_QUALITY, this.sendNetworkQualityEvent.bind(this));
|
|
4578
|
+
}
|
|
4579
|
+
})
|
|
4580
|
+
.catch((error) => {
|
|
4581
|
+
LoggerProxy.logger.error(`${LOG_HEADER} Error adding media , setting up peerconnection, `, error);
|
|
4335
4582
|
|
|
4336
|
-
|
|
4337
|
-
|
|
4338
|
-
|
|
4339
|
-
|
|
4340
|
-
if (peerConnection.connectionState !== CONNECTION_STATE.CONNECTED) {
|
|
4341
|
-
// TODO: Fix this after the error code pr goes in
|
|
4342
|
-
reject(createMeetingsError(30202, 'Meeting connection failed'));
|
|
4343
|
-
}
|
|
4344
|
-
else {
|
|
4345
|
-
LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
|
|
4346
|
-
resolve(peerConnection);
|
|
4347
|
-
}
|
|
4348
|
-
}, PC_BAIL_TIMEOUT);
|
|
4583
|
+
throw error;
|
|
4584
|
+
})
|
|
4585
|
+
.then(() => new Promise((resolve, reject) => {
|
|
4586
|
+
let timerCount = 0;
|
|
4349
4587
|
|
|
4350
|
-
|
|
4351
|
-
|
|
4352
|
-
|
|
4353
|
-
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
|
|
4357
|
-
|
|
4358
|
-
|
|
4359
|
-
|
|
4360
|
-
|
|
4361
|
-
}
|
|
4588
|
+
// eslint-disable-next-line func-names
|
|
4589
|
+
// eslint-disable-next-line prefer-arrow-callback
|
|
4590
|
+
if (this.type === _CALL_) {
|
|
4591
|
+
resolve();
|
|
4592
|
+
}
|
|
4593
|
+
const joiningTimer = setInterval(() => {
|
|
4594
|
+
timerCount += 1;
|
|
4595
|
+
if (this.meetingState === FULL_STATE.ACTIVE) {
|
|
4596
|
+
clearInterval(joiningTimer);
|
|
4597
|
+
resolve();
|
|
4598
|
+
}
|
|
4362
4599
|
|
|
4363
|
-
|
|
4364
|
-
|
|
4600
|
+
if (timerCount === 4) {
|
|
4601
|
+
clearInterval(joiningTimer);
|
|
4602
|
+
reject(new Error('Meeting is still not active '));
|
|
4603
|
+
}
|
|
4604
|
+
}, 1000);
|
|
4605
|
+
}))
|
|
4606
|
+
.then(
|
|
4607
|
+
() => this.mediaProperties.waitForMediaConnectionConnected()
|
|
4608
|
+
.catch(() => {
|
|
4609
|
+
throw createMeetingsError(30202, 'Meeting connection failed');
|
|
4610
|
+
})
|
|
4611
|
+
)
|
|
4612
|
+
.then(() => {
|
|
4613
|
+
LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
|
|
4614
|
+
if (mediaSettings && mediaSettings.sendShare && localShare) {
|
|
4615
|
+
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
4616
|
+
return this.share();
|
|
4365
4617
|
}
|
|
4366
4618
|
|
|
4367
|
-
|
|
4368
|
-
|
|
4369
|
-
|
|
4370
|
-
correlation_id: this.correlationId,
|
|
4371
|
-
locus_id: this.locusUrl.split('/').pop()
|
|
4372
|
-
}
|
|
4373
|
-
);
|
|
4619
|
+
// When the self state changes to JOINED then request the floor
|
|
4620
|
+
this.floorGrantPending = true;
|
|
4621
|
+
}
|
|
4374
4622
|
|
|
4375
|
-
|
|
4376
|
-
|
|
4623
|
+
return {};
|
|
4624
|
+
})
|
|
4625
|
+
.then(() => this.mediaProperties.getCurrentConnectionType())
|
|
4626
|
+
.then((connectionType) => {
|
|
4627
|
+
Metrics.sendBehavioralMetric(
|
|
4628
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
|
|
4629
|
+
{
|
|
4630
|
+
correlation_id: this.correlationId,
|
|
4631
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
4632
|
+
connectionType
|
|
4633
|
+
}
|
|
4634
|
+
);
|
|
4635
|
+
})
|
|
4377
4636
|
.catch((error) => {
|
|
4378
4637
|
// Clean up stats analyzer, peer connection, and turn off listeners
|
|
4379
4638
|
const stopStatsAnalyzer = (this.statsAnalyzer) ? this.statsAnalyzer.stopAnalyzer() : Promise.resolve();
|
|
4380
4639
|
|
|
4381
|
-
stopStatsAnalyzer
|
|
4640
|
+
return stopStatsAnalyzer
|
|
4382
4641
|
.then(() => {
|
|
4383
4642
|
this.statsAnalyzer = null;
|
|
4384
4643
|
|
|
4385
|
-
if (this.mediaProperties.
|
|
4644
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
4386
4645
|
this.closePeerConnections();
|
|
4387
4646
|
this.unsetPeerConnections();
|
|
4388
4647
|
}
|
|
@@ -4413,10 +4672,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4413
4672
|
this
|
|
4414
4673
|
);
|
|
4415
4674
|
|
|
4416
|
-
|
|
4417
|
-
// leave the meeting with reson connection failed as meeting anyways will end
|
|
4418
|
-
// and cannot be connected unless network condition is checked for firewall
|
|
4419
|
-
if (error.code === InvalidSdpError.CODE) {
|
|
4675
|
+
if (error instanceof MC.Errors.SdpError) {
|
|
4420
4676
|
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
4421
4677
|
}
|
|
4422
4678
|
|
|
@@ -4430,7 +4686,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4430
4686
|
* @returns {Boolean}
|
|
4431
4687
|
*/
|
|
4432
4688
|
canUpdateMedia() {
|
|
4433
|
-
|
|
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;
|
|
4434
4693
|
}
|
|
4435
4694
|
|
|
4436
4695
|
/**
|
|
@@ -4514,7 +4773,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4514
4773
|
* @param {MediaStream} options.localShare
|
|
4515
4774
|
* @param {MediaDirection} options.mediaSettings
|
|
4516
4775
|
* @returns {Promise}
|
|
4517
|
-
* @todo fix setRemoteStream for updateMedia
|
|
4518
4776
|
* @public
|
|
4519
4777
|
* @memberof Meeting
|
|
4520
4778
|
*/
|
|
@@ -4530,20 +4788,27 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4530
4788
|
|
|
4531
4789
|
const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
|
|
4532
4790
|
|
|
4791
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
4792
|
+
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
4793
|
+
}
|
|
4794
|
+
|
|
4533
4795
|
return MeetingUtil.validateOptions(options)
|
|
4534
4796
|
.then(() => this.preMedia(localStream, localShare, mediaSettings))
|
|
4535
|
-
.then(() =>
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
|
|
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
|
+
}
|
|
4540
4809
|
})
|
|
4541
|
-
.then((
|
|
4542
|
-
LoggerProxy.logger.info(`${LOG_HEADER}
|
|
4543
|
-
this.setRemoteStream(peerConnection);
|
|
4544
|
-
if (mediaSettings.receiveShare || localShare) {
|
|
4545
|
-
PeerConnectionManager.setContentSlides(peerConnection);
|
|
4546
|
-
}
|
|
4810
|
+
.then(() => {
|
|
4811
|
+
LoggerProxy.logger.info(`${LOG_HEADER} webrtcMediaConnection.updateSendReceiveOptions done`);
|
|
4547
4812
|
})
|
|
4548
4813
|
.catch((error) => {
|
|
4549
4814
|
LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
|
|
@@ -4560,18 +4825,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4560
4825
|
|
|
4561
4826
|
throw error;
|
|
4562
4827
|
})
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
4567
|
-
roapSeq: this.roapSeq,
|
|
4568
|
-
meeting: this, // or can pass meeting ID
|
|
4569
|
-
}),
|
|
4570
|
-
{
|
|
4571
|
-
header: `${LOG_HEADER} sendRoapMediaRequest being sent`,
|
|
4572
|
-
success: `${LOG_HEADER} sendRoadMediaRequest successful`,
|
|
4573
|
-
failure: `${LOG_HEADER} Error updateMedia on send roap media request, `
|
|
4574
|
-
}))
|
|
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)
|
|
4575
4832
|
.then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
|
|
4576
4833
|
.then((startShare) => {
|
|
4577
4834
|
// This is a special case if we do an /floor grant followed by /media
|
|
@@ -4587,6 +4844,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4587
4844
|
|
|
4588
4845
|
/**
|
|
4589
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
|
+
*
|
|
4590
4850
|
* @param {Object} options
|
|
4591
4851
|
* @param {boolean} options.sendAudio
|
|
4592
4852
|
* @param {boolean} options.receiveAudio
|
|
@@ -4599,17 +4859,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4599
4859
|
if (!this.canUpdateMedia()) {
|
|
4600
4860
|
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
|
|
4601
4861
|
}
|
|
4602
|
-
const {
|
|
4603
|
-
sendAudio, receiveAudio, stream
|
|
4604
|
-
} = options;
|
|
4605
|
-
|
|
4606
|
-
const {audioTransceiver} = this.mediaProperties.peerConnection;
|
|
4862
|
+
const {sendAudio, receiveAudio, stream} = options;
|
|
4607
4863
|
let track = MeetingUtil.getTrack(stream).audioTrack;
|
|
4608
4864
|
|
|
4609
4865
|
if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
|
|
4610
4866
|
return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
|
|
4611
4867
|
}
|
|
4612
4868
|
|
|
4869
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
4870
|
+
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
4871
|
+
}
|
|
4872
|
+
|
|
4613
4873
|
if (this.effects && this.effects.state) {
|
|
4614
4874
|
const bnrEnabled = this.effects.state.bnr.enabled;
|
|
4615
4875
|
|
|
@@ -4621,38 +4881,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4621
4881
|
}
|
|
4622
4882
|
|
|
4623
4883
|
return MeetingUtil.validateOptions({sendAudio, localStream: stream})
|
|
4624
|
-
.then(() => {
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
4631
|
-
};
|
|
4632
|
-
}
|
|
4633
|
-
else {
|
|
4634
|
-
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
|
|
4635
4891
|
}
|
|
4636
|
-
|
|
4637
|
-
return MeetingUtil.updateTransceiver(
|
|
4638
|
-
{
|
|
4639
|
-
type: 'audio',
|
|
4640
|
-
sendTrack: options.sendAudio,
|
|
4641
|
-
receiveTrack: options.receiveAudio,
|
|
4642
|
-
track,
|
|
4643
|
-
transceiver: audioTransceiver,
|
|
4644
|
-
peerConnection: this.mediaProperties.peerConnection,
|
|
4645
|
-
previousMediaDirection
|
|
4646
|
-
},
|
|
4647
|
-
{
|
|
4648
|
-
mediaProperties: this.mediaProperties,
|
|
4649
|
-
meeting: this,
|
|
4650
|
-
id: this.id
|
|
4651
|
-
}
|
|
4652
|
-
);
|
|
4653
|
-
})
|
|
4892
|
+
}))
|
|
4654
4893
|
.then(() => {
|
|
4655
4894
|
this.setLocalAudioTrack(track);
|
|
4895
|
+
// todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
|
|
4656
4896
|
this.mediaProperties.mediaDirection.sendAudio = sendAudio;
|
|
4657
4897
|
this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
|
|
4658
4898
|
|
|
@@ -4663,6 +4903,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4663
4903
|
|
|
4664
4904
|
/**
|
|
4665
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
|
+
*
|
|
4666
4909
|
* @param {Object} options
|
|
4667
4910
|
* @param {boolean} options.sendVideo
|
|
4668
4911
|
* @param {boolean} options.receiveVideo
|
|
@@ -4676,30 +4919,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4676
4919
|
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
|
|
4677
4920
|
}
|
|
4678
4921
|
const {sendVideo, receiveVideo, stream} = options;
|
|
4679
|
-
const {videoTransceiver} = this.mediaProperties.peerConnection;
|
|
4680
4922
|
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
4681
4923
|
|
|
4682
4924
|
if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
|
|
4683
4925
|
return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
|
|
4684
4926
|
}
|
|
4685
4927
|
|
|
4928
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
4929
|
+
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
4930
|
+
}
|
|
4931
|
+
|
|
4686
4932
|
return MeetingUtil.validateOptions({sendVideo, localStream: stream})
|
|
4687
|
-
.then(() =>
|
|
4688
|
-
|
|
4689
|
-
|
|
4690
|
-
|
|
4691
|
-
|
|
4692
|
-
|
|
4693
|
-
|
|
4694
|
-
previousMediaDirection: {
|
|
4695
|
-
sendTrack: this.mediaProperties.mediaDirection.sendVideo,
|
|
4696
|
-
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
|
|
4697
4940
|
}
|
|
4698
|
-
},
|
|
4699
|
-
{
|
|
4700
|
-
mediaProperties: this.mediaProperties,
|
|
4701
|
-
meeting: this,
|
|
4702
|
-
id: this.id
|
|
4703
4941
|
}))
|
|
4704
4942
|
.then(() => {
|
|
4705
4943
|
this.setLocalVideoTrack(track);
|
|
@@ -4736,6 +4974,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4736
4974
|
|
|
4737
4975
|
/**
|
|
4738
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
|
+
*
|
|
4739
4980
|
* @param {Object} options
|
|
4740
4981
|
* @param {boolean} options.sendShare
|
|
4741
4982
|
* @param {boolean} options.receiveShare
|
|
@@ -4748,34 +4989,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4748
4989
|
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
|
|
4749
4990
|
}
|
|
4750
4991
|
const {sendShare, receiveShare, stream} = options;
|
|
4751
|
-
const {shareTransceiver} = this.mediaProperties.peerConnection;
|
|
4752
4992
|
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
4753
4993
|
|
|
4754
4994
|
if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
|
|
4755
4995
|
return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
|
|
4756
4996
|
}
|
|
4997
|
+
|
|
4998
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
4999
|
+
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
5000
|
+
}
|
|
5001
|
+
|
|
4757
5002
|
const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
|
|
4758
5003
|
|
|
4759
5004
|
this.setLocalShareTrack(stream);
|
|
4760
5005
|
|
|
4761
5006
|
return MeetingUtil.validateOptions({sendShare, localShare: stream})
|
|
4762
5007
|
.then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
|
|
4763
|
-
.then((startShare) =>
|
|
4764
|
-
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
previousMediaDirection: {
|
|
4771
|
-
sendTrack: this.mediaProperties.mediaDirection.sendShare,
|
|
4772
|
-
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
|
|
4773
5015
|
}
|
|
4774
|
-
},
|
|
4775
|
-
{
|
|
4776
|
-
mediaProperties: this.mediaProperties,
|
|
4777
|
-
meeting: this,
|
|
4778
|
-
id: this.id
|
|
4779
5016
|
})
|
|
4780
5017
|
.then(() => {
|
|
4781
5018
|
if (startShare) {
|
|
@@ -4791,22 +5028,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4791
5028
|
.catch((error) => {
|
|
4792
5029
|
this.unsetLocalShareTrack(stream);
|
|
4793
5030
|
throw error;
|
|
4794
|
-
})
|
|
4795
|
-
.finally(() => {
|
|
4796
|
-
const delay = 1e3;
|
|
4797
|
-
// Check to see if share was stopped natively before onended was assigned.
|
|
4798
|
-
const sharingModeIsActive = this.mediaProperties.peerConnection.shareTransceiver.direction === SENDRECV;
|
|
4799
|
-
const isSharingOutOfSync = sharingModeIsActive && !this.isLocalShareLive;
|
|
4800
|
-
|
|
4801
|
-
if (isSharingOutOfSync) {
|
|
4802
|
-
// Adding a delay to avoid a 409 from server
|
|
4803
|
-
// which results in user still appearing as if sharing.
|
|
4804
|
-
// Also delay give time for changes to peerConnection.
|
|
4805
|
-
setTimeout(
|
|
4806
|
-
() => this.handleShareTrackEnded(stream),
|
|
4807
|
-
delay
|
|
4808
|
-
);
|
|
4809
|
-
}
|
|
4810
5031
|
});
|
|
4811
5032
|
}
|
|
4812
5033
|
|
|
@@ -5514,6 +5735,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5514
5735
|
}
|
|
5515
5736
|
|
|
5516
5737
|
/**
|
|
5738
|
+
*
|
|
5739
|
+
* NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
|
|
5740
|
+
*
|
|
5517
5741
|
* @param {Object} options parameter
|
|
5518
5742
|
* @param {Boolean} options.sendAudio send audio from the display share
|
|
5519
5743
|
* @param {Boolean} options.sendShare send video from the display share
|