@webex/plugin-meetings 3.0.0-beta.404 → 3.0.0-beta.406

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.
@@ -264,7 +264,7 @@ export class StatsAnalyzer extends EventsScope {
264
264
 
265
265
  // Add stats for individual streams
266
266
  Object.keys(this.statsResults).forEach((mediaType) => {
267
- if (mediaType.includes('audio-send')) {
267
+ if (mediaType.startsWith('audio-send')) {
268
268
  const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
269
269
 
270
270
  getAudioSenderStreamMqa({
@@ -276,7 +276,7 @@ export class StatsAnalyzer extends EventsScope {
276
276
  newMqa.audioTransmit[0].streams.push(audioSenderStream);
277
277
 
278
278
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
279
- } else if (mediaType.includes('audio-share-send')) {
279
+ } else if (mediaType.startsWith('audio-share-send')) {
280
280
  const audioSenderStream = cloneDeep(emptyAudioTransmitStream);
281
281
 
282
282
  getAudioSenderStreamMqa({
@@ -288,7 +288,7 @@ export class StatsAnalyzer extends EventsScope {
288
288
  newMqa.audioTransmit[1].streams.push(audioSenderStream);
289
289
 
290
290
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
291
- } else if (mediaType.includes('audio-recv')) {
291
+ } else if (mediaType.startsWith('audio-recv')) {
292
292
  const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
293
293
 
294
294
  getAudioReceiverStreamMqa({
@@ -300,7 +300,7 @@ export class StatsAnalyzer extends EventsScope {
300
300
  newMqa.audioReceive[0].streams.push(audioReceiverStream);
301
301
 
302
302
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
303
- } else if (mediaType.includes('audio-share-recv')) {
303
+ } else if (mediaType.startsWith('audio-share-recv')) {
304
304
  const audioReceiverStream = cloneDeep(emptyAudioReceiveStream);
305
305
 
306
306
  getAudioReceiverStreamMqa({
@@ -312,7 +312,8 @@ export class StatsAnalyzer extends EventsScope {
312
312
  newMqa.audioReceive[1].streams.push(audioReceiverStream);
313
313
 
314
314
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
315
- } else if (mediaType.includes('video-send')) {
315
+ } else if (mediaType.startsWith('video-send-layer')) {
316
+ // We only want the stream-specific stats we get with video-send-layer-0, video-send-layer-1, etc.
316
317
  const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
317
318
 
318
319
  getVideoSenderStreamMqa({
@@ -324,7 +325,7 @@ export class StatsAnalyzer extends EventsScope {
324
325
  newMqa.videoTransmit[0].streams.push(videoSenderStream);
325
326
 
326
327
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
327
- } else if (mediaType.includes('video-share-send')) {
328
+ } else if (mediaType.startsWith('video-share-send')) {
328
329
  const videoSenderStream = cloneDeep(emptyVideoTransmitStream);
329
330
 
330
331
  getVideoSenderStreamMqa({
@@ -336,7 +337,7 @@ export class StatsAnalyzer extends EventsScope {
336
337
  newMqa.videoTransmit[1].streams.push(videoSenderStream);
337
338
 
338
339
  this.lastMqaDataSent[mediaType].send = cloneDeep(this.statsResults[mediaType].send);
339
- } else if (mediaType.includes('video-recv')) {
340
+ } else if (mediaType.startsWith('video-recv')) {
340
341
  const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
341
342
 
342
343
  getVideoReceiverStreamMqa({
@@ -348,7 +349,7 @@ export class StatsAnalyzer extends EventsScope {
348
349
  newMqa.videoReceive[0].streams.push(videoReceiverStream);
349
350
 
350
351
  this.lastMqaDataSent[mediaType].recv = cloneDeep(this.statsResults[mediaType].recv);
351
- } else if (mediaType.includes('video-share-recv')) {
352
+ } else if (mediaType.startsWith('video-share-recv')) {
352
353
  const videoReceiverStream = cloneDeep(emptyVideoReceiveStream);
353
354
 
354
355
  getVideoReceiverStreamMqa({
@@ -373,9 +374,14 @@ export class StatsAnalyzer extends EventsScope {
373
374
  name: MEDIA_DEVICES.MICROPHONE,
374
375
  });
375
376
  }
376
- if (this.statsResults['video-send']) {
377
+
378
+ const existingVideoSender = Object.keys(this.statsResults).find((item) =>
379
+ item.includes('video-send')
380
+ );
381
+
382
+ if (existingVideoSender) {
377
383
  newMqa.intervalMetadata.peripherals.push({
378
- information: this.statsResults['video-send'].trackLabel || _UNKNOWN_,
384
+ information: this.statsResults[existingVideoSender].trackLabel || _UNKNOWN_,
379
385
  name: MEDIA_DEVICES.CAMERA,
380
386
  });
381
387
  }
@@ -552,9 +558,21 @@ export class StatsAnalyzer extends EventsScope {
552
558
  }
553
559
  });
554
560
 
561
+ let videoSenderIndex = 0;
555
562
  statsItem.report.forEach((result) => {
556
563
  if (types.includes(result.type)) {
557
- this.parseGetStatsResult(result, type, isSender);
564
+ // if the video sender has multiple streams in the report, it is a new stream object.
565
+ if (type === 'video-send' && result.type === 'outbound-rtp') {
566
+ const newType = `video-send-layer-${videoSenderIndex}`;
567
+ this.parseGetStatsResult(result, newType, isSender);
568
+ videoSenderIndex += 1;
569
+
570
+ this.statsResults[newType].direction = statsItem.currentDirection;
571
+ this.statsResults[newType].trackLabel = statsItem.localTrackLabel;
572
+ this.statsResults[newType].csi = statsItem.csi;
573
+ } else {
574
+ this.parseGetStatsResult(result, type, isSender);
575
+ }
558
576
  }
559
577
  });
560
578
 
@@ -664,12 +682,23 @@ export class StatsAnalyzer extends EventsScope {
664
682
  const getCurrentStatsTotals = (keyPrefix: string, value: string): number =>
665
683
  Object.keys(this.statsResults)
666
684
  .filter((key) => key.startsWith(keyPrefix))
667
- .reduce((prev, cur) => prev + (this.statsResults[cur]?.recv[value] || 0), 0);
685
+ .reduce(
686
+ (prev, cur) =>
687
+ prev +
688
+ (this.statsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] || 0),
689
+ 0
690
+ );
668
691
 
669
692
  const getPreviousStatsTotals = (keyPrefix: string, value: string): number =>
670
693
  Object.keys(this.statsResults)
671
694
  .filter((key) => key.startsWith(keyPrefix))
672
- .reduce((prev, cur) => prev + (this.lastStatsResults[cur]?.recv[value] || 0), 0);
695
+ .reduce(
696
+ (prev, cur) =>
697
+ prev +
698
+ (this.lastStatsResults[cur]?.[keyPrefix.includes('send') ? 'send' : 'recv'][value] ||
699
+ 0),
700
+ 0
701
+ );
673
702
 
674
703
  // Audio Transmit
675
704
  if (this.lastStatsResults['audio-send']) {
@@ -731,47 +760,54 @@ export class StatsAnalyzer extends EventsScope {
731
760
  false
732
761
  );
733
762
 
763
+ const currentTotalPacketsSent = getCurrentStatsTotals('video-send', 'totalPacketsSent');
764
+ const previousTotalPacketsSent = getPreviousStatsTotals('video-send', 'totalPacketsSent');
765
+
766
+ const currentFramesEncoded = getCurrentStatsTotals('video-send', 'framesEncoded');
767
+ const previousFramesEncoded = getPreviousStatsTotals('video-send', 'framesEncoded');
768
+
769
+ const currentFramesSent = getCurrentStatsTotals('video-send', 'framesSent');
770
+ const previousFramesSent = getPreviousStatsTotals('video-send', 'framesSent');
771
+
772
+ const doesVideoSendExist = Object.keys(this.lastStatsResults).some((item) =>
773
+ item.includes('video-send')
774
+ );
775
+
734
776
  // Video Transmit
735
- if (this.lastStatsResults['video-send']) {
777
+ if (doesVideoSendExist) {
736
778
  // compare video stats sent
737
- const currentStats = this.statsResults['video-send'].send;
738
- const previousStats = this.lastStatsResults['video-send'].send;
739
779
 
740
780
  if (
741
781
  this.meetingMediaStatus.expected.sendVideo &&
742
- (currentStats.totalPacketsSent === previousStats.totalPacketsSent ||
743
- currentStats.totalPacketsSent === 0)
782
+ (currentTotalPacketsSent === previousTotalPacketsSent || currentTotalPacketsSent === 0)
744
783
  ) {
745
784
  LoggerProxy.logger.info(
746
785
  `StatsAnalyzer:index#compareLastStatsResult --> No video RTP packets sent`,
747
- currentStats.totalPacketsSent
786
+ currentTotalPacketsSent
748
787
  );
749
788
  } else {
750
789
  if (
751
790
  this.meetingMediaStatus.expected.sendVideo &&
752
- (currentStats.framesEncoded === previousStats.framesEncoded ||
753
- currentStats.framesEncoded === 0)
791
+ (currentFramesEncoded === previousFramesEncoded || currentFramesEncoded === 0)
754
792
  ) {
755
793
  LoggerProxy.logger.info(
756
794
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames Encoded`,
757
- currentStats.framesEncoded
795
+ currentFramesEncoded
758
796
  );
759
797
  }
760
798
 
761
799
  if (
762
800
  this.meetingMediaStatus.expected.sendVideo &&
763
- (this.statsResults['video-send'].send.framesSent ===
764
- this.lastStatsResults['video-send'].send.framesSent ||
765
- this.statsResults['video-send'].send.framesSent === 0)
801
+ (currentFramesSent === previousFramesSent || currentFramesSent === 0)
766
802
  ) {
767
803
  LoggerProxy.logger.info(
768
804
  `StatsAnalyzer:index#compareLastStatsResult --> No video Frames sent`,
769
- this.statsResults['video-send'].send.framesSent
805
+ currentFramesSent
770
806
  );
771
807
  }
772
808
  }
773
809
 
774
- this.emitStartStopEvents('video', previousStats.framesSent, currentStats.framesSent, true);
810
+ this.emitStartStopEvents('video', previousFramesSent, currentFramesSent, true);
775
811
  }
776
812
 
777
813
  // Video Receive
@@ -8630,6 +8630,34 @@ describe('plugin-meetings', () => {
8630
8630
 
8631
8631
  checkParseMeetingInfo(expectedInfoToParse);
8632
8632
  });
8633
+
8634
+ it('should parse meeting info, set values, and return null when permissionToken is not present', () => {
8635
+ meeting.config.experimental = {enableMediaNegotiatedEvent: true};
8636
+ meeting.config.experimental.enableUnifiedMeetings = true;
8637
+ const FAKE_STRING_DESTINATION = 'sipUrl';
8638
+ const FAKE_MEETING_INFO = {
8639
+ conversationUrl: uuid1,
8640
+ locusUrl: url1,
8641
+ meetingJoinUrl: url2,
8642
+ meetingNumber: '12345',
8643
+ sipMeetingUri: test1,
8644
+ sipUrl: test1,
8645
+ owner: test2,
8646
+ };
8647
+
8648
+ meeting.parseMeetingInfo(FAKE_MEETING_INFO, FAKE_STRING_DESTINATION);
8649
+ const expectedInfoToParse = {
8650
+ conversationUrl: uuid1,
8651
+ locusUrl: url1,
8652
+ sipUri: test1,
8653
+ meetingNumber: '12345',
8654
+ meetingJoinUrl: url2,
8655
+ owner: test2,
8656
+ };
8657
+
8658
+ checkParseMeetingInfo(expectedInfoToParse);
8659
+ });
8660
+
8633
8661
  it('should parse interpretation info correctly', () => {
8634
8662
  const parseInterpretationInfo = sinon.spy(MeetingUtil, 'parseInterpretationInfo');
8635
8663
  const mockToggleOnData = {
@@ -1543,6 +1543,185 @@ describe('plugin-meetings', () => {
1543
1543
  },
1544
1544
  ]);
1545
1545
  });
1546
+
1547
+ it('has three streams for video senders for simulcast', async () => {
1548
+ pc.getTransceiverStats = sinon.stub().resolves({
1549
+ audio: {
1550
+ senders: [fakeStats.audio.senders[0]],
1551
+ receivers: [fakeStats.audio.receivers[0]],
1552
+ },
1553
+ video: {
1554
+ senders: [
1555
+ {
1556
+ localTrackLabel: 'fake-camera',
1557
+ report: [
1558
+ {
1559
+ type: 'outbound-rtp',
1560
+ bytesSent: 1,
1561
+ framesSent: 0,
1562
+ packetsSent: 0,
1563
+ },
1564
+ {
1565
+ type: 'outbound-rtp',
1566
+ bytesSent: 0,
1567
+ framesSent: 0,
1568
+ packetsSent: 0,
1569
+ },
1570
+ {
1571
+ type: 'outbound-rtp',
1572
+ bytesSent: 1000,
1573
+ framesSent: 1,
1574
+ packetsSent: 1,
1575
+ },
1576
+ {
1577
+ type: 'remote-inbound-rtp',
1578
+ packetsLost: 0,
1579
+ },
1580
+ {
1581
+ type: 'candidate-pair',
1582
+ state: 'succeeded',
1583
+ localCandidateId: 'fake-candidate-id',
1584
+ },
1585
+ {
1586
+ type: 'candidate-pair',
1587
+ state: 'failed',
1588
+ localCandidateId: 'bad-candidate-id',
1589
+ },
1590
+ {
1591
+ type: 'local-candidate',
1592
+ id: 'fake-candidate-id',
1593
+ protocol: 'tcp',
1594
+ },
1595
+ ],
1596
+ },
1597
+ ],
1598
+ receivers: [fakeStats.video.receivers[0]],
1599
+ },
1600
+ screenShareAudio: {
1601
+ senders: [fakeStats.audio.senders[0]],
1602
+ receivers: [fakeStats.audio.receivers[0]],
1603
+ },
1604
+ screenShareVideo: {
1605
+ senders: [fakeStats.video.senders[0]],
1606
+ receivers: [fakeStats.video.receivers[0]],
1607
+ },
1608
+ });
1609
+
1610
+ await startStatsAnalyzer({expected: {receiveVideo: true}});
1611
+
1612
+ await progressTime();
1613
+
1614
+ assert.deepEqual(mqeData.videoTransmit[0].streams, [
1615
+ {
1616
+ common: {
1617
+ codec: 'H264',
1618
+ csi: [],
1619
+ duplicateSsci: 0,
1620
+ requestedBitrate: 0,
1621
+ requestedFrames: 0,
1622
+ rtpPackets: 0,
1623
+ ssci: 0,
1624
+ transmittedBitrate: 0.13333333333333333,
1625
+ transmittedFrameRate: 0
1626
+ },
1627
+ h264CodecProfile: 'BP',
1628
+ isAvatar: false,
1629
+ isHardwareEncoded: false,
1630
+ localConfigurationChanges: 2,
1631
+ maxFrameQp: 0,
1632
+ maxNoiseLevel: 0,
1633
+ minRegionQp: 0,
1634
+ remoteConfigurationChanges: 0,
1635
+ requestedFrameSize: 0,
1636
+ requestedKeyFrames: 0,
1637
+ transmittedFrameSize: 0,
1638
+ transmittedHeight: 0,
1639
+ transmittedKeyFrames: 0,
1640
+ transmittedKeyFramesClient: 0,
1641
+ transmittedKeyFramesConfigurationChange: 0,
1642
+ transmittedKeyFramesFeedback: 0,
1643
+ transmittedKeyFramesLocalDrop: 0,
1644
+ transmittedKeyFramesOtherLayer: 0,
1645
+ transmittedKeyFramesPeriodic: 0,
1646
+ transmittedKeyFramesSceneChange: 0,
1647
+ transmittedKeyFramesStartup: 0,
1648
+ transmittedKeyFramesUnknown: 0,
1649
+ transmittedWidth: 0,
1650
+ },
1651
+ {
1652
+ common: {
1653
+ codec: 'H264',
1654
+ csi: [],
1655
+ duplicateSsci: 0,
1656
+ requestedBitrate: 0,
1657
+ requestedFrames: 0,
1658
+ rtpPackets: 0,
1659
+ ssci: 0,
1660
+ transmittedBitrate: 0,
1661
+ transmittedFrameRate: 0,
1662
+ },
1663
+ h264CodecProfile: 'BP',
1664
+ isAvatar: false,
1665
+ isHardwareEncoded: false,
1666
+ localConfigurationChanges: 2,
1667
+ maxFrameQp: 0,
1668
+ maxNoiseLevel: 0,
1669
+ minRegionQp: 0,
1670
+ remoteConfigurationChanges: 0,
1671
+ requestedFrameSize: 0,
1672
+ requestedKeyFrames: 0,
1673
+ transmittedFrameSize: 0,
1674
+ transmittedHeight: 0,
1675
+ transmittedKeyFrames: 0,
1676
+ transmittedKeyFramesClient: 0,
1677
+ transmittedKeyFramesConfigurationChange: 0,
1678
+ transmittedKeyFramesFeedback: 0,
1679
+ transmittedKeyFramesLocalDrop: 0,
1680
+ transmittedKeyFramesOtherLayer: 0,
1681
+ transmittedKeyFramesPeriodic: 0,
1682
+ transmittedKeyFramesSceneChange: 0,
1683
+ transmittedKeyFramesStartup: 0,
1684
+ transmittedKeyFramesUnknown: 0,
1685
+ transmittedWidth: 0,
1686
+ },
1687
+ {
1688
+ common: {
1689
+ codec: 'H264',
1690
+ csi: [],
1691
+ duplicateSsci: 0,
1692
+ requestedBitrate: 0,
1693
+ requestedFrames: 0,
1694
+ rtpPackets: 1,
1695
+ ssci: 0,
1696
+ transmittedBitrate: 133.33333333333334,
1697
+ transmittedFrameRate: 0,
1698
+ },
1699
+ h264CodecProfile: 'BP',
1700
+ isAvatar: false,
1701
+ isHardwareEncoded: false,
1702
+ localConfigurationChanges: 2,
1703
+ maxFrameQp: 0,
1704
+ maxNoiseLevel: 0,
1705
+ minRegionQp: 0,
1706
+ remoteConfigurationChanges: 0,
1707
+ requestedFrameSize: 0,
1708
+ requestedKeyFrames: 0,
1709
+ transmittedFrameSize: 0,
1710
+ transmittedHeight: 0,
1711
+ transmittedKeyFrames: 0,
1712
+ transmittedKeyFramesClient: 0,
1713
+ transmittedKeyFramesConfigurationChange: 0,
1714
+ transmittedKeyFramesFeedback: 0,
1715
+ transmittedKeyFramesLocalDrop: 0,
1716
+ transmittedKeyFramesOtherLayer: 0,
1717
+ transmittedKeyFramesPeriodic: 0,
1718
+ transmittedKeyFramesSceneChange: 0,
1719
+ transmittedKeyFramesStartup: 0,
1720
+ transmittedKeyFramesUnknown: 0,
1721
+ transmittedWidth: 0,
1722
+ }
1723
+ ]);
1724
+ });
1546
1725
  });
1547
1726
  });
1548
1727
  });