@webex/plugin-meetings 2.60.1-next.16 → 2.60.1-next.18

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 (35) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.d.ts +1 -0
  4. package/dist/constants.js +1 -0
  5. package/dist/constants.js.map +1 -1
  6. package/dist/interpretation/index.js +1 -1
  7. package/dist/interpretation/siLanguage.js +1 -1
  8. package/dist/locus-info/index.js +4 -1
  9. package/dist/locus-info/index.js.map +1 -1
  10. package/dist/meeting/index.js +27 -8
  11. package/dist/meeting/index.js.map +1 -1
  12. package/dist/meetings/index.d.ts +1 -1
  13. package/dist/meetings/index.js +6 -20
  14. package/dist/meetings/index.js.map +1 -1
  15. package/dist/reconnection-manager/index.js +22 -26
  16. package/dist/reconnection-manager/index.js.map +1 -1
  17. package/dist/statsAnalyzer/index.d.ts +5 -1
  18. package/dist/statsAnalyzer/index.js +75 -88
  19. package/dist/statsAnalyzer/index.js.map +1 -1
  20. package/dist/webinar/index.js +1 -1
  21. package/package.json +22 -23
  22. package/src/constants.ts +1 -0
  23. package/src/locus-info/index.ts +4 -1
  24. package/src/meeting/index.ts +25 -1
  25. package/src/meetings/index.ts +10 -23
  26. package/src/reconnection-manager/index.ts +11 -13
  27. package/src/statsAnalyzer/index.ts +131 -153
  28. package/test/integration/spec/journey.js +2 -2
  29. package/test/integration/spec/space-meeting.js +1 -1
  30. package/test/unit/spec/locus-info/index.js +13 -6
  31. package/test/unit/spec/meeting/index.js +49 -1
  32. package/test/unit/spec/meetings/index.js +21 -0
  33. package/test/unit/spec/metrics/index.js +1 -2
  34. package/test/unit/spec/reconnection-manager/index.js +1 -11
  35. package/test/unit/spec/stats-analyzer/index.js +686 -493
@@ -726,20 +726,12 @@ export default class Meetings extends WebexPlugin {
726
726
  * @memberof Meetings
727
727
  */
728
728
  public register() {
729
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
730
- step: '[sdk] begin registration',
731
- });
732
-
733
729
  // @ts-ignore
734
730
  if (!this.webex.canAuthorize) {
735
731
  LoggerProxy.logger.error(
736
732
  'Meetings:index#register --> ERROR, Unable to register, SDK cannot authorize'
737
733
  );
738
734
 
739
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
740
- step: '[sdk] cannot authorize',
741
- });
742
-
743
735
  return Promise.reject(new Error('SDK cannot authorize'));
744
736
  }
745
737
 
@@ -748,17 +740,9 @@ export default class Meetings extends WebexPlugin {
748
740
  'Meetings:index#register --> INFO, Meetings plugin already registered'
749
741
  );
750
742
 
751
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
752
- step: '[sdk] already registered',
753
- });
754
-
755
743
  return Promise.resolve();
756
744
  }
757
745
 
758
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
759
- step: '[sdk] begin Promise.all()',
760
- });
761
-
762
746
  return Promise.all([
763
747
  this.fetchUserPreferredWebexSite(),
764
748
  this.getGeoHint(),
@@ -780,9 +764,6 @@ export default class Meetings extends WebexPlugin {
780
764
  MeetingsUtil.checkH264Support.call(this),
781
765
  ])
782
766
  .then(() => {
783
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
784
- step: '[sdk] end Promise.all()',
785
- });
786
767
  this.listenForEvents();
787
768
  Trigger.trigger(
788
769
  this,
@@ -792,9 +773,6 @@ export default class Meetings extends WebexPlugin {
792
773
  },
793
774
  EVENT_TRIGGERS.MEETINGS_REGISTERED
794
775
  );
795
- Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_STEP, {
796
- step: '[sdk] registration complete, triggered MEETINGS_REGISTERED event',
797
- });
798
776
  this.registered = true;
799
777
  Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETINGS_REGISTRATION_SUCCESS);
800
778
  })
@@ -1406,13 +1384,22 @@ export default class Meetings extends WebexPlugin {
1406
1384
  }
1407
1385
 
1408
1386
  /**
1409
- * syncs all the meeting from server
1387
+ * Syncs all the meetings from server. Does nothing and returns immediately if unverified guest.
1410
1388
  * @param {boolean} keepOnlyLocusMeetings - whether the sync should keep only locus meetings or any other meeting in meetingCollection
1411
1389
  * @returns {Promise<void>}
1412
1390
  * @public
1413
1391
  * @memberof Meetings
1414
1392
  */
1415
1393
  public syncMeetings({keepOnlyLocusMeetings = true} = {}): Promise<void> {
1394
+ // @ts-ignore
1395
+ if (this.webex.credentials.isUnverifiedGuest) {
1396
+ LoggerProxy.logger.info(
1397
+ 'Meetings:index#syncMeetings --> skipping meeting sync as unverified guest'
1398
+ );
1399
+
1400
+ return Promise.resolve();
1401
+ }
1402
+
1416
1403
  return this.request
1417
1404
  .getActiveMeetings()
1418
1405
  .then((locusArray) => {
@@ -448,19 +448,17 @@ export default class ReconnectionManager {
448
448
  }
449
449
  }
450
450
 
451
- if (!this.webex.credentials.isUnverifiedGuest) {
452
- try {
453
- LoggerProxy.logger.info(
454
- 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
455
- );
456
- await this.webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
457
- } catch (syncError) {
458
- LoggerProxy.logger.info(
459
- 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
460
- syncError
461
- );
462
- throw new NeedsRetryError(syncError);
463
- }
451
+ try {
452
+ LoggerProxy.logger.info(
453
+ 'ReconnectionManager:index#executeReconnection --> Updating meeting data from server.'
454
+ );
455
+ await this.webex.meetings.syncMeetings({keepOnlyLocusMeetings: false});
456
+ } catch (syncError) {
457
+ LoggerProxy.logger.info(
458
+ 'ReconnectionManager:index#executeReconnection --> Unable to sync meetings, reconnecting.',
459
+ syncError
460
+ );
461
+ throw new NeedsRetryError(syncError);
464
462
  }
465
463
 
466
464
  // TODO: try to improve this logic as the reconnection manager saves the instance of deleted meeting object
@@ -63,7 +63,10 @@ const emptyReceiver = {
63
63
  };
64
64
 
65
65
  type ReceiveSlotCallback = (csi: number) => ReceiveSlot | undefined;
66
-
66
+ type MediaStatus = {
67
+ actual?: any;
68
+ expected?: any;
69
+ };
67
70
  /**
68
71
  * Stats Analyzer class that will emit events based on detected quality
69
72
  *
@@ -147,8 +150,17 @@ export class StatsAnalyzer extends EventsScope {
147
150
  * @memberof StatsAnalyzer
148
151
  * @returns {void}
149
152
  */
150
- public updateMediaStatus(status: object) {
151
- this.meetingMediaStatus = status;
153
+ public updateMediaStatus(status: MediaStatus) {
154
+ this.meetingMediaStatus = {
155
+ actual: {
156
+ ...this.meetingMediaStatus?.actual,
157
+ ...status?.actual,
158
+ },
159
+ expected: {
160
+ ...this.meetingMediaStatus?.expected,
161
+ ...status?.expected,
162
+ },
163
+ };
152
164
  }
153
165
 
154
166
  /**
@@ -659,14 +671,16 @@ export class StatsAnalyzer extends EventsScope {
659
671
  .filter((key) => key.startsWith(keyPrefix))
660
672
  .reduce((prev, cur) => prev + (this.lastStatsResults[cur]?.recv[value] || 0), 0);
661
673
 
662
- if (this.meetingMediaStatus.expected.sendAudio && this.lastStatsResults['audio-send']) {
674
+ // Audio Transmit
675
+ if (this.lastStatsResults['audio-send']) {
663
676
  // compare audio stats sent
664
677
  // NOTE: relies on there being only one sender.
665
678
  const currentStats = this.statsResults['audio-send'].send;
666
679
  const previousStats = this.lastStatsResults['audio-send'].send;
667
680
 
668
681
  if (
669
- currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
682
+ (this.meetingMediaStatus.expected.sendAudio &&
683
+ currentStats.totalPacketsSent === previousStats.totalPacketsSent) ||
670
684
  currentStats.totalPacketsSent === 0
671
685
  ) {
672
686
  LoggerProxy.logger.info(
@@ -675,7 +689,8 @@ export class StatsAnalyzer extends EventsScope {
675
689
  );
676
690
  } else {
677
691
  if (
678
- currentStats.totalAudioEnergy === previousStats.totalAudioEnergy ||
692
+ (this.meetingMediaStatus.expected.sendAudio &&
693
+ currentStats.totalAudioEnergy === previousStats.totalAudioEnergy) ||
679
694
  currentStats.totalAudioEnergy === 0
680
695
  ) {
681
696
  LoggerProxy.logger.info(
@@ -684,7 +699,7 @@ export class StatsAnalyzer extends EventsScope {
684
699
  );
685
700
  }
686
701
 
687
- if (currentStats.audioLevel === 0) {
702
+ if (this.meetingMediaStatus.expected.sendAudio && currentStats.audioLevel === 0) {
688
703
  LoggerProxy.logger.info(
689
704
  `StatsAnalyzer:index#compareLastStatsResult --> audio level is 0 for the user`
690
705
  );
@@ -699,45 +714,33 @@ export class StatsAnalyzer extends EventsScope {
699
714
  );
700
715
  }
701
716
 
702
- if (this.meetingMediaStatus.expected.receiveAudio) {
703
- // compare audio stats received
704
- const currentPacketsReceived = getCurrentStatsTotals('audio-recv', 'totalPacketsReceived');
705
- const previousPacketsReceived = getPreviousStatsTotals(
706
- 'audio-recv',
707
- 'totalPacketsReceived'
708
- );
709
- const currentSamplesReceived = getCurrentStatsTotals('audio-recv', 'totalSamplesReceived');
710
- const previousSamplesReceived = getPreviousStatsTotals(
711
- 'audio-recv',
712
- 'totalSamplesReceived'
713
- );
714
-
715
- if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
716
- LoggerProxy.logger.info(
717
- `StatsAnalyzer:index#compareLastStatsResult --> No audio RTP packets received`,
718
- currentPacketsReceived
719
- );
720
- } else if (
721
- currentSamplesReceived === previousSamplesReceived ||
722
- currentSamplesReceived === 0
723
- ) {
724
- LoggerProxy.logger.info(
725
- `StatsAnalyzer:index#compareLastStatsResult --> No audio samples received`,
726
- currentSamplesReceived
727
- );
728
- }
717
+ // Audio Receive
718
+ const currentAudioPacketsReceived = getCurrentStatsTotals(
719
+ 'audio-recv',
720
+ 'totalPacketsReceived'
721
+ );
722
+ const previousAudioPacketsReceived = getPreviousStatsTotals(
723
+ 'audio-recv',
724
+ 'totalPacketsReceived'
725
+ );
729
726
 
730
- this.emitStartStopEvents('audio', previousPacketsReceived, currentPacketsReceived, false);
731
- }
727
+ this.emitStartStopEvents(
728
+ 'audio',
729
+ previousAudioPacketsReceived,
730
+ currentAudioPacketsReceived,
731
+ false
732
+ );
732
733
 
733
- if (this.meetingMediaStatus.expected.sendVideo && this.lastStatsResults['video-send']) {
734
+ // Video Transmit
735
+ if (this.lastStatsResults['video-send']) {
734
736
  // compare video stats sent
735
737
  const currentStats = this.statsResults['video-send'].send;
736
738
  const previousStats = this.lastStatsResults['video-send'].send;
737
739
 
738
740
  if (
739
- currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
740
- currentStats.totalPacketsSent === 0
741
+ this.meetingMediaStatus.expected.sendVideo &&
742
+ (currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
743
+ currentStats.totalPacketsSent === 0)
741
744
  ) {
742
745
  LoggerProxy.logger.info(
743
746
  `StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
@@ -745,8 +748,9 @@ export class StatsAnalyzer extends EventsScope {
745
748
  );
746
749
  } else {
747
750
  if (
748
- currentStats.framesEncoded === previousStats.framesEncoded ||
749
- currentStats.framesEncoded === 0
751
+ this.meetingMediaStatus.expected.sendVideo &&
752
+ (currentStats.framesEncoded === previousStats.framesEncoded ||
753
+ currentStats.framesEncoded === 0)
750
754
  ) {
751
755
  LoggerProxy.logger.info(
752
756
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
@@ -755,9 +759,10 @@ export class StatsAnalyzer extends EventsScope {
755
759
  }
756
760
 
757
761
  if (
758
- this.statsResults['video-send'].send.framesSent ===
762
+ this.meetingMediaStatus.expected.sendVideo &&
763
+ (this.statsResults['video-send'].send.framesSent ===
759
764
  this.lastStatsResults['video-send'].send.framesSent ||
760
- this.statsResults['video-send'].send.framesSent === 0
765
+ this.statsResults['video-send'].send.framesSent === 0)
761
766
  ) {
762
767
  LoggerProxy.logger.info(
763
768
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
@@ -769,60 +774,28 @@ export class StatsAnalyzer extends EventsScope {
769
774
  this.emitStartStopEvents('video', previousStats.framesSent, currentStats.framesSent, true);
770
775
  }
771
776
 
772
- if (this.meetingMediaStatus.expected.receiveVideo) {
773
- // compare video stats received
774
- const currentPacketsReceived = getCurrentStatsTotals('video-recv', 'totalPacketsReceived');
775
- const previousPacketsReceived = getPreviousStatsTotals(
776
- 'video-recv',
777
- 'totalPacketsReceived'
778
- );
779
- const currentFramesReceived = getCurrentStatsTotals('video-recv', 'framesReceived');
780
- const previousFramesReceived = getPreviousStatsTotals('video-recv', 'framesReceived');
781
- const currentFramesDecoded = getCurrentStatsTotals('video-recv', 'framesDecoded');
782
- const previousFramesDecoded = getPreviousStatsTotals('video-recv', 'framesDecoded');
783
- const currentFramesDropped = getCurrentStatsTotals('video-recv', 'framesDropped');
784
- const previousFramesDropped = getPreviousStatsTotals('video-recv', 'framesDropped');
785
-
786
- if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
787
- LoggerProxy.logger.info(
788
- `StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets received`,
789
- currentPacketsReceived
790
- );
791
- } else {
792
- if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
793
- LoggerProxy.logger.info(
794
- `StatsAnalyzer:index#compareLastStatsResult --> No video frames received`,
795
- currentFramesReceived
796
- );
797
- }
798
-
799
- if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
800
- LoggerProxy.logger.info(
801
- `StatsAnalyzer:index#compareLastStatsResult --> No video frames decoded`,
802
- currentFramesDecoded
803
- );
804
- }
805
-
806
- if (currentFramesDropped - previousFramesDropped > 10) {
807
- LoggerProxy.logger.info(
808
- `StatsAnalyzer:index#compareLastStatsResult --> video frames are getting dropped`,
809
- currentFramesDropped - previousFramesDropped
810
- );
811
- }
812
- }
777
+ // Video Receive
778
+ const currentVideoFramesDecoded = getCurrentStatsTotals('video-recv', 'framesDecoded');
779
+ const previousVideoFramesDecoded = getPreviousStatsTotals('video-recv', 'framesDecoded');
813
780
 
814
- this.emitStartStopEvents('video', previousFramesDecoded, currentFramesDecoded, false);
815
- }
781
+ this.emitStartStopEvents(
782
+ 'video',
783
+ previousVideoFramesDecoded,
784
+ currentVideoFramesDecoded,
785
+ false
786
+ );
816
787
 
817
- if (this.meetingMediaStatus.expected.sendShare && this.lastStatsResults['video-share-send']) {
788
+ // Share Transmit
789
+ if (this.lastStatsResults['video-share-send']) {
818
790
  // compare share stats sent
819
791
 
820
792
  const currentStats = this.statsResults['video-share-send'].send;
821
793
  const previousStats = this.lastStatsResults['video-share-send'].send;
822
794
 
823
795
  if (
824
- currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
825
- currentStats.totalPacketsSent === 0
796
+ this.meetingMediaStatus.expected.sendShare &&
797
+ (currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
798
+ currentStats.totalPacketsSent === 0)
826
799
  ) {
827
800
  LoggerProxy.logger.info(
828
801
  `StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets sent`,
@@ -830,8 +803,9 @@ export class StatsAnalyzer extends EventsScope {
830
803
  );
831
804
  } else {
832
805
  if (
833
- currentStats.framesEncoded === previousStats.framesEncoded ||
834
- currentStats.framesEncoded === 0
806
+ this.meetingMediaStatus.expected.sendShare &&
807
+ (currentStats.framesEncoded === previousStats.framesEncoded ||
808
+ currentStats.framesEncoded === 0)
835
809
  ) {
836
810
  LoggerProxy.logger.info(
837
811
  `StatsAnalyzer:index#compareLastStatsResult --> No share frames getting encoded`,
@@ -840,9 +814,10 @@ export class StatsAnalyzer extends EventsScope {
840
814
  }
841
815
 
842
816
  if (
843
- this.statsResults['video-share-send'].send.framesSent ===
817
+ this.meetingMediaStatus.expected.sendShare &&
818
+ (this.statsResults['video-share-send'].send.framesSent ===
844
819
  this.lastStatsResults['video-share-send'].send.framesSent ||
845
- this.statsResults['video-share-send'].send.framesSent === 0
820
+ this.statsResults['video-share-send'].send.framesSent === 0)
846
821
  ) {
847
822
  LoggerProxy.logger.info(
848
823
  `StatsAnalyzer:index#compareLastStatsResult --> No share frames sent`,
@@ -850,58 +825,23 @@ export class StatsAnalyzer extends EventsScope {
850
825
  );
851
826
  }
852
827
  }
853
- }
854
828
 
855
- if (this.meetingMediaStatus.expected.sendShare) {
856
- // TODO:need to check receive share value
857
- // compare share stats received
858
- const currentPacketsReceived = getCurrentStatsTotals(
859
- 'video-share-recv',
860
- 'totalPacketsReceived'
861
- );
862
- const previousPacketsReceived = getPreviousStatsTotals(
863
- 'video-share-recv',
864
- 'totalPacketsReceived'
865
- );
866
- const currentFramesReceived = getCurrentStatsTotals('video-share-recv', 'framesReceived');
867
- const previousFramesReceived = getPreviousStatsTotals('video-share-recv', 'framesReceived');
868
- const currentFramesDecoded = getCurrentStatsTotals('video-share-recv', 'framesDecoded');
869
- const previousFramesDecoded = getPreviousStatsTotals('video-share-recv', 'framesDecoded');
870
- const currentFramesDropped = getCurrentStatsTotals('video-share-recv', 'framesDropped');
871
- const previousFramesDropped = getPreviousStatsTotals('video-share-recv', 'framesDropped');
872
-
873
- if (currentPacketsReceived === previousPacketsReceived || currentPacketsReceived === 0) {
874
- LoggerProxy.logger.info(
875
- `StatsAnalyzer:index#compareLastStatsResult --> No share RTP packets received`,
876
- currentPacketsReceived
877
- );
878
- } else {
879
- if (currentFramesReceived === previousFramesReceived || currentFramesReceived === 0) {
880
- LoggerProxy.logger.info(
881
- `StatsAnalyzer:index#compareLastStatsResult --> No share frames received`,
882
- currentFramesReceived
883
- );
884
- }
885
-
886
- if (currentFramesDecoded === previousFramesDecoded || currentFramesDecoded === 0) {
887
- LoggerProxy.logger.info(
888
- `StatsAnalyzer:index#compareLastStatsResult --> No share frames decoded`,
889
- currentFramesDecoded
890
- );
891
- }
829
+ this.emitStartStopEvents('share', previousStats.framesSent, currentStats.framesSent, true);
830
+ }
892
831
 
893
- if (currentFramesDropped - previousFramesDropped > 10) {
894
- LoggerProxy.logger.info(
895
- `StatsAnalyzer:index#compareLastStatsResult --> share frames are getting dropped`,
896
- currentFramesDropped - previousFramesDropped
897
- );
898
- }
899
- }
832
+ // Share receive
833
+ const currentShareFramesDecoded = getCurrentStatsTotals('video-share-recv', 'framesDecoded');
834
+ const previousShareFramesDecoded = getPreviousStatsTotals(
835
+ 'video-share-recv',
836
+ 'framesDecoded'
837
+ );
900
838
 
901
- // we are not calling emitStartStopEvents() for sending or receiving share because sharing is often started and stopped
902
- // in meetings and this.meetingMediaStatus.expected values can be out of sync with the actual packet flow
903
- // so we would send "sharing stopped" events incorrectly
904
- }
839
+ this.emitStartStopEvents(
840
+ 'share',
841
+ previousShareFramesDecoded,
842
+ currentShareFramesDecoded,
843
+ false
844
+ );
905
845
  }
906
846
  }
907
847
 
@@ -1049,12 +989,6 @@ export class StatsAnalyzer extends EventsScope {
1049
989
  ? `id: "${receiveSlot.id || ''}"${receiveSlot.csi ? ` and csi: ${receiveSlot.csi}` : ''}`
1050
990
  : '';
1051
991
 
1052
- if (result.frameWidth && result.frameHeight) {
1053
- this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
1054
- this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
1055
- this.statsResults[mediaType][sendrecvType].framesReceived = result.framesReceived;
1056
- }
1057
-
1058
992
  const bytes =
1059
993
  result.bytesReceived - this.statsResults[mediaType][sendrecvType].totalBytesReceived;
1060
994
 
@@ -1066,24 +1000,61 @@ export class StatsAnalyzer extends EventsScope {
1066
1000
  if (currentPacketsLost < 0) {
1067
1001
  currentPacketsLost = 0;
1068
1002
  }
1069
-
1070
- const currentPacketsReceived =
1003
+ const packetsReceivedDiff =
1071
1004
  result.packetsReceived - this.statsResults[mediaType][sendrecvType].totalPacketsReceived;
1072
1005
  this.statsResults[mediaType][sendrecvType].totalPacketsReceived = result.packetsReceived;
1073
1006
 
1074
- if (currentPacketsReceived === 0) {
1007
+ if (packetsReceivedDiff === 0) {
1075
1008
  if (receiveSlot && sourceState === 'live') {
1076
1009
  LoggerProxy.logger.info(
1077
- `StatsAnalyzer:index#processInboundRTPResult --> No packets received for receive slot ${idAndCsi}. Total packets received on slot: `,
1010
+ `StatsAnalyzer:index#processInboundRTPResult --> No packets received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total packets received on slot: `,
1078
1011
  result.packetsReceived
1079
1012
  );
1080
1013
  }
1081
1014
  }
1082
1015
 
1016
+ if (mediaType.startsWith('video') || mediaType.startsWith('share')) {
1017
+ const videoFramesReceivedDiff =
1018
+ result.framesReceived - this.statsResults[mediaType][sendrecvType].framesReceived;
1019
+
1020
+ if (videoFramesReceivedDiff === 0) {
1021
+ if (receiveSlot && sourceState === 'live') {
1022
+ LoggerProxy.logger.info(
1023
+ `StatsAnalyzer:index#processInboundRTPResult --> No frames received for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames received on slot: `,
1024
+ result.framesReceived
1025
+ );
1026
+ }
1027
+ }
1028
+
1029
+ const videoFramesDecodedDiff =
1030
+ result.framesDecoded - this.statsResults[mediaType][sendrecvType].framesDecoded;
1031
+
1032
+ if (videoFramesDecodedDiff === 0) {
1033
+ if (receiveSlot && sourceState === 'live') {
1034
+ LoggerProxy.logger.info(
1035
+ `StatsAnalyzer:index#processInboundRTPResult --> No frames decoded for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames decoded on slot: `,
1036
+ result.framesDecoded
1037
+ );
1038
+ }
1039
+ }
1040
+
1041
+ const videoFramesDroppedDiff =
1042
+ result.framesDropped - this.statsResults[mediaType][sendrecvType].framesDropped;
1043
+
1044
+ if (videoFramesDroppedDiff > 10) {
1045
+ if (receiveSlot && sourceState === 'live') {
1046
+ LoggerProxy.logger.info(
1047
+ `StatsAnalyzer:index#processInboundRTPResult --> Frames dropped for mediaType: ${mediaType}, receive slot ${idAndCsi}. Total frames dropped on slot: `,
1048
+ result.framesDropped
1049
+ );
1050
+ }
1051
+ }
1052
+ }
1053
+
1083
1054
  // Check the over all packet Lost ratio
1084
1055
  this.statsResults[mediaType][sendrecvType].currentPacketLossRatio =
1085
1056
  currentPacketsLost > 0
1086
- ? currentPacketsLost / (currentPacketsReceived + currentPacketsLost)
1057
+ ? currentPacketsLost / (packetsReceivedDiff + currentPacketsLost)
1087
1058
  : 0;
1088
1059
  if (this.statsResults[mediaType][sendrecvType].currentPacketLossRatio > 3) {
1089
1060
  LoggerProxy.logger.info(
@@ -1092,6 +1063,12 @@ export class StatsAnalyzer extends EventsScope {
1092
1063
  );
1093
1064
  }
1094
1065
 
1066
+ if (result.frameWidth && result.frameHeight) {
1067
+ this.statsResults[mediaType][sendrecvType].width = result.frameWidth;
1068
+ this.statsResults[mediaType][sendrecvType].height = result.frameHeight;
1069
+ this.statsResults[mediaType][sendrecvType].framesReceived = result.framesReceived;
1070
+ }
1071
+
1095
1072
  // TODO: check the packet loss value is negative values here
1096
1073
 
1097
1074
  if (result.packetsLost) {
@@ -1109,6 +1086,7 @@ export class StatsAnalyzer extends EventsScope {
1109
1086
  this.statsResults[mediaType][sendrecvType].totalPliCount = result.pliCount;
1110
1087
  this.statsResults[mediaType][sendrecvType].framesDecoded = result.framesDecoded;
1111
1088
  this.statsResults[mediaType][sendrecvType].keyFramesDecoded = result.keyFramesDecoded;
1089
+ this.statsResults[mediaType][sendrecvType].framesDropped = result.framesDropped;
1112
1090
 
1113
1091
  this.statsResults[mediaType][sendrecvType].decoderImplementation =
1114
1092
  result.decoderImplementation;
@@ -239,7 +239,7 @@ skipInNode(describe)('plugin-meetings', () => {
239
239
 
240
240
  // Enabled when config.enableUnifiedMeetings = true
241
241
  xdescribe('Conversation URL', () => {
242
- describe('Successful 1:1 meeting', () => {
242
+ describe('Successful 1:1 meeting', () => {
243
243
  it('Fetch meeting information with a conversation URL for a 1:1 space', async () => {
244
244
  assert.equal(Object.keys(bob.webex.meetings.getAllMeetings()), 0);
245
245
  assert.equal(Object.keys(chris.webex.meetings.getAllMeetings()), 0);
@@ -871,7 +871,7 @@ skipInNode(describe)('plugin-meetings', () => {
871
871
  assert.equal(bob.meeting.shareStatus, 'whiteboard_share_active');
872
872
  }));
873
873
 
874
- it('alice adds chris as guest to 1:1 meeting', () =>
874
+ it('alice adds chris as guest to 1:1 meeting', async() =>
875
875
  Promise.all([
876
876
  testUtils.delayedPromise(alice.meeting.invite({emailAddress: chris.emailAddress})),
877
877
  testUtils.waitForEvents([
@@ -34,7 +34,7 @@ skipInNode(describe)('plugin-meetings', () => {
34
34
  describe('space-meeting', () => {
35
35
  let space = null;
36
36
 
37
- before(() =>
37
+ before(async () =>
38
38
  webexTestUsers
39
39
  .generateTestUsers({
40
40
  count: 4,
@@ -1377,7 +1377,7 @@ describe('plugin-meetings', () => {
1377
1377
  );
1378
1378
  });
1379
1379
 
1380
- const checkMeetingInfoUpdatedCalled = (expected) => {
1380
+ const checkMeetingInfoUpdatedCalled = (expected, payload) => {
1381
1381
  const expectedArgs = [
1382
1382
  locusInfo.emitScoped,
1383
1383
  {
@@ -1385,6 +1385,7 @@ describe('plugin-meetings', () => {
1385
1385
  function: 'updateMeetingInfo',
1386
1386
  },
1387
1387
  LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1388
+ payload
1388
1389
  ];
1389
1390
 
1390
1391
  if (expected) {
@@ -1395,7 +1396,7 @@ describe('plugin-meetings', () => {
1395
1396
  locusInfo.emitScoped.resetHistory();
1396
1397
  };
1397
1398
 
1398
- const checkMeetingInfoUpdatedCalledForRoles = (expected) => {
1399
+ const checkMeetingInfoUpdatedCalledForRoles = (expected, payload) => {
1399
1400
  const expectedArgs = [
1400
1401
  locusInfo.emitScoped,
1401
1402
  {
@@ -1403,6 +1404,7 @@ describe('plugin-meetings', () => {
1403
1404
  function: 'updateMeetingInfo',
1404
1405
  },
1405
1406
  LOCUSINFO.EVENTS.MEETING_INFO_UPDATED,
1407
+ payload
1406
1408
  ];
1407
1409
 
1408
1410
  if (expected) {
@@ -1447,7 +1449,7 @@ describe('plugin-meetings', () => {
1447
1449
 
1448
1450
  // since it was initially undefined, this should trigger the event
1449
1451
 
1450
- checkMeetingInfoUpdatedCalled(true);
1452
+ checkMeetingInfoUpdatedCalled(true, {isInitializing: false});
1451
1453
 
1452
1454
  const newInfo = cloneDeep(meetingInfo);
1453
1455
 
@@ -1472,7 +1474,7 @@ describe('plugin-meetings', () => {
1472
1474
  };
1473
1475
  locusInfo.updateMeetingInfo(newInfo, self);
1474
1476
 
1475
- checkMeetingInfoUpdatedCalled(true);
1477
+ checkMeetingInfoUpdatedCalled(true, {isInitializing: false});
1476
1478
 
1477
1479
  // update it with the same info
1478
1480
  expectedMeeting = {
@@ -1494,7 +1496,7 @@ describe('plugin-meetings', () => {
1494
1496
  locusInfo.updateMeetingInfo(newInfo, self);
1495
1497
 
1496
1498
  // since the info is the same it should not call trigger the event
1497
- checkMeetingInfoUpdatedCalled(false);
1499
+ checkMeetingInfoUpdatedCalled(false, {isInitializing: false});
1498
1500
 
1499
1501
  // update it with the same info, but roles changed
1500
1502
  const updateSelf = cloneDeep(self);
@@ -1525,7 +1527,7 @@ describe('plugin-meetings', () => {
1525
1527
  };
1526
1528
  locusInfo.updateMeetingInfo(newInfo, updateSelf);
1527
1529
  // since the info is the same but roles changed, it should call trigger the event
1528
- checkMeetingInfoUpdatedCalledForRoles(true);
1530
+ checkMeetingInfoUpdatedCalledForRoles(true, {isInitializing: false});
1529
1531
  });
1530
1532
 
1531
1533
  it('gets roles from self if available', () => {
@@ -1546,12 +1548,17 @@ describe('plugin-meetings', () => {
1546
1548
  roles: ['MODERATOR', 'COHOST'],
1547
1549
  };
1548
1550
 
1551
+ sinon.stub(locusInfo, 'emitScoped');
1552
+
1549
1553
  const parsedLocusInfo = cloneDeep(locusInfo.parsedLocus.info);
1550
1554
 
1551
1555
  locusInfo.updateMeetingInfo(initialInfo);
1552
1556
  assert.calledWith(isJoinedSpy, locusInfo.parsedLocus.self);
1553
1557
  assert.neverCalledWith(getRolesSpy, self);
1554
1558
  assert.calledWith(getInfosSpy, parsedLocusInfo, initialInfo, ['MODERATOR', 'COHOST']);
1559
+
1560
+ // since self is not passed to updateMeetingInfo, MEETING_INFO_UPDATED should be triggered with isIntializing: true
1561
+ checkMeetingInfoUpdatedCalledForRoles(true, {isInitializing: true});
1555
1562
  });
1556
1563
  });
1557
1564