@webex/plugin-meetings 3.12.0-next.9 → 3.12.0-next.90

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 (213) hide show
  1. package/AGENTS.md +9 -0
  2. package/dist/aiEnableRequest/index.js +15 -2
  3. package/dist/aiEnableRequest/index.js.map +1 -1
  4. package/dist/breakouts/breakout.js +8 -3
  5. package/dist/breakouts/breakout.js.map +1 -1
  6. package/dist/breakouts/index.js +26 -2
  7. package/dist/breakouts/index.js.map +1 -1
  8. package/dist/config.js +2 -2
  9. package/dist/config.js.map +1 -1
  10. package/dist/constants.js +35 -9
  11. package/dist/constants.js.map +1 -1
  12. package/dist/controls-options-manager/constants.js +11 -1
  13. package/dist/controls-options-manager/constants.js.map +1 -1
  14. package/dist/controls-options-manager/index.js +67 -29
  15. package/dist/controls-options-manager/index.js.map +1 -1
  16. package/dist/controls-options-manager/util.js +91 -0
  17. package/dist/controls-options-manager/util.js.map +1 -1
  18. package/dist/hashTree/constants.js +13 -1
  19. package/dist/hashTree/constants.js.map +1 -1
  20. package/dist/hashTree/hashTreeParser.js +880 -382
  21. package/dist/hashTree/hashTreeParser.js.map +1 -1
  22. package/dist/hashTree/utils.js +42 -0
  23. package/dist/hashTree/utils.js.map +1 -1
  24. package/dist/index.js +7 -0
  25. package/dist/index.js.map +1 -1
  26. package/dist/interceptors/dataChannelAuthToken.js +75 -15
  27. package/dist/interceptors/dataChannelAuthToken.js.map +1 -1
  28. package/dist/interceptors/locusRetry.js +23 -8
  29. package/dist/interceptors/locusRetry.js.map +1 -1
  30. package/dist/interpretation/index.js +10 -1
  31. package/dist/interpretation/index.js.map +1 -1
  32. package/dist/interpretation/interpretation.types.js +7 -0
  33. package/dist/interpretation/interpretation.types.js.map +1 -0
  34. package/dist/interpretation/siLanguage.js +1 -1
  35. package/dist/locus-info/controlsUtils.js +4 -1
  36. package/dist/locus-info/controlsUtils.js.map +1 -1
  37. package/dist/locus-info/index.js +303 -88
  38. package/dist/locus-info/index.js.map +1 -1
  39. package/dist/locus-info/infoUtils.js +36 -5
  40. package/dist/locus-info/infoUtils.js.map +1 -1
  41. package/dist/locus-info/types.js +19 -0
  42. package/dist/locus-info/types.js.map +1 -1
  43. package/dist/media/index.js +3 -1
  44. package/dist/media/index.js.map +1 -1
  45. package/dist/media/properties.js +1 -0
  46. package/dist/media/properties.js.map +1 -1
  47. package/dist/meeting/in-meeting-actions.js +5 -1
  48. package/dist/meeting/in-meeting-actions.js.map +1 -1
  49. package/dist/meeting/index.js +1233 -793
  50. package/dist/meeting/index.js.map +1 -1
  51. package/dist/meeting/locusMediaRequest.js +229 -82
  52. package/dist/meeting/locusMediaRequest.js.map +1 -1
  53. package/dist/meeting/muteState.js +10 -1
  54. package/dist/meeting/muteState.js.map +1 -1
  55. package/dist/meeting/request.js +5 -2
  56. package/dist/meeting/request.js.map +1 -1
  57. package/dist/meeting/util.js +35 -2
  58. package/dist/meeting/util.js.map +1 -1
  59. package/dist/meeting-info/meeting-info-v2.js +2 -2
  60. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  61. package/dist/meetings/index.js +231 -118
  62. package/dist/meetings/index.js.map +1 -1
  63. package/dist/meetings/meetings.types.js +6 -1
  64. package/dist/meetings/meetings.types.js.map +1 -1
  65. package/dist/meetings/request.js +39 -0
  66. package/dist/meetings/request.js.map +1 -1
  67. package/dist/meetings/util.js +79 -5
  68. package/dist/meetings/util.js.map +1 -1
  69. package/dist/member/index.js +10 -0
  70. package/dist/member/index.js.map +1 -1
  71. package/dist/member/types.js.map +1 -1
  72. package/dist/member/util.js +30 -0
  73. package/dist/member/util.js.map +1 -1
  74. package/dist/members/index.js +101 -43
  75. package/dist/members/index.js.map +1 -1
  76. package/dist/members/util.js +8 -1
  77. package/dist/members/util.js.map +1 -1
  78. package/dist/metrics/constants.js +5 -1
  79. package/dist/metrics/constants.js.map +1 -1
  80. package/dist/multistream/codec/constants.js +63 -0
  81. package/dist/multistream/codec/constants.js.map +1 -0
  82. package/dist/multistream/mediaRequestManager.js +62 -15
  83. package/dist/multistream/mediaRequestManager.js.map +1 -1
  84. package/dist/multistream/receiveSlot.js +9 -0
  85. package/dist/multistream/receiveSlot.js.map +1 -1
  86. package/dist/reachability/index.js +10 -13
  87. package/dist/reachability/index.js.map +1 -1
  88. package/dist/reachability/reachability.types.js +2 -0
  89. package/dist/reachability/reachability.types.js.map +1 -1
  90. package/dist/reachability/util.js +45 -0
  91. package/dist/reachability/util.js.map +1 -1
  92. package/dist/reactions/reactions.type.js.map +1 -1
  93. package/dist/recording-controller/index.js +1 -3
  94. package/dist/recording-controller/index.js.map +1 -1
  95. package/dist/types/config.d.ts +2 -2
  96. package/dist/types/constants.d.ts +12 -1
  97. package/dist/types/controls-options-manager/constants.d.ts +6 -1
  98. package/dist/types/controls-options-manager/index.d.ts +16 -0
  99. package/dist/types/hashTree/constants.d.ts +2 -0
  100. package/dist/types/hashTree/hashTreeParser.d.ts +146 -17
  101. package/dist/types/hashTree/utils.d.ts +18 -0
  102. package/dist/types/index.d.ts +3 -0
  103. package/dist/types/interceptors/locusRetry.d.ts +4 -4
  104. package/dist/types/interpretation/interpretation.types.d.ts +10 -0
  105. package/dist/types/locus-info/index.d.ts +50 -6
  106. package/dist/types/locus-info/infoUtils.d.ts +8 -0
  107. package/dist/types/locus-info/types.d.ts +21 -1
  108. package/dist/types/media/properties.d.ts +1 -0
  109. package/dist/types/meeting/in-meeting-actions.d.ts +4 -0
  110. package/dist/types/meeting/index.d.ts +94 -7
  111. package/dist/types/meeting/locusMediaRequest.d.ts +12 -0
  112. package/dist/types/meeting/request.d.ts +1 -0
  113. package/dist/types/meeting/util.d.ts +9 -0
  114. package/dist/types/meetings/index.d.ts +30 -18
  115. package/dist/types/meetings/meetings.types.d.ts +15 -0
  116. package/dist/types/meetings/request.d.ts +14 -0
  117. package/dist/types/member/index.d.ts +1 -0
  118. package/dist/types/member/types.d.ts +1 -0
  119. package/dist/types/member/util.d.ts +9 -0
  120. package/dist/types/members/index.d.ts +15 -2
  121. package/dist/types/members/util.d.ts +1 -5
  122. package/dist/types/metrics/constants.d.ts +4 -0
  123. package/dist/types/multistream/codec/constants.d.ts +7 -0
  124. package/dist/types/multistream/mediaRequestManager.d.ts +22 -5
  125. package/dist/types/reachability/index.d.ts +1 -1
  126. package/dist/types/reachability/reachability.types.d.ts +7 -0
  127. package/dist/types/reachability/util.d.ts +17 -0
  128. package/dist/types/reactions/reactions.type.d.ts +3 -0
  129. package/dist/webinar/index.js +305 -159
  130. package/dist/webinar/index.js.map +1 -1
  131. package/package.json +22 -22
  132. package/src/aiEnableRequest/index.ts +16 -0
  133. package/src/breakouts/breakout.ts +3 -1
  134. package/src/breakouts/index.ts +31 -0
  135. package/src/config.ts +2 -2
  136. package/src/constants.ts +20 -3
  137. package/src/controls-options-manager/constants.ts +14 -1
  138. package/src/controls-options-manager/index.ts +87 -28
  139. package/src/controls-options-manager/util.ts +81 -1
  140. package/src/hashTree/constants.ts +16 -0
  141. package/src/hashTree/hashTreeParser.ts +580 -196
  142. package/src/hashTree/utils.ts +36 -0
  143. package/src/index.ts +6 -0
  144. package/src/interceptors/dataChannelAuthToken.ts +88 -12
  145. package/src/interceptors/locusRetry.ts +25 -4
  146. package/src/interpretation/index.ts +27 -9
  147. package/src/interpretation/interpretation.types.ts +11 -0
  148. package/src/locus-info/controlsUtils.ts +3 -1
  149. package/src/locus-info/index.ts +304 -100
  150. package/src/locus-info/infoUtils.ts +41 -6
  151. package/src/locus-info/types.ts +25 -1
  152. package/src/media/index.ts +3 -0
  153. package/src/media/properties.ts +1 -0
  154. package/src/meeting/in-meeting-actions.ts +8 -0
  155. package/src/meeting/index.ts +494 -67
  156. package/src/meeting/locusMediaRequest.ts +106 -6
  157. package/src/meeting/muteState.ts +10 -1
  158. package/src/meeting/request.ts +11 -0
  159. package/src/meeting/util.ts +47 -5
  160. package/src/meeting-info/meeting-info-v2.ts +4 -2
  161. package/src/meetings/index.ts +133 -78
  162. package/src/meetings/meetings.types.ts +19 -0
  163. package/src/meetings/request.ts +43 -0
  164. package/src/meetings/util.ts +97 -1
  165. package/src/member/index.ts +10 -0
  166. package/src/member/types.ts +1 -0
  167. package/src/member/util.ts +29 -0
  168. package/src/members/index.ts +70 -7
  169. package/src/members/util.ts +12 -4
  170. package/src/metrics/constants.ts +4 -0
  171. package/src/multistream/codec/constants.ts +58 -0
  172. package/src/multistream/mediaRequestManager.ts +119 -28
  173. package/src/multistream/receiveSlot.ts +18 -0
  174. package/src/reachability/index.ts +19 -15
  175. package/src/reachability/reachability.types.ts +11 -0
  176. package/src/reachability/util.ts +36 -0
  177. package/src/reactions/reactions.type.ts +3 -0
  178. package/src/recording-controller/index.ts +1 -2
  179. package/src/webinar/index.ts +214 -36
  180. package/test/unit/spec/aiEnableRequest/index.ts +86 -0
  181. package/test/unit/spec/breakouts/breakout.ts +9 -3
  182. package/test/unit/spec/breakouts/index.ts +49 -0
  183. package/test/unit/spec/controls-options-manager/index.js +244 -29
  184. package/test/unit/spec/controls-options-manager/util.js +165 -0
  185. package/test/unit/spec/hashTree/hashTreeParser.ts +1838 -180
  186. package/test/unit/spec/hashTree/utils.ts +125 -1
  187. package/test/unit/spec/interceptors/dataChannelAuthToken.ts +196 -0
  188. package/test/unit/spec/interceptors/locusRetry.ts +205 -4
  189. package/test/unit/spec/interpretation/index.ts +26 -4
  190. package/test/unit/spec/locus-info/controlsUtils.js +172 -57
  191. package/test/unit/spec/locus-info/index.js +499 -81
  192. package/test/unit/spec/locus-info/infoUtils.js +184 -1
  193. package/test/unit/spec/media/index.ts +31 -0
  194. package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
  195. package/test/unit/spec/meeting/index.js +1615 -42
  196. package/test/unit/spec/meeting/locusMediaRequest.ts +302 -4
  197. package/test/unit/spec/meeting/muteState.js +81 -0
  198. package/test/unit/spec/meeting/request.js +12 -0
  199. package/test/unit/spec/meeting/utils.js +157 -1
  200. package/test/unit/spec/meeting-info/meetinginfov2.js +19 -10
  201. package/test/unit/spec/meetings/index.js +363 -36
  202. package/test/unit/spec/meetings/request.js +141 -0
  203. package/test/unit/spec/meetings/utils.js +189 -0
  204. package/test/unit/spec/member/index.js +7 -0
  205. package/test/unit/spec/member/util.js +89 -0
  206. package/test/unit/spec/members/index.js +216 -0
  207. package/test/unit/spec/members/request.js +37 -1
  208. package/test/unit/spec/members/utils.js +63 -0
  209. package/test/unit/spec/multistream/mediaRequestManager.ts +501 -37
  210. package/test/unit/spec/reachability/index.ts +59 -46
  211. package/test/unit/spec/reachability/util.ts +31 -0
  212. package/test/unit/spec/recording-controller/index.js +9 -8
  213. package/test/unit/spec/webinar/index.ts +329 -28
@@ -140,6 +140,9 @@ const MemberUtil = {
140
140
  isPresenterAssignmentProhibited: (participant: Participant) =>
141
141
  participant && participant.presenterAssignmentNotAllowed,
142
142
 
143
+ isAttendeeAssignmentProhibited: (participant: Participant) =>
144
+ !!(participant && participant.attendeeAssignmentNotAllowed),
145
+
143
146
  /**
144
147
  * checks to see if the participant id is the same as the passed id
145
148
  * there are multiple ids that can be used
@@ -436,5 +439,31 @@ const MemberUtil = {
436
439
 
437
440
  return participantUrl;
438
441
  },
442
+
443
+ /**
444
+ * Collects all CSIs reported for a participant across all their devices,
445
+ * looking at both the top-level `csis` array and any `mediaSessions[].csi` values.
446
+ *
447
+ * @param {Object} participant - The locus participant object.
448
+ * @returns {Array<number>} unique CSIs for this participant
449
+ */
450
+ extractCsis: (participant: Participant): number[] => {
451
+ const csis = new Set<number>();
452
+
453
+ participant?.devices?.forEach((device: any) => {
454
+ device?.csis?.forEach((csi: number) => {
455
+ if (typeof csi === 'number') {
456
+ csis.add(csi);
457
+ }
458
+ });
459
+ device?.mediaSessions?.forEach((mediaSession: any) => {
460
+ if (typeof mediaSession?.csi === 'number') {
461
+ csis.add(mediaSession.csi);
462
+ }
463
+ });
464
+ });
465
+
466
+ return Array.from(csis);
467
+ },
439
468
  };
440
469
  export default MemberUtil;
@@ -1,7 +1,7 @@
1
1
  /*!
2
2
  * Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
3
3
  */
4
- import {get, isEmpty, set} from 'lodash';
4
+ import {get, isEmpty, isEqual, set} from 'lodash';
5
5
  // @ts-ignore
6
6
  import {StatelessWebexPlugin} from '@webex/webex-core';
7
7
 
@@ -15,6 +15,7 @@ import {
15
15
  } from '../constants';
16
16
  import Trigger from '../common/events/trigger-proxy';
17
17
  import Member from '../member';
18
+ import MemberUtil from '../member/util';
18
19
  import LoggerProxy from '../common/logs/logger-proxy';
19
20
  import ParameterError from '../common/errors/parameter';
20
21
  import {
@@ -99,6 +100,15 @@ export default class Members extends StatelessWebexPlugin {
99
100
  selfId: any;
100
101
  type: any;
101
102
 
103
+ /**
104
+ * Map of CSI -> memberId that previously used the CSI in a Locus update.
105
+ * Keyed by CSI so findMemberByCsi can resolve it in O(1).
106
+ * Kept here (rather than on each Member) so it survives members being removed
107
+ * and re-added (e.g. when entering/leaving a breakout session).
108
+ * @private
109
+ */
110
+ private memberIdByHistoryCsi: Map<number, string> = new Map();
111
+
102
112
  namespace = MEETINGS;
103
113
 
104
114
  /**
@@ -315,6 +325,14 @@ export default class Members extends StatelessWebexPlugin {
315
325
  * @memberof Members
316
326
  */
317
327
  clearMembers() {
328
+ // capture CSIs of all current members before reset so findMemberByCsi can
329
+ // still resolve them if a member is later re-added (e.g. when leaving a
330
+ // breakout session)
331
+ Object.values(this.membersCollection.getAll()).forEach((member) => {
332
+ MemberUtil.extractCsis(member.participant).forEach((csi) =>
333
+ this.memberIdByHistoryCsi.set(csi, member.id)
334
+ );
335
+ });
318
336
  this.membersCollection.reset();
319
337
  Trigger.trigger(
320
338
  this,
@@ -569,6 +587,15 @@ export default class Members extends StatelessWebexPlugin {
569
587
  */
570
588
  private removeMembers(removedMembers: Array<string>) {
571
589
  removedMembers.forEach((memberId) => {
590
+ // capture CSIs before removal so findMemberByCsi can still resolve them
591
+ // if the member is later re-added (e.g. when leaving a breakout session)
592
+ // with a participant payload that no longer contains the original CSIs
593
+ const existingMember = this.membersCollection.get(memberId);
594
+ if (existingMember) {
595
+ MemberUtil.extractCsis(existingMember.participant).forEach((csi) =>
596
+ this.memberIdByHistoryCsi.set(csi, memberId)
597
+ );
598
+ }
572
599
  this.membersCollection.remove(memberId);
573
600
  });
574
601
  }
@@ -598,6 +625,19 @@ export default class Members extends StatelessWebexPlugin {
598
625
  set(member, prop, existingValue);
599
626
  }
600
627
  });
628
+
629
+ // remember CSIs the member used previously so findMemberByCsi can still
630
+ // resolve a CSI even after participant.devices[].csis no longer contains it.
631
+ // Skip re-extracting when devices haven't changed - existing history already covers them.
632
+ const devicesUnchanged = isEqual(
633
+ existingMember.participant?.devices,
634
+ member.participant?.devices
635
+ );
636
+ if (!devicesUnchanged) {
637
+ MemberUtil.extractCsis(existingMember.participant).forEach((csi) =>
638
+ this.memberIdByHistoryCsi.set(csi, member.id)
639
+ );
640
+ }
601
641
  }
602
642
  }
603
643
  this.membersCollection.set(member.id, member);
@@ -1096,12 +1136,16 @@ export default class Members extends StatelessWebexPlugin {
1096
1136
  * Transfers the host to another member
1097
1137
  * @param {String} memberId
1098
1138
  * @param {boolean} [moderator] default true
1139
+ * @param {String} [breakoutLocusUrl] when provided, the request is sent against this locus url
1140
+ * (i.e. the breakout session's locus) instead of the main session's locus url
1099
1141
  * @returns {Promise}
1100
1142
  * @public
1101
1143
  * @memberof Members
1102
1144
  */
1103
- public transferHostToMember(memberId: string, moderator = true) {
1104
- if (!this.locusUrl) {
1145
+ public transferHostToMember(memberId: string, moderator = true, breakoutLocusUrl = '') {
1146
+ const locusUrl = breakoutLocusUrl || this.locusUrl;
1147
+
1148
+ if (!locusUrl) {
1105
1149
  return Promise.reject(
1106
1150
  new ParameterError(
1107
1151
  'The associated locus url for this meetings members object must be defined.'
@@ -1113,11 +1157,14 @@ export default class Members extends StatelessWebexPlugin {
1113
1157
  new ParameterError('The member id must be defined to transfer host to the member.')
1114
1158
  );
1115
1159
  }
1116
- const options = MembersUtil.generateTransferHostMemberOptions(
1160
+ const options: Record<string, any> = MembersUtil.generateTransferHostMemberOptions(
1117
1161
  memberId,
1118
1162
  moderator,
1119
- this.locusUrl
1163
+ locusUrl
1120
1164
  );
1165
+ if (breakoutLocusUrl && breakoutLocusUrl !== this.locusUrl) {
1166
+ options.authorizingLocusUrl = this.locusUrl;
1167
+ }
1121
1168
 
1122
1169
  return this.membersRequest.transferHostToMember(options);
1123
1170
  }
@@ -1161,18 +1208,34 @@ export default class Members extends StatelessWebexPlugin {
1161
1208
  );
1162
1209
  }
1163
1210
 
1164
- /** Finds a member that has any device with a csi matching provided value
1211
+ /** Finds a member that has any device with a csi matching provided value.
1212
+ * Falls back to the `memberIdByHistoryCsi` map so that a CSI a member used in
1213
+ * a previous Locus update can still be resolved even if it is no longer
1214
+ * present in `participant.devices[].csis`.
1165
1215
  *
1166
1216
  * @param {number} csi
1167
1217
  * @returns {Member}
1168
1218
  */
1169
1219
  findMemberByCsi(csi) {
1170
- return Object.values(this.membersCollection.getAll()).find((member) =>
1220
+ const members = Object.values(this.membersCollection.getAll());
1221
+
1222
+ const currentMatch = members.find((member) =>
1171
1223
  // @ts-ignore
1172
1224
  member.participant?.devices?.find((device) =>
1173
1225
  device.csis?.find((memberCsi) => memberCsi === csi)
1174
1226
  )
1175
1227
  );
1228
+
1229
+ if (currentMatch) {
1230
+ return currentMatch;
1231
+ }
1232
+
1233
+ const historyMemberId = this.memberIdByHistoryCsi.get(csi);
1234
+ if (historyMemberId) {
1235
+ return this.membersCollection.get(historyMemberId);
1236
+ }
1237
+
1238
+ return undefined;
1176
1239
  }
1177
1240
 
1178
1241
  /**
@@ -13,7 +13,7 @@ import {
13
13
  ALIAS,
14
14
  } from '../constants';
15
15
 
16
- import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape} from './types';
16
+ import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape, ServerRoles} from './types';
17
17
  import {Invitee} from '../meeting/type';
18
18
 
19
19
  const MembersUtil = {
@@ -330,12 +330,20 @@ const MembersUtil = {
330
330
  };
331
331
  },
332
332
 
333
- getTransferHostToMemberRequestParams: (options) => {
334
- const body = {
333
+ getTransferHostToMemberRequestParams: (options: any) => {
334
+ const body: Record<string, any> = {
335
335
  role: {
336
- moderator: options.moderator,
336
+ roles: [
337
+ {
338
+ hasRole: true,
339
+ type: ServerRoles.Moderator,
340
+ },
341
+ ],
337
342
  },
338
343
  };
344
+ if (options.authorizingLocusUrl) {
345
+ body.authorizingLocusUrl = options.authorizingLocusUrl;
346
+ }
339
347
  const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
340
348
 
341
349
  return {
@@ -11,6 +11,7 @@ const BEHAVIORAL_METRICS = {
11
11
  ADD_MEDIA_SUCCESS: 'js_sdk_add_media_success',
12
12
  ADD_MEDIA_FAILURE: 'js_sdk_add_media_failures',
13
13
  ADD_MEDIA_RETRY: 'js_sdk_add_media_retry',
14
+ LOCUS_MEDIA_REQUEST_RETRY: 'js_sdk_locus_media_request_retry',
14
15
  ROAP_MERCURY_EVENT_RECEIVED: 'js_sdk_roap_mercury_received',
15
16
  CONNECTION_SUCCESS: 'js_sdk_connection_success',
16
17
  CONNECTION_FAILURE: 'js_sdk_connection_failures',
@@ -96,6 +97,9 @@ const BEHAVIORAL_METRICS = {
96
97
  SET_CUSTOM_CODEC_PARAMETERS_USED: 'js_sdk_set_custom_codec_parameters_used',
97
98
  MARK_CUSTOM_CODEC_PARAMETERS_FOR_DELETION_USED:
98
99
  'js_sdk_mark_custom_codec_parameters_for_deletion_used',
100
+ HASH_TREE_SYNC_FAILURE: 'js_sdk_hash_tree_sync_failure',
101
+ HASH_TREE_HEARTBEAT_WATCHDOG_EXPIRED: 'js_sdk_hash_tree_heartbeat_watchdog_expired',
102
+ HASH_TREE_EMPTY_LOCUS_STATE_ELEMENTS: 'js_sdk_hash_tree_empty_locus_state_elements',
99
103
  };
100
104
 
101
105
  export {BEHAVIORAL_METRICS as default};
@@ -0,0 +1,58 @@
1
+ import {AV1EncodingParams, SupportedResolution} from '@webex/internal-media-core';
2
+
3
+ export const AV1_CODEC_PARAMETERS: Record<SupportedResolution, AV1EncodingParams> = {
4
+ '90p': {
5
+ levelIdx: 0,
6
+ tier: 0,
7
+ maxWidth: 160,
8
+ maxHeight: 90,
9
+ maxPicSize: 147_456,
10
+ maxDecodeRate: 5_529_600,
11
+ },
12
+ '180p': {
13
+ levelIdx: 0,
14
+ tier: 0,
15
+ maxWidth: 320,
16
+ maxHeight: 180,
17
+ maxPicSize: 147_456,
18
+ maxDecodeRate: 5_529_600,
19
+ },
20
+ '360p': {
21
+ levelIdx: 1,
22
+ tier: 0,
23
+ maxWidth: 640,
24
+ maxHeight: 360,
25
+ maxPicSize: 278_784,
26
+ maxDecodeRate: 10_454_400,
27
+ },
28
+ '540p': {
29
+ levelIdx: 4,
30
+ tier: 0,
31
+ maxWidth: 960,
32
+ maxHeight: 540,
33
+ maxPicSize: 665_856,
34
+ maxDecodeRate: 24_969_600,
35
+ },
36
+ '720p': {
37
+ levelIdx: 5,
38
+ tier: 0,
39
+ maxWidth: 1280,
40
+ maxHeight: 720,
41
+ maxPicSize: 1_065_024,
42
+ maxDecodeRate: 39_938_400,
43
+ },
44
+ '1080p': {
45
+ levelIdx: 8,
46
+ tier: 0,
47
+ maxWidth: 1920,
48
+ maxHeight: 1080,
49
+ maxPicSize: 2_359_296,
50
+ maxDecodeRate: 77_856_768,
51
+ },
52
+ };
53
+
54
+ export const H264_CODEC_PARAMETERS = {
55
+ maxFs: 8192,
56
+ maxFps: 3000,
57
+ maxMbps: 245760,
58
+ };
@@ -9,6 +9,11 @@ import {
9
9
  getRecommendedMaxBitrateForFrameSize,
10
10
  RecommendedOpusBitrates,
11
11
  NamedMediaGroup,
12
+ AV1Codec,
13
+ SupportedResolution,
14
+ AV1EncodingParams,
15
+ MediaType,
16
+ MediaCodecMimeType,
12
17
  } from '@webex/internal-media-core';
13
18
  import {cloneDeepWith, debounce} from 'lodash';
14
19
 
@@ -16,6 +21,7 @@ import LoggerProxy from '../common/logs/logger-proxy';
16
21
 
17
22
  import {ReceiveSlot, ReceiveSlotEvents} from './receiveSlot';
18
23
  import {MAX_FS_VALUES} from './remoteMedia';
24
+ import {AV1_CODEC_PARAMETERS, H264_CODEC_PARAMETERS} from './codec/constants';
19
25
 
20
26
  export interface ActiveSpeakerPolicyInfo {
21
27
  policy: 'active-speaker';
@@ -54,34 +60,49 @@ export interface MediaRequest {
54
60
 
55
61
  export type MediaRequestId = string;
56
62
 
57
- const CODEC_DEFAULTS = {
58
- h264: {
59
- maxFs: 8192,
60
- maxFps: 3000,
61
- maxMbps: 245760,
62
- },
63
- };
64
-
65
63
  const DEBOUNCED_SOURCE_UPDATE_TIME = 1000;
66
64
 
65
+ const RESOLUTION_BUCKETS: Array<[SupportedResolution, number]> = [
66
+ ['90p', MAX_FS_VALUES['90p']],
67
+ ['180p', MAX_FS_VALUES['180p']],
68
+ ['360p', MAX_FS_VALUES['360p']],
69
+ ['540p', MAX_FS_VALUES['540p']],
70
+ ['720p', MAX_FS_VALUES['720p']],
71
+ ];
72
+
67
73
  type DegradationPreferences = {
68
74
  maxMacroblocksLimit: number;
69
75
  };
70
76
 
71
77
  type SendMediaRequestsCallback = (streamRequests: StreamRequest[]) => void;
78
+ type GetIngressPayloadTypeCallback = (
79
+ mediaType: MediaType,
80
+ codecMimeType: MediaCodecMimeType
81
+ ) => number | undefined;
72
82
  type Kind = 'audio' | 'video';
73
83
 
74
- type Options = {
84
+ type AudioMediaRequestManagerOptions = {
75
85
  degradationPreferences: DegradationPreferences;
76
- kind: Kind;
86
+ kind: 'audio';
77
87
  trimRequestsToNumOfSources: boolean; // if enabled, AS speaker requests will be trimmed based on the calls to setNumCurrentSources()
78
88
  };
79
89
 
90
+ type VideoMediaRequestManagerOptions = {
91
+ degradationPreferences: DegradationPreferences;
92
+ kind: 'video';
93
+ trimRequestsToNumOfSources: boolean;
94
+ enableAv1?: boolean;
95
+ };
96
+
97
+ type Options = AudioMediaRequestManagerOptions | VideoMediaRequestManagerOptions;
98
+
80
99
  type ClientRequestsMap = {[key: MediaRequestId]: MediaRequest};
81
100
 
82
101
  export class MediaRequestManager {
83
102
  private sendMediaRequestsCallback: SendMediaRequestsCallback;
84
103
 
104
+ private getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback;
105
+
85
106
  private kind: Kind;
86
107
 
87
108
  private counter: number;
@@ -95,11 +116,17 @@ export class MediaRequestManager {
95
116
  private debouncedSourceUpdateListener: () => void;
96
117
 
97
118
  private trimRequestsToNumOfSources: boolean;
119
+ private enableAv1: boolean;
98
120
  private numTotalSources: number;
99
121
  private numLiveSources: number;
100
122
 
101
- constructor(sendMediaRequestsCallback: SendMediaRequestsCallback, options: Options) {
123
+ constructor(
124
+ sendMediaRequestsCallback: SendMediaRequestsCallback,
125
+ getIngressPayloadTypeCallback: GetIngressPayloadTypeCallback,
126
+ options: Options
127
+ ) {
102
128
  this.sendMediaRequestsCallback = sendMediaRequestsCallback;
129
+ this.getIngressPayloadTypeCallback = getIngressPayloadTypeCallback;
103
130
  this.counter = 0;
104
131
  this.numLiveSources = 0;
105
132
  this.numTotalSources = 0;
@@ -107,6 +134,7 @@ export class MediaRequestManager {
107
134
  this.degradationPreferences = options.degradationPreferences;
108
135
  this.kind = options.kind;
109
136
  this.trimRequestsToNumOfSources = options.trimRequestsToNumOfSources;
137
+ this.enableAv1 = options.kind === 'video' && !!options.enableAv1;
110
138
  this.sourceUpdateListener = this.commit.bind(this);
111
139
  this.debouncedSourceUpdateListener = debounce(
112
140
  this.sourceUpdateListener,
@@ -135,8 +163,8 @@ export class MediaRequestManager {
135
163
  Object.values(clientRequests).forEach((mr) => {
136
164
  if (mr.codecInfo) {
137
165
  mr.codecInfo.maxFs = Math.min(
138
- mr.preferredMaxFs || CODEC_DEFAULTS.h264.maxFs,
139
- mr.codecInfo.maxFs || CODEC_DEFAULTS.h264.maxFs,
166
+ mr.preferredMaxFs || H264_CODEC_PARAMETERS.maxFs,
167
+ mr.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs,
140
168
  maxFsLimits[i]
141
169
  );
142
170
  // we only consider sources with "live" state
@@ -176,7 +204,7 @@ export class MediaRequestManager {
176
204
  }
177
205
 
178
206
  return getRecommendedMaxBitrateForFrameSize(
179
- mediaRequest.codecInfo.maxFs || CODEC_DEFAULTS.h264.maxFs
207
+ mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs
180
208
  );
181
209
  }
182
210
 
@@ -192,12 +220,80 @@ export class MediaRequestManager {
192
220
  // eslint-disable-next-line class-methods-use-this
193
221
  private getH264MaxMbps(mediaRequest: MediaRequest): number {
194
222
  // fallback for maxFps (not needed for maxFs, since there is a fallback already in getDegradedClientRequests)
195
- const maxFps = mediaRequest.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps;
223
+ const maxFps = mediaRequest.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps;
196
224
 
197
225
  // divided by 100 since maxFps is 3000 (for 30 frames per seconds)
198
226
  return (mediaRequest.codecInfo.maxFs * maxFps) / 100;
199
227
  }
200
228
 
229
+ /**
230
+ * Returns the AV1 encoding parameters for a media request
231
+ * @param mediaRequest - The media request to get the AV1 encoding parameters for
232
+ * @returns {AV1EncodingParams} The AV1 encoding parameters
233
+ */
234
+ // eslint-disable-next-line class-methods-use-this
235
+ private getAv1EncodingParams(mediaRequest: MediaRequest): AV1EncodingParams {
236
+ const frameSize = mediaRequest.codecInfo.maxFs || H264_CODEC_PARAMETERS.maxFs;
237
+ const resolution = RESOLUTION_BUCKETS.find(([, maxFs]) => frameSize <= maxFs)?.[0] ?? '1080p';
238
+
239
+ return AV1_CODEC_PARAMETERS[resolution];
240
+ }
241
+
242
+ private buildH264CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
243
+ if (!mr.codecInfo) {
244
+ return undefined;
245
+ }
246
+
247
+ const h264PayloadType = this.getIngressPayloadTypeCallback(
248
+ mr.receiveSlots[0].mediaType,
249
+ MediaCodecMimeType.H264
250
+ );
251
+
252
+ if (h264PayloadType === undefined) {
253
+ return undefined;
254
+ }
255
+
256
+ return WcmeCodecInfo.fromH264(
257
+ h264PayloadType,
258
+ new H264Codec(
259
+ mr.codecInfo.maxFs,
260
+ mr.codecInfo.maxFps || H264_CODEC_PARAMETERS.maxFps,
261
+ this.getH264MaxMbps(mr),
262
+ mr.codecInfo.maxWidth,
263
+ mr.codecInfo.maxHeight
264
+ )
265
+ );
266
+ }
267
+
268
+ private buildAv1CodecInfo(mr: MediaRequest): WcmeCodecInfo | undefined {
269
+ if (!this.enableAv1 || !mr.codecInfo) {
270
+ return undefined;
271
+ }
272
+
273
+ const av1PayloadType = this.getIngressPayloadTypeCallback(
274
+ mr.receiveSlots[0].mediaType,
275
+ MediaCodecMimeType.AV1
276
+ );
277
+
278
+ if (av1PayloadType === undefined) {
279
+ return undefined;
280
+ }
281
+
282
+ const av1EncodingParams = this.getAv1EncodingParams(mr);
283
+
284
+ return WcmeCodecInfo.fromAv1(
285
+ av1PayloadType,
286
+ new AV1Codec(
287
+ av1EncodingParams.levelIdx,
288
+ av1EncodingParams.tier,
289
+ mr.codecInfo.maxWidth || av1EncodingParams.maxWidth,
290
+ mr.codecInfo.maxHeight || av1EncodingParams.maxHeight,
291
+ av1EncodingParams.maxPicSize,
292
+ av1EncodingParams.maxDecodeRate
293
+ )
294
+ );
295
+ }
296
+
201
297
  /** Modifies the passed in clientRequests and makes sure that in total they don't ask
202
298
  * for more streams than there are available.
203
299
  *
@@ -298,6 +394,12 @@ export class MediaRequestManager {
298
394
  // map all the client media requests to wcme stream requests
299
395
  Object.values(clientRequests).forEach((mr) => {
300
396
  if (mr.receiveSlots.length > 0) {
397
+ const codecInfos: WcmeCodecInfo[] = mr.codecInfo
398
+ ? [this.buildH264CodecInfo(mr), this.buildAv1CodecInfo(mr)].filter(
399
+ (info): info is WcmeCodecInfo => info !== undefined
400
+ )
401
+ : [];
402
+
301
403
  streamRequests.push(
302
404
  new StreamRequest(
303
405
  mr.policyInfo.policy === 'active-speaker'
@@ -314,25 +416,14 @@ export class MediaRequestManager {
314
416
  : new ReceiverSelectedInfo(mr.policyInfo.csi),
315
417
  mr.receiveSlots.map((receiveSlot) => receiveSlot.wcmeReceiveSlot),
316
418
  this.getMaxPayloadBitsPerSecond(mr),
317
- mr.codecInfo && [
318
- WcmeCodecInfo.fromH264(
319
- 0x80,
320
- new H264Codec(
321
- mr.codecInfo.maxFs,
322
- mr.codecInfo.maxFps || CODEC_DEFAULTS.h264.maxFps,
323
- this.getH264MaxMbps(mr),
324
- mr.codecInfo.maxWidth,
325
- mr.codecInfo.maxHeight
326
- )
327
- ),
328
- ]
419
+ codecInfos
329
420
  )
330
421
  );
331
422
  }
332
423
  });
333
424
 
334
425
  this.sendMediaRequestsCallback(streamRequests);
335
- LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent. `);
426
+ LoggerProxy.logger.info(`multistream:sendRequests --> media requests sent.`);
336
427
  }
337
428
 
338
429
  public addRequest(mediaRequest: MediaRequest, commit = true): MediaRequestId {
@@ -134,6 +134,24 @@ export class ReceiveSlot extends EventsScope {
134
134
  });
135
135
  }
136
136
  );
137
+
138
+ this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaStarted, () => {
139
+ LoggerProxy.logger.log(
140
+ `ReceiveSlot#setupEventListeners --> media started on receive slot ${this.id}, mediaType=${this.mediaType}`
141
+ );
142
+ });
143
+
144
+ this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaStopped, () => {
145
+ LoggerProxy.logger.log(
146
+ `ReceiveSlot#setupEventListeners --> media stopped on receive slot ${this.id}, mediaType=${this.mediaType}`
147
+ );
148
+ });
149
+
150
+ this.mcReceiveSlot.on(WcmeReceiveSlotEvents.MediaEnded, () => {
151
+ LoggerProxy.logger.log(
152
+ `ReceiveSlot#setupEventListeners --> media ended on receive slot ${this.id}, mediaType=${this.mediaType}`
153
+ );
154
+ });
137
155
  }
138
156
 
139
157
  /** Tries to find the member id for this receive slot if it hasn't got one */
@@ -33,6 +33,7 @@ import {
33
33
  NatTypeUpdatedEventData,
34
34
  ResultEventData,
35
35
  } from './clusterReachability';
36
+ import {isReachabilityEnabled, resolveReachabilityProtocols} from './util';
36
37
  import EventsScope from '../common/events/events-scope';
37
38
  import BEHAVIORAL_METRICS from '../metrics/constants';
38
39
  import Metrics from '../metrics';
@@ -194,7 +195,7 @@ export default class Reachability extends EventsScope {
194
195
  */
195
196
  public async gatherReachability(trigger: string): Promise<ReachabilityResults> {
196
197
  // @ts-ignore
197
- if (!this.webex.config.meetings.enableReachabilityChecks) {
198
+ if (!isReachabilityEnabled(this.webex.config.meetings.enableReachabilityChecks)) {
198
199
  throw new Error('enableReachabilityChecks is disabled in config');
199
200
  }
200
201
 
@@ -515,7 +516,7 @@ export default class Reachability extends EventsScope {
515
516
  * Returns true only if ALL protocols (UDP, TCP and TLS) have been tested and none
516
517
  * of the media clusters where reachable with any of the protocols. This is done
517
518
  * irrespective of the config, so for example:
518
- * if config.meetings.experimental.enableTlsReachability === false,
519
+ * if config.meetings.enableReachabilityChecks disables TLS,
519
520
  * it will return false, because TLS reachability won't be tested,
520
521
  * so we can't say for sure that media backend is unreachable over TLS.
521
522
  *
@@ -888,14 +889,21 @@ export default class Reachability extends EventsScope {
888
889
 
889
890
  this.startTime = performance.now();
890
891
 
892
+ const protocols = resolveReachabilityProtocols(
893
+ // @ts-ignore
894
+ this.webex.config.meetings.enableReachabilityChecks
895
+ );
896
+
897
+ const enabledProtocols = [
898
+ protocols.udp && 'UDP',
899
+ protocols.tcp && 'TCP',
900
+ protocols.tls && 'TLS',
901
+ ].filter(Boolean);
902
+
891
903
  LoggerProxy.logger.log(
892
- `Reachability:index#performReachabilityChecks --> doing UDP${
893
- // @ts-ignore
894
- this.webex.config.meetings.experimental.enableTcpReachability ? ',TCP' : ''
895
- }${
896
- // @ts-ignore
897
- this.webex.config.meetings.experimental.enableTlsReachability ? ',TLS' : ''
898
- } reachability checks`
904
+ `Reachability:index#performReachabilityChecks --> doing ${enabledProtocols.join(
905
+ ','
906
+ )} reachability checks`
899
907
  );
900
908
 
901
909
  this.resetResultCounters();
@@ -905,18 +913,14 @@ export default class Reachability extends EventsScope {
905
913
  const cluster = clusterList[key];
906
914
 
907
915
  // Linus doesn't support TCP reachability checks on video mesh nodes
908
- const includeTcpReachability =
909
- // @ts-ignore
910
- this.webex.config.meetings.experimental.enableTcpReachability && !cluster.isVideoMesh;
916
+ const includeTcpReachability = protocols.tcp && !cluster.isVideoMesh;
911
917
 
912
918
  if (!includeTcpReachability) {
913
919
  cluster.tcp = [];
914
920
  }
915
921
 
916
922
  // Linus doesn't support xTLS reachability checks on video mesh nodes
917
- const includeTlsReachability =
918
- // @ts-ignore
919
- this.webex.config.meetings.experimental.enableTlsReachability && !cluster.isVideoMesh;
923
+ const includeTlsReachability = protocols.tls && !cluster.isVideoMesh;
920
924
 
921
925
  if (!includeTlsReachability) {
922
926
  cluster.xtls = [];
@@ -2,6 +2,17 @@ import {IP_VERSION, Enum} from '../constants';
2
2
 
3
3
  export type Protocol = 'udp' | 'tcp' | 'xtls';
4
4
 
5
+ // Per-protocol reachability flags, fully resolved.
6
+ export type ResolvedReachabilityProtocols = {
7
+ udp: boolean;
8
+ tcp: boolean;
9
+ tls: boolean;
10
+ };
11
+
12
+ export type ReachabilityProtocolConfig = Partial<Omit<ResolvedReachabilityProtocols, 'udp'>>;
13
+
14
+ export type EnableReachabilityChecksConfig = boolean | ReachabilityProtocolConfig;
15
+
5
16
  /**
6
17
  * Events emitted by ReachabilityPeerConnection
7
18
  */