@webex/plugin-meetings 2.60.0 → 2.60.1-next.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (535) hide show
  1. package/README.md +46 -8
  2. package/dist/annotation/annotation.types.d.ts +42 -0
  3. package/dist/annotation/annotation.types.js +7 -0
  4. package/dist/annotation/annotation.types.js.map +1 -0
  5. package/dist/annotation/constants.d.ts +31 -0
  6. package/dist/annotation/constants.js +41 -0
  7. package/dist/annotation/constants.js.map +1 -0
  8. package/dist/annotation/index.d.ts +117 -0
  9. package/dist/annotation/index.js +357 -0
  10. package/dist/annotation/index.js.map +1 -0
  11. package/dist/breakouts/breakout.d.ts +8 -0
  12. package/dist/breakouts/breakout.js +215 -0
  13. package/dist/breakouts/breakout.js.map +1 -0
  14. package/dist/breakouts/collection.d.ts +5 -0
  15. package/dist/breakouts/collection.js +22 -0
  16. package/dist/breakouts/collection.js.map +1 -0
  17. package/dist/breakouts/edit-lock-error.d.ts +15 -0
  18. package/dist/breakouts/edit-lock-error.js +51 -0
  19. package/dist/breakouts/edit-lock-error.js.map +1 -0
  20. package/dist/breakouts/events.d.ts +8 -0
  21. package/dist/breakouts/events.js +44 -0
  22. package/dist/breakouts/events.js.map +1 -0
  23. package/dist/breakouts/index.d.ts +5 -0
  24. package/dist/breakouts/index.js +1047 -0
  25. package/dist/breakouts/index.js.map +1 -0
  26. package/dist/breakouts/request.d.ts +22 -0
  27. package/dist/breakouts/request.js +77 -0
  28. package/dist/breakouts/request.js.map +1 -0
  29. package/dist/breakouts/utils.d.ts +15 -0
  30. package/dist/breakouts/utils.js +64 -0
  31. package/dist/breakouts/utils.js.map +1 -0
  32. package/dist/common/browser-detection.js +2 -3
  33. package/dist/common/browser-detection.js.map +1 -1
  34. package/dist/common/collection.js +3 -4
  35. package/dist/common/collection.js.map +1 -1
  36. package/dist/common/config.js +1 -2
  37. package/dist/common/config.js.map +1 -1
  38. package/dist/common/errors/captcha-error.js +1 -2
  39. package/dist/common/errors/captcha-error.js.map +1 -1
  40. package/dist/common/errors/intent-to-join.js +1 -2
  41. package/dist/common/errors/intent-to-join.js.map +1 -1
  42. package/dist/common/errors/join-meeting.js +1 -2
  43. package/dist/common/errors/join-meeting.js.map +1 -1
  44. package/dist/common/errors/media.js +1 -2
  45. package/dist/common/errors/media.js.map +1 -1
  46. package/dist/common/errors/no-meeting-info.d.ts +14 -0
  47. package/dist/common/errors/no-meeting-info.js +50 -0
  48. package/dist/common/errors/no-meeting-info.js.map +1 -0
  49. package/dist/common/errors/parameter.js +3 -4
  50. package/dist/common/errors/parameter.js.map +1 -1
  51. package/dist/common/errors/password-error.js +1 -2
  52. package/dist/common/errors/password-error.js.map +1 -1
  53. package/dist/common/errors/permission.js +1 -2
  54. package/dist/common/errors/permission.js.map +1 -1
  55. package/dist/common/errors/{reclaim-host-role-error.js → reclaim-host-role-errors.js} +7 -11
  56. package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
  57. package/dist/common/errors/reconnection-in-progress.js +1 -2
  58. package/dist/common/errors/reconnection-in-progress.js.map +1 -1
  59. package/dist/common/errors/reconnection.js +1 -2
  60. package/dist/common/errors/reconnection.js.map +1 -1
  61. package/dist/common/errors/stats.js +1 -2
  62. package/dist/common/errors/stats.js.map +1 -1
  63. package/dist/common/errors/webex-errors.d.ts +20 -8
  64. package/dist/common/errors/webex-errors.js +48 -28
  65. package/dist/common/errors/webex-errors.js.map +1 -1
  66. package/dist/common/errors/webex-meetings-error.js +1 -2
  67. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  68. package/dist/common/events/events-scope.js +1 -2
  69. package/dist/common/events/events-scope.js.map +1 -1
  70. package/dist/common/events/events.js +1 -2
  71. package/dist/common/events/events.js.map +1 -1
  72. package/dist/common/events/trigger-proxy.js +1 -2
  73. package/dist/common/events/trigger-proxy.js.map +1 -1
  74. package/dist/common/events/util.js +1 -2
  75. package/dist/common/events/util.js.map +1 -1
  76. package/dist/common/logs/logger-config.js +1 -2
  77. package/dist/common/logs/logger-config.js.map +1 -1
  78. package/dist/common/logs/logger-proxy.js +2 -3
  79. package/dist/common/logs/logger-proxy.js.map +1 -1
  80. package/dist/common/logs/request.d.ts +3 -1
  81. package/dist/common/logs/request.js +8 -5
  82. package/dist/common/logs/request.js.map +1 -1
  83. package/dist/common/queue.d.ts +9 -7
  84. package/dist/common/queue.js +22 -9
  85. package/dist/common/queue.js.map +1 -1
  86. package/dist/config.d.ts +6 -7
  87. package/dist/config.js +8 -10
  88. package/dist/config.js.map +1 -1
  89. package/dist/constants.d.ts +217 -97
  90. package/dist/constants.js +416 -441
  91. package/dist/constants.js.map +1 -1
  92. package/dist/controls-options-manager/constants.js +3 -6
  93. package/dist/controls-options-manager/constants.js.map +1 -1
  94. package/dist/controls-options-manager/enums.d.ts +11 -1
  95. package/dist/controls-options-manager/enums.js +15 -6
  96. package/dist/controls-options-manager/enums.js.map +1 -1
  97. package/dist/controls-options-manager/index.d.ts +17 -1
  98. package/dist/controls-options-manager/index.js +127 -38
  99. package/dist/controls-options-manager/index.js.map +1 -1
  100. package/dist/controls-options-manager/types.d.ts +43 -0
  101. package/dist/controls-options-manager/types.js +7 -0
  102. package/dist/controls-options-manager/types.js.map +1 -0
  103. package/dist/controls-options-manager/util.d.ts +1 -7
  104. package/dist/controls-options-manager/util.js +309 -19
  105. package/dist/controls-options-manager/util.js.map +1 -1
  106. package/dist/index.d.ts +6 -3
  107. package/dist/index.js +121 -5
  108. package/dist/index.js.map +1 -1
  109. package/dist/interceptors/index.d.ts +2 -0
  110. package/dist/interceptors/index.js +15 -0
  111. package/dist/interceptors/index.js.map +1 -0
  112. package/dist/interceptors/locusRetry.d.ts +27 -0
  113. package/dist/interceptors/locusRetry.js +94 -0
  114. package/dist/interceptors/locusRetry.js.map +1 -0
  115. package/dist/interpretation/collection.d.ts +5 -0
  116. package/dist/interpretation/collection.js +22 -0
  117. package/dist/interpretation/collection.js.map +1 -0
  118. package/dist/interpretation/index.d.ts +5 -0
  119. package/dist/interpretation/index.js +365 -0
  120. package/dist/interpretation/index.js.map +1 -0
  121. package/dist/interpretation/siLanguage.d.ts +5 -0
  122. package/dist/interpretation/siLanguage.js +24 -0
  123. package/dist/interpretation/siLanguage.js.map +1 -0
  124. package/dist/locus-info/controlsUtils.js +100 -11
  125. package/dist/locus-info/controlsUtils.js.map +1 -1
  126. package/dist/locus-info/embeddedAppsUtils.js +3 -4
  127. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  128. package/dist/locus-info/fullState.js +1 -2
  129. package/dist/locus-info/fullState.js.map +1 -1
  130. package/dist/locus-info/hostUtils.js +1 -2
  131. package/dist/locus-info/hostUtils.js.map +1 -1
  132. package/dist/locus-info/index.d.ts +57 -4
  133. package/dist/locus-info/index.js +425 -84
  134. package/dist/locus-info/index.js.map +1 -1
  135. package/dist/locus-info/infoUtils.js +13 -5
  136. package/dist/locus-info/infoUtils.js.map +1 -1
  137. package/dist/locus-info/mediaSharesUtils.js +58 -3
  138. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  139. package/dist/locus-info/parser.d.ts +66 -6
  140. package/dist/locus-info/parser.js +253 -80
  141. package/dist/locus-info/parser.js.map +1 -1
  142. package/dist/locus-info/selfUtils.js +97 -13
  143. package/dist/locus-info/selfUtils.js.map +1 -1
  144. package/dist/media/index.d.ts +2 -0
  145. package/dist/media/index.js +107 -319
  146. package/dist/media/index.js.map +1 -1
  147. package/dist/media/properties.d.ts +38 -53
  148. package/dist/media/properties.js +96 -153
  149. package/dist/media/properties.js.map +1 -1
  150. package/dist/media/util.js +1 -22
  151. package/dist/media/util.js.map +1 -1
  152. package/dist/mediaQualityMetrics/config.d.ts +234 -230
  153. package/dist/mediaQualityMetrics/config.js +302 -498
  154. package/dist/mediaQualityMetrics/config.js.map +1 -1
  155. package/dist/meeting/in-meeting-actions.d.ts +88 -0
  156. package/dist/meeting/in-meeting-actions.js +94 -3
  157. package/dist/meeting/in-meeting-actions.js.map +1 -1
  158. package/dist/meeting/index.d.ts +591 -494
  159. package/dist/meeting/index.js +4732 -2990
  160. package/dist/meeting/index.js.map +1 -1
  161. package/dist/meeting/locusMediaRequest.d.ts +74 -0
  162. package/dist/meeting/locusMediaRequest.js +291 -0
  163. package/dist/meeting/locusMediaRequest.js.map +1 -0
  164. package/dist/meeting/muteState.d.ts +93 -25
  165. package/dist/meeting/muteState.js +224 -133
  166. package/dist/meeting/muteState.js.map +1 -1
  167. package/dist/meeting/request.d.ts +82 -47
  168. package/dist/meeting/request.js +297 -199
  169. package/dist/meeting/request.js.map +1 -1
  170. package/dist/meeting/request.type.d.ts +11 -0
  171. package/dist/meeting/request.type.js +7 -0
  172. package/dist/meeting/request.type.js.map +1 -0
  173. package/dist/meeting/state.js +1 -2
  174. package/dist/meeting/state.js.map +1 -1
  175. package/dist/meeting/util.d.ts +102 -1
  176. package/dist/meeting/util.js +605 -435
  177. package/dist/meeting/util.js.map +1 -1
  178. package/dist/meeting-info/collection.js +3 -4
  179. package/dist/meeting-info/collection.js.map +1 -1
  180. package/dist/meeting-info/index.d.ts +13 -1
  181. package/dist/meeting-info/index.js +74 -7
  182. package/dist/meeting-info/index.js.map +1 -1
  183. package/dist/meeting-info/meeting-info-v2.d.ts +31 -1
  184. package/dist/meeting-info/meeting-info-v2.js +200 -63
  185. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  186. package/dist/meeting-info/request.js +1 -2
  187. package/dist/meeting-info/request.js.map +1 -1
  188. package/dist/meeting-info/util.js +2 -3
  189. package/dist/meeting-info/util.js.map +1 -1
  190. package/dist/meeting-info/utilv2.js +39 -41
  191. package/dist/meeting-info/utilv2.js.map +1 -1
  192. package/dist/meetings/collection.d.ts +17 -0
  193. package/dist/meetings/collection.js +42 -4
  194. package/dist/meetings/collection.js.map +1 -1
  195. package/dist/meetings/index.d.ts +93 -21
  196. package/dist/meetings/index.js +490 -127
  197. package/dist/meetings/index.js.map +1 -1
  198. package/dist/meetings/meetings.types.d.ts +4 -0
  199. package/dist/meetings/meetings.types.js +7 -0
  200. package/dist/meetings/meetings.types.js.map +1 -0
  201. package/dist/meetings/request.js +4 -3
  202. package/dist/meetings/request.js.map +1 -1
  203. package/dist/meetings/util.js +107 -6
  204. package/dist/meetings/util.js.map +1 -1
  205. package/dist/member/index.d.ts +13 -1
  206. package/dist/member/index.js +45 -2
  207. package/dist/member/index.js.map +1 -1
  208. package/dist/member/member.types.js +3 -4
  209. package/dist/member/member.types.js.map +1 -1
  210. package/dist/member/types.d.ts +32 -0
  211. package/dist/member/types.js +23 -0
  212. package/dist/member/types.js.map +1 -0
  213. package/dist/member/util.js +120 -29
  214. package/dist/member/util.js.map +1 -1
  215. package/dist/members/collection.d.ts +5 -0
  216. package/dist/members/collection.js +11 -2
  217. package/dist/members/collection.js.map +1 -1
  218. package/dist/members/index.d.ts +56 -11
  219. package/dist/members/index.js +174 -47
  220. package/dist/members/index.js.map +1 -1
  221. package/dist/members/request.d.ts +67 -11
  222. package/dist/members/request.js +102 -54
  223. package/dist/members/request.js.map +1 -1
  224. package/dist/members/types.js +3 -4
  225. package/dist/members/types.js.map +1 -1
  226. package/dist/members/util.d.ts +214 -1
  227. package/dist/members/util.js +327 -284
  228. package/dist/members/util.js.map +1 -1
  229. package/dist/metrics/constants.d.ts +15 -6
  230. package/dist/metrics/constants.js +17 -9
  231. package/dist/metrics/constants.js.map +1 -1
  232. package/dist/metrics/index.d.ts +4 -111
  233. package/dist/metrics/index.js +4 -452
  234. package/dist/metrics/index.js.map +1 -1
  235. package/dist/multistream/mediaRequestManager.d.ts +118 -0
  236. package/dist/multistream/mediaRequestManager.js +344 -0
  237. package/dist/multistream/mediaRequestManager.js.map +1 -0
  238. package/dist/multistream/receiveSlot.d.ts +68 -0
  239. package/dist/multistream/receiveSlot.js +200 -0
  240. package/dist/multistream/receiveSlot.js.map +1 -0
  241. package/dist/multistream/receiveSlotManager.d.ts +56 -0
  242. package/dist/multistream/receiveSlotManager.js +174 -0
  243. package/dist/multistream/receiveSlotManager.js.map +1 -0
  244. package/dist/multistream/remoteMedia.d.ts +72 -0
  245. package/dist/multistream/remoteMedia.js +268 -0
  246. package/dist/multistream/remoteMedia.js.map +1 -0
  247. package/dist/multistream/remoteMediaGroup.d.ts +47 -0
  248. package/dist/multistream/remoteMediaGroup.js +267 -0
  249. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  250. package/dist/multistream/remoteMediaManager.d.ts +285 -0
  251. package/dist/multistream/remoteMediaManager.js +1211 -0
  252. package/dist/multistream/remoteMediaManager.js.map +1 -0
  253. package/dist/multistream/sendSlotManager.d.ts +61 -0
  254. package/dist/multistream/sendSlotManager.js +236 -0
  255. package/dist/multistream/sendSlotManager.js.map +1 -0
  256. package/dist/networkQualityMonitor/index.js +5 -4
  257. package/dist/networkQualityMonitor/index.js.map +1 -1
  258. package/dist/personal-meeting-room/index.js +2 -3
  259. package/dist/personal-meeting-room/index.js.map +1 -1
  260. package/dist/personal-meeting-room/request.js +2 -3
  261. package/dist/personal-meeting-room/request.js.map +1 -1
  262. package/dist/personal-meeting-room/util.js +1 -2
  263. package/dist/personal-meeting-room/util.js.map +1 -1
  264. package/dist/reachability/clusterReachability.d.ts +109 -0
  265. package/dist/reachability/clusterReachability.js +357 -0
  266. package/dist/reachability/clusterReachability.js.map +1 -0
  267. package/dist/reachability/index.d.ts +61 -95
  268. package/dist/reachability/index.js +300 -393
  269. package/dist/reachability/index.js.map +1 -1
  270. package/dist/reachability/request.d.ts +7 -3
  271. package/dist/reachability/request.js +18 -10
  272. package/dist/reachability/request.js.map +1 -1
  273. package/dist/reachability/util.d.ts +8 -0
  274. package/dist/reachability/util.js +29 -0
  275. package/dist/reachability/util.js.map +1 -0
  276. package/dist/reactions/constants.d.ts +3 -0
  277. package/dist/reactions/constants.js +12 -0
  278. package/dist/reactions/constants.js.map +1 -0
  279. package/dist/reactions/reactions.d.ts +2 -2
  280. package/dist/reactions/reactions.js +4 -6
  281. package/dist/reactions/reactions.js.map +1 -1
  282. package/dist/reactions/reactions.type.d.ts +23 -3
  283. package/dist/reactions/reactions.type.js +21 -23
  284. package/dist/reactions/reactions.type.js.map +1 -1
  285. package/dist/reconnection-manager/index.d.ts +32 -8
  286. package/dist/reconnection-manager/index.js +282 -231
  287. package/dist/reconnection-manager/index.js.map +1 -1
  288. package/dist/recording-controller/enums.js +4 -5
  289. package/dist/recording-controller/enums.js.map +1 -1
  290. package/dist/recording-controller/index.d.ts +15 -1
  291. package/dist/recording-controller/index.js +57 -46
  292. package/dist/recording-controller/index.js.map +1 -1
  293. package/dist/recording-controller/util.d.ts +5 -4
  294. package/dist/recording-controller/util.js +10 -10
  295. package/dist/recording-controller/util.js.map +1 -1
  296. package/dist/roap/index.d.ts +9 -47
  297. package/dist/roap/index.js +101 -235
  298. package/dist/roap/index.js.map +1 -1
  299. package/dist/roap/request.d.ts +18 -12
  300. package/dist/roap/request.js +126 -180
  301. package/dist/roap/request.js.map +1 -1
  302. package/dist/roap/turnDiscovery.d.ts +27 -16
  303. package/dist/roap/turnDiscovery.js +115 -105
  304. package/dist/roap/turnDiscovery.js.map +1 -1
  305. package/dist/rtcMetrics/constants.d.ts +4 -0
  306. package/dist/rtcMetrics/constants.js +11 -0
  307. package/dist/rtcMetrics/constants.js.map +1 -0
  308. package/dist/rtcMetrics/index.d.ts +54 -0
  309. package/dist/rtcMetrics/index.js +140 -0
  310. package/dist/rtcMetrics/index.js.map +1 -0
  311. package/dist/statsAnalyzer/global.d.ts +1 -83
  312. package/dist/statsAnalyzer/global.js +2 -85
  313. package/dist/statsAnalyzer/global.js.map +1 -1
  314. package/dist/statsAnalyzer/index.d.ts +50 -30
  315. package/dist/statsAnalyzer/index.js +435 -510
  316. package/dist/statsAnalyzer/index.js.map +1 -1
  317. package/dist/statsAnalyzer/mqaUtil.d.ts +8 -6
  318. package/dist/statsAnalyzer/mqaUtil.js +120 -83
  319. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  320. package/dist/transcription/index.js +1 -2
  321. package/dist/transcription/index.js.map +1 -1
  322. package/dist/webinar/collection.d.ts +16 -0
  323. package/dist/webinar/collection.js +43 -0
  324. package/dist/webinar/collection.js.map +1 -0
  325. package/dist/webinar/index.d.ts +5 -0
  326. package/dist/webinar/index.js +68 -0
  327. package/dist/webinar/index.js.map +1 -0
  328. package/package.json +38 -26
  329. package/src/annotation/annotation.types.ts +50 -0
  330. package/src/annotation/constants.ts +36 -0
  331. package/src/annotation/index.ts +328 -0
  332. package/src/breakouts/README.md +220 -0
  333. package/src/breakouts/breakout.ts +188 -0
  334. package/src/breakouts/collection.ts +19 -0
  335. package/src/breakouts/edit-lock-error.ts +25 -0
  336. package/src/breakouts/events.ts +56 -0
  337. package/src/breakouts/index.ts +925 -0
  338. package/src/breakouts/request.ts +55 -0
  339. package/src/breakouts/utils.ts +57 -0
  340. package/src/common/errors/no-meeting-info.ts +24 -0
  341. package/src/common/errors/webex-errors.ts +36 -12
  342. package/src/common/logs/logger-proxy.ts +1 -1
  343. package/src/common/logs/request.ts +5 -1
  344. package/src/common/queue.ts +22 -8
  345. package/src/config.ts +6 -7
  346. package/src/constants.ts +244 -97
  347. package/src/controls-options-manager/enums.ts +12 -0
  348. package/src/controls-options-manager/index.ts +116 -21
  349. package/src/controls-options-manager/types.ts +59 -0
  350. package/src/controls-options-manager/util.ts +294 -14
  351. package/src/index.ts +44 -0
  352. package/src/interceptors/index.ts +3 -0
  353. package/src/interceptors/locusRetry.ts +67 -0
  354. package/src/interpretation/README.md +60 -0
  355. package/src/interpretation/collection.ts +19 -0
  356. package/src/interpretation/index.ts +332 -0
  357. package/src/interpretation/siLanguage.ts +18 -0
  358. package/src/locus-info/controlsUtils.ts +110 -0
  359. package/src/locus-info/index.ts +449 -61
  360. package/src/locus-info/infoUtils.ts +14 -2
  361. package/src/locus-info/mediaSharesUtils.ts +64 -0
  362. package/src/locus-info/parser.ts +258 -47
  363. package/src/locus-info/selfUtils.ts +85 -2
  364. package/src/media/index.ts +153 -370
  365. package/src/media/properties.ts +106 -136
  366. package/src/media/util.ts +0 -21
  367. package/src/mediaQualityMetrics/config.ts +244 -377
  368. package/src/meeting/in-meeting-actions.ts +176 -0
  369. package/src/meeting/index.ts +3944 -2489
  370. package/src/meeting/locusMediaRequest.ts +313 -0
  371. package/src/meeting/muteState.ts +224 -138
  372. package/src/meeting/request.ts +207 -127
  373. package/src/meeting/request.type.ts +13 -0
  374. package/src/meeting/util.ts +590 -423
  375. package/src/meeting-info/index.ts +81 -8
  376. package/src/meeting-info/meeting-info-v2.ts +163 -13
  377. package/src/meeting-info/util.ts +1 -1
  378. package/src/meeting-info/utilv2.ts +28 -28
  379. package/src/meetings/collection.ts +33 -0
  380. package/src/meetings/index.ts +487 -126
  381. package/src/meetings/meetings.types.ts +12 -0
  382. package/src/meetings/request.ts +2 -0
  383. package/src/meetings/util.ts +116 -5
  384. package/src/member/index.ts +43 -1
  385. package/src/member/types.ts +38 -0
  386. package/src/member/util.ts +125 -28
  387. package/src/members/collection.ts +8 -0
  388. package/src/members/index.ts +187 -52
  389. package/src/members/request.ts +87 -27
  390. package/src/members/util.ts +332 -291
  391. package/src/metrics/constants.ts +15 -6
  392. package/src/metrics/index.ts +1 -471
  393. package/src/multistream/mediaRequestManager.ts +440 -0
  394. package/src/multistream/receiveSlot.ts +184 -0
  395. package/src/multistream/receiveSlotManager.ts +166 -0
  396. package/src/multistream/remoteMedia.ts +254 -0
  397. package/src/multistream/remoteMediaGroup.ts +284 -0
  398. package/src/multistream/remoteMediaManager.ts +1145 -0
  399. package/src/multistream/sendSlotManager.ts +170 -0
  400. package/src/networkQualityMonitor/index.ts +6 -6
  401. package/src/reachability/clusterReachability.ts +320 -0
  402. package/src/reachability/index.ts +243 -347
  403. package/src/reachability/request.ts +17 -8
  404. package/src/reachability/util.ts +24 -0
  405. package/src/reactions/constants.ts +4 -0
  406. package/src/reactions/reactions.ts +4 -4
  407. package/src/reactions/reactions.type.ts +30 -4
  408. package/src/reconnection-manager/index.ts +168 -156
  409. package/src/recording-controller/index.ts +20 -3
  410. package/src/recording-controller/util.ts +26 -9
  411. package/src/roap/index.ts +98 -241
  412. package/src/roap/request.ts +74 -148
  413. package/src/roap/turnDiscovery.ts +62 -56
  414. package/src/rtcMetrics/constants.ts +3 -0
  415. package/src/rtcMetrics/index.ts +124 -0
  416. package/src/statsAnalyzer/global.ts +1 -84
  417. package/src/statsAnalyzer/index.ts +477 -643
  418. package/src/statsAnalyzer/mqaUtil.ts +115 -114
  419. package/src/webinar/collection.ts +31 -0
  420. package/src/webinar/index.ts +62 -0
  421. package/test/integration/spec/converged-space-meetings.js +233 -0
  422. package/test/integration/spec/journey.js +320 -264
  423. package/test/integration/spec/space-meeting.js +77 -4
  424. package/test/unit/spec/annotation/index.ts +418 -0
  425. package/test/unit/spec/breakouts/breakout.ts +237 -0
  426. package/test/unit/spec/breakouts/collection.ts +15 -0
  427. package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
  428. package/test/unit/spec/breakouts/events.ts +89 -0
  429. package/test/unit/spec/breakouts/index.ts +1790 -0
  430. package/test/unit/spec/breakouts/request.ts +104 -0
  431. package/test/unit/spec/breakouts/utils.js +72 -0
  432. package/test/unit/spec/common/queue.js +31 -2
  433. package/test/unit/spec/controls-options-manager/index.js +163 -0
  434. package/test/unit/spec/controls-options-manager/util.js +576 -60
  435. package/test/unit/spec/fixture/locus.js +1 -0
  436. package/test/unit/spec/interceptors/locusRetry.ts +131 -0
  437. package/test/unit/spec/interpretation/collection.ts +15 -0
  438. package/test/unit/spec/interpretation/index.ts +589 -0
  439. package/test/unit/spec/interpretation/siLanguage.ts +28 -0
  440. package/test/unit/spec/locus-info/controlsUtils.js +323 -30
  441. package/test/unit/spec/locus-info/index.js +1390 -16
  442. package/test/unit/spec/locus-info/infoUtils.js +54 -16
  443. package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
  444. package/test/unit/spec/locus-info/lib/selfConstant.js +48 -0
  445. package/test/unit/spec/locus-info/mediaSharesUtils.ts +32 -0
  446. package/test/unit/spec/locus-info/parser.js +116 -35
  447. package/test/unit/spec/locus-info/selfUtils.js +275 -0
  448. package/test/unit/spec/media/index.ts +290 -0
  449. package/test/unit/spec/media/properties.ts +75 -84
  450. package/test/unit/spec/meeting/in-meeting-actions.ts +86 -0
  451. package/test/unit/spec/meeting/index.js +8187 -2769
  452. package/test/unit/spec/meeting/locusMediaRequest.ts +442 -0
  453. package/test/unit/spec/meeting/muteState.js +409 -213
  454. package/test/unit/spec/meeting/request.js +512 -42
  455. package/test/unit/spec/meeting/utils.js +741 -24
  456. package/test/unit/spec/meeting-info/index.js +300 -0
  457. package/test/unit/spec/meeting-info/meetinginfov2.js +527 -5
  458. package/test/unit/spec/meeting-info/utilv2.js +21 -0
  459. package/test/unit/spec/meetings/collection.js +26 -0
  460. package/test/unit/spec/meetings/index.js +1313 -243
  461. package/test/unit/spec/meetings/utils.js +202 -2
  462. package/test/unit/spec/member/index.js +32 -9
  463. package/test/unit/spec/member/util.js +499 -61
  464. package/test/unit/spec/members/index.js +394 -5
  465. package/test/unit/spec/members/request.js +206 -27
  466. package/test/unit/spec/members/utils.js +173 -38
  467. package/test/unit/spec/metrics/index.js +1 -50
  468. package/test/unit/spec/multistream/mediaRequestManager.ts +1418 -0
  469. package/test/unit/spec/multistream/receiveSlot.ts +163 -0
  470. package/test/unit/spec/multistream/receiveSlotManager.ts +203 -0
  471. package/test/unit/spec/multistream/remoteMedia.ts +255 -0
  472. package/test/unit/spec/multistream/remoteMediaGroup.ts +662 -0
  473. package/test/unit/spec/multistream/remoteMediaManager.ts +1924 -0
  474. package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
  475. package/test/unit/spec/networkQualityMonitor/index.js +4 -4
  476. package/test/unit/spec/reachability/clusterReachability.ts +279 -0
  477. package/test/unit/spec/reachability/index.ts +531 -24
  478. package/test/unit/spec/reachability/request.js +68 -0
  479. package/test/unit/spec/reachability/util.ts +40 -0
  480. package/test/unit/spec/reconnection-manager/index.js +162 -24
  481. package/test/unit/spec/recording-controller/index.js +293 -218
  482. package/test/unit/spec/recording-controller/util.js +223 -96
  483. package/test/unit/spec/roap/index.ts +200 -76
  484. package/test/unit/spec/roap/request.ts +255 -0
  485. package/test/unit/spec/roap/turnDiscovery.ts +86 -48
  486. package/test/unit/spec/rtcMetrics/index.ts +93 -0
  487. package/test/unit/spec/stats-analyzer/index.js +261 -167
  488. package/test/unit/spec/webinar/collection.ts +13 -0
  489. package/test/unit/spec/webinar/index.ts +60 -0
  490. package/test/utils/constants.js +9 -0
  491. package/test/utils/integrationTestUtils.js +46 -0
  492. package/test/utils/testUtils.js +0 -45
  493. package/test/utils/webex-config.js +4 -0
  494. package/test/utils/webex-test-users.js +7 -3
  495. package/dist/common/errors/reclaim-host-role-error.js.map +0 -1
  496. package/dist/meeting/effectsState.d.ts +0 -42
  497. package/dist/meeting/effectsState.js +0 -260
  498. package/dist/meeting/effectsState.js.map +0 -1
  499. package/dist/metrics/config.d.ts +0 -169
  500. package/dist/metrics/config.js +0 -289
  501. package/dist/metrics/config.js.map +0 -1
  502. package/dist/peer-connection-manager/index.d.ts +0 -6
  503. package/dist/peer-connection-manager/index.js +0 -671
  504. package/dist/peer-connection-manager/index.js.map +0 -1
  505. package/dist/peer-connection-manager/util.d.ts +0 -6
  506. package/dist/peer-connection-manager/util.js +0 -110
  507. package/dist/peer-connection-manager/util.js.map +0 -1
  508. package/dist/roap/collection.d.ts +0 -10
  509. package/dist/roap/collection.js +0 -63
  510. package/dist/roap/collection.js.map +0 -1
  511. package/dist/roap/handler.d.ts +0 -47
  512. package/dist/roap/handler.js +0 -279
  513. package/dist/roap/handler.js.map +0 -1
  514. package/dist/roap/state.d.ts +0 -9
  515. package/dist/roap/state.js +0 -127
  516. package/dist/roap/state.js.map +0 -1
  517. package/dist/roap/util.d.ts +0 -2
  518. package/dist/roap/util.js +0 -76
  519. package/dist/roap/util.js.map +0 -1
  520. package/src/index.js +0 -15
  521. package/src/meeting/effectsState.ts +0 -209
  522. package/src/metrics/config.ts +0 -485
  523. package/src/peer-connection-manager/index.ts +0 -847
  524. package/src/peer-connection-manager/util.ts +0 -119
  525. package/src/roap/collection.ts +0 -62
  526. package/src/roap/handler.ts +0 -294
  527. package/src/roap/state.ts +0 -156
  528. package/src/roap/util.ts +0 -100
  529. package/test/unit/spec/meeting/effectsState.js +0 -281
  530. package/test/unit/spec/peerconnection-manager/index.js +0 -218
  531. package/test/unit/spec/peerconnection-manager/utils.js +0 -49
  532. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -388
  533. package/test/unit/spec/roap/util.js +0 -30
  534. /package/dist/common/errors/{reclaim-host-role-error.d.ts → reclaim-host-role-errors.d.ts} +0 -0
  535. /package/src/common/errors/{reclaim-host-role-error.ts → reclaim-host-role-errors.ts} +0 -0
@@ -0,0 +1,1145 @@
1
+ /* eslint-disable valid-jsdoc */
2
+ import {cloneDeep, forEach, remove} from 'lodash';
3
+ import {EventMap} from 'typed-emitter';
4
+ import {MediaType} from '@webex/internal-media-core';
5
+
6
+ import LoggerProxy from '../common/logs/logger-proxy';
7
+ import EventsScope from '../common/events/events-scope';
8
+
9
+ import {RemoteMedia, RemoteVideoResolution} from './remoteMedia';
10
+ import {ReceiveSlot, CSI} from './receiveSlot';
11
+ import {ReceiveSlotManager} from './receiveSlotManager';
12
+ import {RemoteMediaGroup} from './remoteMediaGroup';
13
+ import {MediaRequestManager} from './mediaRequestManager';
14
+
15
+ export type PaneSize = RemoteVideoResolution;
16
+ export type LayoutId = string;
17
+ export type PaneId = string;
18
+ export type PaneGroupId = string;
19
+
20
+ export interface ActiveSpeakerVideoPaneGroup {
21
+ id: PaneGroupId;
22
+ numPanes: number; // maximum number of panes in the group (actual number may be lower, if there are not enough participants in the meeting)
23
+ size: PaneSize; // preferred size for all panes in the group
24
+ priority: number; // 0-255 (255 = highest priority), each group must have a different priority from all other groups
25
+ }
26
+
27
+ export interface MemberVideoPane {
28
+ id: PaneId;
29
+ size: PaneSize;
30
+ csi?: CSI;
31
+ }
32
+
33
+ export interface VideoLayout {
34
+ screenShareVideo?: {
35
+ size: PaneSize;
36
+ };
37
+ activeSpeakerVideoPaneGroups?: ActiveSpeakerVideoPaneGroup[]; // list of active speaker video pane groups
38
+ memberVideoPanes?: MemberVideoPane[]; // list of video panes for specific members, CSI values can be changed later via setVideoPaneCsi()
39
+ }
40
+
41
+ export interface Configuration {
42
+ audio: {
43
+ numOfActiveSpeakerStreams: number; // number of audio streams we want to receive
44
+ numOfScreenShareStreams: number; // 1 should be enough, because in webex only 1 person at a time can be presenting screen share
45
+ };
46
+ video: {
47
+ preferLiveVideo: boolean; // applies to all pane groups with active speaker policy
48
+ initialLayoutId: LayoutId;
49
+
50
+ layouts: {[key: LayoutId]: VideoLayout}; // a map of all available layouts, a layout can be set via setLayout() method
51
+ };
52
+ }
53
+
54
+ /* Predefined layouts: */
55
+
56
+ // An "all equal" grid, with size up to 3 x 3 = 9:
57
+ const AllEqualLayout: VideoLayout = {
58
+ activeSpeakerVideoPaneGroups: [
59
+ {
60
+ id: 'main',
61
+ numPanes: 9,
62
+ size: 'best',
63
+ priority: 255,
64
+ },
65
+ ],
66
+ };
67
+
68
+ // A layout with just a single remote active speaker video pane:
69
+ const SingleLayout: VideoLayout = {
70
+ activeSpeakerVideoPaneGroups: [
71
+ {
72
+ id: 'main',
73
+ numPanes: 1,
74
+ size: 'best',
75
+ priority: 255,
76
+ },
77
+ ],
78
+ };
79
+
80
+ // A layout with 1 big pane for the highest priority active speaker and 5 small panes for other active speakers:
81
+ const OnePlusFiveLayout: VideoLayout = {
82
+ activeSpeakerVideoPaneGroups: [
83
+ {
84
+ id: 'mainBigOne',
85
+ numPanes: 1,
86
+ size: 'large',
87
+ priority: 255,
88
+ },
89
+ {
90
+ id: 'secondarySetOfSmallPanes',
91
+ numPanes: 5,
92
+ size: 'very small',
93
+ priority: 254,
94
+ },
95
+ ],
96
+ };
97
+
98
+ // A layout with 2 big panes for 2 main active speakers and a strip of 6 small panes for other active speakers:
99
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
100
+ const TwoMainPlusSixSmallLayout: VideoLayout = {
101
+ activeSpeakerVideoPaneGroups: [
102
+ {
103
+ id: 'mainGroupWith2BigPanes',
104
+ numPanes: 2,
105
+ size: 'large',
106
+ priority: 255,
107
+ },
108
+ {
109
+ id: 'secondaryGroupOfSmallPanes',
110
+ numPanes: 6,
111
+ size: 'small',
112
+ priority: 254,
113
+ },
114
+ ],
115
+ };
116
+
117
+ // A strip of 8 small video panes (thumbnails) displayed at the top of a remote screenshare:
118
+ const RemoteScreenShareWithSmallThumbnailsLayout: VideoLayout = {
119
+ screenShareVideo: {size: 'best'},
120
+ activeSpeakerVideoPaneGroups: [
121
+ {
122
+ id: 'thumbnails',
123
+ numPanes: 8,
124
+ size: 'thumbnail',
125
+ priority: 255,
126
+ },
127
+ ],
128
+ };
129
+
130
+ // A staged layout with 4 pre-selected meeting participants in the main 2x2 grid and 6 small panes for other active speakers at the top:
131
+ const Stage2x2With6ThumbnailsLayout: VideoLayout = {
132
+ activeSpeakerVideoPaneGroups: [
133
+ {
134
+ id: 'thumbnails',
135
+ numPanes: 6,
136
+ size: 'thumbnail',
137
+ priority: 255,
138
+ },
139
+ ],
140
+ memberVideoPanes: [
141
+ {id: 'stage-1', size: 'medium', csi: undefined},
142
+ {id: 'stage-2', size: 'medium', csi: undefined},
143
+ {id: 'stage-3', size: 'medium', csi: undefined},
144
+ {id: 'stage-4', size: 'medium', csi: undefined},
145
+ ],
146
+ };
147
+
148
+ /**
149
+ * Default configuration:
150
+ * - uses 3 audio streams
151
+ * - prefers active speakers with live video (e.g. are not audio only or video muted) over active speakers without live video
152
+ * - has a few layouts defined, including 1 that contains remote screen share (ScreenShareView)
153
+ */
154
+ export const DefaultConfiguration: Configuration = {
155
+ audio: {
156
+ numOfActiveSpeakerStreams: 3,
157
+ numOfScreenShareStreams: 1,
158
+ },
159
+ video: {
160
+ preferLiveVideo: true,
161
+ initialLayoutId: 'AllEqual',
162
+
163
+ layouts: {
164
+ AllEqual: AllEqualLayout,
165
+ OnePlusFive: OnePlusFiveLayout,
166
+ Single: SingleLayout,
167
+ Stage: Stage2x2With6ThumbnailsLayout,
168
+ ScreenShareView: RemoteScreenShareWithSmallThumbnailsLayout,
169
+ },
170
+ },
171
+ };
172
+
173
+ export enum Event {
174
+ // events for audio streams
175
+ AudioCreated = 'AudioCreated',
176
+ ScreenShareAudioCreated = 'ScreenShareAudioCreated',
177
+
178
+ // events for video streams
179
+ VideoLayoutChanged = 'VideoLayoutChanged',
180
+ }
181
+
182
+ export interface VideoLayoutChangedEventData {
183
+ layoutId: LayoutId;
184
+ activeSpeakerVideoPanes: {
185
+ [key: PaneGroupId]: RemoteMediaGroup;
186
+ };
187
+ memberVideoPanes: {[key: PaneId]: RemoteMedia};
188
+ screenShareVideo?: RemoteMedia;
189
+ }
190
+ export interface Events extends EventMap {
191
+ // audio
192
+ [Event.AudioCreated]: (audio: RemoteMediaGroup) => void;
193
+ [Event.ScreenShareAudioCreated]: (screenShareAudio: RemoteMediaGroup) => void;
194
+
195
+ // video
196
+ [Event.VideoLayoutChanged]: (data: VideoLayoutChangedEventData) => void;
197
+ }
198
+
199
+ /**
200
+ * A helper class that manages all remote audio/video streams in order to achieve a predefined set of layouts.
201
+ * It also creates a fixed number of audio streams and these don't change during the meeting.
202
+ *
203
+ * Things that RemoteMediaManager does:
204
+ * - owns the receive slots (creates them when needed, and re-uses them when switching layouts)
205
+ * - constructs appropriate RemoteMedia and RemoteMediaGroup objects and sends appropriate mediaRequests
206
+ */
207
+ export class RemoteMediaManager extends EventsScope {
208
+ private config: Configuration;
209
+
210
+ private started: boolean;
211
+
212
+ private receiveSlotManager: ReceiveSlotManager;
213
+
214
+ private mediaRequestManagers: {
215
+ audio: MediaRequestManager;
216
+ video: MediaRequestManager;
217
+ screenShareAudio: MediaRequestManager;
218
+ screenShareVideo: MediaRequestManager;
219
+ };
220
+
221
+ private currentLayout?: VideoLayout;
222
+
223
+ private slots: {
224
+ audio: ReceiveSlot[];
225
+ screenShare: {
226
+ audio: ReceiveSlot[];
227
+ video?: ReceiveSlot;
228
+ };
229
+ video: {
230
+ unused: ReceiveSlot[];
231
+ activeSpeaker: ReceiveSlot[];
232
+ receiverSelected: ReceiveSlot[];
233
+ };
234
+ };
235
+
236
+ private media: {
237
+ audio?: RemoteMediaGroup;
238
+ video: {
239
+ activeSpeakerGroups: {
240
+ [key: PaneGroupId]: RemoteMediaGroup;
241
+ };
242
+ memberPanes: {[key: PaneId]: RemoteMedia};
243
+ };
244
+ screenShare: {
245
+ audio?: RemoteMediaGroup;
246
+ video?: RemoteMediaGroup;
247
+ };
248
+ };
249
+
250
+ private receiveSlotAllocations: {
251
+ activeSpeaker: {[key: PaneGroupId]: {slots: ReceiveSlot[]}};
252
+ receiverSelected: {[key: PaneId]: ReceiveSlot};
253
+ };
254
+
255
+ private currentLayoutId?: LayoutId;
256
+
257
+ /**
258
+ * Constructor
259
+ *
260
+ * @param {ReceiveSlotManager} receiveSlotManager
261
+ * @param {{audio: MediaRequestManager, video: mediaRequestManagers}} mediaRequestManagers
262
+ * @param {Configuration} config Configuration describing what video layouts to use during the meeting
263
+ */
264
+ constructor(
265
+ receiveSlotManager: ReceiveSlotManager,
266
+ mediaRequestManagers: {
267
+ audio: MediaRequestManager;
268
+ video: MediaRequestManager;
269
+ screenShareAudio: MediaRequestManager;
270
+ screenShareVideo: MediaRequestManager;
271
+ },
272
+ config: Configuration = DefaultConfiguration
273
+ ) {
274
+ super();
275
+ this.started = false;
276
+ this.config = config;
277
+ this.receiveSlotManager = receiveSlotManager;
278
+ this.mediaRequestManagers = mediaRequestManagers;
279
+ this.media = {
280
+ audio: undefined,
281
+ video: {
282
+ activeSpeakerGroups: {},
283
+ memberPanes: {},
284
+ },
285
+ screenShare: {
286
+ audio: undefined,
287
+ video: undefined,
288
+ },
289
+ };
290
+
291
+ this.checkConfigValidity();
292
+
293
+ this.slots = {
294
+ audio: [],
295
+ screenShare: {
296
+ audio: [],
297
+ video: undefined,
298
+ },
299
+ video: {
300
+ unused: [],
301
+ activeSpeaker: [],
302
+ receiverSelected: [],
303
+ },
304
+ };
305
+
306
+ this.receiveSlotAllocations = {activeSpeaker: {}, receiverSelected: {}};
307
+
308
+ LoggerProxy.logger.log(
309
+ `RemoteMediaManager#constructor --> RemoteMediaManager created with config: ${JSON.stringify(
310
+ this.config
311
+ )}`
312
+ );
313
+ }
314
+
315
+ /**
316
+ * Checks if configuration is valid, throws an error if it's not
317
+ */
318
+ private checkConfigValidity() {
319
+ if (!(this.config.video.initialLayoutId in this.config.video.layouts)) {
320
+ throw new Error(
321
+ `invalid config: initialLayoutId "${this.config.video.initialLayoutId}" doesn't match any of the layouts`
322
+ );
323
+ }
324
+
325
+ // check if each layout is valid
326
+ Object.values(this.config.video.layouts).forEach((layout) => {
327
+ const groupIds = {};
328
+ const paneIds = {};
329
+ const groupPriorites = {};
330
+
331
+ layout.activeSpeakerVideoPaneGroups?.forEach((group) => {
332
+ if (groupIds[group.id]) {
333
+ throw new Error(
334
+ `invalid config: duplicate active speaker video pane group id: ${group.id}`
335
+ );
336
+ }
337
+ groupIds[group.id] = true;
338
+
339
+ if (groupPriorites[group.priority]) {
340
+ throw new Error(
341
+ `invalid config: multiple active speaker video pane groups have same priority: ${group.priority}`
342
+ );
343
+ }
344
+ groupPriorites[group.priority] = true;
345
+ });
346
+
347
+ layout.memberVideoPanes?.forEach((pane) => {
348
+ if (paneIds[pane.id]) {
349
+ throw new Error(`invalid config: duplicate member video pane id: ${pane.id}`);
350
+ }
351
+ paneIds[pane.id] = true;
352
+ });
353
+ });
354
+ }
355
+
356
+ /**
357
+ * Starts the RemoteMediaManager.
358
+ *
359
+ * @returns {Promise}
360
+ */
361
+ public async start() {
362
+ if (this.started) {
363
+ throw new Error('start() failure: already started');
364
+ }
365
+ this.started = true;
366
+
367
+ await this.createAudioMedia();
368
+
369
+ await this.createScreenShareReceiveSlots();
370
+ this.createScreenShareAudioMedia();
371
+
372
+ await this.preallocateVideoReceiveSlots();
373
+
374
+ await this.setLayout(this.config.video.initialLayoutId);
375
+ }
376
+
377
+ /**
378
+ * Releases all the used resources (like allocated receive slots). This function needs
379
+ * to be called when we leave the meeting, etc.
380
+ */
381
+ public stop() {
382
+ // invalidate all remoteMedia objects
383
+ this.invalidateCurrentRemoteMedia({
384
+ audio: true,
385
+ video: true,
386
+ screenShareAudio: true,
387
+ screenShareVideo: true,
388
+ commit: true,
389
+ });
390
+
391
+ // release all audio receive slots
392
+ this.slots.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
393
+ this.slots.audio.length = 0;
394
+
395
+ // release screen share slots
396
+ this.slots.screenShare.audio.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
397
+ this.slots.screenShare.audio.length = 0;
398
+ if (this.slots.screenShare.video) {
399
+ this.receiveSlotManager.releaseSlot(this.slots.screenShare.video);
400
+ this.slots.screenShare.video = undefined;
401
+ }
402
+
403
+ // release video slots
404
+ this.receiveSlotAllocations = {activeSpeaker: {}, receiverSelected: {}};
405
+
406
+ this.slots.video.unused.push(...this.slots.video.activeSpeaker);
407
+ this.slots.video.activeSpeaker.length = 0;
408
+
409
+ this.slots.video.unused.push(...this.slots.video.receiverSelected);
410
+ this.slots.video.receiverSelected.length = 0;
411
+
412
+ this.releaseUnusedVideoSlots();
413
+
414
+ this.currentLayout = undefined;
415
+ this.currentLayoutId = undefined;
416
+ this.started = false;
417
+ }
418
+
419
+ /**
420
+ * Returns the total number of main video panes required for a given layout
421
+ *
422
+ * @param {VideoLayout} layout
423
+ * @returns {number}
424
+ */
425
+ private getRequiredNumVideoSlotsForLayout(layout?: VideoLayout) {
426
+ if (!layout) {
427
+ return 0;
428
+ }
429
+
430
+ const activeSpeakerCount =
431
+ layout.activeSpeakerVideoPaneGroups?.reduce(
432
+ (sum, paneGroup) => sum + paneGroup.numPanes,
433
+ 0
434
+ ) || 0;
435
+
436
+ const receiverSelectedCount = layout.memberVideoPanes?.length || 0;
437
+
438
+ return activeSpeakerCount + receiverSelectedCount;
439
+ }
440
+
441
+ /**
442
+ * Allocates the maximum number of panes that any of the configured layouts will require.
443
+ * We do this at the beginning, because it's more efficient (much faster) then allocating receive slots
444
+ * later, after the SDP exchange was done.
445
+ */
446
+ private async preallocateVideoReceiveSlots() {
447
+ const maxNumVideoPanesRequired = Object.values(this.config.video.layouts).reduce(
448
+ (maxValue, layout) => Math.max(maxValue, this.getRequiredNumVideoSlotsForLayout(layout)),
449
+ 0
450
+ );
451
+
452
+ while (this.slots.video.unused.length < maxNumVideoPanesRequired) {
453
+ // eslint-disable-next-line no-await-in-loop
454
+ this.slots.video.unused.push(
455
+ // eslint-disable-next-line no-await-in-loop
456
+ await this.receiveSlotManager.allocateSlot(MediaType.VideoMain)
457
+ );
458
+ }
459
+ }
460
+
461
+ /**
462
+ * Changes the layout (triggers Event.VideoLayoutChanged)
463
+ *
464
+ * @param {LayoutId} layoutId new layout id
465
+ * @returns {Promise}
466
+ */
467
+ public async setLayout(layoutId: LayoutId) {
468
+ if (!(layoutId in this.config.video.layouts)) {
469
+ throw new Error(
470
+ `invalid layoutId: "${layoutId}" doesn't match any of the configured layouts`
471
+ );
472
+ }
473
+ if (!this.started) {
474
+ throw new Error('setLayout() called before start()');
475
+ }
476
+ LoggerProxy.logger.log(`RemoteMediaManager#setLayout --> new layout selected: ${layoutId}`);
477
+
478
+ this.currentLayoutId = layoutId;
479
+ this.currentLayout = cloneDeep(this.config.video.layouts[this.currentLayoutId]);
480
+
481
+ await this.updateVideoReceiveSlots();
482
+ this.updateVideoRemoteMediaObjects();
483
+ this.updateScreenShareVideoRemoteMediaObject();
484
+ this.emitVideoLayoutChangedEvent();
485
+ }
486
+
487
+ /**
488
+ * Returns the currently selected layout id
489
+ *
490
+ * @returns {LayoutId}
491
+ */
492
+ public getLayoutId(): LayoutId | undefined {
493
+ return this.currentLayoutId;
494
+ }
495
+
496
+ /**
497
+ * sets the preferLiveVideo
498
+ */
499
+ public setPreferLiveVideo(preferLiveVideo: boolean) {
500
+ LoggerProxy.logger.log(
501
+ `RemoteMediaManager#setPreferLiveVideo --> setPreferLiveVideo is called to set preferLiveVideo to ${preferLiveVideo}`
502
+ );
503
+ this.config.video.preferLiveVideo = preferLiveVideo;
504
+ Object.values(this.media.video.activeSpeakerGroups).forEach((activeSpeakerGroup) => {
505
+ activeSpeakerGroup.setPreferLiveVideo(preferLiveVideo, false);
506
+ });
507
+ this.mediaRequestManagers.video.commit();
508
+ }
509
+
510
+ /**
511
+ * Sets CSIs for multiple RemoteMedia instances belonging to RemoteMediaGroup.
512
+ * For each entry in the remoteMediaCsis array:
513
+ * - if csi is specified, the RemoteMedia instance is pinned to that CSI
514
+ * - if csi is undefined, the RemoteMedia instance is unpinned
515
+ */
516
+ public setActiveSpeakerCsis(remoteMediaCsis: {remoteMedia: RemoteMedia; csi?: number}[]) {
517
+ Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
518
+ const groupRemoteMediaCsis = remoteMediaCsis.filter(({remoteMedia}) =>
519
+ remoteMediaGroup.includes(remoteMedia)
520
+ );
521
+ if (groupRemoteMediaCsis.length > 0) {
522
+ remoteMediaGroup.setActiveSpeakerCsis(groupRemoteMediaCsis, false);
523
+ }
524
+ });
525
+ this.mediaRequestManagers.video.commit();
526
+ }
527
+
528
+ /**
529
+ * Creates the audio slots
530
+ */
531
+ private async createAudioMedia() {
532
+ // create the audio receive slots
533
+ for (let i = 0; i < this.config.audio.numOfActiveSpeakerStreams; i += 1) {
534
+ // eslint-disable-next-line no-await-in-loop
535
+ const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioMain);
536
+
537
+ this.slots.audio.push(slot);
538
+ }
539
+
540
+ // create a remote media group
541
+ this.media.audio = new RemoteMediaGroup(
542
+ this.mediaRequestManagers.audio,
543
+ this.slots.audio,
544
+ 255,
545
+ true
546
+ );
547
+
548
+ this.emit(
549
+ {file: 'multistream/remoteMediaManager', function: 'createAudioMedia'},
550
+ Event.AudioCreated,
551
+ this.media.audio
552
+ );
553
+ }
554
+
555
+ /**
556
+ * Creates receive slots required for receiving screen share audio and video
557
+ */
558
+ private async createScreenShareReceiveSlots() {
559
+ // audio
560
+ for (let i = 0; i < this.config.audio.numOfScreenShareStreams; i += 1) {
561
+ // eslint-disable-next-line no-await-in-loop
562
+ const slot = await this.receiveSlotManager.allocateSlot(MediaType.AudioSlides);
563
+
564
+ this.slots.screenShare.audio.push(slot);
565
+ }
566
+
567
+ // video
568
+ const isAnyLayoutContainingScreenShareVideo = Object.values(this.config.video.layouts).some(
569
+ (layout) => !!layout.screenShareVideo
570
+ );
571
+
572
+ if (isAnyLayoutContainingScreenShareVideo) {
573
+ this.slots.screenShare.video = await this.receiveSlotManager.allocateSlot(
574
+ MediaType.VideoSlides
575
+ );
576
+ }
577
+ }
578
+
579
+ /**
580
+ * Creates RemoteMedia objects for screen share
581
+ */
582
+ private createScreenShareAudioMedia() {
583
+ if (this.slots.screenShare.audio.length > 0) {
584
+ this.media.screenShare.audio = new RemoteMediaGroup(
585
+ this.mediaRequestManagers.screenShareAudio,
586
+ this.slots.screenShare.audio,
587
+ 255,
588
+ true
589
+ );
590
+
591
+ this.emit(
592
+ {file: 'multistream/remoteMediaManager', function: 'createScreenShareAudioMedia'},
593
+ Event.ScreenShareAudioCreated,
594
+ this.media.screenShare.audio
595
+ );
596
+ }
597
+ }
598
+
599
+ /**
600
+ * Goes over all receiver-selected slots and keeps only the ones that are required by a given layout,
601
+ * the rest are all moved to the "unused" list
602
+ */
603
+ private trimReceiverSelectedSlots() {
604
+ const requiredCsis = {};
605
+
606
+ // fill requiredCsis with all the CSIs that the given layout requires
607
+ this.currentLayout?.memberVideoPanes?.forEach((memberVideoPane) => {
608
+ if (memberVideoPane.csi !== undefined) {
609
+ requiredCsis[memberVideoPane.csi] = true;
610
+ }
611
+ });
612
+
613
+ const isCsiNeededByCurrentLayout = (csi?: CSI): boolean => {
614
+ if (csi === undefined) {
615
+ return false;
616
+ }
617
+
618
+ return !!requiredCsis[csi];
619
+ };
620
+
621
+ // keep receiverSelected slots that match our new requiredCsis, move the rest of receiverSelected slots to unused
622
+ const notNeededReceiverSelectedSlots = remove(
623
+ this.slots.video.receiverSelected,
624
+ (slot) => isCsiNeededByCurrentLayout(slot.csi) === false
625
+ );
626
+
627
+ this.slots.video.unused.push(...notNeededReceiverSelectedSlots);
628
+ }
629
+
630
+ /**
631
+ * Releases all the "unused" video slots.
632
+ */
633
+ private releaseUnusedVideoSlots() {
634
+ this.slots.video.unused.forEach((slot) => this.receiveSlotManager.releaseSlot(slot));
635
+ this.slots.video.unused.length = 0;
636
+ }
637
+
638
+ /**
639
+ * Allocates receive slots to all active speaker video panes
640
+ * in the current selected layout.
641
+ *
642
+ * Allocation tries to keep the same order of the slots between the previous
643
+ * layout and the new one. Sorting helps making sure that highest priority slots
644
+ * go in the same order in the new layout.
645
+ */
646
+ private allocateSlotsToActiveSpeakerPaneGroups() {
647
+ this.currentLayout?.activeSpeakerVideoPaneGroups
648
+ // sorting in descending order based on group priority
649
+ ?.sort((a, b) => (a.priority < b.priority ? 1 : -1))
650
+ ?.forEach((group) => {
651
+ this.receiveSlotAllocations.activeSpeaker[group.id] = {slots: []};
652
+
653
+ for (let paneIndex = 0; paneIndex < group.numPanes; paneIndex += 1) {
654
+ // allocate a slot from the "unused" list, by grabbing in same order (shift) as previous layout
655
+ const freeSlot = this.slots.video.unused.shift();
656
+
657
+ if (freeSlot) {
658
+ this.slots.video.activeSpeaker.push(freeSlot);
659
+ this.receiveSlotAllocations.activeSpeaker[group.id].slots.push(freeSlot);
660
+ }
661
+ }
662
+ });
663
+ }
664
+
665
+ /**
666
+ * Allocates receive slots to all receiver selected video panes
667
+ * in the current selected layout
668
+ */
669
+ private allocateSlotsToReceiverSelectedVideoPaneGroups() {
670
+ this.currentLayout?.memberVideoPanes?.forEach((memberPane) => {
671
+ // check if there is existing slot for this csi
672
+ const existingSlot = this.slots.video.receiverSelected.find(
673
+ (slot) => slot.csi === memberPane.csi
674
+ );
675
+
676
+ const isExistingSlotAlreadyAllocated = Object.values(
677
+ this.receiveSlotAllocations.receiverSelected
678
+ ).includes(existingSlot);
679
+
680
+ if (memberPane.csi !== undefined && existingSlot && !isExistingSlotAlreadyAllocated) {
681
+ // found it, so use it
682
+ this.receiveSlotAllocations.receiverSelected[memberPane.id] = existingSlot;
683
+ } else {
684
+ // allocate a slot from the "unused" list
685
+ const freeSlot = this.slots.video.unused.pop();
686
+
687
+ if (freeSlot) {
688
+ this.slots.video.receiverSelected.push(freeSlot);
689
+ this.receiveSlotAllocations.receiverSelected[memberPane.id] = freeSlot;
690
+ }
691
+ }
692
+ });
693
+ }
694
+
695
+ /**
696
+ * Ensures that we have enough slots for the current layout.
697
+ */
698
+ private async refillRequiredSlotsIfNeeded() {
699
+ const requiredNumSlots = this.getRequiredNumVideoSlotsForLayout(this.currentLayout);
700
+ const totalNumSlots =
701
+ this.slots.video.unused.length +
702
+ this.slots.video.activeSpeaker.length +
703
+ this.slots.video.receiverSelected.length;
704
+
705
+ if (totalNumSlots < requiredNumSlots) {
706
+ let numSlotsToCreate = requiredNumSlots - totalNumSlots;
707
+
708
+ while (numSlotsToCreate > 0) {
709
+ // eslint-disable-next-line no-await-in-loop
710
+ this.slots.video.unused.push(
711
+ // eslint-disable-next-line no-await-in-loop
712
+ await this.receiveSlotManager.allocateSlot(MediaType.VideoMain)
713
+ );
714
+ numSlotsToCreate -= 1;
715
+ }
716
+ }
717
+ }
718
+
719
+ /**
720
+ * Move all active speaker slots to "unused"
721
+ */
722
+ private trimActiveSpeakerSlots() {
723
+ this.slots.video.unused.push(...this.slots.video.activeSpeaker);
724
+ this.slots.video.activeSpeaker.length = 0;
725
+ }
726
+
727
+ /**
728
+ * Logs the state of the receive slots
729
+ */
730
+ private logMainVideoReceiveSlots() {
731
+ let logMessage = '';
732
+ forEach(this.receiveSlotAllocations.activeSpeaker, (group, groupName) => {
733
+ logMessage += `\ngroup: ${groupName}\n${group.slots
734
+ .map((slot) => slot.logString)
735
+ .join(', ')}`;
736
+ });
737
+
738
+ logMessage += '\nreceiverSelected:\n';
739
+ forEach(this.receiveSlotAllocations.receiverSelected, (slot, key) => {
740
+ logMessage += ` ${key}: ${slot.logString}\n`;
741
+ });
742
+
743
+ LoggerProxy.logger.log(
744
+ `RemoteMediaManager#logMainVideoReceiveSlots --> MAIN VIDEO receive slots: unused=${this.slots.video.unused.length}, activeSpeaker=${this.slots.video.activeSpeaker.length}, receiverSelected=${this.slots.video.receiverSelected.length}${logMessage}`
745
+ );
746
+ }
747
+
748
+ /** logs main audio slots */
749
+ private logMainAudioReceiveSlots() {
750
+ LoggerProxy.logger.log(
751
+ `RemoteMediaManager#logMainAudioReceiveSlots --> MAIN AUDIO receive slots: ${this.slots.audio
752
+ .map((slot) => slot.logString)
753
+ .join(', ')}`
754
+ );
755
+ }
756
+
757
+ /** logs slides video slots */
758
+ private logSlidesVideoReceiveSlots() {
759
+ LoggerProxy.logger.log(
760
+ `RemoteMediaManager#logSlidesVideoReceiveSlots --> SLIDES VIDEO receive slot: ${this.slots.screenShare.video?.logString}`
761
+ );
762
+ }
763
+
764
+ /** logs slides audio slots */
765
+ private logSlidesAudioReceiveSlots() {
766
+ LoggerProxy.logger.log(
767
+ `RemoteMediaManager#logSlidesAudioReceiveSlots --> SLIDES AUDIO receive slots: ${this.slots.screenShare.audio
768
+ .map((slot) => slot.logString)
769
+ .join(', ')}`
770
+ );
771
+ }
772
+
773
+ /** Logs all current receive slots */
774
+ public logAllReceiveSlots() {
775
+ this.logMainVideoReceiveSlots();
776
+ this.logMainAudioReceiveSlots();
777
+ this.logSlidesVideoReceiveSlots();
778
+ this.logSlidesAudioReceiveSlots();
779
+ }
780
+
781
+ /**
782
+ * Makes sure we have the right number of receive slots created for the current layout
783
+ * and allocates them to the right video panes / pane groups
784
+ *
785
+ * @returns {Promise}
786
+ */
787
+ private async updateVideoReceiveSlots() {
788
+ // move all active speaker slots to "unused"
789
+ this.trimActiveSpeakerSlots();
790
+
791
+ // move all no longer needed receiver-selected slots to "unused"
792
+ this.trimReceiverSelectedSlots();
793
+
794
+ // ensure we have enough total slots for current layout
795
+ await this.refillRequiredSlotsIfNeeded();
796
+
797
+ // allocate the slots to the right panes / pane groups
798
+ // reset allocations
799
+ this.receiveSlotAllocations = {activeSpeaker: {}, receiverSelected: {}};
800
+ // allocate active speaker
801
+ this.allocateSlotsToActiveSpeakerPaneGroups();
802
+ // allocate receiver selected
803
+ this.allocateSlotsToReceiverSelectedVideoPaneGroups();
804
+
805
+ this.logMainVideoReceiveSlots();
806
+
807
+ // If this is the initial layout, there may be some "unused" slots left because of the preallocation
808
+ // done in this.preallocateVideoReceiveSlots(), so release them now
809
+ this.releaseUnusedVideoSlots();
810
+ }
811
+
812
+ /**
813
+ * Creates new RemoteMedia and RemoteMediaGroup objects for the current layout
814
+ * and sends the media requests for all of them.
815
+ */
816
+ private updateVideoRemoteMediaObjects() {
817
+ // invalidate all the previous remote media objects and cancel their media requests
818
+ this.invalidateCurrentRemoteMedia({
819
+ audio: false,
820
+ video: true,
821
+ screenShareAudio: false,
822
+ screenShareVideo: false,
823
+ commit: false,
824
+ });
825
+
826
+ // create new remoteMediaGroup objects
827
+ this.media.video.activeSpeakerGroups = {};
828
+ this.media.video.memberPanes = {};
829
+
830
+ for (const [groupId, group] of Object.entries(this.receiveSlotAllocations.activeSpeaker)) {
831
+ const paneGroupInCurrentLayout = this.currentLayout?.activeSpeakerVideoPaneGroups?.find(
832
+ (groupInLayout) => groupInLayout.id === groupId
833
+ );
834
+
835
+ if (paneGroupInCurrentLayout) {
836
+ const mediaGroup = new RemoteMediaGroup(
837
+ this.mediaRequestManagers.video,
838
+ group.slots,
839
+ paneGroupInCurrentLayout.priority,
840
+ false,
841
+ {
842
+ preferLiveVideo: this.config.video.preferLiveVideo,
843
+ resolution: paneGroupInCurrentLayout.size,
844
+ }
845
+ );
846
+
847
+ this.media.video.activeSpeakerGroups[groupId] = mediaGroup;
848
+ } else {
849
+ // this should never happen, because this.receiveSlotAllocations are created based on current layout configuration
850
+ LoggerProxy.logger.warn(
851
+ `a group id ${groupId} from this.receiveSlotAllocations.activeSpeaker cannot be found in the current layout configuration`
852
+ );
853
+ }
854
+ }
855
+
856
+ // create new remoteMedia objects
857
+ for (const [paneId, slot] of Object.entries(this.receiveSlotAllocations.receiverSelected)) {
858
+ const paneInCurrentLayout = this.currentLayout?.memberVideoPanes?.find(
859
+ (paneInLayout) => paneInLayout.id === paneId
860
+ );
861
+
862
+ if (paneInCurrentLayout) {
863
+ const remoteMedia = new RemoteMedia(slot, this.mediaRequestManagers.video, {
864
+ resolution: paneInCurrentLayout.size,
865
+ });
866
+
867
+ if (paneInCurrentLayout.csi) {
868
+ remoteMedia.sendMediaRequest(paneInCurrentLayout.csi, false);
869
+ }
870
+
871
+ this.media.video.memberPanes[paneId] = remoteMedia;
872
+ } else {
873
+ // this should never happen, because this.receiveSlotAllocations are created based on current layout configuration
874
+ LoggerProxy.logger.warn(
875
+ `a pane id ${paneId} from this.receiveSlotAllocations.receiverSelected cannot be found in the current layout configuration`
876
+ );
877
+ }
878
+ }
879
+
880
+ this.mediaRequestManagers.video.commit();
881
+ }
882
+
883
+ /**
884
+ * Checks if current layout requires a screen share.
885
+ * If it does, it creates new RemoteMediaGroup object for screen share
886
+ * and sends the media requests for it.
887
+ * If it doesn't, it makes sure we clean up any RemoteMediaGroup objects
888
+ * created earlier for screen share (for previous layout).
889
+ */
890
+ private updateScreenShareVideoRemoteMediaObject() {
891
+ this.invalidateCurrentRemoteMedia({
892
+ audio: false,
893
+ video: false,
894
+ screenShareAudio: false,
895
+ screenShareVideo: true,
896
+ commit: false,
897
+ });
898
+
899
+ this.media.screenShare.video = undefined;
900
+
901
+ if (this.currentLayout?.screenShareVideo) {
902
+ // we create a group of 1, because for screen share we need to use the "active speaker" policy
903
+ this.media.screenShare.video = new RemoteMediaGroup(
904
+ this.mediaRequestManagers.screenShareVideo,
905
+ [this.slots.screenShare.video],
906
+ 255,
907
+ false,
908
+ {resolution: this.currentLayout.screenShareVideo.size}
909
+ );
910
+ }
911
+
912
+ this.mediaRequestManagers.screenShareVideo.commit();
913
+ }
914
+
915
+ /**
916
+ * Invalidates all remote media objects belonging to currently selected layout
917
+ */
918
+ private invalidateCurrentRemoteMedia(options: {
919
+ audio: boolean;
920
+ video: boolean;
921
+ screenShareAudio: boolean;
922
+ screenShareVideo: boolean;
923
+ commit: boolean;
924
+ }) {
925
+ const {audio, video, screenShareAudio, screenShareVideo, commit} = options;
926
+
927
+ if (audio && this.media.audio) {
928
+ this.media.audio.stop(commit);
929
+ }
930
+ if (video) {
931
+ Object.values(this.media.video.activeSpeakerGroups).forEach((remoteMediaGroup) => {
932
+ remoteMediaGroup.stop(false);
933
+ });
934
+ Object.values(this.media.video.memberPanes).forEach((remoteMedia) => {
935
+ remoteMedia.stop(false);
936
+ });
937
+ if (commit) {
938
+ this.mediaRequestManagers.video.commit();
939
+ }
940
+ }
941
+
942
+ if (screenShareAudio && this.media.screenShare.audio) {
943
+ this.media.screenShare.audio.stop(commit);
944
+ }
945
+ if (screenShareVideo && this.media.screenShare.video) {
946
+ this.media.screenShare.video.stop(commit);
947
+ }
948
+ }
949
+
950
+ /** emits Event.VideoLayoutChanged */
951
+ private emitVideoLayoutChangedEvent() {
952
+ // todo: at this point the receive slots might still be showing a participant from previous layout, we should
953
+ // wait for our media requests to be fulfilled, but there is no API for that right now (we could wait for source updates
954
+ // but in some cases they might never come, or would need to always make sure to use a new set of receiver slots)
955
+ // for now it's fine to have it like this, we will re-evaluate if it needs improving after more testing
956
+
957
+ this.emit(
958
+ {
959
+ file: 'multistream/remoteMediaManager',
960
+ function: 'emitVideoLayoutChangedEvent',
961
+ },
962
+ Event.VideoLayoutChanged,
963
+ {
964
+ layoutId: this.currentLayoutId,
965
+ activeSpeakerVideoPanes: this.media.video.activeSpeakerGroups,
966
+ memberVideoPanes: this.media.video.memberPanes,
967
+ screenShareVideo: this.media.screenShare.video?.getRemoteMedia()[0],
968
+ }
969
+ );
970
+ }
971
+
972
+ /**
973
+ * Sets a new CSI on a given remote media object
974
+ *
975
+ * @param {RemoteMedia} remoteMedia remote Media object to modify
976
+ * @param {CSI} csi new CSI value, can be null if we want to stop receiving media
977
+ */
978
+ public setRemoteVideoCsi(remoteMedia: RemoteMedia, csi: CSI | null) {
979
+ if (!Object.values(this.media.video.memberPanes).includes(remoteMedia)) {
980
+ throw new Error('remoteMedia not found');
981
+ }
982
+
983
+ if (csi) {
984
+ remoteMedia.sendMediaRequest(csi, true);
985
+ } else {
986
+ remoteMedia.cancelMediaRequest(true);
987
+ }
988
+ }
989
+
990
+ /**
991
+ * Adds a new member video pane to the currently selected layout.
992
+ *
993
+ * Changes to the layout are lost after a layout change.
994
+ *
995
+ * @param {MemberVideoPane} newPane
996
+ * @returns {Promise<RemoteMedia>}
997
+ */
998
+ public async addMemberVideoPane(newPane: MemberVideoPane): Promise<RemoteMedia> {
999
+ if (!this.currentLayout) {
1000
+ throw new Error('There is no current layout selected, call start() first');
1001
+ }
1002
+
1003
+ if (!this.currentLayout?.memberVideoPanes) {
1004
+ this.currentLayout.memberVideoPanes = [];
1005
+ }
1006
+
1007
+ if (newPane.id in this.currentLayout.memberVideoPanes) {
1008
+ throw new Error(
1009
+ `duplicate pane id ${newPane.id} - this pane already exists in current layout's memberVideoPanes`
1010
+ );
1011
+ }
1012
+
1013
+ this.currentLayout.memberVideoPanes.push(newPane);
1014
+
1015
+ const receiveSlot = await this.receiveSlotManager.allocateSlot(MediaType.VideoMain);
1016
+
1017
+ this.slots.video.receiverSelected.push(receiveSlot);
1018
+
1019
+ const remoteMedia = new RemoteMedia(receiveSlot, this.mediaRequestManagers.video, {
1020
+ resolution: newPane.size,
1021
+ });
1022
+
1023
+ if (newPane.csi) {
1024
+ remoteMedia.sendMediaRequest(newPane.csi, true);
1025
+ }
1026
+
1027
+ this.media.video.memberPanes[newPane.id] = remoteMedia;
1028
+
1029
+ return remoteMedia;
1030
+ }
1031
+
1032
+ /**
1033
+ * Removes a member video pane from the currently selected layout.
1034
+ *
1035
+ * Changes to the layout are lost after a layout change.
1036
+ *
1037
+ * @param {PaneId} paneId pane id of the pane to remove
1038
+ * @returns {Promise<void>}
1039
+ */
1040
+ public removeMemberVideoPane(paneId: PaneId): Promise<void> {
1041
+ if (!this.currentLayout) {
1042
+ return Promise.reject(new Error('There is no current layout selected, call start() first'));
1043
+ }
1044
+
1045
+ if (!this.currentLayout.memberVideoPanes?.find((pane) => pane.id === paneId)) {
1046
+ // pane id doesn't exist, so nothing to do
1047
+ LoggerProxy.logger.log(
1048
+ `RemoteMediaManager#removeMemberVideoPane --> removeMemberVideoPane() called for a non-existent paneId: ${paneId} (pane not found in currentLayout.memberVideoPanes)`
1049
+ );
1050
+
1051
+ return Promise.resolve();
1052
+ }
1053
+
1054
+ if (!this.media.video.memberPanes[paneId]) {
1055
+ // pane id doesn't exist, so nothing to do
1056
+ LoggerProxy.logger.log(
1057
+ `RemoteMediaManager#removeMemberVideoPane --> removeMemberVideoPane() called for a non-existent paneId: ${paneId} (pane not found in this.media.video.memberPanes)`
1058
+ );
1059
+
1060
+ return Promise.resolve();
1061
+ }
1062
+
1063
+ const remoteMedia = this.media.video.memberPanes[paneId];
1064
+
1065
+ const receiveSlot = remoteMedia.getUnderlyingReceiveSlot();
1066
+
1067
+ if (receiveSlot) {
1068
+ this.receiveSlotManager.releaseSlot(receiveSlot);
1069
+
1070
+ const index = this.slots.video.receiverSelected.indexOf(receiveSlot);
1071
+
1072
+ if (index >= 0) {
1073
+ this.slots.video.receiverSelected.splice(index, 1);
1074
+ }
1075
+ }
1076
+ remoteMedia.stop();
1077
+
1078
+ delete this.media.video.memberPanes[paneId];
1079
+ delete this.currentLayout.memberVideoPanes?.[paneId];
1080
+
1081
+ return Promise.resolve();
1082
+ }
1083
+
1084
+ /**
1085
+ * Pins an active speaker remote media object to the given CSI value. From that moment
1086
+ * onwards the remote media will only play audio/video from that specific CSI until
1087
+ * unpinActiveSpeakerVideoPane() is called or current layout is changed.
1088
+ *
1089
+ * @param {RemoteMedia} remoteMedia remote media object reference
1090
+ * @param {CSI} csi CSI value to pin to, if undefined, then current CSI value is used
1091
+ */
1092
+ public pinActiveSpeakerVideoPane(remoteMedia: RemoteMedia, csi?: CSI): void {
1093
+ const remoteMediaGroup = Object.values(this.media.video.activeSpeakerGroups).find((group) =>
1094
+ group.includes(remoteMedia, 'unpinned')
1095
+ );
1096
+
1097
+ if (!remoteMediaGroup) {
1098
+ throw new Error(
1099
+ 'remoteMedia not found among the unpinned remote media from any active speaker group'
1100
+ );
1101
+ }
1102
+
1103
+ remoteMediaGroup.pin(remoteMedia, csi);
1104
+ }
1105
+
1106
+ /**
1107
+ * Unpins a remote media object from the fixed CSI value it was pinned to.
1108
+ *
1109
+ * @param {RemoteMedia} remoteMedia remote media object reference
1110
+ */
1111
+ public unpinActiveSpeakerVideoPane(remoteMedia: RemoteMedia) {
1112
+ const remoteMediaGroup = Object.values(this.media.video.activeSpeakerGroups).find((group) =>
1113
+ group.includes(remoteMedia, 'pinned')
1114
+ );
1115
+
1116
+ if (!remoteMediaGroup) {
1117
+ throw new Error(
1118
+ 'remoteMedia not found among the pinned remote media from any active speaker group'
1119
+ );
1120
+ }
1121
+
1122
+ remoteMediaGroup.unpin(remoteMedia);
1123
+ }
1124
+
1125
+ /**
1126
+ * Returns true if a given remote media object belongs to an active speaker group and has been pinned.
1127
+ * Throws an error if the remote media object doesn't belong to any active speaker remote media group.
1128
+ *
1129
+ * @param {RemoteMedia} remoteMedia remote media object
1130
+ * @returns {boolean}
1131
+ */
1132
+ public isPinned(remoteMedia: RemoteMedia) {
1133
+ const remoteMediaGroup = Object.values(this.media.video.activeSpeakerGroups).find((group) =>
1134
+ group.includes(remoteMedia)
1135
+ );
1136
+
1137
+ if (!remoteMediaGroup) {
1138
+ throw new Error(
1139
+ 'remoteMedia not found among any remote media (pinned or unpinned) from any active speaker group'
1140
+ );
1141
+ }
1142
+
1143
+ return remoteMediaGroup.isPinned(remoteMedia);
1144
+ }
1145
+ }