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

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 (281) 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 +1146 -602
  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 +83 -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/reactions/reactions.js +111 -0
  130. package/dist/reactions/reactions.js.map +1 -0
  131. package/dist/reactions/reactions.type.js +40 -0
  132. package/dist/reactions/reactions.type.js.map +1 -0
  133. package/dist/reconnection-manager/index.js +130 -132
  134. package/dist/reconnection-manager/index.js.map +1 -1
  135. package/dist/roap/index.js +58 -231
  136. package/dist/roap/index.js.map +1 -1
  137. package/dist/roap/request.js +7 -116
  138. package/dist/roap/request.js.map +1 -1
  139. package/dist/roap/turnDiscovery.js +20 -6
  140. package/dist/roap/turnDiscovery.js.map +1 -1
  141. package/dist/statsAnalyzer/global.js +2 -0
  142. package/dist/statsAnalyzer/global.js.map +1 -1
  143. package/dist/statsAnalyzer/index.js +58 -37
  144. package/dist/statsAnalyzer/index.js.map +1 -1
  145. package/dist/statsAnalyzer/mqaUtil.js +9 -3
  146. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  147. package/dist/transcription/index.js +10 -3
  148. package/dist/transcription/index.js.map +1 -1
  149. package/package.json +21 -20
  150. package/src/common/{browser-detection.js → browser-detection.ts} +1 -1
  151. package/src/common/collection.ts +6 -6
  152. package/src/common/{config.js → config.ts} +1 -1
  153. package/src/common/errors/{captcha-error.js → captcha-error.ts} +5 -1
  154. package/src/common/errors/{intent-to-join.js → intent-to-join.ts} +6 -1
  155. package/src/common/errors/{join-meeting.js → join-meeting.ts} +6 -1
  156. package/src/common/errors/{media.js → media.ts} +5 -1
  157. package/src/common/errors/parameter.ts +3 -2
  158. package/src/common/errors/{password-error.js → password-error.ts} +5 -1
  159. package/src/common/errors/{permission.js → permission.ts} +5 -1
  160. package/src/common/errors/{reconnection-in-progress.js → reconnection-in-progress.ts} +0 -0
  161. package/src/common/errors/{reconnection.js → reconnection.ts} +5 -1
  162. package/src/common/errors/{stats.js → stats.ts} +5 -1
  163. package/src/common/errors/{webex-errors.js → webex-errors.ts} +1 -20
  164. package/src/common/errors/{webex-meetings-error.js → webex-meetings-error.ts} +3 -1
  165. package/src/common/events/{events-scope.js → events-scope.ts} +1 -1
  166. package/src/common/events/{events.js → events.ts} +0 -0
  167. package/src/common/events/{trigger-proxy.js → trigger-proxy.ts} +1 -2
  168. package/src/common/events/{util.js → util.ts} +1 -1
  169. package/src/common/logs/{logger-config.js → logger-config.ts} +1 -2
  170. package/src/common/logs/{logger-proxy.js → logger-proxy.ts} +1 -1
  171. package/src/common/logs/{request.js → request.ts} +12 -2
  172. package/src/common/queue.ts +1 -2
  173. package/src/{config.js → config.ts} +2 -0
  174. package/src/constants.ts +139 -179
  175. package/src/locus-info/{controlsUtils.js → controlsUtils.ts} +4 -4
  176. package/src/locus-info/{embeddedAppsUtils.js → embeddedAppsUtils.ts} +5 -6
  177. package/src/locus-info/{fullState.js → fullState.ts} +1 -1
  178. package/src/locus-info/{hostUtils.js → hostUtils.ts} +5 -5
  179. package/src/locus-info/{index.js → index.ts} +67 -32
  180. package/src/locus-info/{infoUtils.js → infoUtils.ts} +7 -4
  181. package/src/locus-info/{mediaSharesUtils.js → mediaSharesUtils.ts} +13 -13
  182. package/src/locus-info/{parser.js → parser.ts} +22 -12
  183. package/src/locus-info/{selfUtils.js → selfUtils.ts} +17 -19
  184. package/src/media/{index.js → index.ts} +130 -205
  185. package/src/media/internal-media-core-wrapper.ts +9 -0
  186. package/src/media/{properties.js → properties.ts} +35 -29
  187. package/src/media/util.ts +16 -0
  188. package/src/mediaQualityMetrics/{config.js → config.ts} +1 -1
  189. package/src/meeting/{effectsState.js → effectsState.ts} +12 -6
  190. package/src/meeting/{index.js → index.ts} +993 -474
  191. package/src/meeting/{muteState.js → muteState.ts} +16 -11
  192. package/src/meeting/{request.js → request.ts} +148 -36
  193. package/src/meeting/{state.js → state.ts} +6 -6
  194. package/src/meeting/{util.js → util.ts} +9 -51
  195. package/src/meeting-info/{collection.js → collection.ts} +4 -1
  196. package/src/meeting-info/{index.js → index.ts} +10 -6
  197. package/src/meeting-info/{meeting-info-v2.js → meeting-info-v2.ts} +28 -10
  198. package/src/meeting-info/{request.js → request.ts} +6 -2
  199. package/src/meeting-info/{util.js → util.ts} +6 -5
  200. package/src/meeting-info/{utilv2.js → utilv2.ts} +8 -7
  201. package/src/meetings/{collection.js → collection.ts} +5 -2
  202. package/src/meetings/{index.js → index.ts} +118 -22
  203. package/src/meetings/{request.js → request.ts} +6 -1
  204. package/src/meetings/{util.js → util.ts} +28 -5
  205. package/src/member/{index.js → index.ts} +46 -15
  206. package/src/member/{util.js → util.ts} +17 -16
  207. package/src/members/{collection.js → collection.ts} +2 -1
  208. package/src/members/{index.js → index.ts} +94 -26
  209. package/src/members/{request.js → request.ts} +16 -5
  210. package/src/members/{util.js → util.ts} +7 -7
  211. package/src/metrics/{config.js → config.ts} +0 -2
  212. package/src/metrics/{constants.js → constants.ts} +0 -0
  213. package/src/metrics/{index.js → index.ts} +27 -8
  214. package/src/multistream/mediaRequestManager.ts +166 -0
  215. package/src/multistream/multistreamMedia.ts +92 -0
  216. package/src/multistream/receiveSlot.ts +141 -0
  217. package/src/multistream/receiveSlotManager.ts +142 -0
  218. package/src/multistream/remoteMedia.ts +228 -0
  219. package/src/multistream/remoteMediaGroup.ts +224 -0
  220. package/src/multistream/remoteMediaManager.ts +911 -0
  221. package/src/networkQualityMonitor/{index.js → index.ts} +18 -3
  222. package/src/personal-meeting-room/{index.js → index.ts} +17 -4
  223. package/src/personal-meeting-room/{request.js → request.ts} +3 -1
  224. package/src/personal-meeting-room/{util.js → util.ts} +1 -1
  225. package/src/reachability/{index.js → index.ts} +28 -17
  226. package/src/reachability/request.ts +4 -2
  227. package/src/reactions/reactions.ts +104 -0
  228. package/src/reactions/reactions.type.ts +36 -0
  229. package/src/reconnection-manager/{index.js → index.ts} +81 -65
  230. package/src/roap/index.ts +229 -0
  231. package/src/roap/{request.js → request.ts} +15 -74
  232. package/src/roap/turnDiscovery.ts +26 -11
  233. package/src/statsAnalyzer/{global.js → global.ts} +2 -0
  234. package/src/statsAnalyzer/{index.js → index.ts} +66 -61
  235. package/src/statsAnalyzer/{mqaUtil.js → mqaUtil.ts} +6 -1
  236. package/src/transcription/{index.js → index.ts} +16 -11
  237. package/test/integration/spec/journey.js +1 -1
  238. package/test/integration/spec/space-meeting.js +1 -2
  239. package/test/unit/spec/locus-info/infoUtils.js +17 -1
  240. package/test/unit/spec/media/index.ts +207 -0
  241. package/test/unit/spec/media/properties.ts +73 -82
  242. package/test/unit/spec/meeting/effectsState.js +1 -3
  243. package/test/unit/spec/meeting/index.js +672 -245
  244. package/test/unit/spec/meeting/muteState.js +7 -0
  245. package/test/unit/spec/meeting/request.js +25 -1
  246. package/test/unit/spec/meeting/utils.js +63 -2
  247. package/test/unit/spec/meetings/index.js +0 -4
  248. package/test/unit/spec/members/index.js +164 -2
  249. package/test/unit/spec/multistream/mediaRequestManager.ts +515 -0
  250. package/test/unit/spec/multistream/receiveSlot.ts +104 -0
  251. package/test/unit/spec/multistream/receiveSlotManager.ts +173 -0
  252. package/test/unit/spec/multistream/remoteMedia.ts +225 -0
  253. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  254. package/test/unit/spec/multistream/remoteMediaManager.ts +1309 -0
  255. package/test/unit/spec/reconnection-manager/index.js +68 -2
  256. package/test/unit/spec/roap/index.ts +63 -35
  257. package/test/unit/spec/stats-analyzer/index.js +19 -22
  258. package/dist/peer-connection-manager/index.js +0 -794
  259. package/dist/peer-connection-manager/index.js.map +0 -1
  260. package/dist/peer-connection-manager/util.js +0 -124
  261. package/dist/peer-connection-manager/util.js.map +0 -1
  262. package/dist/roap/collection.js +0 -73
  263. package/dist/roap/collection.js.map +0 -1
  264. package/dist/roap/handler.js +0 -337
  265. package/dist/roap/handler.js.map +0 -1
  266. package/dist/roap/state.js +0 -164
  267. package/dist/roap/state.js.map +0 -1
  268. package/dist/roap/util.js +0 -102
  269. package/dist/roap/util.js.map +0 -1
  270. package/src/media/util.js +0 -38
  271. package/src/peer-connection-manager/index.js +0 -723
  272. package/src/peer-connection-manager/util.ts +0 -117
  273. package/src/roap/collection.js +0 -63
  274. package/src/roap/handler.js +0 -252
  275. package/src/roap/index.js +0 -380
  276. package/src/roap/state.js +0 -149
  277. package/src/roap/util.js +0 -93
  278. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  279. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  280. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -389
  281. 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';
@@ -44,7 +44,6 @@ import {
44
44
  FLOOR_ACTION,
45
45
  FULL_STATE,
46
46
  LAYOUT_TYPES,
47
- LIVE,
48
47
  LOCUSINFO,
49
48
  MEETING_INFO_FAILURE_REASON,
50
49
  MEETING_REMOVED_REASON,
@@ -60,9 +59,6 @@ import {
60
59
  PSTN_STATUS,
61
60
  QUALITY_LEVELS,
62
61
  RECORDING_STATE,
63
- ROAP_SEQ_PRE,
64
- SDP,
65
- SENDRECV,
66
62
  SHARE_STATUS,
67
63
  SHARE_STOPPED_REASON,
68
64
  VIDEO_RESOLUTIONS,
@@ -75,14 +71,19 @@ import ParameterError from '../common/errors/parameter';
75
71
  import MediaError from '../common/errors/media';
76
72
  import {MeetingInfoV2PasswordError, MeetingInfoV2CaptchaError} from '../meeting-info/meeting-info-v2';
77
73
  import BrowserDetection from '../common/browser-detection';
78
- 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';
78
+ import {SkinTones, Reactions} from '../reactions/reactions';
79
+ import {Reaction, ReactionType, SkinToneType} from '../reactions/reactions.type';
79
80
 
80
81
  import InMeetingActions from './in-meeting-actions';
81
82
 
82
83
 
83
84
  const {isBrowser} = BrowserDetection();
84
85
 
85
- const logRequest = (request, {header = '', success = '', failure = ''}) => {
86
+ const logRequest = (request: any, { header = '', success = '', failure = '' }) => {
86
87
  LoggerProxy.logger.info(header);
87
88
 
88
89
  return request
@@ -143,6 +144,7 @@ export const MEDIA_UPDATE_TYPE = {
143
144
  * @property {String} [meetingQuality.local]
144
145
  * @property {String} [meetingQuality.remote]
145
146
  * @property {Boolean} [rejoin]
147
+ * @property {Boolean} [enableMultistream]
146
148
  */
147
149
 
148
150
  /**
@@ -392,6 +394,91 @@ export const MEDIA_UPDATE_TYPE = {
392
394
  * @class Meeting
393
395
  */
394
396
  export default class Meeting extends StatelessWebexPlugin {
397
+ attrs: any;
398
+ audio: any;
399
+ conversationUrl: string;
400
+ correlationId: string;
401
+ destination: string;
402
+ destinationType: string;
403
+ deviceUrl: string;
404
+ effects: any;
405
+ hostId: string;
406
+ id: string;
407
+ isMultistream: boolean;
408
+ locusUrl: string;
409
+ mediaConnections: any[];
410
+ mediaId?: string;
411
+ meetingFiniteStateMachine: any;
412
+ meetingInfo: object;
413
+ meetingRequest: MeetingRequest;
414
+ members: Members;
415
+ options: object;
416
+ orgId: string;
417
+ owner: string;
418
+ partner: any;
419
+ policy: string;
420
+ reconnectionManager: ReconnectionManager;
421
+ resource: string;
422
+ roap: Roap;
423
+ roapSeq: number;
424
+ sipUri: string;
425
+ type: string;
426
+ userId: string;
427
+ video: any;
428
+ callEvents: any[];
429
+ deferJoin: Promise<any>;
430
+ dialInDeviceStatus: string;
431
+ dialInUrl: string;
432
+ dialOutDeviceStatus: string;
433
+ dialOutUrl: string;
434
+ fetchMeetingInfoTimeoutId: NodeJS.Timeout;
435
+ floorGrantPending: boolean;
436
+ hasJoinedOnce: boolean;
437
+ hasWebsocketConnected: boolean;
438
+ inMeetingActions: InMeetingActions;
439
+ isLocalShareLive: boolean;
440
+ isRoapInProgress: boolean;
441
+ isSharing: boolean;
442
+ keepAliveTimerId: NodeJS.Timeout;
443
+ lastVideoLayoutInfo: any;
444
+ locusInfo: any;
445
+ media: MultistreamMedia;
446
+ mediaProperties: MediaProperties;
447
+ mediaRequestManagers: {
448
+ audio: MediaRequestManager;
449
+ video: MediaRequestManager;
450
+ };
451
+ meetingInfoFailureReason: string;
452
+ networkQualityMonitor: NetworkQualityMonitor;
453
+ networkStatus: string;
454
+ passwordStatus: string;
455
+ queuedMediaUpdates: any[];
456
+ recording: any;
457
+ remoteMediaManager: RemoteMediaManager | null;
458
+ requiredCaptcha: any;
459
+ receiveSlotManager: ReceiveSlotManager;
460
+ shareStatus: string;
461
+ statsAnalyzer: StatsAnalyzer;
462
+ transcription: Transcription;
463
+ updateMediaConnections: (mediaConnections: any[]) => void;
464
+ endCallInitiateJoinReq: any;
465
+ endJoinReqResp: any;
466
+ endLocalSDPGenRemoteSDPRecvDelay: any;
467
+ joinedWith: any;
468
+ locusId: any;
469
+ startCallInitiateJoinReq: any;
470
+ startJoinReqResp: any;
471
+ startLocalSDPGenRemoteSDPRecvDelay: any;
472
+ wirelessShare: any;
473
+ guest: any;
474
+ meetingJoinUrl: any;
475
+ meetingNumber: any;
476
+ meetingState: any;
477
+ permissionToken: any;
478
+ resourceId: any;
479
+ resourceUrl: string;
480
+ selfId: string;
481
+ state: any;
395
482
  namespace = MEETINGS;
396
483
 
397
484
  /**
@@ -400,7 +487,7 @@ export default class Meeting extends StatelessWebexPlugin {
400
487
  * @constructor
401
488
  * @memberof Meeting
402
489
  */
403
- constructor(attrs, options) {
490
+ constructor(attrs: any, options: object) {
404
491
  super({}, options);
405
492
  /**
406
493
  * @instance
@@ -468,15 +555,6 @@ export default class Meeting extends StatelessWebexPlugin {
468
555
  * @memberof Meeting
469
556
  */
470
557
  this.deviceUrl = attrs.deviceUrl;
471
- /**
472
- * @description set you -1 as default values is 0 (used to idenfify if 1st roap request was sent)
473
- * @instance
474
- * @type {Number}
475
- * @readonly
476
- * @private
477
- * @memberof Meeting
478
- */
479
- this.roapSeq = ROAP_SEQ_PRE;
480
558
  /**
481
559
  * @instance
482
560
  * @type {Object}
@@ -486,13 +564,44 @@ export default class Meeting extends StatelessWebexPlugin {
486
564
  */
487
565
  // TODO: needs to be defined as a class
488
566
  this.meetingInfo = {};
567
+ /**
568
+ * helper class for managing receive slots (for multistream media connections)
569
+ */
570
+ this.receiveSlotManager = new ReceiveSlotManager(this);
571
+ /**
572
+ * Helper class for managing media requests for main video (for multistream media connections)
573
+ * All media requests sent out for main video for this meeting have to go through it.
574
+ */
575
+ this.mediaRequestManagers = {
576
+ audio: new MediaRequestManager((mediaRequests) => {
577
+ if (!this.mediaProperties.webrtcMediaConnection) {
578
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created');
579
+
580
+ return;
581
+ }
582
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.AudioMain, mediaRequests);
583
+ }),
584
+ video: new MediaRequestManager((mediaRequests) => {
585
+ if (!this.mediaProperties.webrtcMediaConnection) {
586
+ LoggerProxy.logger.warn('Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created');
587
+
588
+ return;
589
+ }
590
+ this.mediaProperties.webrtcMediaConnection.requestMedia(MC.MediaType.VideoMain, mediaRequests);
591
+ })
592
+ };
489
593
  /**
490
594
  * @instance
491
595
  * @type {Members}
492
596
  * @public
493
597
  * @memberof Meeting
494
598
  */
495
- this.members = new Members({locusUrl: (attrs.locus && attrs.locus.url)}, {parent: this.webex});
599
+ this.members = new Members({
600
+ locusUrl: (attrs.locus && attrs.locus.url),
601
+ receiveSlotManager: this.receiveSlotManager,
602
+ mediaRequestManagers: this.mediaRequestManagers,
603
+ // @ts-ignore - Fix type
604
+ }, {parent: this.webex});
496
605
  /**
497
606
  * @instance
498
607
  * @type {Roap}
@@ -500,7 +609,18 @@ export default class Meeting extends StatelessWebexPlugin {
500
609
  * @private
501
610
  * @memberof Meeting
502
611
  */
612
+ // @ts-ignore - Fix type
503
613
  this.roap = new Roap({}, {parent: this.webex});
614
+ /**
615
+ * indicates if an SDP exchange is happening
616
+ *
617
+ * @instance
618
+ * @type {Boolean}
619
+ * @readonly
620
+ * @private
621
+ * @memberof Meeting
622
+ */
623
+ this.isRoapInProgress = false;
504
624
  /**
505
625
  * created later
506
626
  * @instance
@@ -638,6 +758,11 @@ export default class Meeting extends StatelessWebexPlugin {
638
758
  */
639
759
  this.mediaConnections = null;
640
760
 
761
+ /**
762
+ * If true, then media is sent over multiple separate streams.
763
+ * If false, then media is transcoded by the server into a single stream.
764
+ */
765
+ this.isMultistream = false;
641
766
  /**
642
767
  * Fetching meeting info can be done randomly 2-5 mins before meeting start
643
768
  * In case it is done before the timer expires, this timeout id is reset to cancel the timer.
@@ -656,7 +781,7 @@ export default class Meeting extends StatelessWebexPlugin {
656
781
  * @private
657
782
  * @memberof Meeting
658
783
  */
659
- this.updateMediaConnections = (mediaConnections) => {
784
+ this.updateMediaConnections = (mediaConnections: any[]) => {
660
785
  if (!isEqual(this.mediaConnections, mediaConnections)) {
661
786
  // grab last/latest item in the new mediaConnections information
662
787
  this.mediaConnections = mediaConnections.slice(-1);
@@ -689,35 +814,13 @@ export default class Meeting extends StatelessWebexPlugin {
689
814
  this.isSharing = false;
690
815
  /**
691
816
  * @instance
692
- * @type {Boolean}
817
+ * @type {string}
693
818
  * @readonly
694
819
  * @public
695
820
  * @memberof Meeting
696
821
  */
697
822
  this.shareStatus = SHARE_STATUS.NO_SHARE;
698
- /**
699
- * @instance
700
- * @type {Boolean}
701
- * @readonly
702
- * @private
703
- * @memberof Meeting
704
- */
705
- Object.defineProperty(this, 'isLocalShareLive', {
706
- get: () => {
707
- const {shareTransceiver} = this.mediaProperties.peerConnection;
708
- const shareDirection = shareTransceiver?.direction;
709
- const trackReadyState = shareTransceiver?.sender?.track?.readyState;
710
- const activeShare = trackReadyState === LIVE;
711
- const offersToSendData = shareDirection === SENDRECV;
712
-
713
- if (activeShare && offersToSendData) {
714
- return true;
715
- }
716
823
 
717
- return false;
718
- },
719
- configurable: true
720
- });
721
824
  /**
722
825
  * @instance
723
826
  * @type {Array}
@@ -729,7 +832,7 @@ export default class Meeting extends StatelessWebexPlugin {
729
832
  /**
730
833
  * There is a pending floor requested by the user
731
834
  * @instance
732
- * @type {floorGrantPending}
835
+ * @type {boolean}
733
836
  * @private
734
837
  * @memberof Meeting
735
838
  */
@@ -797,6 +900,7 @@ export default class Meeting extends StatelessWebexPlugin {
797
900
  * @private
798
901
  * @memberof Meeting
799
902
  */
903
+ // @ts-ignore - Fix type
800
904
  this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
801
905
  // We had to add listeners first before setting up the locus instance
802
906
  /**
@@ -824,6 +928,7 @@ export default class Meeting extends StatelessWebexPlugin {
824
928
  * @private
825
929
  * @memberof Meeting
826
930
  */
931
+ // @ts-ignore - Fix type
827
932
  this.hasWebsocketConnected = this.webex.internal.mercury.connected;
828
933
 
829
934
  /**
@@ -899,6 +1004,13 @@ export default class Meeting extends StatelessWebexPlugin {
899
1004
  this.setUpLocusInfoListeners();
900
1005
  this.locusInfo.init(attrs.locus ? attrs.locus : {});
901
1006
  this.hasJoinedOnce = false;
1007
+
1008
+ this.media = new MultistreamMedia(this);
1009
+
1010
+ /**
1011
+ * helper class for managing remote streams
1012
+ */
1013
+ this.remoteMediaManager = null;
902
1014
  }
903
1015
 
904
1016
  /**
@@ -910,9 +1022,7 @@ export default class Meeting extends StatelessWebexPlugin {
910
1022
  * @memberof Meeting
911
1023
  * @returns {Promise}
912
1024
  */
913
- async fetchMeetingInfo({
914
- password = null, captchaCode = null
915
- }) {
1025
+ public async fetchMeetingInfo({ password = null, captchaCode = null }: { password?: string; captchaCode?: string }) {
916
1026
  // when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
917
1027
  if (this.fetchMeetingInfoTimeoutId) {
918
1028
  clearTimeout(this.fetchMeetingInfoTimeoutId);
@@ -954,6 +1064,7 @@ export default class Meeting extends StatelessWebexPlugin {
954
1064
  }
955
1065
  catch (err) {
956
1066
  if (err instanceof MeetingInfoV2PasswordError) {
1067
+ // @ts-ignore
957
1068
  LoggerProxy.logger.info(`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`);
958
1069
 
959
1070
  // when wbxappapi requires password it still populates partial meeting info in the response
@@ -972,6 +1083,7 @@ export default class Meeting extends StatelessWebexPlugin {
972
1083
  throw (new PasswordError());
973
1084
  }
974
1085
  else if (err instanceof MeetingInfoV2CaptchaError) {
1086
+ // @ts-ignore
975
1087
  LoggerProxy.logger.info(`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`);
976
1088
 
977
1089
  this.meetingInfoFailureReason = (this.requiredCaptcha) ?
@@ -1001,7 +1113,7 @@ export default class Meeting extends StatelessWebexPlugin {
1001
1113
  * @memberof Meeting
1002
1114
  * @returns {Promise<{isPasswordValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
1003
1115
  */
1004
- verifyPassword(password, captchaCode) {
1116
+ public verifyPassword(password: string, captchaCode: string) {
1005
1117
  return this.fetchMeetingInfo({
1006
1118
  password, captchaCode
1007
1119
  })
@@ -1031,7 +1143,7 @@ export default class Meeting extends StatelessWebexPlugin {
1031
1143
  * @memberof Meeting
1032
1144
  * @returns {Promise}
1033
1145
  */
1034
- refreshCaptcha() {
1146
+ public refreshCaptcha() {
1035
1147
  if (!this.requiredCaptcha) {
1036
1148
  return Promise.reject(new Error('There is no captcha to refresh'));
1037
1149
  }
@@ -1061,7 +1173,7 @@ export default class Meeting extends StatelessWebexPlugin {
1061
1173
  * @private
1062
1174
  * @memberof Meeting
1063
1175
  */
1064
- setUpLocusInfoListeners() {
1176
+ private setUpLocusInfoListeners() {
1065
1177
  // meeting update listeners
1066
1178
  this.setUpLocusInfoSelfListener();
1067
1179
  this.setUpLocusInfoMeetingListener();
@@ -1079,14 +1191,13 @@ export default class Meeting extends StatelessWebexPlugin {
1079
1191
  this.setUpLocusInfoMediaInactiveListener();
1080
1192
  }
1081
1193
 
1082
-
1083
1194
  /**
1084
1195
  * Set up the locus info listener for meetings disconnected due to inactivity
1085
1196
  * @returns {undefined}
1086
1197
  * @private
1087
1198
  * @memberof Meeting
1088
1199
  */
1089
- setUpLocusInfoMediaInactiveListener() {
1200
+ private setUpLocusInfoMediaInactiveListener() {
1090
1201
  // User gets kicked off the meeting due to inactivity or user did a refresh
1091
1202
  this.locusInfo.on(EVENTS.DISCONNECT_DUE_TO_INACTIVITY, (res) => {
1092
1203
  // https:// jira-eng-gpk2.cisco.com/jira/browse/SPARK-240520
@@ -1114,6 +1225,7 @@ export default class Meeting extends StatelessWebexPlugin {
1114
1225
 
1115
1226
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMediaInactiveListener --> Meeting disconnected due to inactivity: ${res.reason}`);
1116
1227
 
1228
+ // @ts-ignore - config coming from registerPlugin
1117
1229
  if (this.config.reconnection.autoRejoin) {
1118
1230
  this.reconnect();
1119
1231
  }
@@ -1137,7 +1249,7 @@ export default class Meeting extends StatelessWebexPlugin {
1137
1249
  * @private
1138
1250
  * @memberof Meeting
1139
1251
  */
1140
- setUpLocusInfoAssignHostListener() {
1252
+ private setUpLocusInfoAssignHostListener() {
1141
1253
  this.locusInfo.on(EVENTS.LOCUS_INFO_CAN_ASSIGN_HOST, (payload) => {
1142
1254
  const changed = this.inMeetingActions.set({
1143
1255
  canAssignHost: payload.canAssignHost,
@@ -1163,7 +1275,7 @@ export default class Meeting extends StatelessWebexPlugin {
1163
1275
  * @private
1164
1276
  * @memberof Meeting
1165
1277
  */
1166
- setUpLocusFullStateListener() {
1278
+ private setUpLocusFullStateListener() {
1167
1279
  this.locusInfo.on(LOCUSINFO.EVENTS.FULL_STATE_MEETING_STATE_CHANGE, (payload) => {
1168
1280
  Trigger.trigger(
1169
1281
  this,
@@ -1194,7 +1306,13 @@ export default class Meeting extends StatelessWebexPlugin {
1194
1306
  * @returns {Object}
1195
1307
  * @memberof Meeting
1196
1308
  */
1197
- getAnalyzerMetricsPrePayload(options) {
1309
+ getAnalyzerMetricsPrePayload(options: {
1310
+ event: string;
1311
+ trackingId: string;
1312
+ locus: object;
1313
+ mediaConnections: Array<any>;
1314
+ errors: object;
1315
+ } | any) {
1198
1316
  if (options) {
1199
1317
  const {
1200
1318
  event,
@@ -1208,11 +1326,12 @@ export default class Meeting extends StatelessWebexPlugin {
1208
1326
  return null;
1209
1327
  }
1210
1328
 
1211
- const identifiers = {
1329
+ const identifiers: any = {
1212
1330
  correlationId: this.correlationId,
1213
1331
  userId: this.userId,
1214
1332
  deviceId: this.deviceUrl,
1215
1333
  orgId: this.orgId,
1334
+ // @ts-ignore fix type
1216
1335
  locusUrl: this.webex.internal.services.get('locus')
1217
1336
  };
1218
1337
 
@@ -1334,12 +1453,14 @@ export default class Meeting extends StatelessWebexPlugin {
1334
1453
  * @private
1335
1454
  * @memberof Meeting
1336
1455
  */
1337
- sendCallAnalyzerMetrics(options) {
1456
+ private sendCallAnalyzerMetrics(options: { event: string; trackingId: string; locus: object; errors: object }) {
1338
1457
  const payload = this.getAnalyzerMetricsPrePayload({
1458
+ // @ts-ignore - config coming from registerPlugin
1339
1459
  ...pick(this.config.metrics, ['clientType', 'subClientType']),
1340
1460
  ...options
1341
1461
  });
1342
1462
 
1463
+ // @ts-ignore - fix type
1343
1464
  return this.webex.internal.metrics.submitCallDiagnosticEvents(payload);
1344
1465
  }
1345
1466
 
@@ -1353,13 +1474,15 @@ export default class Meeting extends StatelessWebexPlugin {
1353
1474
  * @private
1354
1475
  * @memberof Meeting
1355
1476
  */
1356
- sendMediaQualityAnalyzerMetrics(options) {
1477
+ private sendMediaQualityAnalyzerMetrics(options: { event: string; trackingId: string; locus: object }) {
1357
1478
  const payload = this.getAnalyzerMetricsPrePayload({
1358
1479
  type: MQA_STATS.CA_TYPE,
1480
+ // @ts-ignore - config coming from registerPlugin
1359
1481
  ...pick(this.config.metrics, ['clientType', 'subClientType']),
1360
1482
  ...options
1361
1483
  });
1362
1484
 
1485
+ // @ts-ignore
1363
1486
  return this.webex.internal.metrics.submitCallDiagnosticEvents(payload);
1364
1487
  }
1365
1488
 
@@ -1370,7 +1493,7 @@ export default class Meeting extends StatelessWebexPlugin {
1370
1493
  * @returns {undefined}
1371
1494
  * @memberof Meeting
1372
1495
  */
1373
- setNetworkStatus(networkStatus) {
1496
+ private setNetworkStatus(networkStatus: string) {
1374
1497
  if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
1375
1498
  Trigger.trigger(
1376
1499
  this,
@@ -1403,14 +1526,14 @@ export default class Meeting extends StatelessWebexPlugin {
1403
1526
  * @private
1404
1527
  * @memberof Meeting
1405
1528
  */
1406
- setUpLocusSelfListener() {
1529
+ private setUpLocusSelfListener() {
1407
1530
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_SELF, (payload) => {
1408
1531
  this.members.locusSelfUpdate(payload);
1409
1532
  this.pstnUpdate(payload);
1410
1533
 
1411
1534
  // If user moved to a JOINED state and there is a pending floor grant trigger it
1412
1535
  if (this.floorGrantPending && payload.newSelf.state === MEETING_STATE.STATES.JOINED) {
1413
- this.share()
1536
+ this.requestScreenShareFloor()
1414
1537
  .then(() => { this.floorGrantPending = false; });
1415
1538
  }
1416
1539
  });
@@ -1423,7 +1546,7 @@ export default class Meeting extends StatelessWebexPlugin {
1423
1546
  * @private
1424
1547
  * @memberof Meeting
1425
1548
  */
1426
- pstnUpdate(payload) {
1549
+ private pstnUpdate(payload: any) {
1427
1550
  if (this.locusInfo.self) {
1428
1551
  const dialInPstnDevice = payload.newSelf?.pstnDevices.find((device) => device.url === this.dialInUrl);
1429
1552
  const dialOutPstnDevice = payload.newSelf?.pstnDevices.find((device) => device.url === this.dialOutUrl);
@@ -1478,7 +1601,7 @@ export default class Meeting extends StatelessWebexPlugin {
1478
1601
  * @private
1479
1602
  * @memberof Meeting
1480
1603
  */
1481
- setUpLocusHostListener() {
1604
+ private setUpLocusHostListener() {
1482
1605
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_HOST, (payload) => {
1483
1606
  this.members.locusHostUpdate(payload);
1484
1607
  });
@@ -1492,13 +1615,12 @@ export default class Meeting extends StatelessWebexPlugin {
1492
1615
  * @private
1493
1616
  * @memberof Meeting
1494
1617
  */
1495
- setUpLocusParticipantsListener() {
1618
+ private setUpLocusParticipantsListener() {
1496
1619
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_PARTICIPANTS, (payload) => {
1497
1620
  this.members.locusParticipantsUpdate(payload);
1498
1621
  });
1499
1622
  }
1500
1623
 
1501
-
1502
1624
  /**
1503
1625
  * Set up the locus info recording update listener
1504
1626
  * update recording value for the meeting
@@ -1517,7 +1639,7 @@ export default class Meeting extends StatelessWebexPlugin {
1517
1639
  * @private
1518
1640
  * @memberof Meeting
1519
1641
  */
1520
- setupLocusControlsListener() {
1642
+ private setupLocusControlsListener() {
1521
1643
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_RECORDING_UPDATED,
1522
1644
  ({state, modifiedBy, lastModified}) => {
1523
1645
  let event;
@@ -1573,6 +1695,8 @@ export default class Meeting extends StatelessWebexPlugin {
1573
1695
 
1574
1696
  this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIBE_UPDATED,
1575
1697
  ({caption, transcribing}) => {
1698
+
1699
+ // @ts-ignore - config coming from registerPlugin
1576
1700
  if (transcribing && this.transcription && this.config.receiveTranscription) {
1577
1701
  this.receiveTranscription();
1578
1702
  }
@@ -1611,7 +1735,7 @@ export default class Meeting extends StatelessWebexPlugin {
1611
1735
  * @private
1612
1736
  * @memberof Meeting
1613
1737
  */
1614
- setUpLocusMediaSharesListener() {
1738
+ private setUpLocusMediaSharesListener() {
1615
1739
  // Will get triggered on local and remote share
1616
1740
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
1617
1741
  const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
@@ -1699,7 +1823,7 @@ export default class Meeting extends StatelessWebexPlugin {
1699
1823
  this,
1700
1824
  {
1701
1825
  file: 'meeting/index',
1702
- function: 'stopFloorRequest'
1826
+ function: 'localShare'
1703
1827
  },
1704
1828
  EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
1705
1829
  {
@@ -1842,7 +1966,7 @@ export default class Meeting extends StatelessWebexPlugin {
1842
1966
  * @private
1843
1967
  * @memberof Meeting
1844
1968
  */
1845
- setUpLocusUrlListener() {
1969
+ private setUpLocusUrlListener() {
1846
1970
  this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
1847
1971
  this.members.locusUrlUpdate(payload);
1848
1972
  this.locusUrl = payload;
@@ -1856,7 +1980,7 @@ export default class Meeting extends StatelessWebexPlugin {
1856
1980
  * @private
1857
1981
  * @memberof meeting
1858
1982
  */
1859
- setUpLocusInfoMeetingInfoListener() {
1983
+ private setUpLocusInfoMeetingInfoListener() {
1860
1984
  this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_LOCKED, (payload) => {
1861
1985
  if (payload) {
1862
1986
  Trigger.trigger(
@@ -1924,17 +2048,34 @@ export default class Meeting extends StatelessWebexPlugin {
1924
2048
  this.inMeetingActions.get()
1925
2049
  );
1926
2050
  }
2051
+
2052
+ this.handleDataChannelUrlChange(payload.info.datachannelUrl);
1927
2053
  }
1928
2054
  });
1929
2055
  }
1930
2056
 
2057
+ /**
2058
+ * Handles a data channel URL change
2059
+ * @param {String} datachannelUrl
2060
+ * @returns {void}
2061
+ */
2062
+ handleDataChannelUrlChange(datachannelUrl) {
2063
+ if (datachannelUrl && this.config.enableAutomaticLLM) {
2064
+ // Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
2065
+ // after the MEETING_INFO_UPDATED callback finishes
2066
+ defer(() => {
2067
+ this.updateLLMConnection();
2068
+ });
2069
+ }
2070
+ }
2071
+
1931
2072
  /**
1932
2073
  * Set up the locus info embedded apps listener
1933
2074
  * @returns {undefined}
1934
2075
  * @private
1935
2076
  * @memberof meeting
1936
2077
  */
1937
- setUpLocusEmbeddedAppsListener() {
2078
+ private setUpLocusEmbeddedAppsListener() {
1938
2079
  this.locusInfo.on(LOCUSINFO.EVENTS.EMBEDDED_APPS_UPDATED, (embeddedApps) => {
1939
2080
  if (embeddedApps) {
1940
2081
  Trigger.trigger(
@@ -1956,7 +2097,7 @@ export default class Meeting extends StatelessWebexPlugin {
1956
2097
  * @private
1957
2098
  * @memberof Meeting
1958
2099
  */
1959
- setUpLocusInfoSelfListener() {
2100
+ private setUpLocusInfoSelfListener() {
1960
2101
  this.locusInfo.on(LOCUSINFO.EVENTS.LOCAL_UNMUTE_REQUIRED, (payload) => {
1961
2102
  if (this.audio) {
1962
2103
  this.audio.handleServerLocalUnmuteRequired(this);
@@ -2054,6 +2195,7 @@ export default class Meeting extends StatelessWebexPlugin {
2054
2195
  }
2055
2196
  });
2056
2197
 
2198
+ // @ts-ignore - check if MEDIA_INACTIVITY exists
2057
2199
  this.locusInfo.on(LOCUSINFO.EVENTS.MEDIA_INACTIVITY, () => {
2058
2200
  Metrics.sendBehavioralMetric(
2059
2201
  BEHAVIORAL_METRICS.MEETING_MEDIA_INACTIVE,
@@ -2120,7 +2262,7 @@ export default class Meeting extends StatelessWebexPlugin {
2120
2262
  * @private
2121
2263
  * @memberof Meeting
2122
2264
  */
2123
- setUpLocusInfoMeetingListener() {
2265
+ private setUpLocusInfoMeetingListener() {
2124
2266
  this.locusInfo.on(EVENTS.REMOTE_RESPONSE, (payload) => {
2125
2267
  this.meetingFiniteStateMachine.remote(payload);
2126
2268
 
@@ -2128,6 +2270,7 @@ export default class Meeting extends StatelessWebexPlugin {
2128
2270
  this.leave({reason: payload.reason}).then(() => {
2129
2271
  LoggerProxy.logger.info('Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Attempting to leave meeting.');
2130
2272
  }).catch((error) => {
2273
+ // @ts-ignore
2131
2274
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`);
2132
2275
  });
2133
2276
  }
@@ -2156,6 +2299,7 @@ export default class Meeting extends StatelessWebexPlugin {
2156
2299
  this.leave({reason: payload.reason}).then(() => {
2157
2300
  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.');
2158
2301
  }).catch((error) => {
2302
+ // @ts-ignore
2159
2303
  LoggerProxy.logger.error(`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this.meeting}, error: ${error}`);
2160
2304
  });
2161
2305
  }
@@ -2187,7 +2331,7 @@ export default class Meeting extends StatelessWebexPlugin {
2187
2331
  * @memberof Meeting
2188
2332
  * // TODO: is this function necessary?
2189
2333
  */
2190
- updateMeetingObject(object) {
2334
+ private updateMeetingObject(object: object) {
2191
2335
  // Validate if these are valid meeting object property
2192
2336
  // TODO: add a check to make sure the value passed in the constructor
2193
2337
  // is not changed by any delta event
@@ -2209,7 +2353,14 @@ export default class Meeting extends StatelessWebexPlugin {
2209
2353
  * @public
2210
2354
  * @memberof Meeting
2211
2355
  */
2212
- invite(invitee, alertIfActive = true) {
2356
+ public invite(
2357
+ invitee: {
2358
+ emailAddress: string;
2359
+ email: string;
2360
+ phoneNumber: string;
2361
+ },
2362
+ alertIfActive: boolean = true
2363
+ ) {
2213
2364
  return this.members.addMember(invitee, alertIfActive);
2214
2365
  }
2215
2366
 
@@ -2221,7 +2372,7 @@ export default class Meeting extends StatelessWebexPlugin {
2221
2372
  * @public
2222
2373
  * @memberof Meeting
2223
2374
  */
2224
- cancelPhoneInvite(invitee) {
2375
+ public cancelPhoneInvite(invitee: { phoneNumber: string }) {
2225
2376
  return this.members.cancelPhoneInvite(invitee);
2226
2377
  }
2227
2378
 
@@ -2232,7 +2383,7 @@ export default class Meeting extends StatelessWebexPlugin {
2232
2383
  * @public
2233
2384
  * @memberof Meeting
2234
2385
  */
2235
- admit(memberIds) {
2386
+ public admit(memberIds: Array<any>) {
2236
2387
  return this.members.admitMembers(memberIds);
2237
2388
  }
2238
2389
 
@@ -2243,7 +2394,7 @@ export default class Meeting extends StatelessWebexPlugin {
2243
2394
  * @public
2244
2395
  * @memberof Meeting
2245
2396
  */
2246
- remove(memberId) {
2397
+ public remove(memberId: string) {
2247
2398
  return this.members.removeMember(memberId);
2248
2399
  }
2249
2400
 
@@ -2255,7 +2406,7 @@ export default class Meeting extends StatelessWebexPlugin {
2255
2406
  * @public
2256
2407
  * @memberof Meeting
2257
2408
  */
2258
- mute(memberId, mute = true) {
2409
+ public mute(memberId: string, mute: boolean = true) {
2259
2410
  return this.members.muteMember(memberId, mute);
2260
2411
  }
2261
2412
 
@@ -2267,7 +2418,7 @@ export default class Meeting extends StatelessWebexPlugin {
2267
2418
  * @public
2268
2419
  * @memberof Meeting
2269
2420
  */
2270
- transfer(memberId, moderator = true) {
2421
+ public transfer(memberId: string, moderator: boolean = true) {
2271
2422
  return this.members.transferHostToMember(memberId, moderator);
2272
2423
  }
2273
2424
 
@@ -2277,7 +2428,7 @@ export default class Meeting extends StatelessWebexPlugin {
2277
2428
  * @public
2278
2429
  * @memberof Meeting
2279
2430
  */
2280
- getMembers() {
2431
+ public getMembers() {
2281
2432
  return this.members;
2282
2433
  }
2283
2434
 
@@ -2287,7 +2438,7 @@ export default class Meeting extends StatelessWebexPlugin {
2287
2438
  * @public
2288
2439
  * @memberof Meeting
2289
2440
  */
2290
- isAudioConnected() {
2441
+ public isAudioConnected() {
2291
2442
  return !!this.audio;
2292
2443
  }
2293
2444
 
@@ -2297,7 +2448,7 @@ export default class Meeting extends StatelessWebexPlugin {
2297
2448
  * @public
2298
2449
  * @memberof Meeting
2299
2450
  */
2300
- isAudioMuted() {
2451
+ public isAudioMuted() {
2301
2452
  return this.audio && this.audio.isMuted();
2302
2453
  }
2303
2454
 
@@ -2307,7 +2458,7 @@ export default class Meeting extends StatelessWebexPlugin {
2307
2458
  * @public
2308
2459
  * @memberof Meeting
2309
2460
  */
2310
- isAudioSelf() {
2461
+ public isAudioSelf() {
2311
2462
  return this.audio && this.audio.isSelf();
2312
2463
  }
2313
2464
 
@@ -2317,7 +2468,7 @@ export default class Meeting extends StatelessWebexPlugin {
2317
2468
  * @public
2318
2469
  * @memberof Meeting
2319
2470
  */
2320
- isVideoConnected() {
2471
+ public isVideoConnected() {
2321
2472
  return !!this.video;
2322
2473
  }
2323
2474
 
@@ -2327,7 +2478,7 @@ export default class Meeting extends StatelessWebexPlugin {
2327
2478
  * @public
2328
2479
  * @memberof Meeting
2329
2480
  */
2330
- isVideoMuted() {
2481
+ public isVideoMuted() {
2331
2482
  return this.video && this.video.isMuted();
2332
2483
  }
2333
2484
 
@@ -2337,7 +2488,7 @@ export default class Meeting extends StatelessWebexPlugin {
2337
2488
  * @public
2338
2489
  * @memberof Meeting
2339
2490
  */
2340
- isVideoSelf() {
2491
+ public isVideoSelf() {
2341
2492
  return this.video && this.video.isSelf();
2342
2493
  }
2343
2494
 
@@ -2354,7 +2505,17 @@ export default class Meeting extends StatelessWebexPlugin {
2354
2505
  * @private
2355
2506
  * @memberof Meeting
2356
2507
  */
2357
- parseMeetingInfo(meetingInfo, destination = null) {
2508
+ parseMeetingInfo(
2509
+ meetingInfo: {
2510
+ body: {
2511
+ conversationUrl: string;
2512
+ locusUrl: string;
2513
+ sipUri: string;
2514
+ owner: object;
2515
+ };
2516
+ } | any,
2517
+ destination: object | string | null = null
2518
+ ) {
2358
2519
  const webexMeetingInfo = meetingInfo?.body;
2359
2520
  // We try to use as much info from Locus meeting object, stored in destination
2360
2521
 
@@ -2368,7 +2529,9 @@ export default class Meeting extends StatelessWebexPlugin {
2368
2529
  if (locusMeetingObject || (webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))) {
2369
2530
  this.conversationUrl = locusMeetingObject?.conversationUrl || webexMeetingInfo?.conversationUrl || this.conversationUrl;
2370
2531
  this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
2532
+ // @ts-ignore - config coming from registerPlugin
2371
2533
  this.setSipUri(this.config.experimental.enableUnifiedMeetings ? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl : locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri);
2534
+ // @ts-ignore - config coming from registerPlugin
2372
2535
  if (this.config.experimental.enableUnifiedMeetings) {
2373
2536
  this.meetingNumber = locusMeetingObject?.info.webExMeetingId || webexMeetingInfo?.meetingNumber;
2374
2537
  this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
@@ -2388,7 +2551,7 @@ export default class Meeting extends StatelessWebexPlugin {
2388
2551
  * @private
2389
2552
  * @memberof Meeting
2390
2553
  */
2391
- parseLocus(locus) {
2554
+ private parseLocus(locus: { url: string; participants: Array<any>; self: object }) {
2392
2555
  if (locus) {
2393
2556
  this.locusUrl = locus.url;
2394
2557
  // TODO: move this to parse participants module
@@ -2415,24 +2578,11 @@ export default class Meeting extends StatelessWebexPlugin {
2415
2578
  * @private
2416
2579
  * @memberof Meeting
2417
2580
  */
2418
- setSipUri(sipUri) {
2581
+ setSipUri(sipUri: string) {
2419
2582
  // This can be tel no, device id or a sip uri, user Id
2420
2583
  this.sipUri = sipUri;
2421
2584
  }
2422
2585
 
2423
- /**
2424
- * Set the roap seq on the class instance
2425
- * @param {Number} seq
2426
- * @returns {undefined}
2427
- * @private
2428
- * @memberof Meeting
2429
- */
2430
- setRoapSeq(seq) {
2431
- if (seq >= 0) {
2432
- this.roapSeq = seq;
2433
- }
2434
- }
2435
-
2436
2586
  /**
2437
2587
  * Set the locus info the class instance
2438
2588
  * @param {Object} locus
@@ -2446,8 +2596,14 @@ export default class Meeting extends StatelessWebexPlugin {
2446
2596
  * @private
2447
2597
  * @memberof Meeting
2448
2598
  */
2449
- setLocus(locus) {
2450
- const mtgLocus = locus.locus || locus;
2599
+ private setLocus(locus: {
2600
+ mediaConnections: Array<any>;
2601
+ locusUrl: string;
2602
+ locusId: string;
2603
+ mediaId: string;
2604
+ host: object;
2605
+ } | any) {
2606
+ const mtgLocus: any = locus.locus || locus;
2451
2607
 
2452
2608
  // LocusInfo object saves the locus object
2453
2609
  // this.locus = mtgLocus;
@@ -2460,111 +2616,6 @@ export default class Meeting extends StatelessWebexPlugin {
2460
2616
  this.locusInfo.initialSetup(mtgLocus);
2461
2617
  }
2462
2618
 
2463
- /**
2464
- * Sets the remote stream on the class instance and emits and
2465
- * event to developers
2466
- * @param {Object} pc The remote stream peer connection
2467
- * @returns {undefined}
2468
- * @public
2469
- * @memberof Meeting
2470
- */
2471
- setRemoteStream(pc) {
2472
- if (!pc) {
2473
- return;
2474
- }
2475
- // eslint-disable-next-line no-param-reassign
2476
- pc.ontrack = (event) => {
2477
- // eslint-disable-next-line no-warning-comments
2478
- // TODO: It's possible for media to not be present
2479
- // so we might need to either
2480
- // A) wait until we have media flowing
2481
- // B) trigger a second event when video is flowing
2482
- LoggerProxy.logger.log(`Meeting:index#setRemoteStream --> ontrack event received for peerConnection: ${event}`);
2483
-
2484
- const MEDIA_ID = {
2485
- AUDIO_TRACK: '0',
2486
- VIDEO_TRACK: '1',
2487
- SHARE_TRACK: '2'
2488
- };
2489
- let eventType = null;
2490
- const mediaTrack = event.track;
2491
- let trackMediaID = null;
2492
-
2493
-
2494
- // In case of safari some time the transceiver is not present for specific os version
2495
- // sdk tries to determine the transceive using the track id present
2496
- if (event.transceiver && event.transceiver.mid) {
2497
- trackMediaID = event.transceiver.mid;
2498
- }
2499
- else {
2500
- const {audioTransceiver, videoTransceiver, shareTransceiver} = event.target;
2501
-
2502
- // audio kind indicates its a audio stream
2503
- if (mediaTrack.id === audioTransceiver.receiver.track.id) {
2504
- trackMediaID = '0';
2505
- }
2506
- else
2507
- if (mediaTrack.id === videoTransceiver.receiver.track.id) {
2508
- trackMediaID = '1';
2509
- }
2510
- else
2511
- if (mediaTrack.id === shareTransceiver.receiver.track.id) {
2512
- trackMediaID = '2';
2513
- }
2514
- else {
2515
- trackMediaID = null;
2516
- Metrics.sendBehavioralMetric(
2517
- BEHAVIORAL_METRICS.MUTE_AUDIO_FAILURE,
2518
- {
2519
- correlation_id: this.correlationId,
2520
- locus_id: this.locusUrl.split('/').pop()
2521
- }
2522
- );
2523
- }
2524
- }
2525
-
2526
-
2527
- switch (trackMediaID) {
2528
- case MEDIA_ID.AUDIO_TRACK:
2529
- eventType = EVENT_TYPES.REMOTE_AUDIO;
2530
- this.mediaProperties.setRemoteAudioTrack(mediaTrack);
2531
- break;
2532
- case MEDIA_ID.VIDEO_TRACK:
2533
- eventType = EVENT_TYPES.REMOTE_VIDEO;
2534
- this.mediaProperties.setRemoteVideoTrack(mediaTrack);
2535
- break;
2536
- case MEDIA_ID.SHARE_TRACK:
2537
- if (event.track) {
2538
- eventType = EVENT_TYPES.REMOTE_SHARE;
2539
- this.mediaProperties.setRemoteShare(mediaTrack);
2540
- }
2541
- break;
2542
- default: {
2543
- LoggerProxy.logger.log('Meeting:index#setRemoteStream --> no matching media track id');
2544
- }
2545
- }
2546
-
2547
- // start stats here the stats are coming null if you dont receive streams
2548
-
2549
- this.statsAnalyzer.startAnalyzer(this.mediaProperties.peerConnection);
2550
-
2551
- if (eventType && mediaTrack) {
2552
- Trigger.trigger(
2553
- this,
2554
- {
2555
- file: 'meeting/index',
2556
- function: 'setRemoteStream:pc.ontrack'
2557
- },
2558
- EVENT_TRIGGERS.MEDIA_READY,
2559
- {
2560
- type: eventType,
2561
- stream: MediaUtil.createMediaStream([mediaTrack])
2562
- }
2563
- );
2564
- }
2565
- };
2566
- }
2567
-
2568
2619
  /**
2569
2620
  * Upload logs for the current meeting
2570
2621
  * @param {object} options file name and function name
@@ -2572,7 +2623,7 @@ export default class Meeting extends StatelessWebexPlugin {
2572
2623
  * @public
2573
2624
  * @memberof Meeting
2574
2625
  */
2575
- uploadLogs(options = {file: 'meeting/index', function: 'uploadLogs'}) {
2626
+ public uploadLogs(options: object = { file: 'meeting/index', function: 'uploadLogs' }) {
2576
2627
  Trigger.trigger(
2577
2628
  this,
2578
2629
  options,
@@ -2581,7 +2632,6 @@ export default class Meeting extends StatelessWebexPlugin {
2581
2632
  );
2582
2633
  }
2583
2634
 
2584
-
2585
2635
  /**
2586
2636
  * Removes remote audio and video stream on the class instance and triggers an event
2587
2637
  * to developers
@@ -2590,7 +2640,7 @@ export default class Meeting extends StatelessWebexPlugin {
2590
2640
  * @memberof Meeting
2591
2641
  * @deprecated after v1.89.3
2592
2642
  */
2593
- unsetRemoteStream() {
2643
+ public unsetRemoteStream() {
2594
2644
  LoggerProxy.logger.warn('Meeting:index#unsetRemoteStream --> [DEPRECATION WARNING]: unsetRemoteStream has been deprecated after v1.89.3');
2595
2645
  this.mediaProperties.unsetRemoteMedia();
2596
2646
  }
@@ -2611,7 +2661,7 @@ export default class Meeting extends StatelessWebexPlugin {
2611
2661
  * @memberof Meeting
2612
2662
  * @deprecated after v1.89.3
2613
2663
  */
2614
- closeRemoteStream() {
2664
+ public closeRemoteStream() {
2615
2665
  LoggerProxy.logger.warn('Meeting:index#closeRemoteStream --> [DEPRECATION WARNING]: closeRemoteStream has been deprecated after v1.89.3');
2616
2666
  this.closeRemoteTracks();
2617
2667
  }
@@ -2635,7 +2685,7 @@ export default class Meeting extends StatelessWebexPlugin {
2635
2685
  * @returns {void}
2636
2686
  * @inner
2637
2687
  */
2638
- const triggerMediaStoppedEvent = (mediaType) => {
2688
+ const triggerMediaStoppedEvent = (mediaType: string) => {
2639
2689
  Trigger.trigger(
2640
2690
  this,
2641
2691
  {
@@ -2657,7 +2707,7 @@ export default class Meeting extends StatelessWebexPlugin {
2657
2707
  * @inner
2658
2708
  */
2659
2709
  // eslint-disable-next-line arrow-body-style
2660
- const stopTrack = (track, type) => {
2710
+ const stopTrack = (track: MediaStreamTrack, type: string) => {
2661
2711
  return Media.stopTracks(track)
2662
2712
  .then(() => {
2663
2713
  const isTrackStopped = track && track.readyState === ENDED;
@@ -2685,7 +2735,7 @@ export default class Meeting extends StatelessWebexPlugin {
2685
2735
  * @private
2686
2736
  * @memberof Meeting
2687
2737
  */
2688
- sendLocalMediaReadyEvent() {
2738
+ private sendLocalMediaReadyEvent() {
2689
2739
  Trigger.trigger(
2690
2740
  this,
2691
2741
  {
@@ -2708,7 +2758,7 @@ export default class Meeting extends StatelessWebexPlugin {
2708
2758
  * @private
2709
2759
  * @memberof Meeting
2710
2760
  */
2711
- setLocalAudioTrack(audioTrack, emitEvent = true) {
2761
+ private setLocalAudioTrack(audioTrack: MediaStreamTrack, emitEvent: boolean = true) {
2712
2762
  if (audioTrack) {
2713
2763
  const settings = audioTrack.getSettings();
2714
2764
 
@@ -2735,7 +2785,7 @@ export default class Meeting extends StatelessWebexPlugin {
2735
2785
  * @private
2736
2786
  * @memberof Meeting
2737
2787
  */
2738
- setLocalVideoTrack(videoTrack, emitEvent = true) {
2788
+ private setLocalVideoTrack(videoTrack: MediaStreamTrack, emitEvent: boolean = true) {
2739
2789
  if (videoTrack) {
2740
2790
  const {
2741
2791
  aspectRatio, frameRate, height, width, deviceId
@@ -2775,7 +2825,7 @@ export default class Meeting extends StatelessWebexPlugin {
2775
2825
  * @public
2776
2826
  * @memberof Meeting
2777
2827
  */
2778
- setLocalTracks(localStream) {
2828
+ public setLocalTracks(localStream: any) {
2779
2829
  if (localStream) {
2780
2830
  const {audioTrack, videoTrack} = MeetingUtil.getTrack(localStream);
2781
2831
 
@@ -2788,12 +2838,12 @@ export default class Meeting extends StatelessWebexPlugin {
2788
2838
 
2789
2839
  /**
2790
2840
  * Sets the local media stream on the class and emits an event to the developer
2791
- * @param {Stream} localShare the local media stream
2841
+ * @param {MediaStream} localShare the local media stream
2792
2842
  * @returns {undefined}
2793
2843
  * @public
2794
2844
  * @memberof Meeting
2795
2845
  */
2796
- setLocalShareTrack(localShare) {
2846
+ public setLocalShareTrack(localShare: MediaStream) {
2797
2847
  let settings = null;
2798
2848
 
2799
2849
  if (localShare) {
@@ -2837,7 +2887,7 @@ export default class Meeting extends StatelessWebexPlugin {
2837
2887
  * @public
2838
2888
  * @memberof Meeting
2839
2889
  */
2840
- closeLocalStream() {
2890
+ public closeLocalStream() {
2841
2891
  const {audioTrack, videoTrack} = this.mediaProperties;
2842
2892
 
2843
2893
  return Media.stopTracks(audioTrack)
@@ -2872,7 +2922,7 @@ export default class Meeting extends StatelessWebexPlugin {
2872
2922
  * @public
2873
2923
  * @memberof Meeting
2874
2924
  */
2875
- closeLocalShare() {
2925
+ public closeLocalShare() {
2876
2926
  const track = this.mediaProperties.shareTrack;
2877
2927
 
2878
2928
  return Media.stopTracks(track).then(() => {
@@ -2901,7 +2951,7 @@ export default class Meeting extends StatelessWebexPlugin {
2901
2951
  * @public
2902
2952
  * @memberof Meeting
2903
2953
  */
2904
- unsetLocalVideoTrack() {
2954
+ public unsetLocalVideoTrack() {
2905
2955
  this.mediaProperties.unsetLocalVideoTrack();
2906
2956
  }
2907
2957
 
@@ -2911,7 +2961,7 @@ export default class Meeting extends StatelessWebexPlugin {
2911
2961
  * @public
2912
2962
  * @memberof Meeting
2913
2963
  */
2914
- unsetLocalShareTrack() {
2964
+ public unsetLocalShareTrack() {
2915
2965
  this.mediaProperties.unsetLocalShareTrack();
2916
2966
  }
2917
2967
 
@@ -2921,9 +2971,10 @@ export default class Meeting extends StatelessWebexPlugin {
2921
2971
  * @public
2922
2972
  * @memberof Meeting
2923
2973
  */
2924
- setMercuryListener() {
2974
+ public setMercuryListener() {
2925
2975
  // Client will have a socket manager and handle reconnecting to mercury, when we reconnect to mercury
2926
2976
  // if the meeting has active peer connections, it should try to reconnect.
2977
+ // @ts-ignore
2927
2978
  this.webex.internal.mercury.on(ONLINE, () => {
2928
2979
  LoggerProxy.logger.info('Meeting:index#setMercuryListener --> Web socket online');
2929
2980
 
@@ -2943,6 +2994,7 @@ export default class Meeting extends StatelessWebexPlugin {
2943
2994
  this.hasWebsocketConnected = true;
2944
2995
  });
2945
2996
 
2997
+ // @ts-ignore
2946
2998
  this.webex.internal.mercury.on(OFFLINE, () => {
2947
2999
  LoggerProxy.logger.error('Meeting:index#setMercuryListener --> Web socket offline');
2948
3000
  Metrics.postEvent({
@@ -2959,27 +3011,41 @@ export default class Meeting extends StatelessWebexPlugin {
2959
3011
  }
2960
3012
 
2961
3013
  /**
2962
- * Close the peer connections and remove them from the class. Triggers an event
2963
- * when each is closed.
2964
- * @returns {Promise} returns a resolved promise with an array of closed peer connections
3014
+ * Close the peer connections and remove them from the class.
3015
+ * Cleanup any media connection related things.
3016
+ *
3017
+ * @returns {Promise}
2965
3018
  * @public
2966
3019
  * @memberof Meeting
2967
3020
  */
2968
- closePeerConnections() {
2969
- return PeerConnectionManager.close(this.mediaProperties.peerConnection);
3021
+ public closePeerConnections() {
3022
+ if (this.mediaProperties.webrtcMediaConnection) {
3023
+ if (this.remoteMediaManager) {
3024
+ this.remoteMediaManager.stop();
3025
+ this.remoteMediaManager = null;
3026
+ }
3027
+
3028
+ Object.values(this.mediaRequestManagers).forEach((mediaRequestManager) => mediaRequestManager.reset());
3029
+
3030
+ this.receiveSlotManager.reset();
3031
+ this.mediaProperties.webrtcMediaConnection.close();
3032
+ }
3033
+
3034
+ return Promise.resolve();
2970
3035
  }
2971
3036
 
2972
3037
  /**
2973
3038
  * Unsets the peer connections on the class
2974
3039
  * warning DO NOT CALL WITHOUT CLOSING PEER CONNECTIONS FIRST
2975
- * @param {PeerConnection} peerConnection
2976
3040
  * @returns {undefined}
2977
3041
  * @public
2978
3042
  * @memberof Meeting
2979
3043
  */
2980
- unsetPeerConnections() {
3044
+ public unsetPeerConnections() {
2981
3045
  this.mediaProperties.unsetPeerConnection();
3046
+ // @ts-ignore - config coming from registerPlugin
2982
3047
  if (this.config.reconnection.detection) {
3048
+ // @ts-ignore
2983
3049
  this.webex.internal.mercury.off(ONLINE);
2984
3050
  }
2985
3051
  }
@@ -2991,7 +3057,7 @@ export default class Meeting extends StatelessWebexPlugin {
2991
3057
  * @private
2992
3058
  * @memberof Meeting
2993
3059
  */
2994
- setCorrelationId(id) {
3060
+ private setCorrelationId(id: string) {
2995
3061
  this.correlationId = id;
2996
3062
  }
2997
3063
 
@@ -3001,11 +3067,12 @@ export default class Meeting extends StatelessWebexPlugin {
3001
3067
  * @public
3002
3068
  * @memberof Meeting
3003
3069
  */
3004
- muteAudio() {
3070
+ public muteAudio() {
3005
3071
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3006
3072
  return Promise.reject(new UserNotJoinedError());
3007
3073
  }
3008
3074
 
3075
+ // @ts-ignore
3009
3076
  if (!this.mediaId) {
3010
3077
  // Happens when addMedia and mute are triggered in succession
3011
3078
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3052,11 +3119,12 @@ export default class Meeting extends StatelessWebexPlugin {
3052
3119
  * @public
3053
3120
  * @memberof Meeting
3054
3121
  */
3055
- unmuteAudio() {
3122
+ public unmuteAudio() {
3056
3123
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3057
3124
  return Promise.reject(new UserNotJoinedError());
3058
3125
  }
3059
3126
 
3127
+ // @ts-ignore
3060
3128
  if (!this.mediaId) {
3061
3129
  // Happens when addMedia and mute are triggered in succession
3062
3130
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3103,11 +3171,12 @@ export default class Meeting extends StatelessWebexPlugin {
3103
3171
  * @public
3104
3172
  * @memberof Meeting
3105
3173
  */
3106
- muteVideo() {
3174
+ public muteVideo() {
3107
3175
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3108
3176
  return Promise.reject(new UserNotJoinedError());
3109
3177
  }
3110
3178
 
3179
+ // @ts-ignore
3111
3180
  if (!this.mediaId) {
3112
3181
  // Happens when addMedia and mute are triggered in succession
3113
3182
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3153,11 +3222,12 @@ export default class Meeting extends StatelessWebexPlugin {
3153
3222
  * @public
3154
3223
  * @memberof Meeting
3155
3224
  */
3156
- unmuteVideo() {
3225
+ public unmuteVideo() {
3157
3226
  if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
3158
3227
  return Promise.reject(new UserNotJoinedError());
3159
3228
  }
3160
3229
 
3230
+ // @ts-ignore
3161
3231
  if (!this.mediaId) {
3162
3232
  // Happens when addMedia and mute are triggered in succession
3163
3233
  return Promise.reject(new NoMediaEstablishedYetError());
@@ -3222,7 +3292,13 @@ export default class Meeting extends StatelessWebexPlugin {
3222
3292
  * video: 'videoDeviceId'
3223
3293
  * }})
3224
3294
  */
3225
- joinWithMedia(options = {}) {
3295
+ public joinWithMedia(
3296
+ options: {
3297
+ joinOptions?: any;
3298
+ mediaSettings: any;
3299
+ audioVideoOptions?: any;
3300
+ } = {} as any
3301
+ ) {
3226
3302
  // TODO: add validations for parameters
3227
3303
  const {mediaSettings, joinOptions, audioVideoOptions} = options;
3228
3304
 
@@ -3266,19 +3342,21 @@ export default class Meeting extends StatelessWebexPlugin {
3266
3342
  * @public
3267
3343
  * @memberof Meeting
3268
3344
  */
3269
- reconnect(options) {
3345
+ public reconnect(options?: object) {
3270
3346
  LoggerProxy.logger.log(`Meeting:index#reconnect --> attempting to reconnect meeting ${this.id}`);
3271
3347
 
3272
3348
  if (!this.reconnectionManager || !this.reconnectionManager.reconnect) {
3273
3349
  return Promise.reject(new ParameterError('Cannot reconnect, ReconnectionManager must first be defined.'));
3274
3350
  }
3275
3351
 
3352
+ // @ts-ignore - currentMediaStatus coming from SelfUtil
3276
3353
  if (!MeetingUtil.isMediaEstablished(this.currentMediaStatus)) {
3277
3354
  return Promise.reject(new ParameterError('Cannot reconnect, Media has not established to reconnect'));
3278
3355
  }
3279
3356
 
3280
3357
  try {
3281
3358
  LoggerProxy.logger.info('Meeting:index#reconnect --> Validating reconnect ability.');
3359
+ // @ts-ignore
3282
3360
  this.reconnectionManager.validate();
3283
3361
  }
3284
3362
  catch (error) {
@@ -3302,7 +3380,6 @@ export default class Meeting extends StatelessWebexPlugin {
3302
3380
  EVENT_TRIGGERS.MEETING_RECONNECTION_STARTING
3303
3381
  );
3304
3382
 
3305
-
3306
3383
  return this.reconnectionManager
3307
3384
  .reconnect(options)
3308
3385
  .then(() => {
@@ -3375,7 +3452,7 @@ export default class Meeting extends StatelessWebexPlugin {
3375
3452
  * @private
3376
3453
  * @returns {void}
3377
3454
  */
3378
- monitorTranscriptionSocketConnection() {
3455
+ private monitorTranscriptionSocketConnection() {
3379
3456
  this.transcription.onCloseSocket((event) => {
3380
3457
  LoggerProxy.logger.info(
3381
3458
  `Meeting:index#onCloseSocket -->
@@ -3413,7 +3490,7 @@ export default class Meeting extends StatelessWebexPlugin {
3413
3490
  * @private
3414
3491
  * @returns {Promise<void>} a promise to open the WebSocket connection
3415
3492
  */
3416
- async receiveTranscription() {
3493
+ private async receiveTranscription() {
3417
3494
  LoggerProxy.logger.info(
3418
3495
  `Meeting:index#receiveTranscription -->
3419
3496
  Attempting to generate a web socket url.`
@@ -3421,6 +3498,7 @@ export default class Meeting extends StatelessWebexPlugin {
3421
3498
 
3422
3499
  try {
3423
3500
  const {datachannelUrl} = this.locusInfo.info;
3501
+ // @ts-ignore - fix type
3424
3502
  const {body: {webSocketUrl}} = await this.request({
3425
3503
  method: HTTP_VERBS.POST,
3426
3504
  uri: datachannelUrl,
@@ -3434,6 +3512,7 @@ export default class Meeting extends StatelessWebexPlugin {
3434
3512
 
3435
3513
  this.transcription = new Transcription(
3436
3514
  webSocketUrl,
3515
+ // @ts-ignore - fix type
3437
3516
  this.webex.sessionId,
3438
3517
  this.members,
3439
3518
  );
@@ -3457,6 +3536,7 @@ export default class Meeting extends StatelessWebexPlugin {
3457
3536
  });
3458
3537
 
3459
3538
  this.monitorTranscriptionSocketConnection();
3539
+ // @ts-ignore - fix type
3460
3540
  this.transcription.connect(this.webex.credentials.supertoken.access_token);
3461
3541
  }
3462
3542
  catch (error) {
@@ -3489,12 +3569,11 @@ export default class Meeting extends StatelessWebexPlugin {
3489
3569
  * @private
3490
3570
  * @returns{void}
3491
3571
  */
3492
- triggerStopReceivingTranscriptionEvent() {
3572
+ private triggerStopReceivingTranscriptionEvent() {
3493
3573
  LoggerProxy.logger.info(`
3494
3574
  Meeting:index#stopReceivingTranscription -->
3495
3575
  closed transcription LLM web socket connection successfully.`);
3496
3576
 
3497
-
3498
3577
  Trigger.trigger(
3499
3578
  this,
3500
3579
  {
@@ -3517,7 +3596,8 @@ export default class Meeting extends StatelessWebexPlugin {
3517
3596
  * if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
3518
3597
  * Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
3519
3598
  */
3520
- join(options = {}) {
3599
+ public join(options: any = {}) {
3600
+ // @ts-ignore - fix type
3521
3601
  if (!this.webex.meetings.registered) {
3522
3602
  const errorMessage = 'Meeting:index#join --> Device not registered';
3523
3603
  const error = new Error(errorMessage);
@@ -3623,6 +3703,8 @@ export default class Meeting extends StatelessWebexPlugin {
3623
3703
  }
3624
3704
  }
3625
3705
 
3706
+ this.isMultistream = !!options.enableMultistream;
3707
+
3626
3708
  return MeetingUtil.joinMeetingOptions(this, options)
3627
3709
  .then((join) => {
3628
3710
  this.meetingFiniteStateMachine.join();
@@ -3641,8 +3723,17 @@ export default class Meeting extends StatelessWebexPlugin {
3641
3723
  );
3642
3724
 
3643
3725
  return join;
3644
- }).then(async (join) => {
3726
+ })
3727
+ .then(async (join) => {
3728
+ if (this.config.enableAutomaticLLM) {
3729
+ await this.updateLLMConnection();
3730
+ }
3731
+
3732
+ return join;
3733
+ })
3734
+ .then(async (join) => {
3645
3735
  if (isBrowser) {
3736
+ // @ts-ignore - config coming from registerPlugin
3646
3737
  if (this.config.receiveTranscription || options.receiveTranscription) {
3647
3738
  if (this.isTranscriptionSupported()) {
3648
3739
  await this.receiveTranscription();
@@ -3654,7 +3745,6 @@ export default class Meeting extends StatelessWebexPlugin {
3654
3745
  LoggerProxy.logger.error('Meeting:index#join --> Receving transcription is not supported on this platform');
3655
3746
  }
3656
3747
 
3657
-
3658
3748
  return join;
3659
3749
  })
3660
3750
  .catch((error) => {
@@ -3700,6 +3790,31 @@ export default class Meeting extends StatelessWebexPlugin {
3700
3790
  });
3701
3791
  }
3702
3792
 
3793
+ /**
3794
+ * Connects to low latency mercury and reconnects if the address has changed
3795
+ * It will also disconnect if called when the meeting has ended
3796
+ * @param {String} datachannelUrl
3797
+ * @returns {Promise}
3798
+ */
3799
+ async updateLLMConnection() {
3800
+ const {url, info: {datachannelUrl} = {}} = this.locusInfo;
3801
+
3802
+ const isJoined = this.joinedWith && this.joinedWith.state === 'JOINED';
3803
+
3804
+ if (this.webex.internal.llm.isConnected()) {
3805
+ if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
3806
+ return undefined;
3807
+ }
3808
+ await this.webex.internal.llm.disconnectLLM();
3809
+ }
3810
+
3811
+ if (!isJoined) {
3812
+ return undefined;
3813
+ }
3814
+
3815
+ return this.webex.internal.llm.registerAndConnect(url, datachannelUrl);
3816
+ }
3817
+
3703
3818
  /**
3704
3819
  * Use phone for meeting audio
3705
3820
  * @param {String} phoneNumber If provided, it will dial-out using this number. If not provided, dial-in will be used
@@ -3707,7 +3822,7 @@ export default class Meeting extends StatelessWebexPlugin {
3707
3822
  * @public
3708
3823
  * @memberof Meeting
3709
3824
  */
3710
- usePhoneAudio(phoneNumber) {
3825
+ public usePhoneAudio(phoneNumber: string) {
3711
3826
  if (!phoneNumber) {
3712
3827
  return this.dialInPstn();
3713
3828
  }
@@ -3722,7 +3837,7 @@ export default class Meeting extends StatelessWebexPlugin {
3722
3837
  * @private
3723
3838
  * @memberof Meeting
3724
3839
  */
3725
- isPhoneProvisioned(pstnStatus) {
3840
+ private isPhoneProvisioned(pstnStatus: string) {
3726
3841
  return [PSTN_STATUS.JOINED, PSTN_STATUS.CONNECTED, PSTN_STATUS.SUCCESS].includes(pstnStatus);
3727
3842
  }
3728
3843
 
@@ -3732,7 +3847,7 @@ export default class Meeting extends StatelessWebexPlugin {
3732
3847
  * @private
3733
3848
  * @memberof Meeting
3734
3849
  */
3735
- dialInPstn() {
3850
+ private dialInPstn() {
3736
3851
  if (this.isPhoneProvisioned(this.dialInDeviceStatus)) return Promise.resolve(); // prevent multiple dial in devices from being provisioned
3737
3852
 
3738
3853
  const {correlationId, locusUrl} = this;
@@ -3770,7 +3885,7 @@ export default class Meeting extends StatelessWebexPlugin {
3770
3885
  * @private
3771
3886
  * @memberof Meeting
3772
3887
  */
3773
- dialOutPstn(phoneNumber) {
3888
+ private dialOutPstn(phoneNumber: string) {
3774
3889
  if (this.isPhoneProvisioned(this.dialOutDeviceStatus)) return Promise.resolve(); // prevent multiple dial out devices from being provisioned
3775
3890
 
3776
3891
  const {correlationId, locusUrl} = this;
@@ -3809,7 +3924,7 @@ export default class Meeting extends StatelessWebexPlugin {
3809
3924
  * @memberof Meeting
3810
3925
  * @returns {Promise}
3811
3926
  */
3812
- disconnectPhoneAudio() {
3927
+ public disconnectPhoneAudio() {
3813
3928
  return Promise.all([
3814
3929
  this.isPhoneProvisioned(this.dialInDeviceStatus) ?
3815
3930
  MeetingUtil.disconnectPhoneAudio(this, this.dialInUrl) :
@@ -3827,7 +3942,7 @@ export default class Meeting extends StatelessWebexPlugin {
3827
3942
  * @public
3828
3943
  * @memberof Meeting
3829
3944
  */
3830
- moveTo(resourceId) {
3945
+ public moveTo(resourceId: string) {
3831
3946
  if (!resourceId) {
3832
3947
  throw new ParameterError('Cannot move call without a resourceId.');
3833
3948
  }
@@ -3862,7 +3977,7 @@ export default class Meeting extends StatelessWebexPlugin {
3862
3977
 
3863
3978
  try {
3864
3979
  if (this.isSharing) {
3865
- await this.stopFloorRequest();
3980
+ await this.releaseScreenShareFloor();
3866
3981
  }
3867
3982
  const mediaSettings = {
3868
3983
  mediaDirection: {
@@ -3887,6 +4002,7 @@ export default class Meeting extends StatelessWebexPlugin {
3887
4002
 
3888
4003
  // 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
3889
4004
  // once the DX answers we establish connection back the media server with only receiveShare enabled
4005
+ // @ts-ignore - reconnectMedia does not accept any argument
3890
4006
  await this.reconnectionManager.reconnectMedia(mediaSettings)
3891
4007
  .then(() => {
3892
4008
  Metrics.sendBehavioralMetric(
@@ -3936,7 +4052,7 @@ export default class Meeting extends StatelessWebexPlugin {
3936
4052
  * @public
3937
4053
  * @memberof Meeting
3938
4054
  */
3939
- moveFrom(resourceId) {
4055
+ public moveFrom(resourceId: string) {
3940
4056
  // On moveFrom ask the developer to re capture it moveFrom then updateMedia
3941
4057
  if (!resourceId) {
3942
4058
  throw new ParameterError('Cannot move call without a resourceId.');
@@ -3974,6 +4090,9 @@ export default class Meeting extends StatelessWebexPlugin {
3974
4090
 
3975
4091
  /**
3976
4092
  * Get local media streams based on options passed
4093
+ *
4094
+ * NOTE: this method can only be used with transcoded meetings, not with multistream meetings
4095
+ *
3977
4096
  * @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
3978
4097
  * @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
3979
4098
  * @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
@@ -3983,10 +4102,10 @@ export default class Meeting extends StatelessWebexPlugin {
3983
4102
  * @memberof Meeting
3984
4103
  */
3985
4104
  getMediaStreams = (
3986
- mediaDirection,
4105
+ mediaDirection: any,
3987
4106
  // This return an OBJECT {video: {height, widght}}
3988
- audioVideo = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
3989
- sharePreferences
4107
+ audioVideo: any = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
4108
+ sharePreferences?: any
3990
4109
  ) => {
3991
4110
  if (
3992
4111
  mediaDirection &&
@@ -4063,6 +4182,7 @@ export default class Meeting extends StatelessWebexPlugin {
4063
4182
  },
4064
4183
  audioVideo,
4065
4184
  sharePreferences,
4185
+ // @ts-ignore - config coming from registerPlugin
4066
4186
  this.config
4067
4187
  )
4068
4188
  .catch((error) => {
@@ -4100,7 +4220,7 @@ export default class Meeting extends StatelessWebexPlugin {
4100
4220
  * @returns {Object}
4101
4221
  * @memberof Meetings
4102
4222
  */
4103
- getSupportedDevices = ({sendAudio = true, sendVideo = true}) => Media.getSupportedDevice({sendAudio, sendVideo});
4223
+ getSupportedDevices = ({ sendAudio = true, sendVideo = true }: { sendAudio: boolean; sendVideo: boolean }) => Media.getSupportedDevice({ sendAudio, sendVideo });
4104
4224
 
4105
4225
  /**
4106
4226
  * Get the devices from the Media module
@@ -4109,6 +4229,344 @@ export default class Meeting extends StatelessWebexPlugin {
4109
4229
  */
4110
4230
  getDevices = () => Media.getDevices();
4111
4231
 
4232
+ /**
4233
+ * Handles ROAP_FAILURE event from the webrtc media connection
4234
+ *
4235
+ * @param {Error} error
4236
+ * @returns {void}
4237
+ */
4238
+ handleRoapFailure = (error) => {
4239
+ const sendBehavioralMetric = (metricName, error, correlationId) => {
4240
+ const data = {
4241
+ code: error.code,
4242
+ correlation_id: correlationId,
4243
+ reason: error.message,
4244
+ stack: error.stack
4245
+ };
4246
+ const metadata = {
4247
+ type: error.cause?.name || error.name
4248
+ };
4249
+
4250
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4251
+ };
4252
+
4253
+
4254
+ if (error instanceof MC.Errors.SdpOfferCreationError) {
4255
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4256
+
4257
+ Metrics.postEvent({
4258
+ event: eventType.LOCAL_SDP_GENERATED,
4259
+ meetingId: this.id,
4260
+ data: {
4261
+ canProceed: false,
4262
+ errors: [
4263
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4264
+ }
4265
+ });
4266
+ }
4267
+ else if ((error instanceof MC.Errors.SdpOfferHandlingError) || (error instanceof MC.Errors.SdpAnswerHandlingError)) {
4268
+ sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
4269
+
4270
+ Metrics.postEvent({
4271
+ event: eventType.REMOTE_SDP_RECEIVED,
4272
+ meetingId: this.id,
4273
+ data: {
4274
+ canProceed: false,
4275
+ errors: [Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4276
+ }
4277
+ });
4278
+ }
4279
+ else if (error instanceof MC.Errors.SdpError) { // this covers also the case of MC.Errors.IceGatheringError which extends MC.Errors.SdpError
4280
+ sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
4281
+
4282
+ Metrics.postEvent({
4283
+ event: eventType.LOCAL_SDP_GENERATED,
4284
+ meetingId: this.id,
4285
+ data: {
4286
+ canProceed: false,
4287
+ errors: [
4288
+ Metrics.generateErrorPayload(2001, true, MetricsError.name.MEDIA_ENGINE, undefined)]
4289
+ }
4290
+ });
4291
+ }
4292
+ };
4293
+
4294
+ setupMediaConnectionListeners = () => {
4295
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_STARTED, () => {
4296
+ this.isRoapInProgress = true;
4297
+ });
4298
+
4299
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_DONE, () => {
4300
+ this.mediaNegotiatedEvent();
4301
+ this.isRoapInProgress = false;
4302
+ this.processNextQueuedMediaUpdate();
4303
+ });
4304
+
4305
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_FAILURE, this.handleRoapFailure);
4306
+
4307
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ROAP_MESSAGE_TO_SEND, (event) => {
4308
+ const LOG_HEADER = 'Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND -->';
4309
+
4310
+ switch (event.roapMessage.messageType) {
4311
+ case 'OK':
4312
+ Metrics.postEvent({
4313
+ event: eventType.REMOTE_SDP_RECEIVED,
4314
+ meetingId: this.id,
4315
+ });
4316
+
4317
+ logRequest(this.roap.sendRoapOK({
4318
+ seq: event.roapMessage.seq,
4319
+ mediaId: this.mediaId,
4320
+ correlationId: this.correlationId
4321
+ }), {
4322
+ header: `${LOG_HEADER} Send Roap OK`,
4323
+ success: `${LOG_HEADER} Successfully send roap OK`,
4324
+ failure: `${LOG_HEADER} Error joining the call on send roap OK, `
4325
+ });
4326
+ break;
4327
+
4328
+ case 'OFFER':
4329
+ Metrics.postEvent({
4330
+ event: eventType.LOCAL_SDP_GENERATED,
4331
+ meetingId: this.id,
4332
+ });
4333
+
4334
+ logRequest(this.roap
4335
+ .sendRoapMediaRequest({
4336
+ sdp: event.roapMessage.sdp,
4337
+ seq: event.roapMessage.seq,
4338
+ tieBreaker: event.roapMessage.tieBreaker,
4339
+ meeting: this, // or can pass meeting ID
4340
+ reconnect: this.reconnectionManager.isReconnectInProgress()
4341
+ }), {
4342
+ header: `${LOG_HEADER} Send Roap Offer`,
4343
+ success: `${LOG_HEADER} Successfully send roap offer`,
4344
+ failure: `${LOG_HEADER} Error joining the call on send roap offer, `
4345
+ });
4346
+ break;
4347
+
4348
+ case 'ANSWER':
4349
+ Metrics.postEvent({
4350
+ event: eventType.REMOTE_SDP_RECEIVED,
4351
+ meetingId: this.id,
4352
+ });
4353
+
4354
+ logRequest(this.roap.sendRoapAnswer({
4355
+ sdp: event.roapMessage.sdp,
4356
+ seq: event.roapMessage.seq,
4357
+ mediaId: this.mediaId,
4358
+ correlationId: this.correlationId
4359
+ }), {
4360
+ header: `${LOG_HEADER} Send Roap Answer.`,
4361
+ success: `${LOG_HEADER} Successfully send roap answer`,
4362
+ failure: `${LOG_HEADER} Error joining the call on send roap answer, `
4363
+ })
4364
+ .catch((error) => {
4365
+ const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
4366
+ const data = {
4367
+ correlation_id: this.correlationId,
4368
+ locus_id: this.locusUrl.split('/').pop(),
4369
+ reason: error.message,
4370
+ stack: error.stack
4371
+ };
4372
+ const metadata = {
4373
+ type: error.name
4374
+ };
4375
+
4376
+ Metrics.sendBehavioralMetric(metricName, data, metadata);
4377
+ });
4378
+ break;
4379
+
4380
+ case 'ERROR':
4381
+ if (event.roapMessage.errorType === MC.ErrorType.CONFLICT || event.roapMessage.errorType === MC.ErrorType.DOUBLECONFLICT) {
4382
+ Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
4383
+ correlation_id: this.correlationId,
4384
+ locus_id: this.locusUrl.split('/').pop(),
4385
+ sequence: event.roapMessage.seq
4386
+ });
4387
+ }
4388
+ logRequest(this.roap.sendRoapError({
4389
+ seq: event.roapMessage.seq,
4390
+ errorType: event.roapMessage.errorType,
4391
+ mediaId: this.mediaId,
4392
+ correlationId: this.correlationId
4393
+ }), {
4394
+ header: `${LOG_HEADER} Send Roap Error.`,
4395
+ success: `${LOG_HEADER} Successfully send roap error`,
4396
+ failure: `${LOG_HEADER} Failed to send roap error, `
4397
+ });
4398
+ break;
4399
+
4400
+ default:
4401
+ LoggerProxy.logger.error(`${LOG_HEADER} Unsupported message type: ${event.roapMessage.messageType}`);
4402
+ break;
4403
+ }
4404
+ });
4405
+
4406
+ // eslint-disable-next-line no-param-reassign
4407
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.REMOTE_TRACK_ADDED, (event) => {
4408
+ LoggerProxy.logger.log(`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(event)}`);
4409
+
4410
+ const mediaTrack = event.track;
4411
+
4412
+ let eventType;
4413
+
4414
+ switch (event.type) {
4415
+ case MC.RemoteTrackType.AUDIO:
4416
+ eventType = EVENT_TYPES.REMOTE_AUDIO;
4417
+ this.mediaProperties.setRemoteAudioTrack(event.track);
4418
+ break;
4419
+ case MC.RemoteTrackType.VIDEO:
4420
+ eventType = EVENT_TYPES.REMOTE_VIDEO;
4421
+ this.mediaProperties.setRemoteVideoTrack(event.track);
4422
+ break;
4423
+ case MC.RemoteTrackType.SCREENSHARE_VIDEO:
4424
+ if (event.track) {
4425
+ eventType = EVENT_TYPES.REMOTE_SHARE;
4426
+ this.mediaProperties.setRemoteShare(event.track);
4427
+ }
4428
+ break;
4429
+ default: {
4430
+ LoggerProxy.logger.log('Meeting:index#setupMediaConnectionListeners --> unexpected track');
4431
+ }
4432
+ }
4433
+
4434
+ // start stats here the stats are coming null if you dont receive streams
4435
+
4436
+ this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
4437
+
4438
+ if (eventType && mediaTrack) {
4439
+ Trigger.trigger(
4440
+ this,
4441
+ {
4442
+ file: 'meeting/index',
4443
+ function: 'setupRemoteTrackListener:Event.REMOTE_TRACK_ADDED'
4444
+ },
4445
+ EVENT_TRIGGERS.MEDIA_READY,
4446
+ {
4447
+ type: eventType,
4448
+ stream: MediaUtil.createMediaStream([mediaTrack])
4449
+ }
4450
+ );
4451
+ }
4452
+ });
4453
+
4454
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.CONNECTION_STATE_CHANGED, (event) => {
4455
+ const connectionFailed = () => {
4456
+ // we know the media connection failed and browser will not attempt to recover it any more
4457
+ // so reset the timer as it's not needed anymore, we want to reconnect immediately
4458
+ this.reconnectionManager.resetReconnectionTimer();
4459
+
4460
+ this.reconnect({networkDisconnect: true});
4461
+ Metrics.postEvent({
4462
+ event: eventType.ICE_END,
4463
+ meeting: this,
4464
+ data: {
4465
+ canProceed: false,
4466
+ errors: [
4467
+ Metrics.generateErrorPayload(
4468
+ 2004, false, MetricsError.name.MEDIA_ENGINE, undefined
4469
+ )]
4470
+ }
4471
+ });
4472
+
4473
+ this.uploadLogs({
4474
+ file: 'peer-connection-manager/index',
4475
+ function: 'connectionFailed'
4476
+ });
4477
+
4478
+ Metrics.sendBehavioralMetric(
4479
+ BEHAVIORAL_METRICS.CONNECTION_FAILURE,
4480
+ {
4481
+ correlation_id: this.correlationId,
4482
+ locus_id: this.locusId
4483
+ }
4484
+ );
4485
+ };
4486
+
4487
+ LoggerProxy.logger.info(`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`);
4488
+ switch (event.state) {
4489
+ case MC.ConnectionState.Connecting:
4490
+ Metrics.postEvent({event: eventType.ICE_START, meeting: this});
4491
+ break;
4492
+ case MC.ConnectionState.Connected:
4493
+ Metrics.postEvent({event: eventType.ICE_END, meeting: this});
4494
+ Metrics.sendBehavioralMetric(
4495
+ BEHAVIORAL_METRICS.CONNECTION_SUCCESS,
4496
+ {
4497
+ correlation_id: this.correlationId,
4498
+ locus_id: this.locusId
4499
+ }
4500
+ );
4501
+ this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
4502
+ this.reconnectionManager.iceReconnected();
4503
+ break;
4504
+ case MC.ConnectionState.Disconnected:
4505
+ this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
4506
+ this.reconnectionManager.waitForIceReconnect()
4507
+ .catch(() => {
4508
+ LoggerProxy.logger.info('Meeting:index#setupMediaConnectionListeners --> state DISCONNECTED, automatic reconnection timed out.');
4509
+
4510
+ connectionFailed();
4511
+ });
4512
+ break;
4513
+ case MC.ConnectionState.Failed:
4514
+ connectionFailed();
4515
+ break;
4516
+ default:
4517
+ break;
4518
+ }
4519
+ });
4520
+
4521
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.ACTIVE_SPEAKERS_CHANGED,
4522
+ (msg) => {
4523
+ Trigger.trigger(
4524
+ this,
4525
+ {
4526
+ file: 'meeting/index',
4527
+ function: 'setupMediaConnectionListeners'
4528
+ },
4529
+ EVENT_TRIGGERS.ACTIVE_SPEAKER_CHANGED,
4530
+ {
4531
+ seqNum: msg.seqNum,
4532
+ memberIds: msg.csis.map((csi) => this.members.findMemberByCsi(csi)?.id).filter((item) => (item !== undefined))
4533
+ }
4534
+ );
4535
+ });
4536
+
4537
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.VIDEO_SOURCES_COUNT_CHANGED,
4538
+ (numTotalSources, numLiveSources) => {
4539
+ Trigger.trigger(
4540
+ this,
4541
+ {
4542
+ file: 'meeting/index',
4543
+ function: 'setupMediaConnectionListeners'
4544
+ },
4545
+ EVENT_TRIGGERS.REMOTE_VIDEO_SOURCE_COUNT_CHANGED,
4546
+ {
4547
+ numTotalSources,
4548
+ numLiveSources
4549
+ }
4550
+ );
4551
+ });
4552
+
4553
+ this.mediaProperties.webrtcMediaConnection.on(MC.Event.AUDIO_SOURCES_COUNT_CHANGED,
4554
+ (numTotalSources, numLiveSources) => {
4555
+ Trigger.trigger(
4556
+ this,
4557
+ {
4558
+ file: 'meeting/index',
4559
+ function: 'setupMediaConnectionListeners'
4560
+ },
4561
+ EVENT_TRIGGERS.REMOTE_AUDIO_SOURCE_COUNT_CHANGED,
4562
+ {
4563
+ numTotalSources,
4564
+ numLiveSources
4565
+ }
4566
+ );
4567
+ });
4568
+ };
4569
+
4112
4570
  /**
4113
4571
  * Registers for all required StatsAnalyzer events
4114
4572
  * @private
@@ -4119,6 +4577,7 @@ export default class Meeting extends StatelessWebexPlugin {
4119
4577
  this.statsAnalyzer.on(StatsAnalyzerEvents.MEDIA_QUALITY, (options) => {
4120
4578
  // TODO: might have to send the same event to the developer
4121
4579
  // Add ip address info if geo hint is present
4580
+ // @ts-ignore fix type
4122
4581
  options.data.intervalMetadata.peerReflexiveIP = this.webex.meetings.geoHintInfo?.clientAddress || options.data.intervalMetadata.peerReflexiveIP || MQA_STATS.DEFAULT_IP;
4123
4582
  Metrics.postEvent({event: eventType.MEDIA_QUALITY, meeting: this, data: {intervalData: options.data, networkType: options.networkType}});
4124
4583
  });
@@ -4178,6 +4637,52 @@ export default class Meeting extends StatelessWebexPlugin {
4178
4637
  });
4179
4638
  };
4180
4639
 
4640
+ getMediaConnectionDebugId() {
4641
+ return `MC-${this.id.substring(0, 4)}`;
4642
+ }
4643
+
4644
+ createMediaConnection(turnServerInfo) {
4645
+ const mc = Media.createMediaConnection(
4646
+ this.isMultistream,
4647
+ this.getMediaConnectionDebugId(),
4648
+ {
4649
+ mediaProperties: this.mediaProperties,
4650
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4651
+ // @ts-ignore - config coming from registerPlugin
4652
+ enableRtx: this.config.enableRtx,
4653
+ // @ts-ignore - config coming from registerPlugin
4654
+ enableExtmap: this.config.enableExtmap,
4655
+ turnServerInfo
4656
+ }
4657
+ );
4658
+
4659
+ this.mediaProperties.setMediaPeerConnection(mc);
4660
+ this.setupMediaConnectionListeners();
4661
+
4662
+ return mc;
4663
+ }
4664
+
4665
+ /**
4666
+ * Listens for an event emitted by eventEmitter and emits it from the meeting object
4667
+ *
4668
+ * @private
4669
+ * @param {*} eventEmitter object from which to forward the event
4670
+ * @param {*} eventTypeToForward which event type to listen on and to forward
4671
+ * @param {string} meetingEventType event type to be used in the event emitted from the meeting object
4672
+ * @returns {void}
4673
+ */
4674
+ forwardEvent(eventEmitter, eventTypeToForward, meetingEventType) {
4675
+ eventEmitter.on(eventTypeToForward, (data) => Trigger.trigger(
4676
+ this,
4677
+ {
4678
+ file: 'meetings',
4679
+ function: 'addMedia'
4680
+ },
4681
+ meetingEventType,
4682
+ data
4683
+ ));
4684
+ }
4685
+
4181
4686
  /**
4182
4687
  * Specify joining via audio (option: pstn), video, screenshare
4183
4688
  * @param {Object} options A configurable options object for joining a meeting
@@ -4185,11 +4690,12 @@ export default class Meeting extends StatelessWebexPlugin {
4185
4690
  * @param {MediaDirection} options.mediaSettings pass media options
4186
4691
  * @param {MediaStream} options.localStream
4187
4692
  * @param {MediaStream} options.localShare
4693
+ * @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
4188
4694
  * @returns {Promise}
4189
4695
  * @public
4190
4696
  * @memberof Meeting
4191
4697
  */
4192
- addMedia(options = {}) {
4698
+ addMedia(options: any = {}) {
4193
4699
  const LOG_HEADER = 'Meeting:index#addMedia -->';
4194
4700
 
4195
4701
  let turnDiscoverySkippedReason;
@@ -4203,11 +4709,14 @@ export default class Meeting extends StatelessWebexPlugin {
4203
4709
  return Promise.reject(new UserNotJoinedError());
4204
4710
  }
4205
4711
  // If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
4712
+ // @ts-ignore - isUserUnadmitted coming from SelfUtil
4206
4713
  if (this.isUserUnadmitted && !this.wirelessShare) {
4207
4714
  return Promise.reject(new UserInLobbyError());
4208
4715
  }
4209
4716
 
4210
- const {localStream, localShare, mediaSettings} = options;
4717
+ const {
4718
+ localStream, localShare, mediaSettings, remoteMediaManagerConfig
4719
+ } = options;
4211
4720
 
4212
4721
  LoggerProxy.logger.info(`${LOG_HEADER} Adding Media.`);
4213
4722
 
@@ -4242,32 +4751,42 @@ export default class Meeting extends StatelessWebexPlugin {
4242
4751
 
4243
4752
  const {turnServerInfo} = turnDiscoveryObject;
4244
4753
 
4245
- this.mediaProperties.setMediaPeerConnection(MediaUtil.createPeerConnection(turnServerInfo));
4246
- this.setMercuryListener();
4247
- PeerConnectionManager.setPeerConnectionEvents(this);
4754
+ this.preMedia(localStream, localShare, mediaSettings);
4248
4755
 
4249
- return this.preMedia(localStream, localShare, mediaSettings);
4756
+ const mc = this.createMediaConnection(turnServerInfo);
4757
+
4758
+ if (this.isMultistream) {
4759
+ this.remoteMediaManager = new RemoteMediaManager(
4760
+ this.receiveSlotManager,
4761
+ this.mediaRequestManagers,
4762
+ remoteMediaManagerConfig
4763
+ );
4764
+
4765
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.AudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED);
4766
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.ScreenShareAudioCreated, EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED);
4767
+ this.forwardEvent(this.remoteMediaManager, RemoteMediaManagerEvent.VideoLayoutChanged, EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED);
4768
+
4769
+ return this.remoteMediaManager.start()
4770
+ .then(() => mc.initiateOffer());
4771
+ }
4772
+
4773
+ return mc.initiateOffer();
4250
4774
  })
4251
- .then(() => Media.attachMedia(this.mediaProperties, {
4252
- meetingId: this.id,
4253
- remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4254
- enableRtx: this.config.enableRtx,
4255
- enableExtmap: this.config.enableExtmap,
4256
- setStartLocalSDPGenRemoteSDPRecvDelay: this.setStartLocalSDPGenRemoteSDPRecvDelay.bind(this)
4257
- }))
4258
- .then((peerConnection) => this.getDevices().then((devices) => {
4775
+ .then(() => {
4776
+ this.setMercuryListener();
4777
+ })
4778
+ .then(() => this.getDevices().then((devices) => {
4259
4779
  MeetingUtil.handleDeviceLogging(devices);
4260
-
4261
- return peerConnection;
4262
4780
  }))
4263
- .then((peerConnection) => {
4781
+ .then(() => {
4264
4782
  this.handleMediaLogging(this.mediaProperties);
4265
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection Received from attachMedia `);
4783
+ LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
4266
4784
 
4267
- this.setRemoteStream(peerConnection);
4785
+ // @ts-ignore - config coming from registerPlugin
4268
4786
  if (this.config.stats.enableStatsAnalyzer) {
4269
- // TODO: ** Dont re create StatsAnalyzer on reconnect or rejoin
4787
+ // @ts-ignore - config coming from registerPlugin
4270
4788
  this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
4789
+ // @ts-ignore - config coming from registerPlugin
4271
4790
  this.statsAnalyzer = new StatsAnalyzer(this.config.stats, this.networkQualityMonitor);
4272
4791
  this.setupStatsAnalyzerEventHandlers();
4273
4792
  this.networkQualityMonitor.on(EVENT_TRIGGERS.NETWORK_QUALITY, this.sendNetworkQualityEvent.bind(this));
@@ -4276,21 +4795,9 @@ export default class Meeting extends StatelessWebexPlugin {
4276
4795
  .catch((error) => {
4277
4796
  LoggerProxy.logger.error(`${LOG_HEADER} Error adding media , setting up peerconnection, `, error);
4278
4797
 
4279
- Metrics.sendBehavioralMetric(
4280
- BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
4281
- {
4282
- correlation_id: this.correlationId,
4283
- locus_id: this.locusUrl.split('/').pop(),
4284
- reason: error.message,
4285
- stack: error.stack,
4286
- turnDiscoverySkippedReason,
4287
- turnServerUsed
4288
- }
4289
- );
4290
-
4291
4798
  throw error;
4292
4799
  })
4293
- .then(() => new Promise((resolve, reject) => {
4800
+ .then(() => new Promise<void>((resolve, reject) => {
4294
4801
  let timerCount = 0;
4295
4802
 
4296
4803
  // eslint-disable-next-line func-names
@@ -4311,29 +4818,17 @@ export default class Meeting extends StatelessWebexPlugin {
4311
4818
  }
4312
4819
  }, 1000);
4313
4820
  }))
4314
- .then(() =>
4315
- logRequest(this.roap
4316
- .sendRoapMediaRequest({
4317
- sdp: this.mediaProperties.peerConnection.sdp,
4318
- roapSeq: this.roapSeq,
4319
- meeting: this // or can pass meeting ID
4320
- }), {
4321
- header: `${LOG_HEADER} Send Roap Media Request.`,
4322
- success: `${LOG_HEADER} Successfully send roap media request`,
4323
- failure: `${LOG_HEADER} Error joining the call on send roap media request, `
4324
- }))
4325
4821
  .then(
4326
- () => this.mediaProperties.waitForIceConnectedState()
4822
+ () => this.mediaProperties.waitForMediaConnectionConnected()
4327
4823
  .catch(() => {
4328
4824
  throw createMeetingsError(30202, 'Meeting connection failed');
4329
4825
  })
4330
4826
  )
4331
4827
  .then(() => {
4332
4828
  LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection CONNECTED`);
4333
-
4334
4829
  if (mediaSettings && mediaSettings.sendShare && localShare) {
4335
4830
  if (this.state === MEETING_STATE.STATES.JOINED) {
4336
- return this.share();
4831
+ return this.requestScreenShareFloor();
4337
4832
  }
4338
4833
 
4339
4834
  // When the self state changes to JOINED then request the floor
@@ -4361,7 +4856,7 @@ export default class Meeting extends StatelessWebexPlugin {
4361
4856
  .then(() => {
4362
4857
  this.statsAnalyzer = null;
4363
4858
 
4364
- if (this.mediaProperties.peerConnection) {
4859
+ if (this.mediaProperties.webrtcMediaConnection) {
4365
4860
  this.closePeerConnections();
4366
4861
  this.unsetPeerConnections();
4367
4862
  }
@@ -4392,10 +4887,7 @@ export default class Meeting extends StatelessWebexPlugin {
4392
4887
  this
4393
4888
  );
4394
4889
 
4395
- // If addMedia failes for not establishing connection then
4396
- // leave the meeting with reson connection failed as meeting anyways will end
4397
- // and cannot be connected unless network condition is checked for firewall
4398
- if (error.code === InvalidSdpError.CODE) {
4890
+ if (error instanceof MC.Errors.SdpError) {
4399
4891
  this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
4400
4892
  }
4401
4893
 
@@ -4409,7 +4901,10 @@ export default class Meeting extends StatelessWebexPlugin {
4409
4901
  * @returns {Boolean}
4410
4902
  */
4411
4903
  canUpdateMedia() {
4412
- return this.mediaProperties.peerConnection.signalingState === SDP.STABLE && !RoapCollection.isBusy(this.correlationId);
4904
+ // in theory we shouldn't need this as RoapMediaConnection handles multiple updates, glare, etc,
4905
+ // but there are some server issues, like https://jira-eng-gpk2.cisco.com/jira/browse/WEBEX-248394
4906
+ // so for now it's better to keep queuing any media updates at SDK meeting level
4907
+ return !this.isRoapInProgress;
4413
4908
  }
4414
4909
 
4415
4910
  /**
@@ -4420,7 +4915,7 @@ export default class Meeting extends StatelessWebexPlugin {
4420
4915
  * @private
4421
4916
  * @memberof Meeting
4422
4917
  */
4423
- enqueueMediaUpdate(mediaUpdateType, options) {
4918
+ private enqueueMediaUpdate(mediaUpdateType: string, options: object) {
4424
4919
  return new Promise((resolve, reject) => {
4425
4920
  const queueItem = {
4426
4921
  pendingPromiseResolve: resolve, pendingPromiseReject: reject, mediaUpdateType, options
@@ -4438,6 +4933,7 @@ export default class Meeting extends StatelessWebexPlugin {
4438
4933
  * @memberof Meeting
4439
4934
  */
4440
4935
  mediaNegotiatedEvent = () => {
4936
+ // @ts-ignore - config coming from registerPlugin
4441
4937
  if (this.config.experimental.enableMediaNegotiatedEvent) {
4442
4938
  LoggerProxy.logger.info('Meeting:mediaNegotiatedEvent --> Media server negotiated');
4443
4939
  Trigger.trigger(
@@ -4493,11 +4989,16 @@ export default class Meeting extends StatelessWebexPlugin {
4493
4989
  * @param {MediaStream} options.localShare
4494
4990
  * @param {MediaDirection} options.mediaSettings
4495
4991
  * @returns {Promise}
4496
- * @todo fix setRemoteStream for updateMedia
4497
4992
  * @public
4498
4993
  * @memberof Meeting
4499
4994
  */
4500
- updateMedia(options = {}) {
4995
+ public updateMedia(
4996
+ options: {
4997
+ localStream?: MediaStream;
4998
+ localShare?: MediaStream;
4999
+ mediaSettings?: any;
5000
+ } = {} as any
5001
+ ) {
4501
5002
  const LOG_HEADER = 'Meeting:index#updateMedia -->';
4502
5003
 
4503
5004
  if (!this.canUpdateMedia()) {
@@ -4509,20 +5010,27 @@ export default class Meeting extends StatelessWebexPlugin {
4509
5010
 
4510
5011
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4511
5012
 
5013
+ if (!this.mediaProperties.webrtcMediaConnection) {
5014
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5015
+ }
5016
+
4512
5017
  return MeetingUtil.validateOptions(options)
4513
5018
  .then(() => this.preMedia(localStream, localShare, mediaSettings))
4514
- .then(() => Media.updateMedia(this.mediaProperties, {
4515
- meetingId: this.id,
4516
- remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
4517
- enableRtx: this.config.enableRtx,
4518
- enableExtmap: this.config.enableExtmap,
5019
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5020
+ send: {
5021
+ audio: this.mediaProperties.mediaDirection.sendAudio ? this.mediaProperties.audioTrack : null,
5022
+ video: this.mediaProperties.mediaDirection.sendVideo ? this.mediaProperties.videoTrack : null,
5023
+ screenShareVideo: this.mediaProperties.mediaDirection.sendShare ? this.mediaProperties.shareTrack : null
5024
+ },
5025
+ receive: {
5026
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5027
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5028
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5029
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
5030
+ }
4519
5031
  })
4520
- .then((peerConnection) => {
4521
- LoggerProxy.logger.info(`${LOG_HEADER} PeerConnection received from updateMedia, ${peerConnection}`);
4522
- this.setRemoteStream(peerConnection);
4523
- if (mediaSettings.receiveShare || localShare) {
4524
- PeerConnectionManager.setContentSlides(peerConnection);
4525
- }
5032
+ .then(() => {
5033
+ LoggerProxy.logger.info(`${LOG_HEADER} webrtcMediaConnection.updateSendReceiveOptions done`);
4526
5034
  })
4527
5035
  .catch((error) => {
4528
5036
  LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
@@ -4539,25 +5047,17 @@ export default class Meeting extends StatelessWebexPlugin {
4539
5047
 
4540
5048
  throw error;
4541
5049
  })
4542
- .then(() =>
4543
- logRequest(this.roap
4544
- .sendRoapMediaRequest({
4545
- sdp: this.mediaProperties.peerConnection.sdp,
4546
- roapSeq: this.roapSeq,
4547
- meeting: this, // or can pass meeting ID
4548
- }),
4549
- {
4550
- header: `${LOG_HEADER} sendRoapMediaRequest being sent`,
4551
- success: `${LOG_HEADER} sendRoadMediaRequest successful`,
4552
- failure: `${LOG_HEADER} Error updateMedia on send roap media request, `
4553
- }))
5050
+ // todo: the following code used to be called always after sending the roap message with the new SDP
5051
+ // now it's called independently from the roap message (so might be before it), check if that's OK
5052
+ // if not, ensure it's called after (now it's called after roap message is sent out, but we're not
5053
+ // waiting for sendRoapMediaRequest() to be resolved)
4554
5054
  .then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
4555
5055
  .then((startShare) => {
4556
5056
  // This is a special case if we do an /floor grant followed by /media
4557
5057
  // we actually get a OFFER from the server and a GLAR condition happens
4558
5058
  if (startShare) {
4559
5059
  // We are assuming that the clients are connected when doing an update
4560
- return this.share();
5060
+ return this.requestScreenShareFloor();
4561
5061
  }
4562
5062
 
4563
5063
  return Promise.resolve();
@@ -4566,6 +5066,9 @@ export default class Meeting extends StatelessWebexPlugin {
4566
5066
 
4567
5067
  /**
4568
5068
  * Update the main audio track with new parameters
5069
+ *
5070
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5071
+ *
4569
5072
  * @param {Object} options
4570
5073
  * @param {boolean} options.sendAudio
4571
5074
  * @param {boolean} options.receiveAudio
@@ -4574,21 +5077,21 @@ export default class Meeting extends StatelessWebexPlugin {
4574
5077
  * @public
4575
5078
  * @memberof Meeting
4576
5079
  */
4577
- async updateAudio(options) {
5080
+ public async updateAudio(options: { sendAudio: boolean; receiveAudio: boolean; stream: MediaStream }) {
4578
5081
  if (!this.canUpdateMedia()) {
4579
5082
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
4580
5083
  }
4581
- const {
4582
- sendAudio, receiveAudio, stream
4583
- } = options;
4584
-
4585
- const {audioTransceiver} = this.mediaProperties.peerConnection;
5084
+ const {sendAudio, receiveAudio, stream} = options;
4586
5085
  let track = MeetingUtil.getTrack(stream).audioTrack;
4587
5086
 
4588
5087
  if (typeof sendAudio !== 'boolean' || typeof receiveAudio !== 'boolean') {
4589
5088
  return Promise.reject(new ParameterError('Pass sendAudio and receiveAudio parameter'));
4590
5089
  }
4591
5090
 
5091
+ if (!this.mediaProperties.webrtcMediaConnection) {
5092
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5093
+ }
5094
+
4592
5095
  if (this.effects && this.effects.state) {
4593
5096
  const bnrEnabled = this.effects.state.bnr.enabled;
4594
5097
 
@@ -4600,38 +5103,18 @@ export default class Meeting extends StatelessWebexPlugin {
4600
5103
  }
4601
5104
 
4602
5105
  return MeetingUtil.validateOptions({sendAudio, localStream: stream})
4603
- .then(() => {
4604
- let previousMediaDirection = {};
4605
-
4606
- if (this.mediaProperties.mediaDirection) {
4607
- previousMediaDirection = {
4608
- sendTrack: this.mediaProperties.mediaDirection.sendAudio,
4609
- receiveTrack: this.mediaProperties.mediaDirection.receiveAudio
4610
- };
4611
- }
4612
- else {
4613
- this.mediaProperties.mediaDirection = {};
5106
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5107
+ send: {audio: track},
5108
+ receive: {
5109
+ audio: options.receiveAudio,
5110
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5111
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5112
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4614
5113
  }
4615
-
4616
- return MeetingUtil.updateTransceiver(
4617
- {
4618
- type: 'audio',
4619
- sendTrack: options.sendAudio,
4620
- receiveTrack: options.receiveAudio,
4621
- track,
4622
- transceiver: audioTransceiver,
4623
- peerConnection: this.mediaProperties.peerConnection,
4624
- previousMediaDirection
4625
- },
4626
- {
4627
- mediaProperties: this.mediaProperties,
4628
- meeting: this,
4629
- id: this.id
4630
- }
4631
- );
4632
- })
5114
+ }))
4633
5115
  .then(() => {
4634
5116
  this.setLocalAudioTrack(track);
5117
+ // todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
4635
5118
  this.mediaProperties.mediaDirection.sendAudio = sendAudio;
4636
5119
  this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
4637
5120
 
@@ -4642,6 +5125,9 @@ export default class Meeting extends StatelessWebexPlugin {
4642
5125
 
4643
5126
  /**
4644
5127
  * Update the main video track with new parameters
5128
+ *
5129
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5130
+ *
4645
5131
  * @param {Object} options
4646
5132
  * @param {boolean} options.sendVideo
4647
5133
  * @param {boolean} options.receiveVideo
@@ -4650,35 +5136,30 @@ export default class Meeting extends StatelessWebexPlugin {
4650
5136
  * @public
4651
5137
  * @memberof Meeting
4652
5138
  */
4653
- updateVideo(options) {
5139
+ public updateVideo(options: { sendVideo: boolean; receiveVideo: boolean; stream: MediaStream }) {
4654
5140
  if (!this.canUpdateMedia()) {
4655
5141
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
4656
5142
  }
4657
5143
  const {sendVideo, receiveVideo, stream} = options;
4658
- const {videoTransceiver} = this.mediaProperties.peerConnection;
4659
5144
  const track = MeetingUtil.getTrack(stream).videoTrack;
4660
5145
 
4661
5146
  if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
4662
5147
  return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
4663
5148
  }
4664
5149
 
5150
+ if (!this.mediaProperties.webrtcMediaConnection) {
5151
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5152
+ }
5153
+
4665
5154
  return MeetingUtil.validateOptions({sendVideo, localStream: stream})
4666
- .then(() => MeetingUtil.updateTransceiver({
4667
- type: 'video',
4668
- sendTrack: options.sendVideo,
4669
- receiveTrack: options.receiveVideo,
4670
- track,
4671
- transceiver: videoTransceiver,
4672
- peerConnection: this.mediaProperties.peerConnection,
4673
- previousMediaDirection: {
4674
- sendTrack: this.mediaProperties.mediaDirection.sendVideo,
4675
- receiveTrack: this.mediaProperties.mediaDirection.receiveVideo
5155
+ .then(() => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5156
+ send: {video: track},
5157
+ receive: {
5158
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5159
+ video: options.receiveVideo,
5160
+ screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
5161
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4676
5162
  }
4677
- },
4678
- {
4679
- mediaProperties: this.mediaProperties,
4680
- meeting: this,
4681
- id: this.id
4682
5163
  }))
4683
5164
  .then(() => {
4684
5165
  this.setLocalVideoTrack(track);
@@ -4698,7 +5179,7 @@ export default class Meeting extends StatelessWebexPlugin {
4698
5179
  * @private
4699
5180
  * @memberof Meeting
4700
5181
  */
4701
- checkForStopShare(sendShare, previousShareStatus) {
5182
+ private checkForStopShare(sendShare: boolean, previousShareStatus: boolean) {
4702
5183
  if (sendShare && !previousShareStatus) {
4703
5184
  // When user starts sharing
4704
5185
  return Promise.resolve(true);
@@ -4706,7 +5187,7 @@ export default class Meeting extends StatelessWebexPlugin {
4706
5187
 
4707
5188
  if (!sendShare && previousShareStatus) {
4708
5189
  // When user stops sharing
4709
- return this.stopFloorRequest()
5190
+ return this.releaseScreenShareFloor()
4710
5191
  .then(() => Promise.resolve(false));
4711
5192
  }
4712
5193
 
@@ -4715,6 +5196,9 @@ export default class Meeting extends StatelessWebexPlugin {
4715
5196
 
4716
5197
  /**
4717
5198
  * Update the share streams, can be used to start sharing
5199
+ *
5200
+ * NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
5201
+ *
4718
5202
  * @param {Object} options
4719
5203
  * @param {boolean} options.sendShare
4720
5204
  * @param {boolean} options.receiveShare
@@ -4722,43 +5206,39 @@ export default class Meeting extends StatelessWebexPlugin {
4722
5206
  * @public
4723
5207
  * @memberof Meeting
4724
5208
  */
4725
- updateShare(options) {
5209
+ public updateShare(options: { sendShare?: boolean; receiveShare?: boolean, stream?: any, skipSignalingCheck?: boolean }) {
4726
5210
  if (!options.skipSignalingCheck && !this.canUpdateMedia()) {
4727
5211
  return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
4728
5212
  }
4729
5213
  const {sendShare, receiveShare, stream} = options;
4730
- const {shareTransceiver} = this.mediaProperties.peerConnection;
4731
5214
  const track = MeetingUtil.getTrack(stream).videoTrack;
4732
5215
 
4733
5216
  if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
4734
5217
  return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
4735
5218
  }
5219
+
5220
+ if (!this.mediaProperties.webrtcMediaConnection) {
5221
+ return Promise.reject(new Error('media connection not established, call addMedia() first'));
5222
+ }
5223
+
4736
5224
  const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
4737
5225
 
4738
5226
  this.setLocalShareTrack(stream);
4739
5227
 
4740
5228
  return MeetingUtil.validateOptions({sendShare, localShare: stream})
4741
5229
  .then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
4742
- .then((startShare) => MeetingUtil.updateTransceiver({
4743
- type: 'video',
4744
- sendTrack: sendShare,
4745
- receiveTrack: receiveShare,
4746
- track,
4747
- transceiver: shareTransceiver,
4748
- peerConnection: this.mediaProperties.peerConnection,
4749
- previousMediaDirection: {
4750
- sendTrack: this.mediaProperties.mediaDirection.sendShare,
4751
- receiveTrack: this.mediaProperties.mediaDirection.receiveShare
5230
+ .then((startShare) => this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
5231
+ send: {screenShareVideo: track},
5232
+ receive: {
5233
+ audio: this.mediaProperties.mediaDirection.receiveAudio,
5234
+ video: this.mediaProperties.mediaDirection.receiveVideo,
5235
+ screenShareVideo: options.receiveShare,
5236
+ remoteQualityLevel: this.mediaProperties.remoteQualityLevel
4752
5237
  }
4753
- },
4754
- {
4755
- mediaProperties: this.mediaProperties,
4756
- meeting: this,
4757
- id: this.id
4758
5238
  })
4759
5239
  .then(() => {
4760
5240
  if (startShare) {
4761
- return this.share();
5241
+ return this.requestScreenShareFloor();
4762
5242
  }
4763
5243
 
4764
5244
  return Promise.resolve();
@@ -4768,24 +5248,8 @@ export default class Meeting extends StatelessWebexPlugin {
4768
5248
  this.mediaProperties.mediaDirection.receiveShare = receiveShare;
4769
5249
  })
4770
5250
  .catch((error) => {
4771
- this.unsetLocalShareTrack(stream);
5251
+ this.unsetLocalShareTrack();
4772
5252
  throw error;
4773
- })
4774
- .finally(() => {
4775
- const delay = 1e3;
4776
- // Check to see if share was stopped natively before onended was assigned.
4777
- const sharingModeIsActive = this.mediaProperties.peerConnection.shareTransceiver.direction === SENDRECV;
4778
- const isSharingOutOfSync = sharingModeIsActive && !this.isLocalShareLive;
4779
-
4780
- if (isSharingOutOfSync) {
4781
- // Adding a delay to avoid a 409 from server
4782
- // which results in user still appearing as if sharing.
4783
- // Also delay give time for changes to peerConnection.
4784
- setTimeout(
4785
- () => this.handleShareTrackEnded(stream),
4786
- delay
4787
- );
4788
- }
4789
5253
  });
4790
5254
  }
4791
5255
 
@@ -4798,9 +5262,10 @@ export default class Meeting extends StatelessWebexPlugin {
4798
5262
  * @private
4799
5263
  * @memberof Meeting
4800
5264
  */
4801
- preMedia(localStream, localShare, mediaSettings) {
5265
+ private preMedia(localStream: MediaStream, localShare: MediaStream, mediaSettings: any) {
4802
5266
  // eslint-disable-next-line no-warning-comments
4803
5267
  // TODO wire into default config. There's currently an issue with the stateless plugin or how we register
5268
+ // @ts-ignore - config coming from registerPlugin
4804
5269
  this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
4805
5270
  // add a setup a function move the create and setup media in future
4806
5271
  // TODO: delete old audio and video if stale
@@ -4818,7 +5283,7 @@ export default class Meeting extends StatelessWebexPlugin {
4818
5283
  * @public
4819
5284
  * @memberof Meeting
4820
5285
  */
4821
- acknowledge(type) {
5286
+ public acknowledge(type: string) {
4822
5287
  if (!type) {
4823
5288
  return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
4824
5289
  }
@@ -4853,7 +5318,7 @@ export default class Meeting extends StatelessWebexPlugin {
4853
5318
  * @public
4854
5319
  * @memberof Meeting
4855
5320
  */
4856
- decline(reason) {
5321
+ public decline(reason: string) {
4857
5322
  return MeetingUtil.declineMeeting(this, reason).then((decline) => {
4858
5323
  this.meetingFiniteStateMachine.decline();
4859
5324
 
@@ -4873,7 +5338,7 @@ export default class Meeting extends StatelessWebexPlugin {
4873
5338
  * @public
4874
5339
  * @memberof Meeting
4875
5340
  */
4876
- leave(options = {}) {
5341
+ public leave(options: { resourceId?: string, reason?: any } = {} as any) {
4877
5342
  Metrics.postEvent({event: eventType.LEAVE, meeting: this, data: {trigger: trigger.USER_INTERACTION, canProceed: false}});
4878
5343
  const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
4879
5344
 
@@ -4950,7 +5415,7 @@ export default class Meeting extends StatelessWebexPlugin {
4950
5415
  * @public
4951
5416
  * @memberof Meeting
4952
5417
  */
4953
- startWhiteboardShare(channelUrl, resourceToken) {
5418
+ public startWhiteboardShare(channelUrl: string, resourceToken: string) {
4954
5419
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
4955
5420
 
4956
5421
  if (!channelUrl) {
@@ -4960,7 +5425,7 @@ export default class Meeting extends StatelessWebexPlugin {
4960
5425
  if (whiteboard) {
4961
5426
  Metrics.postEvent({event: eventType.WHITEBOARD_SHARE_INITIATED, meeting: this});
4962
5427
 
4963
- const body = {
5428
+ const body: any = {
4964
5429
  disposition: FLOOR_ACTION.GRANTED,
4965
5430
  personUrl: this.locusInfo.self.url,
4966
5431
  deviceUrl: this.deviceUrl,
@@ -5006,7 +5471,7 @@ export default class Meeting extends StatelessWebexPlugin {
5006
5471
  * @public
5007
5472
  * @memberof Meeting
5008
5473
  */
5009
- stopWhiteboardShare(channelUrl) {
5474
+ public stopWhiteboardShare(channelUrl: string) {
5010
5475
  const whiteboard = this.locusInfo.mediaShares.find((element) => element.name === 'whiteboard');
5011
5476
 
5012
5477
  if (whiteboard) {
@@ -5022,6 +5487,7 @@ export default class Meeting extends StatelessWebexPlugin {
5022
5487
  LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
5023
5488
 
5024
5489
  Metrics.sendBehavioralMetric(
5490
+ // @ts-ignore - check if STOP_WHITEBOARD_SHARE_FAILURE exists
5025
5491
  BEHAVIORAL_METRICS.STOP_WHITEBOARD_SHARE_FAILURE,
5026
5492
  {
5027
5493
  correlation_id: this.correlationId,
@@ -5042,12 +5508,12 @@ export default class Meeting extends StatelessWebexPlugin {
5042
5508
  }
5043
5509
 
5044
5510
  /**
5045
- * Start sharing content with server
5511
+ * Sends a request to Locus to obtain the screen share floor
5046
5512
  * @returns {Promise} see #meetingRequest.changeMeetingFloor
5047
5513
  * @private
5048
5514
  * @memberof Meeting
5049
5515
  */
5050
- share() {
5516
+ private requestScreenShareFloor() {
5051
5517
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5052
5518
 
5053
5519
  if (content && (this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE)) {
@@ -5093,7 +5559,7 @@ export default class Meeting extends StatelessWebexPlugin {
5093
5559
  */
5094
5560
  // Internal only, temporarily allows optional params
5095
5561
  // eslint-disable-next-line valid-jsdoc
5096
- stopShare(options = {}) {
5562
+ public stopShare(options = {}) {
5097
5563
  return this.updateShare({
5098
5564
  sendShare: false,
5099
5565
  receiveShare: this.mediaProperties.mediaDirection.receiveShare,
@@ -5102,12 +5568,12 @@ export default class Meeting extends StatelessWebexPlugin {
5102
5568
  }
5103
5569
 
5104
5570
  /**
5105
- * sends stops floor request
5571
+ * Sends a request to Locus to release the screen share floor.
5106
5572
  * @returns {Promise} see #meetingRequest.changeMeetingFloor
5107
5573
  * @private
5108
5574
  * @memberof Meeting
5109
5575
  */
5110
- stopFloorRequest() {
5576
+ private releaseScreenShareFloor() {
5111
5577
  const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
5112
5578
 
5113
5579
  if (content && (this.mediaProperties.mediaDirection.sendShare)) {
@@ -5129,7 +5595,7 @@ export default class Meeting extends StatelessWebexPlugin {
5129
5595
  resourceUrl: this.resourceUrl
5130
5596
  })
5131
5597
  .catch((error) => {
5132
- LoggerProxy.logger.error('Meeting:index#stopFloorRequest --> Error ', error);
5598
+ LoggerProxy.logger.error('Meeting:index#releaseScreenShareFloor --> Error ', error);
5133
5599
 
5134
5600
  Metrics.sendBehavioralMetric(
5135
5601
  BEHAVIORAL_METRICS.STOP_FLOOR_REQUEST_FAILURE,
@@ -5157,7 +5623,7 @@ export default class Meeting extends StatelessWebexPlugin {
5157
5623
  * @public
5158
5624
  * @memberof Meeting
5159
5625
  */
5160
- startRecording() {
5626
+ public startRecording() {
5161
5627
  return MeetingUtil.startRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5162
5628
  }
5163
5629
 
@@ -5167,7 +5633,7 @@ export default class Meeting extends StatelessWebexPlugin {
5167
5633
  * @public
5168
5634
  * @memberof Meeting
5169
5635
  */
5170
- stopRecording() {
5636
+ public stopRecording() {
5171
5637
  return MeetingUtil.stopRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5172
5638
  }
5173
5639
 
@@ -5177,7 +5643,7 @@ export default class Meeting extends StatelessWebexPlugin {
5177
5643
  * @public
5178
5644
  * @memberof Meeting
5179
5645
  */
5180
- pauseRecording() {
5646
+ public pauseRecording() {
5181
5647
  return MeetingUtil.pauseRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5182
5648
  }
5183
5649
 
@@ -5187,7 +5653,7 @@ export default class Meeting extends StatelessWebexPlugin {
5187
5653
  * @public
5188
5654
  * @memberof Meeting
5189
5655
  */
5190
- resumeRecording() {
5656
+ public resumeRecording() {
5191
5657
  return MeetingUtil.resumeRecording(this.meetingRequest, this.locusUrl, this.locusInfo);
5192
5658
  }
5193
5659
 
@@ -5197,7 +5663,7 @@ export default class Meeting extends StatelessWebexPlugin {
5197
5663
  * @public
5198
5664
  * @memberof Meeting
5199
5665
  */
5200
- lockMeeting() {
5666
+ public lockMeeting() {
5201
5667
  return MeetingUtil.lockMeeting(this.inMeetingActions, this.meetingRequest, this.locusUrl);
5202
5668
  }
5203
5669
 
@@ -5207,7 +5673,7 @@ export default class Meeting extends StatelessWebexPlugin {
5207
5673
  * @public
5208
5674
  * @memberof Meeting
5209
5675
  */
5210
- unlockMeeting() {
5676
+ public unlockMeeting() {
5211
5677
  return MeetingUtil.unlockMeeting(this.inMeetingActions, this.meetingRequest, this.locusUrl);
5212
5678
  }
5213
5679
 
@@ -5218,7 +5684,7 @@ export default class Meeting extends StatelessWebexPlugin {
5218
5684
  * @private
5219
5685
  * @memberof Meeting
5220
5686
  */
5221
- rejectWithErrorLog(message) {
5687
+ private rejectWithErrorLog(message: string) {
5222
5688
  LoggerProxy.logger.error(message);
5223
5689
 
5224
5690
  return Promise.reject(new Error(message));
@@ -5231,7 +5697,7 @@ export default class Meeting extends StatelessWebexPlugin {
5231
5697
  * @public
5232
5698
  * @memberof Meeting
5233
5699
  */
5234
- sendDTMF(tones) {
5700
+ public sendDTMF(tones: string) {
5235
5701
  if (this.locusInfo && this.locusInfo.self) {
5236
5702
  if (this.locusInfo.self.enableDTMF) {
5237
5703
  return this.meetingRequest
@@ -5262,7 +5728,19 @@ export default class Meeting extends StatelessWebexPlugin {
5262
5728
  * @public
5263
5729
  * @memberof Meeting
5264
5730
  */
5265
- changeVideoLayout(layoutType, renderInfo = {}) {
5731
+ public changeVideoLayout(
5732
+ layoutType?: string,
5733
+ renderInfo: {
5734
+ main: {
5735
+ width: number;
5736
+ height: number;
5737
+ };
5738
+ content: {
5739
+ width: number;
5740
+ height: number;
5741
+ };
5742
+ } = {} as any
5743
+ ) {
5266
5744
  const {main, content} = renderInfo;
5267
5745
  const {mediaDirection, remoteShare, remoteVideoTrack} = this.mediaProperties;
5268
5746
 
@@ -5358,7 +5836,7 @@ export default class Meeting extends StatelessWebexPlugin {
5358
5836
  * @param {String} level {LOW|MEDIUM|HIGH}
5359
5837
  * @returns {Promise<MediaStream>} localStream
5360
5838
  */
5361
- setLocalVideoQuality(level) {
5839
+ setLocalVideoQuality(level: string) {
5362
5840
  LoggerProxy.logger.log(`Meeting:index#setLocalVideoQuality --> Setting quality to ${level}`);
5363
5841
 
5364
5842
  if (!VIDEO_RESOLUTIONS[level]) {
@@ -5408,7 +5886,7 @@ export default class Meeting extends StatelessWebexPlugin {
5408
5886
  * @param {String} level {LOW|MEDIUM|HIGH}
5409
5887
  * @returns {Promise}
5410
5888
  */
5411
- setRemoteQualityLevel(level) {
5889
+ setRemoteQualityLevel(level: string) {
5412
5890
  LoggerProxy.logger.log(`Meeting:index#setRemoteQualityLevel --> Setting quality to ${level}`);
5413
5891
 
5414
5892
  if (!QUALITY_LEVELS[level]) {
@@ -5438,7 +5916,7 @@ export default class Meeting extends StatelessWebexPlugin {
5438
5916
  * @returns {Promise}
5439
5917
  * @deprecated After FHD support
5440
5918
  */
5441
- setMeetingQuality(level) {
5919
+ setMeetingQuality(level: string) {
5442
5920
  LoggerProxy.logger.log(`Meeting:index#setMeetingQuality --> Setting quality to ${level}`);
5443
5921
 
5444
5922
  if (!QUALITY_LEVELS[level]) {
@@ -5493,6 +5971,9 @@ export default class Meeting extends StatelessWebexPlugin {
5493
5971
  }
5494
5972
 
5495
5973
  /**
5974
+ *
5975
+ * NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
5976
+ *
5496
5977
  * @param {Object} options parameter
5497
5978
  * @param {Boolean} options.sendAudio send audio from the display share
5498
5979
  * @param {Boolean} options.sendShare send video from the display share
@@ -5502,7 +5983,13 @@ export default class Meeting extends StatelessWebexPlugin {
5502
5983
  * @param {Boolean} options.sharePreferences.highFrameRate if shareConstraints isn't provided, set default values based off of this boolean
5503
5984
  * @returns {Promise}
5504
5985
  */
5505
- shareScreen(options = {}) {
5986
+ shareScreen(
5987
+ options: {
5988
+ sendAudio: boolean;
5989
+ sendShare: boolean;
5990
+ sharePreferences: { shareConstraints: MediaTrackConstraints };
5991
+ } = {} as any
5992
+ ) {
5506
5993
  LoggerProxy.logger.log('Meeting:index#shareScreen --> Getting local share');
5507
5994
 
5508
5995
  const shareConstraints = {
@@ -5511,6 +5998,7 @@ export default class Meeting extends StatelessWebexPlugin {
5511
5998
  ...options
5512
5999
  };
5513
6000
 
6001
+ // @ts-ignore - config coming from registerPlugin
5514
6002
  return Media.getDisplayMedia(shareConstraints, this.config)
5515
6003
  .then((shareStream) => this.updateShare({
5516
6004
  sendShare: true,
@@ -5548,7 +6036,7 @@ export default class Meeting extends StatelessWebexPlugin {
5548
6036
  * @param {MediaStream} localShare
5549
6037
  * @returns {undefined}
5550
6038
  */
5551
- handleShareTrackEnded(localShare) {
6039
+ private handleShareTrackEnded(localShare: MediaStream) {
5552
6040
  if (this.wirelessShare) {
5553
6041
  this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
5554
6042
  }
@@ -5588,7 +6076,7 @@ export default class Meeting extends StatelessWebexPlugin {
5588
6076
  * @private
5589
6077
  * @memberof Meeting
5590
6078
  */
5591
- sendNetworkQualityEvent(res) {
6079
+ private sendNetworkQualityEvent(res: any) {
5592
6080
  Trigger.trigger(
5593
6081
  this,
5594
6082
  {
@@ -5610,7 +6098,7 @@ export default class Meeting extends StatelessWebexPlugin {
5610
6098
  * @private
5611
6099
  * @returns {undefined}
5612
6100
  */
5613
- handleMediaLogging({audioTrack, videoTrack}) {
6101
+ private handleMediaLogging({ audioTrack, videoTrack }: any) {
5614
6102
  MeetingUtil.handleVideoLogging(videoTrack);
5615
6103
  MeetingUtil.handleAudioLogging(audioTrack);
5616
6104
  }
@@ -5619,7 +6107,7 @@ export default class Meeting extends StatelessWebexPlugin {
5619
6107
  * @param {string} typeMedia 'audio' or 'video'
5620
6108
  * @returns {undefined}
5621
6109
  */
5622
- setStartSetupDelay(typeMedia) {
6110
+ setStartSetupDelay(typeMedia: string) {
5623
6111
  this[`startSetupDelay${typeMedia}`] = performance.now();
5624
6112
  this[`endSetupDelay${typeMedia}`] = undefined;
5625
6113
  }
@@ -5628,7 +6116,7 @@ export default class Meeting extends StatelessWebexPlugin {
5628
6116
  * @param {string} typeMedia 'audio' or 'video'
5629
6117
  * @returns {undefined}
5630
6118
  */
5631
- setEndSetupDelay(typeMedia) {
6119
+ setEndSetupDelay(typeMedia: string) {
5632
6120
  this[`endSetupDelay${typeMedia}`] = performance.now();
5633
6121
  }
5634
6122
 
@@ -5636,7 +6124,7 @@ export default class Meeting extends StatelessWebexPlugin {
5636
6124
  * @param {string} typeMedia 'audio' or 'video'
5637
6125
  * @returns {string} duration between start and end of setup
5638
6126
  */
5639
- getSetupDelayDuration(typeMedia) {
6127
+ getSetupDelayDuration(typeMedia: string) {
5640
6128
  const start = this[`startSetupDelay${typeMedia}`];
5641
6129
  const end = this[`endSetupDelay${typeMedia}`];
5642
6130
 
@@ -5647,7 +6135,7 @@ export default class Meeting extends StatelessWebexPlugin {
5647
6135
  * @param {string} typeMedia 'audio' or 'video'
5648
6136
  * @returns {undefined}
5649
6137
  */
5650
- setStartSendingMediaDelay(typeMedia) {
6138
+ setStartSendingMediaDelay(typeMedia: string) {
5651
6139
  this[`startSendingMediaDelay${typeMedia}`] = performance.now();
5652
6140
  this[`endSendingMediaDelay${typeMedia}`] = undefined;
5653
6141
  }
@@ -5656,7 +6144,7 @@ export default class Meeting extends StatelessWebexPlugin {
5656
6144
  * @param {string} typeMedia 'audio' or 'video'
5657
6145
  * @returns {undefined}
5658
6146
  */
5659
- setEndSendingMediaDelay(typeMedia) {
6147
+ setEndSendingMediaDelay(typeMedia: string) {
5660
6148
  this[`endSendingMediaDelay${typeMedia}`] = performance.now();
5661
6149
  }
5662
6150
 
@@ -5664,7 +6152,7 @@ export default class Meeting extends StatelessWebexPlugin {
5664
6152
  * @param {string} typeMedia 'audio' or 'video'
5665
6153
  * @returns {string} duration between join response and first media tx
5666
6154
  */
5667
- getSendingMediaDelayDuration(typeMedia) {
6155
+ getSendingMediaDelayDuration(typeMedia: string) {
5668
6156
  const start = this[`startSendingMediaDelay${typeMedia}`];
5669
6157
  const end = this[`endSendingMediaDelay${typeMedia}`];
5670
6158
 
@@ -5675,7 +6163,7 @@ export default class Meeting extends StatelessWebexPlugin {
5675
6163
  *
5676
6164
  * @returns {undefined}
5677
6165
  */
5678
- setStartLocalSDPGenRemoteSDPRecvDelay() {
6166
+ setStartLocalSDPGenRemoteSDPRecvDelay() {
5679
6167
  if (!this.startLocalSDPGenRemoteSDPRecvDelay) {
5680
6168
  this.startLocalSDPGenRemoteSDPRecvDelay = performance.now();
5681
6169
  this.endLocalSDPGenRemoteSDPRecvDelay = undefined;
@@ -5801,7 +6289,7 @@ export default class Meeting extends StatelessWebexPlugin {
5801
6289
  * @public
5802
6290
  * @memberof Meeting
5803
6291
  */
5804
- endMeetingForAll() {
6292
+ public endMeetingForAll() {
5805
6293
  Metrics.postEvent({event: eventType.LEAVE, meeting: this, data: {trigger: trigger.USER_INTERACTION, canProceed: false}});
5806
6294
 
5807
6295
  LoggerProxy.logger.log('Meeting:index#endMeetingForAll --> End meeting for All');
@@ -5886,7 +6374,7 @@ export default class Meeting extends StatelessWebexPlugin {
5886
6374
  * @public
5887
6375
  * @memberof Meeting
5888
6376
  */
5889
- isBnrEnabled() {
6377
+ public isBnrEnabled() {
5890
6378
  return this.effects && this.effects.isBnrEnabled();
5891
6379
  }
5892
6380
 
@@ -5897,7 +6385,7 @@ export default class Meeting extends StatelessWebexPlugin {
5897
6385
  * @param {MedaiStreamTrack} audioTrack from updateAudio
5898
6386
  * @memberof Meeting
5899
6387
  */
5900
- async internal_enableBNR(audioTrack) {
6388
+ private async internal_enableBNR(audioTrack: any) {
5901
6389
  try {
5902
6390
  LoggerProxy.logger.info('Meeting:index#internal_enableBNR. Internal enable BNR called');
5903
6391
  const bnrAudioTrack = await WebRTCMedia.Effects.BNR.enableBNR(audioTrack);
@@ -5918,7 +6406,7 @@ export default class Meeting extends StatelessWebexPlugin {
5918
6406
  * @public
5919
6407
  * @memberof Meeting
5920
6408
  */
5921
- enableBNR() {
6409
+ public enableBNR() {
5922
6410
  if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5923
6411
  return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
5924
6412
  }
@@ -5953,7 +6441,7 @@ export default class Meeting extends StatelessWebexPlugin {
5953
6441
  * @public
5954
6442
  * @memberof Meeting
5955
6443
  */
5956
- disableBNR() {
6444
+ public disableBNR() {
5957
6445
  if (typeof this.mediaProperties === 'undefined' || typeof this.mediaProperties.audioTrack === 'undefined') {
5958
6446
  return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
5959
6447
  }
@@ -6036,4 +6524,35 @@ export default class Meeting extends StatelessWebexPlugin {
6036
6524
  clearInterval(this.keepAliveTimerId);
6037
6525
  this.keepAliveTimerId = null;
6038
6526
  };
6527
+
6528
+ /**
6529
+ * Send a reaction inside the meeting.
6530
+ *
6531
+ * @param {ReactionType} reactionType - type of reaction to be sent. Example: "thumbs_up"
6532
+ * @param {SkinToneType} skinToneType - skin tone for the reaction. Example: "medium_dark"
6533
+ * @returns {Promise}
6534
+ * @public
6535
+ * @memberof Meeting
6536
+ */
6537
+ public sendReaction(reactionType: ReactionType, skinToneType?: SkinToneType) {
6538
+ const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
6539
+ const participantId = this.members.selfId;
6540
+
6541
+ const reactionData = Reactions[reactionType];
6542
+
6543
+ if (!reactionData) {
6544
+ return Promise.reject(new Error(`${reactionType} is not a valid reaction.`));
6545
+ }
6546
+ const skinToneData = SkinTones[skinToneType] || SkinTones.normal;
6547
+ const reaction: Reaction = {
6548
+ ...reactionData,
6549
+ tone: skinToneData
6550
+ };
6551
+
6552
+ if (reactionChannelUrl) {
6553
+ return this.meetingRequest.sendReaction({reactionChannelUrl, reaction, participantId});
6554
+ }
6555
+
6556
+ return Promise.reject(new Error('Error sending reaction, service url not found.'));
6557
+ }
6039
6558
  }