@webex/plugin-meetings 3.0.0-beta.0 → 3.0.0-beta.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (274) hide show
  1. package/dist/common/browser-detection.js.map +1 -1
  2. package/dist/common/collection.js.map +1 -1
  3. package/dist/common/config.js.map +1 -1
  4. package/dist/common/errors/captcha-error.js +7 -0
  5. package/dist/common/errors/captcha-error.js.map +1 -1
  6. package/dist/common/errors/intent-to-join.js +8 -0
  7. package/dist/common/errors/intent-to-join.js.map +1 -1
  8. package/dist/common/errors/join-meeting.js +8 -0
  9. package/dist/common/errors/join-meeting.js.map +1 -1
  10. package/dist/common/errors/media.js +7 -0
  11. package/dist/common/errors/media.js.map +1 -1
  12. package/dist/common/errors/parameter.js.map +1 -1
  13. package/dist/common/errors/password-error.js +7 -0
  14. package/dist/common/errors/password-error.js.map +1 -1
  15. package/dist/common/errors/permission.js +7 -0
  16. package/dist/common/errors/permission.js.map +1 -1
  17. package/dist/common/errors/reconnection-in-progress.js.map +1 -1
  18. package/dist/common/errors/reconnection.js +7 -0
  19. package/dist/common/errors/reconnection.js.map +1 -1
  20. package/dist/common/errors/stats.js +7 -0
  21. package/dist/common/errors/stats.js.map +1 -1
  22. package/dist/common/errors/webex-errors.js +5 -29
  23. package/dist/common/errors/webex-errors.js.map +1 -1
  24. package/dist/common/errors/webex-meetings-error.js +5 -2
  25. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  26. package/dist/common/events/events-scope.js.map +1 -1
  27. package/dist/common/events/events.js.map +1 -1
  28. package/dist/common/events/trigger-proxy.js.map +1 -1
  29. package/dist/common/events/util.js.map +1 -1
  30. package/dist/common/logs/logger-config.js.map +1 -1
  31. package/dist/common/logs/logger-proxy.js.map +1 -1
  32. package/dist/common/logs/request.js +3 -0
  33. package/dist/common/logs/request.js.map +1 -1
  34. package/dist/common/queue.js.map +1 -1
  35. package/dist/config.js +1 -0
  36. package/dist/config.js.map +1 -1
  37. package/dist/constants.js +15 -74
  38. package/dist/constants.js.map +1 -1
  39. package/dist/locus-info/controlsUtils.js.map +1 -1
  40. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  41. package/dist/locus-info/fullState.js.map +1 -1
  42. package/dist/locus-info/hostUtils.js.map +1 -1
  43. package/dist/locus-info/index.js +43 -5
  44. package/dist/locus-info/index.js.map +1 -1
  45. package/dist/locus-info/infoUtils.js +4 -0
  46. package/dist/locus-info/infoUtils.js.map +1 -1
  47. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  48. package/dist/locus-info/parser.js +12 -3
  49. package/dist/locus-info/parser.js.map +1 -1
  50. package/dist/locus-info/selfUtils.js.map +1 -1
  51. package/dist/media/index.js +71 -210
  52. package/dist/media/index.js.map +1 -1
  53. package/dist/media/internal-media-core-wrapper.js +22 -0
  54. package/dist/media/internal-media-core-wrapper.js.map +1 -0
  55. package/dist/media/properties.js +32 -25
  56. package/dist/media/properties.js.map +1 -1
  57. package/dist/media/util.js +0 -27
  58. package/dist/media/util.js.map +1 -1
  59. package/dist/mediaQualityMetrics/config.js.map +1 -1
  60. package/dist/meeting/effectsState.js +8 -1
  61. package/dist/meeting/effectsState.js.map +1 -1
  62. package/dist/meeting/index.js +1130 -647
  63. package/dist/meeting/index.js.map +1 -1
  64. package/dist/meeting/muteState.js +6 -0
  65. package/dist/meeting/muteState.js.map +1 -1
  66. package/dist/meeting/request.js +55 -24
  67. package/dist/meeting/request.js.map +1 -1
  68. package/dist/meeting/state.js.map +1 -1
  69. package/dist/meeting/util.js +5 -44
  70. package/dist/meeting/util.js.map +1 -1
  71. package/dist/meeting-info/collection.js +4 -1
  72. package/dist/meeting-info/collection.js.map +1 -1
  73. package/dist/meeting-info/index.js +5 -0
  74. package/dist/meeting-info/index.js.map +1 -1
  75. package/dist/meeting-info/meeting-info-v2.js +14 -2
  76. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  77. package/dist/meeting-info/request.js +3 -0
  78. package/dist/meeting-info/request.js.map +1 -1
  79. package/dist/meeting-info/util.js.map +1 -1
  80. package/dist/meeting-info/utilv2.js.map +1 -1
  81. package/dist/meetings/collection.js +4 -1
  82. package/dist/meetings/collection.js.map +1 -1
  83. package/dist/meetings/index.js +136 -25
  84. package/dist/meetings/index.js.map +1 -1
  85. package/dist/meetings/request.js +4 -0
  86. package/dist/meetings/request.js.map +1 -1
  87. package/dist/meetings/util.js +24 -1
  88. package/dist/meetings/util.js.map +1 -1
  89. package/dist/member/index.js +30 -7
  90. package/dist/member/index.js.map +1 -1
  91. package/dist/member/util.js +2 -1
  92. package/dist/member/util.js.map +1 -1
  93. package/dist/members/collection.js +1 -0
  94. package/dist/members/collection.js.map +1 -1
  95. package/dist/members/index.js +82 -1
  96. package/dist/members/index.js.map +1 -1
  97. package/dist/members/request.js +19 -9
  98. package/dist/members/request.js.map +1 -1
  99. package/dist/members/util.js.map +1 -1
  100. package/dist/metrics/config.js.map +1 -1
  101. package/dist/metrics/constants.js.map +1 -1
  102. package/dist/metrics/index.js +8 -0
  103. package/dist/metrics/index.js.map +1 -1
  104. package/dist/multistream/mediaRequestManager.js +133 -0
  105. package/dist/multistream/mediaRequestManager.js.map +1 -0
  106. package/dist/multistream/multistreamMedia.js +116 -0
  107. package/dist/multistream/multistreamMedia.js.map +1 -0
  108. package/dist/multistream/receiveSlot.js +209 -0
  109. package/dist/multistream/receiveSlot.js.map +1 -0
  110. package/dist/multistream/receiveSlotManager.js +195 -0
  111. package/dist/multistream/receiveSlotManager.js.map +1 -0
  112. package/dist/multistream/remoteMedia.js +289 -0
  113. package/dist/multistream/remoteMedia.js.map +1 -0
  114. package/dist/multistream/remoteMediaGroup.js +243 -0
  115. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  116. package/dist/multistream/remoteMediaManager.js +1113 -0
  117. package/dist/multistream/remoteMediaManager.js.map +1 -0
  118. package/dist/networkQualityMonitor/index.js +10 -2
  119. package/dist/networkQualityMonitor/index.js.map +1 -1
  120. package/dist/personal-meeting-room/index.js +11 -0
  121. package/dist/personal-meeting-room/index.js.map +1 -1
  122. package/dist/personal-meeting-room/request.js +2 -1
  123. package/dist/personal-meeting-room/request.js.map +1 -1
  124. package/dist/personal-meeting-room/util.js.map +1 -1
  125. package/dist/reachability/index.js +17 -7
  126. package/dist/reachability/index.js.map +1 -1
  127. package/dist/reachability/request.js +1 -0
  128. package/dist/reachability/request.js.map +1 -1
  129. package/dist/reconnection-manager/index.js +130 -132
  130. package/dist/reconnection-manager/index.js.map +1 -1
  131. package/dist/roap/index.js +58 -231
  132. package/dist/roap/index.js.map +1 -1
  133. package/dist/roap/request.js +7 -116
  134. package/dist/roap/request.js.map +1 -1
  135. package/dist/roap/turnDiscovery.js +20 -6
  136. package/dist/roap/turnDiscovery.js.map +1 -1
  137. package/dist/statsAnalyzer/global.js +2 -0
  138. package/dist/statsAnalyzer/global.js.map +1 -1
  139. package/dist/statsAnalyzer/index.js +58 -37
  140. package/dist/statsAnalyzer/index.js.map +1 -1
  141. package/dist/statsAnalyzer/mqaUtil.js +9 -3
  142. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  143. package/dist/transcription/index.js +10 -3
  144. package/dist/transcription/index.js.map +1 -1
  145. package/package.json +21 -20
  146. package/src/common/{browser-detection.js → browser-detection.ts} +1 -1
  147. package/src/common/collection.ts +6 -6
  148. package/src/common/{config.js → config.ts} +1 -1
  149. package/src/common/errors/{captcha-error.js → captcha-error.ts} +5 -1
  150. package/src/common/errors/{intent-to-join.js → intent-to-join.ts} +6 -1
  151. package/src/common/errors/{join-meeting.js → join-meeting.ts} +6 -1
  152. package/src/common/errors/{media.js → media.ts} +5 -1
  153. package/src/common/errors/parameter.ts +3 -2
  154. package/src/common/errors/{password-error.js → password-error.ts} +5 -1
  155. package/src/common/errors/{permission.js → permission.ts} +5 -1
  156. package/src/common/errors/{reconnection-in-progress.js → reconnection-in-progress.ts} +0 -0
  157. package/src/common/errors/{reconnection.js → reconnection.ts} +5 -1
  158. package/src/common/errors/{stats.js → stats.ts} +5 -1
  159. package/src/common/errors/{webex-errors.js → webex-errors.ts} +1 -20
  160. package/src/common/errors/{webex-meetings-error.js → webex-meetings-error.ts} +3 -1
  161. package/src/common/events/{events-scope.js → events-scope.ts} +1 -1
  162. package/src/common/events/{events.js → events.ts} +0 -0
  163. package/src/common/events/{trigger-proxy.js → trigger-proxy.ts} +1 -2
  164. package/src/common/events/{util.js → util.ts} +1 -1
  165. package/src/common/logs/{logger-config.js → logger-config.ts} +1 -2
  166. package/src/common/logs/{logger-proxy.js → logger-proxy.ts} +1 -1
  167. package/src/common/logs/{request.js → request.ts} +12 -2
  168. package/src/common/queue.ts +1 -2
  169. package/src/{config.js → config.ts} +2 -0
  170. package/src/constants.ts +139 -179
  171. package/src/locus-info/{controlsUtils.js → controlsUtils.ts} +4 -4
  172. package/src/locus-info/{embeddedAppsUtils.js → embeddedAppsUtils.ts} +5 -6
  173. package/src/locus-info/{fullState.js → fullState.ts} +1 -1
  174. package/src/locus-info/{hostUtils.js → hostUtils.ts} +5 -5
  175. package/src/locus-info/{index.js → index.ts} +67 -32
  176. package/src/locus-info/{infoUtils.js → infoUtils.ts} +7 -4
  177. package/src/locus-info/{mediaSharesUtils.js → mediaSharesUtils.ts} +13 -13
  178. package/src/locus-info/{parser.js → parser.ts} +22 -12
  179. package/src/locus-info/{selfUtils.js → selfUtils.ts} +17 -19
  180. package/src/media/{index.js → index.ts} +130 -205
  181. package/src/media/internal-media-core-wrapper.ts +9 -0
  182. package/src/media/{properties.js → properties.ts} +35 -29
  183. package/src/media/util.ts +16 -0
  184. package/src/mediaQualityMetrics/{config.js → config.ts} +1 -1
  185. package/src/meeting/{effectsState.js → effectsState.ts} +12 -6
  186. package/src/meeting/{index.js → index.ts} +1016 -550
  187. package/src/meeting/{muteState.js → muteState.ts} +16 -11
  188. package/src/meeting/{request.js → request.ts} +125 -36
  189. package/src/meeting/{state.js → state.ts} +6 -6
  190. package/src/meeting/{util.js → util.ts} +9 -51
  191. package/src/meeting-info/{collection.js → collection.ts} +4 -1
  192. package/src/meeting-info/{index.js → index.ts} +10 -6
  193. package/src/meeting-info/{meeting-info-v2.js → meeting-info-v2.ts} +28 -10
  194. package/src/meeting-info/{request.js → request.ts} +6 -2
  195. package/src/meeting-info/{util.js → util.ts} +6 -5
  196. package/src/meeting-info/{utilv2.js → utilv2.ts} +8 -7
  197. package/src/meetings/{collection.js → collection.ts} +5 -2
  198. package/src/meetings/{index.js → index.ts} +118 -22
  199. package/src/meetings/{request.js → request.ts} +6 -1
  200. package/src/meetings/{util.js → util.ts} +28 -5
  201. package/src/member/{index.js → index.ts} +46 -15
  202. package/src/member/{util.js → util.ts} +17 -16
  203. package/src/members/{collection.js → collection.ts} +2 -1
  204. package/src/members/{index.js → index.ts} +94 -26
  205. package/src/members/{request.js → request.ts} +16 -5
  206. package/src/members/{util.js → util.ts} +7 -7
  207. package/src/metrics/{config.js → config.ts} +0 -2
  208. package/src/metrics/{constants.js → constants.ts} +0 -0
  209. package/src/metrics/{index.js → index.ts} +27 -8
  210. package/src/multistream/mediaRequestManager.ts +166 -0
  211. package/src/multistream/multistreamMedia.ts +92 -0
  212. package/src/multistream/receiveSlot.ts +141 -0
  213. package/src/multistream/receiveSlotManager.ts +142 -0
  214. package/src/multistream/remoteMedia.ts +228 -0
  215. package/src/multistream/remoteMediaGroup.ts +224 -0
  216. package/src/multistream/remoteMediaManager.ts +911 -0
  217. package/src/networkQualityMonitor/{index.js → index.ts} +18 -3
  218. package/src/personal-meeting-room/{index.js → index.ts} +17 -4
  219. package/src/personal-meeting-room/{request.js → request.ts} +3 -1
  220. package/src/personal-meeting-room/{util.js → util.ts} +1 -1
  221. package/src/reachability/{index.js → index.ts} +28 -17
  222. package/src/reachability/request.ts +4 -2
  223. package/src/reconnection-manager/{index.js → index.ts} +81 -65
  224. package/src/roap/index.ts +229 -0
  225. package/src/roap/{request.js → request.ts} +15 -74
  226. package/src/roap/turnDiscovery.ts +26 -11
  227. package/src/statsAnalyzer/{global.js → global.ts} +2 -0
  228. package/src/statsAnalyzer/{index.js → index.ts} +66 -61
  229. package/src/statsAnalyzer/{mqaUtil.js → mqaUtil.ts} +6 -1
  230. package/src/transcription/{index.js → index.ts} +16 -11
  231. package/test/integration/spec/journey.js +1 -1
  232. package/test/integration/spec/space-meeting.js +1 -2
  233. package/test/unit/spec/locus-info/infoUtils.js +17 -1
  234. package/test/unit/spec/media/index.ts +207 -0
  235. package/test/unit/spec/media/properties.ts +73 -82
  236. package/test/unit/spec/meeting/effectsState.js +1 -3
  237. package/test/unit/spec/meeting/index.js +585 -245
  238. package/test/unit/spec/meeting/muteState.js +7 -0
  239. package/test/unit/spec/meeting/utils.js +63 -2
  240. package/test/unit/spec/meetings/index.js +0 -4
  241. package/test/unit/spec/members/index.js +164 -2
  242. package/test/unit/spec/multistream/mediaRequestManager.ts +515 -0
  243. package/test/unit/spec/multistream/receiveSlot.ts +104 -0
  244. package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
  245. package/test/unit/spec/multistream/remoteMedia.ts +225 -0
  246. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  247. package/test/unit/spec/multistream/remoteMediaManager.ts +1309 -0
  248. package/test/unit/spec/reconnection-manager/index.js +68 -2
  249. package/test/unit/spec/roap/index.ts +63 -35
  250. package/test/unit/spec/stats-analyzer/index.js +19 -22
  251. package/dist/peer-connection-manager/index.js +0 -794
  252. package/dist/peer-connection-manager/index.js.map +0 -1
  253. package/dist/peer-connection-manager/util.js +0 -124
  254. package/dist/peer-connection-manager/util.js.map +0 -1
  255. package/dist/roap/collection.js +0 -73
  256. package/dist/roap/collection.js.map +0 -1
  257. package/dist/roap/handler.js +0 -337
  258. package/dist/roap/handler.js.map +0 -1
  259. package/dist/roap/state.js +0 -164
  260. package/dist/roap/state.js.map +0 -1
  261. package/dist/roap/util.js +0 -102
  262. package/dist/roap/util.js.map +0 -1
  263. package/src/media/util.js +0 -38
  264. package/src/peer-connection-manager/index.js +0 -723
  265. package/src/peer-connection-manager/util.ts +0 -117
  266. package/src/roap/collection.js +0 -63
  267. package/src/roap/handler.js +0 -252
  268. package/src/roap/index.js +0 -380
  269. package/src/roap/state.js +0 -149
  270. package/src/roap/util.js +0 -93
  271. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  272. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  273. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -389
  274. package/test/unit/spec/roap/util.js +0 -30
@@ -1,11 +1,12 @@
1
1
  import uuid from 'uuid';
2
- import {cloneDeep, isEqual, pick, isString} from 'lodash';
2
+ import {cloneDeep, isEqual, pick, isString, defer} from 'lodash';
3
+ // @ts-ignore - Fix this
3
4
  import {StatelessWebexPlugin} from '@webex/webex-core';
4
- import {Media as WebRTCMedia} from '@webex/internal-media-core';
5
+ import {Media as WebRTCMedia, MediaConnection as MC} from '@webex/internal-media-core';
5
6
 
6
7
  import {
7
8
  MeetingNotActiveError, createMeetingsError, UserInLobbyError,
8
- NoMediaEstablishedYetError, UserNotJoinedError, InvalidSdpError
9
+ NoMediaEstablishedYetError, UserNotJoinedError
9
10
  } from '../common/errors/webex-errors';
10
11
  import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
11
12
  import NetworkQualityMonitor from '../networkQualityMonitor';
@@ -18,9 +19,8 @@ import MeetingStateMachine from '../meeting/state';
18
19
  import createMuteState from '../meeting/muteState';
19
20
  import createEffectsState from '../meeting/effectsState';
20
21
  import LocusInfo from '../locus-info';
21
- import PeerConnectionManager from '../peer-connection-manager';
22
22
  import Metrics from '../metrics';
23
- import {trigger, mediaType, eventType} from '../metrics/config';
23
+ import {trigger, mediaType, error as MetricsError, eventType} from '../metrics/config';
24
24
  import ReconnectionManager from '../reconnection-manager';
25
25
  import MeetingRequest from '../meeting/request';
26
26
  import Members from '../members/index';
@@ -36,7 +36,6 @@ import {
36
36
  _INCOMING_,
37
37
  _JOIN_,
38
38
  AUDIO,
39
- CONNECTION_STATE,
40
39
  CONTENT,
41
40
  ENDED,
42
41
  EVENT_TRIGGERS,
@@ -45,7 +44,6 @@ import {
45
44
  FLOOR_ACTION,
46
45
  FULL_STATE,
47
46
  LAYOUT_TYPES,
48
- LIVE,
49
47
  LOCUSINFO,
50
48
  MEETING_INFO_FAILURE_REASON,
51
49
  MEETING_REMOVED_REASON,
@@ -58,13 +56,9 @@ import {
58
56
  ONLINE,
59
57
  OFFLINE,
60
58
  PASSWORD_STATUS,
61
- PC_BAIL_TIMEOUT,
62
59
  PSTN_STATUS,
63
60
  QUALITY_LEVELS,
64
61
  RECORDING_STATE,
65
- ROAP_SEQ_PRE,
66
- SDP,
67
- SENDRECV,
68
62
  SHARE_STATUS,
69
63
  SHARE_STOPPED_REASON,
70
64
  VIDEO_RESOLUTIONS,
@@ -77,14 +71,17 @@ import ParameterError from '../common/errors/parameter';
77
71
  import MediaError from '../common/errors/media';
78
72
  import {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '../meeting-info/meeting-info-v2';
79
73
  import BrowserDetection from '../common/browser-detection';
80
- import RoapCollection from '../roap/collection';
74
+ import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
75
+ import {MediaRequestManager} from '../multistream/mediaRequestManager';
76
+ import {RemoteMediaManager, Event as RemoteMediaManagerEvent} from '../multistream/remoteMediaManager';
77
+ import {MultistreamMedia} from '../multistream/multistreamMedia';
81
78
 
82
79
  import InMeetingActions from './in-meeting-actions';
83
80
 
84
81
 
85
82
  const {isBrowser} = BrowserDetection();
86
83
 
87
- const logRequest = (request, {header = '', success = '', failure = ''}) => {
84
+ const logRequest = (request: any, { header = '', success = '', failure = '' }) => {
88
85
  LoggerProxy.logger.info(header);
89
86
 
90
87
  return request
@@ -145,6 +142,7 @@ export const MEDIA_UPDATE_TYPE = {
145
142
  * @property {String} [meetingQuality.local]
146
143
  * @property {String} [meetingQuality.remote]
147
144
  * @property {Boolean} [rejoin]
145
+ * @property {Boolean} [enableMultistream]
148
146
  */
149
147
 
150
148
  /**
@@ -394,6 +392,92 @@ export const MEDIA_UPDATE_TYPE = {
394
392
  * @class Meeting
395
393
  */
396
394
  export default class Meeting extends StatelessWebexPlugin {
395
+ attrs: any;
396
+ audio: any;
397
+ conversationUrl: string;
398
+ correlationId: string;
399
+ destination: string;
400
+ destinationType: string;
401
+ deviceUrl: string;
402
+ effects: any;
403
+ hostId: string;
404
+ id: string;
405
+ isMultistream: boolean;
406
+ locusUrl: string;
407
+ mediaConnections: any[];
408
+ mediaId?: string;
409
+ meetingFiniteStateMachine: any;
410
+ meetingInfo: object;
411
+ meetingRequest: any;
412
+ members: Members;
413
+ options: object;
414
+ orgId: string;
415
+ owner: string;
416
+ partner: any;
417
+ policy: string;
418
+ reconnectionManager: ReconnectionManager;
419
+ resource: string;
420
+ roap: Roap;
421
+ roapSeq: number;
422
+ sipUri: string;
423
+ type: string;
424
+ userId: string;
425
+ video: any;
426
+ callEvents: any[];
427
+ deferJoin: Promise<any>;
428
+ dialInDeviceStatus: string;
429
+ dialInUrl: string;
430
+ dialOutDeviceStatus: string;
431
+ dialOutUrl: string;
432
+ fetchMeetingInfoTimeoutId: NodeJS.Timeout;
433
+ floorGrantPending: boolean;
434
+ hasJoinedOnce: boolean;
435
+ hasWebsocketConnected: boolean;
436
+ inMeetingActions: InMeetingActions;
437
+ isLocalShareLive: boolean;
438
+ isRoapInProgress: boolean;
439
+ isSharing: boolean;
440
+ keepAliveTimerId: NodeJS.Timeout;
441
+ lastVideoLayoutInfo: any;
442
+ locusInfo: any;
443
+ media: MultistreamMedia;
444
+ mediaProperties: MediaProperties;
445
+ mediaRequestManagers: {
446
+ audio: MediaRequestManager;
447
+ video: MediaRequestManager;
448
+ };
449
+ meetingInfoFailureReason: string;
450
+ networkQualityMonitor: NetworkQualityMonitor;
451
+ networkStatus: string;
452
+ passwordStatus: string;
453
+ queuedMediaUpdates: any[];
454
+ recording: any;
455
+ remoteMediaManager: RemoteMediaManager | null;
456
+ requiredCaptcha: any;
457
+ receiveSlotManager: ReceiveSlotManager;
458
+ shareStatus: string;
459
+ statsAnalyzer: StatsAnalyzer;
460
+ transcription: Transcription;
461
+ updateMediaConnections: (mediaConnections: any[]) => void;
462
+ endCallInitiateJoinReq: any;
463
+ endJoinReqResp: any;
464
+ endLocalSDPGenRemoteSDPRecvDelay: any;
465
+ joinedWith: any;
466
+ locusId: any;
467
+ startCallInitiateJoinReq: any;
468
+ startJoinReqResp: any;
469
+ startLocalSDPGenRemoteSDPRecvDelay: any;
470
+ wirelessShare: any;
471
+ guest: any;
472
+ meetingJoinUrl: any;
473
+ meetingNumber: any;
474
+ meetingState: any;
475
+ permissionToken: any;
476
+ resourceId: any;
477
+ resourceUrl: string;
478
+ selfId: string;
479
+ state: any;
480
+
397
481
  namespace = MEETINGS;
398
482
 
399
483
  /**
@@ -402,7 +486,7 @@ export default class Meeting extends StatelessWebexPlugin {
402
486
  * @constructor
403
487
  * @memberof Meeting
404
488
  */
405
- constructor(attrs, options) {
489
+ constructor(attrs: any, options: object) {
406
490
  super({}, options);
407
491
  /**
408
492
  * @instance
@@ -470,15 +554,6 @@ export default class Meeting extends StatelessWebexPlugin {
470
554
  * @memberof Meeting
471
555
  */
472
556
  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
557
  /**
483
558
  * @instance
484
559
  * @type {Object}
@@ -488,13 +563,44 @@ export default class Meeting extends StatelessWebexPlugin {
488
563
  */
489
564
  // TODO: needs to be defined as a class
490
565
  this.meetingInfo = {};
566
+ /**
567
+ * helper class for managing receive slots (for multistream media connections)
568
+ */
569
+ this.receiveSlotManager = new ReceiveSlotManager(this);
570
+ /**
571
+ * Helper class for managing media requests for main video (for multistream media connections)
572
+ * All media requests sent out for main video for this meeting have to go through it.
573
+ */
574
+ this.mediaRequestManagers = {
575
+ audio: new MediaRequestManager((mediaRequests) => {
576
+ if (!this.mediaProperties.webrtcMediaConnection) {
577
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created');
578
+
579
+ return;
580
+ }
581
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.AudioMain, mediaRequests);
582
+ }),
583
+ video: new MediaRequestManager((mediaRequests) => {
584
+ if (!this.mediaProperties.webrtcMediaConnection) {
585
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created');
586
+
587
+ return;
588
+ }
589
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.VideoMain, mediaRequests);
590
+ })
591
+ };
491
592
  /**
492
593
  * @instance
493
594
  * @type {Members}
494
595
  * @public
495
596
  * @memberof Meeting
496
597
  */
497
- this.members = new Members({locusUrl: (attrs.locus && attrs.locus.url)}, {parent: this.webex});
598
+ this.members = new Members({
599
+ locusUrl: (attrs.locus && attrs.locus.url),
600
+ receiveSlotManager: this.receiveSlotManager,
601
+ mediaRequestManagers: this.mediaRequestManagers,
602
+ // @ts-ignore - Fix type
603
+ }, {parent: this.webex});
498
604
  /**
499
605
  * @instance
500
606
  * @type {Roap}
@@ -502,7 +608,18 @@ export default class Meeting extends StatelessWebexPlugin {
502
608
  * @private
503
609
  * @memberof Meeting
504
610
  */
611
+ // @ts-ignore - Fix type
505
612
  this.roap = new Roap({}, {parent: this.webex});
613
+ /**
614
+ * indicates if an SDP exchange is happening
615
+ *
616
+ * @instance
617
+ * @type {Boolean}
618
+ * @readonly
619
+ * @private
620
+ * @memberof Meeting
621
+ */
622
+ this.isRoapInProgress = false;
506
623
  /**
507
624
  * created later
508
625
  * @instance
@@ -640,6 +757,11 @@ export default class Meeting extends StatelessWebexPlugin {
640
757
  */
641
758
  this.mediaConnections = null;
642
759
 
760
+ /**
761
+ * If true, then media is sent over multiple separate streams.
762
+ * If false, then media is transcoded by the server into a single stream.
763
+ */
764
+ this.isMultistream = false;
643
765
  /**
644
766
  * Fetching meeting info can be done randomly 2-5 mins before meeting start
645
767
  * In case it is done before the timer expires, this timeout id is reset to cancel the timer.
@@ -658,7 +780,7 @@ export default class Meeting extends StatelessWebexPlugin {
658
780
  * @private
659
781
  * @memberof Meeting
660
782
  */
661
- this.updateMediaConnections = (mediaConnections) => {
783
+ this.updateMediaConnections = (mediaConnections: any[]) => {
662
784
  if (!isEqual(this.mediaConnections, mediaConnections)) {
663
785
  // grab last/latest item in the new mediaConnections information
664
786
  this.mediaConnections = mediaConnections.slice(-1);
@@ -691,35 +813,13 @@ export default class Meeting extends StatelessWebexPlugin {
691
813
  this.isSharing = false;
692
814
  /**
693
815
  * @instance
694
- * @type {Boolean}
816
+ * @type {string}
695
817
  * @readonly
696
818
  * @public
697
819
  * @memberof Meeting
698
820
  */
699
821
  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
822
 
719
- return false;
720
- },
721
- configurable: true
722
- });
723
823
  /**
724
824
  * @instance
725
825
  * @type {Array}
@@ -731,7 +831,7 @@ export default class Meeting extends StatelessWebexPlugin {
731
831
  /**
732
832
  * There is a pending floor requested by the user
733
833
  * @instance
734
- * @type {floorGrantPending}
834
+ * @type {boolean}
735
835
  * @private
736
836
  * @memberof Meeting
737
837
  */
@@ -799,6 +899,7 @@ export default class Meeting extends StatelessWebexPlugin {
799
899
  * @private
800
900
  * @memberof Meeting
801
901
  */
902
+ // @ts-ignore - Fix type
802
903
  this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
803
904
  // We had to add listeners first before setting up the locus instance
804
905
  /**
@@ -826,6 +927,7 @@ export default class Meeting extends StatelessWebexPlugin {
826
927
  * @private
827
928
  * @memberof Meeting
828
929
  */
930
+ // @ts-ignore - Fix type
829
931
  this.hasWebsocketConnected = this.webex.internal.mercury.connected;
830
932
 
831
933
  /**
@@ -901,6 +1003,13 @@ export default class Meeting extends StatelessWebexPlugin {
901
1003
  this.setUpLocusInfoListeners();
902
1004
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
903
1005
  this.hasJoinedOnce = false;
1006
+
1007
+ this.media = new MultistreamMedia(this);
1008
+
1009
+ /**
1010
+ * helper class for managing remote streams
1011
+ */
1012
+ this.remoteMediaManager = null;
904
1013
  }
905
1014
 
906
1015
  /**
@@ -912,9 +1021,7 @@ export default class Meeting extends StatelessWebexPlugin {
912
1021
  * @memberof Meeting
913
1022
  * @returns {Promise}
914
1023
  */
915
- async fetchMeetingInfo({
916
- password = null, captchaCode = null
917
- }) {
1024
+ public async fetchMeetingInfo({ password = null, captchaCode = null }: { password?: string; captchaCode?: string }) {
918
1025
  // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
919
1026
  if (this.fetchMeetingInfoTimeoutId) {
920
1027
  clearTimeout(this.fetchMeetingInfoTimeoutId);
@@ -956,6 +1063,7 @@ export default class Meeting extends StatelessWebexPlugin {
956
1063
  }
957
1064
  catch (err) {
958
1065
  if (err instanceof MeetingInfoV2PasswordError) {
1066
+ // @ts-ignore
959
1067
  LoggerProxy.logger.info(`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`);
960
1068
 
961
1069
  // when wbxappapi requires password it still populates partial meeting info in the response
@@ -974,6 +1082,7 @@ export default class Meeting extends StatelessWebexPlugin {
974
1082
  throw (new PasswordError());
975
1083
  }
976
1084
  else if (err instanceof MeetingInfoV2CaptchaError) {
1085
+ // @ts-ignore
977
1086
  LoggerProxy.logger.info(`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`);
978
1087
 
979
1088
  this.meetingInfoFailureReason = (this.requiredCaptcha) ?
@@ -1003,7 +1112,7 @@ export default class Meeting extends StatelessWebexPlugin {
1003
1112
  * @memberof Meeting
1004
1113
  * @returns {Promise<{isPasswordValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
1005
1114
  */
1006
- verifyPassword(password, captchaCode) {
1115
+ public verifyPassword(password: string, captchaCode: string) {
1007
1116
  return this.fetchMeetingInfo({
1008
1117
  password, captchaCode
1009
1118
  })
@@ -1033,7 +1142,7 @@ export default class Meeting extends StatelessWebexPlugin {
1033
1142
  * @memberof Meeting
1034
1143
  * @returns {Promise}
1035
1144
  */
1036
- refreshCaptcha() {
1145
+ public refreshCaptcha() {
1037
1146
  if (!this.requiredCaptcha) {
1038
1147
  return Promise.reject(new Error('There is no captcha to refresh'));
1039
1148
  }
@@ -1063,7 +1172,7 @@ export default class Meeting extends StatelessWebexPlugin {
1063
1172
  * @private
1064
1173
  * @memberof Meeting
1065
1174
  */
1066
- setUpLocusInfoListeners() {
1175
+ private setUpLocusInfoListeners() {
1067
1176
  // meeting update listeners
1068
1177
  this.setUpLocusInfoSelfListener();
1069
1178
  this.setUpLocusInfoMeetingListener();
@@ -1081,14 +1190,13 @@ export default class Meeting extends StatelessWebexPlugin {
1081
1190
  this.setUpLocusInfoMediaInactiveListener();
1082
1191
  }
1083
1192
 
1084
-
1085
1193
  /**
1086
1194
  * Set up the locus info listener for meetings disconnected due to inactivity
1087
1195
  * @returns {undefined}
1088
1196
  * @private
1089
1197
  * @memberof Meeting
1090
1198
  */
1091
- setUpLocusInfoMediaInactiveListener() {
1199
+ private setUpLocusInfoMediaInactiveListener() {
1092
1200
  // User gets kicked off the meeting due to inactivity or user did a refresh
1093
1201
  this.locusInfo.on(EVENTS.DISCONNECT_DUE_TO_INACTIVITY, (res) => {
1094
1202
  // https:// jira-eng-gpk2.cisco.com/jira/browse/SPARK-240520
@@ -1116,6 +1224,7 @@ export default class Meeting extends StatelessWebexPlugin {
1116
1224
 
1117
1225
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMediaInactiveListener --> Meeting disconnected due to inactivity: ${res.reason}`);
1118
1226
 
1227
+ // @ts-ignore - config coming from registerPlugin
1119
1228
  if (this.config.reconnection.autoRejoin) {
1120
1229
  this.reconnect();
1121
1230
  }
@@ -1139,7 +1248,7 @@ export default class Meeting extends StatelessWebexPlugin {
1139
1248
  * @private
1140
1249
  * @memberof Meeting
1141
1250
  */
1142
- setUpLocusInfoAssignHostListener() {
1251
+ private setUpLocusInfoAssignHostListener() {
1143
1252
  this.locusInfo.on(EVENTS.LOCUS_INFO_CAN_ASSIGN_HOST, (payload) => {
1144
1253
  const changed = this.inMeetingActions.set({
1145
1254
  canAssignHost: payload.canAssignHost,
@@ -1165,7 +1274,7 @@ export default class Meeting extends StatelessWebexPlugin {
1165
1274
  * @private
1166
1275
  * @memberof Meeting
1167
1276
  */
1168
- setUpLocusFullStateListener() {
1277
+ private setUpLocusFullStateListener() {
1169
1278
  this.locusInfo.on(LOCUSINFO.EVENTS.FULL_STATE_MEETING_STATE_CHANGE, (payload) => {
1170
1279
  Trigger.trigger(
1171
1280
  this,
@@ -1196,7 +1305,13 @@ export default class Meeting extends StatelessWebexPlugin {
1196
1305
  * @returns {Object}
1197
1306
  * @memberof Meeting
1198
1307
  */
1199
- getAnalyzerMetricsPrePayload(options) {
1308
+ getAnalyzerMetricsPrePayload(options: {
1309
+ event: string;
1310
+ trackingId: string;
1311
+ locus: object;
1312
+ mediaConnections: Array<any>;
1313
+ errors: object;
1314
+ } | any) {
1200
1315
  if (options) {
1201
1316
  const {
1202
1317
  event,
@@ -1210,11 +1325,12 @@ export default class Meeting extends StatelessWebexPlugin {
1210
1325
  return null;
1211
1326
  }
1212
1327
 
1213
- const identifiers = {
1328
+ const identifiers: any = {
1214
1329
  correlationId: this.correlationId,
1215
1330
  userId: this.userId,
1216
1331
  deviceId: this.deviceUrl,
1217
1332
  orgId: this.orgId,
1333
+ // @ts-ignore fix type
1218
1334
  locusUrl: this.webex.internal.services.get('locus')
1219
1335
  };
1220
1336
 
@@ -1336,12 +1452,14 @@ export default class Meeting extends StatelessWebexPlugin {
1336
1452
  * @private
1337
1453
  * @memberof Meeting
1338
1454
  */
1339
- sendCallAnalyzerMetrics(options) {
1455
+ private sendCallAnalyzerMetrics(options: { event: string; trackingId: string; locus: object; errors: object }) {
1340
1456
  const payload = this.getAnalyzerMetricsPrePayload({
1457
+ // @ts-ignore - config coming from registerPlugin
1341
1458
  ...pick(this.config.metrics, ['clientType', 'subClientType']),
1342
1459
  ...options
1343
1460
  });
1344
1461
 
1462
+ // @ts-ignore - fix type
1345
1463
  return this.webex.internal.metrics.submitCallDiagnosticEvents(payload);
1346
1464
  }
1347
1465
 
@@ -1355,13 +1473,15 @@ export default class Meeting extends StatelessWebexPlugin {
1355
1473
  * @private
1356
1474
  * @memberof Meeting
1357
1475
  */
1358
- sendMediaQualityAnalyzerMetrics(options) {
1476
+ private sendMediaQualityAnalyzerMetrics(options: { event: string; trackingId: string; locus: object }) {
1359
1477
  const payload = this.getAnalyzerMetricsPrePayload({
1360
1478
  type: MQA_STATS.CA_TYPE,
1479
+ // @ts-ignore - config coming from registerPlugin
1361
1480
  ...pick(this.config.metrics, ['clientType', 'subClientType']),
1362
1481
  ...options
1363
1482
  });
1364
1483
 
1484
+ // @ts-ignore
1365
1485
  return this.webex.internal.metrics.submitCallDiagnosticEvents(payload);
1366
1486
  }
1367
1487
 
@@ -1372,7 +1492,7 @@ export default class Meeting extends StatelessWebexPlugin {
1372
1492
  * @returns {undefined}
1373
1493
  * @memberof Meeting
1374
1494
  */
1375
- setNetworkStatus(networkStatus) {
1495
+ private setNetworkStatus(networkStatus: string) {
1376
1496
  if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
1377
1497
  Trigger.trigger(
1378
1498
  this,
@@ -1405,14 +1525,14 @@ export default class Meeting extends StatelessWebexPlugin {
1405
1525
  * @private
1406
1526
  * @memberof Meeting
1407
1527
  */
1408
- setUpLocusSelfListener() {
1528
+ private setUpLocusSelfListener() {
1409
1529
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_SELF, (payload) => {
1410
1530
  this.members.locusSelfUpdate(payload);
1411
1531
  this.pstnUpdate(payload);
1412
1532
 
1413
1533
  // If user moved to a JOINED state and there is a pending floor grant trigger it
1414
1534
  if (this.floorGrantPending && payload.newSelf.state === MEETING_STATE.STATES.JOINED) {
1415
- this.share()
1535
+ this.requestScreenShareFloor()
1416
1536
  .then(() => { this.floorGrantPending = false; });
1417
1537
  }
1418
1538
  });
@@ -1425,7 +1545,7 @@ export default class Meeting extends StatelessWebexPlugin {
1425
1545
  * @private
1426
1546
  * @memberof Meeting
1427
1547
  */
1428
- pstnUpdate(payload) {
1548
+ private pstnUpdate(payload: any) {
1429
1549
  if (this.locusInfo.self) {
1430
1550
  const dialInPstnDevice = payload.newSelf?.pstnDevices.find((device) => device.url === this.dialInUrl);
1431
1551
  const dialOutPstnDevice = payload.newSelf?.pstnDevices.find((device) => device.url === this.dialOutUrl);
@@ -1480,7 +1600,7 @@ export default class Meeting extends StatelessWebexPlugin {
1480
1600
  * @private
1481
1601
  * @memberof Meeting
1482
1602
  */
1483
- setUpLocusHostListener() {
1603
+ private setUpLocusHostListener() {
1484
1604
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_HOST, (payload) => {
1485
1605
  this.members.locusHostUpdate(payload);
1486
1606
  });
@@ -1494,13 +1614,12 @@ export default class Meeting extends StatelessWebexPlugin {
1494
1614
  * @private
1495
1615
  * @memberof Meeting
1496
1616
  */
1497
- setUpLocusParticipantsListener() {
1617
+ private setUpLocusParticipantsListener() {
1498
1618
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS, (payload) => {
1499
1619
  this.members.locusParticipantsUpdate(payload);
1500
1620
  });
1501
1621
  }
1502
1622
 
1503
-
1504
1623
  /**
1505
1624
  * Set up the locus info recording update listener
1506
1625
  * update recording value for the meeting
@@ -1519,7 +1638,7 @@ export default class Meeting extends StatelessWebexPlugin {
1519
1638
  * @private
1520
1639
  * @memberof Meeting
1521
1640
  */
1522
- setupLocusControlsListener() {
1641
+ private setupLocusControlsListener() {
1523
1642
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
1524
1643
  ({state, modifiedBy, lastModified}) => {
1525
1644
  let event;
@@ -1575,6 +1694,8 @@ export default class Meeting extends StatelessWebexPlugin {
1575
1694
 
1576
1695
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIBE_UPDATED,
1577
1696
  ({caption, transcribing}) => {
1697
+
1698
+ // @ts-ignore - config coming from registerPlugin
1578
1699
  if (transcribing && this.transcription && this.config.receiveTranscription) {
1579
1700
  this.receiveTranscription();
1580
1701
  }
@@ -1613,7 +1734,7 @@ export default class Meeting extends StatelessWebexPlugin {
1613
1734
  * @private
1614
1735
  * @memberof Meeting
1615
1736
  */
1616
- setUpLocusMediaSharesListener() {
1737
+ private setUpLocusMediaSharesListener() {
1617
1738
  // Will get triggered on local and remote share
1618
1739
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
1619
1740
  const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
@@ -1701,7 +1822,7 @@ export default class Meeting extends StatelessWebexPlugin {
1701
1822
  this,
1702
1823
  {
1703
1824
  file: 'meeting/index',
1704
- function: 'stopFloorRequest'
1825
+ function: 'localShare'
1705
1826
  },
1706
1827
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
1707
1828
  {
@@ -1844,7 +1965,7 @@ export default class Meeting extends StatelessWebexPlugin {
1844
1965
  * @private
1845
1966
  * @memberof Meeting
1846
1967
  */
1847
- setUpLocusUrlListener() {
1968
+ private setUpLocusUrlListener() {
1848
1969
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
1849
1970
  this.members.locusUrlUpdate(payload);
1850
1971
  this.locusUrl = payload;
@@ -1858,7 +1979,7 @@ export default class Meeting extends StatelessWebexPlugin {
1858
1979
  * @private
1859
1980
  * @memberof meeting
1860
1981
  */
1861
- setUpLocusInfoMeetingInfoListener() {
1982
+ private setUpLocusInfoMeetingInfoListener() {
1862
1983
  this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_LOCKED, (payload) => {
1863
1984
  if (payload) {
1864
1985
  Trigger.trigger(
@@ -1926,17 +2047,34 @@ export default class Meeting extends StatelessWebexPlugin {
1926
2047
  this.inMeetingActions.get()
1927
2048
  );
1928
2049
  }
2050
+
2051
+ this.handleDataChannelUrlChange(payload.info.datachannelUrl);
1929
2052
  }
1930
2053
  });
1931
2054
  }
1932
2055
 
2056
+ /**
2057
+ * Handles a data channel URL change
2058
+ * @param {String} datachannelUrl
2059
+ * @returns {void}
2060
+ */
2061
+ handleDataChannelUrlChange(datachannelUrl) {
2062
+ if (datachannelUrl && this.config.enableAutomaticLLM) {
2063
+ // Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
2064
+ // after the MEETING_INFO_UPDATED callback finishes
2065
+ defer(() => {
2066
+ this.updateLLMConnection();
2067
+ });
2068
+ }
2069
+ }
2070
+
1933
2071
  /**
1934
2072
  * Set up the locus info embedded apps listener
1935
2073
  * @returns {undefined}
1936
2074
  * @private
1937
2075
  * @memberof meeting
1938
2076
  */
1939
- setUpLocusEmbeddedAppsListener() {
2077
+ private setUpLocusEmbeddedAppsListener() {
1940
2078
  this.locusInfo.on(LOCUSINFO.EVENTS.EMBEDDED_APPS_UPDATED, (embeddedApps) => {
1941
2079
  if (embeddedApps) {
1942
2080
  Trigger.trigger(
@@ -1958,7 +2096,7 @@ export default class Meeting extends StatelessWebexPlugin {
1958
2096
  * @private
1959
2097
  * @memberof Meeting
1960
2098
  */
1961
- setUpLocusInfoSelfListener() {
2099
+ private setUpLocusInfoSelfListener() {
1962
2100
  this.locusInfo.on(LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED, (payload) => {
1963
2101
  if (this.audio) {
1964
2102
  this.audio.handleServerLocalUnmuteRequired(this);
@@ -2056,6 +2194,7 @@ export default class Meeting extends StatelessWebexPlugin {
2056
2194
  }
2057
2195
  });
2058
2196
 
2197
+ // @ts-ignore - check if MEDIA_INACTIVITY exists
2059
2198
  this.locusInfo.on(LOCUSINFO.EVENTS.MEDIA_INACTIVITY, () => {
2060
2199
  Metrics.sendBehavioralMetric(
2061
2200
  BEHAVIORAL_METRICS.MEETING_MEDIA_INACTIVE,
@@ -2122,7 +2261,7 @@ export default class Meeting extends StatelessWebexPlugin {
2122
2261
  * @private
2123
2262
  * @memberof Meeting
2124
2263
  */
2125
- setUpLocusInfoMeetingListener() {
2264
+ private setUpLocusInfoMeetingListener() {
2126
2265
  this.locusInfo.on(EVENTS.REMOTE_RESPONSE, (payload) => {
2127
2266
  this.meetingFiniteStateMachine.remote(payload);
2128
2267
 
@@ -2130,6 +2269,7 @@ export default class Meeting extends StatelessWebexPlugin {
2130
2269
  this.leave({reason: payload.reason}).then(() => {
2131
2270
  LoggerProxy.logger.info('Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Attempting to leave meeting.');
2132
2271
  }).catch((error) => {
2272
+ // @ts-ignore
2133
2273
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`);
2134
2274
  });
2135
2275
  }
@@ -2158,6 +2298,7 @@ export default class Meeting extends StatelessWebexPlugin {
2158
2298
  this.leave({reason: payload.reason}).then(() => {
2159
2299
  LoggerProxy.logger.warn('Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. The meeting has been left, but has not been destroyed, you should see a later event for leave.');
2160
2300
  }).catch((error) => {
2301
+ // @ts-ignore
2161
2302
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`);
2162
2303
  });
2163
2304
  }
@@ -2189,7 +2330,7 @@ export default class Meeting extends StatelessWebexPlugin {
2189
2330
  * @memberof Meeting
2190
2331
  * // TODO: is this function necessary?
2191
2332
  */
2192
- updateMeetingObject(object) {
2333
+ private updateMeetingObject(object: object) {
2193
2334
  // Validate if these are valid meeting object property
2194
2335
  // TODO: add a check to make sure the value passed in the constructor
2195
2336
  // is not changed by any delta event
@@ -2211,7 +2352,14 @@ export default class Meeting extends StatelessWebexPlugin {
2211
2352
  * @public
2212
2353
  * @memberof Meeting
2213
2354
  */
2214
- invite(invitee, alertIfActive = true) {
2355
+ public invite(
2356
+ invitee: {
2357
+ emailAddress: string;
2358
+ email: string;
2359
+ phoneNumber: string;
2360
+ },
2361
+ alertIfActive: boolean = true
2362
+ ) {
2215
2363
  return this.members.addMember(invitee, alertIfActive);
2216
2364
  }
2217
2365
 
@@ -2223,7 +2371,7 @@ export default class Meeting extends StatelessWebexPlugin {
2223
2371
  * @public
2224
2372
  * @memberof Meeting
2225
2373
  */
2226
- cancelPhoneInvite(invitee) {
2374
+ public cancelPhoneInvite(invitee: { phoneNumber: string }) {
2227
2375
  return this.members.cancelPhoneInvite(invitee);
2228
2376
  }
2229
2377
 
@@ -2234,7 +2382,7 @@ export default class Meeting extends StatelessWebexPlugin {
2234
2382
  * @public
2235
2383
  * @memberof Meeting
2236
2384
  */
2237
- admit(memberIds) {
2385
+ public admit(memberIds: Array<any>) {
2238
2386
  return this.members.admitMembers(memberIds);
2239
2387
  }
2240
2388
 
@@ -2245,7 +2393,7 @@ export default class Meeting extends StatelessWebexPlugin {
2245
2393
  * @public
2246
2394
  * @memberof Meeting
2247
2395
  */
2248
- remove(memberId) {
2396
+ public remove(memberId: string) {
2249
2397
  return this.members.removeMember(memberId);
2250
2398
  }
2251
2399
 
@@ -2257,7 +2405,7 @@ export default class Meeting extends StatelessWebexPlugin {
2257
2405
  * @public
2258
2406
  * @memberof Meeting
2259
2407
  */
2260
- mute(memberId, mute = true) {
2408
+ public mute(memberId: string, mute: boolean = true) {
2261
2409
  return this.members.muteMember(memberId, mute);
2262
2410
  }
2263
2411
 
@@ -2269,7 +2417,7 @@ export default class Meeting extends StatelessWebexPlugin {
2269
2417
  * @public
2270
2418
  * @memberof Meeting
2271
2419
  */
2272
- transfer(memberId, moderator = true) {
2420
+ public transfer(memberId: string, moderator: boolean = true) {
2273
2421
  return this.members.transferHostToMember(memberId, moderator);
2274
2422
  }
2275
2423
 
@@ -2279,7 +2427,7 @@ export default class Meeting extends StatelessWebexPlugin {
2279
2427
  * @public
2280
2428
  * @memberof Meeting
2281
2429
  */
2282
- getMembers() {
2430
+ public getMembers() {
2283
2431
  return this.members;
2284
2432
  }
2285
2433
 
@@ -2289,7 +2437,7 @@ export default class Meeting extends StatelessWebexPlugin {
2289
2437
  * @public
2290
2438
  * @memberof Meeting
2291
2439
  */
2292
- isAudioConnected() {
2440
+ public isAudioConnected() {
2293
2441
  return !!this.audio;
2294
2442
  }
2295
2443
 
@@ -2299,7 +2447,7 @@ export default class Meeting extends StatelessWebexPlugin {
2299
2447
  * @public
2300
2448
  * @memberof Meeting
2301
2449
  */
2302
- isAudioMuted() {
2450
+ public isAudioMuted() {
2303
2451
  return this.audio && this.audio.isMuted();
2304
2452
  }
2305
2453
 
@@ -2309,7 +2457,7 @@ export default class Meeting extends StatelessWebexPlugin {
2309
2457
  * @public
2310
2458
  * @memberof Meeting
2311
2459
  */
2312
- isAudioSelf() {
2460
+ public isAudioSelf() {
2313
2461
  return this.audio && this.audio.isSelf();
2314
2462
  }
2315
2463
 
@@ -2319,7 +2467,7 @@ export default class Meeting extends StatelessWebexPlugin {
2319
2467
  * @public
2320
2468
  * @memberof Meeting
2321
2469
  */
2322
- isVideoConnected() {
2470
+ public isVideoConnected() {
2323
2471
  return !!this.video;
2324
2472
  }
2325
2473
 
@@ -2329,7 +2477,7 @@ export default class Meeting extends StatelessWebexPlugin {
2329
2477
  * @public
2330
2478
  * @memberof Meeting
2331
2479
  */
2332
- isVideoMuted() {
2480
+ public isVideoMuted() {
2333
2481
  return this.video && this.video.isMuted();
2334
2482
  }
2335
2483
 
@@ -2339,7 +2487,7 @@ export default class Meeting extends StatelessWebexPlugin {
2339
2487
  * @public
2340
2488
  * @memberof Meeting
2341
2489
  */
2342
- isVideoSelf() {
2490
+ public isVideoSelf() {
2343
2491
  return this.video && this.video.isSelf();
2344
2492
  }
2345
2493
 
@@ -2356,7 +2504,17 @@ export default class Meeting extends StatelessWebexPlugin {
2356
2504
  * @private
2357
2505
  * @memberof Meeting
2358
2506
  */
2359
- parseMeetingInfo(meetingInfo, destination = null) {
2507
+ parseMeetingInfo(
2508
+ meetingInfo: {
2509
+ body: {
2510
+ conversationUrl: string;
2511
+ locusUrl: string;
2512
+ sipUri: string;
2513
+ owner: object;
2514
+ };
2515
+ } | any,
2516
+ destination: object | string | null = null
2517
+ ) {
2360
2518
  const webexMeetingInfo = meetingInfo?.body;
2361
2519
  // We try to use as much info from Locus meeting object, stored in destination
2362
2520
 
@@ -2370,7 +2528,9 @@ export default class Meeting extends StatelessWebexPlugin {
2370
2528
  if (locusMeetingObject || (webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))) {
2371
2529
  this.conversationUrl = locusMeetingObject?.conversationUrl || webexMeetingInfo?.conversationUrl || this.conversationUrl;
2372
2530
  this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
2531
+ // @ts-ignore - config coming from registerPlugin
2373
2532
  this.setSipUri(this.config.experimental.enableUnifiedMeetings ? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl : locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri);
2533
+ // @ts-ignore - config coming from registerPlugin
2374
2534
  if (this.config.experimental.enableUnifiedMeetings) {
2375
2535
  this.meetingNumber = locusMeetingObject?.info.webExMeetingId || webexMeetingInfo?.meetingNumber;
2376
2536
  this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
@@ -2390,7 +2550,7 @@ export default class Meeting extends StatelessWebexPlugin {
2390
2550
  * @private
2391
2551
  * @memberof Meeting
2392
2552
  */
2393
- parseLocus(locus) {
2553
+ private parseLocus(locus: { url: string; participants: Array<any>; self: object }) {
2394
2554
  if (locus) {
2395
2555
  this.locusUrl = locus.url;
2396
2556
  // TODO: move this to parse participants module
@@ -2417,24 +2577,11 @@ export default class Meeting extends StatelessWebexPlugin {
2417
2577
  * @private
2418
2578
  * @memberof Meeting
2419
2579
  */
2420
- setSipUri(sipUri) {
2580
+ setSipUri(sipUri: string) {
2421
2581
  // This can be tel no, device id or a sip uri, user Id
2422
2582
  this.sipUri = sipUri;
2423
2583
  }
2424
2584
 
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
2585
  /**
2439
2586
  * Set the locus info the class instance
2440
2587
  * @param {Object} locus
@@ -2448,8 +2595,14 @@ export default class Meeting extends StatelessWebexPlugin {
2448
2595
  * @private
2449
2596
  * @memberof Meeting
2450
2597
  */
2451
- setLocus(locus) {
2452
- const mtgLocus = locus.locus || locus;
2598
+ private setLocus(locus: {
2599
+ mediaConnections: Array<any>;
2600
+ locusUrl: string;
2601
+ locusId: string;
2602
+ mediaId: string;
2603
+ host: object;
2604
+ } | any) {
2605
+ const mtgLocus: any = locus.locus || locus;
2453
2606
 
2454
2607
  // LocusInfo object saves the locus object
2455
2608
  // this.locus = mtgLocus;
@@ -2462,111 +2615,6 @@ export default class Meeting extends StatelessWebexPlugin {
2462
2615
  this.locusInfo.initialSetup(mtgLocus);
2463
2616
  }
2464
2617
 
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
2618
  /**
2571
2619
  * Upload logs for the current meeting
2572
2620
  * @param {object} options file name and function name
@@ -2574,7 +2622,7 @@ export default class Meeting extends StatelessWebexPlugin {
2574
2622
  * @public
2575
2623
  * @memberof Meeting
2576
2624
  */
2577
- uploadLogs(options = {file: 'meeting/index', function: 'uploadLogs'}) {
2625
+ public uploadLogs(options: object = { file: 'meeting/index', function: 'uploadLogs' }) {
2578
2626
  Trigger.trigger(
2579
2627
  this,
2580
2628
  options,
@@ -2583,7 +2631,6 @@ export default class Meeting extends StatelessWebexPlugin {
2583
2631
  );
2584
2632
  }
2585
2633
 
2586
-
2587
2634
  /**
2588
2635
  * Removes remote audio and video stream on the class instance and triggers an event
2589
2636
  * to developers
@@ -2592,7 +2639,7 @@ export default class Meeting extends StatelessWebexPlugin {
2592
2639
  * @memberof Meeting
2593
2640
  * @deprecated after v1.89.3
2594
2641
  */
2595
- unsetRemoteStream() {
2642
+ public unsetRemoteStream() {
2596
2643
  LoggerProxy.logger.warn('Meeting:index#unsetRemoteStream --> [DEPRECATION WARNING]: unsetRemoteStream has been deprecated after v1.89.3');
2597
2644
  this.mediaProperties.unsetRemoteMedia();
2598
2645
  }
@@ -2613,7 +2660,7 @@ export default class Meeting extends StatelessWebexPlugin {
2613
2660
  * @memberof Meeting
2614
2661
  * @deprecated after v1.89.3
2615
2662
  */
2616
- closeRemoteStream() {
2663
+ public closeRemoteStream() {
2617
2664
  LoggerProxy.logger.warn('Meeting:index#closeRemoteStream --> [DEPRECATION WARNING]: closeRemoteStream has been deprecated after v1.89.3');
2618
2665
  this.closeRemoteTracks();
2619
2666
  }
@@ -2637,7 +2684,7 @@ export default class Meeting extends StatelessWebexPlugin {
2637
2684
  * @returns {void}
2638
2685
  * @inner
2639
2686
  */
2640
- const triggerMediaStoppedEvent = (mediaType) => {
2687
+ const triggerMediaStoppedEvent = (mediaType: string) => {
2641
2688
  Trigger.trigger(
2642
2689
  this,
2643
2690
  {
@@ -2659,7 +2706,7 @@ export default class Meeting extends StatelessWebexPlugin {
2659
2706
  * @inner
2660
2707
  */
2661
2708
  // eslint-disable-next-line arrow-body-style
2662
- const stopTrack = (track, type) => {
2709
+ const stopTrack = (track: MediaStreamTrack, type: string) => {
2663
2710
  return Media.stopTracks(track)
2664
2711
  .then(() => {
2665
2712
  const isTrackStopped = track && track.readyState === ENDED;
@@ -2687,7 +2734,7 @@ export default class Meeting extends StatelessWebexPlugin {
2687
2734
  * @private
2688
2735
  * @memberof Meeting
2689
2736
  */
2690
- sendLocalMediaReadyEvent() {
2737
+ private sendLocalMediaReadyEvent() {
2691
2738
  Trigger.trigger(
2692
2739
  this,
2693
2740
  {
@@ -2710,7 +2757,7 @@ export default class Meeting extends StatelessWebexPlugin {
2710
2757
  * @private
2711
2758
  * @memberof Meeting
2712
2759
  */
2713
- setLocalAudioTrack(audioTrack, emitEvent = true) {
2760
+ private setLocalAudioTrack(audioTrack: MediaStreamTrack, emitEvent: boolean = true) {
2714
2761
  if (audioTrack) {
2715
2762
  const settings = audioTrack.getSettings();
2716
2763
 
@@ -2737,7 +2784,7 @@ export default class Meeting extends StatelessWebexPlugin {
2737
2784
  * @private
2738
2785
  * @memberof Meeting
2739
2786
  */
2740
- setLocalVideoTrack(videoTrack, emitEvent = true) {
2787
+ private setLocalVideoTrack(videoTrack: MediaStreamTrack, emitEvent: boolean = true) {
2741
2788
  if (videoTrack) {
2742
2789
  const {
2743
2790
  aspectRatio, frameRate, height, width, deviceId
@@ -2777,7 +2824,7 @@ export default class Meeting extends StatelessWebexPlugin {
2777
2824
  * @public
2778
2825
  * @memberof Meeting
2779
2826
  */
2780
- setLocalTracks(localStream) {
2827
+ public setLocalTracks(localStream: any) {
2781
2828
  if (localStream) {
2782
2829
  const {audioTrack, videoTrack} = MeetingUtil.getTrack(localStream);
2783
2830
 
@@ -2790,12 +2837,12 @@ export default class Meeting extends StatelessWebexPlugin {
2790
2837
 
2791
2838
  /**
2792
2839
  * Sets the local media stream on the class and emits an event to the developer
2793
- * @param {Stream} localShare the local media stream
2840
+ * @param {MediaStream} localShare the local media stream
2794
2841
  * @returns {undefined}
2795
2842
  * @public
2796
2843
  * @memberof Meeting
2797
2844
  */
2798
- setLocalShareTrack(localShare) {
2845
+ public setLocalShareTrack(localShare: MediaStream) {
2799
2846
  let settings = null;
2800
2847
 
2801
2848
  if (localShare) {
@@ -2839,7 +2886,7 @@ export default class Meeting extends StatelessWebexPlugin {
2839
2886
  * @public
2840
2887
  * @memberof Meeting
2841
2888
  */
2842
- closeLocalStream() {
2889
+ public closeLocalStream() {
2843
2890
  const {audioTrack, videoTrack} = this.mediaProperties;
2844
2891
 
2845
2892
  return Media.stopTracks(audioTrack)
@@ -2874,7 +2921,7 @@ export default class Meeting extends StatelessWebexPlugin {
2874
2921
  * @public
2875
2922
  * @memberof Meeting
2876
2923
  */
2877
- closeLocalShare() {
2924
+ public closeLocalShare() {
2878
2925
  const track = this.mediaProperties.shareTrack;
2879
2926
 
2880
2927
  return Media.stopTracks(track).then(() => {
@@ -2903,7 +2950,7 @@ export default class Meeting extends StatelessWebexPlugin {
2903
2950
  * @public
2904
2951
  * @memberof Meeting
2905
2952
  */
2906
- unsetLocalVideoTrack() {
2953
+ public unsetLocalVideoTrack() {
2907
2954
  this.mediaProperties.unsetLocalVideoTrack();
2908
2955
  }
2909
2956
 
@@ -2913,7 +2960,7 @@ export default class Meeting extends StatelessWebexPlugin {
2913
2960
  * @public
2914
2961
  * @memberof Meeting
2915
2962
  */
2916
- unsetLocalShareTrack() {
2963
+ public unsetLocalShareTrack() {
2917
2964
  this.mediaProperties.unsetLocalShareTrack();
2918
2965
  }
2919
2966
 
@@ -2923,9 +2970,10 @@ export default class Meeting extends StatelessWebexPlugin {
2923
2970
  * @public
2924
2971
  * @memberof Meeting
2925
2972
  */
2926
- setMercuryListener() {
2973
+ public setMercuryListener() {
2927
2974
  // Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
2928
2975
  // if the meeting has active peer connections, it should try to reconnect.
2976
+ // @ts-ignore
2929
2977
  this.webex.internal.mercury.on(ONLINE, () => {
2930
2978
  LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
2931
2979
 
@@ -2945,6 +2993,7 @@ export default class Meeting extends StatelessWebexPlugin {
2945
2993
  this.hasWebsocketConnected = true;
2946
2994
  });
2947
2995
 
2996
+ // @ts-ignore
2948
2997
  this.webex.internal.mercury.on(OFFLINE, () => {
2949
2998
  LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
2950
2999
  Metrics.postEvent({
@@ -2961,27 +3010,41 @@ export default class Meeting extends StatelessWebexPlugin {
2961
3010
  }
2962
3011
 
2963
3012
  /**
2964
- * Close the peer connections and remove them from the class. Triggers an event
2965
- * when each is closed.
2966
- * @returns {Promise} returns a resolved promise with an array of closed peer connections
3013
+ * Close the peer connections and remove them from the class.
3014
+ * Cleanup any media connection related things.
3015
+ *
3016
+ * @returns {Promise}
2967
3017
  * @public
2968
3018
  * @memberof Meeting
2969
3019
  */
2970
- closePeerConnections() {
2971
- return PeerConnectionManager.close(this.mediaProperties.peerConnection);
3020
+ public closePeerConnections() {
3021
+ if (this.mediaProperties.webrtcMediaConnection) {
3022
+ if (this.remoteMediaManager) {
3023
+ this.remoteMediaManager.stop();
3024
+ this.remoteMediaManager = null;
3025
+ }
3026
+
3027
+ Object.values(this.mediaRequestManagers).forEach((mediaRequestManager) => mediaRequestManager.reset());
3028
+
3029
+ this.receiveSlotManager.reset();
3030
+ this.mediaProperties.webrtcMediaConnection.close();
3031
+ }
3032
+
3033
+ return Promise.resolve();
2972
3034
  }
2973
3035
 
2974
3036
  /**
2975
3037
  * Unsets the peer connections on the class
2976
3038
  * warning DO NOT CALL WITHOUT CLOSING PEER CONNECTIONS FIRST
2977
- * @param {PeerConnection} peerConnection
2978
3039
  * @returns {undefined}
2979
3040
  * @public
2980
3041
  * @memberof Meeting
2981
3042
  */
2982
- unsetPeerConnections() {
3043
+ public unsetPeerConnections() {
2983
3044
  this.mediaProperties.unsetPeerConnection();
3045
+ // @ts-ignore - config coming from registerPlugin
2984
3046
  if (this.config.reconnection.detection) {
3047
+ // @ts-ignore
2985
3048
  this.webex.internal.mercury.off(ONLINE);
2986
3049
  }
2987
3050
  }
@@ -2993,7 +3056,7 @@ export default class Meeting extends StatelessWebexPlugin {
2993
3056
  * @private
2994
3057
  * @memberof Meeting
2995
3058
  */
2996
- setCorrelationId(id) {
3059
+ private setCorrelationId(id: string) {
2997
3060
  this.correlationId = id;
2998
3061
  }
2999
3062
 
@@ -3003,11 +3066,12 @@ export default class Meeting extends StatelessWebexPlugin {
3003
3066
  * @public
3004
3067
  * @memberof Meeting
3005
3068
  */
3006
- muteAudio() {
3069
+ public muteAudio() {
3007
3070
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3008
3071
  return Promise.reject(new UserNotJoinedError());
3009
3072
  }
3010
3073
 
3074
+ // @ts-ignore
3011
3075
  if (!this.mediaId) {
3012
3076
  // Happens when addMedia and mute are triggered in succession
3013
3077
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3054,11 +3118,12 @@ export default class Meeting extends StatelessWebexPlugin {
3054
3118
  * @public
3055
3119
  * @memberof Meeting
3056
3120
  */
3057
- unmuteAudio() {
3121
+ public unmuteAudio() {
3058
3122
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3059
3123
  return Promise.reject(new UserNotJoinedError());
3060
3124
  }
3061
3125
 
3126
+ // @ts-ignore
3062
3127
  if (!this.mediaId) {
3063
3128
  // Happens when addMedia and mute are triggered in succession
3064
3129
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3105,11 +3170,12 @@ export default class Meeting extends StatelessWebexPlugin {
3105
3170
  * @public
3106
3171
  * @memberof Meeting
3107
3172
  */
3108
- muteVideo() {
3173
+ public muteVideo() {
3109
3174
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3110
3175
  return Promise.reject(new UserNotJoinedError());
3111
3176
  }
3112
3177
 
3178
+ // @ts-ignore
3113
3179
  if (!this.mediaId) {
3114
3180
  // Happens when addMedia and mute are triggered in succession
3115
3181
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3155,11 +3221,12 @@ export default class Meeting extends StatelessWebexPlugin {
3155
3221
  * @public
3156
3222
  * @memberof Meeting
3157
3223
  */
3158
- unmuteVideo() {
3224
+ public unmuteVideo() {
3159
3225
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3160
3226
  return Promise.reject(new UserNotJoinedError());
3161
3227
  }
3162
3228
 
3229
+ // @ts-ignore
3163
3230
  if (!this.mediaId) {
3164
3231
  // Happens when addMedia and mute are triggered in succession
3165
3232
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3224,7 +3291,13 @@ export default class Meeting extends StatelessWebexPlugin {
3224
3291
  * video: 'videoDeviceId'
3225
3292
  * }})
3226
3293
  */
3227
- joinWithMedia(options = {}) {
3294
+ public joinWithMedia(
3295
+ options: {
3296
+ joinOptions?: any;
3297
+ mediaSettings: any;
3298
+ audioVideoOptions?: any;
3299
+ } = {} as any
3300
+ ) {
3228
3301
  // TODO: add validations for parameters
3229
3302
  const {mediaSettings, joinOptions, audioVideoOptions} = options;
3230
3303
 
@@ -3268,19 +3341,21 @@ export default class Meeting extends StatelessWebexPlugin {
3268
3341
  * @public
3269
3342
  * @memberof Meeting
3270
3343
  */
3271
- reconnect(options) {
3344
+ public reconnect(options?: object) {
3272
3345
  LoggerProxy.logger.log(`Meeting:index#reconnect --> attempting to reconnect meeting ${this.id}`);
3273
3346
 
3274
3347
  if (!this.reconnectionManager || !this.reconnectionManager.reconnect) {
3275
3348
  return Promise.reject(new ParameterError('Cannot reconnect, ReconnectionManager must first be defined.'));
3276
3349
  }
3277
3350
 
3351
+ // @ts-ignore - currentMediaStatus coming from SelfUtil
3278
3352
  if (!MeetingUtil.isMediaEstablished(this.currentMediaStatus)) {
3279
3353
  return Promise.reject(new ParameterError('Cannot reconnect, Media has not established to reconnect'));
3280
3354
  }
3281
3355
 
3282
3356
  try {
3283
3357
  LoggerProxy.logger.info('Meeting:index#reconnect --> Validating reconnect ability.');
3358
+ // @ts-ignore
3284
3359
  this.reconnectionManager.validate();
3285
3360
  }
3286
3361
  catch (error) {
@@ -3304,7 +3379,6 @@ export default class Meeting extends StatelessWebexPlugin {
3304
3379
  EVENT_TRIGGERS.MEETING_RECONNECTION_STARTING
3305
3380
  );
3306
3381
 
3307
-
3308
3382
  return this.reconnectionManager
3309
3383
  .reconnect(options)
3310
3384
  .then(() => {
@@ -3377,7 +3451,7 @@ export default class Meeting extends StatelessWebexPlugin {
3377
3451
  * @private
3378
3452
  * @returns {void}
3379
3453
  */
3380
- monitorTranscriptionSocketConnection() {
3454
+ private monitorTranscriptionSocketConnection() {
3381
3455
  this.transcription.onCloseSocket((event) => {
3382
3456
  LoggerProxy.logger.info(
3383
3457
  `Meeting:index#onCloseSocket -->
@@ -3415,7 +3489,7 @@ export default class Meeting extends StatelessWebexPlugin {
3415
3489
  * @private
3416
3490
  * @returns {Promise<void>} a promise to open the WebSocket connection
3417
3491
  */
3418
- async receiveTranscription() {
3492
+ private async receiveTranscription() {
3419
3493
  LoggerProxy.logger.info(
3420
3494
  `Meeting:index#receiveTranscription -->
3421
3495
  Attempting to generate a web socket url.`
@@ -3423,6 +3497,7 @@ export default class Meeting extends StatelessWebexPlugin {
3423
3497
 
3424
3498
  try {
3425
3499
  const {datachannelUrl} = this.locusInfo.info;
3500
+ // @ts-ignore - fix type
3426
3501
  const {body: {webSocketUrl}} = await this.request({
3427
3502
  method: HTTP_VERBS.POST,
3428
3503
  uri: datachannelUrl,
@@ -3436,6 +3511,7 @@ export default class Meeting extends StatelessWebexPlugin {
3436
3511
 
3437
3512
  this.transcription = new Transcription(
3438
3513
  webSocketUrl,
3514
+ // @ts-ignore - fix type
3439
3515
  this.webex.sessionId,
3440
3516
  this.members,
3441
3517
  );
@@ -3459,6 +3535,7 @@ export default class Meeting extends StatelessWebexPlugin {
3459
3535
  });
3460
3536
 
3461
3537
  this.monitorTranscriptionSocketConnection();
3538
+ // @ts-ignore - fix type
3462
3539
  this.transcription.connect(this.webex.credentials.supertoken.access_token);
3463
3540
  }
3464
3541
  catch (error) {
@@ -3491,12 +3568,11 @@ export default class Meeting extends StatelessWebexPlugin {
3491
3568
  * @private
3492
3569
  * @returns{void}
3493
3570
  */
3494
- triggerStopReceivingTranscriptionEvent() {
3571
+ private triggerStopReceivingTranscriptionEvent() {
3495
3572
  LoggerProxy.logger.info(`
3496
3573
  Meeting:index#stopReceivingTranscription -->
3497
3574
  closed transcription LLM web socket connection successfully.`);
3498
3575
 
3499
-
3500
3576
  Trigger.trigger(
3501
3577
  this,
3502
3578
  {
@@ -3519,7 +3595,8 @@ export default class Meeting extends StatelessWebexPlugin {
3519
3595
  * if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
3520
3596
  * Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
3521
3597
  */
3522
- join(options = {}) {
3598
+ public join(options: any = {}) {
3599
+ // @ts-ignore - fix type
3523
3600
  if (!this.webex.meetings.registered) {
3524
3601
  const errorMessage = 'Meeting:index#join --> Device not registered';
3525
3602
  const error = new Error(errorMessage);
@@ -3625,6 +3702,8 @@ export default class Meeting extends StatelessWebexPlugin {
3625
3702
  }
3626
3703
  }
3627
3704
 
3705
+ this.isMultistream = !!options.enableMultistream;
3706
+
3628
3707
  return MeetingUtil.joinMeetingOptions(this, options)
3629
3708
  .then((join) => {
3630
3709
  this.meetingFiniteStateMachine.join();
@@ -3643,8 +3722,17 @@ export default class Meeting extends StatelessWebexPlugin {
3643
3722
  );
3644
3723
 
3645
3724
  return join;
3646
- }).then(async (join) => {
3725
+ })
3726
+ .then(async (join) => {
3727
+ if (this.config.enableAutomaticLLM) {
3728
+ await this.updateLLMConnection();
3729
+ }
3730
+
3731
+ return join;
3732
+ })
3733
+ .then(async (join) => {
3647
3734
  if (isBrowser) {
3735
+ // @ts-ignore - config coming from registerPlugin
3648
3736
  if (this.config.receiveTranscription || options.receiveTranscription) {
3649
3737
  if (this.isTranscriptionSupported()) {
3650
3738
  await this.receiveTranscription();
@@ -3656,7 +3744,6 @@ export default class Meeting extends StatelessWebexPlugin {
3656
3744
  LoggerProxy.logger.error('Meeting:index#join --> Receving transcription is not supported on this platform');
3657
3745
  }
3658
3746
 
3659
-
3660
3747
  return join;
3661
3748
  })
3662
3749
  .catch((error) => {
@@ -3702,6 +3789,31 @@ export default class Meeting extends StatelessWebexPlugin {
3702
3789
  });
3703
3790
  }
3704
3791
 
3792
+ /**
3793
+ * Connects to low latency mercury and reconnects if the address has changed
3794
+ * It will also disconnect if called when the meeting has ended
3795
+ * @param {String} datachannelUrl
3796
+ * @returns {Promise}
3797
+ */
3798
+ async updateLLMConnection() {
3799
+ const {url, info: {datachannelUrl} = {}} = this.locusInfo;
3800
+
3801
+ const isJoined = this.joinedWith && this.joinedWith.state === 'JOINED';
3802
+
3803
+ if (this.webex.internal.llm.isConnected()) {
3804
+ if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
3805
+ return undefined;
3806
+ }
3807
+ await this.webex.internal.llm.disconnectLLM();
3808
+ }
3809
+
3810
+ if (!isJoined) {
3811
+ return undefined;
3812
+ }
3813
+
3814
+ return this.webex.internal.llm.registerAndConnect(url, datachannelUrl);
3815
+ }
3816
+
3705
3817
  /**
3706
3818
  * Use phone for meeting audio
3707
3819
  * @param {String} phoneNumber If provided, it will dial-out using this number. If not provided, dial-in will be used
@@ -3709,7 +3821,7 @@ export default class Meeting extends StatelessWebexPlugin {
3709
3821
  * @public
3710
3822
  * @memberof Meeting
3711
3823
  */
3712
- usePhoneAudio(phoneNumber) {
3824
+ public usePhoneAudio(phoneNumber: string) {
3713
3825
  if (!phoneNumber) {
3714
3826
  return this.dialInPstn();
3715
3827
  }
@@ -3724,7 +3836,7 @@ export default class Meeting extends StatelessWebexPlugin {
3724
3836
  * @private
3725
3837
  * @memberof Meeting
3726
3838
  */
3727
- isPhoneProvisioned(pstnStatus) {
3839
+ private isPhoneProvisioned(pstnStatus: string) {
3728
3840
  return [PSTN_STATUS.JOINED, PSTN_STATUS.CONNECTED, PSTN_STATUS.SUCCESS].includes(pstnStatus);
3729
3841
  }
3730
3842
 
@@ -3734,7 +3846,7 @@ export default class Meeting extends StatelessWebexPlugin {
3734
3846
  * @private
3735
3847
  * @memberof Meeting
3736
3848
  */
3737
- dialInPstn() {
3849
+ private dialInPstn() {
3738
3850
  if (this.isPhoneProvisioned(this.dialInDeviceStatus)) return Promise.resolve(); // prevent multiple dial in devices from being provisioned
3739
3851
 
3740
3852
  const {correlationId, locusUrl} = this;
@@ -3772,7 +3884,7 @@ export default class Meeting extends StatelessWebexPlugin {
3772
3884
  * @private
3773
3885
  * @memberof Meeting
3774
3886
  */
3775
- dialOutPstn(phoneNumber) {
3887
+ private dialOutPstn(phoneNumber: string) {
3776
3888
  if (this.isPhoneProvisioned(this.dialOutDeviceStatus)) return Promise.resolve(); // prevent multiple dial out devices from being provisioned
3777
3889
 
3778
3890
  const {correlationId, locusUrl} = this;
@@ -3811,7 +3923,7 @@ export default class Meeting extends StatelessWebexPlugin {
3811
3923
  * @memberof Meeting
3812
3924
  * @returns {Promise}
3813
3925
  */
3814
- disconnectPhoneAudio() {
3926
+ public disconnectPhoneAudio() {
3815
3927
  return Promise.all([
3816
3928
  this.isPhoneProvisioned(this.dialInDeviceStatus) ?
3817
3929
  MeetingUtil.disconnectPhoneAudio(this, this.dialInUrl) :
@@ -3829,7 +3941,7 @@ export default class Meeting extends StatelessWebexPlugin {
3829
3941
  * @public
3830
3942
  * @memberof Meeting
3831
3943
  */
3832
- moveTo(resourceId) {
3944
+ public moveTo(resourceId: string) {
3833
3945
  if (!resourceId) {
3834
3946
  throw new ParameterError('Cannot move call without a resourceId.');
3835
3947
  }
@@ -3864,7 +3976,7 @@ export default class Meeting extends StatelessWebexPlugin {
3864
3976
 
3865
3977
  try {
3866
3978
  if (this.isSharing) {
3867
- await this.stopFloorRequest();
3979
+ await this.releaseScreenShareFloor();
3868
3980
  }
3869
3981
  const mediaSettings = {
3870
3982
  mediaDirection: {
@@ -3889,6 +4001,7 @@ export default class Meeting extends StatelessWebexPlugin {
3889
4001
 
3890
4002
  // when a move to is intiated by the client , Locus delets the existing media node from the server as soon the DX answers the meeting
3891
4003
  // once the DX answers we establish connection back the media server with only receiveShare enabled
4004
+ // @ts-ignore - reconnectMedia does not accept any argument
3892
4005
  await this.reconnectionManager.reconnectMedia(mediaSettings)
3893
4006
  .then(() => {
3894
4007
  Metrics.sendBehavioralMetric(
@@ -3938,7 +4051,7 @@ export default class Meeting extends StatelessWebexPlugin {
3938
4051
  * @public
3939
4052
  * @memberof Meeting
3940
4053
  */
3941
- moveFrom(resourceId) {
4054
+ public moveFrom(resourceId: string) {
3942
4055
  // On moveFrom ask the developer to re capture it moveFrom then updateMedia
3943
4056
  if (!resourceId) {
3944
4057
  throw new ParameterError('Cannot move call without a resourceId.');
@@ -3976,6 +4089,9 @@ export default class Meeting extends StatelessWebexPlugin {
3976
4089
 
3977
4090
  /**
3978
4091
  * Get local media streams based on options passed
4092
+ *
4093
+ * NOTE: this method can only be used with transcoded meetings, not with multistream meetings
4094
+ *
3979
4095
  * @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
3980
4096
  * @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
3981
4097
  * @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
@@ -3985,10 +4101,10 @@ export default class Meeting extends StatelessWebexPlugin {
3985
4101
  * @memberof Meeting
3986
4102
  */
3987
4103
  getMediaStreams = (
3988
- mediaDirection,
4104
+ mediaDirection: any,
3989
4105
  // This return an OBJECT {video: {height, widght}}
3990
- audioVideo = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
3991
- sharePreferences
4106
+ audioVideo: any = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
4107
+ sharePreferences?: any
3992
4108
  ) => {
3993
4109
  if (
3994
4110
  mediaDirection &&
@@ -4065,6 +4181,7 @@ export default class Meeting extends StatelessWebexPlugin {
4065
4181
  },
4066
4182
  audioVideo,
4067
4183
  sharePreferences,
4184
+ // @ts-ignore - config coming from registerPlugin
4068
4185
  this.config
4069
4186
  )
4070
4187
  .catch((error) => {
@@ -4102,7 +4219,7 @@ export default class Meeting extends StatelessWebexPlugin {
4102
4219
  * @returns {Object}
4103
4220
  * @memberof Meetings
4104
4221
  */
4105
- getSupportedDevices = ({sendAudio = true, sendVideo = true}) => Media.getSupportedDevice({sendAudio, sendVideo});
4222
+ getSupportedDevices = ({ sendAudio = true, sendVideo = true }: { sendAudio: boolean; sendVideo: boolean }) => Media.getSupportedDevice({ sendAudio, sendVideo });
4106
4223
 
4107
4224
  /**
4108
4225
  * Get the devices from the Media module
@@ -4111,6 +4228,344 @@ export default class Meeting extends StatelessWebexPlugin {
4111
4228
  */
4112
4229
  getDevices = () => Media.getDevices();
4113
4230
 
4231
+ /**
4232
+ * Handles ROAP_FAILURE event from the webrtc media connection
4233
+ *
4234
+ * @param {Error} error
4235
+ * @returns {void}
4236
+ */
4237
+ handleRoapFailure = (error) => {
4238
+ const sendBehavioralMetric = (metricName, error, correlationId) => {
4239
+ const data = {
4240
+ code: error.code,
4241
+ correlation_id: correlationId,
4242
+ reason: error.message,
4243
+ stack: error.stack
4244
+ };
4245
+ const metadata = {
4246
+ type: error.cause?.name || error.name
4247
+ };
4248
+
4249
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4250
+ };
4251
+
4252
+
4253
+ if (error instanceof MC.Errors.SdpOfferCreationError) {
4254
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4255
+
4256
+ Metrics.postEvent({
4257
+ event: eventType.LOCAL_SDP_GENERATED,
4258
+ meetingId: this.id,
4259
+ data: {
4260
+ canProceed: false,
4261
+ errors: [
4262
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4263
+ }
4264
+ });
4265
+ }
4266
+ else if ((error instanceof MC.Errors.SdpOfferHandlingError) || (error instanceof MC.Errors.SdpAnswerHandlingError)) {
4267
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4268
+
4269
+ Metrics.postEvent({
4270
+ event: eventType.REMOTE_SDP_RECEIVED,
4271
+ meetingId: this.id,
4272
+ data: {
4273
+ canProceed: false,
4274
+ errors: [Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4275
+ }
4276
+ });
4277
+ }
4278
+ else if (error instanceof MC.Errors.SdpError) { // this covers also the case of MC.Errors.IceGatheringError which extends MC.Errors.SdpError
4279
+ sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
4280
+
4281
+ Metrics.postEvent({
4282
+ event: eventType.LOCAL_SDP_GENERATED,
4283
+ meetingId: this.id,
4284
+ data: {
4285
+ canProceed: false,
4286
+ errors: [
4287
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4288
+ }
4289
+ });
4290
+ }
4291
+ };
4292
+
4293
+ setupMediaConnectionListeners = () => {
4294
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_STARTED, () => {
4295
+ this.isRoapInProgress = true;
4296
+ });
4297
+
4298
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_DONE, () => {
4299
+ this.mediaNegotiatedEvent();
4300
+ this.isRoapInProgress = false;
4301
+ this.processNextQueuedMediaUpdate();
4302
+ });
4303
+
4304
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_FAILURE, this.handleRoapFailure);
4305
+
4306
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_MESSAGE_TO_SEND, (event) => {
4307
+ const LOG_HEADER = 'Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND -->';
4308
+
4309
+ switch (event.roapMessage.messageType) {
4310
+ case 'OK':
4311
+ Metrics.postEvent({
4312
+ event: eventType.REMOTE_SDP_RECEIVED,
4313
+ meetingId: this.id,
4314
+ });
4315
+
4316
+ logRequest(this.roap.sendRoapOK({
4317
+ seq: event.roapMessage.seq,
4318
+ mediaId: this.mediaId,
4319
+ correlationId: this.correlationId
4320
+ }), {
4321
+ header: `${LOG_HEADER} Send Roap OK`,
4322
+ success: `${LOG_HEADER} Successfully send roap OK`,
4323
+ failure: `${LOG_HEADER} Error joining the call on send roap OK, `
4324
+ });
4325
+ break;
4326
+
4327
+ case 'OFFER':
4328
+ Metrics.postEvent({
4329
+ event: eventType.LOCAL_SDP_GENERATED,
4330
+ meetingId: this.id,
4331
+ });
4332
+
4333
+ logRequest(this.roap
4334
+ .sendRoapMediaRequest({
4335
+ sdp: event.roapMessage.sdp,
4336
+ seq: event.roapMessage.seq,
4337
+ tieBreaker: event.roapMessage.tieBreaker,
4338
+ meeting: this, // or can pass meeting ID
4339
+ reconnect: this.reconnectionManager.isReconnectInProgress()
4340
+ }), {
4341
+ header: `${LOG_HEADER} Send Roap Offer`,
4342
+ success: `${LOG_HEADER} Successfully send roap offer`,
4343
+ failure: `${LOG_HEADER} Error joining the call on send roap offer, `
4344
+ });
4345
+ break;
4346
+
4347
+ case 'ANSWER':
4348
+ Metrics.postEvent({
4349
+ event: eventType.REMOTE_SDP_RECEIVED,
4350
+ meetingId: this.id,
4351
+ });
4352
+
4353
+ logRequest(this.roap.sendRoapAnswer({
4354
+ sdp: event.roapMessage.sdp,
4355
+ seq: event.roapMessage.seq,
4356
+ mediaId: this.mediaId,
4357
+ correlationId: this.correlationId
4358
+ }), {
4359
+ header: `${LOG_HEADER} Send Roap Answer.`,
4360
+ success: `${LOG_HEADER} Successfully send roap answer`,
4361
+ failure: `${LOG_HEADER} Error joining the call on send roap answer, `
4362
+ })
4363
+ .catch((error) => {
4364
+ const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
4365
+ const data = {
4366
+ correlation_id: this.correlationId,
4367
+ locus_id: this.locusUrl.split('/').pop(),
4368
+ reason: error.message,
4369
+ stack: error.stack
4370
+ };
4371
+ const metadata = {
4372
+ type: error.name
4373
+ };
4374
+
4375
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4376
+ });
4377
+ break;
4378
+
4379
+ case 'ERROR':
4380
+ if (event.roapMessage.errorType === MC.ErrorType.CONFLICT || event.roapMessage.errorType === MC.ErrorType.DOUBLECONFLICT) {
4381
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
4382
+ correlation_id: this.correlationId,
4383
+ locus_id: this.locusUrl.split('/').pop(),
4384
+ sequence: event.roapMessage.seq
4385
+ });
4386
+ }
4387
+ logRequest(this.roap.sendRoapError({
4388
+ seq: event.roapMessage.seq,
4389
+ errorType: event.roapMessage.errorType,
4390
+ mediaId: this.mediaId,
4391
+ correlationId: this.correlationId
4392
+ }), {
4393
+ header: `${LOG_HEADER} Send Roap Error.`,
4394
+ success: `${LOG_HEADER} Successfully send roap error`,
4395
+ failure: `${LOG_HEADER} Failed to send roap error, `
4396
+ });
4397
+ break;
4398
+
4399
+ default:
4400
+ LoggerProxy.logger.error(`${LOG_HEADER} Unsupported message type: ${event.roapMessage.messageType}`);
4401
+ break;
4402
+ }
4403
+ });
4404
+
4405
+ // eslint-disable-next-line no-param-reassign
4406
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.REMOTE_TRACK_ADDED, (event) => {
4407
+ LoggerProxy.logger.log(`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(event)}`);
4408
+
4409
+ const mediaTrack = event.track;
4410
+
4411
+ let eventType;
4412
+
4413
+ switch (event.type) {
4414
+ case MC.RemoteTrackType.AUDIO:
4415
+ eventType = EVENT_TYPES.REMOTE_AUDIO;
4416
+ this.mediaProperties.setRemoteAudioTrack(event.track);
4417
+ break;
4418
+ case MC.RemoteTrackType.VIDEO:
4419
+ eventType = EVENT_TYPES.REMOTE_VIDEO;
4420
+ this.mediaProperties.setRemoteVideoTrack(event.track);
4421
+ break;
4422
+ case MC.RemoteTrackType.SCREENSHARE_VIDEO:
4423
+ if (event.track) {
4424
+ eventType = EVENT_TYPES.REMOTE_SHARE;
4425
+ this.mediaProperties.setRemoteShare(event.track);
4426
+ }
4427
+ break;
4428
+ default: {
4429
+ LoggerProxy.logger.log('Meeting:index#setupMediaConnectionListeners --> unexpected track');
4430
+ }
4431
+ }
4432
+
4433
+ // start stats here the stats are coming null if you dont receive streams
4434
+
4435
+ this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
4436
+
4437
+ if (eventType && mediaTrack) {
4438
+ Trigger.trigger(
4439
+ this,
4440
+ {
4441
+ file: 'meeting/index',
4442
+ function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED'
4443
+ },
4444
+ EVENT_TRIGGERS.MEDIA_READY,
4445
+ {
4446
+ type: eventType,
4447
+ stream: MediaUtil.createMediaStream([mediaTrack])
4448
+ }
4449
+ );
4450
+ }
4451
+ });
4452
+
4453
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.CONNECTION_STATE_CHANGED, (event) => {
4454
+ const connectionFailed = () => {
4455
+ // we know the media connection failed and browser will not attempt to recover it any more
4456
+ // so reset the timer as it's not needed anymore, we want to reconnect immediately
4457
+ this.reconnectionManager.resetReconnectionTimer();
4458
+
4459
+ this.reconnect({networkDisconnect: true});
4460
+ Metrics.postEvent({
4461
+ event: eventType.ICE_END,
4462
+ meeting: this,
4463
+ data: {
4464
+ canProceed: false,
4465
+ errors: [
4466
+ Metrics.generateErrorPayload(
4467
+ 2004, false, MetricsError.name.MEDIA_ENGINE, undefined
4468
+ )]
4469
+ }
4470
+ });
4471
+
4472
+ this.uploadLogs({
4473
+ file: 'peer-connection-manager/index',
4474
+ function: 'connectionFailed'
4475
+ });
4476
+
4477
+ Metrics.sendBehavioralMetric(
4478
+ BEHAVIORAL_METRICS.CONNECTION_FAILURE,
4479
+ {
4480
+ correlation_id: this.correlationId,
4481
+ locus_id: this.locusId
4482
+ }
4483
+ );
4484
+ };
4485
+
4486
+ LoggerProxy.logger.info(`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`);
4487
+ switch (event.state) {
4488
+ case MC.ConnectionState.Connecting:
4489
+ Metrics.postEvent({event: eventType.ICE_START, meeting: this});
4490
+ break;
4491
+ case MC.ConnectionState.Connected:
4492
+ Metrics.postEvent({event: eventType.ICE_END, meeting: this});
4493
+ Metrics.sendBehavioralMetric(
4494
+ BEHAVIORAL_METRICS.CONNECTION_SUCCESS,
4495
+ {
4496
+ correlation_id: this.correlationId,
4497
+ locus_id: this.locusId
4498
+ }
4499
+ );
4500
+ this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
4501
+ this.reconnectionManager.iceReconnected();
4502
+ break;
4503
+ case MC.ConnectionState.Disconnected:
4504
+ this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
4505
+ this.reconnectionManager.waitForIceReconnect()
4506
+ .catch(() => {
4507
+ LoggerProxy.logger.info('Meeting:index#setupMediaConnectionListeners --> state DISCONNECTED, automatic reconnection timed out.');
4508
+
4509
+ connectionFailed();
4510
+ });
4511
+ break;
4512
+ case MC.ConnectionState.Failed:
4513
+ connectionFailed();
4514
+ break;
4515
+ default:
4516
+ break;
4517
+ }
4518
+ });
4519
+
4520
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ACTIVE_SPEAKERS_CHANGED,
4521
+ (msg) => {
4522
+ Trigger.trigger(
4523
+ this,
4524
+ {
4525
+ file: 'meeting/index',
4526
+ function: 'setupMediaConnectionListeners'
4527
+ },
4528
+ EVENT_TRIGGERS.ACTIVE_SPEAKER_CHANGED,
4529
+ {
4530
+ seqNum: msg.seqNum,
4531
+ memberIds: msg.csis.map((csi) => this.members.findMemberByCsi(csi)?.id).filter((item) => (item !== undefined))
4532
+ }
4533
+ );
4534
+ });
4535
+
4536
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.VIDEO_SOURCES_COUNT_CHANGED,
4537
+ (numTotalSources, numLiveSources) => {
4538
+ Trigger.trigger(
4539
+ this,
4540
+ {
4541
+ file: 'meeting/index',
4542
+ function: 'setupMediaConnectionListeners'
4543
+ },
4544
+ EVENT_TRIGGERS.REMOTE_VIDEO_SOURCE_COUNT_CHANGED,
4545
+ {
4546
+ numTotalSources,
4547
+ numLiveSources
4548
+ }
4549
+ );
4550
+ });
4551
+
4552
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.AUDIO_SOURCES_COUNT_CHANGED,
4553
+ (numTotalSources, numLiveSources) => {
4554
+ Trigger.trigger(
4555
+ this,
4556
+ {
4557
+ file: 'meeting/index',
4558
+ function: 'setupMediaConnectionListeners'
4559
+ },
4560
+ EVENT_TRIGGERS.REMOTE_AUDIO_SOURCE_COUNT_CHANGED,
4561
+ {
4562
+ numTotalSources,
4563
+ numLiveSources
4564
+ }
4565
+ );
4566
+ });
4567
+ };
4568
+
4114
4569
  /**
4115
4570
  * Registers for all required StatsAnalyzer events
4116
4571
  * @private
@@ -4121,6 +4576,7 @@ export default class Meeting extends StatelessWebexPlugin {
4121
4576
  this.statsAnalyzer.on(StatsAnalyzerEvents.MEDIA_QUALITY, (options) => {
4122
4577
  // TODO: might have to send the same event to the developer
4123
4578
  // Add ip address info if geo hint is present
4579
+ // @ts-ignore fix type
4124
4580
  options.data.intervalMetadata.peerReflexiveIP = this.webex.meetings.geoHintInfo?.clientAddress || options.data.intervalMetadata.peerReflexiveIP || MQA_STATS.DEFAULT_IP;
4125
4581
  Metrics.postEvent({event: eventType.MEDIA_QUALITY, meeting: this, data: {intervalData: options.data, networkType: options.networkType}});
4126
4582
  });
@@ -4180,6 +4636,52 @@ export default class Meeting extends StatelessWebexPlugin {
4180
4636
  });
4181
4637
  };
4182
4638
 
4639
+ getMediaConnectionDebugId() {
4640
+ return `MC-${this.id.substring(0, 4)}`;
4641
+ }
4642
+
4643
+ createMediaConnection(turnServerInfo) {
4644
+ const mc = Media.createMediaConnection(
4645
+ this.isMultistream,
4646
+ this.getMediaConnectionDebugId(),
4647
+ {
4648
+ mediaProperties: this.mediaProperties,
4649
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4650
+ // @ts-ignore - config coming from registerPlugin
4651
+ enableRtx: this.config.enableRtx,
4652
+ // @ts-ignore - config coming from registerPlugin
4653
+ enableExtmap: this.config.enableExtmap,
4654
+ turnServerInfo
4655
+ }
4656
+ );
4657
+
4658
+ this.mediaProperties.setMediaPeerConnection(mc);
4659
+ this.setupMediaConnectionListeners();
4660
+
4661
+ return mc;
4662
+ }
4663
+
4664
+ /**
4665
+ * Listens for an event emitted by eventEmitter and emits it from the meeting object
4666
+ *
4667
+ * @private
4668
+ * @param {*} eventEmitter object from which to forward the event
4669
+ * @param {*} eventTypeToForward which event type to listen on and to forward
4670
+ * @param {string} meetingEventType event type to be used in the event emitted from the meeting object
4671
+ * @returns {void}
4672
+ */
4673
+ forwardEvent(eventEmitter, eventTypeToForward, meetingEventType) {
4674
+ eventEmitter.on(eventTypeToForward, (data) => Trigger.trigger(
4675
+ this,
4676
+ {
4677
+ file: 'meetings',
4678
+ function: 'addMedia'
4679
+ },
4680
+ meetingEventType,
4681
+ data
4682
+ ));
4683
+ }
4684
+
4183
4685
  /**
4184
4686
  * Specify joining via audio (option: pstn), video, screenshare
4185
4687
  * @param {Object} options A configurable options object for joining a meeting
@@ -4187,11 +4689,12 @@ export default class Meeting extends StatelessWebexPlugin {
4187
4689
  * @param {MediaDirection} options.mediaSettings pass media options
4188
4690
  * @param {MediaStream} options.localStream
4189
4691
  * @param {MediaStream} options.localShare
4692
+ * @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
4190
4693
  * @returns {Promise}
4191
4694
  * @public
4192
4695
  * @memberof Meeting
4193
4696
  */
4194
- addMedia(options = {}) {
4697
+ addMedia(options: any = {}) {
4195
4698
  const LOG_HEADER = 'Meeting:index#addMedia -->';
4196
4699
 
4197
4700
  let turnDiscoverySkippedReason;
@@ -4205,11 +4708,14 @@ export default class Meeting extends StatelessWebexPlugin {
4205
4708
  return Promise.reject(new UserNotJoinedError());
4206
4709
  }
4207
4710
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
4711
+ // @ts-ignore - isUserUnadmitted coming from SelfUtil
4208
4712
  if (this.isUserUnadmitted && !this.wirelessShare) {
4209
4713
  return Promise.reject(new UserInLobbyError());
4210
4714
  }
4211
4715
 
4212
- const {localStream, localShare, mediaSettings} = options;
4716
+ const {
4717
+ localStream, localShare, mediaSettings, remoteMediaManagerConfig
4718
+ } = options;
4213
4719
 
4214
4720
  LoggerProxy.logger.info(`${LOG_HEADER} Adding Media.`);
4215
4721
 
@@ -4244,145 +4750,112 @@ export default class Meeting extends StatelessWebexPlugin {
4244
4750
 
4245
4751
  const {turnServerInfo} = turnDiscoveryObject;
4246
4752
 
4247
- this.mediaProperties.setMediaPeerConnection(MediaUtil.createPeerConnection(turnServerInfo));
4248
- this.setMercuryListener();
4249
- PeerConnectionManager.setPeerConnectionEvents(this);
4753
+ this.preMedia(localStream, localShare, mediaSettings);
4250
4754
 
4251
- return this.preMedia(localStream, localShare, mediaSettings);
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);
4262
-
4263
- return peerConnection;
4264
- }))
4265
- .then((peerConnection) => {
4266
- this.handleMediaLogging(this.mediaProperties);
4267
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection Received from attachMedia `);
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);
4755
+ const mc = this.createMediaConnection(turnServerInfo);
4280
4756
 
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
- }
4757
+ if (this.isMultistream) {
4758
+ this.remoteMediaManager = new RemoteMediaManager(
4759
+ this.receiveSlotManager,
4760
+ this.mediaRequestManagers,
4761
+ remoteMediaManagerConfig
4291
4762
  );
4292
4763
 
4293
- throw error;
4294
- })
4295
- .then(() => new Promise((resolve, reject) => {
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
- }
4764
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.AudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED);
4765
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.ScreenShareAudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED);
4766
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.VideoLayoutChanged, EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED);
4309
4767
 
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;
4329
-
4330
- return new Promise((resolve, reject) => {
4331
- if (peerConnection.connectionState === CONNECTION_STATE.CONNECTED) {
4332
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
4768
+ return this.remoteMediaManager.start()
4769
+ .then(() => mc.initiateOffer());
4770
+ }
4333
4771
 
4334
- resolve(peerConnection);
4772
+ return mc.initiateOffer();
4773
+ })
4774
+ .then(() => {
4775
+ this.setMercuryListener();
4776
+ })
4777
+ .then(() => this.getDevices().then((devices) => {
4778
+ MeetingUtil.handleDeviceLogging(devices);
4779
+ }))
4780
+ .then(() => {
4781
+ this.handleMediaLogging(this.mediaProperties);
4782
+ LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
4783
+
4784
+ // @ts-ignore - config coming from registerPlugin
4785
+ if (this.config.stats.enableStatsAnalyzer) {
4786
+ // @ts-ignore - config coming from registerPlugin
4787
+ this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
4788
+ // @ts-ignore - config coming from registerPlugin
4789
+ this.statsAnalyzer = new StatsAnalyzer(this.config.stats, this.networkQualityMonitor);
4790
+ this.setupStatsAnalyzerEventHandlers();
4791
+ this.networkQualityMonitor.on(EVENT_TRIGGERS.NETWORK_QUALITY, this.sendNetworkQualityEvent.bind(this));
4792
+ }
4793
+ })
4794
+ .catch((error) => {
4795
+ LoggerProxy.logger.error(`${LOG_HEADER} Error adding media , setting up peerconnection, `, error);
4335
4796
 
4336
- return;
4337
- }
4338
- // Check if Peer Connection is STABLE (connected)
4339
- const stabilityTimeout = setTimeout(() => {
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);
4797
+ throw error;
4798
+ })
4799
+ .then(() => new Promise<void>((resolve, reject) => {
4800
+ let timerCount = 0;
4349
4801
 
4350
- this.once(EVENT_TRIGGERS.MEDIA_READY, () => {
4351
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED, clearing stability timer.`);
4352
- clearTimeout(stabilityTimeout);
4353
- resolve(peerConnection);
4354
- });
4355
- });
4356
- })
4357
- .then(() => {
4358
- if (mediaSettings && mediaSettings.sendShare && localShare) {
4359
- if (this.state === MEETING_STATE.STATES.JOINED) {
4360
- return this.share();
4361
- }
4802
+ // eslint-disable-next-line func-names
4803
+ // eslint-disable-next-line prefer-arrow-callback
4804
+ if (this.type === _CALL_) {
4805
+ resolve();
4806
+ }
4807
+ const joiningTimer = setInterval(() => {
4808
+ timerCount += 1;
4809
+ if (this.meetingState === FULL_STATE.ACTIVE) {
4810
+ clearInterval(joiningTimer);
4811
+ resolve();
4812
+ }
4362
4813
 
4363
- // When the self state changes to JOINED then request the floor
4364
- this.floorGrantPending = true;
4814
+ if (timerCount === 4) {
4815
+ clearInterval(joiningTimer);
4816
+ reject(new Error('Meeting is still not active '));
4817
+ }
4818
+ }, 1000);
4819
+ }))
4820
+ .then(
4821
+ () => this.mediaProperties.waitForMediaConnectionConnected()
4822
+ .catch(() => {
4823
+ throw createMeetingsError(30202, 'Meeting connection failed');
4824
+ })
4825
+ )
4826
+ .then(() => {
4827
+ LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
4828
+ if (mediaSettings && mediaSettings.sendShare && localShare) {
4829
+ if (this.state === MEETING_STATE.STATES.JOINED) {
4830
+ return this.requestScreenShareFloor();
4365
4831
  }
4366
4832
 
4367
- Metrics.sendBehavioralMetric(
4368
- BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
4369
- {
4370
- correlation_id: this.correlationId,
4371
- locus_id: this.locusUrl.split('/').pop()
4372
- }
4373
- );
4833
+ // When the self state changes to JOINED then request the floor
4834
+ this.floorGrantPending = true;
4835
+ }
4374
4836
 
4375
- return Promise.resolve();
4376
- }))
4837
+ return {};
4838
+ })
4839
+ .then(() => this.mediaProperties.getCurrentConnectionType())
4840
+ .then((connectionType) => {
4841
+ Metrics.sendBehavioralMetric(
4842
+ BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS,
4843
+ {
4844
+ correlation_id: this.correlationId,
4845
+ locus_id: this.locusUrl.split('/').pop(),
4846
+ connectionType
4847
+ }
4848
+ );
4849
+ })
4377
4850
  .catch((error) => {
4378
4851
  // Clean up stats analyzer, peer connection, and turn off listeners
4379
4852
  const stopStatsAnalyzer = (this.statsAnalyzer) ? this.statsAnalyzer.stopAnalyzer() : Promise.resolve();
4380
4853
 
4381
- stopStatsAnalyzer
4854
+ return stopStatsAnalyzer
4382
4855
  .then(() => {
4383
4856
  this.statsAnalyzer = null;
4384
4857
 
4385
- if (this.mediaProperties.peerConnection) {
4858
+ if (this.mediaProperties.webrtcMediaConnection) {
4386
4859
  this.closePeerConnections();
4387
4860
  this.unsetPeerConnections();
4388
4861
  }
@@ -4413,10 +4886,7 @@ export default class Meeting extends StatelessWebexPlugin {
4413
4886
  this
4414
4887
  );
4415
4888
 
4416
- // If addMedia failes for not establishing connection then
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) {
4889
+ if (error instanceof MC.Errors.SdpError) {
4420
4890
  this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
4421
4891
  }
4422
4892
 
@@ -4430,7 +4900,10 @@ export default class Meeting extends StatelessWebexPlugin {
4430
4900
  * @returns {Boolean}
4431
4901
  */
4432
4902
  canUpdateMedia() {
4433
- return this.mediaProperties.peerConnection.signalingState === SDP.STABLE && !RoapCollection.isBusy(this.correlationId);
4903
+ // in theory we shouldn't need this as RoapMediaConnection handles multiple updates, glare, etc,
4904
+ // but there are some server issues, like https://jira-eng-gpk2.cisco.com/jira/browse/WEBEX-248394
4905
+ // so for now it's better to keep queuing any media updates at SDK meeting level
4906
+ return !this.isRoapInProgress;
4434
4907
  }
4435
4908
 
4436
4909
  /**
@@ -4441,7 +4914,7 @@ export default class Meeting extends StatelessWebexPlugin {
4441
4914
  * @private
4442
4915
  * @memberof Meeting
4443
4916
  */
4444
- enqueueMediaUpdate(mediaUpdateType, options) {
4917
+ private enqueueMediaUpdate(mediaUpdateType: string, options: object) {
4445
4918
  return new Promise((resolve, reject) => {
4446
4919
  const queueItem = {
4447
4920
  pendingPromiseResolve: resolve, pendingPromiseReject: reject, mediaUpdateType, options
@@ -4459,6 +4932,7 @@ export default class Meeting extends StatelessWebexPlugin {
4459
4932
  * @memberof Meeting
4460
4933
  */
4461
4934
  mediaNegotiatedEvent = () => {
4935
+ // @ts-ignore - config coming from registerPlugin
4462
4936
  if (this.config.experimental.enableMediaNegotiatedEvent) {
4463
4937
  LoggerProxy.logger.info('Meeting:mediaNegotiatedEvent --> Media server negotiated');
4464
4938
  Trigger.trigger(
@@ -4514,11 +4988,16 @@ export default class Meeting extends StatelessWebexPlugin {
4514
4988
  * @param {MediaStream} options.localShare
4515
4989
  * @param {MediaDirection} options.mediaSettings
4516
4990
  * @returns {Promise}
4517
- * @todo fix setRemoteStream for updateMedia
4518
4991
  * @public
4519
4992
  * @memberof Meeting
4520
4993
  */
4521
- updateMedia(options = {}) {
4994
+ public updateMedia(
4995
+ options: {
4996
+ localStream?: MediaStream;
4997
+ localShare?: MediaStream;
4998
+ mediaSettings?: any;
4999
+ } = {} as any
5000
+ ) {
4522
5001
  const LOG_HEADER = 'Meeting:index#updateMedia -->';
4523
5002
 
4524
5003
  if (!this.canUpdateMedia()) {
@@ -4530,20 +5009,27 @@ export default class Meeting extends StatelessWebexPlugin {
4530
5009
 
4531
5010
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4532
5011
 
5012
+ if (!this.mediaProperties.webrtcMediaConnection) {
5013
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5014
+ }
5015
+
4533
5016
  return MeetingUtil.validateOptions(options)
4534
5017
  .then(() => this.preMedia(localStream, localShare, mediaSettings))
4535
- .then(() => Media.updateMedia(this.mediaProperties, {
4536
- meetingId: this.id,
4537
- remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4538
- enableRtx: this.config.enableRtx,
4539
- enableExtmap: this.config.enableExtmap,
5018
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5019
+ send: {
5020
+ audio: this.mediaProperties.mediaDirection.sendAudio ? this.mediaProperties.audioTrack : null,
5021
+ video: this.mediaProperties.mediaDirection.sendVideo ? this.mediaProperties.videoTrack : null,
5022
+ screenShareVideo: this.mediaProperties.mediaDirection.sendShare ? this.mediaProperties.shareTrack : null
5023
+ },
5024
+ receive: {
5025
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5026
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5027
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5028
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
5029
+ }
4540
5030
  })
4541
- .then((peerConnection) => {
4542
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection received from updateMedia, ${peerConnection}`);
4543
- this.setRemoteStream(peerConnection);
4544
- if (mediaSettings.receiveShare || localShare) {
4545
- PeerConnectionManager.setContentSlides(peerConnection);
4546
- }
5031
+ .then(() => {
5032
+ LoggerProxy.logger.info(`${LOG_HEADER} webrtcMediaConnection.updateSendReceiveOptions done`);
4547
5033
  })
4548
5034
  .catch((error) => {
4549
5035
  LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
@@ -4560,25 +5046,17 @@ export default class Meeting extends StatelessWebexPlugin {
4560
5046
 
4561
5047
  throw error;
4562
5048
  })
4563
- .then(() =>
4564
- logRequest(this.roap
4565
- .sendRoapMediaRequest({
4566
- sdp: this.mediaProperties.peerConnection.sdp,
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
- }))
5049
+ // todo: the following code used to be called always after sending the roap message with the new SDP
5050
+ // now it's called independently from the roap message (so might be before it), check if that's OK
5051
+ // if not, ensure it's called after (now it's called after roap message is sent out, but we're not
5052
+ // waiting for sendRoapMediaRequest() to be resolved)
4575
5053
  .then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
4576
5054
  .then((startShare) => {
4577
5055
  // This is a special case if we do an /floor grant followed by /media
4578
5056
  // we actually get a OFFER from the server and a GLAR condition happens
4579
5057
  if (startShare) {
4580
5058
  // We are assuming that the clients are connected when doing an update
4581
- return this.share();
5059
+ return this.requestScreenShareFloor();
4582
5060
  }
4583
5061
 
4584
5062
  return Promise.resolve();
@@ -4587,6 +5065,9 @@ export default class Meeting extends StatelessWebexPlugin {
4587
5065
 
4588
5066
  /**
4589
5067
  * Update the main audio track with new parameters
5068
+ *
5069
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5070
+ *
4590
5071
  * @param {Object} options
4591
5072
  * @param {boolean} options.sendAudio
4592
5073
  * @param {boolean} options.receiveAudio
@@ -4595,21 +5076,21 @@ export default class Meeting extends StatelessWebexPlugin {
4595
5076
  * @public
4596
5077
  * @memberof Meeting
4597
5078
  */
4598
- async updateAudio(options) {
5079
+ public async updateAudio(options: { sendAudio: boolean; receiveAudio: boolean; stream: MediaStream }) {
4599
5080
  if (!this.canUpdateMedia()) {
4600
5081
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
4601
5082
  }
4602
- const {
4603
- sendAudio, receiveAudio, stream
4604
- } = options;
4605
-
4606
- const {audioTransceiver} = this.mediaProperties.peerConnection;
5083
+ const {sendAudio, receiveAudio, stream} = options;
4607
5084
  let track = MeetingUtil.getTrack(stream).audioTrack;
4608
5085
 
4609
5086
  if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
4610
5087
  return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
4611
5088
  }
4612
5089
 
5090
+ if (!this.mediaProperties.webrtcMediaConnection) {
5091
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5092
+ }
5093
+
4613
5094
  if (this.effects && this.effects.state) {
4614
5095
  const bnrEnabled = this.effects.state.bnr.enabled;
4615
5096
 
@@ -4621,38 +5102,18 @@ export default class Meeting extends StatelessWebexPlugin {
4621
5102
  }
4622
5103
 
4623
5104
  return MeetingUtil.validateOptions({sendAudio, localStream: stream})
4624
- .then(() => {
4625
- let previousMediaDirection = {};
4626
-
4627
- if (this.mediaProperties.mediaDirection) {
4628
- previousMediaDirection = {
4629
- sendTrack: this.mediaProperties.mediaDirection.sendAudio,
4630
- receiveTrack: this.mediaProperties.mediaDirection.receiveAudio
4631
- };
4632
- }
4633
- else {
4634
- this.mediaProperties.mediaDirection = {};
5105
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5106
+ send: {audio: track},
5107
+ receive: {
5108
+ audio: options.receiveAudio,
5109
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5110
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5111
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4635
5112
  }
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
- })
5113
+ }))
4654
5114
  .then(() => {
4655
5115
  this.setLocalAudioTrack(track);
5116
+ // todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
4656
5117
  this.mediaProperties.mediaDirection.sendAudio = sendAudio;
4657
5118
  this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
4658
5119
 
@@ -4663,6 +5124,9 @@ export default class Meeting extends StatelessWebexPlugin {
4663
5124
 
4664
5125
  /**
4665
5126
  * Update the main video track with new parameters
5127
+ *
5128
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5129
+ *
4666
5130
  * @param {Object} options
4667
5131
  * @param {boolean} options.sendVideo
4668
5132
  * @param {boolean} options.receiveVideo
@@ -4671,35 +5135,30 @@ export default class Meeting extends StatelessWebexPlugin {
4671
5135
  * @public
4672
5136
  * @memberof Meeting
4673
5137
  */
4674
- updateVideo(options) {
5138
+ public updateVideo(options: { sendVideo: boolean; receiveVideo: boolean; stream: MediaStream }) {
4675
5139
  if (!this.canUpdateMedia()) {
4676
5140
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
4677
5141
  }
4678
5142
  const {sendVideo, receiveVideo, stream} = options;
4679
- const {videoTransceiver} = this.mediaProperties.peerConnection;
4680
5143
  const track = MeetingUtil.getTrack(stream).videoTrack;
4681
5144
 
4682
5145
  if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
4683
5146
  return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
4684
5147
  }
4685
5148
 
5149
+ if (!this.mediaProperties.webrtcMediaConnection) {
5150
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5151
+ }
5152
+
4686
5153
  return MeetingUtil.validateOptions({sendVideo, localStream: stream})
4687
- .then(() => MeetingUtil.updateTransceiver({
4688
- type: 'video',
4689
- sendTrack: options.sendVideo,
4690
- receiveTrack: options.receiveVideo,
4691
- track,
4692
- transceiver: videoTransceiver,
4693
- peerConnection: this.mediaProperties.peerConnection,
4694
- previousMediaDirection: {
4695
- sendTrack: this.mediaProperties.mediaDirection.sendVideo,
4696
- receiveTrack: this.mediaProperties.mediaDirection.receiveVideo
5154
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5155
+ send: {video: track},
5156
+ receive: {
5157
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5158
+ video: options.receiveVideo,
5159
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5160
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4697
5161
  }
4698
- },
4699
- {
4700
- mediaProperties: this.mediaProperties,
4701
- meeting: this,
4702
- id: this.id
4703
5162
  }))
4704
5163
  .then(() => {
4705
5164
  this.setLocalVideoTrack(track);
@@ -4719,7 +5178,7 @@ export default class Meeting extends StatelessWebexPlugin {
4719
5178
  * @private
4720
5179
  * @memberof Meeting
4721
5180
  */
4722
- checkForStopShare(sendShare, previousShareStatus) {
5181
+ private checkForStopShare(sendShare: boolean, previousShareStatus: boolean) {
4723
5182
  if (sendShare && !previousShareStatus) {
4724
5183
  // When user starts sharing
4725
5184
  return Promise.resolve(true);
@@ -4727,7 +5186,7 @@ export default class Meeting extends StatelessWebexPlugin {
4727
5186
 
4728
5187
  if (!sendShare && previousShareStatus) {
4729
5188
  // When user stops sharing
4730
- return this.stopFloorRequest()
5189
+ return this.releaseScreenShareFloor()
4731
5190
  .then(() => Promise.resolve(false));
4732
5191
  }
4733
5192
 
@@ -4736,6 +5195,9 @@ export default class Meeting extends StatelessWebexPlugin {
4736
5195
 
4737
5196
  /**
4738
5197
  * Update the share streams, can be used to start sharing
5198
+ *
5199
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5200
+ *
4739
5201
  * @param {Object} options
4740
5202
  * @param {boolean} options.sendShare
4741
5203
  * @param {boolean} options.receiveShare
@@ -4743,43 +5205,39 @@ export default class Meeting extends StatelessWebexPlugin {
4743
5205
  * @public
4744
5206
  * @memberof Meeting
4745
5207
  */
4746
- updateShare(options) {
5208
+ public updateShare(options: { sendShare?: boolean; receiveShare?: boolean, stream?: any, skipSignalingCheck?: boolean }) {
4747
5209
  if (!options.skipSignalingCheck && !this.canUpdateMedia()) {
4748
5210
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
4749
5211
  }
4750
5212
  const {sendShare, receiveShare, stream} = options;
4751
- const {shareTransceiver} = this.mediaProperties.peerConnection;
4752
5213
  const track = MeetingUtil.getTrack(stream).videoTrack;
4753
5214
 
4754
5215
  if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
4755
5216
  return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
4756
5217
  }
5218
+
5219
+ if (!this.mediaProperties.webrtcMediaConnection) {
5220
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5221
+ }
5222
+
4757
5223
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4758
5224
 
4759
5225
  this.setLocalShareTrack(stream);
4760
5226
 
4761
5227
  return MeetingUtil.validateOptions({sendShare, localShare: stream})
4762
5228
  .then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
4763
- .then((startShare) => MeetingUtil.updateTransceiver({
4764
- type: 'video',
4765
- sendTrack: sendShare,
4766
- receiveTrack: receiveShare,
4767
- track,
4768
- transceiver: shareTransceiver,
4769
- peerConnection: this.mediaProperties.peerConnection,
4770
- previousMediaDirection: {
4771
- sendTrack: this.mediaProperties.mediaDirection.sendShare,
4772
- receiveTrack: this.mediaProperties.mediaDirection.receiveShare
5229
+ .then((startShare) => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5230
+ send: {screenShareVideo: track},
5231
+ receive: {
5232
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5233
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5234
+ screenShareVideo: options.receiveShare,
5235
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4773
5236
  }
4774
- },
4775
- {
4776
- mediaProperties: this.mediaProperties,
4777
- meeting: this,
4778
- id: this.id
4779
5237
  })
4780
5238
  .then(() => {
4781
5239
  if (startShare) {
4782
- return this.share();
5240
+ return this.requestScreenShareFloor();
4783
5241
  }
4784
5242
 
4785
5243
  return Promise.resolve();
@@ -4789,24 +5247,8 @@ export default class Meeting extends StatelessWebexPlugin {
4789
5247
  this.mediaProperties.mediaDirection.receiveShare = receiveShare;
4790
5248
  })
4791
5249
  .catch((error) => {
4792
- this.unsetLocalShareTrack(stream);
5250
+ this.unsetLocalShareTrack();
4793
5251
  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
5252
  });
4811
5253
  }
4812
5254
 
@@ -4819,9 +5261,10 @@ export default class Meeting extends StatelessWebexPlugin {
4819
5261
  * @private
4820
5262
  * @memberof Meeting
4821
5263
  */
4822
- preMedia(localStream, localShare, mediaSettings) {
5264
+ private preMedia(localStream: MediaStream, localShare: MediaStream, mediaSettings: any) {
4823
5265
  // eslint-disable-next-line no-warning-comments
4824
5266
  // TODO wire into default config. There's currently an issue with the stateless plugin or how we register
5267
+ // @ts-ignore - config coming from registerPlugin
4825
5268
  this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
4826
5269
  // add a setup a function move the create and setup media in future
4827
5270
  // TODO: delete old audio and video if stale
@@ -4839,7 +5282,7 @@ export default class Meeting extends StatelessWebexPlugin {
4839
5282
  * @public
4840
5283
  * @memberof Meeting
4841
5284
  */
4842
- acknowledge(type) {
5285
+ public acknowledge(type: string) {
4843
5286
  if (!type) {
4844
5287
  return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
4845
5288
  }
@@ -4874,7 +5317,7 @@ export default class Meeting extends StatelessWebexPlugin {
4874
5317
  * @public
4875
5318
  * @memberof Meeting
4876
5319
  */
4877
- decline(reason) {
5320
+ public decline(reason: string) {
4878
5321
  return MeetingUtil.declineMeeting(this, reason).then((decline) => {
4879
5322
  this.meetingFiniteStateMachine.decline();
4880
5323
 
@@ -4894,7 +5337,7 @@ export default class Meeting extends StatelessWebexPlugin {
4894
5337
  * @public
4895
5338
  * @memberof Meeting
4896
5339
  */
4897
- leave(options = {}) {
5340
+ public leave(options: { resourceId?: string, reason?: any } = {} as any) {
4898
5341
  Metrics.postEvent({event: eventType.LEAVE, meeting: this, data: {trigger: trigger.USER_INTERACTION, canProceed: false}});
4899
5342
  const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
4900
5343
 
@@ -4971,7 +5414,7 @@ export default class Meeting extends StatelessWebexPlugin {
4971
5414
  * @public
4972
5415
  * @memberof Meeting
4973
5416
  */
4974
- startWhiteboardShare(channelUrl, resourceToken) {
5417
+ public startWhiteboardShare(channelUrl: string, resourceToken: string) {
4975
5418
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
4976
5419
 
4977
5420
  if (!channelUrl) {
@@ -4981,7 +5424,7 @@ export default class Meeting extends StatelessWebexPlugin {
4981
5424
  if (whiteboard) {
4982
5425
  Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_INITIATED, meeting: this});
4983
5426
 
4984
- const body = {
5427
+ const body: any = {
4985
5428
  disposition: FLOOR_ACTION.GRANTED,
4986
5429
  personUrl: this.locusInfo.self.url,
4987
5430
  deviceUrl: this.deviceUrl,
@@ -5027,7 +5470,7 @@ export default class Meeting extends StatelessWebexPlugin {
5027
5470
  * @public
5028
5471
  * @memberof Meeting
5029
5472
  */
5030
- stopWhiteboardShare(channelUrl) {
5473
+ public stopWhiteboardShare(channelUrl: string) {
5031
5474
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
5032
5475
 
5033
5476
  if (whiteboard) {
@@ -5043,6 +5486,7 @@ export default class Meeting extends StatelessWebexPlugin {
5043
5486
  LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
5044
5487
 
5045
5488
  Metrics.sendBehavioralMetric(
5489
+ // @ts-ignore - check if STOP_WHITEBOARD_SHARE_FAILURE exists
5046
5490
  BEHAVIORAL_METRICS.STOP_WHITEBOARD_SHARE_FAILURE,
5047
5491
  {
5048
5492
  correlation_id: this.correlationId,
@@ -5063,12 +5507,12 @@ export default class Meeting extends StatelessWebexPlugin {
5063
5507
  }
5064
5508
 
5065
5509
  /**
5066
- * Start sharing content with server
5510
+ * Sends a request to Locus to obtain the screen share floor
5067
5511
  * @returns {Promise} see #meetingRequest.changeMeetingFloor
5068
5512
  * @private
5069
5513
  * @memberof Meeting
5070
5514
  */
5071
- share() {
5515
+ private requestScreenShareFloor() {
5072
5516
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5073
5517
 
5074
5518
  if (content && (this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE)) {
@@ -5114,7 +5558,7 @@ export default class Meeting extends StatelessWebexPlugin {
5114
5558
  */
5115
5559
  // Internal only, temporarily allows optional params
5116
5560
  // eslint-disable-next-line valid-jsdoc
5117
- stopShare(options = {}) {
5561
+ public stopShare(options = {}) {
5118
5562
  return this.updateShare({
5119
5563
  sendShare: false,
5120
5564
  receiveShare: this.mediaProperties.mediaDirection.receiveShare,
@@ -5123,12 +5567,12 @@ export default class Meeting extends StatelessWebexPlugin {
5123
5567
  }
5124
5568
 
5125
5569
  /**
5126
- * sends stops floor request
5570
+ * Sends a request to Locus to release the screen share floor.
5127
5571
  * @returns {Promise} see #meetingRequest.changeMeetingFloor
5128
5572
  * @private
5129
5573
  * @memberof Meeting
5130
5574
  */
5131
- stopFloorRequest() {
5575
+ private releaseScreenShareFloor() {
5132
5576
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5133
5577
 
5134
5578
  if (content && (this.mediaProperties.mediaDirection.sendShare)) {
@@ -5150,7 +5594,7 @@ export default class Meeting extends StatelessWebexPlugin {
5150
5594
  resourceUrl: this.resourceUrl
5151
5595
  })
5152
5596
  .catch((error) => {
5153
- LoggerProxy.logger.error('Meeting:index#stopFloorRequest --> Error ', error);
5597
+ LoggerProxy.logger.error('Meeting:index#releaseScreenShareFloor --> Error ', error);
5154
5598
 
5155
5599
  Metrics.sendBehavioralMetric(
5156
5600
  BEHAVIORAL_METRICS.STOP_FLOOR_REQUEST_FAILURE,
@@ -5178,7 +5622,7 @@ export default class Meeting extends StatelessWebexPlugin {
5178
5622
  * @public
5179
5623
  * @memberof Meeting
5180
5624
  */
5181
- startRecording() {
5625
+ public startRecording() {
5182
5626
  return MeetingUtil.startRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5183
5627
  }
5184
5628
 
@@ -5188,7 +5632,7 @@ export default class Meeting extends StatelessWebexPlugin {
5188
5632
  * @public
5189
5633
  * @memberof Meeting
5190
5634
  */
5191
- stopRecording() {
5635
+ public stopRecording() {
5192
5636
  return MeetingUtil.stopRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5193
5637
  }
5194
5638
 
@@ -5198,7 +5642,7 @@ export default class Meeting extends StatelessWebexPlugin {
5198
5642
  * @public
5199
5643
  * @memberof Meeting
5200
5644
  */
5201
- pauseRecording() {
5645
+ public pauseRecording() {
5202
5646
  return MeetingUtil.pauseRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5203
5647
  }
5204
5648
 
@@ -5208,7 +5652,7 @@ export default class Meeting extends StatelessWebexPlugin {
5208
5652
  * @public
5209
5653
  * @memberof Meeting
5210
5654
  */
5211
- resumeRecording() {
5655
+ public resumeRecording() {
5212
5656
  return MeetingUtil.resumeRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5213
5657
  }
5214
5658
 
@@ -5218,7 +5662,7 @@ export default class Meeting extends StatelessWebexPlugin {
5218
5662
  * @public
5219
5663
  * @memberof Meeting
5220
5664
  */
5221
- lockMeeting() {
5665
+ public lockMeeting() {
5222
5666
  return MeetingUtil.lockMeeting(this.inMeetingActions, this.meetingRequest, this.locusUrl);
5223
5667
  }
5224
5668
 
@@ -5228,7 +5672,7 @@ export default class Meeting extends StatelessWebexPlugin {
5228
5672
  * @public
5229
5673
  * @memberof Meeting
5230
5674
  */
5231
- unlockMeeting() {
5675
+ public unlockMeeting() {
5232
5676
  return MeetingUtil.unlockMeeting(this.inMeetingActions, this.meetingRequest, this.locusUrl);
5233
5677
  }
5234
5678
 
@@ -5239,7 +5683,7 @@ export default class Meeting extends StatelessWebexPlugin {
5239
5683
  * @private
5240
5684
  * @memberof Meeting
5241
5685
  */
5242
- rejectWithErrorLog(message) {
5686
+ private rejectWithErrorLog(message: string) {
5243
5687
  LoggerProxy.logger.error(message);
5244
5688
 
5245
5689
  return Promise.reject(new Error(message));
@@ -5252,7 +5696,7 @@ export default class Meeting extends StatelessWebexPlugin {
5252
5696
  * @public
5253
5697
  * @memberof Meeting
5254
5698
  */
5255
- sendDTMF(tones) {
5699
+ public sendDTMF(tones: string) {
5256
5700
  if (this.locusInfo && this.locusInfo.self) {
5257
5701
  if (this.locusInfo.self.enableDTMF) {
5258
5702
  return this.meetingRequest
@@ -5283,7 +5727,19 @@ export default class Meeting extends StatelessWebexPlugin {
5283
5727
  * @public
5284
5728
  * @memberof Meeting
5285
5729
  */
5286
- changeVideoLayout(layoutType, renderInfo = {}) {
5730
+ public changeVideoLayout(
5731
+ layoutType?: string,
5732
+ renderInfo: {
5733
+ main: {
5734
+ width: number;
5735
+ height: number;
5736
+ };
5737
+ content: {
5738
+ width: number;
5739
+ height: number;
5740
+ };
5741
+ } = {} as any
5742
+ ) {
5287
5743
  const {main, content} = renderInfo;
5288
5744
  const {mediaDirection, remoteShare, remoteVideoTrack} = this.mediaProperties;
5289
5745
 
@@ -5379,7 +5835,7 @@ export default class Meeting extends StatelessWebexPlugin {
5379
5835
  * @param {String} level {LOW|MEDIUM|HIGH}
5380
5836
  * @returns {Promise<MediaStream>} localStream
5381
5837
  */
5382
- setLocalVideoQuality(level) {
5838
+ setLocalVideoQuality(level: string) {
5383
5839
  LoggerProxy.logger.log(`Meeting:index#setLocalVideoQuality --> Setting quality to ${level}`);
5384
5840
 
5385
5841
  if (!VIDEO_RESOLUTIONS[level]) {
@@ -5429,7 +5885,7 @@ export default class Meeting extends StatelessWebexPlugin {
5429
5885
  * @param {String} level {LOW|MEDIUM|HIGH}
5430
5886
  * @returns {Promise}
5431
5887
  */
5432
- setRemoteQualityLevel(level) {
5888
+ setRemoteQualityLevel(level: string) {
5433
5889
  LoggerProxy.logger.log(`Meeting:index#setRemoteQualityLevel --> Setting quality to ${level}`);
5434
5890
 
5435
5891
  if (!QUALITY_LEVELS[level]) {
@@ -5459,7 +5915,7 @@ export default class Meeting extends StatelessWebexPlugin {
5459
5915
  * @returns {Promise}
5460
5916
  * @deprecated After FHD support
5461
5917
  */
5462
- setMeetingQuality(level) {
5918
+ setMeetingQuality(level: string) {
5463
5919
  LoggerProxy.logger.log(`Meeting:index#setMeetingQuality --> Setting quality to ${level}`);
5464
5920
 
5465
5921
  if (!QUALITY_LEVELS[level]) {
@@ -5514,6 +5970,9 @@ export default class Meeting extends StatelessWebexPlugin {
5514
5970
  }
5515
5971
 
5516
5972
  /**
5973
+ *
5974
+ * NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
5975
+ *
5517
5976
  * @param {Object} options parameter
5518
5977
  * @param {Boolean} options.sendAudio send audio from the display share
5519
5978
  * @param {Boolean} options.sendShare send video from the display share
@@ -5523,7 +5982,13 @@ export default class Meeting extends StatelessWebexPlugin {
5523
5982
  * @param {Boolean} options.sharePreferences.highFrameRate if shareConstraints isn't provided, set default values based off of this boolean
5524
5983
  * @returns {Promise}
5525
5984
  */
5526
- shareScreen(options = {}) {
5985
+ shareScreen(
5986
+ options: {
5987
+ sendAudio: boolean;
5988
+ sendShare: boolean;
5989
+ sharePreferences: { shareConstraints: MediaTrackConstraints };
5990
+ } = {} as any
5991
+ ) {
5527
5992
  LoggerProxy.logger.log('Meeting:index#shareScreen --> Getting local share');
5528
5993
 
5529
5994
  const shareConstraints = {
@@ -5532,6 +5997,7 @@ export default class Meeting extends StatelessWebexPlugin {
5532
5997
  ...options
5533
5998
  };
5534
5999
 
6000
+ // @ts-ignore - config coming from registerPlugin
5535
6001
  return Media.getDisplayMedia(shareConstraints, this.config)
5536
6002
  .then((shareStream) => this.updateShare({
5537
6003
  sendShare: true,
@@ -5569,7 +6035,7 @@ export default class Meeting extends StatelessWebexPlugin {
5569
6035
  * @param {MediaStream} localShare
5570
6036
  * @returns {undefined}
5571
6037
  */
5572
- handleShareTrackEnded(localShare) {
6038
+ private handleShareTrackEnded(localShare: MediaStream) {
5573
6039
  if (this.wirelessShare) {
5574
6040
  this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
5575
6041
  }
@@ -5609,7 +6075,7 @@ export default class Meeting extends StatelessWebexPlugin {
5609
6075
  * @private
5610
6076
  * @memberof Meeting
5611
6077
  */
5612
- sendNetworkQualityEvent(res) {
6078
+ private sendNetworkQualityEvent(res: any) {
5613
6079
  Trigger.trigger(
5614
6080
  this,
5615
6081
  {
@@ -5631,7 +6097,7 @@ export default class Meeting extends StatelessWebexPlugin {
5631
6097
  * @private
5632
6098
  * @returns {undefined}
5633
6099
  */
5634
- handleMediaLogging({audioTrack, videoTrack}) {
6100
+ private handleMediaLogging({ audioTrack, videoTrack }: any) {
5635
6101
  MeetingUtil.handleVideoLogging(videoTrack);
5636
6102
  MeetingUtil.handleAudioLogging(audioTrack);
5637
6103
  }
@@ -5640,7 +6106,7 @@ export default class Meeting extends StatelessWebexPlugin {
5640
6106
  * @param {string} typeMedia 'audio' or 'video'
5641
6107
  * @returns {undefined}
5642
6108
  */
5643
- setStartSetupDelay(typeMedia) {
6109
+ setStartSetupDelay(typeMedia: string) {
5644
6110
  this[`startSetupDelay${typeMedia}`] = performance.now();
5645
6111
  this[`endSetupDelay${typeMedia}`] = undefined;
5646
6112
  }
@@ -5649,7 +6115,7 @@ export default class Meeting extends StatelessWebexPlugin {
5649
6115
  * @param {string} typeMedia 'audio' or 'video'
5650
6116
  * @returns {undefined}
5651
6117
  */
5652
- setEndSetupDelay(typeMedia) {
6118
+ setEndSetupDelay(typeMedia: string) {
5653
6119
  this[`endSetupDelay${typeMedia}`] = performance.now();
5654
6120
  }
5655
6121
 
@@ -5657,7 +6123,7 @@ export default class Meeting extends StatelessWebexPlugin {
5657
6123
  * @param {string} typeMedia 'audio' or 'video'
5658
6124
  * @returns {string} duration between start and end of setup
5659
6125
  */
5660
- getSetupDelayDuration(typeMedia) {
6126
+ getSetupDelayDuration(typeMedia: string) {
5661
6127
  const start = this[`startSetupDelay${typeMedia}`];
5662
6128
  const end = this[`endSetupDelay${typeMedia}`];
5663
6129
 
@@ -5668,7 +6134,7 @@ export default class Meeting extends StatelessWebexPlugin {
5668
6134
  * @param {string} typeMedia 'audio' or 'video'
5669
6135
  * @returns {undefined}
5670
6136
  */
5671
- setStartSendingMediaDelay(typeMedia) {
6137
+ setStartSendingMediaDelay(typeMedia: string) {
5672
6138
  this[`startSendingMediaDelay${typeMedia}`] = performance.now();
5673
6139
  this[`endSendingMediaDelay${typeMedia}`] = undefined;
5674
6140
  }
@@ -5677,7 +6143,7 @@ export default class Meeting extends StatelessWebexPlugin {
5677
6143
  * @param {string} typeMedia 'audio' or 'video'
5678
6144
  * @returns {undefined}
5679
6145
  */
5680
- setEndSendingMediaDelay(typeMedia) {
6146
+ setEndSendingMediaDelay(typeMedia: string) {
5681
6147
  this[`endSendingMediaDelay${typeMedia}`] = performance.now();
5682
6148
  }
5683
6149
 
@@ -5685,7 +6151,7 @@ export default class Meeting extends StatelessWebexPlugin {
5685
6151
  * @param {string} typeMedia 'audio' or 'video'
5686
6152
  * @returns {string} duration between join response and first media tx
5687
6153
  */
5688
- getSendingMediaDelayDuration(typeMedia) {
6154
+ getSendingMediaDelayDuration(typeMedia: string) {
5689
6155
  const start = this[`startSendingMediaDelay${typeMedia}`];
5690
6156
  const end = this[`endSendingMediaDelay${typeMedia}`];
5691
6157
 
@@ -5696,7 +6162,7 @@ export default class Meeting extends StatelessWebexPlugin {
5696
6162
  *
5697
6163
  * @returns {undefined}
5698
6164
  */
5699
- setStartLocalSDPGenRemoteSDPRecvDelay() {
6165
+ setStartLocalSDPGenRemoteSDPRecvDelay() {
5700
6166
  if (!this.startLocalSDPGenRemoteSDPRecvDelay) {
5701
6167
  this.startLocalSDPGenRemoteSDPRecvDelay = performance.now();
5702
6168
  this.endLocalSDPGenRemoteSDPRecvDelay = undefined;
@@ -5822,7 +6288,7 @@ export default class Meeting extends StatelessWebexPlugin {
5822
6288
  * @public
5823
6289
  * @memberof Meeting
5824
6290
  */
5825
- endMeetingForAll() {
6291
+ public endMeetingForAll() {
5826
6292
  Metrics.postEvent({event: eventType.LEAVE, meeting: this, data: {trigger: trigger.USER_INTERACTION, canProceed: false}});
5827
6293
 
5828
6294
  LoggerProxy.logger.log('Meeting:index#endMeetingForAll --> End meeting for All');
@@ -5907,7 +6373,7 @@ export default class Meeting extends StatelessWebexPlugin {
5907
6373
  * @public
5908
6374
  * @memberof Meeting
5909
6375
  */
5910
- isBnrEnabled() {
6376
+ public isBnrEnabled() {
5911
6377
  return this.effects && this.effects.isBnrEnabled();
5912
6378
  }
5913
6379
 
@@ -5918,7 +6384,7 @@ export default class Meeting extends StatelessWebexPlugin {
5918
6384
  * @param {MedaiStreamTrack} audioTrack from updateAudio
5919
6385
  * @memberof Meeting
5920
6386
  */
5921
- async internal_enableBNR(audioTrack) {
6387
+ private async internal_enableBNR(audioTrack: any) {
5922
6388
  try {
5923
6389
  LoggerProxy.logger.info('Meeting:index#internal_enableBNR. Internal enable BNR called');
5924
6390
  const bnrAudioTrack = await WebRTCMedia.Effects.BNR.enableBNR(audioTrack);
@@ -5939,7 +6405,7 @@ export default class Meeting extends StatelessWebexPlugin {
5939
6405
  * @public
5940
6406
  * @memberof Meeting
5941
6407
  */
5942
- enableBNR() {
6408
+ public enableBNR() {
5943
6409
  if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5944
6410
  return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
5945
6411
  }
@@ -5974,7 +6440,7 @@ export default class Meeting extends StatelessWebexPlugin {
5974
6440
  * @public
5975
6441
  * @memberof Meeting
5976
6442
  */
5977
- disableBNR() {
6443
+ public disableBNR() {
5978
6444
  if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5979
6445
  return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
5980
6446
  }