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

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 (172) hide show
  1. package/dist/common/browser-detection.d.ts +9 -0
  2. package/dist/common/browser-detection.js +2 -2
  3. package/dist/common/browser-detection.js.map +1 -1
  4. package/dist/common/collection.d.ts +48 -0
  5. package/dist/common/collection.js +2 -2
  6. package/dist/common/collection.js.map +1 -1
  7. package/dist/common/config.d.ts +2 -0
  8. package/dist/common/errors/captcha-error.d.ts +15 -0
  9. package/dist/common/errors/intent-to-join.d.ts +16 -0
  10. package/dist/common/errors/join-meeting.d.ts +17 -0
  11. package/dist/common/errors/media.d.ts +15 -0
  12. package/dist/common/errors/parameter.d.ts +15 -0
  13. package/dist/common/errors/password-error.d.ts +15 -0
  14. package/dist/common/errors/permission.d.ts +14 -0
  15. package/dist/common/errors/reclaim-host-role-error.d.ts +60 -0
  16. package/dist/common/errors/reclaim-host-role-error.js +158 -0
  17. package/dist/common/errors/reclaim-host-role-error.js.map +1 -0
  18. package/dist/common/errors/reconnection-in-progress.d.ts +9 -0
  19. package/dist/common/errors/reconnection.d.ts +15 -0
  20. package/dist/common/errors/stats.d.ts +15 -0
  21. package/dist/common/errors/webex-errors.d.ts +81 -0
  22. package/dist/common/errors/webex-meetings-error.d.ts +20 -0
  23. package/dist/common/events/events-scope.d.ts +17 -0
  24. package/dist/common/events/events.d.ts +12 -0
  25. package/dist/common/events/trigger-proxy.d.ts +2 -0
  26. package/dist/common/events/util.d.ts +2 -0
  27. package/dist/common/logs/logger-config.d.ts +2 -0
  28. package/dist/common/logs/logger-proxy.d.ts +2 -0
  29. package/dist/common/logs/request.d.ts +34 -0
  30. package/dist/common/queue.d.ts +32 -0
  31. package/dist/config.d.ts +73 -0
  32. package/dist/config.js +2 -2
  33. package/dist/config.js.map +1 -1
  34. package/dist/constants.d.ts +952 -0
  35. package/dist/constants.js +31 -2
  36. package/dist/constants.js.map +1 -1
  37. package/dist/controls-options-manager/constants.d.ts +4 -0
  38. package/dist/controls-options-manager/enums.d.ts +5 -0
  39. package/dist/controls-options-manager/index.d.ts +120 -0
  40. package/dist/controls-options-manager/index.js +2 -2
  41. package/dist/controls-options-manager/index.js.map +1 -1
  42. package/dist/controls-options-manager/util.d.ts +7 -0
  43. package/dist/index.d.ts +4 -0
  44. package/dist/locus-info/controlsUtils.d.ts +2 -0
  45. package/dist/locus-info/controlsUtils.js +6 -6
  46. package/dist/locus-info/controlsUtils.js.map +1 -1
  47. package/dist/locus-info/embeddedAppsUtils.d.ts +2 -0
  48. package/dist/locus-info/fullState.d.ts +2 -0
  49. package/dist/locus-info/hostUtils.d.ts +2 -0
  50. package/dist/locus-info/index.d.ts +269 -0
  51. package/dist/locus-info/index.js +18 -18
  52. package/dist/locus-info/index.js.map +1 -1
  53. package/dist/locus-info/infoUtils.d.ts +2 -0
  54. package/dist/locus-info/mediaSharesUtils.d.ts +2 -0
  55. package/dist/locus-info/parser.d.ts +212 -0
  56. package/dist/locus-info/parser.js +2 -2
  57. package/dist/locus-info/parser.js.map +1 -1
  58. package/dist/locus-info/selfUtils.d.ts +2 -0
  59. package/dist/media/index.d.ts +32 -0
  60. package/dist/media/properties.d.ts +108 -0
  61. package/dist/media/util.d.ts +2 -0
  62. package/dist/mediaQualityMetrics/config.d.ts +233 -0
  63. package/dist/meeting/effectsState.d.ts +42 -0
  64. package/dist/meeting/in-meeting-actions.d.ts +79 -0
  65. package/dist/meeting/index.d.ts +1622 -0
  66. package/dist/meeting/index.js +51 -28
  67. package/dist/meeting/index.js.map +1 -1
  68. package/dist/meeting/muteState.d.ts +116 -0
  69. package/dist/meeting/request.d.ts +255 -0
  70. package/dist/meeting/request.js +2 -2
  71. package/dist/meeting/request.js.map +1 -1
  72. package/dist/meeting/state.d.ts +9 -0
  73. package/dist/meeting/util.d.ts +2 -0
  74. package/dist/meeting/util.js +4 -4
  75. package/dist/meeting/util.js.map +1 -1
  76. package/dist/meeting-info/collection.d.ts +20 -0
  77. package/dist/meeting-info/collection.js +2 -2
  78. package/dist/meeting-info/collection.js.map +1 -1
  79. package/dist/meeting-info/index.d.ts +57 -0
  80. package/dist/meeting-info/meeting-info-v2.d.ts +93 -0
  81. package/dist/meeting-info/request.d.ts +22 -0
  82. package/dist/meeting-info/util.d.ts +2 -0
  83. package/dist/meeting-info/utilv2.d.ts +2 -0
  84. package/dist/meetings/collection.d.ts +23 -0
  85. package/dist/meetings/collection.js +2 -2
  86. package/dist/meetings/collection.js.map +1 -1
  87. package/dist/meetings/index.d.ts +296 -0
  88. package/dist/meetings/request.d.ts +27 -0
  89. package/dist/meetings/util.d.ts +18 -0
  90. package/dist/member/index.d.ts +148 -0
  91. package/dist/member/index.js +9 -0
  92. package/dist/member/index.js.map +1 -1
  93. package/dist/member/member.types.d.ts +11 -0
  94. package/dist/member/util.d.ts +2 -0
  95. package/dist/member/util.js +11 -0
  96. package/dist/member/util.js.map +1 -1
  97. package/dist/members/collection.d.ts +24 -0
  98. package/dist/members/index.d.ts +308 -0
  99. package/dist/members/index.js +39 -2
  100. package/dist/members/index.js.map +1 -1
  101. package/dist/members/request.d.ts +58 -0
  102. package/dist/members/request.js +19 -0
  103. package/dist/members/request.js.map +1 -1
  104. package/dist/members/types.d.ts +25 -0
  105. package/dist/members/types.js +15 -0
  106. package/dist/members/types.js.map +1 -0
  107. package/dist/members/util.d.ts +2 -0
  108. package/dist/members/util.js +50 -0
  109. package/dist/members/util.js.map +1 -1
  110. package/dist/metrics/config.d.ts +169 -0
  111. package/dist/metrics/constants.d.ts +59 -0
  112. package/dist/metrics/constants.js +2 -0
  113. package/dist/metrics/constants.js.map +1 -1
  114. package/dist/metrics/index.d.ts +152 -0
  115. package/dist/metrics/index.js +2 -2
  116. package/dist/metrics/index.js.map +1 -1
  117. package/dist/networkQualityMonitor/index.d.ts +70 -0
  118. package/dist/peer-connection-manager/index.d.ts +6 -0
  119. package/dist/peer-connection-manager/util.d.ts +6 -0
  120. package/dist/personal-meeting-room/index.d.ts +47 -0
  121. package/dist/personal-meeting-room/request.d.ts +14 -0
  122. package/dist/personal-meeting-room/util.d.ts +2 -0
  123. package/dist/reachability/index.d.ts +139 -0
  124. package/dist/reachability/index.js +2 -9
  125. package/dist/reachability/index.js.map +1 -1
  126. package/dist/reachability/request.d.ts +35 -0
  127. package/dist/reactions/reactions.d.ts +4 -0
  128. package/dist/reactions/reactions.type.d.ts +32 -0
  129. package/dist/reconnection-manager/index.d.ts +112 -0
  130. package/dist/recording-controller/enums.d.ts +7 -0
  131. package/dist/recording-controller/index.d.ts +193 -0
  132. package/dist/recording-controller/util.d.ts +13 -0
  133. package/dist/roap/collection.d.ts +10 -0
  134. package/dist/roap/handler.d.ts +47 -0
  135. package/dist/roap/index.d.ts +116 -0
  136. package/dist/roap/index.js.map +1 -1
  137. package/dist/roap/request.d.ts +35 -0
  138. package/dist/roap/state.d.ts +9 -0
  139. package/dist/roap/turnDiscovery.d.ts +81 -0
  140. package/dist/roap/util.d.ts +2 -0
  141. package/dist/statsAnalyzer/global.d.ts +118 -0
  142. package/dist/statsAnalyzer/global.js +4 -12
  143. package/dist/statsAnalyzer/global.js.map +1 -1
  144. package/dist/statsAnalyzer/index.d.ts +193 -0
  145. package/dist/statsAnalyzer/index.js +56 -14
  146. package/dist/statsAnalyzer/index.js.map +1 -1
  147. package/dist/statsAnalyzer/mqaUtil.d.ts +22 -0
  148. package/dist/statsAnalyzer/mqaUtil.js +15 -15
  149. package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
  150. package/dist/transcription/index.d.ts +64 -0
  151. package/package.json +21 -22
  152. package/src/common/errors/reclaim-host-role-error.ts +134 -0
  153. package/src/config.ts +2 -2
  154. package/src/constants.ts +33 -0
  155. package/src/meeting/index.ts +35 -0
  156. package/src/member/index.ts +9 -0
  157. package/src/member/util.ts +14 -0
  158. package/src/members/index.ts +55 -1
  159. package/src/members/request.ts +20 -0
  160. package/src/members/types.ts +29 -0
  161. package/src/members/util.ts +52 -0
  162. package/src/metrics/constants.ts +2 -0
  163. package/src/roap/index.ts +1 -0
  164. package/src/statsAnalyzer/global.ts +2 -10
  165. package/src/statsAnalyzer/index.ts +80 -17
  166. package/test/unit/spec/meeting/index.js +77 -6
  167. package/test/unit/spec/member/index.js +7 -0
  168. package/test/unit/spec/member/util.js +32 -0
  169. package/test/unit/spec/members/index.js +205 -0
  170. package/test/unit/spec/members/utils.js +75 -0
  171. package/test/unit/spec/roap/index.ts +0 -1
  172. package/test/unit/spec/stats-analyzer/index.js +151 -0
@@ -59,6 +59,26 @@ export default class MembersRequest extends StatelessWebexPlugin {
59
59
  return this.request(requestParams);
60
60
  }
61
61
 
62
+ /**
63
+ * Sends a request to assign roles to a member
64
+ * @param {Object} options
65
+ * @param {String} options.locusUrl
66
+ * @param {String} options.memberId ID of PSTN user
67
+ * @returns {Promise}
68
+ */
69
+ assignRolesMember(options: any) {
70
+ if (!options || !options.locusUrl || !options.memberId) {
71
+ throw new ParameterError(
72
+ 'memberId must be defined, and the associated locus url for this meeting object must be defined.'
73
+ );
74
+ }
75
+
76
+ const requestParams = MembersUtil.getRoleAssignmentMemberRequestParams(options);
77
+
78
+ // @ts-ignore
79
+ return this.request(requestParams);
80
+ }
81
+
62
82
  removeMember(options) {
63
83
  if (!options || !options.locusUrl || !options.memberId) {
64
84
  throw new ParameterError(
@@ -0,0 +1,29 @@
1
+ export enum ServerRoles {
2
+ Cohost = 'COHOST',
3
+ Moderator = 'MODERATOR',
4
+ Presenter = 'PRESENTER',
5
+ }
6
+
7
+ export type ServerRoleShape = {
8
+ type: ServerRoles;
9
+ hasRole: boolean;
10
+ hostKey?: string;
11
+ };
12
+
13
+ export type RoleAssignmentOptions = {
14
+ roles: Array<ServerRoleShape>;
15
+ locusUrl: string;
16
+ memberId: string;
17
+ };
18
+
19
+ export type RoleAssignmentBody = {
20
+ role: {
21
+ roles: Array<ServerRoleShape>;
22
+ };
23
+ };
24
+
25
+ export type RoleAssignmentRequest = {
26
+ method: string;
27
+ uri: string;
28
+ body: RoleAssignmentBody;
29
+ };
@@ -11,6 +11,7 @@ import {
11
11
  SEND_DTMF_ENDPOINT,
12
12
  _REMOVE_,
13
13
  } from '../constants';
14
+ import {RoleAssignmentOptions, RoleAssignmentRequest, ServerRoleShape} from './types';
14
15
 
15
16
  const MembersUtil: any = {};
16
17
 
@@ -91,6 +92,20 @@ MembersUtil.getAddMemberRequestParams = (format: any) => {
91
92
  return requestParams;
92
93
  };
93
94
 
95
+ /**
96
+ * @param {ServerRoleShape} role
97
+ * @returns {ServerRoleShape} the role shape to be added to the body
98
+ */
99
+ MembersUtil.getAddedRoleShape = (role: ServerRoleShape) => {
100
+ const roleShape: ServerRoleShape = {type: role.type, hasRole: role.hasRole};
101
+
102
+ if (role.hostKey) {
103
+ roleShape.hostKey = role.hostKey;
104
+ }
105
+
106
+ return roleShape;
107
+ };
108
+
94
109
  MembersUtil.isInvalidInvitee = (invitee) => {
95
110
  if (!(invitee && (invitee.email || invitee.emailAddress || invitee.phoneNumber))) {
96
111
  return true;
@@ -116,6 +131,43 @@ MembersUtil.getRemoveMemberRequestParams = (options) => {
116
131
  };
117
132
  };
118
133
 
134
+ /**
135
+ * @param {String} memberId
136
+ * @param {[ServerRoleShape]} roles
137
+ * @param {String} locusUrl
138
+ * @returns {RoleAssignmentOptions}
139
+ */
140
+ MembersUtil.generateRoleAssignmentMemberOptions = (
141
+ memberId: string,
142
+ roles: Array<ServerRoleShape>,
143
+ locusUrl: string
144
+ ): RoleAssignmentOptions => ({
145
+ memberId,
146
+ roles,
147
+ locusUrl,
148
+ });
149
+
150
+ /**
151
+ * @param {RoleAssignmentOptions} options
152
+ * @returns {RoleAssignmentRequest} the request parameters (method, uri, body) needed to make a addMember request
153
+ */
154
+ MembersUtil.getRoleAssignmentMemberRequestParams = (
155
+ options: RoleAssignmentOptions
156
+ ): RoleAssignmentRequest => {
157
+ const body = {role: {roles: []}};
158
+ options.roles.forEach((role) => {
159
+ body.role.roles.push(MembersUtil.getAddedRoleShape(role));
160
+ });
161
+
162
+ const uri = `${options.locusUrl}/${PARTICIPANT}/${options.memberId}/${CONTROLS}`;
163
+
164
+ return {
165
+ method: HTTP_VERBS.PATCH,
166
+ uri,
167
+ body,
168
+ };
169
+ };
170
+
119
171
  MembersUtil.generateTransferHostMemberOptions = (transfer, moderator, locusUrl) => ({
120
172
  moderator,
121
173
  locusUrl,
@@ -1,6 +1,8 @@
1
1
  // Metrics constants ----------------------------------------------------------
2
2
 
3
3
  const BEHAVIORAL_METRICS = {
4
+ NO_FRAMES_SENT: 'js_sdk_meetings_no_frames_sent',
5
+ NO_VIDEO_ENCODED: 'js_sdk_meetings_no_video_encoded',
4
6
  MEETINGS_REGISTRATION_FAILED: 'js_sdk_meetings_registration_failed',
5
7
  MEETINGS_REGISTRATION_SUCCESS: 'js_sdk_meetings_registration_success',
6
8
  MERCURY_CONNECTION_FAILURE: 'js_sdk_mercury_connection_failure',
package/src/roap/index.ts CHANGED
@@ -302,6 +302,7 @@ export default class Roap extends StatelessWebexPlugin {
302
302
  meetingId: meeting.id,
303
303
  });
304
304
  })
305
+
305
306
  .then(({locus, mediaConnections}) => {
306
307
  this.roapHandler.submit({
307
308
  type: ROAP.SEND_ROAP_MSG_SUCCESS,
@@ -76,20 +76,11 @@ const STATS_DEFAULT = {
76
76
  },
77
77
  },
78
78
  resolutions: {
79
- audio: {
80
- send: {
81
- width: 0,
82
- height: 0,
83
- },
84
- recv: {
85
- width: 0,
86
- height: 0,
87
- },
88
- },
89
79
  video: {
90
80
  send: {
91
81
  width: 0,
92
82
  height: 0,
83
+ framesSent: 0,
93
84
  },
94
85
  recv: {
95
86
  width: 0,
@@ -100,6 +91,7 @@ const STATS_DEFAULT = {
100
91
  send: {
101
92
  width: 0,
102
93
  height: 0,
94
+ framesSent: 0,
103
95
  },
104
96
  recv: {
105
97
  width: 0,
@@ -25,6 +25,8 @@ import {
25
25
 
26
26
  export const EVENTS = {
27
27
  MEDIA_QUALITY: 'MEDIA_QUALITY',
28
+ NO_FRAMES_SENT: 'NO_FRAMES_SENT',
29
+ NO_VIDEO_ENCODED: 'NO_VIDEO_ENCODED',
28
30
  LOCAL_MEDIA_STARTED: 'LOCAL_MEDIA_STARTED',
29
31
  LOCAL_MEDIA_STOPPED: 'LOCAL_MEDIA_STOPPED',
30
32
  REMOTE_MEDIA_STARTED: 'REMOTE_MEDIA_STARTED',
@@ -420,9 +422,6 @@ export class StatsAnalyzer extends EventsScope {
420
422
  case 'inbound-rtp':
421
423
  this.processInboundRTPResult(getStatsResult, type);
422
424
  break;
423
- case 'track':
424
- this.processTrackResult(getStatsResult, type);
425
- break;
426
425
  case 'remote-inbound-rtp':
427
426
  case 'remote-outbound-rtp':
428
427
  // @ts-ignore
@@ -520,7 +519,7 @@ export class StatsAnalyzer extends EventsScope {
520
519
 
521
520
  if (currentValue - previousValue > 0) {
522
521
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STARTED : EVENTS.REMOTE_MEDIA_STARTED;
523
- } else if (currentValue === previousValue && currentValue > 0) {
522
+ } else if (currentValue === previousValue && currentValue >= 0) {
524
523
  newEvent = isLocal ? EVENTS.LOCAL_MEDIA_STOPPED : EVENTS.REMOTE_MEDIA_STOPPED;
525
524
  }
526
525
 
@@ -635,14 +634,25 @@ export class StatsAnalyzer extends EventsScope {
635
634
  LoggerProxy.logger.info(
636
635
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
637
636
  );
638
- } else {
637
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
639
638
  if (
640
639
  currentStats.framesEncoded === previousStats.framesEncoded ||
641
640
  currentStats.framesEncoded === 0
642
641
  ) {
642
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
643
643
  LoggerProxy.logger.info(
644
644
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames Encoded`
645
645
  );
646
+ this.emit(
647
+ {
648
+ file: 'statsAnalyzer',
649
+ function: 'compareLastStatsResult',
650
+ },
651
+ EVENTS.NO_VIDEO_ENCODED,
652
+ {
653
+ mediaType,
654
+ }
655
+ );
646
656
  }
647
657
 
648
658
  if (
@@ -654,8 +664,28 @@ export class StatsAnalyzer extends EventsScope {
654
664
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames sent`
655
665
  );
656
666
  }
657
- }
658
667
 
668
+ // Video is encoded but frames are not sent
669
+ if (
670
+ currentStats.framesEncoded !== previousStats.framesEncoded &&
671
+ (currentStats.framesSent === previousStats.framesSent || currentStats.framesSent === 0)
672
+ ) {
673
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_FRAMES_SENT;
674
+ LoggerProxy.logger.info(
675
+ `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames sent even though frames are encoded`
676
+ );
677
+ this.emit(
678
+ {
679
+ file: 'statsAnalyzer',
680
+ function: 'compareLastStatsResult',
681
+ },
682
+ EVENTS.NO_FRAMES_SENT,
683
+ {
684
+ mediaType,
685
+ }
686
+ );
687
+ }
688
+ }
659
689
  this.emitStartStopEvents(
660
690
  mediaType,
661
691
  previousStats.framesSent,
@@ -731,14 +761,25 @@ export class StatsAnalyzer extends EventsScope {
731
761
  LoggerProxy.logger.info(
732
762
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} RTP packets sent`
733
763
  );
734
- } else {
764
+ } else if (this.lastEmittedStartStopEvent[mediaType].local !== EVENTS.LOCAL_MEDIA_STOPPED) {
735
765
  if (
736
766
  currentStats.framesEncoded === previousStats.framesEncoded ||
737
767
  currentStats.framesEncoded === 0
738
768
  ) {
769
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_VIDEO_ENCODED;
739
770
  LoggerProxy.logger.info(
740
771
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames getting encoded`
741
772
  );
773
+ this.emit(
774
+ {
775
+ file: 'statsAnalyzer',
776
+ function: 'compareLastStatsResult',
777
+ },
778
+ EVENTS.NO_VIDEO_ENCODED,
779
+ {
780
+ mediaType,
781
+ }
782
+ );
742
783
  }
743
784
 
744
785
  if (
@@ -750,6 +791,27 @@ export class StatsAnalyzer extends EventsScope {
750
791
  `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} frames sent`
751
792
  );
752
793
  }
794
+
795
+ // Share video is encoded but frames are not sent
796
+ if (
797
+ currentStats.framesEncoded !== previousStats.framesEncoded &&
798
+ (currentStats.framesSent === previousStats.framesSent || currentStats.framesSent === 0)
799
+ ) {
800
+ this.lastEmittedStartStopEvent[mediaType].local = EVENTS.NO_FRAMES_SENT;
801
+ LoggerProxy.logger.info(
802
+ `StatsAnalyzer:index#compareLastStatsResult --> No ${mediaType} Frames sent even though frames are being encoded`
803
+ );
804
+ this.emit(
805
+ {
806
+ file: 'statsAnalyzer',
807
+ function: 'compareLastStatsResult',
808
+ },
809
+ EVENTS.NO_FRAMES_SENT,
810
+ {
811
+ mediaType,
812
+ }
813
+ );
814
+ }
753
815
  }
754
816
 
755
817
  // TODO:need to check receive share value
@@ -881,6 +943,7 @@ export class StatsAnalyzer extends EventsScope {
881
943
  const mediaType = type || STATS.AUDIO_CORRELATE;
882
944
  const sendrecvType = STATS.SEND_DIRECTION;
883
945
 
946
+ this.processTrackResult(result, type, sendrecvType);
884
947
  if (result.bytesSent) {
885
948
  let kilobytes = 0;
886
949
 
@@ -904,7 +967,6 @@ export class StatsAnalyzer extends EventsScope {
904
967
 
905
968
  this.statsResults[mediaType][sendrecvType].availableBandwidth = kilobytes.toFixed(1);
906
969
  this.statsResults[mediaType].bytesSent = kilobytes;
907
-
908
970
  this.statsResults[mediaType][sendrecvType].framesEncoded =
909
971
  result.framesEncoded - this.statsResults.internal[mediaType][sendrecvType].framesEncoded;
910
972
  this.statsResults[mediaType][sendrecvType].keyFramesEncoded =
@@ -955,6 +1017,7 @@ export class StatsAnalyzer extends EventsScope {
955
1017
  const mediaType = type || STATS.AUDIO_CORRELATE;
956
1018
  const sendrecvType = STATS.RECEIVE_DIRECTION;
957
1019
 
1020
+ this.processTrackResult(result, type, sendrecvType);
958
1021
  if (result.bytesReceived) {
959
1022
  let kilobytes = 0;
960
1023
 
@@ -1158,29 +1221,29 @@ export class StatsAnalyzer extends EventsScope {
1158
1221
  * @private
1159
1222
  * @param {*} result
1160
1223
  * @param {*} mediaType
1224
+ * @param {*} sendrecvType
1161
1225
  * @returns {void}
1162
1226
  * @memberof StatsAnalyzer
1163
1227
  */
1164
- private processTrackResult(result: any, mediaType: any) {
1165
- if (!result || result.type !== 'track') {
1228
+ private processTrackResult(result: any, mediaType: any, sendrecvType: any) {
1229
+ if (!result || mediaType === STATS.AUDIO_CORRELATE) {
1230
+ return;
1231
+ }
1232
+ if (result.type !== 'inbound-rtp' && result.type !== 'outbound-rtp') {
1166
1233
  return;
1167
1234
  }
1168
- if (result.type !== 'track') return;
1169
-
1170
- const sendrecvType =
1171
- result.remoteSource === true ? STATS.RECEIVE_DIRECTION : STATS.SEND_DIRECTION;
1172
-
1173
1235
  if (result.frameWidth && result.frameHeight) {
1174
1236
  this.statsResults.resolutions[mediaType][sendrecvType].width = result.frameWidth;
1175
1237
  this.statsResults.resolutions[mediaType][sendrecvType].height = result.frameHeight;
1176
- this.statsResults.resolutions[mediaType][sendrecvType].framesSent = result.framesSent;
1177
- this.statsResults.resolutions[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
1178
1238
  }
1179
1239
 
1180
1240
  if (sendrecvType === STATS.RECEIVE_DIRECTION) {
1181
1241
  this.statsResults.resolutions[mediaType][sendrecvType].framesReceived = result.framesReceived;
1182
1242
  this.statsResults.resolutions[mediaType][sendrecvType].framesDecoded = result.framesDecoded;
1183
1243
  this.statsResults.resolutions[mediaType][sendrecvType].framesDropped = result.framesDropped;
1244
+ } else if (sendrecvType === STATS.SEND_DIRECTION) {
1245
+ this.statsResults.resolutions[mediaType][sendrecvType].framesSent = result.framesSent;
1246
+ this.statsResults.resolutions[mediaType][sendrecvType].hugeFramesSent = result.hugeFramesSent;
1184
1247
  }
1185
1248
 
1186
1249
  if (result.trackIdentifier && mediaType !== STATS.AUDIO_CORRELATE) {
@@ -1329,6 +1329,71 @@ describe('plugin-meetings', () => {
1329
1329
  data: {intervalData: fakeData, networkType: 'wifi'},
1330
1330
  });
1331
1331
  });
1332
+ it('NO_FRAMES_SENT triggers "meeting:noFramesSent" event and sends metrics', async () => {
1333
+ meeting.mediaProperties.mediaDirection = {sendVideo: true};
1334
+ statsAnalyzerStub.emit(
1335
+ {file: 'test', function: 'test'},
1336
+ StatsAnalyzerModule.EVENTS.NO_FRAMES_SENT,
1337
+ {mediaType: 'video'}
1338
+ );
1339
+
1340
+ assert.calledWith(
1341
+ TriggerProxy.trigger,
1342
+ sinon.match.instanceOf(Meeting),
1343
+ {
1344
+ file: 'meeting/index',
1345
+ function: 'compareLastStatsResult',
1346
+ },
1347
+ EVENT_TRIGGERS.MEETING_NO_FRAMES_SENT,
1348
+ {
1349
+ mediaType: 'video',
1350
+ }
1351
+ );
1352
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_FRAMES_SENT);
1353
+ });
1354
+ it('NO_FRAMES_SENT triggers "meeting:noFramesSent" event and sends metrics for share', async () => {
1355
+ meeting.mediaProperties.mediaDirection = {sendShare: true};
1356
+ statsAnalyzerStub.emit(
1357
+ {file: 'test', function: 'test'},
1358
+ StatsAnalyzerModule.EVENTS.NO_FRAMES_SENT,
1359
+ {mediaType: 'share'}
1360
+ );
1361
+
1362
+ assert.calledWith(
1363
+ TriggerProxy.trigger,
1364
+ sinon.match.instanceOf(Meeting),
1365
+ {
1366
+ file: 'meeting/index',
1367
+ function: 'compareLastStatsResult',
1368
+ },
1369
+ EVENT_TRIGGERS.MEETING_NO_FRAMES_SENT,
1370
+ {
1371
+ mediaType: 'share',
1372
+ }
1373
+ );
1374
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_FRAMES_SENT);
1375
+ });
1376
+ it('NO_VIDEO_ENCODED triggers "meeting:noVideoEncoded" event and sends metrics', async () => {
1377
+ statsAnalyzerStub.emit(
1378
+ {file: 'test', function: 'test'},
1379
+ StatsAnalyzerModule.EVENTS.NO_VIDEO_ENCODED,
1380
+ {mediaType: 'video'}
1381
+ );
1382
+
1383
+ assert.calledWith(
1384
+ TriggerProxy.trigger,
1385
+ sinon.match.instanceOf(Meeting),
1386
+ {
1387
+ file: 'meeting/index',
1388
+ function: 'compareLastStatsResult',
1389
+ },
1390
+ EVENT_TRIGGERS.MEETING_NO_VIDEO_ENCODED,
1391
+ {
1392
+ mediaType: 'video',
1393
+ }
1394
+ );
1395
+ assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.NO_VIDEO_ENCODED);
1396
+ });
1332
1397
  });
1333
1398
  });
1334
1399
  describe('#acknowledge', () => {
@@ -3646,14 +3711,17 @@ describe('plugin-meetings', () => {
3646
3711
  describe('#setUpLocusServicesListener', () => {
3647
3712
  it('listens to the locus services update event', (done) => {
3648
3713
  const newLocusServices = {
3649
- services: {
3650
- record: {
3651
- url: 'url',
3652
- }
3714
+ services: {
3715
+ record: {
3716
+ url: 'url',
3653
3717
  },
3718
+ },
3654
3719
  };
3655
3720
 
3656
- meeting.recordingController = {setServiceUrl: sinon.stub().returns(undefined), setSessionId: sinon.stub().returns(undefined)};
3721
+ meeting.recordingController = {
3722
+ setServiceUrl: sinon.stub().returns(undefined),
3723
+ setSessionId: sinon.stub().returns(undefined),
3724
+ };
3657
3725
 
3658
3726
  meeting.locusInfo.emit(
3659
3727
  {function: 'test', file: 'test'},
@@ -3661,7 +3729,10 @@ describe('plugin-meetings', () => {
3661
3729
  newLocusServices
3662
3730
  );
3663
3731
 
3664
- assert.calledWith(meeting.recordingController.setServiceUrl, newLocusServices.services.record.url);
3732
+ assert.calledWith(
3733
+ meeting.recordingController.setServiceUrl,
3734
+ newLocusServices.services.record.url
3735
+ );
3665
3736
  assert.calledOnce(meeting.recordingController.setSessionId);
3666
3737
  done();
3667
3738
  });
@@ -20,6 +20,13 @@ describe('member', () => {
20
20
 
21
21
  assert.calledOnceWithExactly(MemberUtil.isHandRaised, participant);
22
22
  });
23
+
24
+ it('checks that processParticipant calls canReclaimHost', () => {
25
+ sinon.spy(MemberUtil, 'canReclaimHost');
26
+ member.processParticipant(participant);
27
+
28
+ assert.calledOnceWithExactly(MemberUtil.canReclaimHost, participant);
29
+ });
23
30
  })
24
31
 
25
32
  describe('#processMember', ()=>{
@@ -78,3 +78,35 @@ describe('extractMediaStatus', () => {
78
78
  assert.deepEqual(mediaStatus, {audio: 'RECVONLY', video: 'SENDRECV'});
79
79
  });
80
80
  });
81
+
82
+ describe('MemberUtil.canReclaimHost', () => {
83
+ it('throws error when there is no participant', () => {
84
+ assert.throws(() => {
85
+ MemberUtil.canReclaimHost();
86
+ }, 'canReclaimHostRole could not be processed, participant is undefined.');
87
+ });
88
+
89
+ it('returns true when canReclaimHostRole is true', () => {
90
+ const participant = {
91
+ canReclaimHostRole: true,
92
+ };
93
+
94
+ assert.isTrue(MemberUtil.canReclaimHost(participant));
95
+ });
96
+
97
+ it('returns false when canReclaimHostRole is false', () => {
98
+ const participant = {
99
+ canReclaimHostRole: false,
100
+ };
101
+
102
+ assert.isFalse(MemberUtil.canReclaimHost(participant));
103
+ });
104
+
105
+ it('returns false when canReclaimHostRole is falsy', () => {
106
+ const participant = {
107
+ canReclaimHostRole: undefined,
108
+ };
109
+
110
+ assert.isFalse(MemberUtil.canReclaimHost(participant));
111
+ });
112
+ });