@webex/plugin-meetings 3.0.0-beta.250 → 3.0.0-beta.252

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 (65) hide show
  1. package/dist/breakouts/breakout.js +1 -1
  2. package/dist/breakouts/index.js +1 -1
  3. package/dist/constants.js +3 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +30 -24
  6. package/dist/index.js.map +1 -1
  7. package/dist/interpretation/index.js +1 -1
  8. package/dist/interpretation/siLanguage.js +1 -1
  9. package/dist/media/index.js +18 -19
  10. package/dist/media/index.js.map +1 -1
  11. package/dist/media/properties.js +52 -52
  12. package/dist/media/properties.js.map +1 -1
  13. package/dist/meeting/index.js +417 -372
  14. package/dist/meeting/index.js.map +1 -1
  15. package/dist/meeting/muteState.js +39 -38
  16. package/dist/meeting/muteState.js.map +1 -1
  17. package/dist/meeting/util.js +9 -9
  18. package/dist/meeting/util.js.map +1 -1
  19. package/dist/meeting-info/index.js +45 -23
  20. package/dist/meeting-info/index.js.map +1 -1
  21. package/dist/meeting-info/meeting-info-v2.js +22 -4
  22. package/dist/meeting-info/meeting-info-v2.js.map +1 -1
  23. package/dist/meetings/index.js +4 -2
  24. package/dist/meetings/index.js.map +1 -1
  25. package/dist/multistream/sendSlotManager.js +233 -0
  26. package/dist/multistream/sendSlotManager.js.map +1 -0
  27. package/dist/reconnection-manager/index.js +10 -10
  28. package/dist/reconnection-manager/index.js.map +1 -1
  29. package/dist/types/constants.d.ts +2 -0
  30. package/dist/types/index.d.ts +1 -1
  31. package/dist/types/media/index.d.ts +2 -2
  32. package/dist/types/media/properties.d.ts +24 -24
  33. package/dist/types/meeting/index.d.ts +61 -51
  34. package/dist/types/meeting/muteState.d.ts +16 -16
  35. package/dist/types/meeting/util.d.ts +2 -2
  36. package/dist/types/meeting-info/index.d.ts +7 -0
  37. package/dist/types/meeting-info/meeting-info-v2.d.ts +1 -0
  38. package/dist/types/multistream/sendSlotManager.d.ts +61 -0
  39. package/dist/types/reconnection-manager/index.d.ts +2 -2
  40. package/package.json +20 -20
  41. package/src/constants.ts +2 -0
  42. package/src/index.ts +14 -13
  43. package/src/media/index.ts +32 -34
  44. package/src/media/properties.ts +47 -46
  45. package/src/meeting/index.ts +402 -331
  46. package/src/meeting/muteState.ts +35 -34
  47. package/src/meeting/util.ts +11 -10
  48. package/src/meeting-info/index.ts +45 -20
  49. package/src/meeting-info/meeting-info-v2.ts +25 -5
  50. package/src/meetings/index.ts +9 -2
  51. package/src/multistream/sendSlotManager.ts +170 -0
  52. package/src/reconnection-manager/index.ts +8 -8
  53. package/test/integration/spec/converged-space-meetings.js +7 -7
  54. package/test/integration/spec/journey.js +85 -103
  55. package/test/integration/spec/space-meeting.js +9 -9
  56. package/test/unit/spec/media/index.ts +23 -66
  57. package/test/unit/spec/meeting/index.js +786 -848
  58. package/test/unit/spec/meeting/muteState.js +113 -75
  59. package/test/unit/spec/meeting/utils.js +14 -16
  60. package/test/unit/spec/meeting-info/index.js +173 -61
  61. package/test/unit/spec/meeting-info/meetinginfov2.js +186 -52
  62. package/test/unit/spec/meetings/index.js +6 -5
  63. package/test/unit/spec/multistream/sendSlotManager.ts +242 -0
  64. package/test/unit/spec/reconnection-manager/index.js +4 -3
  65. package/test/utils/integrationTestUtils.js +4 -4
@@ -38,7 +38,9 @@ import {
38
38
  RemoteTrackType,
39
39
  MediaType,
40
40
  } from '@webex/internal-media-core';
41
- import {LocalTrackEvents} from '@webex/media-helpers';
41
+ import {
42
+ StreamEventNames,
43
+ } from '@webex/media-helpers';
42
44
  import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
43
45
  import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
44
46
  import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
@@ -65,6 +67,7 @@ import Metrics from '@webex/plugin-meetings/src/metrics';
65
67
  import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
66
68
  import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
67
69
  import * as ReceiveSlotManagerModule from '@webex/plugin-meetings/src/multistream/receiveSlotManager';
70
+ import * as SendSlotManagerModule from '@webex/plugin-meetings/src/multistream/sendSlotManager';
68
71
 
69
72
  import LLM from '@webex/internal-plugin-llm';
70
73
  import Mercury from '@webex/internal-plugin-mercury';
@@ -433,6 +436,35 @@ describe('plugin-meetings', () => {
433
436
  assert.equal(memberId, undefined);
434
437
  });
435
438
  });
439
+
440
+ describe('creates SendSlot manager instance', () => {
441
+ let mockSendSlotManagerCtor;
442
+
443
+ beforeEach(() => {
444
+ mockSendSlotManagerCtor = sinon
445
+ .stub(SendSlotManagerModule,'default');
446
+
447
+ meeting = new Meeting(
448
+ {
449
+ userId: uuid1,
450
+ resource: uuid2,
451
+ deviceUrl: uuid3,
452
+ locus: {url: url1},
453
+ destination: testDestination,
454
+ destinationType: _MEETING_ID_,
455
+ },
456
+ {
457
+ parent: webex,
458
+ }
459
+ );
460
+
461
+ meeting.mediaProperties.webrtcMediaConnection = {createSendSlot: sinon.stub()};
462
+ });
463
+
464
+ it('calls SendSlotManager constructor', () => {
465
+ assert.calledOnce(mockSendSlotManagerCtor);
466
+ });
467
+ });
436
468
  });
437
469
 
438
470
  describe('#isLocusCall', () => {
@@ -824,75 +856,8 @@ describe('plugin-meetings', () => {
824
856
  sinon.assert.called(setCorrelationIdSpy);
825
857
  assert.equal(meeting.correlationId, '123');
826
858
  });
827
-
828
- it('should send Meeting Info CA events if meetingInfo is not empty', async () => {
829
- meeting.meetingInfo = {info: 'info', meetingLookupUrl: 'url'};
830
-
831
- const join = meeting.join();
832
-
833
- assert.calledWithMatch(webex.internal.newMetrics.submitClientEvent, {
834
- name: 'client.call.initiated',
835
- payload: {trigger: 'user-interaction', isRoapCallEnabled: true},
836
- options: {meetingId: meeting.id},
837
- });
838
-
839
- assert.exists(join.then);
840
- const result = await join;
841
-
842
- assert.calledOnce(MeetingUtil.joinMeeting);
843
- assert.calledOnce(meeting.setLocus);
844
- assert.equal(result, joinMeetingResult);
845
-
846
- assert.calledThrice(webex.internal.newMetrics.submitClientEvent);
847
-
848
- assert.deepEqual(webex.internal.newMetrics.submitClientEvent.getCall(0).args[0], {
849
- name: 'client.call.initiated',
850
- payload: {
851
- trigger: 'user-interaction',
852
- isRoapCallEnabled: true,
853
- },
854
- options: {meetingId: meeting.id},
855
- });
856
-
857
- assert.deepEqual(webex.internal.newMetrics.submitClientEvent.getCall(1).args[0], {
858
- name: 'client.meetinginfo.request',
859
- options: {meetingId: meeting.id},
860
- });
861
- assert.deepEqual(webex.internal.newMetrics.submitClientEvent.getCall(2).args[0], {
862
- name: 'client.meetinginfo.response',
863
- payload: {
864
- identifiers: {meetingLookupUrl: 'url'},
865
- },
866
- options: {meetingId: meeting.id},
867
- });
868
- });
869
-
870
- it('should not send Meeting Info CA events if meetingInfo is empty', async () => {
871
- meeting.meetingInfo = {};
872
-
873
- const join = meeting.join();
874
-
875
- assert.calledWith(webex.internal.newMetrics.submitClientEvent, {
876
- name: 'client.call.initiated',
877
- payload: {trigger: 'user-interaction', isRoapCallEnabled: true},
878
- options: {meetingId: meeting.id},
879
- });
880
-
881
- assert.exists(join.then);
882
- const result = await join;
883
-
884
- assert.calledOnce(MeetingUtil.joinMeeting);
885
- assert.calledOnce(meeting.setLocus);
886
- assert.equal(result, joinMeetingResult);
887
-
888
- assert.calledOnce(webex.internal.newMetrics.submitClientEvent);
889
-
890
- assert.equal(
891
- webex.internal.newMetrics.submitClientEvent.getCall(0).args[0].name,
892
- 'client.call.initiated'
893
- );
894
- });
895
859
  });
860
+
896
861
  describe('failure', () => {
897
862
  beforeEach(() => {
898
863
  sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.reject());
@@ -1624,440 +1589,424 @@ describe('plugin-meetings', () => {
1624
1589
  to @webex/internal-media-core when addMedia, updateMedia, publishTracks, unpublishTracks are called
1625
1590
  in various combinations.
1626
1591
  */
1627
- [true, false].forEach((isMultistream) =>
1628
- describe(`addMedia/updateMedia semi-integration tests (${
1629
- isMultistream ? 'multistream' : 'transcoded'
1630
- })`, () => {
1631
- const webrtcAudioTrack = {
1632
- id: 'underlying audio track',
1633
- getSettings: sinon.stub().returns({deviceId: 'fake device id for audio track'}),
1634
- };
1635
-
1636
- let fakeMicrophoneTrack;
1637
- let fakeRoapMediaConnection;
1638
- let fakeMultistreamRoapMediaConnection;
1639
- let roapMediaConnectionConstructorStub;
1640
- let multistreamRoapMediaConnectionConstructorStub;
1641
- let locusMediaRequestStub; // stub for /media requests to Locus
1592
+ [true,false].forEach((isMultistream) =>
1593
+ describe(`addMedia/updateMedia semi-integration tests (${isMultistream ? 'multistream' : 'transcoded'})`, () => {
1594
+ let fakeMicrophoneStream;
1595
+ let fakeRoapMediaConnection;
1596
+ let fakeMultistreamRoapMediaConnection;
1597
+ let roapMediaConnectionConstructorStub;
1598
+ let multistreamRoapMediaConnectionConstructorStub;
1599
+ let locusMediaRequestStub; // stub for /media requests to Locus
1642
1600
 
1643
- const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
1601
+ const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
1644
1602
 
1645
- let expectedMediaConnectionConfig;
1646
- let expectedDebugId;
1603
+ let expectedMediaConnectionConfig;
1604
+ let expectedDebugId;
1647
1605
 
1648
- let clock;
1606
+ let clock;
1649
1607
 
1650
- beforeEach(() => {
1651
- clock = sinon.useFakeTimers();
1608
+ beforeEach(() => {
1609
+ clock = sinon.useFakeTimers();
1652
1610
 
1653
- sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
1611
+ sinon.stub(MeetingUtil, 'getIpVersion').returns(IP_VERSION.unknown);
1654
1612
 
1655
- meeting.deviceUrl = 'deviceUrl';
1656
- meeting.config.deviceType = 'web';
1657
- meeting.isMultistream = isMultistream;
1658
- meeting.meetingState = 'ACTIVE';
1659
- meeting.mediaId = 'fake media id';
1660
- meeting.selfUrl = 'selfUrl';
1661
- meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
1662
- meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
1663
- meeting.setMercuryListener = sinon.stub();
1664
- meeting.locusInfo.onFullLocus = sinon.stub();
1665
- meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
1666
- meeting.webex.meetings.reachability = {
1667
- isAnyClusterReachable: sinon.stub().resolves(true),
1668
- };
1669
- meeting.roap.doTurnDiscovery = sinon
1670
- .stub()
1671
- .resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
1672
-
1673
- StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
1674
-
1675
- // setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
1676
- expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
1677
- expectedMediaConnectionConfig = {
1678
- iceServers: [{urls: undefined, username: '', credential: ''}],
1679
- skipInactiveTransceivers: false,
1680
- requireH264: true,
1681
- sdpMunging: {
1682
- convertPort9to0: false,
1683
- addContentSlides: true,
1684
- bandwidthLimits: {
1685
- audio: StaticConfig.meetings.bandwidth.audio,
1686
- video: StaticConfig.meetings.bandwidth.video,
1687
- },
1688
- startBitrate: StaticConfig.meetings.bandwidth.startBitrate,
1689
- periodicKeyframes: 20,
1690
- disableExtmap: !meeting.config.enableExtmap,
1691
- disableRtx: !meeting.config.enableRtx,
1613
+ meeting.deviceUrl = 'deviceUrl';
1614
+ meeting.config.deviceType = 'web';
1615
+ meeting.isMultistream = isMultistream;
1616
+ meeting.meetingState = 'ACTIVE';
1617
+ meeting.mediaId = 'fake media id';
1618
+ meeting.selfUrl = 'selfUrl';
1619
+ meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
1620
+ meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
1621
+ meeting.setMercuryListener = sinon.stub();
1622
+ meeting.locusInfo.onFullLocus = sinon.stub();
1623
+ meeting.webex.meetings.geoHintInfo = {regionCode: 'EU', countryCode: 'UK'};
1624
+ meeting.webex.meetings.reachability = {
1625
+ isAnyClusterReachable: sinon.stub().resolves(true),
1626
+ };
1627
+ meeting.roap.doTurnDiscovery = sinon
1628
+ .stub()
1629
+ .resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
1630
+
1631
+ StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
1632
+
1633
+ // setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
1634
+ expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
1635
+ expectedMediaConnectionConfig = {
1636
+ iceServers: [{urls: undefined, username: '', credential: ''}],
1637
+ skipInactiveTransceivers: false,
1638
+ requireH264: true,
1639
+ sdpMunging: {
1640
+ convertPort9to0: false,
1641
+ addContentSlides: true,
1642
+ bandwidthLimits: {
1643
+ audio: StaticConfig.meetings.bandwidth.audio,
1644
+ video: StaticConfig.meetings.bandwidth.video,
1692
1645
  },
1693
- };
1646
+ startBitrate: StaticConfig.meetings.bandwidth.startBitrate,
1647
+ periodicKeyframes: 20,
1648
+ disableExtmap: !meeting.config.enableExtmap,
1649
+ disableRtx: !meeting.config.enableRtx,
1650
+ },
1651
+ };
1694
1652
 
1695
- // setup stubs
1696
- fakeMicrophoneTrack = {
1697
- id: 'fake mic',
1698
- on: sinon.stub(),
1699
- off: sinon.stub(),
1700
- setUnmuteAllowed: sinon.stub(),
1701
- setMuted: sinon.stub(),
1702
- setPublished: sinon.stub(),
1703
- muted: false,
1704
- underlyingTrack: webrtcAudioTrack,
1705
- };
1653
+ // setup stubs
1654
+ fakeMicrophoneStream = {
1655
+ on: sinon.stub(),
1656
+ off: sinon.stub(),
1657
+ getSettings: sinon.stub().returns({
1658
+ deviceId: 'some device id'
1659
+ }),
1660
+ muted: false,
1661
+ setUnmuteAllowed: sinon.stub(),
1662
+ setMuted: sinon.stub(),
1663
+ setServerMuted: sinon.stub(),
1664
+ outputTrack: {
1665
+ id: 'fake mic'
1666
+ }
1667
+ }
1706
1668
 
1707
- fakeRoapMediaConnection = {
1708
- id: 'roap media connection',
1709
- close: sinon.stub(),
1710
- getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1711
- initiateOffer: sinon.stub().resolves({}),
1712
- update: sinon.stub().resolves({}),
1713
- on: sinon.stub(),
1714
- };
1669
+ fakeRoapMediaConnection = {
1670
+ id: 'roap media connection',
1671
+ close: sinon.stub(),
1672
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1673
+ initiateOffer: sinon.stub().resolves({}),
1674
+ update: sinon.stub().resolves({}),
1675
+ on: sinon.stub(),
1676
+ };
1715
1677
 
1716
- fakeMultistreamRoapMediaConnection = {
1717
- id: 'multistream roap media connection',
1718
- close: sinon.stub(),
1719
- getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1720
- initiateOffer: sinon.stub().resolves({}),
1721
- publishTrack: sinon.stub().resolves({}),
1722
- unpublishTrack: sinon.stub().resolves({}),
1723
- on: sinon.stub(),
1724
- requestMedia: sinon.stub(),
1725
- createReceiveSlot: sinon.stub().resolves({on: sinon.stub()}),
1726
- enableMultistreamAudio: sinon.stub(),
1727
- };
1678
+ fakeMultistreamRoapMediaConnection = {
1679
+ id: 'multistream roap media connection',
1680
+ close: sinon.stub(),
1681
+ getConnectionState: sinon.stub().returns(ConnectionState.Connected),
1682
+ initiateOffer: sinon.stub().resolves({}),
1683
+ on: sinon.stub(),
1684
+ requestMedia: sinon.stub(),
1685
+ createReceiveSlot: sinon.stub().resolves({on: sinon.stub()}),
1686
+ createSendSlot: sinon.stub().returns({
1687
+ publishStream: sinon.stub(),
1688
+ unpublishStream: sinon.stub(),
1689
+ }),
1690
+ enableMultistreamAudio: sinon.stub(),
1691
+ };
1728
1692
 
1729
- roapMediaConnectionConstructorStub = sinon
1730
- .stub(internalMediaModule, 'RoapMediaConnection')
1731
- .returns(fakeRoapMediaConnection);
1693
+ roapMediaConnectionConstructorStub = sinon
1694
+ .stub(internalMediaModule, 'RoapMediaConnection')
1695
+ .returns(fakeRoapMediaConnection);
1732
1696
 
1733
- multistreamRoapMediaConnectionConstructorStub = sinon
1734
- .stub(internalMediaModule, 'MultistreamRoapMediaConnection')
1735
- .returns(fakeMultistreamRoapMediaConnection);
1697
+ multistreamRoapMediaConnectionConstructorStub = sinon
1698
+ .stub(internalMediaModule, 'MultistreamRoapMediaConnection')
1699
+ .returns(fakeMultistreamRoapMediaConnection);
1736
1700
 
1737
- locusMediaRequestStub = sinon
1738
- .stub(WebexPlugin.prototype, 'request')
1739
- .resolves({body: {locus: {fullState: {}}}});
1740
- });
1701
+ locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({body: {locus: { fullState: {}}}});
1702
+ });
1741
1703
 
1742
- afterEach(() => {
1743
- clock.restore();
1744
- sinon.restore();
1745
- });
1704
+ afterEach(() => {
1705
+ clock.restore();
1706
+ sinon.restore();
1707
+ });
1746
1708
 
1747
- // helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
1748
- const stableState = async () => {
1749
- await testUtils.flushPromises();
1750
- clock.tick(1); // needed because LocusMediaRequest uses Lodash.defer()
1751
- };
1709
+ // helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
1710
+ const stableState = async () => {
1711
+ await testUtils.flushPromises();
1712
+ clock.tick(1); // needed because LocusMediaRequest uses Lodash.defer()
1713
+ }
1752
1714
 
1753
- const resetHistory = () => {
1754
- locusMediaRequestStub.resetHistory();
1755
- fakeRoapMediaConnection.update.resetHistory();
1756
- fakeMultistreamRoapMediaConnection.publishTrack.resetHistory();
1757
- fakeMultistreamRoapMediaConnection.unpublishTrack.resetHistory();
1758
- };
1715
+ const resetHistory = () => {
1716
+ locusMediaRequestStub.resetHistory();
1717
+ fakeRoapMediaConnection.update.resetHistory();
1718
+ try{
1719
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream.resetHistory();
1720
+ }
1721
+ catch(e){}
1722
+ };
1759
1723
 
1760
- const getRoapListener = () => {
1761
- const roapMediaConnectionToCheck = isMultistream
1762
- ? fakeMultistreamRoapMediaConnection
1763
- : fakeRoapMediaConnection;
1724
+ const getRoapListener = () => {
1725
+ const roapMediaConnectionToCheck = isMultistream ? fakeMultistreamRoapMediaConnection : fakeRoapMediaConnection;
1764
1726
 
1765
- for (let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx += 1) {
1766
- if (
1767
- roapMediaConnectionToCheck.on.getCall(idx).args[0] === Event.ROAP_MESSAGE_TO_SEND
1768
- ) {
1769
- return roapMediaConnectionToCheck.on.getCall(idx).args[1];
1770
- }
1727
+ for(let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx+= 1) {
1728
+ if (roapMediaConnectionToCheck.on.getCall(idx).args[0] === Event.ROAP_MESSAGE_TO_SEND) {
1729
+ return roapMediaConnectionToCheck.on.getCall(idx).args[1];
1771
1730
  }
1772
- assert.fail(
1773
- 'listener for "roap:messageToSend" (Event.ROAP_MESSAGE_TO_SEND) was not registered'
1774
- );
1775
1731
  };
1732
+ assert.fail(
1733
+ 'listener for "roap:messageToSend" (Event.ROAP_MESSAGE_TO_SEND) was not registered'
1734
+ );
1735
+ };
1776
1736
 
1777
- // simulates a Roap offer being generated by the RoapMediaConnection
1778
- const simulateRoapOffer = async () => {
1779
- const roapListener = getRoapListener();
1737
+ // simulates a Roap offer being generated by the RoapMediaConnection
1738
+ const simulateRoapOffer = async () => {
1739
+ const roapListener = getRoapListener();
1780
1740
 
1781
- await roapListener({roapMessage: roapOfferMessage});
1782
- await stableState();
1783
- };
1741
+ await roapListener({roapMessage: roapOfferMessage});
1742
+ await stableState();
1743
+ };
1784
1744
 
1785
- const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
1786
- const {sdp, seq, tieBreaker} = roapOfferMessage;
1787
-
1788
- assert.calledWith(locusMediaRequestStub, {
1789
- method: 'PUT',
1790
- uri: `${meeting.selfUrl}/media`,
1791
- body: {
1792
- device: {
1793
- url: meeting.deviceUrl,
1794
- deviceType: meeting.config.deviceType,
1795
- regionCode: 'EU',
1796
- countryCode: 'UK',
1797
- },
1798
- correlationId: meeting.correlationId,
1799
- localMedias: [
1800
- {
1801
- localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}"}}`,
1802
- mediaId: 'fake media id',
1803
- },
1804
- ],
1805
- clientMediaPreferences: {
1806
- preferTranscoding: !meeting.isMultistream,
1807
- joinCookie: undefined,
1808
- ipver: 0,
1809
- },
1810
- },
1811
- });
1812
- };
1745
+ const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
1746
+ const {sdp, seq, tieBreaker} = roapOfferMessage;
1813
1747
 
1814
- const checkLocalMuteSentToLocus = ({audioMuted, videoMuted}) => {
1815
- assert.calledWith(locusMediaRequestStub, {
1816
- method: 'PUT',
1817
- uri: `${meeting.selfUrl}/media`,
1818
- body: {
1819
- device: {
1820
- url: meeting.deviceUrl,
1821
- deviceType: meeting.config.deviceType,
1822
- regionCode: 'EU',
1823
- countryCode: 'UK',
1824
- },
1825
- correlationId: meeting.correlationId,
1826
- localMedias: [
1827
- {
1828
- localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted}}`,
1829
- mediaId: 'fake media id',
1830
- },
1831
- ],
1832
- clientMediaPreferences: {
1833
- preferTranscoding: !meeting.isMultistream,
1834
- ipver: undefined
1748
+ assert.calledWith(locusMediaRequestStub, {
1749
+ method: 'PUT',
1750
+ uri: `${meeting.selfUrl}/media`,
1751
+ body: {
1752
+ device: {
1753
+ url: meeting.deviceUrl,
1754
+ deviceType: meeting.config.deviceType,
1755
+ regionCode: 'EU',
1756
+ countryCode: 'UK',
1757
+ },
1758
+ correlationId: meeting.correlationId,
1759
+ localMedias: [
1760
+ {
1761
+ localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}"}}`,
1762
+ mediaId: 'fake media id',
1835
1763
  },
1836
- respOnlySdp: true,
1837
- usingResource: null,
1764
+ ],
1765
+ clientMediaPreferences: {
1766
+ preferTranscoding: !meeting.isMultistream,
1767
+ joinCookie: undefined,
1768
+ ipver: 0,
1838
1769
  },
1839
- });
1840
- };
1841
-
1842
- const checkMediaConnectionCreated = ({
1843
- mediaConnectionConfig,
1844
- localTracks,
1845
- direction,
1846
- remoteQualityLevel,
1847
- expectedDebugId,
1848
- meetingId,
1849
- }) => {
1850
- if (isMultistream) {
1851
- const {iceServers} = mediaConnectionConfig;
1770
+ },
1771
+ });
1772
+ };
1852
1773
 
1853
- assert.calledOnceWithMatch(
1854
- multistreamRoapMediaConnectionConstructorStub,
1774
+ const checkLocalMuteSentToLocus = ({audioMuted, videoMuted}) => {
1775
+ assert.calledWith(locusMediaRequestStub, {
1776
+ method: 'PUT',
1777
+ uri: `${meeting.selfUrl}/media`,
1778
+ body: {
1779
+ device: {
1780
+ url: meeting.deviceUrl,
1781
+ deviceType: meeting.config.deviceType,
1782
+ regionCode: 'EU',
1783
+ countryCode: 'UK',
1784
+ },
1785
+ correlationId: meeting.correlationId,
1786
+ localMedias: [
1855
1787
  {
1856
- iceServers,
1857
- enableMainAudio: direction.audio !== 'inactive',
1858
- enableMainVideo: true,
1788
+ localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted}}`,
1789
+ mediaId: 'fake media id',
1859
1790
  },
1860
- meetingId
1861
- );
1791
+ ],
1792
+ clientMediaPreferences: {
1793
+ preferTranscoding: !meeting.isMultistream,
1794
+ ipver: undefined
1795
+ },
1796
+ respOnlySdp: true,
1797
+ usingResource: null,
1798
+ },
1799
+ });
1800
+ };
1862
1801
 
1863
- Object.values(localTracks).forEach((track) => {
1864
- if (track) {
1865
- assert.calledOnceWithExactly(
1866
- fakeMultistreamRoapMediaConnection.publishTrack,
1867
- track
1868
- );
1802
+ const checkMediaConnectionCreated = ({mediaConnectionConfig, localStreams, direction, remoteQualityLevel, expectedDebugId, meetingId}) => {
1803
+ if (isMultistream) {
1804
+ const {iceServers} = mediaConnectionConfig;
1805
+
1806
+ assert.calledOnceWithMatch(multistreamRoapMediaConnectionConstructorStub, {
1807
+ iceServers,
1808
+ }, meetingId);
1809
+
1810
+ for(let type in localStreams){
1811
+ const stream = localStreams[type];
1812
+ if(stream !== undefined){
1813
+ switch(type){
1814
+ case 'audio':
1815
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream, stream);
1816
+ break;
1817
+ case 'video':
1818
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream, stream);
1819
+ break;
1820
+ case 'screenShareAudio':
1821
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream, stream);
1822
+ break;
1823
+ case 'screenShareVideo':
1824
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream, stream);
1825
+ break;
1869
1826
  }
1870
- });
1871
- } else {
1872
- assert.calledOnceWithExactly(
1873
- roapMediaConnectionConstructorStub,
1874
- mediaConnectionConfig,
1875
- {
1876
- localTracks: {
1877
- audio: localTracks.audio?.underlyingTrack,
1878
- video: localTracks.video?.underlyingTrack,
1879
- screenShareVideo: localTracks.screenShareVideo?.underlyingTrack,
1880
- },
1881
- direction,
1882
- remoteQualityLevel,
1883
- },
1884
- expectedDebugId
1885
- );
1827
+ }
1886
1828
  }
1887
- };
1888
-
1889
- it('addMedia() works correctly when media is enabled without tracks to publish', async () => {
1890
- await meeting.addMedia();
1891
- await simulateRoapOffer();
1892
-
1893
- // check RoapMediaConnection was created correctly
1894
- checkMediaConnectionCreated({
1895
- mediaConnectionConfig: expectedMediaConnectionConfig,
1896
- localTracks: {
1897
- audio: undefined,
1898
- video: undefined,
1899
- screenShareVideo: undefined,
1900
- screenShareAudio: undefined,
1901
- },
1902
- direction: {
1903
- audio: 'sendrecv',
1904
- video: 'sendrecv',
1905
- screenShareVideo: 'recvonly',
1829
+ } else {
1830
+ assert.calledOnceWithExactly(roapMediaConnectionConstructorStub, mediaConnectionConfig,
1831
+ {
1832
+ localTracks: {
1833
+ audio: localStreams.audio?.outputTrack,
1834
+ video: localStreams.video?.outputTrack,
1835
+ screenShareVideo: localStreams.screenShareVideo?.outputTrack,
1836
+ screenShareAudio: localStreams.screenShareAudio?.outputTrack,
1837
+ },
1838
+ direction,
1839
+ remoteQualityLevel,
1906
1840
  },
1907
- remoteQualityLevel: 'HIGH',
1908
- expectedDebugId,
1909
- meetingId: meeting.id,
1910
- });
1911
-
1912
- // and SDP offer was sent with the right audioMuted/videoMuted values
1913
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
1841
+ expectedDebugId
1842
+ );
1843
+ }
1844
+ };
1914
1845
 
1915
- // and that it was the only /media request that was sent
1916
- assert.calledOnce(locusMediaRequestStub);
1846
+ it('addMedia() works correctly when media is enabled without tracks to publish', async () => {
1847
+ await meeting.addMedia();
1848
+ await simulateRoapOffer();
1849
+
1850
+ // check RoapMediaConnection was created correctly
1851
+ checkMediaConnectionCreated({
1852
+ mediaConnectionConfig: expectedMediaConnectionConfig,
1853
+ localStreams: {
1854
+ audio: undefined,
1855
+ video: undefined,
1856
+ screenShareVideo: undefined,
1857
+ screenShareAudio: undefined,
1858
+ },
1859
+ direction: {
1860
+ audio: 'sendrecv',
1861
+ video: 'sendrecv',
1862
+ screenShareVideo: 'recvonly',
1863
+ },
1864
+ remoteQualityLevel: 'HIGH',
1865
+ expectedDebugId,
1866
+ meetingId: meeting.id
1917
1867
  });
1918
1868
 
1919
- it('addMedia() works correctly when media is enabled with tracks to publish', async () => {
1920
- await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
1921
- await simulateRoapOffer();
1869
+ // and SDP offer was sent with the right audioMuted/videoMuted values
1870
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
1922
1871
 
1923
- // check RoapMediaConnection was created correctly
1924
- checkMediaConnectionCreated({
1925
- mediaConnectionConfig: expectedMediaConnectionConfig,
1926
- localTracks: {
1927
- audio: fakeMicrophoneTrack,
1928
- video: undefined,
1929
- screenShareVideo: undefined,
1930
- screenShareAudio: undefined,
1931
- },
1932
- direction: {
1933
- audio: 'sendrecv',
1934
- video: 'sendrecv',
1935
- screenShareVideo: 'recvonly',
1936
- },
1937
- remoteQualityLevel: 'HIGH',
1938
- expectedDebugId,
1939
- meetingId: meeting.id,
1940
- });
1872
+ // and that it was the only /media request that was sent
1873
+ assert.calledOnce(locusMediaRequestStub);
1874
+ });
1941
1875
 
1942
- // and SDP offer was sent with the right audioMuted/videoMuted values
1943
- checkSdpOfferSent({audioMuted: false, videoMuted: true});
1876
+ it('addMedia() works correctly when media is enabled with streams to publish', async () => {
1877
+ await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
1878
+ await simulateRoapOffer();
1944
1879
 
1945
- // and no other local mute requests were sent to Locus
1946
- assert.calledOnce(locusMediaRequestStub);
1880
+ // check RoapMediaConnection was created correctly
1881
+ checkMediaConnectionCreated({
1882
+ mediaConnectionConfig: expectedMediaConnectionConfig,
1883
+ localStreams: {
1884
+ audio: fakeMicrophoneStream,
1885
+ video: undefined,
1886
+ screenShareVideo: undefined,
1887
+ screenShareAudio: undefined,
1888
+ },
1889
+ direction: {
1890
+ audio: 'sendrecv',
1891
+ video: 'sendrecv',
1892
+ screenShareVideo: 'recvonly',
1893
+ },
1894
+ remoteQualityLevel: 'HIGH',
1895
+ expectedDebugId,
1896
+ meetingId: meeting.id
1947
1897
  });
1948
1898
 
1949
- it('addMedia() works correctly when media is enabled with tracks to publish and track is muted', async () => {
1950
- fakeMicrophoneTrack.muted = true;
1899
+ // and SDP offer was sent with the right audioMuted/videoMuted values
1900
+ checkSdpOfferSent({audioMuted: false, videoMuted: true});
1951
1901
 
1952
- await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
1953
- await simulateRoapOffer();
1902
+ // and no other local mute requests were sent to Locus
1903
+ assert.calledOnce(locusMediaRequestStub);
1904
+ });
1954
1905
 
1955
- // check RoapMediaConnection was created correctly
1956
- checkMediaConnectionCreated({
1957
- mediaConnectionConfig: expectedMediaConnectionConfig,
1958
- localTracks: {
1959
- audio: fakeMicrophoneTrack,
1960
- video: undefined,
1961
- screenShareVideo: undefined,
1962
- screenShareAudio: undefined,
1963
- },
1964
- direction: {
1965
- audio: 'sendrecv',
1966
- video: 'sendrecv',
1967
- screenShareVideo: 'recvonly',
1968
- },
1969
- remoteQualityLevel: 'HIGH',
1970
- expectedDebugId,
1971
- meetingId: meeting.id,
1972
- });
1906
+ it('addMedia() works correctly when media is enabled with tracks to publish and track is muted', async () => {
1907
+ fakeMicrophoneStream.muted = true;
1973
1908
 
1974
- // and SDP offer was sent with the right audioMuted/videoMuted values
1975
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
1909
+ await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
1910
+ await simulateRoapOffer();
1976
1911
 
1977
- // and no other local mute requests were sent to Locus
1978
- assert.calledOnce(locusMediaRequestStub);
1912
+ // check RoapMediaConnection was created correctly
1913
+ checkMediaConnectionCreated({
1914
+ mediaConnectionConfig: expectedMediaConnectionConfig,
1915
+ localStreams: {
1916
+ audio: fakeMicrophoneStream,
1917
+ video: undefined,
1918
+ screenShareVideo: undefined,
1919
+ screenShareAudio: undefined,
1920
+ },
1921
+ direction: {
1922
+ audio: 'sendrecv',
1923
+ video: 'sendrecv',
1924
+ screenShareVideo: 'recvonly',
1925
+ },
1926
+ remoteQualityLevel: 'HIGH',
1927
+ expectedDebugId,
1928
+ meetingId: meeting.id
1979
1929
  });
1930
+ // and SDP offer was sent with the right audioMuted/videoMuted values
1931
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
1980
1932
 
1981
- it('addMedia() works correctly when media is disabled with tracks to publish', async () => {
1982
- await meeting.addMedia({
1983
- localTracks: {microphone: fakeMicrophoneTrack},
1984
- audioEnabled: false,
1985
- });
1986
- await simulateRoapOffer();
1987
-
1988
- // check RoapMediaConnection was created correctly
1989
- checkMediaConnectionCreated({
1990
- mediaConnectionConfig: expectedMediaConnectionConfig,
1991
- localTracks: {
1992
- audio: fakeMicrophoneTrack,
1993
- video: undefined,
1994
- screenShareVideo: undefined,
1995
- screenShareAudio: undefined,
1996
- },
1997
- direction: {
1998
- audio: 'inactive',
1999
- video: 'sendrecv',
2000
- screenShareVideo: 'recvonly',
2001
- },
2002
- remoteQualityLevel: 'HIGH',
2003
- expectedDebugId,
2004
- meetingId: meeting.id,
2005
- });
1933
+ // and no other local mute requests were sent to Locus
1934
+ assert.calledOnce(locusMediaRequestStub);
1935
+ });
2006
1936
 
2007
- // and SDP offer was sent with the right audioMuted/videoMuted values
2008
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
1937
+ it('addMedia() works correctly when media is disabled with tracks to publish', async () => {
1938
+ await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}, audioEnabled: false});
1939
+ await simulateRoapOffer();
2009
1940
 
2010
- // and no other local mute requests were sent to Locus
2011
- assert.calledOnce(locusMediaRequestStub);
1941
+ // check RoapMediaConnection was created correctly
1942
+ checkMediaConnectionCreated({
1943
+ mediaConnectionConfig: expectedMediaConnectionConfig,
1944
+ localStreams: {
1945
+ audio: fakeMicrophoneStream,
1946
+ video: undefined,
1947
+ screenShareVideo: undefined,
1948
+ screenShareAudio: undefined,
1949
+ },
1950
+ direction: {
1951
+ audio: 'inactive',
1952
+ video: 'sendrecv',
1953
+ screenShareVideo: 'recvonly',
1954
+ },
1955
+ remoteQualityLevel: 'HIGH',
1956
+ expectedDebugId,
1957
+ meetingId: meeting.id
2012
1958
  });
2013
1959
 
2014
- it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
2015
- await meeting.addMedia({audioEnabled: false});
2016
- await simulateRoapOffer();
1960
+ // and SDP offer was sent with the right audioMuted/videoMuted values
1961
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
2017
1962
 
2018
- // check RoapMediaConnection was created correctly
2019
- checkMediaConnectionCreated({
2020
- mediaConnectionConfig: expectedMediaConnectionConfig,
2021
- localTracks: {
2022
- audio: undefined,
2023
- video: undefined,
2024
- screenShareVideo: undefined,
2025
- screenShareAudio: undefined,
2026
- },
2027
- direction: {
2028
- audio: 'inactive',
2029
- video: 'sendrecv',
2030
- screenShareVideo: 'recvonly',
2031
- },
2032
- remoteQualityLevel: 'HIGH',
2033
- expectedDebugId,
2034
- meetingId: meeting.id,
2035
- });
1963
+ // and no other local mute requests were sent to Locus
1964
+ assert.calledOnce(locusMediaRequestStub);
1965
+ });
2036
1966
 
2037
- // and SDP offer was sent with the right audioMuted/videoMuted values
2038
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
1967
+ it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
1968
+ await meeting.addMedia({audioEnabled: false});
1969
+ await simulateRoapOffer();
2039
1970
 
2040
- // and no other local mute requests were sent to Locus
2041
- assert.calledOnce(locusMediaRequestStub);
1971
+ // check RoapMediaConnection was created correctly
1972
+ checkMediaConnectionCreated({
1973
+ mediaConnectionConfig: expectedMediaConnectionConfig,
1974
+ localStreams: {
1975
+ audio: undefined,
1976
+ video: undefined,
1977
+ screenShareVideo: undefined,
1978
+ screenShareAudio: undefined,
1979
+ },
1980
+ direction: {
1981
+ audio: 'inactive',
1982
+ video: 'sendrecv',
1983
+ screenShareVideo: 'recvonly',
1984
+ },
1985
+ remoteQualityLevel: 'HIGH',
1986
+ expectedDebugId,
1987
+ meetingId: meeting.id
2042
1988
  });
2043
1989
 
2044
- describe('publishTracks()/unpublishTracks() calls', () => {
2045
- [
2046
- {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
2047
- {
2048
- mediaEnabled: false,
2049
- expected: {direction: 'inactive', localMuteSentValue: undefined},
2050
- },
2051
- ].forEach(({mediaEnabled, expected}) => {
2052
- it(`first publishTracks() call while media is ${
2053
- mediaEnabled ? 'enabled' : 'disabled'
2054
- }`, async () => {
1990
+ // and SDP offer was sent with the right audioMuted/videoMuted values
1991
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
1992
+
1993
+ // and no other local mute requests were sent to Locus
1994
+ assert.calledOnce(locusMediaRequestStub);
1995
+ });
1996
+
1997
+ describe('publishStreams()/unpublishStreams() calls', () => {
1998
+ [
1999
+ {mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
2000
+ {mediaEnabled: false, expected: {direction: 'inactive', localMuteSentValue: undefined}}
2001
+ ]
2002
+ .forEach(({mediaEnabled, expected}) => {
2003
+ it(`first publishStreams() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
2055
2004
  await meeting.addMedia({audioEnabled: mediaEnabled});
2056
2005
  await simulateRoapOffer();
2057
2006
 
2058
2007
  resetHistory();
2059
2008
 
2060
- await meeting.publishTracks({microphone: fakeMicrophoneTrack});
2009
+ await meeting.publishStreams({microphone: fakeMicrophoneStream});
2061
2010
  await stableState();
2062
2011
 
2063
2012
  if (expected.localMuteSentValue !== undefined) {
@@ -2070,14 +2019,12 @@ describe('plugin-meetings', () => {
2070
2019
  } else {
2071
2020
  assert.notCalled(locusMediaRequestStub);
2072
2021
  }
2022
+
2073
2023
  if (isMultistream) {
2074
- assert.calledOnceWithExactly(
2075
- fakeMultistreamRoapMediaConnection.publishTrack,
2076
- fakeMicrophoneTrack
2077
- );
2024
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream, fakeMicrophoneStream);
2078
2025
  } else {
2079
2026
  assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
2080
- localTracks: {audio: webrtcAudioTrack, video: null, screenShareVideo: null},
2027
+ localTracks: { audio: fakeMicrophoneStream.outputTrack, video: null, screenShareVideo: null, screenShareAudio: null },
2081
2028
  direction: {
2082
2029
  audio: expected.direction,
2083
2030
  video: 'sendrecv',
@@ -2088,40 +2035,34 @@ describe('plugin-meetings', () => {
2088
2035
  }
2089
2036
  });
2090
2037
 
2091
- it(`second publishTracks() call while media is ${
2092
- mediaEnabled ? 'enabled' : 'disabled'
2093
- }`, async () => {
2038
+ it(`second publishStreams() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
2094
2039
  await meeting.addMedia({audioEnabled: mediaEnabled});
2095
2040
  await simulateRoapOffer();
2096
- await meeting.publishTracks({microphone: fakeMicrophoneTrack});
2041
+ await meeting.publishStreams({microphone: fakeMicrophoneStream});
2097
2042
  await stableState();
2098
2043
 
2099
2044
  resetHistory();
2100
2045
 
2101
- const webrtcAudioTrack2 = {id: 'underlying audio track 2'};
2102
- const fakeMicrophoneTrack2 = {
2103
- id: 'fake mic 2',
2046
+ const fakeMicrophoneStream2 = {
2104
2047
  on: sinon.stub(),
2105
2048
  off: sinon.stub(),
2049
+ muted: false,
2106
2050
  setUnmuteAllowed: sinon.stub(),
2107
2051
  setMuted: sinon.stub(),
2108
- setPublished: sinon.stub(),
2109
- muted: false,
2110
- underlyingTrack: webrtcAudioTrack2,
2111
- };
2052
+ outputTrack:{
2053
+ id: 'fake mic 2',
2054
+ }
2055
+ }
2112
2056
 
2113
- await meeting.publishTracks({microphone: fakeMicrophoneTrack2});
2057
+ await meeting.publishStreams({microphone: fakeMicrophoneStream2});
2114
2058
  await stableState();
2115
2059
 
2116
2060
  // only the roap media connection should be updated
2117
2061
  if (isMultistream) {
2118
- assert.calledOnceWithExactly(
2119
- fakeMultistreamRoapMediaConnection.publishTrack,
2120
- fakeMicrophoneTrack2
2121
- );
2062
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream, fakeMicrophoneStream2);
2122
2063
  } else {
2123
2064
  assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
2124
- localTracks: {audio: webrtcAudioTrack2, video: null, screenShareVideo: null},
2065
+ localTracks: { audio: fakeMicrophoneStream2.outputTrack, video: null, screenShareVideo: null, screenShareAudio: null },
2125
2066
  direction: {
2126
2067
  audio: expected.direction,
2127
2068
  video: 'sendrecv',
@@ -2135,28 +2076,23 @@ describe('plugin-meetings', () => {
2135
2076
  assert.notCalled(locusMediaRequestStub);
2136
2077
  });
2137
2078
 
2138
- it(`unpublishTracks() call while media is ${
2139
- mediaEnabled ? 'enabled' : 'disabled'
2140
- }`, async () => {
2079
+ it(`unpublishStreams() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
2141
2080
  await meeting.addMedia({audioEnabled: mediaEnabled});
2142
2081
  await simulateRoapOffer();
2143
- await meeting.publishTracks({microphone: fakeMicrophoneTrack});
2082
+ await meeting.publishStreams({microphone: fakeMicrophoneStream});
2144
2083
  await stableState();
2145
2084
 
2146
2085
  resetHistory();
2147
2086
 
2148
- await meeting.unpublishTracks([fakeMicrophoneTrack]);
2087
+ await meeting.unpublishStreams([fakeMicrophoneStream]);
2149
2088
  await stableState();
2150
2089
 
2151
2090
  // the roap media connection should be updated
2152
2091
  if (isMultistream) {
2153
- assert.calledOnceWithExactly(
2154
- fakeMultistreamRoapMediaConnection.unpublishTrack,
2155
- fakeMicrophoneTrack
2156
- );
2092
+ assert.calledOnce(meeting.sendSlotManager.getSlot(MediaType.AudioMain).unpublishStream);
2157
2093
  } else {
2158
2094
  assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
2159
- localTracks: {audio: null, video: null, screenShareVideo: null},
2095
+ localTracks: { audio: null, video: null, screenShareVideo: null, screenShareAudio: null },
2160
2096
  direction: {
2161
2097
  audio: expected.direction,
2162
2098
  video: 'sendrecv',
@@ -2179,155 +2115,152 @@ describe('plugin-meetings', () => {
2179
2115
  }
2180
2116
  });
2181
2117
  });
2182
- });
2118
+ });
2183
2119
 
2184
- describe('updateMedia()', () => {
2185
- const addMedia = async (enableMedia, track) => {
2186
- await meeting.addMedia({audioEnabled: enableMedia, localTracks: {microphone: track}});
2187
- await simulateRoapOffer();
2120
+ describe('updateMedia()', () => {
2188
2121
 
2189
- resetHistory();
2190
- };
2122
+ const addMedia = async (enableMedia, stream) => {
2123
+ await meeting.addMedia({audioEnabled: enableMedia, localStreams: {microphone: stream}});
2124
+ await simulateRoapOffer();
2191
2125
 
2192
- const checkAudioEnabled = (expectedTrack, expectedDirection) => {
2193
- if (isMultistream) {
2194
- assert.calledOnceWithExactly(
2195
- fakeMultistreamRoapMediaConnection.enableMultistreamAudio,
2196
- expectedDirection !== 'inactive'
2197
- );
2198
- } else {
2199
- assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
2200
- localTracks: {audio: expectedTrack, video: null, screenShareVideo: null},
2201
- direction: {
2202
- audio: expectedDirection,
2203
- video: 'sendrecv',
2204
- screenShareVideo: 'recvonly',
2205
- },
2206
- remoteQualityLevel: 'HIGH',
2207
- });
2208
- }
2209
- };
2126
+ resetHistory();
2127
+ }
2128
+
2129
+ const checkAudioEnabled = (expectedStream, expectedDirection) => {
2130
+ if (isMultistream) {
2131
+ assert.equal(meeting.sendSlotManager.getSlot(MediaType.AudioMain).active, expectedDirection !== 'inactive');
2132
+ } else {
2133
+ assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
2134
+ localTracks: { audio: expectedStream?.outputTrack ?? null, video: null, screenShareVideo: null, screenShareAudio: null },
2135
+ direction: {
2136
+ audio: expectedDirection,
2137
+ video: 'sendrecv',
2138
+ screenShareVideo: 'recvonly',
2139
+ },
2140
+ remoteQualityLevel: 'HIGH'
2141
+ });
2142
+ }
2143
+ }
2210
2144
 
2211
- it('updateMedia() disables media when nothing is published', async () => {
2212
- await addMedia(true);
2145
+ it('updateMedia() disables media when nothing is published', async () => {
2146
+ await addMedia(true);
2213
2147
 
2214
- await meeting.updateMedia({audioEnabled: false});
2148
+ await meeting.updateMedia({audioEnabled: false});
2215
2149
 
2216
- // the roap media connection should be updated
2217
- checkAudioEnabled(null, 'inactive');
2150
+ // the roap media connection should be updated
2151
+ checkAudioEnabled(null, 'inactive');
2218
2152
 
2219
- // and that would trigger a new offer so we simulate it happening
2220
- await simulateRoapOffer();
2153
+ // and that would trigger a new offer so we simulate it happening
2154
+ await simulateRoapOffer();
2221
2155
 
2222
- // check SDP offer was sent with the right audioMuted/videoMuted values
2223
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
2156
+ // check SDP offer was sent with the right audioMuted/videoMuted values
2157
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
2224
2158
 
2225
- // and no other local mute requests were sent to Locus
2226
- assert.calledOnce(locusMediaRequestStub);
2227
- });
2159
+ // and no other local mute requests were sent to Locus
2160
+ assert.calledOnce(locusMediaRequestStub);
2161
+ });
2228
2162
 
2229
- it('updateMedia() enables media when nothing is published', async () => {
2230
- await addMedia(false);
2163
+ it('updateMedia() enables media when nothing is published', async () => {
2164
+ await addMedia(false);
2231
2165
 
2232
- await meeting.updateMedia({audioEnabled: true});
2166
+ await meeting.updateMedia({audioEnabled: true});
2233
2167
 
2234
- // the roap media connection should be updated
2235
- checkAudioEnabled(null, 'sendrecv');
2168
+ // the roap media connection should be updated
2169
+ checkAudioEnabled(null, 'sendrecv');
2236
2170
 
2237
- // and that would trigger a new offer so we simulate it happening
2238
- await simulateRoapOffer();
2171
+ // and that would trigger a new offer so we simulate it happening
2172
+ await simulateRoapOffer();
2239
2173
 
2240
- // check SDP offer was sent with the right audioMuted/videoMuted values
2241
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
2174
+ // check SDP offer was sent with the right audioMuted/videoMuted values
2175
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
2242
2176
 
2243
- // and no other local mute requests were sent to Locus
2244
- assert.calledOnce(locusMediaRequestStub);
2245
- });
2177
+ // and no other local mute requests were sent to Locus
2178
+ assert.calledOnce(locusMediaRequestStub);
2179
+ });
2246
2180
 
2247
- it('updateMedia() disables media when track is published', async () => {
2248
- await addMedia(true, fakeMicrophoneTrack);
2181
+ it('updateMedia() disables media when stream is published', async () => {
2182
+ await addMedia(true, fakeMicrophoneStream);
2249
2183
 
2250
- await meeting.updateMedia({audioEnabled: false});
2251
- await stableState();
2184
+ await meeting.updateMedia({audioEnabled: false});
2185
+ await stableState();
2252
2186
 
2253
- // the roap media connection should be updated
2254
- checkAudioEnabled(webrtcAudioTrack, 'inactive');
2187
+ // the roap media connection should be updated
2188
+ checkAudioEnabled(fakeMicrophoneStream, 'inactive');
2255
2189
 
2256
- checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
2190
+ checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
2257
2191
 
2258
- locusMediaRequestStub.resetHistory();
2192
+ locusMediaRequestStub.resetHistory();
2259
2193
 
2260
- // and that would trigger a new offer so we simulate it happening
2261
- await simulateRoapOffer();
2194
+ // and that would trigger a new offer so we simulate it happening
2195
+ await simulateRoapOffer();
2262
2196
 
2263
- // check SDP offer was sent with the right audioMuted/videoMuted values
2264
- checkSdpOfferSent({audioMuted: true, videoMuted: true});
2197
+ // check SDP offer was sent with the right audioMuted/videoMuted values
2198
+ checkSdpOfferSent({audioMuted: true, videoMuted: true});
2265
2199
 
2266
- // and no other local mute requests were sent to Locus
2267
- assert.calledOnce(locusMediaRequestStub);
2268
- });
2200
+ // and no other local mute requests were sent to Locus
2201
+ assert.calledOnce(locusMediaRequestStub);
2202
+ });
2269
2203
 
2270
- it('updateMedia() enables media when track is published', async () => {
2271
- await addMedia(false, fakeMicrophoneTrack);
2204
+ it('updateMedia() enables media when stream is published', async () => {
2205
+ await addMedia(false, fakeMicrophoneStream);
2272
2206
 
2273
- await meeting.updateMedia({audioEnabled: true});
2274
- await stableState();
2207
+ await meeting.updateMedia({audioEnabled: true});
2208
+ await stableState();
2275
2209
 
2276
- // the roap media connection should be updated
2277
- checkAudioEnabled(webrtcAudioTrack, 'sendrecv');
2210
+ // the roap media connection should be updated
2211
+ checkAudioEnabled(fakeMicrophoneStream, 'sendrecv');
2278
2212
 
2279
- checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
2213
+ checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
2280
2214
 
2281
- locusMediaRequestStub.resetHistory();
2215
+ locusMediaRequestStub.resetHistory();
2282
2216
 
2283
- // and that would trigger a new offer so we simulate it happening
2284
- await simulateRoapOffer();
2217
+ // and that would trigger a new offer so we simulate it happening
2218
+ await simulateRoapOffer();
2285
2219
 
2286
- // check SDP offer was sent with the right audioMuted/videoMuted values
2287
- checkSdpOfferSent({audioMuted: false, videoMuted: true});
2220
+ // check SDP offer was sent with the right audioMuted/videoMuted values
2221
+ checkSdpOfferSent({audioMuted: false, videoMuted: true});
2288
2222
 
2289
- // and no other local mute requests were sent to Locus
2290
- assert.calledOnce(locusMediaRequestStub);
2291
- });
2223
+ // and no other local mute requests were sent to Locus
2224
+ assert.calledOnce(locusMediaRequestStub);
2292
2225
  });
2226
+ });
2293
2227
 
2294
- [
2295
- {mute: true, title: 'muting a track before confluence is created'},
2296
- {mute: false, title: 'unmuting a track before confluence is created'},
2297
- ].forEach(({mute, title}) =>
2298
- it(title, async () => {
2299
- // initialize the microphone mute state to opposite of what we do in the test
2300
- fakeMicrophoneTrack.muted = !mute;
2228
+ [
2229
+ {mute: true, title: 'muting a track before confluence is created'},
2230
+ {mute: false, title: 'unmuting a track before confluence is created'}
2231
+ ].forEach(({mute, title}) =>
2232
+ it(title, async () => {
2233
+ // initialize the microphone mute state to opposite of what we do in the test
2234
+ fakeMicrophoneStream.muted = !mute;
2301
2235
 
2302
- await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
2303
- await stableState();
2236
+ await meeting.addMedia({localStreams: {microphone: fakeMicrophoneStream}});
2237
+ await stableState();
2304
2238
 
2305
- resetHistory();
2239
+ resetHistory();
2306
2240
 
2307
- assert.equal(fakeMicrophoneTrack.on.getCall(0).args[0], LocalTrackEvents.Muted);
2308
- const mutedListener = fakeMicrophoneTrack.on.getCall(0).args[1];
2309
- // simulate track being muted
2310
- mutedListener({trackState: {muted: mute}});
2241
+ assert.equal(fakeMicrophoneStream.on.getCall(0).args[0], StreamEventNames.MuteStateChange);
2242
+ const mutedListener = fakeMicrophoneStream.on.getCall(0).args[1];
2243
+ // simulate track being muted
2244
+ mutedListener(mute);
2311
2245
 
2312
- await stableState();
2246
+ await stableState();
2313
2247
 
2314
- // nothing should happen
2315
- assert.notCalled(locusMediaRequestStub);
2316
- assert.notCalled(fakeRoapMediaConnection.update);
2248
+ // nothing should happen
2249
+ assert.notCalled(locusMediaRequestStub);
2250
+ assert.notCalled(fakeRoapMediaConnection.update);
2317
2251
 
2318
- // now simulate roap offer
2319
- await simulateRoapOffer();
2252
+ // now simulate roap offer
2253
+ await simulateRoapOffer();
2320
2254
 
2321
- // it should be sent with the right mute status
2322
- checkSdpOfferSent({audioMuted: mute, videoMuted: true});
2255
+ // it should be sent with the right mute status
2256
+ checkSdpOfferSent({audioMuted: mute, videoMuted: true});
2323
2257
 
2324
- // nothing else should happen
2325
- assert.calledOnce(locusMediaRequestStub);
2326
- assert.notCalled(fakeRoapMediaConnection.update);
2327
- })
2328
- );
2329
- })
2330
- );
2258
+ // nothing else should happen
2259
+ assert.calledOnce(locusMediaRequestStub);
2260
+ assert.notCalled(fakeRoapMediaConnection.update);
2261
+ })
2262
+ );
2263
+ }));
2331
2264
 
2332
2265
  describe('#acknowledge', () => {
2333
2266
  it('should have #acknowledge', () => {
@@ -2393,11 +2326,11 @@ describe('plugin-meetings', () => {
2393
2326
  .stub()
2394
2327
  .returns(Promise.resolve({body: 'test'}));
2395
2328
  meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
2396
- meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
2329
+ meeting.cleanupLocalStreams = sinon.stub().returns(Promise.resolve());
2397
2330
  meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
2398
- sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
2331
+ sandbox.stub(meeting, 'closeRemoteStreams').returns(Promise.resolve());
2399
2332
  meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
2400
- meeting.unsetRemoteTracks = sinon.stub();
2333
+ meeting.unsetRemoteStreams = sinon.stub();
2401
2334
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
2402
2335
  meeting.unsetPeerConnections = sinon.stub().returns(true);
2403
2336
  meeting.logger.error = sinon.stub().returns(true);
@@ -2417,10 +2350,10 @@ describe('plugin-meetings', () => {
2417
2350
  assert.exists(leave.then);
2418
2351
  await leave;
2419
2352
  assert.calledOnce(meeting.meetingRequest.leaveMeeting);
2420
- assert.calledOnce(meeting.cleanupLocalTracks);
2421
- assert.calledOnce(meeting.closeRemoteTracks);
2353
+ assert.calledOnce(meeting.cleanupLocalStreams);
2354
+ assert.calledOnce(meeting.closeRemoteStreams);
2422
2355
  assert.calledOnce(meeting.closePeerConnections);
2423
- assert.calledOnce(meeting.unsetRemoteTracks);
2356
+ assert.calledOnce(meeting.unsetRemoteStreams);
2424
2357
  assert.calledOnce(meeting.unsetPeerConnections);
2425
2358
  });
2426
2359
 
@@ -2562,7 +2495,7 @@ describe('plugin-meetings', () => {
2562
2495
  meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
2563
2496
  meeting.locusInfo.self = {url: url1};
2564
2497
  meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
2565
- meeting.mediaProperties.shareVideoTrack = {};
2498
+ meeting.mediaProperties.shareVideoStream = {};
2566
2499
  meeting.mediaProperties.mediaDirection.sendShare = true;
2567
2500
  meeting.state = 'JOINED';
2568
2501
  });
@@ -2620,16 +2553,17 @@ describe('plugin-meetings', () => {
2620
2553
  describe('#updateMedia', () => {
2621
2554
  let sandbox;
2622
2555
 
2623
- const createFakeLocalTrack = () => ({
2624
- underlyingTrack: {id: 'fake underlying track'},
2556
+ const createFakeLocalStream = () => ({
2557
+ outputTrack: {id: 'fake underlying track'},
2625
2558
  });
2626
2559
  beforeEach(() => {
2627
2560
  sandbox = sinon.createSandbox();
2628
- meeting.audio = {enable: sinon.stub()};
2629
- meeting.video = {enable: sinon.stub()};
2630
- meeting.mediaProperties.audioTrack = createFakeLocalTrack();
2631
- meeting.mediaProperties.videoTrack = createFakeLocalTrack();
2632
- meeting.mediaProperties.shareVideoTrack = createFakeLocalTrack();
2561
+ meeting.audio = { enable: sinon.stub()};
2562
+ meeting.video = { enable: sinon.stub()};
2563
+ meeting.mediaProperties.audioStream = createFakeLocalStream();
2564
+ meeting.mediaProperties.videoStream = createFakeLocalStream();
2565
+ meeting.mediaProperties.shareVideoStream = createFakeLocalStream();
2566
+ meeting.mediaProperties.shareAudioStream = createFakeLocalStream();
2633
2567
  meeting.mediaProperties.mediaDirection = {
2634
2568
  sendAudio: true,
2635
2569
  sendVideo: true,
@@ -2637,12 +2571,18 @@ describe('plugin-meetings', () => {
2637
2571
  receiveAudio: true,
2638
2572
  receiveVideo: true,
2639
2573
  receiveShare: true,
2574
+ }
2575
+ const fakeMultistreamRoapMediaConnection = {
2576
+ createSendSlot: () => {}
2640
2577
  };
2578
+ sinon.stub(fakeMultistreamRoapMediaConnection,'createSendSlot').returns({active: true});
2579
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection,MediaType.AudioMain);
2641
2580
  });
2642
2581
 
2643
2582
  afterEach(() => {
2644
2583
  sandbox.restore();
2645
2584
  sandbox = null;
2585
+ sinon.restore();
2646
2586
  });
2647
2587
 
2648
2588
  forEach(
@@ -2659,8 +2599,8 @@ describe('plugin-meetings', () => {
2659
2599
 
2660
2600
  await meeting.updateMedia({audioEnabled});
2661
2601
 
2662
- assert.calledOnceWithExactly(
2663
- meeting.mediaProperties.webrtcMediaConnection.enableMultistreamAudio,
2602
+ assert.equal(
2603
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).active,
2664
2604
  enableMultistreamAudio
2665
2605
  );
2666
2606
  assert.calledOnceWithExactly(meeting.audio.enable, meeting, enableMultistreamAudio);
@@ -2692,19 +2632,24 @@ describe('plugin-meetings', () => {
2692
2632
 
2693
2633
  // and check that update is called with the original args
2694
2634
  assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
2695
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.update, {
2696
- localTracks: {
2697
- audio: meeting.mediaProperties.audioTrack.underlyingTrack,
2698
- video: meeting.mediaProperties.videoTrack.underlyingTrack,
2699
- screenShareVideo: meeting.mediaProperties.shareVideoTrack.underlyingTrack,
2700
- },
2701
- direction: {
2702
- audio: 'inactive',
2703
- video: 'inactive',
2704
- screenShareVideo: 'sendrecv',
2705
- },
2706
- remoteQualityLevel: 'HIGH',
2707
- });
2635
+
2636
+ assert.calledWith(
2637
+ meeting.mediaProperties.webrtcMediaConnection.update,
2638
+ {
2639
+ localTracks: {
2640
+ audio: meeting.mediaProperties.audioStream.outputTrack,
2641
+ video: meeting.mediaProperties.videoStream.outputTrack,
2642
+ screenShareVideo: meeting.mediaProperties.shareVideoStream.outputTrack,
2643
+ screenShareAudio: meeting.mediaProperties.shareVideoStream.outputTrack,
2644
+ },
2645
+ direction: {
2646
+ audio: 'inactive',
2647
+ video: 'inactive',
2648
+ screenShareVideo: 'sendrecv',
2649
+ },
2650
+ remoteQualityLevel: 'HIGH',
2651
+ }
2652
+ );
2708
2653
  assert.isTrue(myPromiseResolved);
2709
2654
  });
2710
2655
  });
@@ -2722,9 +2667,9 @@ describe('plugin-meetings', () => {
2722
2667
  receiveVideo: true,
2723
2668
  };
2724
2669
  meeting.mediaProperties.mediaDirection = mediaDirection;
2725
- meeting.mediaProperties.remoteVideoTrack = sinon
2670
+ meeting.mediaProperties.remoteVideoStream = sinon
2726
2671
  .stub()
2727
- .returns({mockTrack: 'mockTrack'});
2672
+ .returns({outputTrack: {id: 'some mock id'}});
2728
2673
 
2729
2674
  meeting.meetingRequest.changeVideoLayoutDebounced = sinon
2730
2675
  .stub()
@@ -2746,7 +2691,7 @@ describe('plugin-meetings', () => {
2746
2691
 
2747
2692
  it('should have receiveVideo true and remote video track should exist', () => {
2748
2693
  assert.equal(meeting.mediaProperties.mediaDirection.receiveVideo, true);
2749
- assert.exists(meeting.mediaProperties.remoteVideoTrack);
2694
+ assert.exists(meeting.mediaProperties.remoteVideoStream);
2750
2695
  });
2751
2696
 
2752
2697
  it('has layoutType which exists in the list of allowed layoutTypes and should call meetingRequest changeVideoLayoutDebounced method', async () => {
@@ -2801,7 +2746,7 @@ describe('plugin-meetings', () => {
2801
2746
  });
2802
2747
 
2803
2748
  meeting.mediaProperties.mediaDirection.receiveShare = true;
2804
- meeting.mediaProperties.remoteShare = sinon.stub().returns({mockTrack: 'mockTrack'});
2749
+ meeting.mediaProperties.remoteShareStream = sinon.stub().returns({mockTrack: 'mockTrack'});
2805
2750
 
2806
2751
  // now call it again with just content
2807
2752
  await meeting.changeVideoLayout(layoutTypeSingle, {content: {width: 500, height: 600}});
@@ -2870,7 +2815,7 @@ describe('plugin-meetings', () => {
2870
2815
 
2871
2816
  it('does not call changeVideoLayoutDebounced if renderInfo content changes only very slightly', async () => {
2872
2817
  meeting.mediaProperties.mediaDirection.receiveShare = true;
2873
- meeting.mediaProperties.remoteShare = sinon.stub().returns({mockTrack: 'mockTrack'});
2818
+ meeting.mediaProperties.remoteShareStream = sinon.stub().returns({mockTrack: 'mockTrack'});
2874
2819
 
2875
2820
  await meeting.changeVideoLayout(layoutTypeSingle, {
2876
2821
  main: {width: 500, height: 510},
@@ -2910,7 +2855,7 @@ describe('plugin-meetings', () => {
2910
2855
 
2911
2856
  it('rounds the width and height values to nearest integers', async () => {
2912
2857
  meeting.mediaProperties.mediaDirection.receiveShare = true;
2913
- meeting.mediaProperties.remoteShare = sinon.stub().returns({mockTrack: 'mockTrack'});
2858
+ meeting.mediaProperties.remoteShareStream = sinon.stub().returns({mockTrack: 'mockTrack'});
2914
2859
 
2915
2860
  await meeting.changeVideoLayout(layoutTypeSingle, {
2916
2861
  main: {width: 500.5, height: 510.09},
@@ -3141,7 +3086,7 @@ describe('plugin-meetings', () => {
3141
3086
  beforeEach(() => {
3142
3087
  meeting.locusId = 'locus-id';
3143
3088
  meeting.id = 'meeting-id';
3144
- FAKE_OPTIONS = {meetingId: meeting.id};
3089
+ FAKE_OPTIONS = {meetingId: meeting.id, sendCAevents: true};
3145
3090
  });
3146
3091
 
3147
3092
  it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
@@ -3161,6 +3106,7 @@ describe('plugin-meetings', () => {
3161
3106
  password: FAKE_PASSWORD,
3162
3107
  captchaCode: FAKE_CAPTCHA_CODE,
3163
3108
  extraParams: FAKE_EXTRA_PARAMS,
3109
+ sendCAevents: true,
3164
3110
  });
3165
3111
 
3166
3112
  assert.calledWith(
@@ -3212,7 +3158,7 @@ describe('plugin-meetings', () => {
3212
3158
  const clock = sinon.useFakeTimers();
3213
3159
  const clearTimeoutSpy = sinon.spy(clock, 'clearTimeout');
3214
3160
 
3215
- await meeting.fetchMeetingInfo({});
3161
+ await meeting.fetchMeetingInfo({sendCAevents: false});
3216
3162
 
3217
3163
  // clear timer
3218
3164
  assert.calledWith(clearTimeoutSpy, FAKE_TIMEOUT_FETCHMEETINGINFO_ID);
@@ -3229,7 +3175,7 @@ describe('plugin-meetings', () => {
3229
3175
  undefined,
3230
3176
  meeting.locusId,
3231
3177
  {},
3232
- {meetingId: meeting.id}
3178
+ {meetingId: meeting.id, sendCAevents: false}
3233
3179
  );
3234
3180
 
3235
3181
  // parseMeeting info
@@ -3308,7 +3254,7 @@ describe('plugin-meetings', () => {
3308
3254
  .throws(new MeetingInfoV2PasswordError(403004, FAKE_MEETING_INFO)),
3309
3255
  };
3310
3256
 
3311
- await assert.isRejected(meeting.fetchMeetingInfo({}), PasswordError);
3257
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), PasswordError);
3312
3258
 
3313
3259
  assert.calledWith(
3314
3260
  meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
@@ -3319,7 +3265,7 @@ describe('plugin-meetings', () => {
3319
3265
  undefined,
3320
3266
  'locus-id',
3321
3267
  {},
3322
- {meetingId: meeting.id}
3268
+ {meetingId: meeting.id, sendCAevents: true}
3323
3269
  );
3324
3270
 
3325
3271
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
@@ -3341,7 +3287,7 @@ describe('plugin-meetings', () => {
3341
3287
  .throws(new MeetingInfoV2PolicyError(123456, FAKE_MEETING_INFO, 'a message')),
3342
3288
  };
3343
3289
 
3344
- await assert.isRejected(meeting.fetchMeetingInfo({}), PermissionError);
3290
+ await assert.isRejected(meeting.fetchMeetingInfo({sendCAevents: true}), PermissionError);
3345
3291
 
3346
3292
  assert.calledWith(
3347
3293
  meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
@@ -3352,7 +3298,7 @@ describe('plugin-meetings', () => {
3352
3298
  undefined,
3353
3299
  'locus-id',
3354
3300
  {},
3355
- {meetingId: meeting.id}
3301
+ {meetingId: meeting.id, sendCAevents: true}
3356
3302
  );
3357
3303
 
3358
3304
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
@@ -3373,6 +3319,7 @@ describe('plugin-meetings', () => {
3373
3319
  await assert.isRejected(
3374
3320
  meeting.fetchMeetingInfo({
3375
3321
  password: 'aaa',
3322
+ sendCAevents: true
3376
3323
  }),
3377
3324
  CaptchaError
3378
3325
  );
@@ -3386,7 +3333,7 @@ describe('plugin-meetings', () => {
3386
3333
  undefined,
3387
3334
  'locus-id',
3388
3335
  {},
3389
- {meetingId: meeting.id}
3336
+ {meetingId: meeting.id, sendCAevents: true}
3390
3337
  );
3391
3338
 
3392
3339
  assert.deepEqual(meeting.meetingInfo, {});
@@ -3418,6 +3365,7 @@ describe('plugin-meetings', () => {
3418
3365
  meeting.fetchMeetingInfo({
3419
3366
  password: 'aaa',
3420
3367
  captchaCode: 'bbb',
3368
+ sendCAevents: true,
3421
3369
  }),
3422
3370
  CaptchaError
3423
3371
  );
@@ -3431,7 +3379,7 @@ describe('plugin-meetings', () => {
3431
3379
  undefined,
3432
3380
  'locus-id',
3433
3381
  {},
3434
- {meetingId: meeting.id}
3382
+ {meetingId: meeting.id, sendCAevents: true}
3435
3383
  );
3436
3384
 
3437
3385
  assert.deepEqual(meeting.meetingInfo, {});
@@ -3453,6 +3401,7 @@ describe('plugin-meetings', () => {
3453
3401
 
3454
3402
  await meeting.fetchMeetingInfo({
3455
3403
  password: 'aaa',
3404
+ sendCAevents: true,
3456
3405
  });
3457
3406
 
3458
3407
  assert.calledWith(
@@ -3464,7 +3413,7 @@ describe('plugin-meetings', () => {
3464
3413
  undefined,
3465
3414
  'locus-id',
3466
3415
  {},
3467
- {meetingId: meeting.id}
3416
+ {meetingId: meeting.id, sendCAevents: true}
3468
3417
  );
3469
3418
 
3470
3419
  assert.deepEqual(meeting.meetingInfo, {
@@ -3504,6 +3453,7 @@ describe('plugin-meetings', () => {
3504
3453
  meeting.fetchMeetingInfo({
3505
3454
  password: 'aaa',
3506
3455
  captchaCode: 'bbb',
3456
+ sendCAevents: true,
3507
3457
  })
3508
3458
  );
3509
3459
 
@@ -3516,7 +3466,7 @@ describe('plugin-meetings', () => {
3516
3466
  undefined,
3517
3467
  'locus-id',
3518
3468
  {},
3519
- {meetingId: meeting.id}
3469
+ {meetingId: meeting.id, sendCAevents: true}
3520
3470
  );
3521
3471
 
3522
3472
  assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
@@ -3610,6 +3560,7 @@ describe('plugin-meetings', () => {
3610
3560
  assert.calledWith(meeting.fetchMeetingInfo, {
3611
3561
  password: 'password',
3612
3562
  captchaCode: 'captcha id',
3563
+ sendCAevents: false,
3613
3564
  });
3614
3565
  });
3615
3566
  it('handles PasswordError returned by fetchMeetingInfo', async () => {
@@ -3696,11 +3647,11 @@ describe('plugin-meetings', () => {
3696
3647
  .stub()
3697
3648
  .returns(Promise.resolve({body: 'test'}));
3698
3649
  meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
3699
- meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
3650
+ meeting.cleanupLocalStreams = sinon.stub().returns(Promise.resolve());
3700
3651
  meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
3701
- sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
3652
+ sandbox.stub(meeting, 'closeRemoteStreams').returns(Promise.resolve());
3702
3653
  meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
3703
- meeting.unsetRemoteTracks = sinon.stub();
3654
+ meeting.unsetRemoteStreams = sinon.stub();
3704
3655
  meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
3705
3656
  meeting.unsetPeerConnections = sinon.stub().returns(true);
3706
3657
  meeting.logger.error = sinon.stub().returns(true);
@@ -3720,10 +3671,10 @@ describe('plugin-meetings', () => {
3720
3671
  assert.exists(endMeetingForAll.then);
3721
3672
  await endMeetingForAll;
3722
3673
  assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
3723
- assert.calledOnce(meeting?.cleanupLocalTracks);
3724
- assert.calledOnce(meeting?.closeRemoteTracks);
3674
+ assert.calledOnce(meeting?.cleanupLocalStreams);
3675
+ assert.calledOnce(meeting?.closeRemoteStreams);
3725
3676
  assert.calledOnce(meeting?.closePeerConnections);
3726
- assert.calledOnce(meeting?.unsetRemoteTracks);
3677
+ assert.calledOnce(meeting?.unsetRemoteStreams);
3727
3678
  assert.calledOnce(meeting?.unsetPeerConnections);
3728
3679
  });
3729
3680
  });
@@ -3733,7 +3684,7 @@ describe('plugin-meetings', () => {
3733
3684
 
3734
3685
  beforeEach(() => {
3735
3686
  sandbox = sinon.createSandbox();
3736
- sandbox.stub(meeting, 'cleanupLocalTracks');
3687
+ sandbox.stub(meeting, 'cleanupLocalStreams');
3737
3688
 
3738
3689
  sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
3739
3690
 
@@ -3812,7 +3763,7 @@ describe('plugin-meetings', () => {
3812
3763
 
3813
3764
  // beacuse we are calling callback so we need to wait
3814
3765
 
3815
- assert.called(meeting.cleanupLocalTracks);
3766
+ assert.called(meeting.cleanupLocalStreams);
3816
3767
 
3817
3768
  // give queued Promise callbacks a chance to run
3818
3769
  await Promise.resolve();
@@ -3936,43 +3887,45 @@ describe('plugin-meetings', () => {
3936
3887
  });
3937
3888
  });
3938
3889
  describe('Local tracks publishing', () => {
3939
- let audioTrack;
3940
- let videoTrack;
3941
- let audioShareTrack;
3942
- let videoShareTrack;
3943
- let createMuteStateStub;
3944
- let LocalDisplayTrackConstructorStub;
3945
- let LocalMicrophoneTrackConstructorStub;
3946
- let LocalCameraTrackConstructorStub;
3947
- let fakeLocalDisplayTrack;
3948
- let fakeLocalMicrophoneTrack;
3949
- let fakeLocalCameraTrack;
3890
+ let audioStream;
3891
+ let videoStream;
3892
+ let audioShareStream;
3893
+ let videoShareStream;
3894
+ let fakeMultistreamRoapMediaConnection;
3950
3895
 
3951
3896
  beforeEach(() => {
3952
- audioTrack = {
3953
- id: 'audio track',
3954
- getSettings: sinon.stub().returns({}),
3897
+ audioStream = {
3898
+ getSettings: sinon.stub().returns({
3899
+ deviceId: 'some device id'
3900
+ }),
3955
3901
  on: sinon.stub(),
3956
3902
  off: sinon.stub(),
3957
- };
3958
- videoTrack = {
3959
- id: 'video track',
3960
- getSettings: sinon.stub().returns({}),
3903
+ }
3904
+
3905
+ videoStream = {
3906
+ getSettings: sinon.stub().returns({
3907
+ deviceId: 'some device id'
3908
+ }),
3961
3909
  on: sinon.stub(),
3962
3910
  off: sinon.stub(),
3963
- };
3964
- audioShareTrack = {
3965
- id: 'share track',
3911
+ }
3912
+
3913
+ audioShareStream = {
3966
3914
  on: sinon.stub(),
3967
3915
  off: sinon.stub(),
3968
- getSettings: sinon.stub(),
3969
- };
3970
- videoShareTrack = {
3971
- id: 'share track',
3916
+ getSettings: sinon.stub().returns({
3917
+ deviceId: 'some device id'
3918
+ }),
3919
+ }
3920
+
3921
+ videoShareStream = {
3972
3922
  on: sinon.stub(),
3973
3923
  off: sinon.stub(),
3974
- getSettings: sinon.stub().returns({}),
3975
- };
3924
+ getSettings: sinon.stub().returns({
3925
+ deviceId: 'some device id'
3926
+ }),
3927
+ }
3928
+
3976
3929
  meeting.requestScreenShareFloor = sinon.stub().resolves({});
3977
3930
  meeting.releaseScreenShareFloor = sinon.stub().resolves({});
3978
3931
  meeting.mediaProperties.mediaDirection = {
@@ -3981,266 +3934,239 @@ describe('plugin-meetings', () => {
3981
3934
  sendShare: false,
3982
3935
  };
3983
3936
  meeting.isMultistream = true;
3984
- meeting.mediaProperties.webrtcMediaConnection = {
3985
- publishTrack: sinon.stub().resolves({}),
3986
- unpublishTrack: sinon.stub().resolves({}),
3987
- };
3988
- meeting.audio = {handleLocalTrackChange: sinon.stub()};
3989
- meeting.video = {handleLocalTrackChange: sinon.stub()};
3990
-
3991
- const createFakeLocalTrack = (originalTrack) => ({
3992
- on: sinon.stub(),
3993
- off: sinon.stub(),
3994
- stop: sinon.stub(),
3995
- originalTrack,
3996
- });
3997
-
3998
- // setup mock constructors for webrtc-core local track classes in such a way
3999
- // that they return the original track correctly (this is needed for unpublish() API tests)
4000
- LocalDisplayTrackConstructorStub = sinon
4001
- .stub(InternalMediaCoreModule, 'LocalDisplayTrack')
4002
- .callsFake((stream) => {
4003
- fakeLocalDisplayTrack = createFakeLocalTrack(stream.getTracks()[0]);
4004
- return fakeLocalDisplayTrack;
4005
- });
4006
- LocalMicrophoneTrackConstructorStub = sinon
4007
- .stub(InternalMediaCoreModule, 'LocalMicrophoneTrack')
4008
- .callsFake((stream) => {
4009
- fakeLocalMicrophoneTrack = createFakeLocalTrack(stream.getTracks()[0]);
4010
- return fakeLocalMicrophoneTrack;
4011
- });
4012
- LocalCameraTrackConstructorStub = sinon
4013
- .stub(InternalMediaCoreModule, 'LocalCameraTrack')
4014
- .callsFake((stream) => {
4015
- fakeLocalCameraTrack = createFakeLocalTrack(stream.getTracks()[0]);
4016
- return fakeLocalCameraTrack;
4017
- });
4018
-
4019
- createMuteStateStub = sinon
4020
- .stub(MuteStateModule, 'createMuteState')
4021
- .returns({id: 'fake mute state instance'});
3937
+ meeting.mediaProperties.webrtcMediaConnection = {};
3938
+ meeting.audio = { handleLocalStreamChange: sinon.stub()};
3939
+ meeting.video = { handleLocalStreamChange: sinon.stub()};
3940
+ fakeMultistreamRoapMediaConnection = {
3941
+ createSendSlot: () => {
3942
+ return {
3943
+ publishStream: sinon.stub(),
3944
+ unpublishStream: sinon.stub(),
3945
+ }
3946
+ }
3947
+ }
3948
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection, MediaType.VideoSlides);
3949
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection, MediaType.AudioSlides);
3950
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection, MediaType.AudioMain);
3951
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection, MediaType.VideoMain);
4022
3952
  });
4023
- describe('#publishTracks', () => {
3953
+ afterEach(() => {
3954
+ sinon.restore();
3955
+ });
3956
+ describe('#publishStreams', () => {
4024
3957
  it('fails if there is no media connection', async () => {
4025
3958
  meeting.mediaProperties.webrtcMediaConnection = undefined;
4026
- await assert.isRejected(meeting.publishTracks({audio: {id: 'some audio track'}}));
3959
+ await assert.isRejected(meeting.publishStreams({audio: {id: 'some audio track'}}));
4027
3960
  });
4028
3961
 
4029
- const checkAudioPublished = (track) => {
4030
- assert.calledOnceWithExactly(meeting.audio.handleLocalTrackChange, meeting);
4031
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, track);
4032
- assert.equal(meeting.mediaProperties.audioTrack, track);
3962
+ const checkAudioPublished = (stream) => {
3963
+ assert.calledOnceWithExactly(meeting.audio.handleLocalStreamChange, meeting);
3964
+ assert.calledWith(
3965
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).publishStream,
3966
+ stream
3967
+ );
3968
+ assert.equal(meeting.mediaProperties.audioStream, stream);
4033
3969
  // check that sendAudio hasn't been touched
4034
3970
  assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
4035
3971
  };
4036
3972
 
4037
- const checkVideoPublished = (track) => {
4038
- assert.calledOnceWithExactly(meeting.video.handleLocalTrackChange, meeting);
4039
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, track);
4040
- assert.equal(meeting.mediaProperties.videoTrack, track);
3973
+ const checkVideoPublished = (stream) => {
3974
+ assert.calledOnceWithExactly(meeting.video.handleLocalStreamChange, meeting);
3975
+ assert.calledWith(
3976
+ meeting.sendSlotManager.getSlot(MediaType.VideoMain).publishStream,
3977
+ stream
3978
+ );
3979
+ assert.equal(meeting.mediaProperties.videoStream, stream);
4041
3980
  // check that sendVideo hasn't been touched
4042
3981
  assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
4043
3982
  };
4044
3983
 
4045
- const checkScreenShareVideoPublished = (track) => {
3984
+ const checkScreenShareVideoPublished = (stream) => {
4046
3985
  assert.calledOnce(meeting.requestScreenShareFloor);
4047
3986
 
4048
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, track);
4049
- assert.equal(meeting.mediaProperties.shareVideoTrack, track);
3987
+ assert.calledWith(
3988
+ meeting.sendSlotManager.getSlot(MediaType.VideoSlides).publishStream,
3989
+ stream
3990
+ );
3991
+ assert.equal(meeting.mediaProperties.shareVideoStream, stream);
4050
3992
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
4051
3993
  };
4052
3994
 
4053
- const checkScreenShareAudioPublished = (track) => {
3995
+ const checkScreenShareAudioPublished = (stream) => {
4054
3996
  assert.calledOnce(meeting.requestScreenShareFloor);
4055
3997
 
4056
- assert.calledWith(meeting.mediaProperties.webrtcMediaConnection.publishTrack, track);
4057
- assert.equal(meeting.mediaProperties.shareAudioTrack, track);
3998
+ assert.calledWith(
3999
+ meeting.sendSlotManager.getSlot(MediaType.AudioSlides).publishStream,
4000
+ stream
4001
+ );
4002
+ assert.equal(meeting.mediaProperties.shareAudioStream, stream);
4058
4003
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
4059
4004
  };
4060
4005
 
4061
- it('requests screen share floor and publishes the screen share video track', async () => {
4062
- await meeting.publishTracks({screenShare: {video: videoShareTrack}});
4006
+ it('requests screen share floor and publishes the screen share video stream', async () => {
4007
+ await meeting.publishStreams({screenShare: {video: videoShareStream}});
4063
4008
 
4064
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
4065
- checkScreenShareVideoPublished(videoShareTrack);
4009
+ checkScreenShareVideoPublished(videoShareStream);
4066
4010
  });
4067
4011
 
4068
- it('requests screen share floor and publishes the screen share audio track', async () => {
4069
- await meeting.publishTracks({screenShare: {audio: audioShareTrack}});
4012
+ it('requests screen share floor and publishes the screen share audio stream', async () => {
4013
+ await meeting.publishStreams({screenShare: {audio: audioShareStream}});
4070
4014
 
4071
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
4072
- checkScreenShareAudioPublished(audioShareTrack);
4015
+ checkScreenShareAudioPublished(audioShareStream);
4073
4016
  });
4074
4017
 
4075
- it('does not request screen share floor when publishing video share track if already sharing audio', async () => {
4076
- await meeting.publishTracks({screenShare: {audio: audioShareTrack}});
4018
+ it('does not request screen share floor when publishing video share stream if already sharing audio', async () => {
4019
+ await meeting.publishStreams({screenShare: {audio: audioShareStream}});
4077
4020
  assert.calledOnce(meeting.requestScreenShareFloor);
4078
4021
 
4079
4022
  meeting.requestScreenShareFloor.reset();
4080
- await meeting.publishTracks({screenShare: {video: videoShareTrack}});
4023
+ await meeting.publishStreams({screenShare: {video: videoShareStream}});
4081
4024
  assert.notCalled(meeting.requestScreenShareFloor);
4082
4025
  });
4083
4026
 
4084
- it('does not request screen share floor when publishing audio share track if already sharing video', async () => {
4085
- await meeting.publishTracks({screenShare: {video: videoShareTrack}});
4027
+ it('does not request screen share floor when publishing audio share stream if already sharing video', async () => {
4028
+ await meeting.publishStreams({screenShare: {video: videoShareStream}});
4086
4029
  assert.calledOnce(meeting.requestScreenShareFloor);
4087
4030
 
4088
4031
  meeting.requestScreenShareFloor.reset();
4089
- await meeting.publishTracks({screenShare: {audio: audioShareTrack}});
4032
+ await meeting.publishStreams({screenShare: {audio: audioShareStream}});
4090
4033
  assert.notCalled(meeting.requestScreenShareFloor);
4091
4034
  });
4092
4035
 
4093
- it('updates MuteState instance and publishes the track for main audio', async () => {
4094
- await meeting.publishTracks({microphone: audioTrack});
4036
+ it('updates MuteState instance and publishes the stream for main audio', async () => {
4037
+ await meeting.publishStreams({microphone: audioStream});
4095
4038
 
4096
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
4097
- checkAudioPublished(audioTrack);
4039
+ checkAudioPublished(audioStream);
4098
4040
  });
4099
4041
 
4100
- it('updates MuteState instance and publishes the track for main video', async () => {
4101
- await meeting.publishTracks({camera: videoTrack});
4042
+ it('updates MuteState instance and publishes the stream for main video', async () => {
4043
+ await meeting.publishStreams({camera: videoStream});
4102
4044
 
4103
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
4104
- checkVideoPublished(videoTrack);
4045
+ checkVideoPublished(videoStream);
4105
4046
  });
4106
4047
 
4107
4048
  it('publishes audio, video and screen share together', async () => {
4108
- await meeting.publishTracks({
4109
- microphone: audioTrack,
4110
- camera: videoTrack,
4049
+ await meeting.publishStreams({
4050
+ microphone: audioStream,
4051
+ camera: videoStream,
4111
4052
  screenShare: {
4112
- video: videoShareTrack,
4113
- audio: audioShareTrack,
4053
+ video: videoShareStream,
4054
+ audio: audioShareStream,
4114
4055
  },
4115
4056
  });
4116
4057
 
4117
- assert.callCount(meeting.mediaProperties.webrtcMediaConnection.publishTrack, 4);
4118
- checkAudioPublished(audioTrack);
4119
- checkVideoPublished(videoTrack);
4120
- checkScreenShareVideoPublished(videoShareTrack);
4121
- checkScreenShareAudioPublished(audioShareTrack);
4058
+ checkAudioPublished(audioStream);
4059
+ checkVideoPublished(videoStream);
4060
+ checkScreenShareVideoPublished(videoShareStream);
4061
+ checkScreenShareAudioPublished(audioShareStream);
4122
4062
  });
4123
4063
  });
4124
4064
 
4125
- describe('unpublishTracks', () => {
4065
+ describe('unpublishStreams', () => {
4126
4066
  beforeEach(async () => {
4127
- await meeting.publishTracks({
4128
- microphone: audioTrack,
4129
- camera: videoTrack,
4130
- screenShare: {video: videoShareTrack, audio: audioShareTrack},
4067
+ await meeting.publishStreams({
4068
+ microphone: audioStream,
4069
+ camera: videoStream,
4070
+ screenShare: {video: videoShareStream, audio: audioShareStream},
4131
4071
  });
4132
4072
  });
4133
4073
 
4134
4074
  const checkAudioUnpublished = () => {
4135
- assert.calledWith(
4136
- meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
4137
- audioTrack
4075
+ assert.calledOnce(
4076
+ meeting.sendSlotManager.getSlot(MediaType.AudioMain).unpublishStream
4138
4077
  );
4139
4078
 
4140
- assert.equal(meeting.mediaProperties.audioTrack, null);
4079
+ assert.equal(meeting.mediaProperties.audioStream, null);
4141
4080
  assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
4142
4081
  };
4143
4082
 
4144
4083
  const checkVideoUnpublished = () => {
4145
- assert.calledWith(
4146
- meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
4147
- videoTrack
4084
+ assert.calledOnce(
4085
+ meeting.sendSlotManager.getSlot(MediaType.VideoMain).unpublishStream
4148
4086
  );
4149
4087
 
4150
- assert.equal(meeting.mediaProperties.videoTrack, null);
4088
+ assert.equal(meeting.mediaProperties.videoStream, null);
4151
4089
  assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
4152
4090
  };
4153
4091
 
4154
- // share direction will remain true if only one of the two share tracks are unpublished
4092
+ // share direction will remain true if only one of the two share streams are unpublished
4155
4093
  const checkScreenShareVideoUnpublished = (shareDirection = true) => {
4156
- assert.calledWith(
4157
- meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
4158
- videoShareTrack
4094
+ assert.calledOnce(
4095
+ meeting.sendSlotManager.getSlot(MediaType.VideoSlides).unpublishStream
4159
4096
  );
4160
4097
 
4161
4098
  assert.calledOnce(meeting.requestScreenShareFloor);
4162
4099
 
4163
- assert.equal(meeting.mediaProperties.shareVideoTrack, null);
4100
+ assert.equal(meeting.mediaProperties.shareVideoStream, null);
4164
4101
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, shareDirection);
4165
4102
  };
4166
4103
 
4167
- // share direction will remain true if only one of the two share tracks are unpublished
4104
+ // share direction will remain true if only one of the two share streams are unpublished
4168
4105
  const checkScreenShareAudioUnpublished = (shareDirection = true) => {
4169
- assert.calledWith(
4170
- meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
4171
- audioShareTrack
4106
+ assert.calledOnce(
4107
+ meeting.sendSlotManager.getSlot(MediaType.AudioSlides).unpublishStream
4172
4108
  );
4173
4109
 
4174
4110
  assert.calledOnce(meeting.requestScreenShareFloor);
4175
4111
 
4176
- assert.equal(meeting.mediaProperties.shareAudioTrack, null);
4112
+ assert.equal(meeting.mediaProperties.shareAudioStream, null);
4177
4113
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, shareDirection);
4178
4114
  };
4179
4115
 
4180
4116
  it('fails if there is no media connection', async () => {
4181
4117
  meeting.mediaProperties.webrtcMediaConnection = undefined;
4182
4118
  await assert.isRejected(
4183
- meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack, audioShareTrack])
4119
+ meeting.unpublishStreams([audioStream, videoStream, videoShareStream, audioShareStream])
4184
4120
  );
4185
4121
  });
4186
4122
 
4187
- it('un-publishes the tracks correctly (all 4 together)', async () => {
4188
- await meeting.unpublishTracks([
4189
- audioTrack,
4190
- videoTrack,
4191
- videoShareTrack,
4192
- audioShareTrack,
4193
- ]);
4123
+ it('un-publishes the streams correctly (all 4 together)', async () => {
4124
+ await meeting.unpublishStreams([audioStream, videoStream, videoShareStream, audioShareStream]);
4194
4125
 
4195
- assert.equal(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack.callCount, 4);
4196
4126
  checkAudioUnpublished();
4197
4127
  checkVideoUnpublished();
4198
4128
  checkScreenShareVideoUnpublished(false);
4199
4129
  checkScreenShareAudioUnpublished(false);
4200
4130
  });
4201
4131
 
4202
- it('un-publishes the audio track correctly', async () => {
4203
- await meeting.unpublishTracks([audioTrack]);
4132
+ it('un-publishes the audio stream correctly', async () => {
4133
+ await meeting.unpublishStreams([audioStream]);
4204
4134
 
4205
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
4206
4135
  checkAudioUnpublished();
4207
4136
  });
4208
4137
 
4209
- it('un-publishes the video track correctly', async () => {
4210
- await meeting.unpublishTracks([videoTrack]);
4138
+ it('un-publishes the video stream correctly', async () => {
4139
+ await meeting.unpublishStreams([videoStream]);
4211
4140
 
4212
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
4213
4141
  checkVideoUnpublished();
4214
4142
  });
4215
4143
 
4216
- it('un-publishes the screen share video track correctly', async () => {
4217
- await meeting.unpublishTracks([videoShareTrack]);
4144
+ it('un-publishes the screen share video stream correctly', async () => {
4145
+ await meeting.unpublishStreams([videoShareStream]);
4218
4146
 
4219
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
4220
4147
  checkScreenShareVideoUnpublished();
4221
4148
  });
4222
4149
 
4223
- it('un-publishes the screen share audio track correctly', async () => {
4224
- await meeting.unpublishTracks([audioShareTrack]);
4150
+ it('un-publishes the screen share audio stream correctly', async () => {
4151
+ await meeting.unpublishStreams([audioShareStream]);
4225
4152
 
4226
- assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
4227
4153
  checkScreenShareAudioUnpublished();
4228
4154
  });
4229
4155
 
4230
- it('releases share floor and sets send direction to false when both screen share tracks are undefined', async () => {
4231
- await meeting.unpublishTracks([videoShareTrack, audioShareTrack]);
4156
+ it('releases share floor and sets send direction to false when both screen share streams are undefined', async () => {
4157
+ await meeting.unpublishStreams([videoShareStream, audioShareStream]);
4232
4158
 
4233
4159
  assert.calledOnce(meeting.releaseScreenShareFloor);
4234
4160
  assert.equal(meeting.mediaProperties.mediaDirection.sendShare, false);
4235
4161
  });
4236
4162
 
4237
4163
  it('does not release share floor when audio is released and video still exists', async () => {
4238
- await meeting.unpublishTracks([audioShareTrack]);
4164
+ await meeting.unpublishStreams([audioShareStream]);
4239
4165
  assert.notCalled(meeting.releaseScreenShareFloor);
4240
4166
  });
4241
4167
 
4242
4168
  it('does not release share floor when video is released and audio still exists', async () => {
4243
- await meeting.unpublishTracks([videoShareTrack]);
4169
+ await meeting.unpublishStreams([videoShareStream]);
4244
4170
  assert.notCalled(meeting.releaseScreenShareFloor);
4245
4171
  });
4246
4172
  });
@@ -4250,60 +4176,68 @@ describe('plugin-meetings', () => {
4250
4176
  describe('#enableMusicMode', () => {
4251
4177
  beforeEach(() => {
4252
4178
  meeting.isMultistream = true;
4253
- meeting.mediaProperties.webrtcMediaConnection = {
4254
- setCodecParameters: sinon.stub().resolves({}),
4255
- deleteCodecParameters: sinon.stub().resolves({}),
4179
+ const fakeMultistreamRoapMediaConnection = {
4180
+ createSendSlot: () => {
4181
+ return {
4182
+ setCodecParameters: sinon.stub().resolves(),
4183
+ deleteCodecParameters: sinon.stub().resolves(),
4184
+ }
4185
+ }
4256
4186
  };
4187
+ meeting.sendSlotManager.createSlot(fakeMultistreamRoapMediaConnection, MediaType.AudioMain, false);
4188
+ meeting.mediaProperties.webrtcMediaConnection = {};
4189
+ });
4190
+ afterEach(() => {
4191
+ sinon.restore();
4192
+ });
4193
+ [
4194
+ {shouldEnableMusicMode: true},
4195
+ {shouldEnableMusicMode: false},
4196
+ ].forEach(({shouldEnableMusicMode}) => {
4197
+ it(`fails if there is no media connection for shouldEnableMusicMode: ${shouldEnableMusicMode}`, async () => {
4198
+ meeting.mediaProperties.webrtcMediaConnection = undefined;
4199
+ await assert.isRejected(meeting.enableMusicMode(shouldEnableMusicMode));
4200
+ });
4257
4201
  });
4258
- [{shouldEnableMusicMode: true}, {shouldEnableMusicMode: false}].forEach(
4259
- ({shouldEnableMusicMode}) => {
4260
- it(`fails if there is no media connection for shouldEnableMusicMode: ${shouldEnableMusicMode}`, async () => {
4261
- meeting.mediaProperties.webrtcMediaConnection = undefined;
4262
- await assert.isRejected(meeting.enableMusicMode(shouldEnableMusicMode));
4263
- });
4264
- }
4265
- );
4266
4202
 
4267
4203
  it('should set the codec parameters when shouldEnableMusicMode is true', async () => {
4268
4204
  await meeting.enableMusicMode(true);
4269
- assert.calledOnceWithExactly(
4270
- meeting.mediaProperties.webrtcMediaConnection.setCodecParameters,
4271
- MediaType.AudioMain,
4272
- {
4273
- maxaveragebitrate: '64000',
4274
- maxplaybackrate: '48000',
4275
- }
4276
- );
4277
- assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters);
4205
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters, {
4206
+ maxaveragebitrate: '64000',
4207
+ maxplaybackrate: '48000',
4208
+ });
4209
+ assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters);
4278
4210
  });
4279
4211
 
4280
4212
  it('should set the codec parameters when shouldEnableMusicMode is false', async () => {
4281
4213
  await meeting.enableMusicMode(false);
4282
- assert.calledOnceWithExactly(
4283
- meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters,
4284
- MediaType.AudioMain,
4285
- ['maxaveragebitrate', 'maxplaybackrate']
4286
- );
4287
- assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.setCodecParameters);
4214
+ assert.calledOnceWithExactly(meeting.sendSlotManager.getSlot(MediaType.AudioMain).deleteCodecParameters, [
4215
+ 'maxaveragebitrate',
4216
+ 'maxplaybackrate',
4217
+ ]);
4218
+ assert.notCalled(meeting.sendSlotManager.getSlot(MediaType.AudioMain).setCodecParameters);
4288
4219
  });
4289
4220
  });
4290
4221
 
4291
4222
  describe('Public Event Triggers', () => {
4292
4223
  let sandbox;
4293
- const {ENDED} = CONSTANTS;
4294
4224
 
4295
4225
  beforeEach(() => {
4296
- const fakeMediaTrack = () => ({stop: () => {}, readyState: ENDED});
4226
+ const fakeMediaStream = () => {
4227
+ return {
4228
+ id: 'fake stream'
4229
+ }
4230
+ };
4297
4231
 
4298
4232
  sandbox = sinon.createSandbox();
4299
- sandbox.stub(Media, 'stopTracks').returns(Promise.resolve());
4300
- sandbox.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
4301
- sandbox.stub(meeting.mediaProperties, 'videoTrack').value(fakeMediaTrack());
4302
- sandbox.stub(meeting.mediaProperties, 'shareVideoTrack').value(fakeMediaTrack());
4303
- sandbox.stub(meeting.mediaProperties, 'shareAudioTrack').value(fakeMediaTrack());
4304
- sandbox.stub(meeting.mediaProperties, 'remoteAudioTrack').value(fakeMediaTrack());
4305
- sandbox.stub(meeting.mediaProperties, 'remoteVideoTrack').value(fakeMediaTrack());
4306
- sandbox.stub(meeting.mediaProperties, 'remoteShare').value(fakeMediaTrack());
4233
+ sandbox.stub(Media, 'stopStream').returns(Promise.resolve());
4234
+ sandbox.stub(meeting.mediaProperties, 'audioStream').value(fakeMediaStream());
4235
+ sandbox.stub(meeting.mediaProperties, 'videoStream').value(fakeMediaStream());
4236
+ sandbox.stub(meeting.mediaProperties, 'shareVideoStream').value(fakeMediaStream());
4237
+ sandbox.stub(meeting.mediaProperties, 'shareAudioStream').value(fakeMediaStream());
4238
+ sandbox.stub(meeting.mediaProperties, 'remoteAudioStream').value(fakeMediaStream());
4239
+ sandbox.stub(meeting.mediaProperties, 'remoteVideoStream').value(fakeMediaStream());
4240
+ sandbox.stub(meeting.mediaProperties, 'remoteShareStream').value(fakeMediaStream());
4307
4241
  });
4308
4242
  afterEach(() => {
4309
4243
  sandbox.restore();
@@ -4403,27 +4337,27 @@ describe('plugin-meetings', () => {
4403
4337
  });
4404
4338
  describe('#closeRemoteStream', () => {
4405
4339
  it('should stop remote tracks, and trigger a media:stopped event when the remote tracks are stopped', async () => {
4406
- await meeting.closeRemoteTracks();
4340
+ await meeting.closeRemoteStreams();
4407
4341
 
4408
- assert.equal(TriggerProxy.trigger.callCount, 5);
4342
+ assert.equal(TriggerProxy.trigger.callCount, 6);
4409
4343
  assert.calledWith(
4410
4344
  TriggerProxy.trigger,
4411
4345
  sinon.match.instanceOf(Meeting),
4412
- {file: 'meeting/index', function: 'closeRemoteTracks'},
4346
+ {file: 'meeting/index', function: 'closeRemoteStreams'},
4413
4347
  'media:stopped',
4414
4348
  {type: 'remoteAudio'}
4415
4349
  );
4416
4350
  assert.calledWith(
4417
4351
  TriggerProxy.trigger,
4418
4352
  sinon.match.instanceOf(Meeting),
4419
- {file: 'meeting/index', function: 'closeRemoteTracks'},
4353
+ {file: 'meeting/index', function: 'closeRemoteStreams'},
4420
4354
  'media:stopped',
4421
4355
  {type: 'remoteVideo'}
4422
4356
  );
4423
4357
  assert.calledWith(
4424
4358
  TriggerProxy.trigger,
4425
4359
  sinon.match.instanceOf(Meeting),
4426
- {file: 'meeting/index', function: 'closeRemoteTracks'},
4360
+ {file: 'meeting/index', function: 'closeRemoteStreams'},
4427
4361
  'media:stopped',
4428
4362
  {type: 'remoteShare'}
4429
4363
  );
@@ -4431,6 +4365,10 @@ describe('plugin-meetings', () => {
4431
4365
  });
4432
4366
  describe('#setupMediaConnectionListeners', () => {
4433
4367
  let eventListeners;
4368
+ const fakeStream = {
4369
+ id: 'stream',
4370
+ getTracks: () => [{ id: 'track', addEventListener: sinon.stub() }]
4371
+ };
4434
4372
 
4435
4373
  beforeEach(() => {
4436
4374
  eventListeners = {};
@@ -4441,7 +4379,7 @@ describe('plugin-meetings', () => {
4441
4379
  eventListeners[event] = listener;
4442
4380
  }),
4443
4381
  };
4444
- MediaUtil.createMediaStream.returns({id: 'stream'});
4382
+ MediaUtil.createMediaStream.returns(fakeStream);
4445
4383
  });
4446
4384
 
4447
4385
  it('should register for all the correct RoapMediaConnection events', () => {
@@ -4463,7 +4401,7 @@ describe('plugin-meetings', () => {
4463
4401
  assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
4464
4402
  assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {
4465
4403
  type: 'remoteAudio',
4466
- stream: {id: 'stream'},
4404
+ stream: fakeStream,
4467
4405
  });
4468
4406
 
4469
4407
  eventListeners[Event.REMOTE_TRACK_ADDED]({
@@ -4473,7 +4411,7 @@ describe('plugin-meetings', () => {
4473
4411
  assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
4474
4412
  assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {
4475
4413
  type: 'remoteVideo',
4476
- stream: {id: 'stream'},
4414
+ stream: fakeStream,
4477
4415
  });
4478
4416
 
4479
4417
  eventListeners[Event.REMOTE_TRACK_ADDED]({
@@ -4483,7 +4421,7 @@ describe('plugin-meetings', () => {
4483
4421
  assert.equal(TriggerProxy.trigger.getCall(4).args[2], 'media:ready');
4484
4422
  assert.deepEqual(TriggerProxy.trigger.getCall(4).args[3], {
4485
4423
  type: 'remoteShare',
4486
- stream: {id: 'stream'},
4424
+ stream: fakeStream,
4487
4425
  });
4488
4426
  });
4489
4427
 
@@ -5514,7 +5452,7 @@ describe('plugin-meetings', () => {
5514
5452
 
5515
5453
  beforeEach(() => {
5516
5454
  sandbox = sinon.createSandbox();
5517
- sandbox.stub(meeting.mediaProperties, 'unsetRemoteTracks').returns(Promise.resolve());
5455
+ sandbox.stub(meeting.mediaProperties, 'unsetRemoteStreams').returns(Promise.resolve());
5518
5456
  });
5519
5457
 
5520
5458
  afterEach(() => {
@@ -5621,11 +5559,11 @@ describe('plugin-meetings', () => {
5621
5559
  });
5622
5560
  });
5623
5561
 
5624
- describe('#unsetRemoteTracks', () => {
5625
- it('should unset the remote tracks and return null', () => {
5626
- meeting.mediaProperties.unsetRemoteTracks = sinon.stub().returns(true);
5627
- meeting.unsetRemoteTracks();
5628
- assert.calledOnce(meeting.mediaProperties.unsetRemoteTracks);
5562
+ describe('#unsetRemoteStreams', () => {
5563
+ it('should unset the remote streams and return null', () => {
5564
+ meeting.mediaProperties.unsetRemoteStreams = sinon.stub().returns(true);
5565
+ meeting.unsetRemoteStreams();
5566
+ assert.calledOnce(meeting.mediaProperties.unsetRemoteStreams);
5629
5567
  });
5630
5568
  });
5631
5569
  // TODO: remove