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

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 (548) hide show
  1. package/UPGRADING.md +9 -9
  2. package/browsers.js +19 -24
  3. package/dist/annotation/annotation.types.js +7 -0
  4. package/dist/annotation/annotation.types.js.map +1 -0
  5. package/dist/annotation/constants.js +48 -0
  6. package/dist/annotation/constants.js.map +1 -0
  7. package/dist/annotation/index.js +357 -0
  8. package/dist/annotation/index.js.map +1 -0
  9. package/dist/breakouts/breakout.js +176 -0
  10. package/dist/breakouts/breakout.js.map +1 -0
  11. package/dist/breakouts/collection.js +23 -0
  12. package/dist/breakouts/collection.js.map +1 -0
  13. package/dist/breakouts/edit-lock-error.js +52 -0
  14. package/dist/breakouts/edit-lock-error.js.map +1 -0
  15. package/dist/breakouts/events.js +43 -0
  16. package/dist/breakouts/events.js.map +1 -0
  17. package/dist/breakouts/index.js +919 -0
  18. package/dist/breakouts/index.js.map +1 -0
  19. package/dist/breakouts/request.js +78 -0
  20. package/dist/breakouts/request.js.map +1 -0
  21. package/dist/breakouts/utils.js +67 -0
  22. package/dist/breakouts/utils.js.map +1 -0
  23. package/dist/common/browser-detection.js +1 -20
  24. package/dist/common/browser-detection.js.map +1 -1
  25. package/dist/common/collection.js +5 -20
  26. package/dist/common/collection.js.map +1 -1
  27. package/dist/common/config.js +0 -7
  28. package/dist/common/config.js.map +1 -1
  29. package/dist/common/errors/captcha-error.js +10 -24
  30. package/dist/common/errors/captcha-error.js.map +1 -1
  31. package/dist/common/errors/intent-to-join.js +11 -24
  32. package/dist/common/errors/intent-to-join.js.map +1 -1
  33. package/dist/common/errors/join-meeting.js +12 -25
  34. package/dist/common/errors/join-meeting.js.map +1 -1
  35. package/dist/common/errors/media.js +10 -24
  36. package/dist/common/errors/media.js.map +1 -1
  37. package/dist/common/errors/parameter.js +5 -33
  38. package/dist/common/errors/parameter.js.map +1 -1
  39. package/dist/common/errors/password-error.js +10 -24
  40. package/dist/common/errors/password-error.js.map +1 -1
  41. package/dist/common/errors/permission.js +9 -23
  42. package/dist/common/errors/permission.js.map +1 -1
  43. package/dist/common/errors/reconnection-in-progress.js +0 -17
  44. package/dist/common/errors/reconnection-in-progress.js.map +1 -1
  45. package/dist/common/errors/reconnection.js +10 -24
  46. package/dist/common/errors/reconnection.js.map +1 -1
  47. package/dist/common/errors/stats.js +10 -24
  48. package/dist/common/errors/stats.js.map +1 -1
  49. package/dist/common/errors/webex-errors.js +10 -69
  50. package/dist/common/errors/webex-errors.js.map +1 -1
  51. package/dist/common/errors/webex-meetings-error.js +5 -25
  52. package/dist/common/errors/webex-meetings-error.js.map +1 -1
  53. package/dist/common/events/events-scope.js +0 -22
  54. package/dist/common/events/events-scope.js.map +1 -1
  55. package/dist/common/events/events.js +0 -23
  56. package/dist/common/events/events.js.map +1 -1
  57. package/dist/common/events/trigger-proxy.js +0 -12
  58. package/dist/common/events/trigger-proxy.js.map +1 -1
  59. package/dist/common/events/util.js +0 -15
  60. package/dist/common/events/util.js.map +1 -1
  61. package/dist/common/logs/logger-config.js +0 -4
  62. package/dist/common/logs/logger-config.js.map +1 -1
  63. package/dist/common/logs/logger-proxy.js +1 -8
  64. package/dist/common/logs/logger-proxy.js.map +1 -1
  65. package/dist/common/logs/request.js +37 -60
  66. package/dist/common/logs/request.js.map +1 -1
  67. package/dist/common/queue.js +4 -14
  68. package/dist/common/queue.js.map +1 -1
  69. package/dist/config.js +7 -6
  70. package/dist/config.js.map +1 -1
  71. package/dist/constants.js +184 -122
  72. package/dist/constants.js.map +1 -1
  73. package/dist/controls-options-manager/constants.js +14 -0
  74. package/dist/controls-options-manager/constants.js.map +1 -0
  75. package/dist/controls-options-manager/enums.js +25 -0
  76. package/dist/controls-options-manager/enums.js.map +1 -0
  77. package/dist/controls-options-manager/index.js +297 -0
  78. package/dist/controls-options-manager/index.js.map +1 -0
  79. package/dist/controls-options-manager/types.js +7 -0
  80. package/dist/controls-options-manager/types.js.map +1 -0
  81. package/dist/controls-options-manager/util.js +250 -0
  82. package/dist/controls-options-manager/util.js.map +1 -0
  83. package/dist/index.js +72 -17
  84. package/dist/index.js.map +1 -1
  85. package/dist/locus-info/controlsUtils.js +56 -29
  86. package/dist/locus-info/controlsUtils.js.map +1 -1
  87. package/dist/locus-info/embeddedAppsUtils.js +3 -26
  88. package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
  89. package/dist/locus-info/fullState.js +0 -15
  90. package/dist/locus-info/fullState.js.map +1 -1
  91. package/dist/locus-info/hostUtils.js +4 -12
  92. package/dist/locus-info/hostUtils.js.map +1 -1
  93. package/dist/locus-info/index.js +362 -208
  94. package/dist/locus-info/index.js.map +1 -1
  95. package/dist/locus-info/infoUtils.js +3 -37
  96. package/dist/locus-info/infoUtils.js.map +1 -1
  97. package/dist/locus-info/mediaSharesUtils.js +12 -38
  98. package/dist/locus-info/mediaSharesUtils.js.map +1 -1
  99. package/dist/locus-info/parser.js +92 -118
  100. package/dist/locus-info/parser.js.map +1 -1
  101. package/dist/locus-info/selfUtils.js +99 -91
  102. package/dist/locus-info/selfUtils.js.map +1 -1
  103. package/dist/media/index.js +113 -337
  104. package/dist/media/index.js.map +1 -1
  105. package/dist/media/properties.js +96 -135
  106. package/dist/media/properties.js.map +1 -1
  107. package/dist/media/util.js +1 -35
  108. package/dist/media/util.js.map +1 -1
  109. package/dist/mediaQualityMetrics/config.js +505 -495
  110. package/dist/mediaQualityMetrics/config.js.map +1 -1
  111. package/dist/meeting/in-meeting-actions.js +59 -14
  112. package/dist/meeting/in-meeting-actions.js.map +1 -1
  113. package/dist/meeting/index.js +2909 -2398
  114. package/dist/meeting/index.js.map +1 -1
  115. package/dist/meeting/muteState.js +257 -112
  116. package/dist/meeting/muteState.js.map +1 -1
  117. package/dist/meeting/request.js +330 -264
  118. package/dist/meeting/request.js.map +1 -1
  119. package/dist/meeting/request.type.js +7 -0
  120. package/dist/meeting/request.type.js.map +1 -0
  121. package/dist/meeting/state.js +21 -31
  122. package/dist/meeting/state.js.map +1 -1
  123. package/dist/meeting/util.js +63 -261
  124. package/dist/meeting/util.js.map +1 -1
  125. package/dist/meeting-info/collection.js +6 -25
  126. package/dist/meeting-info/collection.js.map +1 -1
  127. package/dist/meeting-info/index.js +14 -32
  128. package/dist/meeting-info/index.js.map +1 -1
  129. package/dist/meeting-info/meeting-info-v2.js +273 -280
  130. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  131. package/dist/meeting-info/request.js +3 -15
  132. package/dist/meeting-info/request.js.map +1 -1
  133. package/dist/meeting-info/util.js +98 -183
  134. package/dist/meeting-info/util.js.map +1 -1
  135. package/dist/meeting-info/utilv2.js +155 -232
  136. package/dist/meeting-info/utilv2.js.map +1 -1
  137. package/dist/meetings/collection.js +26 -19
  138. package/dist/meetings/collection.js.map +1 -1
  139. package/dist/meetings/index.js +741 -548
  140. package/dist/meetings/index.js.map +1 -1
  141. package/dist/meetings/request.js +26 -41
  142. package/dist/meetings/request.js.map +1 -1
  143. package/dist/meetings/util.js +194 -149
  144. package/dist/meetings/util.js.map +1 -1
  145. package/dist/member/index.js +100 -85
  146. package/dist/member/index.js.map +1 -1
  147. package/dist/member/types.js +15 -0
  148. package/dist/member/types.js.map +1 -0
  149. package/dist/member/util.js +90 -68
  150. package/dist/member/util.js.map +1 -1
  151. package/dist/members/collection.js +13 -12
  152. package/dist/members/collection.js.map +1 -1
  153. package/dist/members/index.js +227 -188
  154. package/dist/members/index.js.map +1 -1
  155. package/dist/members/request.js +54 -39
  156. package/dist/members/request.js.map +1 -1
  157. package/dist/members/types.js +15 -0
  158. package/dist/members/types.js.map +1 -0
  159. package/dist/members/util.js +107 -44
  160. package/dist/members/util.js.map +1 -1
  161. package/dist/metrics/config.js +5 -14
  162. package/dist/metrics/config.js.map +1 -1
  163. package/dist/metrics/constants.js +3 -7
  164. package/dist/metrics/constants.js.map +1 -1
  165. package/dist/metrics/index.js +67 -159
  166. package/dist/metrics/index.js.map +1 -1
  167. package/dist/multistream/mediaRequestManager.js +250 -0
  168. package/dist/multistream/mediaRequestManager.js.map +1 -0
  169. package/dist/multistream/receiveSlot.js +202 -0
  170. package/dist/multistream/receiveSlot.js.map +1 -0
  171. package/dist/multistream/receiveSlotManager.js +176 -0
  172. package/dist/multistream/receiveSlotManager.js.map +1 -0
  173. package/dist/multistream/remoteMedia.js +270 -0
  174. package/dist/multistream/remoteMedia.js.map +1 -0
  175. package/dist/multistream/remoteMediaGroup.js +209 -0
  176. package/dist/multistream/remoteMediaGroup.js.map +1 -0
  177. package/dist/multistream/remoteMediaManager.js +1137 -0
  178. package/dist/multistream/remoteMediaManager.js.map +1 -0
  179. package/dist/networkQualityMonitor/index.js +40 -59
  180. package/dist/networkQualityMonitor/index.js.map +1 -1
  181. package/dist/personal-meeting-room/index.js +21 -45
  182. package/dist/personal-meeting-room/index.js.map +1 -1
  183. package/dist/personal-meeting-room/request.js +1 -31
  184. package/dist/personal-meeting-room/request.js.map +1 -1
  185. package/dist/personal-meeting-room/util.js +0 -13
  186. package/dist/personal-meeting-room/util.js.map +1 -1
  187. package/dist/reachability/index.js +192 -191
  188. package/dist/reachability/index.js.map +1 -1
  189. package/dist/reachability/request.js +15 -23
  190. package/dist/reachability/request.js.map +1 -1
  191. package/dist/reactions/constants.js +13 -0
  192. package/dist/reactions/constants.js.map +1 -0
  193. package/dist/reactions/reactions.js +109 -0
  194. package/dist/reactions/reactions.js.map +1 -0
  195. package/dist/reactions/reactions.type.js +36 -0
  196. package/dist/reactions/reactions.type.js.map +1 -0
  197. package/dist/reconnection-manager/index.js +386 -527
  198. package/dist/reconnection-manager/index.js.map +1 -1
  199. package/dist/recording-controller/enums.js +17 -0
  200. package/dist/recording-controller/enums.js.map +1 -0
  201. package/dist/recording-controller/index.js +343 -0
  202. package/dist/recording-controller/index.js.map +1 -0
  203. package/dist/recording-controller/util.js +63 -0
  204. package/dist/recording-controller/util.js.map +1 -0
  205. package/dist/roap/index.js +84 -286
  206. package/dist/roap/index.js.map +1 -1
  207. package/dist/roap/request.js +138 -238
  208. package/dist/roap/request.js.map +1 -1
  209. package/dist/roap/turnDiscovery.js +164 -102
  210. package/dist/roap/turnDiscovery.js.map +1 -1
  211. package/dist/statsAnalyzer/global.js +1 -93
  212. package/dist/statsAnalyzer/global.js.map +1 -1
  213. package/dist/statsAnalyzer/index.js +399 -470
  214. package/dist/statsAnalyzer/index.js.map +1 -1
  215. package/dist/statsAnalyzer/mqaUtil.js +143 -87
  216. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  217. package/dist/transcription/index.js +22 -47
  218. package/dist/transcription/index.js.map +1 -1
  219. package/dist/types/annotation/annotation.types.d.ts +34 -0
  220. package/dist/types/annotation/constants.d.ts +31 -0
  221. package/dist/types/annotation/index.d.ts +124 -0
  222. package/dist/types/breakouts/breakout.d.ts +8 -0
  223. package/dist/types/breakouts/collection.d.ts +5 -0
  224. package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
  225. package/dist/types/breakouts/events.d.ts +2 -0
  226. package/dist/types/breakouts/index.d.ts +5 -0
  227. package/dist/types/breakouts/request.d.ts +22 -0
  228. package/dist/types/breakouts/utils.d.ts +15 -0
  229. package/dist/types/common/browser-detection.d.ts +9 -0
  230. package/dist/types/common/collection.d.ts +48 -0
  231. package/dist/types/common/config.d.ts +2 -0
  232. package/dist/types/common/errors/captcha-error.d.ts +15 -0
  233. package/dist/types/common/errors/intent-to-join.d.ts +16 -0
  234. package/dist/types/common/errors/join-meeting.d.ts +17 -0
  235. package/dist/types/common/errors/media.d.ts +15 -0
  236. package/dist/types/common/errors/parameter.d.ts +15 -0
  237. package/dist/types/common/errors/password-error.d.ts +15 -0
  238. package/dist/types/common/errors/permission.d.ts +14 -0
  239. package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
  240. package/dist/types/common/errors/reconnection.d.ts +15 -0
  241. package/dist/types/common/errors/stats.d.ts +15 -0
  242. package/dist/types/common/errors/webex-errors.d.ts +69 -0
  243. package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
  244. package/dist/types/common/events/events-scope.d.ts +17 -0
  245. package/dist/types/common/events/events.d.ts +12 -0
  246. package/dist/types/common/events/trigger-proxy.d.ts +2 -0
  247. package/dist/types/common/events/util.d.ts +2 -0
  248. package/dist/types/common/logs/logger-config.d.ts +2 -0
  249. package/dist/types/common/logs/logger-proxy.d.ts +2 -0
  250. package/dist/types/common/logs/request.d.ts +34 -0
  251. package/dist/types/common/queue.d.ts +32 -0
  252. package/dist/types/config.d.ts +78 -0
  253. package/dist/types/constants.d.ts +968 -0
  254. package/dist/types/controls-options-manager/constants.d.ts +4 -0
  255. package/dist/types/controls-options-manager/enums.d.ts +13 -0
  256. package/dist/types/controls-options-manager/index.d.ts +136 -0
  257. package/dist/types/controls-options-manager/types.d.ts +37 -0
  258. package/dist/types/controls-options-manager/util.d.ts +1 -0
  259. package/dist/types/index.d.ts +7 -0
  260. package/dist/types/locus-info/controlsUtils.d.ts +2 -0
  261. package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
  262. package/dist/types/locus-info/fullState.d.ts +2 -0
  263. package/dist/types/locus-info/hostUtils.d.ts +2 -0
  264. package/dist/types/locus-info/index.d.ts +315 -0
  265. package/dist/types/locus-info/infoUtils.d.ts +2 -0
  266. package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
  267. package/dist/types/locus-info/parser.d.ts +212 -0
  268. package/dist/types/locus-info/selfUtils.d.ts +2 -0
  269. package/dist/types/media/index.d.ts +34 -0
  270. package/dist/types/media/properties.d.ts +108 -0
  271. package/dist/types/media/util.d.ts +2 -0
  272. package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
  273. package/dist/types/meeting/in-meeting-actions.d.ts +129 -0
  274. package/dist/types/meeting/index.d.ts +1748 -0
  275. package/dist/types/meeting/muteState.d.ts +185 -0
  276. package/dist/types/meeting/request.d.ts +275 -0
  277. package/dist/types/meeting/request.type.d.ts +11 -0
  278. package/dist/types/meeting/state.d.ts +9 -0
  279. package/dist/types/meeting/util.d.ts +2 -0
  280. package/dist/types/meeting-info/collection.d.ts +20 -0
  281. package/dist/types/meeting-info/index.d.ts +57 -0
  282. package/dist/types/meeting-info/meeting-info-v2.d.ts +112 -0
  283. package/dist/types/meeting-info/request.d.ts +22 -0
  284. package/dist/types/meeting-info/util.d.ts +2 -0
  285. package/dist/types/meeting-info/utilv2.d.ts +2 -0
  286. package/dist/types/meetings/collection.d.ts +31 -0
  287. package/dist/types/meetings/index.d.ts +345 -0
  288. package/dist/types/meetings/request.d.ts +27 -0
  289. package/dist/types/meetings/util.d.ts +18 -0
  290. package/dist/types/member/index.d.ts +156 -0
  291. package/dist/types/member/types.d.ts +21 -0
  292. package/dist/types/member/util.d.ts +2 -0
  293. package/dist/types/members/collection.d.ts +29 -0
  294. package/dist/types/members/index.d.ts +353 -0
  295. package/dist/types/members/request.d.ts +69 -0
  296. package/dist/types/members/types.d.ts +24 -0
  297. package/dist/types/members/util.d.ts +2 -0
  298. package/dist/types/metrics/config.d.ts +172 -0
  299. package/dist/types/metrics/constants.d.ts +54 -0
  300. package/dist/types/metrics/index.d.ts +152 -0
  301. package/dist/types/multistream/mediaRequestManager.d.ts +101 -0
  302. package/dist/types/multistream/receiveSlot.d.ts +68 -0
  303. package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
  304. package/dist/types/multistream/remoteMedia.d.ts +72 -0
  305. package/dist/types/multistream/remoteMediaGroup.d.ts +47 -0
  306. package/dist/types/multistream/remoteMediaManager.d.ts +263 -0
  307. package/dist/types/networkQualityMonitor/index.d.ts +70 -0
  308. package/dist/types/personal-meeting-room/index.d.ts +47 -0
  309. package/dist/types/personal-meeting-room/request.d.ts +14 -0
  310. package/dist/types/personal-meeting-room/util.d.ts +2 -0
  311. package/dist/types/reachability/index.d.ts +152 -0
  312. package/dist/types/reachability/request.d.ts +37 -0
  313. package/dist/types/reactions/constants.d.ts +3 -0
  314. package/dist/types/reactions/reactions.d.ts +4 -0
  315. package/dist/types/reactions/reactions.type.d.ts +52 -0
  316. package/dist/types/reconnection-manager/index.d.ts +126 -0
  317. package/dist/types/recording-controller/enums.d.ts +7 -0
  318. package/dist/types/recording-controller/index.d.ts +193 -0
  319. package/dist/types/recording-controller/util.d.ts +13 -0
  320. package/dist/types/roap/index.d.ts +77 -0
  321. package/dist/types/roap/request.d.ts +38 -0
  322. package/dist/types/roap/turnDiscovery.d.ts +88 -0
  323. package/dist/types/statsAnalyzer/global.d.ts +36 -0
  324. package/dist/types/statsAnalyzer/index.d.ts +200 -0
  325. package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
  326. package/dist/types/transcription/index.d.ts +64 -0
  327. package/internal-README.md +7 -6
  328. package/package.json +29 -21
  329. package/src/annotation/annotation.types.ts +41 -0
  330. package/src/annotation/constants.ts +36 -0
  331. package/src/annotation/index.ts +339 -0
  332. package/src/breakouts/README.md +219 -0
  333. package/src/breakouts/breakout.ts +141 -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 +37 -0
  337. package/src/breakouts/index.ts +823 -0
  338. package/src/breakouts/request.ts +55 -0
  339. package/src/breakouts/utils.ts +57 -0
  340. package/src/common/{browser-detection.js → browser-detection.ts} +9 -6
  341. package/src/common/collection.ts +9 -7
  342. package/src/common/{config.js → config.ts} +1 -1
  343. package/src/common/errors/{captcha-error.js → captcha-error.ts} +11 -7
  344. package/src/common/errors/{intent-to-join.js → intent-to-join.ts} +12 -7
  345. package/src/common/errors/{join-meeting.js → join-meeting.ts} +17 -8
  346. package/src/common/errors/{media.js → media.ts} +11 -7
  347. package/src/common/errors/parameter.ts +11 -7
  348. package/src/common/errors/{password-error.js → password-error.ts} +11 -7
  349. package/src/common/errors/{permission.js → permission.ts} +10 -6
  350. package/src/common/errors/{reconnection.js → reconnection.ts} +11 -7
  351. package/src/common/errors/{stats.js → stats.ts} +11 -7
  352. package/src/common/errors/{webex-errors.js → webex-errors.ts} +8 -25
  353. package/src/common/errors/{webex-meetings-error.js → webex-meetings-error.ts} +4 -2
  354. package/src/common/events/{events-scope.js → events-scope.ts} +6 -2
  355. package/src/common/events/{events.js → events.ts} +5 -1
  356. package/src/common/events/{trigger-proxy.js → trigger-proxy.ts} +9 -5
  357. package/src/common/events/{util.js → util.ts} +2 -3
  358. package/src/common/logs/{logger-config.js → logger-config.ts} +1 -2
  359. package/src/common/logs/logger-proxy.ts +44 -0
  360. package/src/common/logs/{request.js → request.ts} +22 -9
  361. package/src/common/queue.ts +1 -2
  362. package/src/{config.js → config.ts} +18 -12
  363. package/src/constants.ts +256 -183
  364. package/src/controls-options-manager/constants.ts +5 -0
  365. package/src/controls-options-manager/enums.ts +16 -0
  366. package/src/controls-options-manager/index.ts +278 -0
  367. package/src/controls-options-manager/types.ts +49 -0
  368. package/src/controls-options-manager/util.ts +229 -0
  369. package/src/index.ts +33 -0
  370. package/src/locus-info/controlsUtils.ts +169 -0
  371. package/src/locus-info/{embeddedAppsUtils.js → embeddedAppsUtils.ts} +5 -6
  372. package/src/locus-info/{fullState.js → fullState.ts} +16 -12
  373. package/src/locus-info/{hostUtils.js → hostUtils.ts} +9 -8
  374. package/src/locus-info/{index.js → index.ts} +331 -80
  375. package/src/locus-info/{infoUtils.js → infoUtils.ts} +19 -8
  376. package/src/locus-info/{mediaSharesUtils.js → mediaSharesUtils.ts} +17 -17
  377. package/src/locus-info/{parser.js → parser.ts} +67 -79
  378. package/src/locus-info/{selfUtils.js → selfUtils.ts} +196 -67
  379. package/src/media/index.ts +488 -0
  380. package/src/media/{properties.js → properties.ts} +67 -54
  381. package/src/media/util.ts +16 -0
  382. package/src/mediaQualityMetrics/config.ts +384 -0
  383. package/src/meeting/in-meeting-actions.ts +123 -3
  384. package/src/meeting/{index.js → index.ts} +3334 -1775
  385. package/src/meeting/muteState.ts +526 -0
  386. package/src/meeting/{request.js → request.ts} +350 -142
  387. package/src/meeting/request.type.ts +13 -0
  388. package/src/meeting/{state.js → state.ts} +50 -35
  389. package/src/meeting/{util.js → util.ts} +126 -159
  390. package/src/meeting-info/{collection.js → collection.ts} +6 -2
  391. package/src/meeting-info/{index.js → index.ts} +42 -36
  392. package/src/meeting-info/meeting-info-v2.ts +345 -0
  393. package/src/meeting-info/{request.js → request.ts} +14 -4
  394. package/src/meeting-info/{util.js → util.ts} +60 -51
  395. package/src/meeting-info/{utilv2.js → utilv2.ts} +76 -60
  396. package/src/meetings/{collection.js → collection.ts} +26 -3
  397. package/src/meetings/index.ts +1394 -0
  398. package/src/meetings/{request.js → request.ts} +34 -25
  399. package/src/meetings/util.ts +288 -0
  400. package/src/member/{index.js → index.ts} +124 -56
  401. package/src/member/types.ts +24 -0
  402. package/src/member/{util.js → util.ts} +105 -25
  403. package/src/members/{collection.js → collection.ts} +10 -2
  404. package/src/members/{index.js → index.ts} +359 -139
  405. package/src/members/request.ts +215 -0
  406. package/src/members/types.ts +28 -0
  407. package/src/members/{util.js → util.ts} +145 -54
  408. package/src/metrics/{config.js → config.ts} +256 -92
  409. package/src/metrics/{constants.js → constants.ts} +1 -6
  410. package/src/metrics/{index.js → index.ts} +116 -97
  411. package/src/multistream/mediaRequestManager.ts +324 -0
  412. package/src/multistream/receiveSlot.ts +184 -0
  413. package/src/multistream/receiveSlotManager.ts +166 -0
  414. package/src/multistream/remoteMedia.ts +254 -0
  415. package/src/multistream/remoteMediaGroup.ts +225 -0
  416. package/src/multistream/remoteMediaManager.ts +1075 -0
  417. package/src/networkQualityMonitor/{index.js → index.ts} +41 -29
  418. package/src/personal-meeting-room/{index.js → index.ts} +28 -19
  419. package/src/personal-meeting-room/{request.js → request.ts} +13 -4
  420. package/src/personal-meeting-room/{util.js → util.ts} +4 -4
  421. package/src/reachability/{index.js → index.ts} +157 -94
  422. package/src/reachability/request.ts +46 -35
  423. package/src/reactions/constants.ts +4 -0
  424. package/src/reactions/reactions.ts +104 -0
  425. package/src/reactions/reactions.type.ts +62 -0
  426. package/src/reconnection-manager/{index.js → index.ts} +261 -163
  427. package/src/recording-controller/enums.ts +8 -0
  428. package/src/recording-controller/index.ts +315 -0
  429. package/src/recording-controller/util.ts +58 -0
  430. package/src/roap/index.ts +241 -0
  431. package/src/roap/request.ts +172 -0
  432. package/src/roap/turnDiscovery.ts +127 -53
  433. package/src/statsAnalyzer/global.ts +37 -0
  434. package/src/statsAnalyzer/index.ts +1273 -0
  435. package/src/statsAnalyzer/mqaUtil.ts +291 -0
  436. package/src/transcription/{index.js → index.ts} +46 -39
  437. package/test/integration/spec/converged-space-meetings.js +177 -0
  438. package/test/integration/spec/journey.js +666 -464
  439. package/test/integration/spec/space-meeting.js +321 -206
  440. package/test/integration/spec/transcription.js +7 -8
  441. package/test/unit/spec/annotation/index.ts +435 -0
  442. package/test/unit/spec/breakouts/breakout.ts +184 -0
  443. package/test/unit/spec/breakouts/collection.ts +15 -0
  444. package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
  445. package/test/unit/spec/breakouts/events.ts +77 -0
  446. package/test/unit/spec/breakouts/index.ts +1504 -0
  447. package/test/unit/spec/breakouts/request.ts +104 -0
  448. package/test/unit/spec/breakouts/utils.js +72 -0
  449. package/test/unit/spec/common/browser-detection.js +9 -28
  450. package/test/unit/spec/controls-options-manager/index.js +287 -0
  451. package/test/unit/spec/controls-options-manager/util.js +403 -0
  452. package/test/unit/spec/fixture/locus.js +92 -90
  453. package/test/unit/spec/locus-info/controlsUtils.js +177 -32
  454. package/test/unit/spec/locus-info/embeddedAppsUtils.js +8 -6
  455. package/test/unit/spec/locus-info/index.js +493 -3
  456. package/test/unit/spec/locus-info/infoUtils.js +41 -32
  457. package/test/unit/spec/locus-info/lib/BasicSeqCmp.json +88 -430
  458. package/test/unit/spec/locus-info/lib/SeqCmp.json +513 -685
  459. package/test/unit/spec/locus-info/parser.js +3 -9
  460. package/test/unit/spec/locus-info/selfConstant.js +110 -103
  461. package/test/unit/spec/locus-info/selfUtils.js +236 -12
  462. package/test/unit/spec/media/index.ts +303 -0
  463. package/test/unit/spec/media/properties.ts +73 -82
  464. package/test/unit/spec/meeting/in-meeting-actions.ts +58 -3
  465. package/test/unit/spec/meeting/index.js +3127 -975
  466. package/test/unit/spec/meeting/muteState.js +375 -70
  467. package/test/unit/spec/meeting/request.js +217 -43
  468. package/test/unit/spec/meeting/utils.js +205 -163
  469. package/test/unit/spec/meeting-info/meetinginfov2.js +268 -74
  470. package/test/unit/spec/meeting-info/request.js +7 -9
  471. package/test/unit/spec/meeting-info/util.js +11 -12
  472. package/test/unit/spec/meeting-info/utilv2.js +131 -74
  473. package/test/unit/spec/meetings/collection.js +15 -1
  474. package/test/unit/spec/meetings/index.js +1052 -333
  475. package/test/unit/spec/meetings/utils.js +163 -14
  476. package/test/unit/spec/member/index.js +24 -1
  477. package/test/unit/spec/member/util.js +359 -32
  478. package/test/unit/spec/members/index.js +547 -37
  479. package/test/unit/spec/members/request.js +76 -20
  480. package/test/unit/spec/members/utils.js +191 -4
  481. package/test/unit/spec/metrics/index.js +46 -20
  482. package/test/unit/spec/multistream/mediaRequestManager.ts +1060 -0
  483. package/test/unit/spec/multistream/receiveSlot.ts +163 -0
  484. package/test/unit/spec/multistream/receiveSlotManager.ts +203 -0
  485. package/test/unit/spec/multistream/remoteMedia.ts +255 -0
  486. package/test/unit/spec/multistream/remoteMediaGroup.ts +396 -0
  487. package/test/unit/spec/multistream/remoteMediaManager.ts +1793 -0
  488. package/test/unit/spec/networkQualityMonitor/index.js +24 -18
  489. package/test/unit/spec/personal-meeting-room/personal-meeting-room.js +2 -7
  490. package/test/unit/spec/reachability/index.ts +176 -27
  491. package/test/unit/spec/reachability/request.js +66 -0
  492. package/test/unit/spec/reconnection-manager/index.js +106 -9
  493. package/test/unit/spec/recording-controller/index.js +231 -0
  494. package/test/unit/spec/recording-controller/util.js +102 -0
  495. package/test/unit/spec/roap/index.ts +78 -45
  496. package/test/unit/spec/roap/request.ts +217 -0
  497. package/test/unit/spec/roap/turnDiscovery.ts +93 -49
  498. package/test/unit/spec/stats-analyzer/index.js +118 -65
  499. package/test/utils/cmr.js +44 -42
  500. package/test/utils/constants.js +9 -0
  501. package/test/utils/integrationTestUtils.js +64 -0
  502. package/test/utils/testUtils.js +63 -99
  503. package/test/utils/webex-config.js +22 -18
  504. package/test/utils/webex-test-users.js +57 -50
  505. package/tsconfig.json +6 -0
  506. package/dist/meeting/effectsState.js +0 -327
  507. package/dist/meeting/effectsState.js.map +0 -1
  508. package/dist/peer-connection-manager/index.js +0 -794
  509. package/dist/peer-connection-manager/index.js.map +0 -1
  510. package/dist/peer-connection-manager/util.js +0 -124
  511. package/dist/peer-connection-manager/util.js.map +0 -1
  512. package/dist/roap/collection.js +0 -73
  513. package/dist/roap/collection.js.map +0 -1
  514. package/dist/roap/handler.js +0 -337
  515. package/dist/roap/handler.js.map +0 -1
  516. package/dist/roap/state.js +0 -164
  517. package/dist/roap/state.js.map +0 -1
  518. package/dist/roap/util.js +0 -102
  519. package/dist/roap/util.js.map +0 -1
  520. package/src/common/logs/logger-proxy.js +0 -33
  521. package/src/index.js +0 -15
  522. package/src/locus-info/controlsUtils.js +0 -102
  523. package/src/media/index.js +0 -593
  524. package/src/media/util.js +0 -38
  525. package/src/mediaQualityMetrics/config.js +0 -382
  526. package/src/meeting/effectsState.js +0 -205
  527. package/src/meeting/muteState.js +0 -318
  528. package/src/meeting-info/meeting-info-v2.js +0 -255
  529. package/src/meetings/index.js +0 -986
  530. package/src/meetings/util.js +0 -176
  531. package/src/members/request.js +0 -131
  532. package/src/peer-connection-manager/index.js +0 -723
  533. package/src/peer-connection-manager/util.ts +0 -117
  534. package/src/roap/collection.js +0 -63
  535. package/src/roap/handler.js +0 -252
  536. package/src/roap/index.js +0 -380
  537. package/src/roap/request.js +0 -198
  538. package/src/roap/state.js +0 -149
  539. package/src/roap/util.js +0 -93
  540. package/src/statsAnalyzer/global.js +0 -131
  541. package/src/statsAnalyzer/index.js +0 -1020
  542. package/src/statsAnalyzer/mqaUtil.js +0 -173
  543. package/test/unit/spec/meeting/effectsState.js +0 -293
  544. package/test/unit/spec/peerconnection-manager/index.js +0 -188
  545. package/test/unit/spec/peerconnection-manager/utils.js +0 -48
  546. package/test/unit/spec/peerconnection-manager/utils.test-fixtures.ts +0 -389
  547. package/test/unit/spec/roap/util.js +0 -30
  548. /package/src/common/errors/{reconnection-in-progress.js → reconnection-in-progress.ts} +0 -0
@@ -0,0 +1,1793 @@
1
+ /* eslint-disable require-jsdoc */
2
+ import EventEmitter from 'events';
3
+
4
+ import {MediaType} from '@webex/internal-media-core';
5
+ import {
6
+ Configuration,
7
+ Event,
8
+ RemoteMediaManager,
9
+ VideoLayoutChangedEventData,
10
+ } from '@webex/plugin-meetings/src/multistream/remoteMediaManager';
11
+ import {RemoteMediaGroup} from '@webex/plugin-meetings/src/multistream/remoteMediaGroup';
12
+ import sinon from 'sinon';
13
+ import {assert} from '@webex/test-helper-chai';
14
+ import {cloneDeep} from 'lodash';
15
+ import {MediaRequest} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
16
+ import {CSI, ReceiveSlotId} from '@webex/plugin-meetings/src/multistream/receiveSlot';
17
+ import testUtils from '../../../utils/testUtils';
18
+ import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
19
+ import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
20
+ import { expect } from 'chai';
21
+
22
+ class FakeSlot extends EventEmitter {
23
+ public mediaType: MediaType;
24
+
25
+ public id: string;
26
+
27
+ public csi?: number;
28
+
29
+ constructor(mediaType: MediaType, id: string) {
30
+ super();
31
+ this.mediaType = mediaType;
32
+ this.id = id;
33
+ // Many of the tests use the same FakeSlot instance for all remote media, so it gets
34
+ // a lot of listeners registered causing a warning about a potential listener leak.
35
+ // Calling setMaxListeners() fixes the warning.
36
+ this.setMaxListeners(50);
37
+ }
38
+
39
+ public get logString() {
40
+ return this.id;
41
+ }
42
+ }
43
+
44
+ const DefaultTestConfiguration: Configuration = {
45
+ audio: {
46
+ numOfActiveSpeakerStreams: 3,
47
+ numOfScreenShareStreams: 1,
48
+ },
49
+ video: {
50
+ preferLiveVideo: true,
51
+ initialLayoutId: 'AllEqual',
52
+
53
+ layouts: {
54
+ AllEqual: {
55
+ activeSpeakerVideoPaneGroups: [
56
+ {
57
+ id: 'main',
58
+ numPanes: 9,
59
+ size: 'best',
60
+ priority: 255,
61
+ },
62
+ ],
63
+ },
64
+ OnePlusFive: {
65
+ activeSpeakerVideoPaneGroups: [
66
+ {
67
+ id: 'mainBigOne',
68
+ numPanes: 1,
69
+ size: 'large',
70
+ priority: 255,
71
+ },
72
+ {
73
+ id: 'secondarySetOfSmallPanes',
74
+ numPanes: 5,
75
+ size: 'very small',
76
+ priority: 254,
77
+ },
78
+ ],
79
+ },
80
+ Single: {
81
+ activeSpeakerVideoPaneGroups: [
82
+ {
83
+ id: 'main',
84
+ numPanes: 1,
85
+ size: 'best',
86
+ priority: 255,
87
+ },
88
+ ],
89
+ },
90
+ Stage: {
91
+ activeSpeakerVideoPaneGroups: [
92
+ {
93
+ id: 'thumbnails',
94
+ numPanes: 6,
95
+ size: 'thumbnail',
96
+ priority: 255,
97
+ },
98
+ ],
99
+ memberVideoPanes: [
100
+ {id: 'stage-1', size: 'medium', csi: undefined},
101
+ {id: 'stage-2', size: 'medium', csi: undefined},
102
+ {id: 'stage-3', size: 'medium', csi: undefined},
103
+ {id: 'stage-4', size: 'medium', csi: undefined},
104
+ ],
105
+ },
106
+ ScreenShareView: {
107
+ screenShareVideo: {size: 'medium'},
108
+ activeSpeakerVideoPaneGroups: [
109
+ {
110
+ id: 'thumbnails',
111
+ numPanes: 6,
112
+ size: 'thumbnail',
113
+ priority: 255,
114
+ },
115
+ ],
116
+ },
117
+ },
118
+ },
119
+ };
120
+
121
+ describe('RemoteMediaManager', () => {
122
+ let remoteMediaManager;
123
+ let fakeReceiveSlotManager;
124
+ let fakeMediaRequestManagers;
125
+ let fakeAudioSlot;
126
+ let fakeVideoSlot;
127
+ let fakeScreenShareAudioSlot;
128
+ let fakeScreenShareVideoSlot;
129
+
130
+ const logger = {
131
+ log: sinon.fake(),
132
+ error: () => {},
133
+ warn: () => {},
134
+ trace: () => {},
135
+ debug: () => {},
136
+ };
137
+
138
+ afterEach(() => {
139
+ LoggerConfig.set({enable: false});
140
+ LoggerProxy.set();
141
+ });
142
+
143
+ beforeEach(() => {
144
+ LoggerConfig.set({enable: true});
145
+ LoggerProxy.set(logger);
146
+
147
+ fakeAudioSlot = new FakeSlot(MediaType.AudioMain, 'fake audio slot');
148
+ fakeVideoSlot = new FakeSlot(MediaType.VideoMain, 'fake video slot');
149
+ fakeScreenShareAudioSlot = new FakeSlot(
150
+ MediaType.AudioSlides,
151
+ 'fake screen share audio slot'
152
+ );
153
+ fakeScreenShareVideoSlot = new FakeSlot(
154
+ MediaType.VideoSlides,
155
+ 'fake screen share video slot'
156
+ );
157
+
158
+ fakeReceiveSlotManager = {
159
+ allocateSlot: sinon.stub().callsFake((mediaType) => {
160
+ switch (mediaType) {
161
+ case MediaType.AudioMain:
162
+ return Promise.resolve(fakeAudioSlot);
163
+ case MediaType.VideoMain:
164
+ return Promise.resolve(fakeVideoSlot);
165
+ case MediaType.AudioSlides:
166
+ return Promise.resolve(fakeScreenShareAudioSlot);
167
+ case MediaType.VideoSlides:
168
+ return Promise.resolve(fakeScreenShareVideoSlot);
169
+ }
170
+ throw new Error(`invalid mediaType: ${mediaType}`);
171
+ }),
172
+ releaseSlot: sinon.stub(),
173
+ };
174
+
175
+ fakeMediaRequestManagers = {
176
+ audio: {
177
+ addRequest: sinon.stub(),
178
+ cancelRequest: sinon.stub(),
179
+ commit: sinon.stub(),
180
+ },
181
+ video: {
182
+ addRequest: sinon.stub(),
183
+ cancelRequest: sinon.stub(),
184
+ commit: sinon.stub(),
185
+ },
186
+ screenShareAudio: {
187
+ addRequest: sinon.stub(),
188
+ cancelRequest: sinon.stub(),
189
+ commit: sinon.stub(),
190
+ },
191
+ screenShareVideo: {
192
+ addRequest: sinon.stub(),
193
+ cancelRequest: sinon.stub(),
194
+ commit: sinon.stub(),
195
+ },
196
+ };
197
+
198
+ // create remote media manager with default configuration
199
+ remoteMediaManager = new RemoteMediaManager(
200
+ fakeReceiveSlotManager,
201
+ fakeMediaRequestManagers,
202
+ DefaultTestConfiguration
203
+ );
204
+ });
205
+
206
+ const resetHistory = () => {
207
+ fakeReceiveSlotManager.allocateSlot.resetHistory();
208
+ fakeReceiveSlotManager.releaseSlot.resetHistory();
209
+ fakeMediaRequestManagers.audio.addRequest.resetHistory();
210
+ fakeMediaRequestManagers.audio.cancelRequest.resetHistory();
211
+ fakeMediaRequestManagers.audio.commit.resetHistory();
212
+ fakeMediaRequestManagers.video.addRequest.resetHistory();
213
+ fakeMediaRequestManagers.video.cancelRequest.resetHistory();
214
+ fakeMediaRequestManagers.video.commit.resetHistory();
215
+ fakeMediaRequestManagers.screenShareVideo.commit.resetHistory();
216
+ fakeMediaRequestManagers.screenShareAudio.commit.resetHistory();
217
+ logger.log.resetHistory();
218
+ };
219
+
220
+ describe('start', () => {
221
+ it('rejects if called twice', async () => {
222
+ await remoteMediaManager.start();
223
+ await assert.isRejected(remoteMediaManager.start());
224
+ });
225
+
226
+ it('can be called again after stop()', async () => {
227
+ await remoteMediaManager.start();
228
+ remoteMediaManager.stop();
229
+
230
+ fakeReceiveSlotManager.allocateSlot.resetHistory();
231
+
232
+ await remoteMediaManager.start();
233
+
234
+ // check that the 2nd start() creates slots and media requests and is not a no-op
235
+ assert.calledWith(fakeReceiveSlotManager.allocateSlot, MediaType.AudioMain);
236
+ assert.calledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
237
+
238
+ assert.called(fakeMediaRequestManagers.audio.addRequest);
239
+ assert.called(fakeMediaRequestManagers.video.addRequest);
240
+ });
241
+
242
+ it('creates a RemoteMediaGroup for audio correctly', async () => {
243
+ let createdAudioGroup: RemoteMediaGroup | null = null;
244
+
245
+ // create a config with just audio, no video at all and no screen share
246
+ const config: Configuration = {
247
+ audio: {
248
+ numOfActiveSpeakerStreams: 5,
249
+ numOfScreenShareStreams: 0,
250
+ },
251
+ video: {
252
+ preferLiveVideo: false,
253
+ initialLayoutId: 'empty',
254
+ layouts: {
255
+ empty: {},
256
+ },
257
+ },
258
+ };
259
+
260
+ remoteMediaManager = new RemoteMediaManager(
261
+ fakeReceiveSlotManager,
262
+ fakeMediaRequestManagers,
263
+ config
264
+ );
265
+
266
+ remoteMediaManager.on(Event.AudioCreated, (audio: RemoteMediaGroup) => {
267
+ createdAudioGroup = audio;
268
+ });
269
+
270
+ remoteMediaManager.start();
271
+
272
+ await testUtils.flushPromises();
273
+
274
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 5);
275
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.AudioMain);
276
+
277
+ assert.isNotNull(createdAudioGroup);
278
+ if (createdAudioGroup) {
279
+ assert.strictEqual(createdAudioGroup.getRemoteMedia().length, 5);
280
+ assert.isTrue(
281
+ createdAudioGroup
282
+ .getRemoteMedia()
283
+ .every((remoteMedia) => remoteMedia.mediaType === MediaType.AudioMain)
284
+ );
285
+ assert.strictEqual(createdAudioGroup.getRemoteMedia('pinned').length, 0);
286
+ }
287
+
288
+ assert.calledOnce(fakeMediaRequestManagers.audio.addRequest);
289
+ assert.calledWith(
290
+ fakeMediaRequestManagers.audio.addRequest,
291
+ sinon.match({
292
+ policyInfo: sinon.match({
293
+ policy: 'active-speaker',
294
+ priority: 255,
295
+ }),
296
+ receiveSlots: Array(5).fill(fakeAudioSlot),
297
+ codecInfo: undefined,
298
+ })
299
+ );
300
+ });
301
+
302
+ it('pre-allocates receive slots based on the biggest layout', async () => {
303
+ const config = cloneDeep(DefaultTestConfiguration);
304
+
305
+ config.audio.numOfActiveSpeakerStreams = 0;
306
+ config.video.layouts.huge = {
307
+ activeSpeakerVideoPaneGroups: [
308
+ {
309
+ id: 'big one',
310
+ numPanes: 99,
311
+ size: 'small',
312
+ priority: 255,
313
+ },
314
+ ],
315
+ };
316
+ config.audio.numOfScreenShareStreams = 0;
317
+ delete config.video.layouts.ScreenShareView;
318
+
319
+ remoteMediaManager = new RemoteMediaManager(
320
+ fakeReceiveSlotManager,
321
+ fakeMediaRequestManagers,
322
+ config
323
+ );
324
+
325
+ await remoteMediaManager.start();
326
+
327
+ // even though our "big one" layout is not the default one, the remote media manager should still
328
+ // preallocate enough video receive slots for it up front
329
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 99);
330
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
331
+ });
332
+
333
+ it('starts with the initial layout', async () => {
334
+ let receivedLayoutInfo: VideoLayoutChangedEventData | null = null;
335
+
336
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
337
+ receivedLayoutInfo = layoutInfo;
338
+ });
339
+
340
+ // the initial layout is "AllEqual", so we check that it gets selected by default
341
+ await remoteMediaManager.start();
342
+
343
+ assert.strictEqual(remoteMediaManager.getLayoutId(), 'AllEqual');
344
+ assert.isNotNull(receivedLayoutInfo);
345
+ if (receivedLayoutInfo) {
346
+ assert.strictEqual(receivedLayoutInfo.layoutId, 'AllEqual');
347
+ assert.strictEqual(Object.keys(receivedLayoutInfo.memberVideoPanes).length, 0);
348
+ assert.strictEqual(Object.keys(receivedLayoutInfo.activeSpeakerVideoPanes).length, 1); // this layout has only 1 active speaker group
349
+ assert.strictEqual(
350
+ receivedLayoutInfo.activeSpeakerVideoPanes.main.getRemoteMedia().length,
351
+ 9
352
+ );
353
+ assert.isUndefined(receivedLayoutInfo.screenShareVideo); // the initial layout has no screen share
354
+ }
355
+ });
356
+
357
+ it('creates RemoteMedia for screen share audio correctly', async () => {
358
+ let createdAudioGroup: RemoteMediaGroup | null = null;
359
+
360
+ const NUM_STREAMS = 2;
361
+
362
+ // create a config with just screen share audio, nothing else
363
+ const config: Configuration = {
364
+ audio: {
365
+ numOfActiveSpeakerStreams: 0,
366
+ numOfScreenShareStreams: NUM_STREAMS,
367
+ },
368
+ video: {
369
+ preferLiveVideo: false,
370
+ initialLayoutId: 'empty',
371
+ layouts: {
372
+ empty: {},
373
+ },
374
+ },
375
+ };
376
+
377
+ remoteMediaManager = new RemoteMediaManager(
378
+ fakeReceiveSlotManager,
379
+ fakeMediaRequestManagers,
380
+ config
381
+ );
382
+
383
+ remoteMediaManager.on(Event.ScreenShareAudioCreated, (audio: RemoteMediaGroup) => {
384
+ createdAudioGroup = audio;
385
+ });
386
+
387
+ remoteMediaManager.start();
388
+
389
+ await testUtils.flushPromises();
390
+
391
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, NUM_STREAMS);
392
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.AudioSlides);
393
+
394
+ assert.isNotNull(createdAudioGroup);
395
+ if (createdAudioGroup) {
396
+ assert.strictEqual(createdAudioGroup.getRemoteMedia().length, NUM_STREAMS);
397
+ assert.isTrue(
398
+ createdAudioGroup
399
+ .getRemoteMedia()
400
+ .every((remoteMedia) => remoteMedia.mediaType === MediaType.AudioSlides)
401
+ );
402
+ assert.strictEqual(createdAudioGroup.getRemoteMedia('pinned').length, 0);
403
+ }
404
+
405
+ assert.calledOnce(fakeMediaRequestManagers.screenShareAudio.addRequest);
406
+ assert.calledWith(
407
+ fakeMediaRequestManagers.screenShareAudio.addRequest,
408
+ sinon.match({
409
+ policyInfo: sinon.match({
410
+ policy: 'active-speaker',
411
+ priority: 255,
412
+ }),
413
+ receiveSlots: Array(NUM_STREAMS).fill(fakeScreenShareAudioSlot),
414
+ codecInfo: undefined,
415
+ })
416
+ );
417
+ });
418
+
419
+ it('creates a single receive slot for screen share video if any layout has screen share', async () => {
420
+ // create a config with 2 layouts that use screen share
421
+ const config: Configuration = {
422
+ audio: {
423
+ numOfActiveSpeakerStreams: 0,
424
+ numOfScreenShareStreams: 0,
425
+ },
426
+ video: {
427
+ preferLiveVideo: false,
428
+ initialLayoutId: 'first',
429
+ layouts: {
430
+ first: {
431
+ screenShareVideo: { size: 'small'}
432
+ },
433
+ second: {
434
+ screenShareVideo: { size: 'medium'}
435
+ }
436
+ },
437
+ },
438
+ };
439
+
440
+ remoteMediaManager = new RemoteMediaManager(
441
+ fakeReceiveSlotManager,
442
+ fakeMediaRequestManagers,
443
+ config
444
+ );
445
+
446
+ await remoteMediaManager.start();
447
+
448
+ // even though 2 layouts use screen share, only 1 video screen share slot should be created
449
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 1);
450
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoSlides);
451
+ });
452
+
453
+ it('does not create any receive slot for screen share video if none of the layouts have screen share', async () => {
454
+ const config = cloneDeep(DefaultTestConfiguration);
455
+
456
+ config.audio.numOfActiveSpeakerStreams = 0;
457
+ config.audio.numOfScreenShareStreams = 0;
458
+
459
+ // delete the only layout that uses screen share
460
+ delete config.video.layouts.ScreenShareView;
461
+
462
+ remoteMediaManager = new RemoteMediaManager(
463
+ fakeReceiveSlotManager,
464
+ fakeMediaRequestManagers,
465
+ config
466
+ );
467
+
468
+ await remoteMediaManager.start();
469
+
470
+ // we don't expect any audio and for video there should be no VideoSlides, so all the calls should be just for VideoMain
471
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
472
+ });
473
+
474
+
475
+ });
476
+
477
+ describe('constructor', () => {
478
+ it('throws if the initial layout in the config is invalid', () => {
479
+ const config = cloneDeep(DefaultTestConfiguration);
480
+
481
+ config.video.initialLayoutId = 'invalid';
482
+
483
+ assert.throws(() => {
484
+ remoteMediaManager = new RemoteMediaManager(
485
+ fakeReceiveSlotManager,
486
+ fakeMediaRequestManagers,
487
+ config
488
+ );
489
+ }, 'invalid config: initialLayoutId "invalid" doesn\'t match any of the layouts');
490
+ });
491
+
492
+ it('throws if there are duplicate active speaker video pane groups', () => {
493
+ const config = cloneDeep(DefaultTestConfiguration);
494
+
495
+ config.video.layouts.test = {
496
+ activeSpeakerVideoPaneGroups: [
497
+ {
498
+ id: 'someDuplicate',
499
+ numPanes: 10,
500
+ priority: 255,
501
+ size: 'best',
502
+ },
503
+ {
504
+ id: 'other',
505
+ numPanes: 10,
506
+ priority: 254,
507
+ size: 'best',
508
+ },
509
+ {
510
+ id: 'someDuplicate',
511
+ numPanes: 10,
512
+ priority: 255,
513
+ size: 'best',
514
+ },
515
+ ],
516
+ };
517
+
518
+ assert.throws(() => {
519
+ remoteMediaManager = new RemoteMediaManager(
520
+ fakeReceiveSlotManager,
521
+ fakeMediaRequestManagers,
522
+ config
523
+ );
524
+ }, 'invalid config: duplicate active speaker video pane group id: someDuplicate');
525
+ });
526
+
527
+ it('throws if there are active speaker video pane groups with duplicate priority', () => {
528
+ const config = cloneDeep(DefaultTestConfiguration);
529
+
530
+ config.video.layouts.test = {
531
+ activeSpeakerVideoPaneGroups: [
532
+ {
533
+ id: 'group1',
534
+ numPanes: 10,
535
+ priority: 200,
536
+ size: 'best',
537
+ },
538
+ {
539
+ id: 'group2',
540
+ numPanes: 2,
541
+ priority: 200,
542
+ size: 'medium',
543
+ },
544
+ {
545
+ id: 'group3',
546
+ numPanes: 5,
547
+ priority: 100,
548
+ size: 'large',
549
+ },
550
+ ],
551
+ };
552
+
553
+ assert.throws(() => {
554
+ remoteMediaManager = new RemoteMediaManager(
555
+ fakeReceiveSlotManager,
556
+ fakeMediaRequestManagers,
557
+ config
558
+ );
559
+ }, 'invalid config: multiple active speaker video pane groups have same priority: 200');
560
+ });
561
+
562
+ it('throws if there are duplicate member video panes', () => {
563
+ const config = cloneDeep(DefaultTestConfiguration);
564
+
565
+ config.video.layouts.test = {
566
+ memberVideoPanes: [
567
+ {id: 'paneA', size: 'best', csi: 123},
568
+ {id: 'paneB', size: 'large', csi: 222},
569
+ {id: 'paneC', size: 'medium', csi: 333},
570
+ {id: 'paneB', size: 'small', csi: 444},
571
+ ],
572
+ };
573
+
574
+ assert.throws(() => {
575
+ remoteMediaManager = new RemoteMediaManager(
576
+ fakeReceiveSlotManager,
577
+ fakeMediaRequestManagers,
578
+ config
579
+ );
580
+ }, 'invalid config: duplicate member video pane id: paneB');
581
+ });
582
+
583
+ });
584
+
585
+ describe('stop', () => {
586
+ it('releases all the slots and invalidates all remote media', async () => {
587
+ let audioStopStub;
588
+ let videoActiveSpeakerGroupStopStub;
589
+ const memberVideoPaneStopStubs: any[] = [];
590
+ let screenShareAudioStopStub;
591
+ let screenShareVideoStopStub;
592
+
593
+ // change the initial layout to one that has both active speakers and receiver selected videos
594
+ const config = cloneDeep(DefaultTestConfiguration);
595
+
596
+ config.video.initialLayoutId = 'Stage';
597
+
598
+ // and also modify it to have screen share so we can test that too
599
+ config.video.layouts['Stage'].screenShareVideo = {size: 'medium'};
600
+
601
+ remoteMediaManager = new RemoteMediaManager(
602
+ fakeReceiveSlotManager,
603
+ fakeMediaRequestManagers,
604
+ config
605
+ );
606
+
607
+ remoteMediaManager.on(Event.AudioCreated, (audio: RemoteMediaGroup) => {
608
+ audioStopStub = sinon.stub(audio, 'stop');
609
+ });
610
+
611
+ remoteMediaManager.on(Event.ScreenShareAudioCreated, (audio: RemoteMediaGroup) => {
612
+ screenShareAudioStopStub = sinon.stub(audio, 'stop');
613
+ });
614
+
615
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
616
+ // The "Stage" layout that we're using has only 1 active speaker group called "thumbnails"
617
+ videoActiveSpeakerGroupStopStub = sinon.stub(
618
+ layoutInfo.activeSpeakerVideoPanes.thumbnails,
619
+ 'stop'
620
+ );
621
+
622
+ Object.values(layoutInfo.memberVideoPanes).forEach((pane) => {
623
+ memberVideoPaneStopStubs.push(sinon.stub(pane, 'stop'));
624
+ });
625
+
626
+ screenShareVideoStopStub = sinon.stub(layoutInfo.screenShareVideo, 'stop');
627
+ });
628
+
629
+ await remoteMediaManager.start();
630
+
631
+ // we're using the default config that requires 3 main audio slots, 10 video slots (for Stage2x2With6ThumbnailsLayout), 1 screenshare audio, 1 screenshare video
632
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 15);
633
+
634
+ // our layout has 4 member video panes, we should have a stub for each of these panes' stop methods
635
+ assert.strictEqual(memberVideoPaneStopStubs.length, 4);
636
+
637
+ resetHistory();
638
+
639
+ remoteMediaManager.stop();
640
+
641
+ // check that all slots have been released
642
+ assert.callCount(fakeReceiveSlotManager.releaseSlot, 15);
643
+
644
+ // and that all RemoteMedia and RemoteMediaGroups have been stopped
645
+ assert.calledOnce(audioStopStub);
646
+ assert.calledWith(audioStopStub, true);
647
+ assert.calledOnce(screenShareAudioStopStub);
648
+ assert.calledWith(screenShareAudioStopStub, true);
649
+ assert.calledOnce(videoActiveSpeakerGroupStopStub);
650
+ memberVideoPaneStopStubs.forEach((stub) => {
651
+ assert.calledOnce(stub);
652
+ });
653
+ assert.calledOnce(fakeMediaRequestManagers.video.commit);
654
+ assert.calledOnce(screenShareVideoStopStub);
655
+ assert.calledOnce(fakeMediaRequestManagers.screenShareVideo.commit);
656
+ });
657
+
658
+ it('can be called multiple times', async () => {
659
+ await remoteMediaManager.start();
660
+
661
+ // just checking that nothing crashes etc.
662
+ remoteMediaManager.stop();
663
+ remoteMediaManager.stop();
664
+ });
665
+ });
666
+ describe('setLayout', () => {
667
+ it('rejects if called with invalid layoutId', async () => {
668
+ await assert.isRejected(remoteMediaManager.setLayout('invalid value'));
669
+ });
670
+
671
+ it('rejects if called before calling start()', async () => {
672
+ await assert.isRejected(remoteMediaManager.setLayout('Stage'));
673
+ });
674
+
675
+ it('allocates more slots when switching to a layout that requires more slots', async () => {
676
+ // start with "Single" layout that needs just 1 video slot
677
+ const config = cloneDeep(DefaultTestConfiguration);
678
+
679
+ config.video.initialLayoutId = 'Single';
680
+
681
+ remoteMediaManager = new RemoteMediaManager(
682
+ fakeReceiveSlotManager,
683
+ fakeMediaRequestManagers,
684
+ config
685
+ );
686
+
687
+ await remoteMediaManager.start();
688
+
689
+ resetHistory();
690
+
691
+ // switch to "Stage" layout that requires 9 more video slots (10)
692
+ await remoteMediaManager.setLayout('Stage');
693
+
694
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 9);
695
+ assert.alwaysCalledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
696
+ });
697
+
698
+ it('logs layout changes - receiver selected', async () => {
699
+ const config = cloneDeep(DefaultTestConfiguration);
700
+
701
+ remoteMediaManager = new RemoteMediaManager(
702
+ fakeReceiveSlotManager,
703
+ fakeMediaRequestManagers,
704
+ config
705
+ );
706
+
707
+ await remoteMediaManager.start();
708
+
709
+ resetHistory();
710
+
711
+ await remoteMediaManager.setLayout('Stage');
712
+
713
+ assert.calledWith(
714
+ logger.log,
715
+ 'RemoteMediaManager#updateVideoReceiveSlots --> receive slots updated: unused=0, activeSpeaker=6, receiverSelected=4\ngroup: thumbnails\nfake video slot fake video slot fake video slot fake video slot fake video slot fake video slot\nreceiverSelected:\n stage-1: fake video slot\n stage-2: fake video slot\n stage-3: fake video slot\n stage-4: fake video slot\n'
716
+ );
717
+ });
718
+
719
+ it('logs layout changes - active speaker', async () => {
720
+ const config = cloneDeep(DefaultTestConfiguration);
721
+ config.video.initialLayoutId = 'OnePlusFive'
722
+
723
+ remoteMediaManager = new RemoteMediaManager(
724
+ fakeReceiveSlotManager,
725
+ fakeMediaRequestManagers,
726
+ config
727
+ );
728
+
729
+ await remoteMediaManager.start();
730
+
731
+ resetHistory();
732
+
733
+ await remoteMediaManager.setLayout('AllEqual');
734
+
735
+ assert.calledWith(
736
+ logger.log,
737
+ 'RemoteMediaManager#updateVideoReceiveSlots --> receive slots updated: unused=0, activeSpeaker=9, receiverSelected=0\ngroup: main\nfake video slot fake video slot fake video slot fake video slot fake video slot fake video slot fake video slot fake video slot fake video slot\nreceiverSelected:\n'
738
+ );
739
+ });
740
+
741
+
742
+ it('releases slots when switching to layout that requires less active speaker slots', async () => {
743
+ // start with "AllEqual" layout that needs just 9 video slots
744
+ const config = cloneDeep(DefaultTestConfiguration);
745
+
746
+ config.video.initialLayoutId = 'AllEqual';
747
+
748
+ remoteMediaManager = new RemoteMediaManager(
749
+ fakeReceiveSlotManager,
750
+ fakeMediaRequestManagers,
751
+ config
752
+ );
753
+
754
+ await remoteMediaManager.start();
755
+
756
+ resetHistory();
757
+
758
+ // switch to "OnePlusFive" layout that requires 3 less video slots (6)
759
+ await remoteMediaManager.setLayout('OnePlusFive');
760
+
761
+ // verify that 3 main video slots were released
762
+ assert.callCount(fakeReceiveSlotManager.releaseSlot, 3);
763
+ fakeReceiveSlotManager.releaseSlot.getCalls().forEach((call) => {
764
+ const slot = call.args[0];
765
+
766
+ assert.strictEqual(slot.mediaType, MediaType.VideoMain);
767
+ });
768
+ });
769
+
770
+ it('releases slots and reallocates slots when switching to layouts in correct order', async () => {
771
+
772
+ const config = cloneDeep(DefaultTestConfiguration);
773
+ let count = 0;
774
+
775
+ fakeReceiveSlotManager.allocateSlot = sinon.stub().callsFake((mediaType) => {
776
+ switch (mediaType) {
777
+ case MediaType.AudioMain:
778
+ return Promise.resolve(fakeAudioSlot);
779
+ case MediaType.VideoMain:
780
+ return Promise.resolve(new FakeSlot(MediaType.VideoMain, `fake video ${count++}`));
781
+ case MediaType.AudioSlides:
782
+ return Promise.resolve(fakeScreenShareAudioSlot);
783
+ case MediaType.VideoSlides:
784
+ return Promise.resolve(fakeScreenShareVideoSlot);
785
+ }
786
+ throw new Error(`invalid mediaType: ${mediaType}`);
787
+ })
788
+
789
+ remoteMediaManager = new RemoteMediaManager(
790
+ fakeReceiveSlotManager,
791
+ fakeMediaRequestManagers,
792
+ config
793
+ );
794
+
795
+ await remoteMediaManager.start();
796
+
797
+ resetHistory();
798
+
799
+ assert.deepEqual(remoteMediaManager.slots.video.activeSpeaker.map((slot: any) => slot.id), [
800
+ "fake video 0",
801
+ "fake video 1",
802
+ "fake video 2",
803
+ "fake video 3",
804
+ "fake video 4",
805
+ "fake video 5",
806
+ "fake video 6",
807
+ "fake video 7",
808
+ "fake video 8",
809
+ ]);
810
+
811
+ assert.deepEqual(remoteMediaManager.receiveSlotAllocations.activeSpeaker["main"].slots.map((slot: any) => slot.id), [
812
+ "fake video 0",
813
+ "fake video 1",
814
+ "fake video 2",
815
+ "fake video 3",
816
+ "fake video 4",
817
+ "fake video 5",
818
+ "fake video 6",
819
+ "fake video 7",
820
+ "fake video 8",
821
+ ])
822
+
823
+ // switch to "OnePlusFive" layout that requires 3 less video slots (6)
824
+ await remoteMediaManager.setLayout('OnePlusFive');
825
+
826
+ assert.deepEqual(remoteMediaManager.slots.video.unused, []);
827
+
828
+ assert.deepEqual(remoteMediaManager.slots.video.activeSpeaker.map((slot: any) => slot.id), [
829
+ "fake video 0",
830
+ "fake video 1",
831
+ "fake video 2",
832
+ "fake video 3",
833
+ "fake video 4",
834
+ "fake video 5"
835
+ ]);
836
+
837
+ // we're checking that the slots are in the same order as in the previous layout
838
+ // first one goes into main
839
+ assert.deepEqual(remoteMediaManager.receiveSlotAllocations.activeSpeaker["mainBigOne"].slots.map((slot: any) => slot.id), [
840
+ "fake video 0",
841
+ ])
842
+ // and rest go in the pips
843
+ assert.deepEqual(remoteMediaManager.receiveSlotAllocations.activeSpeaker["secondarySetOfSmallPanes"].slots.map((slot: any) => slot.id), [
844
+ "fake video 1",
845
+ "fake video 2",
846
+ "fake video 3",
847
+ "fake video 4",
848
+ "fake video 5"
849
+ ])
850
+
851
+ // verify that 3 main video slots were released
852
+ assert.callCount(fakeReceiveSlotManager.releaseSlot, 3);
853
+ fakeReceiveSlotManager.releaseSlot.getCalls().forEach((call) => {
854
+ const slot = call.args[0];
855
+
856
+ assert.strictEqual(slot.mediaType, MediaType.VideoMain);
857
+ });
858
+
859
+ await remoteMediaManager.setLayout('AllEqual');
860
+
861
+ assert.deepEqual(remoteMediaManager.slots.video.unused, []);
862
+
863
+ // checking that slots are in the same order as in previous layout + 3 new ones
864
+ assert.deepEqual(remoteMediaManager.slots.video.activeSpeaker.map((slot: any) => slot.id), [
865
+ "fake video 0",
866
+ "fake video 1",
867
+ "fake video 2",
868
+ "fake video 3",
869
+ "fake video 4",
870
+ "fake video 5",
871
+ "fake video 10",
872
+ "fake video 11",
873
+ "fake video 12",
874
+ ]);
875
+
876
+ assert.deepEqual(remoteMediaManager.receiveSlotAllocations.activeSpeaker["main"].slots.map((slot: any) => slot.id), [
877
+ "fake video 0",
878
+ "fake video 1",
879
+ "fake video 2",
880
+ "fake video 3",
881
+ "fake video 4",
882
+ "fake video 5",
883
+ "fake video 10",
884
+ "fake video 11",
885
+ "fake video 12"
886
+ ])
887
+
888
+ // verify that 3 main video slots were allocated
889
+ assert.callCount(fakeReceiveSlotManager.allocateSlot, 3);
890
+ fakeReceiveSlotManager.allocateSlot.getCalls().forEach((call) => {
891
+ const mediaType = call.args[0];
892
+
893
+ assert.strictEqual(mediaType, MediaType.VideoMain);
894
+ });
895
+ });
896
+
897
+ it('stops all current video remoteMedia instances when switching to new layout', async () => {
898
+ const audioStopStubs = [];
899
+ const videoStopStubs = [];
900
+
901
+ const config = cloneDeep(DefaultTestConfiguration);
902
+
903
+ // start with the stage layout because it has both active speaker and receiver selected panes
904
+ config.video.initialLayoutId = 'Stage';
905
+
906
+ remoteMediaManager = new RemoteMediaManager(
907
+ fakeReceiveSlotManager,
908
+ fakeMediaRequestManagers,
909
+ config
910
+ );
911
+
912
+ // mock all stop() methods for all remote audio objects we get with AudioCreated event
913
+ remoteMediaManager.on(Event.AudioCreated, (audio: RemoteMediaGroup) => {
914
+ audio
915
+ .getRemoteMedia()
916
+ .forEach((remoteAudio) => audioStopStubs.push(sinon.stub(remoteAudio, 'stop')));
917
+ });
918
+
919
+ // mock all stop() methods for all remote video objects we get with VideoLayoutChanged event
920
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
921
+ Object.values(layoutInfo.activeSpeakerVideoPanes).forEach((group) =>
922
+ group
923
+ .getRemoteMedia()
924
+ .forEach((remoteMedia) => videoStopStubs.push(sinon.stub(remoteMedia, 'stop')))
925
+ );
926
+
927
+ Object.values(layoutInfo.memberVideoPanes).forEach((pane) => {
928
+ videoStopStubs.push(sinon.stub(pane, 'stop'));
929
+ });
930
+ });
931
+
932
+ await remoteMediaManager.start();
933
+
934
+ // sanity check that we've got all our stop() mocks setup correctly
935
+ assert.strictEqual(audioStopStubs.length, 3);
936
+ assert.strictEqual(videoStopStubs.length, 10); // 10 = 6 thumbnail panes + 4 stage panes
937
+
938
+ // next, we'll change the layout, we don't care about the new video panes from the new layout, so unregister the event listeners
939
+ remoteMediaManager.removeAllListeners();
940
+
941
+ await remoteMediaManager.setLayout('AllEqual');
942
+
943
+ // check that NONE of the audio RemoteMedia instances were stopped
944
+ audioStopStubs.forEach((audioStopStub) => {
945
+ assert.notCalled(audioStopStub);
946
+ });
947
+
948
+ // check that ALL of the video RemoteMedia instances were stopped
949
+ videoStopStubs.forEach((videoStopStub) => {
950
+ assert.calledOnce(videoStopStub);
951
+ assert.calledWith(videoStopStub, false);
952
+ });
953
+ });
954
+
955
+ it('emits Event.VideoLayoutChanged with correct data', async () => {
956
+ // setup the initial layout to be empty and a testLayout that has screen share, active speaker groups and member video panes
957
+ const config: Configuration = {
958
+ audio: {
959
+ numOfActiveSpeakerStreams: 0,
960
+ numOfScreenShareStreams: 0,
961
+ },
962
+ video: {
963
+ preferLiveVideo: true,
964
+ initialLayoutId: 'empty',
965
+ layouts: {
966
+ empty: {},
967
+ testLayout: {
968
+ screenShareVideo: {size: 'very small'},
969
+ activeSpeakerVideoPaneGroups: [
970
+ {
971
+ id: 'big',
972
+ numPanes: 10,
973
+ priority: 255,
974
+ size: 'large',
975
+ },
976
+ {
977
+ id: 'small',
978
+ numPanes: 3,
979
+ priority: 254,
980
+ size: 'medium',
981
+ },
982
+ ],
983
+ memberVideoPanes: [
984
+ {id: 'pane 1', size: 'best', csi: 555},
985
+ {id: 'pane 2', size: 'best', csi: undefined},
986
+ ],
987
+ },
988
+ },
989
+ },
990
+ };
991
+
992
+ remoteMediaManager = new RemoteMediaManager(
993
+ fakeReceiveSlotManager,
994
+ fakeMediaRequestManagers,
995
+ config
996
+ );
997
+
998
+ await remoteMediaManager.start();
999
+
1000
+ resetHistory();
1001
+
1002
+ let receivedLayoutInfo: VideoLayoutChangedEventData | null = null;
1003
+
1004
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo) => {
1005
+ receivedLayoutInfo = layoutInfo;
1006
+ });
1007
+
1008
+ // switch to the test layout
1009
+ await remoteMediaManager.setLayout('testLayout');
1010
+
1011
+ assert.isNotNull(receivedLayoutInfo);
1012
+
1013
+ if (receivedLayoutInfo) {
1014
+ assert.strictEqual(receivedLayoutInfo.layoutId, 'testLayout');
1015
+
1016
+ // check screen share video
1017
+ assert.isTrue(!!receivedLayoutInfo.screenShareVideo);
1018
+ assert.strictEqual(receivedLayoutInfo.screenShareVideo.mediaType, MediaType.VideoSlides);
1019
+
1020
+ // check member videos
1021
+ assert.strictEqual(Object.keys(receivedLayoutInfo.memberVideoPanes).length, 2);
1022
+ Object.values(receivedLayoutInfo.memberVideoPanes).forEach((remoteMedia) =>
1023
+ assert.strictEqual(remoteMedia.mediaType, MediaType.VideoMain)
1024
+ );
1025
+
1026
+ // check the 2 active speaker groups
1027
+ assert.strictEqual(Object.keys(receivedLayoutInfo.activeSpeakerVideoPanes).length, 2);
1028
+
1029
+ // "big" group
1030
+ assert.strictEqual(
1031
+ receivedLayoutInfo.activeSpeakerVideoPanes.big.getRemoteMedia().length,
1032
+ 10
1033
+ );
1034
+ receivedLayoutInfo.activeSpeakerVideoPanes.big
1035
+ .getRemoteMedia()
1036
+ .forEach((remoteMedia) =>
1037
+ assert.strictEqual(remoteMedia.mediaType, MediaType.VideoMain)
1038
+ );
1039
+
1040
+ // "small" group
1041
+ assert.strictEqual(
1042
+ receivedLayoutInfo.activeSpeakerVideoPanes.small.getRemoteMedia().length,
1043
+ 3
1044
+ );
1045
+ receivedLayoutInfo.activeSpeakerVideoPanes.small
1046
+ .getRemoteMedia()
1047
+ .forEach((remoteMedia) =>
1048
+ assert.strictEqual(remoteMedia.mediaType, MediaType.VideoMain)
1049
+ );
1050
+ }
1051
+ });
1052
+
1053
+ describe('switching between different receiver selected layouts', () => {
1054
+ let fakeSlots: {[key: ReceiveSlotId]: FakeSlot};
1055
+ let slotCounter: number;
1056
+
1057
+ type Csi2SlotsMapping = {[key: CSI]: Array<ReceiveSlotId>};
1058
+ // in these mappings: key is the CSI and value is an array of slot ids
1059
+ // of slots that were used in media requests for that CSI
1060
+ let csi2slotMappingBeforeLayoutChange: Csi2SlotsMapping;
1061
+ let csi2slotMappingAfterLayoutChange: Csi2SlotsMapping;
1062
+ let csi2slotMapping: Csi2SlotsMapping;
1063
+
1064
+ beforeEach(() => {
1065
+ // setup the mocks so that we can keep track of all the slots and their CSIs
1066
+ fakeSlots = {};
1067
+ slotCounter = 0;
1068
+
1069
+ fakeReceiveSlotManager.allocateSlot.callsFake(() => {
1070
+ slotCounter += 1;
1071
+ const newSlotId = `fake video slot ${slotCounter}`;
1072
+
1073
+ fakeSlots[newSlotId] = new FakeSlot(MediaType.VideoMain, newSlotId);
1074
+ return fakeSlots[newSlotId];
1075
+ });
1076
+
1077
+ csi2slotMappingBeforeLayoutChange = {};
1078
+ csi2slotMappingAfterLayoutChange = {};
1079
+
1080
+ csi2slotMapping = csi2slotMappingBeforeLayoutChange;
1081
+
1082
+ fakeMediaRequestManagers.video.addRequest.callsFake((mediaRequest: MediaRequest) => {
1083
+ if (mediaRequest.policyInfo.policy === 'receiver-selected') {
1084
+ const slot = mediaRequest.receiveSlots[0] as unknown as FakeSlot;
1085
+ const csi = mediaRequest.policyInfo.csi;
1086
+
1087
+ slot.csi = csi;
1088
+ if (csi2slotMapping[csi]) {
1089
+ csi2slotMapping[csi].push(slot.id);
1090
+ } else {
1091
+ csi2slotMapping[csi] = [slot.id];
1092
+ }
1093
+
1094
+ return slot.id;
1095
+ }
1096
+ });
1097
+ });
1098
+
1099
+ it('releases slots when switching to layout that requires less receiver selected slots', async () => {
1100
+ const config = cloneDeep(DefaultTestConfiguration);
1101
+
1102
+ // This test starts with a layout that has 5 receiver selected video slots
1103
+ // and switches to a different layout that has fewer slots, but 2 of them match CSIs
1104
+ // from the initial layout. We want to verify that these 2 slots get re-used correctly.
1105
+ // There are no screen share or audio slots being used in this test.
1106
+ delete config.video.layouts.ScreenShareView;
1107
+ config.audio.numOfActiveSpeakerStreams = 0;
1108
+ config.audio.numOfScreenShareStreams = 0;
1109
+ config.video.initialLayoutId = 'biggerLayout';
1110
+ config.video.layouts['biggerLayout'] = {
1111
+ memberVideoPanes: [
1112
+ {id: '1', size: 'best', csi: 100},
1113
+ {id: '2', size: 'best', csi: 200},
1114
+ {id: '3', size: 'best', csi: 300},
1115
+ {id: '4', size: 'best', csi: 400},
1116
+ {id: '5', size: 'best', csi: 500},
1117
+ ],
1118
+ };
1119
+ config.video.layouts['smallerLayout'] = {
1120
+ memberVideoPanes: [
1121
+ {id: '1', size: 'medium', csi: 200}, // this csi matches pane '2' from biggerLayout
1122
+ {id: '2', size: 'medium', csi: 123},
1123
+ {id: '3', size: 'medium', csi: 400}, // this csi matches pane '4' from biggerLayout
1124
+ ],
1125
+ };
1126
+
1127
+ remoteMediaManager = new RemoteMediaManager(
1128
+ fakeReceiveSlotManager,
1129
+ fakeMediaRequestManagers,
1130
+ config
1131
+ );
1132
+
1133
+ await remoteMediaManager.start();
1134
+
1135
+ resetHistory();
1136
+
1137
+ // switch the mock to now use csi2slotMappingAfterLayoutChange as we're about to change the layout
1138
+ csi2slotMapping = csi2slotMappingAfterLayoutChange;
1139
+
1140
+ // switch to "smallerLayout" layout that requires 2 less video slots and has 2 receive selected slots with same CSIs
1141
+ await remoteMediaManager.setLayout('smallerLayout');
1142
+
1143
+ // verify that 2 main video slots were released
1144
+ assert.callCount(fakeReceiveSlotManager.releaseSlot, 2);
1145
+
1146
+ // verify that each CSI has 1 slot assigned
1147
+ assert.equal(Object.keys(csi2slotMappingAfterLayoutChange).length, 3);
1148
+ assert.equal(csi2slotMappingAfterLayoutChange[200].length, 1);
1149
+ assert.equal(csi2slotMappingAfterLayoutChange[123].length, 1);
1150
+ assert.equal(csi2slotMappingAfterLayoutChange[400].length, 1);
1151
+
1152
+ // verify that the slots have been re-used for csi 200 and 400
1153
+ assert.equal(
1154
+ csi2slotMappingBeforeLayoutChange[200][0],
1155
+ csi2slotMappingAfterLayoutChange[200][0]
1156
+ );
1157
+ assert.equal(
1158
+ csi2slotMappingBeforeLayoutChange[400][0],
1159
+ csi2slotMappingAfterLayoutChange[400][0]
1160
+ );
1161
+ });
1162
+
1163
+ it('correctly handles a change to a layout that has member video panes with duplicate CSIs', async () => {
1164
+ const config = cloneDeep(DefaultTestConfiguration);
1165
+
1166
+ // This test starts with a layout that has video slot with a specific CSI
1167
+ // and switches to a different layout that 2 panes with that same CSI.
1168
+ // We want to verify that the slot gets reused, but also that a 2nd slot is allocated.
1169
+ // There are no screen share or audio slots being used in this test.
1170
+ delete config.video.layouts.ScreenShareView;
1171
+ config.audio.numOfActiveSpeakerStreams = 0;
1172
+ config.audio.numOfScreenShareStreams = 0;
1173
+ config.video.initialLayoutId = 'initialEmptyLayout';
1174
+ config.video.layouts['initialEmptyLayout'] = {
1175
+ memberVideoPanes: [{id: '2', size: 'medium', csi: 456}],
1176
+ };
1177
+ config.video.layouts['layoutWithDuplicateCSIs'] = {
1178
+ memberVideoPanes: [
1179
+ {id: '1', size: 'medium', csi: 123},
1180
+ {id: '2', size: 'medium', csi: 456},
1181
+ {id: '3', size: 'medium', csi: 456}, // duplicate CSI and also matching one of CSIs from previous layout
1182
+ {id: '4', size: 'medium', csi: 789},
1183
+ ],
1184
+ };
1185
+
1186
+ remoteMediaManager = new RemoteMediaManager(
1187
+ fakeReceiveSlotManager,
1188
+ fakeMediaRequestManagers,
1189
+ config
1190
+ );
1191
+
1192
+ await remoteMediaManager.start();
1193
+
1194
+ resetHistory();
1195
+
1196
+ // switch the mock to now use csi2slotMappingAfterLayoutChange as we're about to change the layout
1197
+ csi2slotMapping = csi2slotMappingAfterLayoutChange;
1198
+
1199
+ // switch to "smallerLayout" layout that requires 2 less video slots and has 2 receive selected slots with same CSIs
1200
+ await remoteMediaManager.setLayout('layoutWithDuplicateCSIs');
1201
+
1202
+ // verify that the 2 member panes with duplicate CSI value of 456 have 2 separate receive slots allocated
1203
+ assert.equal(csi2slotMappingAfterLayoutChange[456].length, 2);
1204
+ assert.notEqual(
1205
+ csi2slotMappingAfterLayoutChange[456][0],
1206
+ csi2slotMappingAfterLayoutChange[456][1]
1207
+ );
1208
+
1209
+ // and that one of them is the same re-used slot from previous layout
1210
+ assert.isTrue(
1211
+ csi2slotMappingBeforeLayoutChange[456][0] === csi2slotMappingAfterLayoutChange[456][0] ||
1212
+ csi2slotMappingBeforeLayoutChange[456][0] === csi2slotMappingAfterLayoutChange[456][1]
1213
+ );
1214
+
1215
+ // and the other panes have 1 slot each
1216
+ assert.equal(csi2slotMappingAfterLayoutChange[123].length, 1);
1217
+ assert.equal(csi2slotMappingAfterLayoutChange[789].length, 1);
1218
+ });
1219
+ });
1220
+
1221
+ describe('media requests', () => {
1222
+ it('sends correct media requests when switching to a layout with receiver selected slots', async () => {
1223
+ const config = cloneDeep(DefaultTestConfiguration);
1224
+
1225
+ config.video.layouts.Stage.memberVideoPanes = [
1226
+ {id: 'stage-1', size: 'medium', csi: 11111},
1227
+ {id: 'stage-2', size: 'medium', csi: 22222},
1228
+ {id: 'stage-3', size: 'medium', csi: undefined},
1229
+ {id: 'stage-4', size: 'medium', csi: undefined},
1230
+ ];
1231
+ remoteMediaManager = new RemoteMediaManager(
1232
+ fakeReceiveSlotManager,
1233
+ fakeMediaRequestManagers,
1234
+ config
1235
+ );
1236
+
1237
+ await remoteMediaManager.start();
1238
+
1239
+ resetHistory();
1240
+
1241
+ // switch to "Stage" layout that has an active speaker group and 4 receiver selected slots
1242
+ // and a CSI set on 2 of them
1243
+ await remoteMediaManager.setLayout('Stage');
1244
+
1245
+ assert.callCount(fakeMediaRequestManagers.video.addRequest, 3);
1246
+ assert.calledWith(
1247
+ fakeMediaRequestManagers.video.addRequest,
1248
+ sinon.match({
1249
+ policyInfo: sinon.match({
1250
+ policy: 'active-speaker',
1251
+ priority: 255,
1252
+ }),
1253
+ receiveSlots: Array(6).fill(fakeVideoSlot),
1254
+ codecInfo: sinon.match({
1255
+ codec: 'h264',
1256
+ maxFs: 60,
1257
+ }),
1258
+ })
1259
+ );
1260
+ assert.calledWith(
1261
+ fakeMediaRequestManagers.video.addRequest,
1262
+ sinon.match({
1263
+ policyInfo: sinon.match({
1264
+ policy: 'receiver-selected',
1265
+ csi: 11111,
1266
+ }),
1267
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1268
+ codecInfo: sinon.match({
1269
+ codec: 'h264',
1270
+ maxFs: 3600,
1271
+ }),
1272
+ })
1273
+ );
1274
+ assert.calledWith(
1275
+ fakeMediaRequestManagers.video.addRequest,
1276
+ sinon.match({
1277
+ policyInfo: sinon.match({
1278
+ policy: 'receiver-selected',
1279
+ csi: 22222,
1280
+ }),
1281
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1282
+ codecInfo: sinon.match({
1283
+ codec: 'h264',
1284
+ maxFs: 3600,
1285
+ }),
1286
+ })
1287
+ );
1288
+ });
1289
+
1290
+ it('sends correct media requests when switching to a layout with multiple active-speaker groups', async () => {
1291
+ // start with "AllEqual" layout that needs just 9 video slots
1292
+ const config = cloneDeep(DefaultTestConfiguration);
1293
+
1294
+ config.video.initialLayoutId = 'AllEqual';
1295
+
1296
+ remoteMediaManager = new RemoteMediaManager(
1297
+ fakeReceiveSlotManager,
1298
+ fakeMediaRequestManagers,
1299
+ config
1300
+ );
1301
+
1302
+ const allEqualMediaRequestId = 'fake request id';
1303
+
1304
+ fakeMediaRequestManagers.video.addRequest.returns(allEqualMediaRequestId);
1305
+
1306
+ await remoteMediaManager.start();
1307
+
1308
+ resetHistory();
1309
+
1310
+ // switch to "OnePlusFive" layout that has 2 active speaker groups
1311
+ await remoteMediaManager.setLayout('OnePlusFive');
1312
+
1313
+ // check that the previous active speaker request for "AllEqual" group was cancelled
1314
+ assert.calledOnce(fakeMediaRequestManagers.video.cancelRequest);
1315
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, allEqualMediaRequestId);
1316
+
1317
+ // check that 2 correct active speaker media requests were sent out
1318
+ assert.callCount(fakeMediaRequestManagers.video.addRequest, 2);
1319
+ assert.calledWith(
1320
+ fakeMediaRequestManagers.video.addRequest,
1321
+ sinon.match({
1322
+ policyInfo: sinon.match({
1323
+ policy: 'active-speaker',
1324
+ priority: 255,
1325
+ }),
1326
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1327
+ codecInfo: sinon.match({
1328
+ codec: 'h264',
1329
+ maxFs: 8192,
1330
+ }),
1331
+ })
1332
+ );
1333
+ assert.calledWith(
1334
+ fakeMediaRequestManagers.video.addRequest,
1335
+ sinon.match({
1336
+ policyInfo: sinon.match({
1337
+ policy: 'active-speaker',
1338
+ priority: 254,
1339
+ }),
1340
+ receiveSlots: Array(5).fill(fakeVideoSlot),
1341
+ codecInfo: sinon.match({
1342
+ codec: 'h264',
1343
+ maxFs: 240,
1344
+ }),
1345
+ })
1346
+ );
1347
+ });
1348
+
1349
+ it('cancels all media requests for the previous layout when switching to a new one', async () => {
1350
+ // setup the initial layout to have screen share, active speaker groups and member video panes
1351
+ const config: Configuration = {
1352
+ audio: {
1353
+ numOfActiveSpeakerStreams: 0,
1354
+ numOfScreenShareStreams: 0,
1355
+ },
1356
+ video: {
1357
+ preferLiveVideo: true,
1358
+ initialLayoutId: 'initial',
1359
+ layouts: {
1360
+ initial: {
1361
+ screenShareVideo: {size: 'best'},
1362
+ activeSpeakerVideoPaneGroups: [
1363
+ {
1364
+ id: 'big',
1365
+ numPanes: 10,
1366
+ priority: 255,
1367
+ size: 'large',
1368
+ },
1369
+ {
1370
+ id: 'small',
1371
+ numPanes: 3,
1372
+ priority: 254,
1373
+ size: 'medium',
1374
+ },
1375
+ ],
1376
+ memberVideoPanes: [
1377
+ {id: 'pane 1', size: 'best', csi: 123},
1378
+ {id: 'pane 2', size: 'best', csi: 234},
1379
+ ],
1380
+ },
1381
+ other: {},
1382
+ },
1383
+ },
1384
+ };
1385
+
1386
+ remoteMediaManager = new RemoteMediaManager(
1387
+ fakeReceiveSlotManager,
1388
+ fakeMediaRequestManagers,
1389
+ config
1390
+ );
1391
+
1392
+ let activeSpeakerRequestCounter = 0;
1393
+ let receiverSelectedRequestCounter = 0;
1394
+
1395
+ // setup the mock for addRequest to return request ids that we want
1396
+ fakeMediaRequestManagers.video.addRequest.callsFake((mediaRequest) => {
1397
+ if (mediaRequest.policyInfo.policy === 'active-speaker') {
1398
+ activeSpeakerRequestCounter += 1;
1399
+
1400
+ return `active speaker request ${activeSpeakerRequestCounter}`;
1401
+ }
1402
+ receiverSelectedRequestCounter += 1;
1403
+
1404
+ return `receiver selected request ${receiverSelectedRequestCounter}`;
1405
+ });
1406
+ // setup the mock for screen share addRequest - this one should be called just once
1407
+ fakeMediaRequestManagers.screenShareVideo.addRequest.callsFake(() => {
1408
+ return 'video screen share request id';
1409
+ });
1410
+
1411
+ await remoteMediaManager.start();
1412
+
1413
+ assert.calledOnce(fakeMediaRequestManagers.screenShareVideo.addRequest);
1414
+
1415
+ resetHistory();
1416
+
1417
+ // switch to "other" layout
1418
+ await remoteMediaManager.setLayout('other');
1419
+
1420
+ // check that all the previous media requests for "initial" layout have been cancelled
1421
+ assert.callCount(fakeMediaRequestManagers.video.cancelRequest, 4);
1422
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, 'active speaker request 1');
1423
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, 'active speaker request 2');
1424
+ assert.calledWith(
1425
+ fakeMediaRequestManagers.video.cancelRequest,
1426
+ 'receiver selected request 1'
1427
+ );
1428
+ assert.calledWith(
1429
+ fakeMediaRequestManagers.video.cancelRequest,
1430
+ 'receiver selected request 2'
1431
+ );
1432
+ assert.calledOnce(fakeMediaRequestManagers.screenShareVideo.cancelRequest);
1433
+ assert.calledWith(
1434
+ fakeMediaRequestManagers.screenShareVideo.cancelRequest,
1435
+ 'video screen share request id'
1436
+ );
1437
+
1438
+ // new layout has no videos, so no new requests should be sent out
1439
+ assert.callCount(fakeMediaRequestManagers.video.addRequest, 0);
1440
+ });
1441
+
1442
+ it('sends media request for screen share if layout contains screen share', async () => {
1443
+ const allEqualMediaRequestId = 'fake request id';
1444
+
1445
+ fakeMediaRequestManagers.video.addRequest.returns(allEqualMediaRequestId);
1446
+
1447
+ await remoteMediaManager.start();
1448
+
1449
+ resetHistory();
1450
+
1451
+ // switch to a layout that contains a screen share video pane
1452
+ await remoteMediaManager.setLayout('ScreenShareView');
1453
+
1454
+ // check that a correct active speaker media request for screen share has been sent out
1455
+ assert.callCount(fakeMediaRequestManagers.screenShareVideo.addRequest, 1);
1456
+ assert.calledWith(
1457
+ fakeMediaRequestManagers.screenShareVideo.addRequest,
1458
+ sinon.match({
1459
+ policyInfo: sinon.match({
1460
+ policy: 'active-speaker',
1461
+ priority: 255,
1462
+ }),
1463
+ receiveSlots: [fakeScreenShareVideoSlot],
1464
+ codecInfo: sinon.match({
1465
+ codec: 'h264',
1466
+ maxFs: 3600,
1467
+ }),
1468
+ })
1469
+ );
1470
+ });
1471
+ });
1472
+ });
1473
+
1474
+ describe('setRemoteVideoCsi', () => {
1475
+ it('sends correct media requests', async () => {
1476
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1477
+
1478
+ await remoteMediaManager.start();
1479
+
1480
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1481
+ currentLayoutInfo = layoutInfo;
1482
+ });
1483
+ // switch to "Stage" layout which has some receiver selected slots
1484
+ await remoteMediaManager.setLayout('Stage');
1485
+ resetHistory();
1486
+
1487
+ assert.isNotNull(currentLayoutInfo);
1488
+
1489
+ if (currentLayoutInfo) {
1490
+ const fakeRequestId1 = 'fake request id 1';
1491
+ const fakeRequestId2 = 'fake request id 2';
1492
+
1493
+ fakeMediaRequestManagers.video.addRequest.returns(fakeRequestId1);
1494
+
1495
+ remoteMediaManager.setRemoteVideoCsi(currentLayoutInfo.memberVideoPanes['stage-1'], 1001);
1496
+
1497
+ // a new media request should have been sent out
1498
+ assert.calledOnce(fakeMediaRequestManagers.video.addRequest);
1499
+ assert.calledWith(
1500
+ fakeMediaRequestManagers.video.addRequest,
1501
+ sinon.match({
1502
+ policyInfo: sinon.match({
1503
+ policy: 'receiver-selected',
1504
+ csi: 1001,
1505
+ }),
1506
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1507
+ codecInfo: sinon.match({
1508
+ codec: 'h264',
1509
+ maxFs: 3600,
1510
+ }),
1511
+ })
1512
+ );
1513
+ assert.notCalled(fakeMediaRequestManagers.video.cancelRequest);
1514
+
1515
+ resetHistory();
1516
+
1517
+ // change the same video pane again
1518
+ remoteMediaManager.setRemoteVideoCsi(currentLayoutInfo.memberVideoPanes['stage-1'], 1002);
1519
+
1520
+ // a new media request should have been sent out
1521
+ assert.calledOnce(fakeMediaRequestManagers.video.addRequest);
1522
+ assert.calledWith(
1523
+ fakeMediaRequestManagers.video.addRequest,
1524
+ sinon.match({
1525
+ policyInfo: sinon.match({
1526
+ policy: 'receiver-selected',
1527
+ csi: 1002,
1528
+ }),
1529
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1530
+ codecInfo: sinon.match({
1531
+ codec: 'h264',
1532
+ maxFs: 3600,
1533
+ }),
1534
+ })
1535
+ );
1536
+ // and previous one should have been cancelled
1537
+ assert.calledOnce(fakeMediaRequestManagers.video.cancelRequest);
1538
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, fakeRequestId1);
1539
+
1540
+ resetHistory();
1541
+
1542
+ fakeMediaRequestManagers.video.addRequest.returns(fakeRequestId2);
1543
+
1544
+ // now change some other video pane
1545
+ remoteMediaManager.setRemoteVideoCsi(currentLayoutInfo.memberVideoPanes['stage-3'], 2001);
1546
+
1547
+ // a new media request should have been sent out
1548
+ assert.calledOnce(fakeMediaRequestManagers.video.addRequest);
1549
+ assert.calledWith(
1550
+ fakeMediaRequestManagers.video.addRequest,
1551
+ sinon.match({
1552
+ policyInfo: sinon.match({
1553
+ policy: 'receiver-selected',
1554
+ csi: 2001,
1555
+ }),
1556
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1557
+ codecInfo: sinon.match({
1558
+ codec: 'h264',
1559
+ maxFs: 3600,
1560
+ }),
1561
+ })
1562
+ );
1563
+ // nothing should have been cancelled
1564
+ assert.notCalled(fakeMediaRequestManagers.video.cancelRequest);
1565
+
1566
+ resetHistory();
1567
+
1568
+ // now set CSI back to undefined
1569
+ remoteMediaManager.setRemoteVideoCsi(
1570
+ currentLayoutInfo.memberVideoPanes['stage-3'],
1571
+ undefined
1572
+ );
1573
+
1574
+ // no new media request should have been sent out
1575
+ assert.notCalled(fakeMediaRequestManagers.video.addRequest);
1576
+ // and previous one should have been cancelled
1577
+ assert.calledOnce(fakeMediaRequestManagers.video.cancelRequest);
1578
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, fakeRequestId2);
1579
+ }
1580
+ });
1581
+ });
1582
+
1583
+ describe('addMemberVideoPane()', () => {
1584
+ it('fails if there is no current layout', () => {
1585
+ // we haven't called start() so there is no layout set, yet
1586
+ assert.isRejected(
1587
+ remoteMediaManager.addMemberVideoPane({id: 'newPane', size: 'best', csi: 54321})
1588
+ );
1589
+ });
1590
+
1591
+ it('fails if called with a duplicate paneId', async () => {
1592
+ await remoteMediaManager.start();
1593
+ await remoteMediaManager.setLayout('Stage');
1594
+
1595
+ assert.isRejected(
1596
+ remoteMediaManager.addMemberVideoPane({id: 'stage-3', size: 'best', csi: 54321})
1597
+ );
1598
+ });
1599
+
1600
+ it('works as expected when called with a CSI value', async () => {
1601
+ await remoteMediaManager.start();
1602
+ await remoteMediaManager.setLayout('Stage');
1603
+
1604
+ resetHistory();
1605
+
1606
+ await remoteMediaManager.addMemberVideoPane({id: 'newPane', size: 'best', csi: 54321});
1607
+
1608
+ // new slot should be allocated
1609
+ assert.calledOnce(fakeReceiveSlotManager.allocateSlot);
1610
+ assert.calledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
1611
+
1612
+ // and a media request sent out
1613
+ assert.calledOnce(fakeMediaRequestManagers.video.addRequest);
1614
+ assert.calledWith(
1615
+ fakeMediaRequestManagers.video.addRequest,
1616
+ sinon.match({
1617
+ policyInfo: sinon.match({
1618
+ policy: 'receiver-selected',
1619
+ csi: 54321,
1620
+ }),
1621
+ receiveSlots: Array(1).fill(fakeVideoSlot),
1622
+ codecInfo: sinon.match({
1623
+ codec: 'h264',
1624
+ maxFs: 8192,
1625
+ }),
1626
+ })
1627
+ );
1628
+ });
1629
+ it('works as expected when called without a CSI value', async () => {
1630
+ await remoteMediaManager.start();
1631
+ await remoteMediaManager.setLayout('Stage');
1632
+
1633
+ resetHistory();
1634
+
1635
+ await remoteMediaManager.addMemberVideoPane({id: 'newPane', size: 'best'});
1636
+
1637
+ // new slot should be allocated
1638
+ assert.calledOnce(fakeReceiveSlotManager.allocateSlot);
1639
+ assert.calledWith(fakeReceiveSlotManager.allocateSlot, MediaType.VideoMain);
1640
+
1641
+ // but no media requests sent out
1642
+ assert.notCalled(fakeMediaRequestManagers.video.addRequest);
1643
+ });
1644
+ });
1645
+
1646
+ describe('removeMemberVideoPane()', () => {
1647
+ it('fails if there is no current layout', () => {
1648
+ // we haven't called start() so there is no layout set, yet
1649
+ assert.isRejected(remoteMediaManager.removeMemberVideoPane('newPane'));
1650
+ });
1651
+
1652
+ it('does nothing when called for a pane not in the current layout', async () => {
1653
+ await remoteMediaManager.start();
1654
+ await remoteMediaManager.setLayout('Stage');
1655
+
1656
+ resetHistory();
1657
+
1658
+ await remoteMediaManager.removeMemberVideoPane('some pane');
1659
+
1660
+ assert.notCalled(fakeReceiveSlotManager.releaseSlot);
1661
+ assert.notCalled(fakeMediaRequestManagers.video.cancelRequest);
1662
+ });
1663
+
1664
+ it('works as expected', async () => {
1665
+ await remoteMediaManager.start();
1666
+ await remoteMediaManager.setLayout('Stage');
1667
+
1668
+ const fakeNewSlot = new FakeSlot(MediaType.VideoMain, 'fake video slot');
1669
+ const fakeRequestId = 'fake request id';
1670
+
1671
+ fakeReceiveSlotManager.allocateSlot.resolves(fakeNewSlot);
1672
+ fakeMediaRequestManagers.video.addRequest.returns(fakeRequestId);
1673
+
1674
+ // first, add some pane
1675
+ await remoteMediaManager.addMemberVideoPane({id: 'newPane', size: 'best', csi: 54321});
1676
+
1677
+ resetHistory();
1678
+
1679
+ // now remove it
1680
+ await remoteMediaManager.removeMemberVideoPane('newPane');
1681
+
1682
+ // slot should be released
1683
+ assert.calledOnce(fakeReceiveSlotManager.releaseSlot);
1684
+ assert.calledWith(fakeReceiveSlotManager.releaseSlot, fakeNewSlot);
1685
+
1686
+ // and a media request cancelled
1687
+ assert.calledOnce(fakeMediaRequestManagers.video.cancelRequest);
1688
+ assert.calledWith(fakeMediaRequestManagers.video.cancelRequest, fakeRequestId);
1689
+ });
1690
+ });
1691
+
1692
+ describe('pinActiveSpeakerVideoPane() and isPinned()', () => {
1693
+ it('throws if called on a pane not belonging to an active speaker group', async () => {
1694
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1695
+
1696
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1697
+ currentLayoutInfo = layoutInfo;
1698
+ });
1699
+
1700
+ await remoteMediaManager.start();
1701
+ await remoteMediaManager.setLayout('Stage');
1702
+
1703
+ assert.isNotNull(currentLayoutInfo);
1704
+
1705
+ if (currentLayoutInfo) {
1706
+ const remoteVideo = currentLayoutInfo.memberVideoPanes['stage-1'];
1707
+
1708
+ assert.throws(() => remoteMediaManager.pinActiveSpeakerVideoPane(remoteVideo));
1709
+ assert.throws(() => remoteMediaManager.isPinned(remoteVideo));
1710
+ }
1711
+ });
1712
+
1713
+ it('calls pin()/isPinned() on the correct remote media group', async () => {
1714
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1715
+ let pinStub;
1716
+ let isPinnedStub;
1717
+
1718
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1719
+ currentLayoutInfo = layoutInfo;
1720
+ pinStub = sinon.stub(layoutInfo.activeSpeakerVideoPanes.main, 'pin');
1721
+ isPinnedStub = sinon.stub(layoutInfo.activeSpeakerVideoPanes.main, 'isPinned');
1722
+ });
1723
+
1724
+ await remoteMediaManager.start();
1725
+
1726
+ assert.isNotNull(currentLayoutInfo);
1727
+
1728
+ if (currentLayoutInfo) {
1729
+ const remoteVideo = currentLayoutInfo.activeSpeakerVideoPanes.main.getRemoteMedia()[0];
1730
+
1731
+ // first test pinActiveSpeakerVideoPane()
1732
+ remoteMediaManager.pinActiveSpeakerVideoPane(remoteVideo);
1733
+
1734
+ assert.calledOnce(pinStub);
1735
+ assert.calledWith(pinStub, remoteVideo, undefined);
1736
+
1737
+ // now test isPinned()
1738
+ remoteMediaManager.isPinned(remoteVideo);
1739
+
1740
+ assert.calledOnce(isPinnedStub);
1741
+ assert.calledWith(isPinnedStub, remoteVideo);
1742
+ }
1743
+ });
1744
+ });
1745
+
1746
+ describe('unpinActiveSpeakerVideoPane', () => {
1747
+ it('throws if called on a remote media instance that was not pinned', async () => {
1748
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1749
+
1750
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1751
+ currentLayoutInfo = layoutInfo;
1752
+ });
1753
+
1754
+ await remoteMediaManager.start();
1755
+
1756
+ assert.isNotNull(currentLayoutInfo);
1757
+
1758
+ if (currentLayoutInfo) {
1759
+ const remoteVideoToUnPin =
1760
+ currentLayoutInfo.activeSpeakerVideoPanes.main.getRemoteMedia('unpinned')[0];
1761
+
1762
+ assert.throws(() => remoteMediaManager.unpinActiveSpeakerVideoPane(remoteVideoToUnPin));
1763
+ }
1764
+ });
1765
+
1766
+ it('calls unpin() on the correct remote media group', async () => {
1767
+ let currentLayoutInfo: VideoLayoutChangedEventData | null = null;
1768
+ let unpinStub;
1769
+
1770
+ remoteMediaManager.on(Event.VideoLayoutChanged, (layoutInfo: VideoLayoutChangedEventData) => {
1771
+ currentLayoutInfo = layoutInfo;
1772
+ unpinStub = sinon.stub(layoutInfo.activeSpeakerVideoPanes.main, 'unpin');
1773
+ });
1774
+
1775
+ await remoteMediaManager.start();
1776
+
1777
+ assert.isNotNull(currentLayoutInfo);
1778
+
1779
+ if (currentLayoutInfo) {
1780
+ const remoteVideo = currentLayoutInfo.activeSpeakerVideoPanes.main.getRemoteMedia()[0];
1781
+
1782
+ // first we need to pin it
1783
+ remoteMediaManager.pinActiveSpeakerVideoPane(remoteVideo, 99999);
1784
+
1785
+ // now we can unpin it
1786
+ remoteMediaManager.unpinActiveSpeakerVideoPane(remoteVideo);
1787
+
1788
+ assert.calledOnce(unpinStub);
1789
+ assert.calledWith(unpinStub, remoteVideo);
1790
+ }
1791
+ });
1792
+ });
1793
+ });