@webex/plugin-meetings 3.0.0-beta.16 → 3.0.0-beta.160
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.
- package/README.md +45 -1
- package/dist/annotation/annotation.types.js +7 -0
- package/dist/annotation/annotation.types.js.map +1 -0
- package/dist/annotation/constants.js +49 -0
- package/dist/annotation/constants.js.map +1 -0
- package/dist/annotation/index.js +359 -0
- package/dist/annotation/index.js.map +1 -0
- package/dist/breakouts/breakout.js +212 -0
- package/dist/breakouts/breakout.js.map +1 -0
- package/dist/breakouts/collection.js +23 -0
- package/dist/breakouts/collection.js.map +1 -0
- package/dist/breakouts/edit-lock-error.js +52 -0
- package/dist/breakouts/edit-lock-error.js.map +1 -0
- package/dist/breakouts/events.js +43 -0
- package/dist/breakouts/events.js.map +1 -0
- package/dist/breakouts/index.js +1046 -0
- package/dist/breakouts/index.js.map +1 -0
- package/dist/breakouts/request.js +78 -0
- package/dist/breakouts/request.js.map +1 -0
- package/dist/breakouts/utils.js +67 -0
- package/dist/breakouts/utils.js.map +1 -0
- package/dist/common/errors/webex-errors.js +3 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/logs/logger-proxy.js +1 -1
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/config.js +6 -8
- package/dist/config.js.map +1 -1
- package/dist/constants.js +165 -26
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +14 -0
- package/dist/controls-options-manager/constants.js.map +1 -0
- package/dist/controls-options-manager/enums.js +27 -0
- package/dist/controls-options-manager/enums.js.map +1 -0
- package/dist/controls-options-manager/index.js +297 -0
- package/dist/controls-options-manager/index.js.map +1 -0
- package/dist/controls-options-manager/types.js +7 -0
- package/dist/controls-options-manager/types.js.map +1 -0
- package/dist/controls-options-manager/util.js +300 -0
- package/dist/controls-options-manager/util.js.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +91 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +298 -24
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +43 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +2 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js +88 -14
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +39 -134
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +19 -97
- package/dist/media/properties.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +505 -493
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +79 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +2275 -2152
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +291 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -0
- package/dist/meeting/muteState.js +229 -124
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +191 -167
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +443 -443
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +157 -49
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +20 -5
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +22 -0
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +365 -73
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +7 -0
- package/dist/meetings/meetings.types.js.map +1 -0
- package/dist/meetings/request.js +16 -12
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +88 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +41 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js +15 -0
- package/dist/member/types.js.map +1 -0
- package/dist/member/util.js +86 -3
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +10 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +94 -11
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +109 -39
- package/dist/members/request.js.map +1 -1
- package/dist/members/types.js +15 -0
- package/dist/members/types.js.map +1 -0
- package/dist/members/util.js +316 -233
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js +50 -14
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/constants.js +3 -5
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +48 -29
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +265 -36
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +52 -19
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +53 -33
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +44 -18
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +60 -3
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +322 -103
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.js +4 -2
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/reachability/index.js +117 -60
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +12 -5
- package/dist/reachability/request.js.map +1 -1
- package/dist/reactions/constants.js +13 -0
- package/dist/reactions/constants.js.map +1 -0
- package/dist/reactions/reactions.js +2 -2
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js +18 -18
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +190 -145
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/enums.js +17 -0
- package/dist/recording-controller/enums.js.map +1 -0
- package/dist/recording-controller/index.js +343 -0
- package/dist/recording-controller/index.js.map +1 -0
- package/dist/recording-controller/util.js +63 -0
- package/dist/recording-controller/util.js.map +1 -0
- package/dist/roap/index.js +21 -29
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +127 -92
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +135 -53
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +1 -93
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +329 -314
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +103 -54
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/annotation/annotation.types.d.ts +43 -0
- package/dist/types/annotation/constants.d.ts +31 -0
- package/dist/types/annotation/index.d.ts +124 -0
- package/dist/types/breakouts/breakout.d.ts +8 -0
- package/dist/types/breakouts/collection.d.ts +5 -0
- package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
- package/dist/types/breakouts/events.d.ts +2 -0
- package/dist/types/breakouts/index.d.ts +5 -0
- package/dist/types/breakouts/request.d.ts +22 -0
- package/dist/types/breakouts/utils.d.ts +15 -0
- package/dist/types/common/browser-detection.d.ts +9 -0
- package/dist/types/common/collection.d.ts +48 -0
- package/dist/types/common/config.d.ts +2 -0
- package/dist/types/common/errors/captcha-error.d.ts +15 -0
- package/dist/types/common/errors/intent-to-join.d.ts +16 -0
- package/dist/types/common/errors/join-meeting.d.ts +17 -0
- package/dist/types/common/errors/media.d.ts +15 -0
- package/dist/types/common/errors/parameter.d.ts +15 -0
- package/dist/types/common/errors/password-error.d.ts +15 -0
- package/dist/types/common/errors/permission.d.ts +14 -0
- package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
- package/dist/types/common/errors/reconnection.d.ts +15 -0
- package/dist/types/common/errors/stats.d.ts +15 -0
- package/dist/types/common/errors/webex-errors.d.ts +69 -0
- package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
- package/dist/types/common/events/events-scope.d.ts +17 -0
- package/dist/types/common/events/events.d.ts +12 -0
- package/dist/types/common/events/trigger-proxy.d.ts +2 -0
- package/dist/types/common/events/util.d.ts +2 -0
- package/dist/types/common/logs/logger-config.d.ts +2 -0
- package/dist/types/common/logs/logger-proxy.d.ts +2 -0
- package/dist/types/common/logs/request.d.ts +34 -0
- package/dist/types/common/queue.d.ts +32 -0
- package/dist/types/config.d.ts +72 -0
- package/dist/types/constants.d.ts +978 -0
- package/dist/types/controls-options-manager/constants.d.ts +4 -0
- package/dist/types/controls-options-manager/enums.d.ts +15 -0
- package/dist/types/controls-options-manager/index.d.ts +136 -0
- package/dist/types/controls-options-manager/types.d.ts +43 -0
- package/dist/types/controls-options-manager/util.d.ts +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/locus-info/controlsUtils.d.ts +2 -0
- package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
- package/dist/types/locus-info/fullState.d.ts +2 -0
- package/dist/types/locus-info/hostUtils.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +315 -0
- package/dist/types/locus-info/infoUtils.d.ts +2 -0
- package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
- package/dist/types/locus-info/parser.d.ts +212 -0
- package/dist/types/locus-info/selfUtils.d.ts +2 -0
- package/dist/types/media/index.d.ts +34 -0
- package/dist/types/media/properties.d.ts +86 -0
- package/dist/types/media/util.d.ts +2 -0
- package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +149 -0
- package/dist/types/meeting/index.d.ts +1516 -0
- package/dist/types/meeting/locusMediaRequest.d.ts +70 -0
- package/dist/types/meeting/muteState.d.ts +184 -0
- package/dist/types/meeting/request.d.ts +270 -0
- package/dist/types/meeting/request.type.d.ts +11 -0
- package/dist/types/meeting/state.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +75 -0
- package/dist/types/meeting-info/collection.d.ts +20 -0
- package/dist/types/meeting-info/index.d.ts +57 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +122 -0
- package/dist/types/meeting-info/request.d.ts +22 -0
- package/dist/types/meeting-info/util.d.ts +2 -0
- package/dist/types/meeting-info/utilv2.d.ts +2 -0
- package/dist/types/meetings/collection.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +364 -0
- package/dist/types/meetings/meetings.types.d.ts +4 -0
- package/dist/types/meetings/request.d.ts +27 -0
- package/dist/types/meetings/util.d.ts +18 -0
- package/dist/types/member/index.d.ts +157 -0
- package/dist/types/member/types.d.ts +21 -0
- package/dist/types/member/util.d.ts +2 -0
- package/dist/types/members/collection.d.ts +29 -0
- package/dist/types/members/index.d.ts +353 -0
- package/dist/types/members/request.d.ts +114 -0
- package/dist/types/members/types.d.ts +24 -0
- package/dist/types/members/util.d.ts +210 -0
- package/dist/types/metrics/config.d.ts +195 -0
- package/dist/types/metrics/constants.d.ts +55 -0
- package/dist/types/metrics/index.d.ts +169 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +118 -0
- package/dist/types/multistream/receiveSlot.d.ts +68 -0
- package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
- package/dist/types/multistream/remoteMedia.d.ts +72 -0
- package/dist/types/multistream/remoteMediaGroup.d.ts +47 -0
- package/dist/types/multistream/remoteMediaManager.d.ts +277 -0
- package/dist/types/networkQualityMonitor/index.d.ts +70 -0
- package/dist/types/personal-meeting-room/index.d.ts +47 -0
- package/dist/types/personal-meeting-room/request.d.ts +14 -0
- package/dist/types/personal-meeting-room/util.d.ts +2 -0
- package/dist/types/reachability/index.d.ts +152 -0
- package/dist/types/reachability/request.d.ts +37 -0
- package/dist/types/reactions/constants.d.ts +3 -0
- package/dist/types/reactions/reactions.d.ts +4 -0
- package/dist/types/reactions/reactions.type.d.ts +52 -0
- package/dist/types/reconnection-manager/index.d.ts +126 -0
- package/dist/types/recording-controller/enums.d.ts +7 -0
- package/dist/types/recording-controller/index.d.ts +193 -0
- package/dist/types/recording-controller/util.d.ts +13 -0
- package/dist/types/roap/index.d.ts +77 -0
- package/dist/types/roap/request.d.ts +36 -0
- package/dist/types/roap/turnDiscovery.d.ts +91 -0
- package/dist/types/statsAnalyzer/global.d.ts +36 -0
- package/dist/types/statsAnalyzer/index.d.ts +200 -0
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
- package/dist/types/transcription/index.d.ts +64 -0
- package/package.json +28 -21
- package/src/annotation/annotation.types.ts +52 -0
- package/src/annotation/constants.ts +36 -0
- package/src/annotation/index.ts +343 -0
- package/src/breakouts/README.md +220 -0
- package/src/breakouts/breakout.ts +180 -0
- package/src/breakouts/collection.ts +19 -0
- package/src/breakouts/edit-lock-error.ts +25 -0
- package/src/breakouts/events.ts +37 -0
- package/src/breakouts/index.ts +921 -0
- package/src/breakouts/request.ts +55 -0
- package/src/breakouts/utils.ts +57 -0
- package/src/common/errors/webex-errors.ts +6 -2
- package/src/common/logs/logger-proxy.ts +1 -1
- package/src/config.ts +5 -7
- package/src/constants.ts +155 -20
- package/src/controls-options-manager/constants.ts +5 -0
- package/src/controls-options-manager/enums.ts +18 -0
- package/src/controls-options-manager/index.ts +278 -0
- package/src/controls-options-manager/types.ts +59 -0
- package/src/controls-options-manager/util.ts +286 -0
- package/src/index.ts +34 -0
- package/src/locus-info/controlsUtils.ts +108 -0
- package/src/locus-info/index.ts +310 -21
- package/src/locus-info/mediaSharesUtils.ts +48 -0
- package/src/locus-info/parser.ts +2 -1
- package/src/locus-info/selfUtils.ts +80 -2
- package/src/media/index.ts +70 -142
- package/src/media/properties.ts +41 -104
- package/src/mediaQualityMetrics/config.ts +379 -377
- package/src/meeting/in-meeting-actions.ts +156 -0
- package/src/meeting/index.ts +1744 -1767
- package/src/meeting/locusMediaRequest.ts +309 -0
- package/src/meeting/muteState.ts +228 -132
- package/src/meeting/request.ts +100 -91
- package/src/meeting/request.type.ts +2 -0
- package/src/meeting/util.ts +421 -421
- package/src/meeting-info/meeting-info-v2.ts +134 -13
- package/src/meeting-info/utilv2.ts +13 -3
- package/src/meetings/collection.ts +20 -0
- package/src/meetings/index.ts +385 -83
- package/src/meetings/meetings.types.ts +12 -0
- package/src/meetings/request.ts +3 -1
- package/src/meetings/util.ts +103 -4
- package/src/member/index.ts +40 -0
- package/src/member/types.ts +24 -0
- package/src/member/util.ts +81 -1
- package/src/members/collection.ts +8 -0
- package/src/members/index.ts +108 -6
- package/src/members/request.ts +98 -17
- package/src/members/types.ts +28 -0
- package/src/members/util.ts +319 -240
- package/src/metrics/config.ts +49 -10
- package/src/metrics/constants.ts +2 -4
- package/src/metrics/index.ts +43 -27
- package/src/multistream/mediaRequestManager.ts +337 -63
- package/src/multistream/receiveSlot.ts +68 -26
- package/src/multistream/receiveSlotManager.ts +61 -38
- package/src/multistream/remoteMedia.ts +29 -3
- package/src/multistream/remoteMediaGroup.ts +61 -2
- package/src/multistream/remoteMediaManager.ts +260 -66
- package/src/networkQualityMonitor/index.ts +6 -6
- package/src/reachability/index.ts +75 -25
- package/src/reachability/request.ts +10 -5
- package/src/reactions/constants.ts +4 -0
- package/src/reactions/reactions.ts +4 -4
- package/src/reactions/reactions.type.ts +28 -3
- package/src/reconnection-manager/index.ts +53 -32
- package/src/recording-controller/enums.ts +8 -0
- package/src/recording-controller/index.ts +315 -0
- package/src/recording-controller/util.ts +58 -0
- package/src/roap/index.ts +21 -30
- package/src/roap/request.ts +51 -52
- package/src/roap/turnDiscovery.ts +51 -27
- package/src/statsAnalyzer/global.ts +1 -94
- package/src/statsAnalyzer/index.ts +380 -390
- package/src/statsAnalyzer/mqaUtil.ts +106 -99
- package/test/integration/spec/converged-space-meetings.js +233 -0
- package/test/integration/spec/journey.js +331 -254
- package/test/integration/spec/space-meeting.js +77 -4
- package/test/unit/spec/annotation/index.ts +436 -0
- package/test/unit/spec/breakouts/breakout.ts +233 -0
- package/test/unit/spec/breakouts/collection.ts +15 -0
- package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
- package/test/unit/spec/breakouts/events.ts +77 -0
- package/test/unit/spec/breakouts/index.ts +1790 -0
- package/test/unit/spec/breakouts/request.ts +104 -0
- package/test/unit/spec/breakouts/utils.js +72 -0
- package/test/unit/spec/controls-options-manager/index.js +287 -0
- package/test/unit/spec/controls-options-manager/util.js +518 -0
- package/test/unit/spec/fixture/locus.js +1 -0
- package/test/unit/spec/locus-info/controlsUtils.js +303 -30
- package/test/unit/spec/locus-info/index.js +616 -4
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
- package/test/unit/spec/locus-info/selfConstant.js +38 -0
- package/test/unit/spec/locus-info/selfUtils.js +249 -0
- package/test/unit/spec/media/index.ts +118 -22
- package/test/unit/spec/media/properties.ts +9 -9
- package/test/unit/spec/meeting/in-meeting-actions.ts +76 -0
- package/test/unit/spec/meeting/index.js +2496 -1375
- package/test/unit/spec/meeting/locusMediaRequest.ts +436 -0
- package/test/unit/spec/meeting/muteState.js +370 -208
- package/test/unit/spec/meeting/request.js +354 -42
- package/test/unit/spec/meeting/utils.js +268 -156
- package/test/unit/spec/meeting-info/meetinginfov2.js +383 -5
- package/test/unit/spec/meeting-info/utilv2.js +21 -0
- package/test/unit/spec/meetings/collection.js +14 -0
- package/test/unit/spec/meetings/index.js +866 -120
- package/test/unit/spec/meetings/utils.js +206 -2
- package/test/unit/spec/member/index.js +24 -0
- package/test/unit/spec/member/util.js +384 -32
- package/test/unit/spec/members/index.js +320 -1
- package/test/unit/spec/members/request.js +206 -27
- package/test/unit/spec/members/utils.js +184 -0
- package/test/unit/spec/metrics/index.js +98 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +1012 -109
- package/test/unit/spec/multistream/receiveSlot.ts +77 -18
- package/test/unit/spec/multistream/receiveSlotManager.ts +69 -39
- package/test/unit/spec/multistream/remoteMedia.ts +32 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +271 -5
- package/test/unit/spec/multistream/remoteMediaManager.ts +672 -65
- package/test/unit/spec/networkQualityMonitor/index.js +4 -4
- package/test/unit/spec/reachability/index.ts +176 -25
- package/test/unit/spec/reachability/request.js +66 -0
- package/test/unit/spec/reconnection-manager/index.js +46 -13
- package/test/unit/spec/recording-controller/index.js +231 -0
- package/test/unit/spec/recording-controller/util.js +102 -0
- package/test/unit/spec/roap/index.ts +21 -51
- package/test/unit/spec/roap/request.ts +187 -0
- package/test/unit/spec/roap/turnDiscovery.ts +73 -34
- package/test/unit/spec/stats-analyzer/index.js +94 -43
- package/test/utils/constants.js +9 -0
- package/test/utils/integrationTestUtils.js +46 -0
- package/test/utils/testUtils.js +0 -45
- package/test/utils/webex-config.js +4 -0
- package/test/utils/webex-test-users.js +7 -3
- package/tsconfig.json +6 -0
- package/dist/media/internal-media-core-wrapper.js +0 -18
- package/dist/media/internal-media-core-wrapper.js.map +0 -1
- package/dist/meeting/effectsState.js +0 -262
- package/dist/meeting/effectsState.js.map +0 -1
- package/dist/multistream/multistreamMedia.js +0 -106
- package/dist/multistream/multistreamMedia.js.map +0 -1
- package/src/index.js +0 -15
- package/src/media/internal-media-core-wrapper.ts +0 -9
- package/src/meeting/effectsState.ts +0 -211
- package/src/multistream/multistreamMedia.ts +0 -93
- package/test/unit/spec/meeting/effectsState.js +0 -281
|
@@ -2,14 +2,16 @@
|
|
|
2
2
|
* Copyright (c) 2015-2020 Cisco Systems, Inc. See LICENSE file.
|
|
3
3
|
*/
|
|
4
4
|
import 'jsdom-global/register';
|
|
5
|
-
import {cloneDeep, isEqual} from 'lodash';
|
|
5
|
+
import {cloneDeep, forEach, isEqual} from 'lodash';
|
|
6
6
|
import sinon from 'sinon';
|
|
7
|
+
import * as internalMediaModule from '@webex/internal-media-core';
|
|
7
8
|
import StateMachine from 'javascript-state-machine';
|
|
8
9
|
import uuid from 'uuid';
|
|
9
10
|
import {assert} from '@webex/test-helper-chai';
|
|
10
|
-
import {Credentials} from '@webex/webex-core';
|
|
11
|
+
import {Credentials, Token, WebexPlugin} from '@webex/webex-core';
|
|
11
12
|
import Support from '@webex/internal-plugin-support';
|
|
12
13
|
import MockWebex from '@webex/test-helper-mock-webex';
|
|
14
|
+
import StaticConfig from '@webex/plugin-meetings/src/common/config';
|
|
13
15
|
import {
|
|
14
16
|
FLOOR_ACTION,
|
|
15
17
|
SHARE_STATUS,
|
|
@@ -19,23 +21,42 @@ import {
|
|
|
19
21
|
EVENT_TRIGGERS,
|
|
20
22
|
_SIP_URI_,
|
|
21
23
|
_MEETING_ID_,
|
|
24
|
+
MEETING_REMOVED_REASON,
|
|
22
25
|
LOCUSINFO,
|
|
23
26
|
PC_BAIL_TIMEOUT,
|
|
27
|
+
DISPLAY_HINTS,
|
|
24
28
|
} from '@webex/plugin-meetings/src/constants';
|
|
25
|
-
import
|
|
29
|
+
import * as InternalMediaCoreModule from '@webex/internal-media-core';
|
|
30
|
+
import {
|
|
31
|
+
ConnectionState,
|
|
32
|
+
Event,
|
|
33
|
+
Errors,
|
|
34
|
+
ErrorType,
|
|
35
|
+
RemoteTrackType,
|
|
36
|
+
MediaType,
|
|
37
|
+
} from '@webex/internal-media-core';
|
|
38
|
+
import {
|
|
39
|
+
LocalTrackEvents,
|
|
40
|
+
} from '@webex/media-helpers';
|
|
26
41
|
import * as StatsAnalyzerModule from '@webex/plugin-meetings/src/statsAnalyzer';
|
|
42
|
+
import * as MuteStateModule from '@webex/plugin-meetings/src/meeting/muteState';
|
|
27
43
|
import EventsScope from '@webex/plugin-meetings/src/common/events/events-scope';
|
|
28
44
|
import Meetings, {CONSTANTS} from '@webex/plugin-meetings';
|
|
29
45
|
import Meeting from '@webex/plugin-meetings/src/meeting';
|
|
30
46
|
import Members from '@webex/plugin-meetings/src/members';
|
|
47
|
+
import * as MembersImport from '@webex/plugin-meetings/src/members';
|
|
31
48
|
import Roap from '@webex/plugin-meetings/src/roap';
|
|
49
|
+
import RoapRequest from '@webex/plugin-meetings/src/roap/request';
|
|
32
50
|
import MeetingRequest from '@webex/plugin-meetings/src/meeting/request';
|
|
51
|
+
import * as MeetingRequestImport from '@webex/plugin-meetings/src/meeting/request';
|
|
33
52
|
import LocusInfo from '@webex/plugin-meetings/src/locus-info';
|
|
34
53
|
import MediaProperties from '@webex/plugin-meetings/src/media/properties';
|
|
35
54
|
import MeetingUtil from '@webex/plugin-meetings/src/meeting/util';
|
|
36
55
|
import Media from '@webex/plugin-meetings/src/media/index';
|
|
37
56
|
import ReconnectionManager from '@webex/plugin-meetings/src/reconnection-manager';
|
|
38
57
|
import MediaUtil from '@webex/plugin-meetings/src/media/util';
|
|
58
|
+
import RecordingUtil from '@webex/plugin-meetings/src/recording-controller/util';
|
|
59
|
+
import ControlsOptionsUtil from '@webex/plugin-meetings/src/controls-options-manager/util';
|
|
39
60
|
import LoggerProxy from '@webex/plugin-meetings/src/common/logs/logger-proxy';
|
|
40
61
|
import LoggerConfig from '@webex/plugin-meetings/src/common/logs/logger-config';
|
|
41
62
|
import TriggerProxy from '@webex/plugin-meetings/src/common/events/trigger-proxy';
|
|
@@ -43,8 +64,13 @@ import BrowserDetection from '@webex/plugin-meetings/src/common/browser-detectio
|
|
|
43
64
|
import Metrics from '@webex/plugin-meetings/src/metrics';
|
|
44
65
|
import {trigger, eventType} from '@webex/plugin-meetings/src/metrics/config';
|
|
45
66
|
import BEHAVIORAL_METRICS from '@webex/plugin-meetings/src/metrics/constants';
|
|
46
|
-
import {
|
|
67
|
+
import {MediaRequestManager} from '@webex/plugin-meetings/src/multistream/mediaRequestManager';
|
|
68
|
+
import * as ReceiveSlotManagerModule from '@webex/plugin-meetings/src/multistream/receiveSlotManager';
|
|
47
69
|
|
|
70
|
+
import LLM from '@webex/internal-plugin-llm';
|
|
71
|
+
import Mercury from '@webex/internal-plugin-mercury';
|
|
72
|
+
import Breakouts from '@webex/plugin-meetings/src/breakouts';
|
|
73
|
+
import {REACTION_RELAY_TYPES} from '../../../../src/reactions/constants';
|
|
48
74
|
import locus from '../fixture/locus';
|
|
49
75
|
import {
|
|
50
76
|
UserNotJoinedError,
|
|
@@ -56,15 +82,18 @@ import WebExMeetingsErrors from '../../../../src/common/errors/webex-meetings-er
|
|
|
56
82
|
import ParameterError from '../../../../src/common/errors/parameter';
|
|
57
83
|
import PasswordError from '../../../../src/common/errors/password-error';
|
|
58
84
|
import CaptchaError from '../../../../src/common/errors/captcha-error';
|
|
85
|
+
import PermissionError from '../../../../src/common/errors/permission';
|
|
59
86
|
import IntentToJoinError from '../../../../src/common/errors/intent-to-join';
|
|
60
87
|
import DefaultSDKConfig from '../../../../src/config';
|
|
61
88
|
import testUtils from '../../../utils/testUtils';
|
|
62
89
|
import {
|
|
63
90
|
MeetingInfoV2CaptchaError,
|
|
64
91
|
MeetingInfoV2PasswordError,
|
|
92
|
+
MeetingInfoV2PolicyError,
|
|
65
93
|
} from '../../../../src/meeting-info/meeting-info-v2';
|
|
94
|
+
import {ANNOTATION_POLICY} from "../../../../src/annotation/constants";
|
|
66
95
|
|
|
67
|
-
const {getBrowserName} = BrowserDetection();
|
|
96
|
+
const {getBrowserName, getOSVersion} = BrowserDetection();
|
|
68
97
|
|
|
69
98
|
// Non-stubbed function
|
|
70
99
|
const {getDisplayMedia} = Media;
|
|
@@ -127,6 +156,15 @@ describe('plugin-meetings', () => {
|
|
|
127
156
|
},
|
|
128
157
|
});
|
|
129
158
|
|
|
159
|
+
Object.defineProperty(global.window.navigator, 'permissions', {
|
|
160
|
+
writable: true,
|
|
161
|
+
value: {
|
|
162
|
+
query: sinon.stub().callsFake(async (arg) => {
|
|
163
|
+
return {state: 'granted', name: arg.name};
|
|
164
|
+
}),
|
|
165
|
+
},
|
|
166
|
+
});
|
|
167
|
+
|
|
130
168
|
Object.defineProperty(global.window, 'MediaStream', {
|
|
131
169
|
writable: true,
|
|
132
170
|
value: MediaStream,
|
|
@@ -148,6 +186,8 @@ describe('plugin-meetings', () => {
|
|
|
148
186
|
let test3;
|
|
149
187
|
let test4;
|
|
150
188
|
let testDestination;
|
|
189
|
+
let membersSpy;
|
|
190
|
+
let meetingRequestSpy;
|
|
151
191
|
|
|
152
192
|
beforeEach(() => {
|
|
153
193
|
webex = new MockWebex({
|
|
@@ -155,10 +195,12 @@ describe('plugin-meetings', () => {
|
|
|
155
195
|
meetings: Meetings,
|
|
156
196
|
credentials: Credentials,
|
|
157
197
|
support: Support,
|
|
198
|
+
llm: LLM,
|
|
199
|
+
mercury: Mercury,
|
|
158
200
|
},
|
|
159
201
|
config: {
|
|
160
202
|
credentials: {
|
|
161
|
-
client_id: 'mock-client-id'
|
|
203
|
+
client_id: 'mock-client-id'
|
|
162
204
|
},
|
|
163
205
|
meetings: {
|
|
164
206
|
reconnection: {
|
|
@@ -168,6 +210,7 @@ describe('plugin-meetings', () => {
|
|
|
168
210
|
metrics: {},
|
|
169
211
|
stats: {},
|
|
170
212
|
experimental: {enableUnifiedMeetings: true},
|
|
213
|
+
degradationPreferences: { maxMacroblocksLimit: 8192 },
|
|
171
214
|
},
|
|
172
215
|
metrics: {
|
|
173
216
|
type: ['behavioral'],
|
|
@@ -179,11 +222,18 @@ describe('plugin-meetings', () => {
|
|
|
179
222
|
webex.credentials.getOrgId = sinon.stub().returns('fake-org-id');
|
|
180
223
|
webex.internal.metrics.submitClientMetrics = sinon.stub().returns(Promise.resolve());
|
|
181
224
|
webex.meetings.uploadLogs = sinon.stub().returns(Promise.resolve());
|
|
225
|
+
webex.internal.llm.on = sinon.stub();
|
|
226
|
+
membersSpy = sinon.spy(MembersImport, 'default');
|
|
227
|
+
meetingRequestSpy = sinon.spy(MeetingRequestImport, 'default');
|
|
182
228
|
|
|
183
229
|
TriggerProxy.trigger = sinon.stub().returns(true);
|
|
184
230
|
Metrics.postEvent = sinon.stub();
|
|
185
231
|
Metrics.initialSetup(null, webex);
|
|
186
|
-
MediaUtil.createMediaStream = sinon.stub().
|
|
232
|
+
MediaUtil.createMediaStream = sinon.stub().callsFake((tracks) => {
|
|
233
|
+
return {
|
|
234
|
+
getTracks: () => tracks,
|
|
235
|
+
};
|
|
236
|
+
});
|
|
187
237
|
|
|
188
238
|
uuid1 = uuid.v4();
|
|
189
239
|
uuid2 = uuid.v4();
|
|
@@ -228,6 +278,16 @@ describe('plugin-meetings', () => {
|
|
|
228
278
|
assert.equal(meeting.deviceUrl, uuid3);
|
|
229
279
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
230
280
|
assert.instanceOf(meeting.members, Members);
|
|
281
|
+
assert.calledOnceWithExactly(
|
|
282
|
+
membersSpy,
|
|
283
|
+
{
|
|
284
|
+
locusUrl: meeting.locusUrl,
|
|
285
|
+
receiveSlotManager: meeting.receiveSlotManager,
|
|
286
|
+
mediaRequestManagers: meeting.mediaRequestManagers,
|
|
287
|
+
meeting,
|
|
288
|
+
},
|
|
289
|
+
{parent: meeting.webex}
|
|
290
|
+
);
|
|
231
291
|
assert.instanceOf(meeting.roap, Roap);
|
|
232
292
|
assert.instanceOf(meeting.reconnectionManager, ReconnectionManager);
|
|
233
293
|
assert.isNull(meeting.audio);
|
|
@@ -242,6 +302,13 @@ describe('plugin-meetings', () => {
|
|
|
242
302
|
assert.isNull(meeting.hostId);
|
|
243
303
|
assert.isNull(meeting.policy);
|
|
244
304
|
assert.instanceOf(meeting.meetingRequest, MeetingRequest);
|
|
305
|
+
assert.calledOnceWithExactly(
|
|
306
|
+
meetingRequestSpy,
|
|
307
|
+
{
|
|
308
|
+
meeting,
|
|
309
|
+
},
|
|
310
|
+
{parent: meeting.webex}
|
|
311
|
+
);
|
|
245
312
|
assert.instanceOf(meeting.locusInfo, LocusInfo);
|
|
246
313
|
assert.equal(meeting.fetchMeetingInfoTimeoutId, undefined);
|
|
247
314
|
assert.instanceOf(meeting.mediaProperties, MediaProperties);
|
|
@@ -250,6 +317,98 @@ describe('plugin-meetings', () => {
|
|
|
250
317
|
assert.equal(meeting.meetingInfoFailureReason, undefined);
|
|
251
318
|
assert.equal(meeting.destination, testDestination);
|
|
252
319
|
assert.equal(meeting.destinationType, _MEETING_ID_);
|
|
320
|
+
assert.instanceOf(meeting.breakouts, Breakouts);
|
|
321
|
+
});
|
|
322
|
+
it('creates MediaRequestManager instances', () => {
|
|
323
|
+
assert.instanceOf(meeting.mediaRequestManagers.audio, MediaRequestManager);
|
|
324
|
+
assert.instanceOf(meeting.mediaRequestManagers.video, MediaRequestManager);
|
|
325
|
+
assert.instanceOf(meeting.mediaRequestManagers.screenShareAudio, MediaRequestManager);
|
|
326
|
+
assert.instanceOf(meeting.mediaRequestManagers.screenShareVideo, MediaRequestManager);
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
describe('creates ReceiveSlot manager instance', () => {
|
|
330
|
+
let mockReceiveSlotManagerCtor;
|
|
331
|
+
let providedCreateSlotCallback;
|
|
332
|
+
let providedFindMemberIdByCsiCallback;
|
|
333
|
+
|
|
334
|
+
beforeEach(() => {
|
|
335
|
+
mockReceiveSlotManagerCtor = sinon
|
|
336
|
+
.stub(ReceiveSlotManagerModule, 'ReceiveSlotManager')
|
|
337
|
+
.callsFake((createSlotCallback, findMemberIdByCsiCallback) => {
|
|
338
|
+
providedCreateSlotCallback = createSlotCallback;
|
|
339
|
+
providedFindMemberIdByCsiCallback = findMemberIdByCsiCallback;
|
|
340
|
+
|
|
341
|
+
return {updateMemberIds: sinon.stub()};
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
meeting = new Meeting(
|
|
345
|
+
{
|
|
346
|
+
userId: uuid1,
|
|
347
|
+
resource: uuid2,
|
|
348
|
+
deviceUrl: uuid3,
|
|
349
|
+
locus: {url: url1},
|
|
350
|
+
destination: testDestination,
|
|
351
|
+
destinationType: _MEETING_ID_,
|
|
352
|
+
},
|
|
353
|
+
{
|
|
354
|
+
parent: webex,
|
|
355
|
+
}
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
meeting.mediaProperties.webrtcMediaConnection = {createReceiveSlot: sinon.stub()};
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
it('calls ReceiveSlotManager constructor', () => {
|
|
362
|
+
assert.calledOnce(mockReceiveSlotManagerCtor);
|
|
363
|
+
assert.isDefined(providedCreateSlotCallback);
|
|
364
|
+
assert.isDefined(providedFindMemberIdByCsiCallback);
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
it('calls createReceiveSlot on the webrtc media connection in the createSlotCallback', async () => {
|
|
368
|
+
assert.isDefined(providedCreateSlotCallback);
|
|
369
|
+
|
|
370
|
+
await providedCreateSlotCallback(MediaType.VideoMain);
|
|
371
|
+
|
|
372
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot);
|
|
373
|
+
assert.calledWith(
|
|
374
|
+
meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot,
|
|
375
|
+
MediaType.VideoMain
|
|
376
|
+
);
|
|
377
|
+
});
|
|
378
|
+
|
|
379
|
+
it('rejects createSlotCallback if there is no webrtc media connection', () => {
|
|
380
|
+
assert.isDefined(providedCreateSlotCallback);
|
|
381
|
+
|
|
382
|
+
meeting.mediaProperties.webrtcMediaConnection.createReceiveSlot.rejects({});
|
|
383
|
+
|
|
384
|
+
assert.isRejected(providedCreateSlotCallback(MediaType.VideoMain));
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
it('calls findMemberByCsi in findMemberIdByCsiCallback and returns the right value', () => {
|
|
388
|
+
assert.isDefined(providedFindMemberIdByCsiCallback);
|
|
389
|
+
|
|
390
|
+
const fakeMember = {id: 'aaa-bbb'};
|
|
391
|
+
|
|
392
|
+
sinon.stub(meeting.members, 'findMemberByCsi').returns(fakeMember);
|
|
393
|
+
|
|
394
|
+
const memberId = providedFindMemberIdByCsiCallback(123);
|
|
395
|
+
|
|
396
|
+
assert.calledOnce(meeting.members.findMemberByCsi);
|
|
397
|
+
assert.calledWith(meeting.members.findMemberByCsi, 123);
|
|
398
|
+
assert.equal(memberId, fakeMember.id);
|
|
399
|
+
});
|
|
400
|
+
|
|
401
|
+
it('returns undefined if findMemberByCsi does not find the member', () => {
|
|
402
|
+
assert.isDefined(providedFindMemberIdByCsiCallback);
|
|
403
|
+
|
|
404
|
+
sinon.stub(meeting.members, 'findMemberByCsi').returns(undefined);
|
|
405
|
+
|
|
406
|
+
const memberId = providedFindMemberIdByCsiCallback(123);
|
|
407
|
+
|
|
408
|
+
assert.calledOnce(meeting.members.findMemberByCsi);
|
|
409
|
+
assert.calledWith(meeting.members.findMemberByCsi, 123);
|
|
410
|
+
assert.equal(memberId, undefined);
|
|
411
|
+
});
|
|
253
412
|
});
|
|
254
413
|
});
|
|
255
414
|
describe('#invite', () => {
|
|
@@ -299,6 +458,21 @@ describe('plugin-meetings', () => {
|
|
|
299
458
|
assert.calledOnce(meeting.members.admitMembers);
|
|
300
459
|
assert.calledWith(meeting.members.admitMembers, [uuid1]);
|
|
301
460
|
});
|
|
461
|
+
it('should call from a breakout session if caller is in a breakout session', async () => {
|
|
462
|
+
const locusUrls = {
|
|
463
|
+
authorizingLocusUrl: 'authorizingLocusUrl',
|
|
464
|
+
mainLocusUrl: 'mainLocusUrl',
|
|
465
|
+
};
|
|
466
|
+
await meeting.admit([uuid1], locusUrls);
|
|
467
|
+
assert.calledOnce(meeting.members.admitMembers);
|
|
468
|
+
assert.calledWith(meeting.members.admitMembers, [uuid1], locusUrls);
|
|
469
|
+
|
|
470
|
+
meeting.breakouts.set('locusUrl', 'authorizingLocusUrl');
|
|
471
|
+
meeting.breakouts.set('mainLocusUrl', 'mainLocusUrl');
|
|
472
|
+
await meeting.admit([uuid1]);
|
|
473
|
+
const args = meeting.members.admitMembers.getCall(1).args;
|
|
474
|
+
assert.deepEqual(args, [[uuid1], locusUrls]);
|
|
475
|
+
});
|
|
302
476
|
});
|
|
303
477
|
describe('#getMembers', () => {
|
|
304
478
|
it('should have #getMembers', () => {
|
|
@@ -310,325 +484,7 @@ describe('plugin-meetings', () => {
|
|
|
310
484
|
assert.instanceOf(members, Members);
|
|
311
485
|
});
|
|
312
486
|
});
|
|
313
|
-
describe('#isAudioMuted', () => {
|
|
314
|
-
it('should have #isAudioMuted', () => {
|
|
315
|
-
assert.exists(meeting.invite);
|
|
316
|
-
});
|
|
317
|
-
it('should get the audio muted status and return as a boolean', () => {
|
|
318
|
-
const muted = meeting.isAudioMuted();
|
|
319
|
-
|
|
320
|
-
assert.isNotOk(muted);
|
|
321
|
-
});
|
|
322
|
-
});
|
|
323
|
-
describe('#isAudioSelf', () => {
|
|
324
|
-
it('should have #isAudioSelf', () => {
|
|
325
|
-
assert.exists(meeting.invite);
|
|
326
|
-
});
|
|
327
|
-
it('should get the audio self status and return as a boolean', () => {
|
|
328
|
-
const self = meeting.isAudioSelf();
|
|
329
|
-
|
|
330
|
-
assert.isNotOk(self);
|
|
331
|
-
});
|
|
332
|
-
});
|
|
333
|
-
describe('#isVideoMuted', () => {
|
|
334
|
-
it('should have #isVideoMuted', () => {
|
|
335
|
-
assert.exists(meeting.isVideoMuted);
|
|
336
|
-
});
|
|
337
|
-
it('should get the video muted status and return as a boolean', () => {
|
|
338
|
-
const muted = meeting.isVideoMuted();
|
|
339
|
-
|
|
340
|
-
assert.isNotOk(muted);
|
|
341
|
-
});
|
|
342
|
-
});
|
|
343
|
-
describe('#isVideoSelf', () => {
|
|
344
|
-
it('should have #isVideoSelf', () => {
|
|
345
|
-
assert.exists(meeting.invite);
|
|
346
|
-
});
|
|
347
|
-
it('should get the video self status and return as a boolean', () => {
|
|
348
|
-
const self = meeting.isVideoSelf();
|
|
349
|
-
|
|
350
|
-
assert.isNotOk(self);
|
|
351
|
-
});
|
|
352
|
-
});
|
|
353
|
-
describe('#muteAudio', () => {
|
|
354
|
-
it('should have #muteAudio', () => {
|
|
355
|
-
assert.exists(meeting.muteAudio);
|
|
356
|
-
});
|
|
357
|
-
describe('before audio is defined', () => {
|
|
358
|
-
it('should reject and return a promise', async () => {
|
|
359
|
-
await meeting.muteAudio().catch((err) => {
|
|
360
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
361
|
-
});
|
|
362
|
-
});
|
|
363
|
-
|
|
364
|
-
it('should reject and return a promise', async () => {
|
|
365
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
366
|
-
await meeting.muteAudio().catch((err) => {
|
|
367
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
368
|
-
});
|
|
369
|
-
});
|
|
370
|
-
|
|
371
|
-
it('should reject and return a promise', async () => {
|
|
372
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
373
|
-
meeting.mediaId = 'mediaId';
|
|
374
|
-
await meeting.muteAudio().catch((err) => {
|
|
375
|
-
assert.instanceOf(err, ParameterError);
|
|
376
|
-
});
|
|
377
|
-
});
|
|
378
|
-
});
|
|
379
|
-
describe('after audio is defined', () => {
|
|
380
|
-
let handleClientRequest;
|
|
381
|
-
|
|
382
|
-
beforeEach(() => {
|
|
383
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
384
|
-
meeting.audio = {handleClientRequest};
|
|
385
|
-
});
|
|
386
|
-
|
|
387
|
-
it('should return a promise resolution', async () => {
|
|
388
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
389
|
-
meeting.mediaId = 'mediaId';
|
|
390
|
-
|
|
391
|
-
const audio = meeting.muteAudio();
|
|
392
|
-
|
|
393
|
-
assert.exists(audio.then);
|
|
394
|
-
await audio;
|
|
395
|
-
assert.calledOnce(handleClientRequest);
|
|
396
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
397
|
-
});
|
|
398
|
-
});
|
|
399
|
-
});
|
|
400
|
-
describe('#unmuteAudio', () => {
|
|
401
|
-
it('should have #unmuteAudio', () => {
|
|
402
|
-
assert.exists(meeting.unmuteAudio);
|
|
403
|
-
});
|
|
404
|
-
describe('before audio is defined', () => {
|
|
405
|
-
it('should reject when user not joined', async () => {
|
|
406
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
407
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
408
|
-
});
|
|
409
|
-
});
|
|
410
|
-
|
|
411
|
-
it('should reject when no media is established yet ', async () => {
|
|
412
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
413
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
414
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
415
|
-
});
|
|
416
|
-
});
|
|
417
|
-
|
|
418
|
-
it('should reject when audio is not there or established', async () => {
|
|
419
|
-
meeting.mediaId = 'mediaId';
|
|
420
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
421
|
-
await meeting.unmuteAudio().catch((err) => {
|
|
422
|
-
assert.instanceOf(err, ParameterError);
|
|
423
|
-
});
|
|
424
|
-
});
|
|
425
|
-
});
|
|
426
|
-
describe('after audio is defined', () => {
|
|
427
|
-
let handleClientRequest;
|
|
428
|
-
|
|
429
|
-
beforeEach(() => {
|
|
430
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
431
|
-
meeting.mediaId = 'mediaId';
|
|
432
|
-
meeting.audio = {handleClientRequest};
|
|
433
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
it('should return a promise resolution', async () => {
|
|
437
|
-
meeting.audio = {handleClientRequest};
|
|
438
|
-
|
|
439
|
-
const audio = meeting.unmuteAudio();
|
|
440
|
-
|
|
441
|
-
assert.exists(audio.then);
|
|
442
|
-
await audio;
|
|
443
|
-
assert.calledOnce(handleClientRequest);
|
|
444
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
445
|
-
});
|
|
446
|
-
});
|
|
447
|
-
});
|
|
448
|
-
describe('BNR', () => {
|
|
449
|
-
const fakeMediaTrack = () => ({
|
|
450
|
-
id: Date.now().toString(),
|
|
451
|
-
stop: () => {},
|
|
452
|
-
readyState: 'live',
|
|
453
|
-
enabled: true,
|
|
454
|
-
getSettings: () => ({
|
|
455
|
-
sampleRate: 48000,
|
|
456
|
-
}),
|
|
457
|
-
});
|
|
458
|
-
|
|
459
|
-
beforeEach(() => {
|
|
460
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve());
|
|
461
|
-
sinon.replace(meeting, 'addMedia', () => {
|
|
462
|
-
sinon.stub(meeting.mediaProperties, 'audioTrack').value(fakeMediaTrack());
|
|
463
|
-
sinon.stub(meeting.mediaProperties, 'mediaDirection').value({
|
|
464
|
-
receiveAudio: true,
|
|
465
|
-
});
|
|
466
|
-
});
|
|
467
|
-
});
|
|
468
|
-
describe('#enableBNR', () => {
|
|
469
|
-
it('should have #enableBnr', () => {
|
|
470
|
-
assert.exists(meeting.enableBNR);
|
|
471
|
-
});
|
|
472
|
-
|
|
473
|
-
describe('before audio attached to meeting', () => {
|
|
474
|
-
it('should throw no audio error', async () => {
|
|
475
|
-
await meeting.enableBNR().catch((err) => {
|
|
476
|
-
assert.equal(err.toString(), "Error: Meeting doesn't have an audioTrack attached");
|
|
477
|
-
});
|
|
478
|
-
});
|
|
479
|
-
});
|
|
480
|
-
|
|
481
|
-
describe('after audio attached to meeting', () => {
|
|
482
|
-
let handleClientRequest;
|
|
483
|
-
|
|
484
|
-
beforeEach(async () => {
|
|
485
|
-
await meeting.getMediaStreams();
|
|
486
|
-
await meeting.addMedia();
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
it('should throw error if meeting audio is muted', async () => {
|
|
490
|
-
const handleClientRequest = (meeting, mute) => {
|
|
491
|
-
meeting.mediaProperties.audioTrack.enabled = !mute;
|
|
492
|
-
|
|
493
|
-
return Promise.resolve();
|
|
494
|
-
};
|
|
495
|
-
const isMuted = () => !meeting.mediaProperties.audioTrack.enabled;
|
|
496
|
-
|
|
497
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
498
|
-
meeting.mediaId = 'mediaId';
|
|
499
|
-
meeting.audio = {handleClientRequest, isMuted};
|
|
500
|
-
await meeting.muteAudio();
|
|
501
|
-
await meeting.enableBNR().catch((err) => {
|
|
502
|
-
assert.equal(err.message, 'Cannot enable BNR while meeting is muted');
|
|
503
|
-
});
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('should return true on enable bnr success', async () => {
|
|
507
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
508
|
-
meeting.effects = {handleClientRequest};
|
|
509
|
-
const response = await meeting.enableBNR();
|
|
510
|
-
|
|
511
|
-
assert.equal(response, true);
|
|
512
|
-
});
|
|
513
|
-
});
|
|
514
|
-
});
|
|
515
|
-
|
|
516
|
-
describe('#disableBNR', () => {
|
|
517
|
-
describe('before audio attached to meeting', () => {
|
|
518
|
-
it('should have #disableBnr', () => {
|
|
519
|
-
assert.exists(meeting.disableBNR);
|
|
520
|
-
});
|
|
521
|
-
|
|
522
|
-
it('should throw no audio error', async () => {
|
|
523
|
-
await meeting.disableBNR().catch((err) => {
|
|
524
|
-
assert.equal(err.toString(), "Error: Meeting doesn't have an audioTrack attached");
|
|
525
|
-
});
|
|
526
|
-
});
|
|
527
|
-
});
|
|
528
|
-
describe('after audio attached to meeting', () => {
|
|
529
|
-
beforeEach(async () => {
|
|
530
|
-
await meeting.getMediaStreams();
|
|
531
|
-
await meeting.addMedia();
|
|
532
|
-
});
|
|
533
|
-
|
|
534
|
-
let handleClientRequest;
|
|
535
|
-
let isBnrEnabled;
|
|
536
|
-
|
|
537
|
-
it('should return true on disable bnr success', async () => {
|
|
538
|
-
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
539
|
-
isBnrEnabled = sinon.stub().returns(Promise.resolve(true));
|
|
540
|
-
meeting.effects = {handleClientRequest, isBnrEnabled};
|
|
541
|
-
const response = await meeting.disableBNR();
|
|
542
|
-
|
|
543
|
-
assert.equal(response, true);
|
|
544
|
-
});
|
|
545
|
-
});
|
|
546
|
-
});
|
|
547
|
-
});
|
|
548
|
-
describe('#muteVideo', () => {
|
|
549
|
-
it('should have #muteVideo', () => {
|
|
550
|
-
assert.exists(meeting.muteVideo);
|
|
551
|
-
});
|
|
552
|
-
describe('before video is defined', () => {
|
|
553
|
-
it('should reject when user not joined', async () => {
|
|
554
|
-
await meeting.muteVideo().catch((err) => {
|
|
555
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
556
|
-
});
|
|
557
|
-
});
|
|
558
|
-
|
|
559
|
-
it('should reject when no media is established', async () => {
|
|
560
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
561
|
-
await meeting.muteVideo().catch((err) => {
|
|
562
|
-
assert.instanceOf(err, NoMediaEstablishedYetError);
|
|
563
|
-
});
|
|
564
|
-
});
|
|
565
|
-
|
|
566
|
-
it('should reject when no video added or established', async () => {
|
|
567
|
-
meeting.mediaId = 'mediaId';
|
|
568
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
569
|
-
await meeting.muteVideo().catch((err) => {
|
|
570
|
-
assert.instanceOf(err, ParameterError);
|
|
571
|
-
});
|
|
572
|
-
});
|
|
573
|
-
});
|
|
574
|
-
describe('after video is defined', () => {
|
|
575
|
-
it('should return a promise resolution', async () => {
|
|
576
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
577
|
-
|
|
578
|
-
meeting.mediaId = 'mediaId';
|
|
579
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
580
|
-
meeting.video = {handleClientRequest};
|
|
581
|
-
const video = meeting.muteVideo();
|
|
582
|
-
|
|
583
|
-
assert.exists(video.then);
|
|
584
|
-
await video;
|
|
585
|
-
assert.calledOnce(handleClientRequest);
|
|
586
|
-
assert.calledWith(handleClientRequest, meeting, true);
|
|
587
|
-
});
|
|
588
|
-
});
|
|
589
|
-
});
|
|
590
|
-
describe('#unmuteVideo', () => {
|
|
591
|
-
it('should have #unmuteVideo', () => {
|
|
592
|
-
assert.exists(meeting.unmuteVideo);
|
|
593
|
-
});
|
|
594
|
-
describe('before video is defined', () => {
|
|
595
|
-
it('should reject no user joined', async () => {
|
|
596
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
597
|
-
assert.instanceOf(err, Error);
|
|
598
|
-
});
|
|
599
|
-
});
|
|
600
|
-
|
|
601
|
-
it('should reject no media established', async () => {
|
|
602
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
603
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
604
|
-
assert.instanceOf(err, Error);
|
|
605
|
-
});
|
|
606
|
-
});
|
|
607
|
-
|
|
608
|
-
it('should reject when no video added or established', async () => {
|
|
609
|
-
meeting.mediaId = 'mediaId';
|
|
610
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
611
|
-
await meeting.unmuteVideo().catch((err) => {
|
|
612
|
-
assert.instanceOf(err, Error);
|
|
613
|
-
});
|
|
614
|
-
});
|
|
615
|
-
});
|
|
616
|
-
describe('after video is defined', () => {
|
|
617
|
-
it('should return a promise resolution', async () => {
|
|
618
|
-
const handleClientRequest = sinon.stub().returns(Promise.resolve());
|
|
619
487
|
|
|
620
|
-
meeting.mediaId = 'mediaId';
|
|
621
|
-
meeting.locusInfo.parsedLocus = {self: {state: 'JOINED'}};
|
|
622
|
-
meeting.video = {handleClientRequest};
|
|
623
|
-
const video = meeting.unmuteVideo();
|
|
624
|
-
|
|
625
|
-
assert.exists(video.then);
|
|
626
|
-
await video;
|
|
627
|
-
assert.calledOnce(handleClientRequest);
|
|
628
|
-
assert.calledWith(handleClientRequest, meeting, false);
|
|
629
|
-
});
|
|
630
|
-
});
|
|
631
|
-
});
|
|
632
488
|
describe('#joinWithMedia', () => {
|
|
633
489
|
it('should have #joinWithMedia', () => {
|
|
634
490
|
assert.exists(meeting.joinWithMedia);
|
|
@@ -636,125 +492,21 @@ describe('plugin-meetings', () => {
|
|
|
636
492
|
describe('resolution', () => {
|
|
637
493
|
it('should success and return a promise', async () => {
|
|
638
494
|
meeting.join = sinon.stub().returns(Promise.resolve(test1));
|
|
639
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([test2, test3]));
|
|
640
495
|
meeting.addMedia = sinon.stub().returns(Promise.resolve(test4));
|
|
641
|
-
await meeting.joinWithMedia({});
|
|
496
|
+
const result = await meeting.joinWithMedia({});
|
|
642
497
|
assert.calledOnce(meeting.join);
|
|
643
|
-
assert.calledOnce(meeting.
|
|
498
|
+
assert.calledOnce(meeting.addMedia);
|
|
499
|
+
assert.deepEqual(result, {join: test1, media: test4});
|
|
644
500
|
});
|
|
645
501
|
});
|
|
646
502
|
describe('rejection', () => {
|
|
647
503
|
it('should error out and return a promise', async () => {
|
|
648
504
|
meeting.join = sinon.stub().returns(Promise.reject());
|
|
649
|
-
meeting.getMediaStreams = sinon.stub().returns(true);
|
|
650
505
|
assert.isRejected(meeting.joinWithMedia({}));
|
|
651
506
|
});
|
|
652
507
|
});
|
|
653
508
|
});
|
|
654
|
-
describe('#getMediaStreams', () => {
|
|
655
|
-
beforeEach(() => {
|
|
656
|
-
sinon
|
|
657
|
-
.stub(Media, 'getSupportedDevice')
|
|
658
|
-
.callsFake((options) =>
|
|
659
|
-
Promise.resolve({sendAudio: options.sendAudio, sendVideo: options.sendVideo})
|
|
660
|
-
);
|
|
661
|
-
sinon.stub(Media, 'getUserMedia').returns(Promise.resolve(['stream1', 'stream2']));
|
|
662
|
-
});
|
|
663
|
-
afterEach(() => {
|
|
664
|
-
sinon.restore();
|
|
665
|
-
});
|
|
666
|
-
it('should have #getMediaStreams', () => {
|
|
667
|
-
assert.exists(meeting.getMediaStreams);
|
|
668
|
-
});
|
|
669
|
-
it('should proxy Media getUserMedia, and return a promise', async () => {
|
|
670
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: true});
|
|
671
|
-
|
|
672
|
-
assert.calledOnce(Media.getUserMedia);
|
|
673
|
-
});
|
|
674
|
-
|
|
675
|
-
it('uses the preferred video device if set', async () => {
|
|
676
|
-
const videoDevice = 'video1';
|
|
677
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
678
|
-
const audioVideoSettings = {};
|
|
679
|
-
|
|
680
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(videoDevice);
|
|
681
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('480p');
|
|
682
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
683
|
-
|
|
684
|
-
assert.calledWith(
|
|
685
|
-
Media.getUserMedia,
|
|
686
|
-
{
|
|
687
|
-
...mediaDirection,
|
|
688
|
-
isSharing: false,
|
|
689
|
-
},
|
|
690
|
-
{
|
|
691
|
-
video: {
|
|
692
|
-
width: {max: 640, ideal: 640},
|
|
693
|
-
height: {max: 480, ideal: 480},
|
|
694
|
-
deviceId: videoDevice,
|
|
695
|
-
},
|
|
696
|
-
}
|
|
697
|
-
);
|
|
698
|
-
});
|
|
699
|
-
it('will set a new preferred video input device if passed in', async () => {
|
|
700
|
-
// if audioVideo settings parameter specifies a new video device it
|
|
701
|
-
// will store that device as the preferred video device.
|
|
702
|
-
// Which is the case with meeting.updateVideo()
|
|
703
|
-
const oldVideoDevice = 'video1';
|
|
704
|
-
const newVideoDevice = 'video2';
|
|
705
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
706
|
-
const audioVideoSettings = {video: {deviceId: newVideoDevice}};
|
|
707
|
-
|
|
708
|
-
sinon.stub(meeting.mediaProperties, 'videoDeviceId').value(oldVideoDevice);
|
|
709
|
-
sinon.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
710
|
-
|
|
711
|
-
await meeting.getMediaStreams(mediaDirection, audioVideoSettings);
|
|
712
|
-
|
|
713
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, newVideoDevice);
|
|
714
|
-
});
|
|
715
|
-
|
|
716
|
-
it('uses the passed custom video resolution', async () => {
|
|
717
|
-
const mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
718
|
-
const customAudioVideoSettings = {
|
|
719
|
-
video: {
|
|
720
|
-
width: {
|
|
721
|
-
max: 400,
|
|
722
|
-
ideal: 400,
|
|
723
|
-
},
|
|
724
|
-
height: {
|
|
725
|
-
max: 200,
|
|
726
|
-
ideal: 200,
|
|
727
|
-
},
|
|
728
|
-
frameRate: {
|
|
729
|
-
ideal: 15,
|
|
730
|
-
max: 30,
|
|
731
|
-
},
|
|
732
|
-
facingMode: {
|
|
733
|
-
ideal: 'user',
|
|
734
|
-
},
|
|
735
|
-
},
|
|
736
|
-
};
|
|
737
|
-
|
|
738
|
-
sinon.stub(meeting.mediaProperties, 'localQualityLevel').value('200p');
|
|
739
|
-
await meeting.getMediaStreams(mediaDirection, customAudioVideoSettings);
|
|
740
|
-
|
|
741
|
-
assert.calledWith(
|
|
742
|
-
Media.getUserMedia,
|
|
743
|
-
{
|
|
744
|
-
...mediaDirection,
|
|
745
|
-
isSharing: false,
|
|
746
|
-
},
|
|
747
|
-
customAudioVideoSettings
|
|
748
|
-
);
|
|
749
|
-
});
|
|
750
|
-
it('should not access camera if sendVideo is false ', async () => {
|
|
751
|
-
await meeting.getMediaStreams({sendAudio: true, sendVideo: false});
|
|
752
|
-
|
|
753
|
-
assert.calledOnce(Media.getUserMedia);
|
|
754
509
|
|
|
755
|
-
assert.equal(Media.getUserMedia.args[0][0].sendVideo, false);
|
|
756
|
-
});
|
|
757
|
-
});
|
|
758
510
|
describe('#isTranscriptionSupported', () => {
|
|
759
511
|
it('should return false if the feature is not supported for the meeting', () => {
|
|
760
512
|
meeting.locusInfo.controls = {transcribe: {transcribing: false}};
|
|
@@ -779,7 +531,7 @@ describe('plugin-meetings', () => {
|
|
|
779
531
|
});
|
|
780
532
|
});
|
|
781
533
|
|
|
782
|
-
it(
|
|
534
|
+
it("should throw error if request doesn't work", async () => {
|
|
783
535
|
meeting.request = sinon.stub().returns(Promise.reject());
|
|
784
536
|
|
|
785
537
|
try {
|
|
@@ -799,6 +551,64 @@ describe('plugin-meetings', () => {
|
|
|
799
551
|
assert.calledOnce(meeting.transcription.closeSocket);
|
|
800
552
|
});
|
|
801
553
|
});
|
|
554
|
+
describe('#isReactionsSupported', () => {
|
|
555
|
+
it('should return false if the feature is not supported for the meeting', () => {
|
|
556
|
+
meeting.locusInfo.controls = {reactions: {enabled: false}};
|
|
557
|
+
|
|
558
|
+
assert.equal(meeting.isReactionsSupported(), false);
|
|
559
|
+
});
|
|
560
|
+
it('should return true if the feature is not supported for the meeting', () => {
|
|
561
|
+
meeting.locusInfo.controls = {reactions: {enabled: true}};
|
|
562
|
+
|
|
563
|
+
assert.equal(meeting.isReactionsSupported(), true);
|
|
564
|
+
});
|
|
565
|
+
});
|
|
566
|
+
describe('#processRelayEvent', () => {
|
|
567
|
+
it('should process a Reaction event type', () => {
|
|
568
|
+
meeting.isReactionsSupported = sinon.stub().returns(true);
|
|
569
|
+
meeting.config.receiveReactions = true;
|
|
570
|
+
const fakeSendersName = 'Fake reactors name';
|
|
571
|
+
meeting.members.membersCollection.get = sinon.stub().returns({name: fakeSendersName});
|
|
572
|
+
const fakeReactionPayload = {
|
|
573
|
+
type: 'fake_type',
|
|
574
|
+
codepoints: 'fake_codepoints',
|
|
575
|
+
shortcodes: 'fake_shortcodes',
|
|
576
|
+
tone: {
|
|
577
|
+
type: 'fake_tone_type',
|
|
578
|
+
codepoints: 'fake_tone_codepoints',
|
|
579
|
+
shortcodes: 'fake_tone_shortcodes',
|
|
580
|
+
},
|
|
581
|
+
};
|
|
582
|
+
const fakeSenderPayload = {
|
|
583
|
+
participantId: 'fake_participant_id',
|
|
584
|
+
};
|
|
585
|
+
const fakeProcessedReaction = {
|
|
586
|
+
reaction: fakeReactionPayload,
|
|
587
|
+
sender: {
|
|
588
|
+
id: fakeSenderPayload.participantId,
|
|
589
|
+
name: fakeSendersName,
|
|
590
|
+
},
|
|
591
|
+
};
|
|
592
|
+
const fakeRelayEvent = {
|
|
593
|
+
data: {
|
|
594
|
+
relayType: REACTION_RELAY_TYPES.REACTION,
|
|
595
|
+
reaction: fakeReactionPayload,
|
|
596
|
+
sender: fakeSenderPayload,
|
|
597
|
+
},
|
|
598
|
+
};
|
|
599
|
+
meeting.processRelayEvent(fakeRelayEvent);
|
|
600
|
+
assert.calledWith(
|
|
601
|
+
TriggerProxy.trigger,
|
|
602
|
+
sinon.match.instanceOf(Meeting),
|
|
603
|
+
{
|
|
604
|
+
file: 'meeting/index',
|
|
605
|
+
function: 'join',
|
|
606
|
+
},
|
|
607
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
608
|
+
fakeProcessedReaction
|
|
609
|
+
);
|
|
610
|
+
});
|
|
611
|
+
});
|
|
802
612
|
describe('#join', () => {
|
|
803
613
|
let sandbox = null;
|
|
804
614
|
const joinMeetingResult = 'JOIN_MEETINGS_OPTION_RESULT';
|
|
@@ -873,7 +683,67 @@ describe('plugin-meetings', () => {
|
|
|
873
683
|
await meeting.join();
|
|
874
684
|
sinon.assert.called(meeting.setCorrelationId);
|
|
875
685
|
});
|
|
876
|
-
|
|
686
|
+
|
|
687
|
+
it('should send Meeting Info CA events if meetingInfo is not empty', async () => {
|
|
688
|
+
meeting.meetingInfo = {info: 'info', meetingLookupUrl: 'url'};
|
|
689
|
+
|
|
690
|
+
const join = meeting.join();
|
|
691
|
+
|
|
692
|
+
assert.calledWithMatch(Metrics.postEvent, {
|
|
693
|
+
event: eventType.CALL_INITIATED,
|
|
694
|
+
data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
assert.exists(join.then);
|
|
698
|
+
const result = await join;
|
|
699
|
+
|
|
700
|
+
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
701
|
+
assert.calledOnce(meeting.setLocus);
|
|
702
|
+
assert.equal(result, joinMeetingResult);
|
|
703
|
+
|
|
704
|
+
assert.calledThrice(Metrics.postEvent)
|
|
705
|
+
|
|
706
|
+
assert.deepEqual(Metrics.postEvent.getCall(0).args[0].event, 'client.call.initiated');
|
|
707
|
+
assert.deepEqual(Metrics.postEvent.getCall(0).args[0].data, {
|
|
708
|
+
isRoapCallEnabled: true,
|
|
709
|
+
trigger: 'user-interaction',
|
|
710
|
+
});
|
|
711
|
+
assert.deepEqual(
|
|
712
|
+
Metrics.postEvent.getCall(1).args[0].event,
|
|
713
|
+
'client.meetinginfo.request'
|
|
714
|
+
);
|
|
715
|
+
assert.deepEqual(Metrics.postEvent.getCall(1).args[0].data, undefined);
|
|
716
|
+
assert.deepEqual(
|
|
717
|
+
Metrics.postEvent.getCall(2).args[0].event,
|
|
718
|
+
'client.meetinginfo.response'
|
|
719
|
+
);
|
|
720
|
+
assert.deepEqual(Metrics.postEvent.getCall(2).args[0].data, {
|
|
721
|
+
meetingLookupUrl: 'url',
|
|
722
|
+
});
|
|
723
|
+
});
|
|
724
|
+
|
|
725
|
+
it('should not send Meeting Info CA events if meetingInfo is empty', async () => {
|
|
726
|
+
meeting.meetingInfo = {};
|
|
727
|
+
|
|
728
|
+
const join = meeting.join();
|
|
729
|
+
|
|
730
|
+
assert.calledWithMatch(Metrics.postEvent, {
|
|
731
|
+
event: eventType.CALL_INITIATED,
|
|
732
|
+
data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
assert.exists(join.then);
|
|
736
|
+
const result = await join;
|
|
737
|
+
|
|
738
|
+
assert.calledOnce(MeetingUtil.joinMeeting);
|
|
739
|
+
assert.calledOnce(meeting.setLocus);
|
|
740
|
+
assert.equal(result, joinMeetingResult);
|
|
741
|
+
|
|
742
|
+
assert.calledOnce(Metrics.postEvent)
|
|
743
|
+
|
|
744
|
+
assert.equal(Metrics.postEvent.getCall(0).args[0].event, 'client.call.initiated');
|
|
745
|
+
});
|
|
746
|
+
});
|
|
877
747
|
describe('failure', () => {
|
|
878
748
|
beforeEach(() => {
|
|
879
749
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.reject());
|
|
@@ -952,7 +822,7 @@ describe('plugin-meetings', () => {
|
|
|
952
822
|
beforeEach(() => {
|
|
953
823
|
fakeMediaConnection = {
|
|
954
824
|
close: sinon.stub(),
|
|
955
|
-
getConnectionState: sinon.stub().returns(
|
|
825
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
956
826
|
initiateOffer: sinon.stub().resolves({}),
|
|
957
827
|
on: sinon.stub(),
|
|
958
828
|
};
|
|
@@ -961,7 +831,7 @@ describe('plugin-meetings', () => {
|
|
|
961
831
|
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
962
832
|
meeting.audio = muteStateStub;
|
|
963
833
|
meeting.video = muteStateStub;
|
|
964
|
-
|
|
834
|
+
sinon.stub(Media, 'createMediaConnection').returns(fakeMediaConnection);
|
|
965
835
|
meeting.setMercuryListener = sinon.stub().returns(true);
|
|
966
836
|
meeting.setupMediaConnectionListeners = sinon.stub();
|
|
967
837
|
meeting.setMercuryListener = sinon.stub();
|
|
@@ -1022,6 +892,10 @@ describe('plugin-meetings', () => {
|
|
|
1022
892
|
code: error.code,
|
|
1023
893
|
turnDiscoverySkippedReason: undefined,
|
|
1024
894
|
turnServerUsed: true,
|
|
895
|
+
isMultistream: false,
|
|
896
|
+
signalingState: 'unknown',
|
|
897
|
+
connectionState: 'unknown',
|
|
898
|
+
iceConnectionState: 'unknown'
|
|
1025
899
|
});
|
|
1026
900
|
});
|
|
1027
901
|
|
|
@@ -1038,8 +912,13 @@ describe('plugin-meetings', () => {
|
|
|
1038
912
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1039
913
|
reason: err.message,
|
|
1040
914
|
stack: err.stack,
|
|
915
|
+
code: err.code,
|
|
1041
916
|
turnDiscoverySkippedReason: 'config',
|
|
1042
917
|
turnServerUsed: false,
|
|
918
|
+
isMultistream: false,
|
|
919
|
+
signalingState: 'unknown',
|
|
920
|
+
connectionState: 'unknown',
|
|
921
|
+
iceConnectionState: 'unknown'
|
|
1043
922
|
});
|
|
1044
923
|
});
|
|
1045
924
|
});
|
|
@@ -1053,21 +932,111 @@ describe('plugin-meetings', () => {
|
|
|
1053
932
|
});
|
|
1054
933
|
const result = await assert.isRejected(meeting.addMedia());
|
|
1055
934
|
|
|
935
|
+
|
|
936
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
937
|
+
assert.calledWith(
|
|
938
|
+
Metrics.sendBehavioralMetric,
|
|
939
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
940
|
+
sinon.match({
|
|
941
|
+
correlation_id: meeting.correlationId,
|
|
942
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
943
|
+
reason: result.message,
|
|
944
|
+
turnDiscoverySkippedReason: undefined,
|
|
945
|
+
turnServerUsed: true,
|
|
946
|
+
isMultistream: false,
|
|
947
|
+
signalingState: 'unknown',
|
|
948
|
+
connectionState: 'unknown',
|
|
949
|
+
iceConnectionState: 'unknown'
|
|
950
|
+
})
|
|
951
|
+
);
|
|
952
|
+
|
|
1056
953
|
assert.instanceOf(result, Error);
|
|
1057
954
|
assert.isNull(meeting.mediaProperties.webrtcMediaConnection);
|
|
1058
955
|
|
|
956
|
+
});
|
|
957
|
+
|
|
958
|
+
it('should include the peer connection properties correctly for multistream', async () => {
|
|
959
|
+
meeting.meetingState = 'ACTIVE';
|
|
960
|
+
// setup the mock to return an incomplete object - this will cause addMedia to fail
|
|
961
|
+
// because some methods (like on() or initiateOffer()) are missing
|
|
962
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
963
|
+
close: sinon.stub(),
|
|
964
|
+
multistreamConnection :{
|
|
965
|
+
pc: {
|
|
966
|
+
pc: {
|
|
967
|
+
signalingState: 'have-local-offer',
|
|
968
|
+
connectionState: 'connecting',
|
|
969
|
+
iceConnectionState: 'checking',
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
});
|
|
974
|
+
// set a statsAnalyzer on the meeting so that we can check that it gets reset to null
|
|
975
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
976
|
+
const error = await assert.isRejected(meeting.addMedia());
|
|
977
|
+
|
|
978
|
+
assert.calledWith(
|
|
979
|
+
Metrics.sendBehavioralMetric,
|
|
980
|
+
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
981
|
+
sinon.match({
|
|
982
|
+
correlation_id: meeting.correlationId,
|
|
983
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
984
|
+
reason: error.message,
|
|
985
|
+
stack: error.stack,
|
|
986
|
+
code: error.code,
|
|
987
|
+
turnDiscoverySkippedReason: undefined,
|
|
988
|
+
turnServerUsed: true,
|
|
989
|
+
isMultistream: false,
|
|
990
|
+
signalingState: 'have-local-offer',
|
|
991
|
+
connectionState: 'connecting',
|
|
992
|
+
iceConnectionState: 'checking'
|
|
993
|
+
|
|
994
|
+
})
|
|
995
|
+
);
|
|
996
|
+
|
|
997
|
+
assert.isNull(meeting.statsAnalyzer);
|
|
1059
998
|
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
999
|
+
});
|
|
1000
|
+
|
|
1001
|
+
it('should include the peer connection properties correctly for transcoded', async () => {
|
|
1002
|
+
meeting.meetingState = 'ACTIVE';
|
|
1003
|
+
// setup the mock to return an incomplete object - this will cause addMedia to fail
|
|
1004
|
+
// because some methods (like on() or initiateOffer()) are missing
|
|
1005
|
+
Media.createMediaConnection = sinon.stub().returns({
|
|
1006
|
+
close: sinon.stub(),
|
|
1007
|
+
mediaConnection :{
|
|
1008
|
+
pc: {
|
|
1009
|
+
signalingState: 'have-local-offer',
|
|
1010
|
+
connectionState: 'connecting',
|
|
1011
|
+
iceConnectionState: 'checking',
|
|
1012
|
+
}
|
|
1013
|
+
}
|
|
1014
|
+
});
|
|
1015
|
+
// set a statsAnalyzer on the meeting so that we can check that it gets reset to null
|
|
1016
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
1017
|
+
const error = await assert.isRejected(meeting.addMedia());
|
|
1018
|
+
|
|
1060
1019
|
assert.calledWith(
|
|
1061
1020
|
Metrics.sendBehavioralMetric,
|
|
1062
1021
|
BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE,
|
|
1063
1022
|
sinon.match({
|
|
1064
1023
|
correlation_id: meeting.correlationId,
|
|
1065
1024
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1066
|
-
reason:
|
|
1025
|
+
reason: error.message,
|
|
1026
|
+
stack: error.stack,
|
|
1027
|
+
code: error.code,
|
|
1067
1028
|
turnDiscoverySkippedReason: undefined,
|
|
1068
1029
|
turnServerUsed: true,
|
|
1030
|
+
isMultistream: false,
|
|
1031
|
+
signalingState: 'have-local-offer',
|
|
1032
|
+
connectionState: 'connecting',
|
|
1033
|
+
iceConnectionState: 'checking'
|
|
1034
|
+
|
|
1069
1035
|
})
|
|
1070
1036
|
);
|
|
1037
|
+
|
|
1038
|
+
assert.isNull(meeting.statsAnalyzer);
|
|
1039
|
+
assert(Metrics.sendBehavioralMetric.calledOnce);
|
|
1071
1040
|
});
|
|
1072
1041
|
|
|
1073
1042
|
it('should work the second time addMedia is called in case the first time fails', async () => {
|
|
@@ -1198,9 +1167,7 @@ describe('plugin-meetings', () => {
|
|
|
1198
1167
|
|
|
1199
1168
|
it('should attach the media and return WebExMeetingsErrors when connection does not reach CONNECTED state', async () => {
|
|
1200
1169
|
meeting.meetingState = 'ACTIVE';
|
|
1201
|
-
fakeMediaConnection.getConnectionState = sinon
|
|
1202
|
-
.stub()
|
|
1203
|
-
.returns(MC.ConnectionState.Connecting);
|
|
1170
|
+
fakeMediaConnection.getConnectionState = sinon.stub().returns(ConnectionState.Connecting);
|
|
1204
1171
|
const clock = sinon.useFakeTimers();
|
|
1205
1172
|
const media = meeting.addMedia({
|
|
1206
1173
|
mediaSettings: {},
|
|
@@ -1230,8 +1197,7 @@ describe('plugin-meetings', () => {
|
|
|
1230
1197
|
.addMedia({
|
|
1231
1198
|
mediaSettings: {},
|
|
1232
1199
|
})
|
|
1233
|
-
.catch((
|
|
1234
|
-
assert.equal(error.code, IceGatheringFailed.CODE);
|
|
1200
|
+
.catch(() => {
|
|
1235
1201
|
errorThrown = true;
|
|
1236
1202
|
});
|
|
1237
1203
|
|
|
@@ -1249,6 +1215,7 @@ describe('plugin-meetings', () => {
|
|
|
1249
1215
|
correlation_id: meeting.correlationId,
|
|
1250
1216
|
locus_id: meeting.locusUrl.split('/').pop(),
|
|
1251
1217
|
connectionType: 'udp',
|
|
1218
|
+
isMultistream: false
|
|
1252
1219
|
});
|
|
1253
1220
|
});
|
|
1254
1221
|
|
|
@@ -1366,628 +1333,864 @@ describe('plugin-meetings', () => {
|
|
|
1366
1333
|
});
|
|
1367
1334
|
});
|
|
1368
1335
|
});
|
|
1369
|
-
});
|
|
1370
|
-
describe('#acknowledge', () => {
|
|
1371
|
-
it('should have #acknowledge', () => {
|
|
1372
|
-
assert.exists(meeting.acknowledge);
|
|
1373
|
-
});
|
|
1374
|
-
beforeEach(() => {
|
|
1375
|
-
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
1376
|
-
});
|
|
1377
|
-
it('should acknowledge incoming and return a promise', async () => {
|
|
1378
|
-
const ack = meeting.acknowledge('INCOMING', false);
|
|
1379
|
-
|
|
1380
|
-
assert.exists(ack.then);
|
|
1381
|
-
await ack;
|
|
1382
|
-
assert.calledOnce(meeting.meetingRequest.acknowledgeMeeting);
|
|
1383
|
-
});
|
|
1384
|
-
it('should acknowledge a non incoming and return a promise', async () => {
|
|
1385
|
-
const ack = meeting.acknowledge(test1, false);
|
|
1386
1336
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
});
|
|
1392
|
-
describe('#decline', () => {
|
|
1393
|
-
it('should have #decline', () => {
|
|
1394
|
-
assert.exists(meeting.decline);
|
|
1395
|
-
});
|
|
1396
|
-
beforeEach(() => {
|
|
1397
|
-
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
1398
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1399
|
-
});
|
|
1400
|
-
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
1401
|
-
await meeting.decline();
|
|
1402
|
-
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
1403
|
-
});
|
|
1404
|
-
});
|
|
1405
|
-
describe('#leave', () => {
|
|
1406
|
-
let sandbox;
|
|
1337
|
+
it('should pass bundlePolicy to createMediaConnection', async () => {
|
|
1338
|
+
const FAKE_TURN_URL = 'turns:webex.com:3478';
|
|
1339
|
+
const FAKE_TURN_USER = 'some-turn-username';
|
|
1340
|
+
const FAKE_TURN_PASSWORD = 'some-password';
|
|
1407
1341
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
});
|
|
1342
|
+
meeting.meetingState = 'ACTIVE';
|
|
1343
|
+
Media.createMediaConnection.resetHistory();
|
|
1411
1344
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1345
|
+
meeting.roap.doTurnDiscovery = sinon.stub().resolves({
|
|
1346
|
+
turnServerInfo: {
|
|
1347
|
+
url: FAKE_TURN_URL,
|
|
1348
|
+
username: FAKE_TURN_USER,
|
|
1349
|
+
password: FAKE_TURN_PASSWORD,
|
|
1350
|
+
},
|
|
1351
|
+
turnServerSkippedReason: undefined,
|
|
1415
1352
|
});
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
meeting.meetingState = 'ACTIVE';
|
|
1420
|
-
await meeting.leave().catch((err) => {
|
|
1421
|
-
assert.instanceOf(err, UserNotJoinedError);
|
|
1353
|
+
const media = meeting.addMedia({
|
|
1354
|
+
mediaSettings: {},
|
|
1355
|
+
bundlePolicy: 'bundlePolicy-value',
|
|
1422
1356
|
});
|
|
1423
|
-
});
|
|
1424
1357
|
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
meeting.
|
|
1428
|
-
meeting.
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
.
|
|
1432
|
-
|
|
1433
|
-
|
|
1434
|
-
|
|
1435
|
-
|
|
1436
|
-
|
|
1437
|
-
|
|
1438
|
-
|
|
1439
|
-
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
meeting.logger.error = sinon.stub().returns(true);
|
|
1446
|
-
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
1358
|
+
assert.exists(media);
|
|
1359
|
+
await media;
|
|
1360
|
+
assert.calledOnce(meeting.roap.doTurnDiscovery);
|
|
1361
|
+
assert.calledWith(meeting.roap.doTurnDiscovery, meeting, false);
|
|
1362
|
+
assert.calledOnce(Media.createMediaConnection);
|
|
1363
|
+
assert.calledWith(
|
|
1364
|
+
Media.createMediaConnection,
|
|
1365
|
+
false,
|
|
1366
|
+
meeting.getMediaConnectionDebugId(),
|
|
1367
|
+
sinon.match({
|
|
1368
|
+
turnServerInfo: {
|
|
1369
|
+
url: FAKE_TURN_URL,
|
|
1370
|
+
username: FAKE_TURN_USER,
|
|
1371
|
+
password: FAKE_TURN_PASSWORD,
|
|
1372
|
+
},
|
|
1373
|
+
bundlePolicy: 'bundlePolicy-value',
|
|
1374
|
+
})
|
|
1375
|
+
);
|
|
1376
|
+
assert.calledOnce(fakeMediaConnection.initiateOffer);
|
|
1377
|
+
});
|
|
1447
1378
|
|
|
1448
|
-
|
|
1379
|
+
it('succeeds even if getDevices() throws', async () => {
|
|
1449
1380
|
meeting.meetingState = 'ACTIVE';
|
|
1450
|
-
meeting.state = 'JOINED';
|
|
1451
|
-
});
|
|
1452
|
-
afterEach(() => {
|
|
1453
|
-
sandbox.restore();
|
|
1454
|
-
sandbox = null;
|
|
1455
|
-
});
|
|
1456
|
-
it('should leave the meeting and return promise', async () => {
|
|
1457
|
-
const leave = meeting.leave();
|
|
1458
1381
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
assert.calledOnce(meeting.closeLocalStream);
|
|
1463
|
-
assert.calledOnce(meeting.closeLocalShare);
|
|
1464
|
-
assert.calledOnce(meeting.closeRemoteTracks);
|
|
1465
|
-
assert.calledOnce(meeting.closePeerConnections);
|
|
1466
|
-
assert.calledOnce(meeting.unsetLocalVideoTrack);
|
|
1467
|
-
assert.calledOnce(meeting.unsetLocalShareTrack);
|
|
1468
|
-
assert.calledOnce(meeting.unsetRemoteTracks);
|
|
1469
|
-
assert.calledOnce(meeting.unsetPeerConnections);
|
|
1470
|
-
});
|
|
1471
|
-
describe('after audio/video is defined', () => {
|
|
1472
|
-
let handleClientRequest;
|
|
1382
|
+
sinon
|
|
1383
|
+
.stub(internalMediaModule, 'getDevices')
|
|
1384
|
+
.rejects(new Error('fake error'));
|
|
1473
1385
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1386
|
+
await meeting.addMedia();
|
|
1387
|
+
})
|
|
1388
|
+
});
|
|
1476
1389
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1390
|
+
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
1391
|
+
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
1392
|
+
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
1393
|
+
to @webex/internal-media-core when addMedia, updateMedia, publishTracks, unpublishTracks are called
|
|
1394
|
+
in various combinations.
|
|
1395
|
+
*/
|
|
1396
|
+
[true,false].forEach((isMultistream) =>
|
|
1397
|
+
describe(`addMedia/updateMedia semi-integration tests (${isMultistream ? 'multistream' : 'transcoded'})`, () => {
|
|
1398
|
+
const webrtcAudioTrack = {
|
|
1399
|
+
id: 'underlying audio track',
|
|
1400
|
+
getSettings: sinon.stub().returns({deviceId: 'fake device id for audio track'}),
|
|
1401
|
+
};
|
|
1480
1402
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1403
|
+
let fakeMicrophoneTrack;
|
|
1404
|
+
let fakeRoapMediaConnection;
|
|
1405
|
+
let fakeMultistreamRoapMediaConnection;
|
|
1406
|
+
let roapMediaConnectionConstructorStub;
|
|
1407
|
+
let multistreamRoapMediaConnectionConstructorStub;
|
|
1408
|
+
let locusMediaRequestStub; // stub for /media requests to Locus
|
|
1483
1409
|
|
|
1484
|
-
|
|
1485
|
-
await leave;
|
|
1410
|
+
const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
|
|
1486
1411
|
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
});
|
|
1490
|
-
});
|
|
1491
|
-
it('should leave the meeting without leaving resource', async () => {
|
|
1492
|
-
const leave = meeting.leave({resourceId: null});
|
|
1412
|
+
let expectedMediaConnectionConfig;
|
|
1413
|
+
let expectedDebugId;
|
|
1493
1414
|
|
|
1494
|
-
|
|
1495
|
-
await leave;
|
|
1496
|
-
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
1497
|
-
locusUrl: meeting.locusUrl,
|
|
1498
|
-
correlationId: meeting.correlationId,
|
|
1499
|
-
selfId: meeting.selfId,
|
|
1500
|
-
resourceId: null,
|
|
1501
|
-
deviceUrl: meeting.deviceUrl,
|
|
1502
|
-
});
|
|
1503
|
-
});
|
|
1504
|
-
it('should leave the meeting on the resource', async () => {
|
|
1505
|
-
const leave = meeting.leave();
|
|
1415
|
+
let clock;
|
|
1506
1416
|
|
|
1507
|
-
assert.exists(leave.then);
|
|
1508
|
-
await leave;
|
|
1509
|
-
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
1510
|
-
locusUrl: meeting.locusUrl,
|
|
1511
|
-
correlationId: meeting.correlationId,
|
|
1512
|
-
selfId: meeting.selfId,
|
|
1513
|
-
resourceId: meeting.resourceId,
|
|
1514
|
-
deviceUrl: meeting.deviceUrl,
|
|
1515
|
-
});
|
|
1516
|
-
});
|
|
1517
|
-
});
|
|
1518
|
-
describe('#requestScreenShareFloor', () => {
|
|
1519
|
-
it('should have #requestScreenShareFloor', () => {
|
|
1520
|
-
assert.exists(meeting.requestScreenShareFloor);
|
|
1521
|
-
});
|
|
1522
1417
|
beforeEach(() => {
|
|
1523
|
-
|
|
1524
|
-
meeting.locusInfo.self = {url: url1};
|
|
1525
|
-
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
1526
|
-
});
|
|
1527
|
-
it('should send the share', async () => {
|
|
1528
|
-
const share = meeting.requestScreenShareFloor();
|
|
1418
|
+
clock = sinon.useFakeTimers();
|
|
1529
1419
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1420
|
+
meeting.deviceUrl = 'deviceUrl';
|
|
1421
|
+
meeting.config.deviceType = 'web';
|
|
1422
|
+
meeting.isMultistream = isMultistream;
|
|
1423
|
+
meeting.meetingState = 'ACTIVE';
|
|
1424
|
+
meeting.mediaId = 'fake media id';
|
|
1425
|
+
meeting.selfUrl = 'selfUrl';
|
|
1426
|
+
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
1427
|
+
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
1428
|
+
meeting.setMercuryListener = sinon.stub();
|
|
1429
|
+
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
1430
|
+
meeting.webex.meetings.reachability = {
|
|
1431
|
+
isAnyClusterReachable: sinon.stub().resolves(true),
|
|
1432
|
+
};
|
|
1433
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
1434
|
+
.stub()
|
|
1435
|
+
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
|
|
1436
|
+
|
|
1437
|
+
StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
|
|
1438
|
+
|
|
1439
|
+
Metrics.postEvent = sinon.stub();
|
|
1440
|
+
|
|
1441
|
+
// setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
|
|
1442
|
+
expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
|
|
1443
|
+
expectedMediaConnectionConfig = {
|
|
1444
|
+
iceServers: [ { urls: undefined, username: '', credential: '' } ],
|
|
1445
|
+
skipInactiveTransceivers: false,
|
|
1446
|
+
requireH264: true,
|
|
1447
|
+
sdpMunging: {
|
|
1448
|
+
convertPort9to0: false,
|
|
1449
|
+
addContentSlides: true,
|
|
1450
|
+
bandwidthLimits: {
|
|
1451
|
+
audio: StaticConfig.meetings.bandwidth.audio,
|
|
1452
|
+
video: StaticConfig.meetings.bandwidth.video,
|
|
1453
|
+
},
|
|
1454
|
+
startBitrate: StaticConfig.meetings.bandwidth.startBitrate,
|
|
1455
|
+
periodicKeyframes: 20,
|
|
1456
|
+
disableExtmap: !meeting.config.enableExtmap,
|
|
1457
|
+
disableRtx: !meeting.config.enableRtx,
|
|
1458
|
+
},
|
|
1459
|
+
};
|
|
1535
1460
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1461
|
+
// setup stubs
|
|
1462
|
+
fakeMicrophoneTrack = {
|
|
1463
|
+
id: 'fake mic',
|
|
1464
|
+
on: sinon.stub(),
|
|
1465
|
+
off: sinon.stub(),
|
|
1466
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1467
|
+
setMuted: sinon.stub(),
|
|
1468
|
+
setPublished: sinon.stub(),
|
|
1469
|
+
muted: false,
|
|
1470
|
+
underlyingTrack: webrtcAudioTrack
|
|
1471
|
+
};
|
|
1538
1472
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
.stub(
|
|
1543
|
-
.
|
|
1544
|
-
|
|
1473
|
+
fakeRoapMediaConnection = {
|
|
1474
|
+
id: 'roap media connection',
|
|
1475
|
+
close: sinon.stub(),
|
|
1476
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1477
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1478
|
+
update: sinon.stub().resolves({}),
|
|
1479
|
+
on: sinon.stub(),
|
|
1480
|
+
};
|
|
1545
1481
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1482
|
+
fakeMultistreamRoapMediaConnection = {
|
|
1483
|
+
id: 'multistream roap media connection',
|
|
1484
|
+
close: sinon.stub(),
|
|
1485
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1486
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1487
|
+
publishTrack: sinon.stub().resolves({}),
|
|
1488
|
+
unpublishTrack: sinon.stub().resolves({}),
|
|
1489
|
+
on: sinon.stub(),
|
|
1490
|
+
requestMedia: sinon.stub(),
|
|
1491
|
+
createReceiveSlot: sinon.stub().resolves({on: sinon.stub()}),
|
|
1492
|
+
enableMultistreamAudio: sinon.stub(),
|
|
1493
|
+
};
|
|
1494
|
+
|
|
1495
|
+
roapMediaConnectionConstructorStub = sinon
|
|
1496
|
+
.stub(internalMediaModule, 'RoapMediaConnection')
|
|
1497
|
+
.returns(fakeRoapMediaConnection);
|
|
1498
|
+
|
|
1499
|
+
multistreamRoapMediaConnectionConstructorStub = sinon
|
|
1500
|
+
.stub(internalMediaModule, 'MultistreamRoapMediaConnection')
|
|
1501
|
+
.returns(fakeMultistreamRoapMediaConnection);
|
|
1502
|
+
|
|
1503
|
+
locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({body: {locus: { fullState: {}}}});
|
|
1548
1504
|
});
|
|
1549
1505
|
|
|
1550
|
-
|
|
1551
|
-
|
|
1506
|
+
afterEach(() => {
|
|
1507
|
+
clock.restore();
|
|
1552
1508
|
});
|
|
1553
1509
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1510
|
+
// helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
|
|
1511
|
+
const stableState = async () => {
|
|
1512
|
+
await testUtils.flushPromises();
|
|
1513
|
+
clock.tick(1); // needed because LocusMediaRequest uses Lodash.defer()
|
|
1514
|
+
}
|
|
1515
|
+
|
|
1516
|
+
const resetHistory = () => {
|
|
1517
|
+
locusMediaRequestStub.resetHistory();
|
|
1518
|
+
fakeRoapMediaConnection.update.resetHistory();
|
|
1519
|
+
fakeMultistreamRoapMediaConnection.publishTrack.resetHistory();
|
|
1520
|
+
fakeMultistreamRoapMediaConnection.unpublishTrack.resetHistory();
|
|
1521
|
+
};
|
|
1559
1522
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
meeting.updateShare.restore();
|
|
1563
|
-
});
|
|
1523
|
+
const getRoapListener = () => {
|
|
1524
|
+
const roapMediaConnectionToCheck = isMultistream ? fakeMultistreamRoapMediaConnection : fakeRoapMediaConnection;
|
|
1564
1525
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1526
|
+
for(let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx+= 1) {
|
|
1527
|
+
if (roapMediaConnectionToCheck.on.getCall(idx).args[0] === Event.ROAP_MESSAGE_TO_SEND) {
|
|
1528
|
+
return roapMediaConnectionToCheck.on.getCall(idx).args[1];
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
assert.fail('listener for "roap:messageToSend" (Event.ROAP_MESSAGE_TO_SEND) was not registered')
|
|
1532
|
+
}
|
|
1567
1533
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1534
|
+
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
1535
|
+
const simulateRoapOffer = async () => {
|
|
1536
|
+
const roapListener = getRoapListener();
|
|
1570
1537
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1538
|
+
await roapListener({roapMessage: roapOfferMessage});
|
|
1539
|
+
await stableState();
|
|
1540
|
+
}
|
|
1573
1541
|
|
|
1574
|
-
|
|
1575
|
-
}
|
|
1542
|
+
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
1543
|
+
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
1576
1544
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1545
|
+
assert.calledWith(locusMediaRequestStub,
|
|
1546
|
+
{
|
|
1547
|
+
method: 'PUT',
|
|
1548
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1549
|
+
body: {
|
|
1550
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1551
|
+
correlationId: meeting.correlationId,
|
|
1552
|
+
localMedias: [
|
|
1553
|
+
{
|
|
1554
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}"}}`,
|
|
1555
|
+
mediaId: 'fake media id'
|
|
1556
|
+
}
|
|
1557
|
+
],
|
|
1558
|
+
clientMediaPreferences: {
|
|
1559
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1560
|
+
joinCookie: undefined
|
|
1561
|
+
}
|
|
1562
|
+
},
|
|
1563
|
+
});
|
|
1564
|
+
};
|
|
1579
1565
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1566
|
+
const checkLocalMuteSentToLocus = ({audioMuted, videoMuted}) => {
|
|
1567
|
+
assert.calledWith(locusMediaRequestStub, {
|
|
1568
|
+
method: 'PUT',
|
|
1569
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1570
|
+
body: {
|
|
1571
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1572
|
+
correlationId: meeting.correlationId,
|
|
1573
|
+
localMedias: [
|
|
1574
|
+
{
|
|
1575
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted}}`,
|
|
1576
|
+
mediaId: 'fake media id'
|
|
1577
|
+
}
|
|
1578
|
+
],
|
|
1579
|
+
clientMediaPreferences: {
|
|
1580
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1581
|
+
},
|
|
1582
|
+
respOnlySdp: true,
|
|
1583
|
+
usingResource: null,
|
|
1584
|
+
},
|
|
1584
1585
|
});
|
|
1586
|
+
};
|
|
1587
|
+
|
|
1588
|
+
const checkMediaConnectionCreated = ({mediaConnectionConfig, localTracks, direction, remoteQualityLevel, expectedDebugId}) => {
|
|
1589
|
+
if (isMultistream) {
|
|
1590
|
+
const {iceServers} = mediaConnectionConfig;
|
|
1591
|
+
|
|
1592
|
+
assert.calledOnceWithExactly(multistreamRoapMediaConnectionConstructorStub, {
|
|
1593
|
+
iceServers,
|
|
1594
|
+
enableMainAudio: direction.audio !== 'inactive',
|
|
1595
|
+
enableMainVideo: true
|
|
1596
|
+
}, expectedDebugId);
|
|
1597
|
+
|
|
1598
|
+
Object.values(localTracks).forEach((track) => {
|
|
1599
|
+
if (track) {
|
|
1600
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, track);
|
|
1601
|
+
}
|
|
1602
|
+
})
|
|
1603
|
+
} else {
|
|
1604
|
+
assert.calledOnceWithExactly(roapMediaConnectionConstructorStub, mediaConnectionConfig,
|
|
1605
|
+
{
|
|
1606
|
+
localTracks: {
|
|
1607
|
+
audio: localTracks.audio?.underlyingTrack,
|
|
1608
|
+
video: localTracks.video?.underlyingTrack,
|
|
1609
|
+
screenShareVideo: localTracks.screenShareVideo?.underlyingTrack,
|
|
1610
|
+
},
|
|
1611
|
+
direction,
|
|
1612
|
+
remoteQualityLevel,
|
|
1613
|
+
},
|
|
1614
|
+
expectedDebugId);
|
|
1615
|
+
}
|
|
1616
|
+
}
|
|
1617
|
+
|
|
1618
|
+
it('addMedia() works correctly when media is enabled without tracks to publish', async () => {
|
|
1619
|
+
await meeting.addMedia();
|
|
1620
|
+
await simulateRoapOffer();
|
|
1621
|
+
|
|
1622
|
+
// check RoapMediaConnection was created correctly
|
|
1623
|
+
checkMediaConnectionCreated({
|
|
1624
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1625
|
+
localTracks: {
|
|
1626
|
+
audio: undefined,
|
|
1627
|
+
video: undefined,
|
|
1628
|
+
screenShareVideo: undefined,
|
|
1629
|
+
},
|
|
1630
|
+
direction: {
|
|
1631
|
+
audio: 'sendrecv',
|
|
1632
|
+
video: 'sendrecv',
|
|
1633
|
+
screenShareVideo: 'recvonly',
|
|
1634
|
+
},
|
|
1635
|
+
remoteQualityLevel: 'HIGH',
|
|
1636
|
+
expectedDebugId,
|
|
1585
1637
|
});
|
|
1638
|
+
|
|
1639
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1640
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1641
|
+
|
|
1642
|
+
// and that it was the only /media request that was sent
|
|
1643
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1586
1644
|
});
|
|
1587
1645
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1646
|
+
it('addMedia() works correctly when media is enabled with tracks to publish', async () => {
|
|
1647
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1648
|
+
await simulateRoapOffer();
|
|
1590
1649
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1650
|
+
// check RoapMediaConnection was created correctly
|
|
1651
|
+
checkMediaConnectionCreated({
|
|
1652
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1653
|
+
localTracks: {
|
|
1654
|
+
audio: fakeMicrophoneTrack,
|
|
1655
|
+
video: undefined,
|
|
1656
|
+
screenShareVideo: undefined,
|
|
1657
|
+
},
|
|
1658
|
+
direction: {
|
|
1659
|
+
audio: 'sendrecv',
|
|
1660
|
+
video: 'sendrecv',
|
|
1661
|
+
screenShareVideo: 'recvonly',
|
|
1662
|
+
},
|
|
1663
|
+
remoteQualityLevel: 'HIGH',
|
|
1664
|
+
expectedDebugId
|
|
1593
1665
|
});
|
|
1594
1666
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
sandbox = null;
|
|
1598
|
-
});
|
|
1667
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1668
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1599
1669
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
const stream = 'stream';
|
|
1670
|
+
// and no other local mute requests were sent to Locus
|
|
1671
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1672
|
+
});
|
|
1604
1673
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
sandbox.stub(meeting, 'canUpdateMedia').returns(true);
|
|
1608
|
-
sandbox.stub(meeting, 'setLocalShareTrack');
|
|
1674
|
+
it('addMedia() works correctly when media is enabled with tracks to publish and track is muted', async () => {
|
|
1675
|
+
fakeMicrophoneTrack.muted = true;
|
|
1609
1676
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
receiveShare,
|
|
1613
|
-
stream,
|
|
1614
|
-
skipSignalingCheck: true,
|
|
1615
|
-
});
|
|
1677
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1678
|
+
await simulateRoapOffer();
|
|
1616
1679
|
|
|
1617
|
-
|
|
1680
|
+
// check RoapMediaConnection was created correctly
|
|
1681
|
+
checkMediaConnectionCreated({
|
|
1682
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1683
|
+
localTracks: {
|
|
1684
|
+
audio: fakeMicrophoneTrack,
|
|
1685
|
+
video: undefined,
|
|
1686
|
+
screenShareVideo: undefined,
|
|
1687
|
+
},
|
|
1688
|
+
direction: {
|
|
1689
|
+
audio: 'sendrecv',
|
|
1690
|
+
video: 'sendrecv',
|
|
1691
|
+
screenShareVideo: 'recvonly',
|
|
1692
|
+
},
|
|
1693
|
+
remoteQualityLevel: 'HIGH',
|
|
1694
|
+
expectedDebugId,
|
|
1618
1695
|
});
|
|
1619
1696
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
const fakeTrack = {
|
|
1623
|
-
getSettings: sinon.stub().returns({}),
|
|
1624
|
-
onended: sinon.stub(),
|
|
1625
|
-
};
|
|
1697
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1698
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1626
1699
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
sandbox.stub(meeting, 'stopShare').resolves(true);
|
|
1631
|
-
meeting.setLocalShareTrack(fakeTrack);
|
|
1700
|
+
// and no other local mute requests were sent to Locus
|
|
1701
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1702
|
+
});
|
|
1632
1703
|
|
|
1633
|
-
|
|
1704
|
+
it('addMedia() works correctly when media is disabled with tracks to publish', async () => {
|
|
1705
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}, audioEnabled: false});
|
|
1706
|
+
await simulateRoapOffer();
|
|
1634
1707
|
|
|
1635
|
-
|
|
1708
|
+
// check RoapMediaConnection was created correctly
|
|
1709
|
+
checkMediaConnectionCreated({
|
|
1710
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1711
|
+
localTracks: {
|
|
1712
|
+
audio: fakeMicrophoneTrack,
|
|
1713
|
+
video: undefined,
|
|
1714
|
+
screenShareVideo: undefined,
|
|
1715
|
+
},
|
|
1716
|
+
direction: {
|
|
1717
|
+
audio: 'inactive',
|
|
1718
|
+
video: 'sendrecv',
|
|
1719
|
+
screenShareVideo: 'recvonly',
|
|
1720
|
+
},
|
|
1721
|
+
remoteQualityLevel: 'HIGH',
|
|
1722
|
+
expectedDebugId
|
|
1636
1723
|
});
|
|
1637
1724
|
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
abc: 123,
|
|
1641
|
-
receiveShare: false,
|
|
1642
|
-
sendShare: false,
|
|
1643
|
-
};
|
|
1725
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1726
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1644
1727
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1728
|
+
// and no other local mute requests were sent to Locus
|
|
1729
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1730
|
+
});
|
|
1647
1731
|
|
|
1648
|
-
|
|
1732
|
+
it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
|
|
1733
|
+
await meeting.addMedia({audioEnabled: false});
|
|
1734
|
+
await simulateRoapOffer();
|
|
1649
1735
|
|
|
1650
|
-
|
|
1736
|
+
// check RoapMediaConnection was created correctly
|
|
1737
|
+
checkMediaConnectionCreated({
|
|
1738
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1739
|
+
localTracks: {
|
|
1740
|
+
audio: undefined,
|
|
1741
|
+
video: undefined,
|
|
1742
|
+
screenShareVideo: undefined,
|
|
1743
|
+
},
|
|
1744
|
+
direction: {
|
|
1745
|
+
audio: 'inactive',
|
|
1746
|
+
video: 'sendrecv',
|
|
1747
|
+
screenShareVideo: 'recvonly',
|
|
1748
|
+
},
|
|
1749
|
+
remoteQualityLevel: 'HIGH',
|
|
1750
|
+
expectedDebugId
|
|
1651
1751
|
});
|
|
1752
|
+
|
|
1753
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1754
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1755
|
+
|
|
1756
|
+
// and no other local mute requests were sent to Locus
|
|
1757
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1652
1758
|
});
|
|
1653
1759
|
|
|
1654
|
-
describe('
|
|
1655
|
-
|
|
1760
|
+
describe('publishTracks()/unpublishTracks() calls', () => {
|
|
1761
|
+
[
|
|
1762
|
+
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
1763
|
+
{mediaEnabled: false, expected: {direction: 'inactive', localMuteSentValue: undefined}}
|
|
1764
|
+
]
|
|
1765
|
+
.forEach(({mediaEnabled, expected}) => {
|
|
1766
|
+
it(`first publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1767
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1768
|
+
await simulateRoapOffer();
|
|
1656
1769
|
|
|
1657
|
-
|
|
1658
|
-
sandbox = sinon.createSandbox();
|
|
1659
|
-
});
|
|
1770
|
+
resetHistory();
|
|
1660
1771
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1772
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1773
|
+
await stableState();
|
|
1774
|
+
|
|
1775
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1776
|
+
// check local mute was sent and it was the only /media request
|
|
1777
|
+
checkLocalMuteSentToLocus({audioMuted: expected.localMuteSentValue, videoMuted: true});
|
|
1778
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1779
|
+
} else {
|
|
1780
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1781
|
+
}
|
|
1782
|
+
if (isMultistream) {
|
|
1783
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack);
|
|
1784
|
+
} else {
|
|
1785
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1786
|
+
localTracks: { audio: webrtcAudioTrack, video: null, screenShareVideo: null },
|
|
1787
|
+
direction: {
|
|
1788
|
+
audio: expected.direction,
|
|
1789
|
+
video: 'sendrecv',
|
|
1790
|
+
screenShareVideo: 'recvonly',
|
|
1791
|
+
},
|
|
1792
|
+
remoteQualityLevel: 'HIGH'
|
|
1793
|
+
});
|
|
1794
|
+
}
|
|
1795
|
+
});
|
|
1796
|
+
|
|
1797
|
+
it(`second publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1798
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1799
|
+
await simulateRoapOffer();
|
|
1800
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1801
|
+
await stableState();
|
|
1802
|
+
|
|
1803
|
+
resetHistory();
|
|
1804
|
+
|
|
1805
|
+
const webrtcAudioTrack2 = {id: 'underlying audio track 2'};
|
|
1806
|
+
const fakeMicrophoneTrack2 = {
|
|
1807
|
+
id: 'fake mic 2',
|
|
1808
|
+
on: sinon.stub(),
|
|
1809
|
+
off: sinon.stub(),
|
|
1810
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1811
|
+
setMuted: sinon.stub(),
|
|
1812
|
+
setPublished: sinon.stub(),
|
|
1813
|
+
muted: false,
|
|
1814
|
+
underlyingTrack: webrtcAudioTrack2
|
|
1815
|
+
};
|
|
1816
|
+
|
|
1817
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack2});
|
|
1818
|
+
await stableState();
|
|
1819
|
+
|
|
1820
|
+
// only the roap media connection should be updated
|
|
1821
|
+
if (isMultistream) {
|
|
1822
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack2);
|
|
1823
|
+
} else {
|
|
1824
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1825
|
+
localTracks: { audio: webrtcAudioTrack2, video: null, screenShareVideo: null },
|
|
1826
|
+
direction: {
|
|
1827
|
+
audio: expected.direction,
|
|
1828
|
+
video: 'sendrecv',
|
|
1829
|
+
screenShareVideo: 'recvonly',
|
|
1830
|
+
},
|
|
1831
|
+
remoteQualityLevel: 'HIGH'
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
// and no other roap messages or local mute requests were sent
|
|
1836
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1837
|
+
});
|
|
1838
|
+
|
|
1839
|
+
it(`unpublishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1840
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1841
|
+
await simulateRoapOffer();
|
|
1842
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1843
|
+
await stableState();
|
|
1844
|
+
|
|
1845
|
+
resetHistory();
|
|
1846
|
+
|
|
1847
|
+
await meeting.unpublishTracks([fakeMicrophoneTrack]);
|
|
1848
|
+
await stableState();
|
|
1849
|
+
|
|
1850
|
+
// the roap media connection should be updated
|
|
1851
|
+
if (isMultistream) {
|
|
1852
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.unpublishTrack, fakeMicrophoneTrack);
|
|
1853
|
+
} else {
|
|
1854
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1855
|
+
localTracks: { audio: null, video: null, screenShareVideo: null },
|
|
1856
|
+
direction: {
|
|
1857
|
+
audio: expected.direction,
|
|
1858
|
+
video: 'sendrecv',
|
|
1859
|
+
screenShareVideo: 'recvonly',
|
|
1860
|
+
},
|
|
1861
|
+
remoteQualityLevel: 'HIGH'
|
|
1862
|
+
});
|
|
1863
|
+
}
|
|
1864
|
+
|
|
1865
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1866
|
+
// and local mute sent to Locus
|
|
1867
|
+
checkLocalMuteSentToLocus({audioMuted: !expected.localMuteSentValue /* negation, because we're un-publishing */, videoMuted: true});
|
|
1868
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1869
|
+
} else {
|
|
1870
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1871
|
+
}
|
|
1872
|
+
});
|
|
1873
|
+
});
|
|
1874
|
+
});
|
|
1875
|
+
|
|
1876
|
+
describe('updateMedia()', () => {
|
|
1877
|
+
|
|
1878
|
+
const addMedia = async (enableMedia, track) => {
|
|
1879
|
+
await meeting.addMedia({audioEnabled: enableMedia, localTracks: {microphone: track}});
|
|
1880
|
+
await simulateRoapOffer();
|
|
1881
|
+
|
|
1882
|
+
resetHistory();
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1885
|
+
const checkAudioEnabled = (expectedTrack, expectedDirection) => {
|
|
1886
|
+
if (isMultistream) {
|
|
1887
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.enableMultistreamAudio, expectedDirection !== 'inactive');
|
|
1888
|
+
} else {
|
|
1889
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1890
|
+
localTracks: { audio: expectedTrack, video: null, screenShareVideo: null },
|
|
1891
|
+
direction: {
|
|
1892
|
+
audio: expectedDirection,
|
|
1893
|
+
video: 'sendrecv',
|
|
1894
|
+
screenShareVideo: 'recvonly',
|
|
1895
|
+
},
|
|
1896
|
+
remoteQualityLevel: 'HIGH'
|
|
1897
|
+
});
|
|
1898
|
+
}
|
|
1899
|
+
}
|
|
1900
|
+
|
|
1901
|
+
it('updateMedia() disables media when nothing is published', async () => {
|
|
1902
|
+
await addMedia(true);
|
|
1903
|
+
|
|
1904
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1905
|
+
|
|
1906
|
+
// the roap media connection should be updated
|
|
1907
|
+
checkAudioEnabled(null, 'inactive');
|
|
1908
|
+
|
|
1909
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1910
|
+
await simulateRoapOffer();
|
|
1911
|
+
|
|
1912
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1913
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1914
|
+
|
|
1915
|
+
// and no other local mute requests were sent to Locus
|
|
1916
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1664
1917
|
});
|
|
1665
1918
|
|
|
1666
|
-
it('
|
|
1667
|
-
|
|
1668
|
-
const {EVENT_TYPES} = CONSTANTS;
|
|
1919
|
+
it('updateMedia() enables media when nothing is published', async () => {
|
|
1920
|
+
await addMedia(false);
|
|
1669
1921
|
|
|
1670
|
-
|
|
1922
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1671
1923
|
|
|
1672
|
-
|
|
1924
|
+
// the roap media connection should be updated
|
|
1925
|
+
checkAudioEnabled(null, 'sendrecv');
|
|
1673
1926
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
{
|
|
1683
|
-
stream,
|
|
1684
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
1685
|
-
}
|
|
1686
|
-
);
|
|
1927
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1928
|
+
await simulateRoapOffer();
|
|
1929
|
+
|
|
1930
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1931
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1932
|
+
|
|
1933
|
+
// and no other local mute requests were sent to Locus
|
|
1934
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1687
1935
|
});
|
|
1688
|
-
});
|
|
1689
|
-
});
|
|
1690
1936
|
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
const config = DefaultSDKConfig.meetings;
|
|
1694
|
-
const {resolution} = config;
|
|
1695
|
-
const shareOptions = {
|
|
1696
|
-
sendShare: true,
|
|
1697
|
-
sendAudio: false,
|
|
1698
|
-
};
|
|
1699
|
-
const fireFoxOptions = {
|
|
1700
|
-
audio: false,
|
|
1701
|
-
video: {
|
|
1702
|
-
audio: shareOptions.sendAudio,
|
|
1703
|
-
video: shareOptions.sendShare,
|
|
1704
|
-
},
|
|
1705
|
-
};
|
|
1937
|
+
it('updateMedia() disables media when track is published', async () => {
|
|
1938
|
+
await addMedia(true, fakeMicrophoneTrack);
|
|
1706
1939
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
{
|
|
1710
|
-
applyConstraints: () => {},
|
|
1711
|
-
},
|
|
1712
|
-
],
|
|
1713
|
-
};
|
|
1940
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1941
|
+
await stableState();
|
|
1714
1942
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
aspectRatio: config.aspectRatio,
|
|
1718
|
-
frameRate: config.screenFrameRate,
|
|
1719
|
-
width: null,
|
|
1720
|
-
height: null,
|
|
1721
|
-
};
|
|
1943
|
+
// the roap media connection should be updated
|
|
1944
|
+
checkAudioEnabled(webrtcAudioTrack, 'inactive');
|
|
1722
1945
|
|
|
1723
|
-
|
|
1724
|
-
const key = getBrowserName().toLowerCase();
|
|
1725
|
-
const defaultKey = 'default';
|
|
1946
|
+
checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
|
|
1726
1947
|
|
|
1727
|
-
|
|
1728
|
-
};
|
|
1948
|
+
locusMediaRequestStub.resetHistory();
|
|
1729
1949
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1950
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1951
|
+
await simulateRoapOffer();
|
|
1732
1952
|
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
|
|
1736
|
-
|
|
1737
|
-
|
|
1738
|
-
};
|
|
1739
|
-
}
|
|
1740
|
-
_getDisplayMedia = global.navigator.mediaDevices.getDisplayMedia;
|
|
1741
|
-
Object.defineProperty(global.navigator.mediaDevices, 'getDisplayMedia', {
|
|
1742
|
-
value: sinon.stub().returns(Promise.resolve(MediaStream)),
|
|
1743
|
-
writable: true,
|
|
1953
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1954
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1955
|
+
|
|
1956
|
+
// and no other local mute requests were sent to Locus
|
|
1957
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1744
1958
|
});
|
|
1745
|
-
});
|
|
1746
1959
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1960
|
+
it('updateMedia() enables media when track is published', async () => {
|
|
1961
|
+
await addMedia(false, fakeMicrophoneTrack);
|
|
1962
|
+
|
|
1963
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1964
|
+
await stableState();
|
|
1965
|
+
|
|
1966
|
+
// the roap media connection should be updated
|
|
1967
|
+
checkAudioEnabled(webrtcAudioTrack, 'sendrecv');
|
|
1968
|
+
|
|
1969
|
+
checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
|
|
1970
|
+
|
|
1971
|
+
locusMediaRequestStub.resetHistory();
|
|
1972
|
+
|
|
1973
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1974
|
+
await simulateRoapOffer();
|
|
1975
|
+
|
|
1976
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1977
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1978
|
+
|
|
1979
|
+
// and no other local mute requests were sent to Locus
|
|
1980
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1752
1981
|
});
|
|
1753
1982
|
});
|
|
1754
1983
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
maxHeight: SHARE_HEIGHT,
|
|
1763
|
-
idealWidth: SHARE_WIDTH,
|
|
1764
|
-
idealHeight: SHARE_HEIGHT,
|
|
1765
|
-
};
|
|
1984
|
+
[
|
|
1985
|
+
{mute: true, title: 'muting a track before confluence is created'},
|
|
1986
|
+
{mute: false, title: 'unmuting a track before confluence is created'}
|
|
1987
|
+
].forEach(({mute, title}) =>
|
|
1988
|
+
it(title, async () => {
|
|
1989
|
+
// initialize the microphone mute state to opposite of what we do in the test
|
|
1990
|
+
fakeMicrophoneTrack.muted = !mute;
|
|
1766
1991
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
getDisplayMedia(
|
|
1770
|
-
{
|
|
1771
|
-
...shareOptions,
|
|
1772
|
-
sharePreferences: {shareConstraints},
|
|
1773
|
-
},
|
|
1774
|
-
config
|
|
1775
|
-
);
|
|
1992
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1993
|
+
await stableState();
|
|
1776
1994
|
|
|
1777
|
-
|
|
1778
|
-
assert.calledWith(
|
|
1779
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
1780
|
-
browserConditionalValue({
|
|
1781
|
-
default: {
|
|
1782
|
-
video: {...shareConstraints},
|
|
1783
|
-
},
|
|
1784
|
-
// Firefox is being handled differently
|
|
1785
|
-
firefox: fireFoxOptions,
|
|
1786
|
-
})
|
|
1787
|
-
);
|
|
1788
|
-
});
|
|
1995
|
+
resetHistory();
|
|
1789
1996
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
{
|
|
1795
|
-
...shareOptions,
|
|
1796
|
-
sharePreferences: {
|
|
1797
|
-
highFrameRate: true,
|
|
1798
|
-
},
|
|
1799
|
-
},
|
|
1800
|
-
config
|
|
1801
|
-
);
|
|
1997
|
+
assert.equal(fakeMicrophoneTrack.on.getCall(0).args[0], LocalTrackEvents.Muted);
|
|
1998
|
+
const mutedListener = fakeMicrophoneTrack.on.getCall(0).args[1];
|
|
1999
|
+
// simulate track being muted
|
|
2000
|
+
mutedListener({trackState: {muted: mute}});
|
|
1802
2001
|
|
|
1803
|
-
|
|
1804
|
-
assert.calledWith(
|
|
1805
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
1806
|
-
browserConditionalValue({
|
|
1807
|
-
default: {
|
|
1808
|
-
video: {
|
|
1809
|
-
...MediaConstraint,
|
|
1810
|
-
frameRate: config.videoShareFrameRate,
|
|
1811
|
-
width: resolution.idealWidth,
|
|
1812
|
-
height: resolution.idealHeight,
|
|
1813
|
-
maxWidth: resolution.maxWidth,
|
|
1814
|
-
maxHeight: resolution.maxHeight,
|
|
1815
|
-
idealWidth: resolution.idealWidth,
|
|
1816
|
-
idealHeight: resolution.idealHeight,
|
|
1817
|
-
},
|
|
1818
|
-
},
|
|
1819
|
-
firefox: fireFoxOptions,
|
|
1820
|
-
})
|
|
1821
|
-
);
|
|
1822
|
-
});
|
|
2002
|
+
await stableState();
|
|
1823
2003
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
const {screenResolution} = config;
|
|
2004
|
+
// nothing should happen
|
|
2005
|
+
assert.notCalled(locusMediaRequestStub);
|
|
2006
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
1828
2007
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
1832
|
-
browserConditionalValue({
|
|
1833
|
-
default: {
|
|
1834
|
-
video: {
|
|
1835
|
-
...MediaConstraint,
|
|
1836
|
-
width: screenResolution.idealWidth,
|
|
1837
|
-
height: screenResolution.idealHeight,
|
|
1838
|
-
},
|
|
1839
|
-
},
|
|
1840
|
-
firefox: fireFoxOptions,
|
|
1841
|
-
})
|
|
1842
|
-
);
|
|
1843
|
-
});
|
|
2008
|
+
// now simulate roap offer
|
|
2009
|
+
await simulateRoapOffer();
|
|
1844
2010
|
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
it('will use SDK config screenResolution if set, with shareConstraints and highFrameRate being undefined', () => {
|
|
1848
|
-
const SHARE_WIDTH = 800;
|
|
1849
|
-
const SHARE_HEIGHT = 600;
|
|
1850
|
-
const customConfig = {
|
|
1851
|
-
screenResolution: {
|
|
1852
|
-
maxWidth: SHARE_WIDTH,
|
|
1853
|
-
maxHeight: SHARE_HEIGHT,
|
|
1854
|
-
idealWidth: SHARE_WIDTH,
|
|
1855
|
-
idealHeight: SHARE_HEIGHT,
|
|
1856
|
-
},
|
|
1857
|
-
};
|
|
2011
|
+
// it should be sent with the right mute status
|
|
2012
|
+
checkSdpOfferSent({audioMuted: mute, videoMuted: true});
|
|
1858
2013
|
|
|
1859
|
-
|
|
2014
|
+
// nothing else should happen
|
|
2015
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
2016
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2017
|
+
})
|
|
2018
|
+
);
|
|
2019
|
+
}));
|
|
1860
2020
|
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
browserConditionalValue({
|
|
1865
|
-
default: {
|
|
1866
|
-
video: {
|
|
1867
|
-
...MediaConstraint,
|
|
1868
|
-
width: SHARE_WIDTH,
|
|
1869
|
-
height: SHARE_HEIGHT,
|
|
1870
|
-
maxWidth: SHARE_WIDTH,
|
|
1871
|
-
maxHeight: SHARE_HEIGHT,
|
|
1872
|
-
idealWidth: SHARE_WIDTH,
|
|
1873
|
-
idealHeight: SHARE_HEIGHT,
|
|
1874
|
-
},
|
|
1875
|
-
},
|
|
1876
|
-
firefox: fireFoxOptions,
|
|
1877
|
-
})
|
|
1878
|
-
);
|
|
2021
|
+
describe('#acknowledge', () => {
|
|
2022
|
+
it('should have #acknowledge', () => {
|
|
2023
|
+
assert.exists(meeting.acknowledge);
|
|
1879
2024
|
});
|
|
2025
|
+
beforeEach(() => {
|
|
2026
|
+
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
2027
|
+
});
|
|
2028
|
+
it('should acknowledge incoming and return a promise', async () => {
|
|
2029
|
+
const ack = meeting.acknowledge('INCOMING', false);
|
|
1880
2030
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
screenResolution: {
|
|
1888
|
-
maxWidth: SHARE_WIDTH,
|
|
1889
|
-
maxHeight: SHARE_HEIGHT,
|
|
1890
|
-
idealWidth: SHARE_WIDTH,
|
|
1891
|
-
idealHeight: SHARE_HEIGHT,
|
|
1892
|
-
},
|
|
1893
|
-
};
|
|
1894
|
-
|
|
1895
|
-
getDisplayMedia(shareOptions, customConfig);
|
|
2031
|
+
assert.exists(ack.then);
|
|
2032
|
+
await ack;
|
|
2033
|
+
assert.calledOnce(meeting.meetingRequest.acknowledgeMeeting);
|
|
2034
|
+
});
|
|
2035
|
+
it('should acknowledge a non incoming and return a promise', async () => {
|
|
2036
|
+
const ack = meeting.acknowledge(test1, false);
|
|
1896
2037
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
browserConditionalValue({
|
|
1901
|
-
default: {
|
|
1902
|
-
video: {
|
|
1903
|
-
...MediaConstraint,
|
|
1904
|
-
frameRate: customConfig.screenFrameRate,
|
|
1905
|
-
width: SHARE_WIDTH,
|
|
1906
|
-
height: SHARE_HEIGHT,
|
|
1907
|
-
maxWidth: SHARE_WIDTH,
|
|
1908
|
-
maxHeight: SHARE_HEIGHT,
|
|
1909
|
-
idealWidth: SHARE_WIDTH,
|
|
1910
|
-
idealHeight: SHARE_HEIGHT,
|
|
1911
|
-
},
|
|
1912
|
-
},
|
|
1913
|
-
firefox: fireFoxOptions,
|
|
1914
|
-
})
|
|
1915
|
-
);
|
|
2038
|
+
assert.exists(ack.then);
|
|
2039
|
+
await ack;
|
|
2040
|
+
assert.notCalled(meeting.meetingRequest.acknowledgeMeeting);
|
|
1916
2041
|
});
|
|
1917
2042
|
});
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
assert.exists(meeting.stopShare);
|
|
2043
|
+
describe('#decline', () => {
|
|
2044
|
+
it('should have #decline', () => {
|
|
2045
|
+
assert.exists(meeting.decline);
|
|
1922
2046
|
});
|
|
1923
2047
|
beforeEach(() => {
|
|
1924
|
-
meeting.
|
|
1925
|
-
meeting.
|
|
2048
|
+
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
2049
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
1926
2050
|
});
|
|
1927
|
-
it('should
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
assert.exists(share.then);
|
|
1931
|
-
await share;
|
|
1932
|
-
assert.calledOnce(meeting.updateShare);
|
|
2051
|
+
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
2052
|
+
await meeting.decline();
|
|
2053
|
+
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
1933
2054
|
});
|
|
1934
2055
|
});
|
|
2056
|
+
describe('#leave', () => {
|
|
2057
|
+
let sandbox;
|
|
1935
2058
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
1939
|
-
|
|
1940
|
-
|
|
2059
|
+
it('should have #leave', () => {
|
|
2060
|
+
assert.exists(meeting.leave);
|
|
2061
|
+
});
|
|
2062
|
+
|
|
2063
|
+
it('should reject if meeting is already inactive', async () => {
|
|
2064
|
+
await meeting.leave().catch((err) => {
|
|
2065
|
+
assert.instanceOf(err, MeetingNotActiveError);
|
|
2066
|
+
});
|
|
2067
|
+
});
|
|
2068
|
+
|
|
2069
|
+
it('should reject if meeting is already left', async () => {
|
|
2070
|
+
meeting.meetingState = 'ACTIVE';
|
|
2071
|
+
await meeting.leave().catch((err) => {
|
|
2072
|
+
assert.instanceOf(err, UserNotJoinedError);
|
|
2073
|
+
});
|
|
2074
|
+
});
|
|
2075
|
+
|
|
2076
|
+
beforeEach(() => {
|
|
2077
|
+
sandbox = sinon.createSandbox();
|
|
2078
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
2079
|
+
meeting.meetingFiniteStateMachine.join();
|
|
2080
|
+
meeting.meetingRequest.leaveMeeting = sinon
|
|
2081
|
+
.stub()
|
|
2082
|
+
.returns(Promise.resolve({body: 'test'}));
|
|
2083
|
+
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
2084
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
2085
|
+
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
2086
|
+
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
2087
|
+
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
2088
|
+
meeting.unsetRemoteTracks = sinon.stub();
|
|
2089
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2090
|
+
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
2091
|
+
meeting.logger.error = sinon.stub().returns(true);
|
|
2092
|
+
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
2093
|
+
|
|
2094
|
+
// A meeting needs to be joined to leave
|
|
2095
|
+
meeting.meetingState = 'ACTIVE';
|
|
2096
|
+
meeting.state = 'JOINED';
|
|
2097
|
+
});
|
|
2098
|
+
afterEach(() => {
|
|
2099
|
+
sandbox.restore();
|
|
2100
|
+
sandbox = null;
|
|
2101
|
+
});
|
|
2102
|
+
it('should leave the meeting and return promise', async () => {
|
|
2103
|
+
const leave = meeting.leave();
|
|
2104
|
+
|
|
2105
|
+
assert.exists(leave.then);
|
|
2106
|
+
await leave;
|
|
2107
|
+
assert.calledOnce(meeting.meetingRequest.leaveMeeting);
|
|
2108
|
+
assert.calledOnce(meeting.cleanupLocalTracks);
|
|
2109
|
+
assert.calledOnce(meeting.closeRemoteTracks);
|
|
2110
|
+
assert.calledOnce(meeting.closePeerConnections);
|
|
2111
|
+
assert.calledOnce(meeting.unsetRemoteTracks);
|
|
2112
|
+
assert.calledOnce(meeting.unsetPeerConnections);
|
|
2113
|
+
});
|
|
2114
|
+
describe('after audio/video is defined', () => {
|
|
2115
|
+
let handleClientRequest;
|
|
1941
2116
|
|
|
1942
|
-
describe('when canUpdateMedia is true', () => {
|
|
1943
2117
|
beforeEach(() => {
|
|
1944
|
-
|
|
2118
|
+
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
2119
|
+
|
|
2120
|
+
meeting.audio = {handleClientRequest};
|
|
2121
|
+
meeting.video = {handleClientRequest};
|
|
1945
2122
|
});
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
|
|
1955
|
-
receiveShare: true,
|
|
1956
|
-
};
|
|
1957
|
-
meeting.mediaProperties.webrtcMediaConnection = {
|
|
1958
|
-
updateSendReceiveOptions: sinon.stub(),
|
|
1959
|
-
};
|
|
1960
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({audioTrack: FAKE_AUDIO_TRACK});
|
|
1961
|
-
});
|
|
1962
|
-
it('calls this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions', () =>
|
|
1963
|
-
meeting
|
|
1964
|
-
.updateAudio({
|
|
1965
|
-
sendAudio: true,
|
|
1966
|
-
receiveAudio: true,
|
|
1967
|
-
stream: {id: 'fake stream'},
|
|
1968
|
-
})
|
|
1969
|
-
.then(() => {
|
|
1970
|
-
assert.calledOnce(
|
|
1971
|
-
meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions
|
|
1972
|
-
);
|
|
1973
|
-
assert.calledWith(
|
|
1974
|
-
meeting.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions,
|
|
1975
|
-
{
|
|
1976
|
-
send: {audio: FAKE_AUDIO_TRACK},
|
|
1977
|
-
receive: {
|
|
1978
|
-
audio: true,
|
|
1979
|
-
video: true,
|
|
1980
|
-
screenShareVideo: true,
|
|
1981
|
-
remoteQualityLevel: 'HIGH',
|
|
1982
|
-
},
|
|
1983
|
-
}
|
|
1984
|
-
);
|
|
1985
|
-
}));
|
|
2123
|
+
|
|
2124
|
+
it('should delete audio and video state machines when leaving the meeting', async () => {
|
|
2125
|
+
const leave = meeting.leave();
|
|
2126
|
+
|
|
2127
|
+
assert.exists(leave.then);
|
|
2128
|
+
await leave;
|
|
2129
|
+
|
|
2130
|
+
assert.isNull(meeting.audio);
|
|
2131
|
+
assert.isNull(meeting.video);
|
|
1986
2132
|
});
|
|
1987
|
-
|
|
1988
|
-
|
|
2133
|
+
});
|
|
2134
|
+
it('should leave the meeting without leaving resource', async () => {
|
|
2135
|
+
const leave = meeting.leave({resourceId: null});
|
|
2136
|
+
|
|
2137
|
+
assert.exists(leave.then);
|
|
2138
|
+
await leave;
|
|
2139
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2140
|
+
locusUrl: meeting.locusUrl,
|
|
2141
|
+
correlationId: meeting.correlationId,
|
|
2142
|
+
selfId: meeting.selfId,
|
|
2143
|
+
resourceId: null,
|
|
2144
|
+
deviceUrl: meeting.deviceUrl,
|
|
1989
2145
|
});
|
|
1990
2146
|
});
|
|
2147
|
+
it('should leave the meeting on the resource', async () => {
|
|
2148
|
+
const leave = meeting.leave();
|
|
2149
|
+
|
|
2150
|
+
assert.exists(leave.then);
|
|
2151
|
+
await leave;
|
|
2152
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2153
|
+
locusUrl: meeting.locusUrl,
|
|
2154
|
+
correlationId: meeting.correlationId,
|
|
2155
|
+
selfId: meeting.selfId,
|
|
2156
|
+
resourceId: meeting.resourceId,
|
|
2157
|
+
deviceUrl: meeting.deviceUrl
|
|
2158
|
+
});
|
|
2159
|
+
});
|
|
2160
|
+
it('should leave the meeting on the resource with reason', async () => {
|
|
2161
|
+
const leave = meeting.leave({resourceId: meeting.resourceId, reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST});
|
|
2162
|
+
|
|
2163
|
+
assert.exists(leave.then);
|
|
2164
|
+
await leave;
|
|
2165
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2166
|
+
locusUrl: meeting.locusUrl,
|
|
2167
|
+
correlationId: meeting.correlationId,
|
|
2168
|
+
selfId: meeting.selfId,
|
|
2169
|
+
resourceId: meeting.resourceId,
|
|
2170
|
+
deviceUrl: meeting.deviceUrl,
|
|
2171
|
+
reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST
|
|
2172
|
+
});
|
|
2173
|
+
});
|
|
2174
|
+
});
|
|
2175
|
+
describe('#requestScreenShareFloor', () => {
|
|
2176
|
+
it('should have #requestScreenShareFloor', () => {
|
|
2177
|
+
assert.exists(meeting.requestScreenShareFloor);
|
|
2178
|
+
});
|
|
2179
|
+
beforeEach(() => {
|
|
2180
|
+
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
2181
|
+
meeting.locusInfo.self = {url: url1};
|
|
2182
|
+
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
2183
|
+
meeting.mediaProperties.shareTrack = {}
|
|
2184
|
+
meeting.mediaProperties.mediaDirection.sendShare = true;
|
|
2185
|
+
meeting.state = 'JOINED';
|
|
2186
|
+
});
|
|
2187
|
+
it('should send the share', async () => {
|
|
2188
|
+
const share = meeting.requestScreenShareFloor();
|
|
2189
|
+
|
|
2190
|
+
assert.exists(share.then);
|
|
2191
|
+
await share;
|
|
2192
|
+
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
2193
|
+
});
|
|
1991
2194
|
});
|
|
1992
2195
|
|
|
1993
2196
|
describe('#sendDTMF', () => {
|
|
@@ -2034,37 +2237,25 @@ describe('plugin-meetings', () => {
|
|
|
2034
2237
|
|
|
2035
2238
|
describe('#updateMedia', () => {
|
|
2036
2239
|
let sandbox;
|
|
2037
|
-
const mockLocalStream = {id: 'mock local stream'};
|
|
2038
|
-
const mockLocalShare = {id: 'mock local share stream'};
|
|
2039
|
-
const FAKE_TRACKS = {
|
|
2040
|
-
audio: {
|
|
2041
|
-
id: 'fake audio track',
|
|
2042
|
-
getSettings: sinon.stub().returns({}),
|
|
2043
|
-
},
|
|
2044
|
-
video: {
|
|
2045
|
-
id: 'fake video track',
|
|
2046
|
-
getSettings: sinon.stub().returns({}),
|
|
2047
|
-
},
|
|
2048
|
-
screenshareVideo: {
|
|
2049
|
-
id: 'fake share track',
|
|
2050
|
-
getSettings: sinon.stub().returns({}),
|
|
2051
|
-
},
|
|
2052
|
-
};
|
|
2053
2240
|
|
|
2241
|
+
const createFakeLocalTrack = () => ({
|
|
2242
|
+
underlyingTrack: {id: 'fake underlying track'}
|
|
2243
|
+
});
|
|
2054
2244
|
beforeEach(() => {
|
|
2055
2245
|
sandbox = sinon.createSandbox();
|
|
2056
|
-
meeting.
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2246
|
+
meeting.audio = { enable: sinon.stub()};
|
|
2247
|
+
meeting.video = { enable: sinon.stub()};
|
|
2248
|
+
meeting.mediaProperties.audioTrack = createFakeLocalTrack();
|
|
2249
|
+
meeting.mediaProperties.videoTrack = createFakeLocalTrack();
|
|
2250
|
+
meeting.mediaProperties.shareTrack = createFakeLocalTrack();
|
|
2251
|
+
meeting.mediaProperties.mediaDirection = {
|
|
2252
|
+
sendAudio: true,
|
|
2253
|
+
sendVideo: true,
|
|
2254
|
+
sendShare: true,
|
|
2255
|
+
receiveAudio: true,
|
|
2256
|
+
receiveVideo: true,
|
|
2257
|
+
receiveShare: true,
|
|
2258
|
+
}
|
|
2068
2259
|
});
|
|
2069
2260
|
|
|
2070
2261
|
afterEach(() => {
|
|
@@ -2072,36 +2263,45 @@ describe('plugin-meetings', () => {
|
|
|
2072
2263
|
sandbox = null;
|
|
2073
2264
|
});
|
|
2074
2265
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2266
|
+
forEach(
|
|
2267
|
+
[
|
|
2268
|
+
{audioEnabled: true, enableMultistreamAudio: true},
|
|
2269
|
+
{audioEnabled: false, enableMultistreamAudio: false},
|
|
2270
|
+
],
|
|
2271
|
+
({audioEnabled, enableMultistreamAudio}) => {
|
|
2272
|
+
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and audioEnabled: ${audioEnabled}`, async () => {
|
|
2273
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2274
|
+
enableMultistreamAudio: sinon.stub().resolves({}),
|
|
2275
|
+
};
|
|
2276
|
+
meeting.isMultistream = true;
|
|
2277
|
+
|
|
2278
|
+
await meeting.updateMedia({audioEnabled});
|
|
2279
|
+
|
|
2280
|
+
assert.calledOnceWithExactly(
|
|
2281
|
+
meeting.mediaProperties.webrtcMediaConnection.enableMultistreamAudio,
|
|
2282
|
+
enableMultistreamAudio
|
|
2283
|
+
);
|
|
2284
|
+
assert.calledOnceWithExactly(meeting.audio.enable, meeting, enableMultistreamAudio);
|
|
2285
|
+
});
|
|
2286
|
+
}
|
|
2287
|
+
);
|
|
2085
2288
|
|
|
2289
|
+
it('should use a queue if currently busy', async () => {
|
|
2086
2290
|
sandbox.stub(meeting, 'canUpdateMedia').returns(false);
|
|
2087
2291
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2088
|
-
|
|
2292
|
+
update: sinon.stub().resolves({}),
|
|
2089
2293
|
};
|
|
2090
2294
|
|
|
2091
2295
|
let myPromiseResolved = false;
|
|
2092
2296
|
|
|
2093
2297
|
meeting
|
|
2094
|
-
.updateMedia({
|
|
2095
|
-
localStream: mockLocalStream,
|
|
2096
|
-
localShare: mockLocalShare,
|
|
2097
|
-
mediaSettings,
|
|
2098
|
-
})
|
|
2298
|
+
.updateMedia({audioEnabled: false, videoEnabled: false})
|
|
2099
2299
|
.then(() => {
|
|
2100
2300
|
myPromiseResolved = true;
|
|
2101
2301
|
});
|
|
2102
2302
|
|
|
2103
2303
|
// verify that nothing was done
|
|
2104
|
-
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.
|
|
2304
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2105
2305
|
|
|
2106
2306
|
// now trigger processing of the queue
|
|
2107
2307
|
meeting.canUpdateMedia.restore();
|
|
@@ -2110,22 +2310,22 @@ describe('plugin-meetings', () => {
|
|
|
2110
2310
|
meeting.processNextQueuedMediaUpdate();
|
|
2111
2311
|
await testUtils.flushPromises();
|
|
2112
2312
|
|
|
2113
|
-
// and check that
|
|
2114
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.
|
|
2313
|
+
// and check that update is called with the original args
|
|
2314
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2115
2315
|
assert.calledWith(
|
|
2116
|
-
meeting.mediaProperties.webrtcMediaConnection.
|
|
2316
|
+
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2117
2317
|
{
|
|
2118
|
-
|
|
2119
|
-
audio:
|
|
2120
|
-
video:
|
|
2121
|
-
screenShareVideo:
|
|
2318
|
+
localTracks: {
|
|
2319
|
+
audio: meeting.mediaProperties.audioTrack.underlyingTrack,
|
|
2320
|
+
video: meeting.mediaProperties.videoTrack.underlyingTrack,
|
|
2321
|
+
screenShareVideo: meeting.mediaProperties.shareTrack.underlyingTrack,
|
|
2122
2322
|
},
|
|
2123
|
-
|
|
2124
|
-
audio:
|
|
2125
|
-
video:
|
|
2126
|
-
screenShareVideo:
|
|
2127
|
-
remoteQualityLevel: 'HIGH',
|
|
2323
|
+
direction: {
|
|
2324
|
+
audio: 'inactive',
|
|
2325
|
+
video: 'inactive',
|
|
2326
|
+
screenShareVideo: 'sendrecv',
|
|
2128
2327
|
},
|
|
2328
|
+
remoteQualityLevel: 'HIGH',
|
|
2129
2329
|
}
|
|
2130
2330
|
);
|
|
2131
2331
|
assert.isTrue(myPromiseResolved);
|
|
@@ -2144,8 +2344,6 @@ describe('plugin-meetings', () => {
|
|
|
2144
2344
|
sendShare: false,
|
|
2145
2345
|
receiveVideo: true,
|
|
2146
2346
|
};
|
|
2147
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2148
|
-
meeting.updateVideo = sinon.stub().returns(Promise.resolve());
|
|
2149
2347
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2150
2348
|
meeting.mediaProperties.remoteVideoTrack = sinon
|
|
2151
2349
|
.stub()
|
|
@@ -2382,76 +2580,12 @@ describe('plugin-meetings', () => {
|
|
|
2382
2580
|
});
|
|
2383
2581
|
});
|
|
2384
2582
|
|
|
2385
|
-
describe('#setLocalVideoQuality', () => {
|
|
2386
|
-
let mediaDirection;
|
|
2387
|
-
|
|
2388
|
-
const fakeTrack = {getSettings: () => ({height: 720})};
|
|
2389
|
-
const USER_AGENT_CHROME_MAC =
|
|
2390
|
-
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) ' +
|
|
2391
|
-
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/90.0.4430.85 Safari/537.36';
|
|
2392
|
-
|
|
2393
|
-
beforeEach(() => {
|
|
2394
|
-
mediaDirection = {sendAudio: true, sendVideo: true, sendShare: false};
|
|
2395
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2396
|
-
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2397
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
2398
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
2399
|
-
meeting.updateVideo = sinon.stub().resolves();
|
|
2400
|
-
sinon.stub(MeetingUtil, 'getTrack').returns({videoTrack: fakeTrack});
|
|
2401
|
-
});
|
|
2402
|
-
|
|
2403
|
-
it('should have #setLocalVideoQuality', () => {
|
|
2404
|
-
assert.exists(meeting.setLocalVideoQuality);
|
|
2405
|
-
});
|
|
2406
|
-
|
|
2407
|
-
it('should call getMediaStreams with the proper level', () =>
|
|
2408
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2409
|
-
delete mediaDirection.receiveVideo;
|
|
2410
|
-
assert.calledWith(
|
|
2411
|
-
meeting.getMediaStreams,
|
|
2412
|
-
mediaDirection,
|
|
2413
|
-
CONSTANTS.VIDEO_RESOLUTIONS[CONSTANTS.QUALITY_LEVELS.LOW]
|
|
2414
|
-
);
|
|
2415
|
-
}));
|
|
2416
|
-
|
|
2417
|
-
it('when browser is chrome then it should stop previous video track', () => {
|
|
2418
|
-
meeting.mediaProperties.videoTrack = fakeTrack;
|
|
2419
|
-
assert.equal(BrowserDetection(USER_AGENT_CHROME_MAC).getBrowserName(), 'Chrome');
|
|
2420
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2421
|
-
assert.calledWith(Media.stopTracks, fakeTrack);
|
|
2422
|
-
});
|
|
2423
|
-
});
|
|
2424
|
-
|
|
2425
|
-
it('should set mediaProperty with the proper level', () =>
|
|
2426
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2427
|
-
assert.equal(meeting.mediaProperties.localQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
2428
|
-
}));
|
|
2429
|
-
|
|
2430
|
-
it('when device does not support 1080p then it should set localQualityLevel with highest possible resolution', () => {
|
|
2431
|
-
meeting.setLocalVideoQuality(CONSTANTS.QUALITY_LEVELS['1080p']).then(() => {
|
|
2432
|
-
assert.equal(
|
|
2433
|
-
meeting.mediaProperties.localQualityLevel,
|
|
2434
|
-
CONSTANTS.QUALITY_LEVELS['720p']
|
|
2435
|
-
);
|
|
2436
|
-
});
|
|
2437
|
-
});
|
|
2438
|
-
|
|
2439
|
-
it('should error if set to a invalid level', () => {
|
|
2440
|
-
assert.isRejected(meeting.setLocalVideoQuality('invalid'));
|
|
2441
|
-
});
|
|
2442
|
-
|
|
2443
|
-
it('should error if sendVideo is set to false', () => {
|
|
2444
|
-
meeting.mediaProperties.mediaDirection = {sendVideo: false};
|
|
2445
|
-
assert.isRejected(meeting.setLocalVideoQuality('LOW'));
|
|
2446
|
-
});
|
|
2447
|
-
});
|
|
2448
|
-
|
|
2449
2583
|
describe('#setRemoteQualityLevel', () => {
|
|
2450
2584
|
let mediaDirection;
|
|
2451
2585
|
|
|
2452
2586
|
beforeEach(() => {
|
|
2453
2587
|
mediaDirection = {receiveAudio: true, receiveVideo: true, receiveShare: false};
|
|
2454
|
-
meeting.
|
|
2588
|
+
meeting.updateTranscodedMediaConnection = sinon.stub().returns(Promise.resolve());
|
|
2455
2589
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2456
2590
|
});
|
|
2457
2591
|
|
|
@@ -2464,9 +2598,9 @@ describe('plugin-meetings', () => {
|
|
|
2464
2598
|
assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
2465
2599
|
}));
|
|
2466
2600
|
|
|
2467
|
-
it('should call
|
|
2601
|
+
it('should call Meeting.updateTranscodedMediaConnection()', () =>
|
|
2468
2602
|
meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2469
|
-
assert.calledOnce(meeting.
|
|
2603
|
+
assert.calledOnce(meeting.updateTranscodedMediaConnection);
|
|
2470
2604
|
}));
|
|
2471
2605
|
|
|
2472
2606
|
it('should error if set to a invalid level', () => {
|
|
@@ -2487,7 +2621,6 @@ describe('plugin-meetings', () => {
|
|
|
2487
2621
|
meeting.meetingRequest.dialOut = sinon
|
|
2488
2622
|
.stub()
|
|
2489
2623
|
.returns(Promise.resolve({body: {locus: 'testData'}}));
|
|
2490
|
-
meeting.locusInfo.onFullLocus = sinon.stub().returns(Promise.resolve());
|
|
2491
2624
|
});
|
|
2492
2625
|
|
|
2493
2626
|
it('with no parameters triggers dial-in, delegating request to meetingRequest correctly', async () => {
|
|
@@ -2500,11 +2633,9 @@ describe('plugin-meetings', () => {
|
|
|
2500
2633
|
locusUrl: meeting.locusUrl,
|
|
2501
2634
|
clientUrl: meeting.deviceUrl,
|
|
2502
2635
|
});
|
|
2503
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2504
2636
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
2505
2637
|
|
|
2506
2638
|
meeting.meetingRequest.dialIn.resetHistory();
|
|
2507
|
-
meeting.locusInfo.onFullLocus.resetHistory();
|
|
2508
2639
|
|
|
2509
2640
|
// try again. the dial in urls should match
|
|
2510
2641
|
await meeting.usePhoneAudio();
|
|
@@ -2515,7 +2646,6 @@ describe('plugin-meetings', () => {
|
|
|
2515
2646
|
locusUrl: meeting.locusUrl,
|
|
2516
2647
|
clientUrl: meeting.deviceUrl,
|
|
2517
2648
|
});
|
|
2518
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2519
2649
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
2520
2650
|
});
|
|
2521
2651
|
|
|
@@ -2532,11 +2662,9 @@ describe('plugin-meetings', () => {
|
|
|
2532
2662
|
clientUrl: meeting.deviceUrl,
|
|
2533
2663
|
phoneNumber,
|
|
2534
2664
|
});
|
|
2535
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2536
2665
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
2537
2666
|
|
|
2538
2667
|
meeting.meetingRequest.dialOut.resetHistory();
|
|
2539
|
-
meeting.locusInfo.onFullLocus.resetHistory();
|
|
2540
2668
|
|
|
2541
2669
|
// try again. the dial out urls should match
|
|
2542
2670
|
await meeting.usePhoneAudio(phoneNumber);
|
|
@@ -2548,7 +2676,6 @@ describe('plugin-meetings', () => {
|
|
|
2548
2676
|
clientUrl: meeting.deviceUrl,
|
|
2549
2677
|
phoneNumber,
|
|
2550
2678
|
});
|
|
2551
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2552
2679
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
2553
2680
|
});
|
|
2554
2681
|
|
|
@@ -2583,6 +2710,19 @@ describe('plugin-meetings', () => {
|
|
|
2583
2710
|
});
|
|
2584
2711
|
});
|
|
2585
2712
|
|
|
2713
|
+
describe("#isJoined", () => {
|
|
2714
|
+
it("should returns isJoined correctly", () => {
|
|
2715
|
+
meeting.joinedWith = undefined;
|
|
2716
|
+
assert.equal(meeting.isJoined(), false);
|
|
2717
|
+
|
|
2718
|
+
meeting.joinedWith = {state: "NOT_JOINED"};
|
|
2719
|
+
assert.equal(meeting.isJoined(), false);
|
|
2720
|
+
|
|
2721
|
+
meeting.joinedWith = {state: "JOINED"};
|
|
2722
|
+
assert.equal(meeting.isJoined(), true);
|
|
2723
|
+
});
|
|
2724
|
+
});
|
|
2725
|
+
|
|
2586
2726
|
describe('#fetchMeetingInfo', () => {
|
|
2587
2727
|
const FAKE_DESTINATION = 'something@somecompany.com';
|
|
2588
2728
|
const FAKE_TYPE = _SIP_URI_;
|
|
@@ -2593,6 +2733,9 @@ describe('plugin-meetings', () => {
|
|
|
2593
2733
|
const FAKE_CAPTCHA_IMAGE_URL = 'http://captchaimage';
|
|
2594
2734
|
const FAKE_CAPTCHA_AUDIO_URL = 'http://captchaaudio';
|
|
2595
2735
|
const FAKE_CAPTCHA_REFRESH_URL = 'http://captcharefresh';
|
|
2736
|
+
const FAKE_INSTALLED_ORG_ID = '123456';
|
|
2737
|
+
const FAKE_EXTRA_PARAMS = {mtid: 'm9fe0afd8c435e892afcce9ea25b97046', joinTXId: 'TSmrX61wNF'};
|
|
2738
|
+
let FAKE_OPTIONS;
|
|
2596
2739
|
const FAKE_MEETING_INFO = {
|
|
2597
2740
|
conversationUrl: 'some_convo_url',
|
|
2598
2741
|
locusUrl: 'some_locus_url',
|
|
@@ -2600,6 +2743,8 @@ describe('plugin-meetings', () => {
|
|
|
2600
2743
|
meetingNumber: '123456', // this.config.experimental.enableUnifiedMeetings
|
|
2601
2744
|
hostId: 'some_host_id', // this.owner;
|
|
2602
2745
|
};
|
|
2746
|
+
const FAKE_MEETING_INFO_LOOKUP_URL = 'meetingLookupUrl';
|
|
2747
|
+
|
|
2603
2748
|
const FAKE_SDK_CAPTCHA_INFO = {
|
|
2604
2749
|
captchaId: FAKE_CAPTCHA_ID,
|
|
2605
2750
|
verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
|
|
@@ -2613,18 +2758,26 @@ describe('plugin-meetings', () => {
|
|
|
2613
2758
|
refreshURL: `${FAKE_CAPTCHA_REFRESH_URL}-2`,
|
|
2614
2759
|
};
|
|
2615
2760
|
|
|
2761
|
+
beforeEach(() => {
|
|
2762
|
+
meeting.locusId = 'locus-id';
|
|
2763
|
+
meeting.id = 'meeting-id';
|
|
2764
|
+
FAKE_OPTIONS = {meetingId: meeting.id};
|
|
2765
|
+
});
|
|
2766
|
+
|
|
2616
2767
|
it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
|
|
2617
2768
|
meeting.attrs.meetingInfoProvider = {
|
|
2618
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2769
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2619
2770
|
};
|
|
2620
2771
|
meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
|
|
2621
2772
|
meeting.destination = FAKE_DESTINATION;
|
|
2622
2773
|
meeting.destinationType = FAKE_TYPE;
|
|
2774
|
+
meeting.config.installedOrgID = FAKE_INSTALLED_ORG_ID;
|
|
2623
2775
|
meeting.parseMeetingInfo = sinon.stub().returns(undefined);
|
|
2624
2776
|
|
|
2625
2777
|
await meeting.fetchMeetingInfo({
|
|
2626
2778
|
password: FAKE_PASSWORD,
|
|
2627
2779
|
captchaCode: FAKE_CAPTCHA_CODE,
|
|
2780
|
+
extraParams: FAKE_EXTRA_PARAMS,
|
|
2628
2781
|
});
|
|
2629
2782
|
|
|
2630
2783
|
assert.calledWith(
|
|
@@ -2632,11 +2785,15 @@ describe('plugin-meetings', () => {
|
|
|
2632
2785
|
FAKE_DESTINATION,
|
|
2633
2786
|
FAKE_TYPE,
|
|
2634
2787
|
FAKE_PASSWORD,
|
|
2635
|
-
{code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID}
|
|
2788
|
+
{code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID},
|
|
2789
|
+
FAKE_INSTALLED_ORG_ID,
|
|
2790
|
+
meeting.locusId,
|
|
2791
|
+
FAKE_EXTRA_PARAMS,
|
|
2792
|
+
FAKE_OPTIONS
|
|
2636
2793
|
);
|
|
2637
2794
|
|
|
2638
|
-
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
|
|
2639
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2795
|
+
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}, FAKE_DESTINATION);
|
|
2796
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL});
|
|
2640
2797
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
|
|
2641
2798
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2642
2799
|
assert.equal(meeting.requiredCaptcha, null);
|
|
@@ -2651,7 +2808,7 @@ describe('plugin-meetings', () => {
|
|
|
2651
2808
|
|
|
2652
2809
|
it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
|
|
2653
2810
|
meeting.attrs.meetingInfoProvider = {
|
|
2654
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2811
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2655
2812
|
};
|
|
2656
2813
|
meeting.destination = FAKE_DESTINATION;
|
|
2657
2814
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -2674,13 +2831,17 @@ describe('plugin-meetings', () => {
|
|
|
2674
2831
|
FAKE_DESTINATION,
|
|
2675
2832
|
FAKE_TYPE,
|
|
2676
2833
|
null,
|
|
2677
|
-
null
|
|
2834
|
+
null,
|
|
2835
|
+
undefined,
|
|
2836
|
+
meeting.locusId,
|
|
2837
|
+
{},
|
|
2838
|
+
{meetingId: meeting.id}
|
|
2678
2839
|
);
|
|
2679
2840
|
|
|
2680
2841
|
// parseMeeting info
|
|
2681
|
-
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
|
|
2842
|
+
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}, FAKE_DESTINATION);
|
|
2682
2843
|
|
|
2683
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2844
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL});
|
|
2684
2845
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2685
2846
|
assert.equal(meeting.requiredCaptcha, null);
|
|
2686
2847
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
|
|
@@ -2696,7 +2857,7 @@ describe('plugin-meetings', () => {
|
|
|
2696
2857
|
|
|
2697
2858
|
it('fails if captchaCode is provided when captcha not needed', async () => {
|
|
2698
2859
|
meeting.attrs.meetingInfoProvider = {
|
|
2699
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2860
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2700
2861
|
};
|
|
2701
2862
|
meeting.requiredCaptcha = null;
|
|
2702
2863
|
meeting.destination = FAKE_DESTINATION;
|
|
@@ -2715,7 +2876,7 @@ describe('plugin-meetings', () => {
|
|
|
2715
2876
|
|
|
2716
2877
|
it('fails if password is provided when not required', async () => {
|
|
2717
2878
|
meeting.attrs.meetingInfoProvider = {
|
|
2718
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2879
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2719
2880
|
};
|
|
2720
2881
|
meeting.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
2721
2882
|
meeting.destination = FAKE_DESTINATION;
|
|
@@ -2748,10 +2909,15 @@ describe('plugin-meetings', () => {
|
|
|
2748
2909
|
FAKE_DESTINATION,
|
|
2749
2910
|
FAKE_TYPE,
|
|
2750
2911
|
null,
|
|
2751
|
-
null
|
|
2912
|
+
null,
|
|
2913
|
+
undefined,
|
|
2914
|
+
'locus-id',
|
|
2915
|
+
{},
|
|
2916
|
+
{meetingId: meeting.id},
|
|
2752
2917
|
);
|
|
2753
2918
|
|
|
2754
2919
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2920
|
+
assert.equal(meeting.meetingInfoFailureCode, 403004);
|
|
2755
2921
|
assert.equal(
|
|
2756
2922
|
meeting.meetingInfoFailureReason,
|
|
2757
2923
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
@@ -2760,6 +2926,38 @@ describe('plugin-meetings', () => {
|
|
|
2760
2926
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
2761
2927
|
});
|
|
2762
2928
|
|
|
2929
|
+
it('handles meetingInfoProvider policy error', async () => {
|
|
2930
|
+
meeting.destination = FAKE_DESTINATION;
|
|
2931
|
+
meeting.destinationType = FAKE_TYPE;
|
|
2932
|
+
meeting.attrs.meetingInfoProvider = {
|
|
2933
|
+
fetchMeetingInfo: sinon
|
|
2934
|
+
.stub()
|
|
2935
|
+
.throws(new MeetingInfoV2PolicyError(123456, FAKE_MEETING_INFO, 'a message')),
|
|
2936
|
+
};
|
|
2937
|
+
|
|
2938
|
+
await assert.isRejected(meeting.fetchMeetingInfo({}), PermissionError);
|
|
2939
|
+
|
|
2940
|
+
assert.calledWith(
|
|
2941
|
+
meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
|
|
2942
|
+
FAKE_DESTINATION,
|
|
2943
|
+
FAKE_TYPE,
|
|
2944
|
+
null,
|
|
2945
|
+
null,
|
|
2946
|
+
undefined,
|
|
2947
|
+
'locus-id',
|
|
2948
|
+
{},
|
|
2949
|
+
{meetingId: meeting.id},
|
|
2950
|
+
);
|
|
2951
|
+
|
|
2952
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2953
|
+
assert.equal(meeting.meetingInfoFailureCode, 123456);
|
|
2954
|
+
assert.equal(
|
|
2955
|
+
meeting.meetingInfoFailureReason,
|
|
2956
|
+
MEETING_INFO_FAILURE_REASON.POLICY
|
|
2957
|
+
);
|
|
2958
|
+
});
|
|
2959
|
+
|
|
2960
|
+
|
|
2763
2961
|
it('handles meetingInfoProvider requiring captcha because of wrong password', async () => {
|
|
2764
2962
|
meeting.destination = FAKE_DESTINATION;
|
|
2765
2963
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -2782,7 +2980,11 @@ describe('plugin-meetings', () => {
|
|
|
2782
2980
|
FAKE_DESTINATION,
|
|
2783
2981
|
FAKE_TYPE,
|
|
2784
2982
|
'aaa',
|
|
2785
|
-
null
|
|
2983
|
+
null,
|
|
2984
|
+
undefined,
|
|
2985
|
+
'locus-id',
|
|
2986
|
+
{},
|
|
2987
|
+
{meetingId: meeting.id},
|
|
2786
2988
|
);
|
|
2787
2989
|
|
|
2788
2990
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
@@ -2790,6 +2992,7 @@ describe('plugin-meetings', () => {
|
|
|
2790
2992
|
meeting.meetingInfoFailureReason,
|
|
2791
2993
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
2792
2994
|
);
|
|
2995
|
+
assert.equal(meeting.meetingInfoFailureCode, 423005);
|
|
2793
2996
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
2794
2997
|
assert.deepEqual(meeting.requiredCaptcha, {
|
|
2795
2998
|
captchaId: FAKE_CAPTCHA_ID,
|
|
@@ -2822,7 +3025,11 @@ describe('plugin-meetings', () => {
|
|
|
2822
3025
|
FAKE_DESTINATION,
|
|
2823
3026
|
FAKE_TYPE,
|
|
2824
3027
|
'aaa',
|
|
2825
|
-
{code: 'bbb', id: FAKE_CAPTCHA_ID}
|
|
3028
|
+
{code: 'bbb', id: FAKE_CAPTCHA_ID},
|
|
3029
|
+
undefined,
|
|
3030
|
+
'locus-id',
|
|
3031
|
+
{},
|
|
3032
|
+
{meetingId: meeting.id}
|
|
2826
3033
|
);
|
|
2827
3034
|
|
|
2828
3035
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
@@ -2851,10 +3058,14 @@ describe('plugin-meetings', () => {
|
|
|
2851
3058
|
FAKE_DESTINATION,
|
|
2852
3059
|
FAKE_TYPE,
|
|
2853
3060
|
'aaa',
|
|
2854
|
-
null
|
|
3061
|
+
null,
|
|
3062
|
+
undefined,
|
|
3063
|
+
'locus-id',
|
|
3064
|
+
{},
|
|
3065
|
+
{meetingId: meeting.id},
|
|
2855
3066
|
);
|
|
2856
3067
|
|
|
2857
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
3068
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: undefined});
|
|
2858
3069
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2859
3070
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.VERIFIED);
|
|
2860
3071
|
assert.equal(meeting.requiredCaptcha, null);
|
|
@@ -2896,7 +3107,11 @@ describe('plugin-meetings', () => {
|
|
|
2896
3107
|
FAKE_DESTINATION,
|
|
2897
3108
|
FAKE_TYPE,
|
|
2898
3109
|
'aaa',
|
|
2899
|
-
{code: 'bbb', id: FAKE_CAPTCHA_ID}
|
|
3110
|
+
{code: 'bbb', id: FAKE_CAPTCHA_ID},
|
|
3111
|
+
undefined,
|
|
3112
|
+
'locus-id',
|
|
3113
|
+
{},
|
|
3114
|
+
{meetingId: meeting.id},
|
|
2900
3115
|
);
|
|
2901
3116
|
|
|
2902
3117
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
@@ -3041,6 +3256,17 @@ describe('plugin-meetings', () => {
|
|
|
3041
3256
|
});
|
|
3042
3257
|
});
|
|
3043
3258
|
|
|
3259
|
+
describe('#postMetrics', () => {
|
|
3260
|
+
it('should have #postMetrics', () => {
|
|
3261
|
+
assert.exists(meeting.postMetrics);
|
|
3262
|
+
});
|
|
3263
|
+
|
|
3264
|
+
it('should trigger `postMetrics`', async () => {
|
|
3265
|
+
await meeting.postMetrics(eventType.LEAVE);
|
|
3266
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.LEAVE});
|
|
3267
|
+
});
|
|
3268
|
+
});
|
|
3269
|
+
|
|
3044
3270
|
describe('#endMeeting for all', () => {
|
|
3045
3271
|
let sandbox;
|
|
3046
3272
|
|
|
@@ -3061,16 +3287,12 @@ describe('plugin-meetings', () => {
|
|
|
3061
3287
|
.stub()
|
|
3062
3288
|
.returns(Promise.resolve({body: 'test'}));
|
|
3063
3289
|
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
3064
|
-
meeting.
|
|
3065
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
3290
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
3066
3291
|
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
3067
3292
|
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
3068
3293
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
3069
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
3070
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
3071
3294
|
meeting.unsetRemoteTracks = sinon.stub();
|
|
3072
3295
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
3073
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
3074
3296
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
3075
3297
|
meeting.logger.error = sinon.stub().returns(true);
|
|
3076
3298
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
@@ -3089,12 +3311,9 @@ describe('plugin-meetings', () => {
|
|
|
3089
3311
|
assert.exists(endMeetingForAll.then);
|
|
3090
3312
|
await endMeetingForAll;
|
|
3091
3313
|
assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
|
|
3092
|
-
assert.calledOnce(meeting?.
|
|
3093
|
-
assert.calledOnce(meeting?.closeLocalShare);
|
|
3314
|
+
assert.calledOnce(meeting?.cleanupLocalTracks);
|
|
3094
3315
|
assert.calledOnce(meeting?.closeRemoteTracks);
|
|
3095
3316
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
3096
|
-
assert.calledOnce(meeting?.unsetLocalVideoTrack);
|
|
3097
|
-
assert.calledOnce(meeting?.unsetLocalShareTrack);
|
|
3098
3317
|
assert.calledOnce(meeting?.unsetRemoteTracks);
|
|
3099
3318
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
3100
3319
|
});
|
|
@@ -3105,11 +3324,9 @@ describe('plugin-meetings', () => {
|
|
|
3105
3324
|
|
|
3106
3325
|
beforeEach(() => {
|
|
3107
3326
|
sandbox = sinon.createSandbox();
|
|
3108
|
-
sandbox.stub(meeting, '
|
|
3109
|
-
sandbox.stub(meeting, 'closeLocalShare');
|
|
3327
|
+
sandbox.stub(meeting, 'cleanupLocalTracks');
|
|
3110
3328
|
|
|
3111
3329
|
sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
|
|
3112
|
-
sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
|
|
3113
3330
|
|
|
3114
3331
|
sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
|
|
3115
3332
|
sandbox
|
|
@@ -3182,14 +3399,12 @@ describe('plugin-meetings', () => {
|
|
|
3182
3399
|
|
|
3183
3400
|
// beacuse we are calling callback so we need to wait
|
|
3184
3401
|
|
|
3185
|
-
assert.called(meeting.
|
|
3186
|
-
assert.called(meeting.closeLocalShare);
|
|
3402
|
+
assert.called(meeting.cleanupLocalTracks);
|
|
3187
3403
|
|
|
3188
3404
|
// give queued Promise callbacks a chance to run
|
|
3189
3405
|
await Promise.resolve();
|
|
3190
3406
|
|
|
3191
3407
|
assert.called(meeting.mediaProperties.setMediaDirection);
|
|
3192
|
-
assert.called(meeting.mediaProperties.unsetMediaTracks);
|
|
3193
3408
|
|
|
3194
3409
|
assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
|
|
3195
3410
|
mediaDirection: {
|
|
@@ -3304,6 +3519,284 @@ describe('plugin-meetings', () => {
|
|
|
3304
3519
|
}
|
|
3305
3520
|
});
|
|
3306
3521
|
});
|
|
3522
|
+
describe('Local tracks publishing', () => {
|
|
3523
|
+
let audioTrack;
|
|
3524
|
+
let videoTrack;
|
|
3525
|
+
let videoShareTrack;
|
|
3526
|
+
let createMuteStateStub;
|
|
3527
|
+
let LocalDisplayTrackConstructorStub;
|
|
3528
|
+
let LocalMicrophoneTrackConstructorStub;
|
|
3529
|
+
let LocalCameraTrackConstructorStub;
|
|
3530
|
+
let fakeLocalDisplayTrack;
|
|
3531
|
+
let fakeLocalMicrophoneTrack;
|
|
3532
|
+
let fakeLocalCameraTrack;
|
|
3533
|
+
|
|
3534
|
+
beforeEach(() => {
|
|
3535
|
+
audioTrack = {
|
|
3536
|
+
id: 'audio track',
|
|
3537
|
+
getSettings: sinon.stub().returns({}),
|
|
3538
|
+
on: sinon.stub(),
|
|
3539
|
+
off: sinon.stub(),
|
|
3540
|
+
};
|
|
3541
|
+
videoTrack = {
|
|
3542
|
+
id: 'video track',
|
|
3543
|
+
getSettings: sinon.stub().returns({}),
|
|
3544
|
+
on: sinon.stub(),
|
|
3545
|
+
off: sinon.stub(),
|
|
3546
|
+
};
|
|
3547
|
+
videoShareTrack = {
|
|
3548
|
+
id: 'share track',
|
|
3549
|
+
on: sinon.stub(),
|
|
3550
|
+
off: sinon.stub(),
|
|
3551
|
+
getSettings: sinon.stub().returns({}),
|
|
3552
|
+
};
|
|
3553
|
+
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
3554
|
+
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
3555
|
+
meeting.mediaProperties.mediaDirection = {
|
|
3556
|
+
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
|
3557
|
+
sendVideo: 'fake value',
|
|
3558
|
+
sendShare: false,
|
|
3559
|
+
};
|
|
3560
|
+
meeting.isMultistream = true;
|
|
3561
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
3562
|
+
publishTrack: sinon.stub().resolves({}),
|
|
3563
|
+
unpublishTrack: sinon.stub().resolves({}),
|
|
3564
|
+
};
|
|
3565
|
+
meeting.audio = { handleLocalTrackChange: sinon.stub()};
|
|
3566
|
+
meeting.video = { handleLocalTrackChange: sinon.stub()};
|
|
3567
|
+
|
|
3568
|
+
const createFakeLocalTrack = (originalTrack) => ({
|
|
3569
|
+
on: sinon.stub(),
|
|
3570
|
+
off: sinon.stub(),
|
|
3571
|
+
stop: sinon.stub(),
|
|
3572
|
+
originalTrack,
|
|
3573
|
+
});
|
|
3574
|
+
|
|
3575
|
+
// setup mock constructors for webrtc-core local track classes in such a way
|
|
3576
|
+
// that they return the original track correctly (this is needed for unpublish() API tests)
|
|
3577
|
+
LocalDisplayTrackConstructorStub = sinon
|
|
3578
|
+
.stub(InternalMediaCoreModule, 'LocalDisplayTrack')
|
|
3579
|
+
.callsFake((stream) => {
|
|
3580
|
+
fakeLocalDisplayTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3581
|
+
return fakeLocalDisplayTrack;
|
|
3582
|
+
});
|
|
3583
|
+
LocalMicrophoneTrackConstructorStub = sinon
|
|
3584
|
+
.stub(InternalMediaCoreModule, 'LocalMicrophoneTrack')
|
|
3585
|
+
.callsFake((stream) => {
|
|
3586
|
+
fakeLocalMicrophoneTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3587
|
+
return fakeLocalMicrophoneTrack;
|
|
3588
|
+
});
|
|
3589
|
+
LocalCameraTrackConstructorStub = sinon
|
|
3590
|
+
.stub(InternalMediaCoreModule, 'LocalCameraTrack')
|
|
3591
|
+
.callsFake((stream) => {
|
|
3592
|
+
fakeLocalCameraTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3593
|
+
return fakeLocalCameraTrack;
|
|
3594
|
+
});
|
|
3595
|
+
|
|
3596
|
+
createMuteStateStub = sinon
|
|
3597
|
+
.stub(MuteStateModule, 'createMuteState')
|
|
3598
|
+
.returns({id: 'fake mute state instance'});
|
|
3599
|
+
});
|
|
3600
|
+
describe('#publishTracks', () => {
|
|
3601
|
+
it('fails if there is no media connection', async () => {
|
|
3602
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3603
|
+
await assert.isRejected(meeting.publishTracks({audio: {id: 'some audio track'}}));
|
|
3604
|
+
});
|
|
3605
|
+
|
|
3606
|
+
const checkAudioPublished = (track) => {
|
|
3607
|
+
assert.calledOnceWithExactly(meeting.audio.handleLocalTrackChange, meeting);
|
|
3608
|
+
assert.calledWith(
|
|
3609
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3610
|
+
track
|
|
3611
|
+
);
|
|
3612
|
+
assert.equal(meeting.mediaProperties.audioTrack, track);
|
|
3613
|
+
// check that sendAudio hasn't been touched
|
|
3614
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
3615
|
+
};
|
|
3616
|
+
|
|
3617
|
+
const checkVideoPublished = (track) => {
|
|
3618
|
+
assert.calledOnceWithExactly(meeting.video.handleLocalTrackChange, meeting);
|
|
3619
|
+
assert.calledWith(
|
|
3620
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3621
|
+
track
|
|
3622
|
+
);
|
|
3623
|
+
assert.equal(meeting.mediaProperties.videoTrack, track);
|
|
3624
|
+
// check that sendVideo hasn't been touched
|
|
3625
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
3626
|
+
};
|
|
3627
|
+
|
|
3628
|
+
const checkScreenShareVideoPublished = (track) => {
|
|
3629
|
+
assert.calledOnce(meeting.requestScreenShareFloor);
|
|
3630
|
+
|
|
3631
|
+
assert.calledWith(
|
|
3632
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3633
|
+
track
|
|
3634
|
+
);
|
|
3635
|
+
assert.equal(meeting.mediaProperties.shareTrack, track);
|
|
3636
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
|
3637
|
+
};
|
|
3638
|
+
|
|
3639
|
+
it('requests screen share floor and publishes the screen share video track', async () => {
|
|
3640
|
+
await meeting.publishTracks({screenShare: {video: videoShareTrack}});
|
|
3641
|
+
|
|
3642
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3643
|
+
checkScreenShareVideoPublished(videoShareTrack);
|
|
3644
|
+
});
|
|
3645
|
+
|
|
3646
|
+
it('updates MuteState instance and publishes the track for main audio', async () => {
|
|
3647
|
+
await meeting.publishTracks({microphone: audioTrack});
|
|
3648
|
+
|
|
3649
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3650
|
+
checkAudioPublished(audioTrack);
|
|
3651
|
+
});
|
|
3652
|
+
|
|
3653
|
+
it('updates MuteState instance and publishes the track for main video', async () => {
|
|
3654
|
+
await meeting.publishTracks({camera: videoTrack});
|
|
3655
|
+
|
|
3656
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3657
|
+
checkVideoPublished(videoTrack);
|
|
3658
|
+
});
|
|
3659
|
+
|
|
3660
|
+
it('publishes audio, video and screen share together', async () => {
|
|
3661
|
+
await meeting.publishTracks({
|
|
3662
|
+
microphone: audioTrack,
|
|
3663
|
+
camera: videoTrack,
|
|
3664
|
+
screenShare: {
|
|
3665
|
+
video: videoShareTrack,
|
|
3666
|
+
},
|
|
3667
|
+
});
|
|
3668
|
+
|
|
3669
|
+
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3670
|
+
checkAudioPublished(audioTrack);
|
|
3671
|
+
checkVideoPublished(videoTrack);
|
|
3672
|
+
checkScreenShareVideoPublished(videoShareTrack);
|
|
3673
|
+
});
|
|
3674
|
+
});
|
|
3675
|
+
it('creates instance and publishes with annotation info', async () => {
|
|
3676
|
+
const annotationInfo = {
|
|
3677
|
+
version: '1',
|
|
3678
|
+
policy: ANNOTATION_POLICY.APPROVAL,
|
|
3679
|
+
};
|
|
3680
|
+
await meeting.publishTracks({annotationInfo});
|
|
3681
|
+
assert.equal(meeting.annotationInfo, annotationInfo);
|
|
3682
|
+
});
|
|
3683
|
+
|
|
3684
|
+
describe('unpublishTracks', () => {
|
|
3685
|
+
beforeEach(async () => {
|
|
3686
|
+
await meeting.publishTracks({
|
|
3687
|
+
microphone: audioTrack,
|
|
3688
|
+
camera: videoTrack,
|
|
3689
|
+
screenShare: {video: videoShareTrack},
|
|
3690
|
+
});
|
|
3691
|
+
});
|
|
3692
|
+
|
|
3693
|
+
const checkAudioUnpublished = () => {
|
|
3694
|
+
assert.calledWith(
|
|
3695
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3696
|
+
audioTrack
|
|
3697
|
+
);
|
|
3698
|
+
|
|
3699
|
+
assert.equal(meeting.mediaProperties.audioTrack, null);
|
|
3700
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
3701
|
+
};
|
|
3702
|
+
|
|
3703
|
+
const checkVideoUnpublished = () => {
|
|
3704
|
+
assert.calledWith(
|
|
3705
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3706
|
+
videoTrack
|
|
3707
|
+
);
|
|
3708
|
+
|
|
3709
|
+
assert.equal(meeting.mediaProperties.videoTrack, null);
|
|
3710
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
3711
|
+
};
|
|
3712
|
+
|
|
3713
|
+
const checkScreenShareVideoUnpublished = () => {
|
|
3714
|
+
assert.calledWith(
|
|
3715
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3716
|
+
videoShareTrack
|
|
3717
|
+
);
|
|
3718
|
+
|
|
3719
|
+
assert.calledOnce(meeting.requestScreenShareFloor);
|
|
3720
|
+
|
|
3721
|
+
assert.equal(meeting.mediaProperties.shareTrack, null);
|
|
3722
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, false);
|
|
3723
|
+
};
|
|
3724
|
+
|
|
3725
|
+
it('fails if there is no media connection', async () => {
|
|
3726
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3727
|
+
await assert.isRejected(
|
|
3728
|
+
meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack])
|
|
3729
|
+
);
|
|
3730
|
+
});
|
|
3731
|
+
|
|
3732
|
+
it('un-publishes the tracks correctly (all 3 together)', async () => {
|
|
3733
|
+
await meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack]);
|
|
3734
|
+
|
|
3735
|
+
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3736
|
+
checkAudioUnpublished();
|
|
3737
|
+
checkVideoUnpublished();
|
|
3738
|
+
checkScreenShareVideoUnpublished();
|
|
3739
|
+
});
|
|
3740
|
+
|
|
3741
|
+
it('un-publishes the audio track correctly', async () => {
|
|
3742
|
+
await meeting.unpublishTracks([audioTrack]);
|
|
3743
|
+
|
|
3744
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3745
|
+
checkAudioUnpublished();
|
|
3746
|
+
});
|
|
3747
|
+
|
|
3748
|
+
it('un-publishes the video track correctly', async () => {
|
|
3749
|
+
await meeting.unpublishTracks([videoTrack]);
|
|
3750
|
+
|
|
3751
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3752
|
+
checkVideoUnpublished();
|
|
3753
|
+
});
|
|
3754
|
+
|
|
3755
|
+
it('un-publishes the screen share video track correctly', async () => {
|
|
3756
|
+
await meeting.unpublishTracks([videoShareTrack]);
|
|
3757
|
+
|
|
3758
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3759
|
+
checkScreenShareVideoUnpublished();
|
|
3760
|
+
});
|
|
3761
|
+
});
|
|
3762
|
+
});
|
|
3763
|
+
});
|
|
3764
|
+
|
|
3765
|
+
describe('#enableMusicMode', () => {
|
|
3766
|
+
beforeEach(() => {
|
|
3767
|
+
meeting.isMultistream = true;
|
|
3768
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
3769
|
+
setCodecParameters: sinon.stub().resolves({}),
|
|
3770
|
+
deleteCodecParameters: sinon.stub().resolves({}),
|
|
3771
|
+
};
|
|
3772
|
+
});
|
|
3773
|
+
[
|
|
3774
|
+
{shouldEnableMusicMode: true},
|
|
3775
|
+
{shouldEnableMusicMode: false},
|
|
3776
|
+
].forEach(({shouldEnableMusicMode}) => {
|
|
3777
|
+
it(`fails if there is no media connection for shouldEnableMusicMode: ${shouldEnableMusicMode}`, async () => {
|
|
3778
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3779
|
+
await assert.isRejected(meeting.enableMusicMode(shouldEnableMusicMode));
|
|
3780
|
+
});
|
|
3781
|
+
});
|
|
3782
|
+
|
|
3783
|
+
it('should set the codec parameters when shouldEnableMusicMode is true', async () => {
|
|
3784
|
+
await meeting.enableMusicMode(true);
|
|
3785
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.webrtcMediaConnection.setCodecParameters, MediaType.AudioMain, {
|
|
3786
|
+
maxaveragebitrate: '64000',
|
|
3787
|
+
maxplaybackrate: '48000',
|
|
3788
|
+
});
|
|
3789
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters);
|
|
3790
|
+
});
|
|
3791
|
+
|
|
3792
|
+
it('should set the codec parameters when shouldEnableMusicMode is false', async () => {
|
|
3793
|
+
await meeting.enableMusicMode(false);
|
|
3794
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters, MediaType.AudioMain, [
|
|
3795
|
+
'maxaveragebitrate',
|
|
3796
|
+
'maxplaybackrate',
|
|
3797
|
+
]);
|
|
3798
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.setCodecParameters);
|
|
3799
|
+
});
|
|
3307
3800
|
});
|
|
3308
3801
|
|
|
3309
3802
|
describe('Public Event Triggers', () => {
|
|
@@ -3431,88 +3924,19 @@ describe('plugin-meetings', () => {
|
|
|
3431
3924
|
{type: 'remoteAudio'}
|
|
3432
3925
|
);
|
|
3433
3926
|
assert.calledWith(
|
|
3434
|
-
TriggerProxy.trigger,
|
|
3435
|
-
sinon.match.instanceOf(Meeting),
|
|
3436
|
-
{file: 'meeting/index', function: 'closeRemoteTracks'},
|
|
3437
|
-
'media:stopped',
|
|
3438
|
-
{type: 'remoteVideo'}
|
|
3439
|
-
);
|
|
3440
|
-
assert.calledWith(
|
|
3441
|
-
TriggerProxy.trigger,
|
|
3442
|
-
sinon.match.instanceOf(Meeting),
|
|
3443
|
-
{file: 'meeting/index', function: 'closeRemoteTracks'},
|
|
3444
|
-
'media:stopped',
|
|
3445
|
-
{type: 'remoteShare'}
|
|
3446
|
-
);
|
|
3447
|
-
});
|
|
3448
|
-
});
|
|
3449
|
-
describe('#closeLocalShare', () => {
|
|
3450
|
-
it('should stop the stream, and trigger a media:stopped event when the local share stream stops', async () => {
|
|
3451
|
-
await meeting.closeLocalShare();
|
|
3452
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
3453
|
-
|
|
3454
|
-
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:stopped');
|
|
3455
|
-
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {type: 'localShare'});
|
|
3456
|
-
});
|
|
3457
|
-
});
|
|
3458
|
-
describe('#closeLocalStream', () => {
|
|
3459
|
-
it('should stop the stream, and trigger a media:stopped event when the local stream stops', async () => {
|
|
3460
|
-
await meeting.closeLocalStream();
|
|
3461
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
3462
|
-
assert.calledWith(
|
|
3463
|
-
TriggerProxy.trigger,
|
|
3464
|
-
sinon.match.instanceOf(Meeting),
|
|
3465
|
-
{file: 'meeting/index', function: 'closeLocalStream'},
|
|
3466
|
-
'media:stopped',
|
|
3467
|
-
{type: 'local'}
|
|
3468
|
-
);
|
|
3469
|
-
});
|
|
3470
|
-
});
|
|
3471
|
-
describe('#setLocalTracks', () => {
|
|
3472
|
-
it('stores the current video device as the preferred video device', () => {
|
|
3473
|
-
const videoDevice = 'video1';
|
|
3474
|
-
const fakeTrack = {getSettings: () => ({deviceId: videoDevice})};
|
|
3475
|
-
const fakeStream = 'stream1';
|
|
3476
|
-
|
|
3477
|
-
sandbox.stub(MeetingUtil, 'getTrack').returns({audioTrack: null, videoTrack: fakeTrack});
|
|
3478
|
-
sandbox.stub(meeting.mediaProperties, 'setMediaSettings');
|
|
3479
|
-
sandbox.stub(meeting.mediaProperties, 'setVideoDeviceId');
|
|
3480
|
-
|
|
3481
|
-
meeting.setLocalTracks(fakeStream);
|
|
3482
|
-
|
|
3483
|
-
assert.calledWith(meeting.mediaProperties.setVideoDeviceId, videoDevice);
|
|
3484
|
-
});
|
|
3485
|
-
});
|
|
3486
|
-
describe('#setLocalShareTrack', () => {
|
|
3487
|
-
it('should trigger a media:ready event with local share stream', () => {
|
|
3488
|
-
const track = {
|
|
3489
|
-
getSettings: sinon.stub().returns({
|
|
3490
|
-
aspectRatio: '1.7',
|
|
3491
|
-
frameRate: 30,
|
|
3492
|
-
height: 1980,
|
|
3493
|
-
width: 1080,
|
|
3494
|
-
displaySurface: true,
|
|
3495
|
-
cursor: true,
|
|
3496
|
-
}),
|
|
3497
|
-
};
|
|
3498
|
-
const getVideoTracks = sinon.stub().returns([track]);
|
|
3499
|
-
|
|
3500
|
-
meeting.mediaProperties.setLocalShareTrack = sinon.stub().returns(true);
|
|
3501
|
-
meeting.mediaProperties.shareTrack = {getVideoTracks, getSettings: track.getSettings};
|
|
3502
|
-
meeting.stopShare = sinon.stub().resolves(true);
|
|
3503
|
-
meeting.mediaProperties.mediaDirection = {};
|
|
3504
|
-
meeting.setLocalShareTrack(test1);
|
|
3505
|
-
assert.calledTwice(TriggerProxy.trigger);
|
|
3927
|
+
TriggerProxy.trigger,
|
|
3928
|
+
sinon.match.instanceOf(Meeting),
|
|
3929
|
+
{file: 'meeting/index', function: 'closeRemoteTracks'},
|
|
3930
|
+
'media:stopped',
|
|
3931
|
+
{type: 'remoteVideo'}
|
|
3932
|
+
);
|
|
3506
3933
|
assert.calledWith(
|
|
3507
3934
|
TriggerProxy.trigger,
|
|
3508
3935
|
sinon.match.instanceOf(Meeting),
|
|
3509
|
-
{file: 'meeting/index', function: '
|
|
3510
|
-
'media:
|
|
3936
|
+
{file: 'meeting/index', function: 'closeRemoteTracks'},
|
|
3937
|
+
'media:stopped',
|
|
3938
|
+
{type: 'remoteShare'}
|
|
3511
3939
|
);
|
|
3512
|
-
assert.calledOnce(meeting.mediaProperties.setLocalShareTrack);
|
|
3513
|
-
assert.equal(meeting.mediaProperties.localStream, undefined);
|
|
3514
|
-
meeting.mediaProperties.shareTrack.onended();
|
|
3515
|
-
assert.calledOnce(meeting.stopShare);
|
|
3516
3940
|
});
|
|
3517
3941
|
});
|
|
3518
3942
|
describe('#setupMediaConnectionListeners', () => {
|
|
@@ -3527,48 +3951,49 @@ describe('plugin-meetings', () => {
|
|
|
3527
3951
|
eventListeners[event] = listener;
|
|
3528
3952
|
}),
|
|
3529
3953
|
};
|
|
3954
|
+
MediaUtil.createMediaStream.returns({id: 'stream'});
|
|
3530
3955
|
});
|
|
3531
3956
|
|
|
3532
3957
|
it('should register for all the correct RoapMediaConnection events', () => {
|
|
3533
3958
|
meeting.setupMediaConnectionListeners();
|
|
3534
|
-
assert.isFunction(eventListeners[
|
|
3535
|
-
assert.isFunction(eventListeners[
|
|
3536
|
-
assert.isFunction(eventListeners[
|
|
3537
|
-
assert.isFunction(eventListeners[
|
|
3538
|
-
assert.isFunction(eventListeners[
|
|
3539
|
-
assert.isFunction(eventListeners[
|
|
3959
|
+
assert.isFunction(eventListeners[Event.ROAP_STARTED]);
|
|
3960
|
+
assert.isFunction(eventListeners[Event.ROAP_DONE]);
|
|
3961
|
+
assert.isFunction(eventListeners[Event.ROAP_FAILURE]);
|
|
3962
|
+
assert.isFunction(eventListeners[Event.ROAP_MESSAGE_TO_SEND]);
|
|
3963
|
+
assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
|
|
3964
|
+
assert.isFunction(eventListeners[Event.CONNECTION_STATE_CHANGED]);
|
|
3540
3965
|
});
|
|
3541
3966
|
|
|
3542
3967
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
3543
3968
|
meeting.setupMediaConnectionListeners();
|
|
3544
|
-
eventListeners[
|
|
3969
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3545
3970
|
track: 'track',
|
|
3546
|
-
type:
|
|
3971
|
+
type: RemoteTrackType.AUDIO,
|
|
3547
3972
|
});
|
|
3548
3973
|
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
|
|
3549
3974
|
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {
|
|
3550
3975
|
type: 'remoteAudio',
|
|
3551
|
-
stream:
|
|
3976
|
+
stream: {id: 'stream'},
|
|
3552
3977
|
});
|
|
3553
3978
|
|
|
3554
|
-
eventListeners[
|
|
3979
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3555
3980
|
track: 'track',
|
|
3556
|
-
type:
|
|
3981
|
+
type: RemoteTrackType.VIDEO,
|
|
3557
3982
|
});
|
|
3558
3983
|
assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
|
|
3559
3984
|
assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {
|
|
3560
3985
|
type: 'remoteVideo',
|
|
3561
|
-
stream:
|
|
3986
|
+
stream: {id: 'stream'},
|
|
3562
3987
|
});
|
|
3563
3988
|
|
|
3564
|
-
eventListeners[
|
|
3989
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3565
3990
|
track: 'track',
|
|
3566
|
-
type:
|
|
3991
|
+
type: RemoteTrackType.SCREENSHARE_VIDEO,
|
|
3567
3992
|
});
|
|
3568
3993
|
assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
|
|
3569
3994
|
assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {
|
|
3570
3995
|
type: 'remoteShare',
|
|
3571
|
-
stream:
|
|
3996
|
+
stream: {id: 'stream'},
|
|
3572
3997
|
});
|
|
3573
3998
|
});
|
|
3574
3999
|
|
|
@@ -3613,51 +4038,51 @@ describe('plugin-meetings', () => {
|
|
|
3613
4038
|
};
|
|
3614
4039
|
|
|
3615
4040
|
it('should send metrics for SdpOfferCreationError error', () => {
|
|
3616
|
-
const fakeError = new
|
|
4041
|
+
const fakeError = new Errors.SdpOfferCreationError(fakeErrorMessage, {
|
|
3617
4042
|
name: fakeErrorName,
|
|
3618
4043
|
cause: {name: fakeRootCauseName},
|
|
3619
4044
|
});
|
|
3620
4045
|
|
|
3621
|
-
eventListeners[
|
|
4046
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3622
4047
|
|
|
3623
4048
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3624
4049
|
checkBehavioralMetricSent(
|
|
3625
4050
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3626
|
-
|
|
4051
|
+
Errors.ErrorCode.SdpOfferCreationError,
|
|
3627
4052
|
fakeErrorMessage,
|
|
3628
4053
|
fakeRootCauseName
|
|
3629
4054
|
);
|
|
3630
4055
|
});
|
|
3631
4056
|
|
|
3632
4057
|
it('should send metrics for SdpOfferHandlingError error', () => {
|
|
3633
|
-
const fakeError = new
|
|
4058
|
+
const fakeError = new Errors.SdpOfferHandlingError(fakeErrorMessage, {
|
|
3634
4059
|
name: fakeErrorName,
|
|
3635
4060
|
cause: {name: fakeRootCauseName},
|
|
3636
4061
|
});
|
|
3637
4062
|
|
|
3638
|
-
eventListeners[
|
|
4063
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3639
4064
|
|
|
3640
4065
|
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3641
4066
|
checkBehavioralMetricSent(
|
|
3642
4067
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3643
|
-
|
|
4068
|
+
Errors.ErrorCode.SdpOfferHandlingError,
|
|
3644
4069
|
fakeErrorMessage,
|
|
3645
4070
|
fakeRootCauseName
|
|
3646
4071
|
);
|
|
3647
4072
|
});
|
|
3648
4073
|
|
|
3649
4074
|
it('should send metrics for SdpAnswerHandlingError error', () => {
|
|
3650
|
-
const fakeError = new
|
|
4075
|
+
const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
|
|
3651
4076
|
name: fakeErrorName,
|
|
3652
4077
|
cause: {name: fakeRootCauseName},
|
|
3653
4078
|
});
|
|
3654
4079
|
|
|
3655
|
-
eventListeners[
|
|
4080
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3656
4081
|
|
|
3657
4082
|
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3658
4083
|
checkBehavioralMetricSent(
|
|
3659
4084
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3660
|
-
|
|
4085
|
+
Errors.ErrorCode.SdpAnswerHandlingError,
|
|
3661
4086
|
fakeErrorMessage,
|
|
3662
4087
|
fakeRootCauseName
|
|
3663
4088
|
);
|
|
@@ -3665,15 +4090,15 @@ describe('plugin-meetings', () => {
|
|
|
3665
4090
|
|
|
3666
4091
|
it('should send metrics for SdpError error', () => {
|
|
3667
4092
|
// SdpError is usually without a cause
|
|
3668
|
-
const fakeError = new
|
|
4093
|
+
const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
|
|
3669
4094
|
|
|
3670
|
-
eventListeners[
|
|
4095
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3671
4096
|
|
|
3672
4097
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3673
4098
|
// expectedMetadataType is the error name in this case
|
|
3674
4099
|
checkBehavioralMetricSent(
|
|
3675
4100
|
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
3676
|
-
|
|
4101
|
+
Errors.ErrorCode.SdpError,
|
|
3677
4102
|
fakeErrorMessage,
|
|
3678
4103
|
fakeErrorName
|
|
3679
4104
|
);
|
|
@@ -3681,24 +4106,24 @@ describe('plugin-meetings', () => {
|
|
|
3681
4106
|
|
|
3682
4107
|
it('should send metrics for IceGatheringError error', () => {
|
|
3683
4108
|
// IceGatheringError is usually without a cause
|
|
3684
|
-
const fakeError = new
|
|
4109
|
+
const fakeError = new Errors.IceGatheringError(fakeErrorMessage, {
|
|
3685
4110
|
name: fakeErrorName,
|
|
3686
4111
|
});
|
|
3687
4112
|
|
|
3688
|
-
eventListeners[
|
|
4113
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3689
4114
|
|
|
3690
4115
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3691
4116
|
// expectedMetadataType is the error name in this case
|
|
3692
4117
|
checkBehavioralMetricSent(
|
|
3693
4118
|
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
3694
|
-
|
|
4119
|
+
Errors.ErrorCode.IceGatheringError,
|
|
3695
4120
|
fakeErrorMessage,
|
|
3696
4121
|
fakeErrorName
|
|
3697
4122
|
);
|
|
3698
4123
|
});
|
|
3699
4124
|
});
|
|
3700
4125
|
|
|
3701
|
-
describe('handles
|
|
4126
|
+
describe('handles Event.ROAP_MESSAGE_TO_SEND correctly', () => {
|
|
3702
4127
|
let sendRoapOKStub;
|
|
3703
4128
|
let sendRoapMediaRequestStub;
|
|
3704
4129
|
let sendRoapAnswerStub;
|
|
@@ -3716,7 +4141,7 @@ describe('plugin-meetings', () => {
|
|
|
3716
4141
|
});
|
|
3717
4142
|
|
|
3718
4143
|
it('handles OK message correctly', () => {
|
|
3719
|
-
eventListeners[
|
|
4144
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3720
4145
|
roapMessage: {messageType: 'OK', seq: 1},
|
|
3721
4146
|
});
|
|
3722
4147
|
|
|
@@ -3735,7 +4160,7 @@ describe('plugin-meetings', () => {
|
|
|
3735
4160
|
});
|
|
3736
4161
|
|
|
3737
4162
|
it('handles OFFER message correctly', () => {
|
|
3738
|
-
eventListeners[
|
|
4163
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3739
4164
|
roapMessage: {
|
|
3740
4165
|
messageType: 'OFFER',
|
|
3741
4166
|
seq: 1,
|
|
@@ -3761,7 +4186,7 @@ describe('plugin-meetings', () => {
|
|
|
3761
4186
|
});
|
|
3762
4187
|
|
|
3763
4188
|
it('handles ANSWER message correctly', () => {
|
|
3764
|
-
eventListeners[
|
|
4189
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3765
4190
|
roapMessage: {
|
|
3766
4191
|
messageType: 'ANSWER',
|
|
3767
4192
|
seq: 10,
|
|
@@ -3788,7 +4213,7 @@ describe('plugin-meetings', () => {
|
|
|
3788
4213
|
it('sends metrics if fails to send roap ANSWER message', async () => {
|
|
3789
4214
|
sendRoapAnswerStub.rejects(new Error('sending answer failed'));
|
|
3790
4215
|
|
|
3791
|
-
await eventListeners[
|
|
4216
|
+
await eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3792
4217
|
roapMessage: {
|
|
3793
4218
|
messageType: 'ANSWER',
|
|
3794
4219
|
seq: 10,
|
|
@@ -3798,100 +4223,496 @@ describe('plugin-meetings', () => {
|
|
|
3798
4223
|
});
|
|
3799
4224
|
await testUtils.flushPromises();
|
|
3800
4225
|
|
|
3801
|
-
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3802
|
-
assert.calledWithMatch(
|
|
3803
|
-
Metrics.sendBehavioralMetric,
|
|
3804
|
-
BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE,
|
|
3805
|
-
{
|
|
3806
|
-
correlation_id: meeting.correlationId,
|
|
3807
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3808
|
-
reason: 'sending answer failed',
|
|
3809
|
-
}
|
|
3810
|
-
);
|
|
3811
|
-
});
|
|
4226
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
4227
|
+
assert.calledWithMatch(
|
|
4228
|
+
Metrics.sendBehavioralMetric,
|
|
4229
|
+
BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE,
|
|
4230
|
+
{
|
|
4231
|
+
correlation_id: meeting.correlationId,
|
|
4232
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
4233
|
+
reason: 'sending answer failed',
|
|
4234
|
+
}
|
|
4235
|
+
);
|
|
4236
|
+
});
|
|
4237
|
+
|
|
4238
|
+
[ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
|
|
4239
|
+
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
4240
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
4241
|
+
roapMessage: {
|
|
4242
|
+
messageType: 'ERROR',
|
|
4243
|
+
seq: 10,
|
|
4244
|
+
errorType,
|
|
4245
|
+
tieBreaker: 12345,
|
|
4246
|
+
},
|
|
4247
|
+
});
|
|
4248
|
+
|
|
4249
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
4250
|
+
assert.calledWithMatch(
|
|
4251
|
+
Metrics.sendBehavioralMetric,
|
|
4252
|
+
BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION,
|
|
4253
|
+
{
|
|
4254
|
+
correlation_id: meeting.correlationId,
|
|
4255
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
4256
|
+
sequence: 10,
|
|
4257
|
+
}
|
|
4258
|
+
);
|
|
4259
|
+
|
|
4260
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
4261
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
4262
|
+
seq: 10,
|
|
4263
|
+
errorType,
|
|
4264
|
+
mediaId: meeting.mediaId,
|
|
4265
|
+
correlationId: meeting.correlationId,
|
|
4266
|
+
});
|
|
4267
|
+
})
|
|
4268
|
+
);
|
|
4269
|
+
|
|
4270
|
+
it('handles ERROR message indicating other errors correctly', () => {
|
|
4271
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
4272
|
+
roapMessage: {
|
|
4273
|
+
messageType: 'ERROR',
|
|
4274
|
+
seq: 10,
|
|
4275
|
+
errorType: ErrorType.FAILED,
|
|
4276
|
+
tieBreaker: 12345,
|
|
4277
|
+
},
|
|
4278
|
+
});
|
|
4279
|
+
|
|
4280
|
+
assert.notCalled(Metrics.sendBehavioralMetric);
|
|
4281
|
+
|
|
4282
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
4283
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
4284
|
+
seq: 10,
|
|
4285
|
+
errorType: ErrorType.FAILED,
|
|
4286
|
+
mediaId: meeting.mediaId,
|
|
4287
|
+
correlationId: meeting.correlationId,
|
|
4288
|
+
});
|
|
4289
|
+
});
|
|
4290
|
+
});
|
|
4291
|
+
|
|
4292
|
+
describe('audio and video source count change events', () => {
|
|
4293
|
+
beforeEach(() => {
|
|
4294
|
+
TriggerProxy.trigger.resetHistory();
|
|
4295
|
+
meeting.setupMediaConnectionListeners();
|
|
4296
|
+
});
|
|
4297
|
+
|
|
4298
|
+
it('registers for audio and video source count changed', () => {
|
|
4299
|
+
assert.isFunction(eventListeners[Event.VIDEO_SOURCES_COUNT_CHANGED]);
|
|
4300
|
+
assert.isFunction(eventListeners[Event.AUDIO_SOURCES_COUNT_CHANGED]);
|
|
4301
|
+
})
|
|
4302
|
+
|
|
4303
|
+
it('forwards the VIDEO_SOURCES_COUNT_CHANGED event as "media:remoteVideoSourceCountChanged"', () => {
|
|
4304
|
+
const numTotalSources = 10;
|
|
4305
|
+
const numLiveSources = 6;
|
|
4306
|
+
const mediaContent = 'SLIDES';
|
|
4307
|
+
|
|
4308
|
+
sinon.stub(meeting.mediaRequestManagers.video, 'setNumCurrentSources');
|
|
4309
|
+
|
|
4310
|
+
eventListeners[Event.VIDEO_SOURCES_COUNT_CHANGED](numTotalSources, numLiveSources, mediaContent);
|
|
4311
|
+
|
|
4312
|
+
assert.calledOnceWithExactly(TriggerProxy.trigger,
|
|
4313
|
+
meeting,
|
|
4314
|
+
sinon.match.object,
|
|
4315
|
+
'media:remoteVideoSourceCountChanged',
|
|
4316
|
+
{
|
|
4317
|
+
numTotalSources,
|
|
4318
|
+
numLiveSources,
|
|
4319
|
+
mediaContent,
|
|
4320
|
+
}
|
|
4321
|
+
);
|
|
4322
|
+
});
|
|
4323
|
+
|
|
4324
|
+
it('forwards the AUDIO_SOURCES_COUNT_CHANGED event as "media:remoteAudioSourceCountChanged"', () => {
|
|
4325
|
+
const numTotalSources = 5;
|
|
4326
|
+
const numLiveSources = 2;
|
|
4327
|
+
const mediaContent = 'MAIN';
|
|
4328
|
+
|
|
4329
|
+
eventListeners[Event.AUDIO_SOURCES_COUNT_CHANGED](numTotalSources, numLiveSources, mediaContent);
|
|
4330
|
+
|
|
4331
|
+
assert.calledOnceWithExactly(TriggerProxy.trigger,
|
|
4332
|
+
meeting,
|
|
4333
|
+
sinon.match.object,
|
|
4334
|
+
'media:remoteAudioSourceCountChanged',
|
|
4335
|
+
{
|
|
4336
|
+
numTotalSources,
|
|
4337
|
+
numLiveSources,
|
|
4338
|
+
mediaContent,
|
|
4339
|
+
}
|
|
4340
|
+
);
|
|
4341
|
+
});
|
|
4342
|
+
|
|
4343
|
+
it('calls setNumCurrentSources() when receives VIDEO_SOURCES_COUNT_CHANGED event for MAIN', () => {
|
|
4344
|
+
const numTotalSources = 20;
|
|
4345
|
+
const numLiveSources = 10;
|
|
4346
|
+
|
|
4347
|
+
const setNumCurrentSourcesSpy = sinon.stub(meeting.mediaRequestManagers.video, 'setNumCurrentSources');
|
|
4348
|
+
|
|
4349
|
+
eventListeners[Event.VIDEO_SOURCES_COUNT_CHANGED](numTotalSources, numLiveSources, 'MAIN');
|
|
4350
|
+
|
|
4351
|
+
assert.calledOnceWithExactly(setNumCurrentSourcesSpy, numTotalSources, numLiveSources);
|
|
4352
|
+
});
|
|
4353
|
+
|
|
4354
|
+
it('does not call setNumCurrentSources() when receives VIDEO_SOURCES_COUNT_CHANGED event for SLIDES', () => {
|
|
4355
|
+
const numTotalSources = 20;
|
|
4356
|
+
const numLiveSources = 10;
|
|
4357
|
+
|
|
4358
|
+
const setNumCurrentSourcesSpy = sinon.stub(meeting.mediaRequestManagers.video, 'setNumCurrentSources');
|
|
4359
|
+
|
|
4360
|
+
eventListeners[Event.VIDEO_SOURCES_COUNT_CHANGED](numTotalSources, numLiveSources, 'SLIDES');
|
|
4361
|
+
|
|
4362
|
+
assert.notCalled(setNumCurrentSourcesSpy);
|
|
4363
|
+
});
|
|
4364
|
+
|
|
4365
|
+
})
|
|
4366
|
+
});
|
|
4367
|
+
describe('#setUpLocusInfoSelfListener', () => {
|
|
4368
|
+
it('listens to the self unadmitted guest event', (done) => {
|
|
4369
|
+
meeting.startKeepAlive = sinon.stub();
|
|
4370
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_UNADMITTED_GUEST', test1);
|
|
4371
|
+
assert.calledOnceWithExactly(meeting.startKeepAlive);
|
|
4372
|
+
assert.calledTwice(TriggerProxy.trigger);
|
|
4373
|
+
assert.calledWith(
|
|
4374
|
+
TriggerProxy.trigger,
|
|
4375
|
+
sinon.match.instanceOf(Meeting),
|
|
4376
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4377
|
+
'meeting:self:lobbyWaiting',
|
|
4378
|
+
{payload: test1}
|
|
4379
|
+
);
|
|
4380
|
+
done();
|
|
4381
|
+
});
|
|
4382
|
+
it('listens to the self admitted guest event', (done) => {
|
|
4383
|
+
meeting.stopKeepAlive = sinon.stub();
|
|
4384
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
4385
|
+
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
4386
|
+
assert.calledTwice(TriggerProxy.trigger);
|
|
4387
|
+
assert.calledWith(
|
|
4388
|
+
TriggerProxy.trigger,
|
|
4389
|
+
sinon.match.instanceOf(Meeting),
|
|
4390
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4391
|
+
'meeting:self:guestAdmitted',
|
|
4392
|
+
{payload: test1}
|
|
4393
|
+
);
|
|
4394
|
+
done();
|
|
4395
|
+
});
|
|
4396
|
+
|
|
4397
|
+
it('listens to the breakouts changed event', () => {
|
|
4398
|
+
meeting.breakouts.updateBreakoutSessions = sinon.stub();
|
|
4399
|
+
|
|
4400
|
+
const payload = 'payload';
|
|
4401
|
+
|
|
4402
|
+
meeting.locusInfo.emit(
|
|
4403
|
+
{function: 'test', file: 'test'},
|
|
4404
|
+
'SELF_MEETING_BREAKOUTS_CHANGED',
|
|
4405
|
+
payload
|
|
4406
|
+
);
|
|
4407
|
+
|
|
4408
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateBreakoutSessions, payload);
|
|
4409
|
+
assert.calledWith(
|
|
4410
|
+
TriggerProxy.trigger,
|
|
4411
|
+
meeting,
|
|
4412
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4413
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4414
|
+
);
|
|
4415
|
+
});
|
|
4416
|
+
|
|
4417
|
+
it('listens to the self roles changed event', () => {
|
|
4418
|
+
const payload = {oldRoles: [], newRoles: ['COHOST']};
|
|
4419
|
+
meeting.breakouts.updateCanManageBreakouts = sinon.stub();
|
|
4420
|
+
|
|
4421
|
+
meeting.locusInfo.emit(
|
|
4422
|
+
{function: 'test', file: 'test'},
|
|
4423
|
+
'SELF_ROLES_CHANGED',
|
|
4424
|
+
payload
|
|
4425
|
+
);
|
|
4426
|
+
|
|
4427
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateCanManageBreakouts, true);
|
|
4428
|
+
assert.calledWith(
|
|
4429
|
+
TriggerProxy.trigger,
|
|
4430
|
+
meeting,
|
|
4431
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4432
|
+
EVENT_TRIGGERS.MEETING_SELF_ROLES_CHANGED,
|
|
4433
|
+
{payload}
|
|
4434
|
+
);
|
|
4435
|
+
});
|
|
4436
|
+
});
|
|
4437
|
+
|
|
4438
|
+
describe('#setUpBreakoutsListener', () => {
|
|
4439
|
+
it('listens to the closing event from breakouts and triggers the closing event', () => {
|
|
4440
|
+
TriggerProxy.trigger.reset();
|
|
4441
|
+
meeting.breakouts.trigger('BREAKOUTS_CLOSING');
|
|
4442
|
+
|
|
4443
|
+
assert.calledWith(
|
|
4444
|
+
TriggerProxy.trigger,
|
|
4445
|
+
meeting,
|
|
4446
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4447
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
|
|
4448
|
+
);
|
|
4449
|
+
});
|
|
4450
|
+
|
|
4451
|
+
it('listens to the message event from breakouts and triggers the message event', () => {
|
|
4452
|
+
TriggerProxy.trigger.reset();
|
|
4453
|
+
|
|
4454
|
+
const messageEvent = 'message';
|
|
4455
|
+
|
|
4456
|
+
meeting.breakouts.trigger('MESSAGE', messageEvent);
|
|
4457
|
+
|
|
4458
|
+
assert.calledWith(
|
|
4459
|
+
TriggerProxy.trigger,
|
|
4460
|
+
meeting,
|
|
4461
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4462
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
|
|
4463
|
+
messageEvent
|
|
4464
|
+
);
|
|
4465
|
+
});
|
|
4466
|
+
|
|
4467
|
+
it('listens to the members update event from breakouts and triggers the breakouts update event', () => {
|
|
4468
|
+
TriggerProxy.trigger.reset();
|
|
4469
|
+
meeting.breakouts.trigger('MEMBERS_UPDATE');
|
|
4470
|
+
|
|
4471
|
+
assert.calledWith(
|
|
4472
|
+
TriggerProxy.trigger,
|
|
4473
|
+
meeting,
|
|
4474
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4475
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4476
|
+
);
|
|
4477
|
+
});
|
|
4478
|
+
|
|
4479
|
+
it('should not trigger ASK_RETURN_TO_MAIN before joined', () => {
|
|
4480
|
+
TriggerProxy.trigger.reset();
|
|
4481
|
+
meeting.joinedWith = {state: "NOT_JOINED"};
|
|
4482
|
+
meeting.breakouts.trigger('ASK_RETURN_TO_MAIN');
|
|
4483
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
4484
|
+
});
|
|
4485
|
+
|
|
4486
|
+
it('listens to the ask return to main event from breakouts and triggers the ask return to main event from meeting', () => {
|
|
4487
|
+
TriggerProxy.trigger.reset();
|
|
4488
|
+
meeting.joinedWith = {state: "JOINED"};
|
|
4489
|
+
meeting.breakouts.trigger('ASK_RETURN_TO_MAIN');
|
|
4490
|
+
assert.calledWith(
|
|
4491
|
+
TriggerProxy.trigger,
|
|
4492
|
+
meeting,
|
|
4493
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4494
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_RETURN_TO_MAIN
|
|
4495
|
+
);
|
|
4496
|
+
});
|
|
4497
|
+
|
|
4498
|
+
it('listens to the leave event from breakouts and triggers the breakout leave event', () => {
|
|
4499
|
+
TriggerProxy.trigger.reset();
|
|
4500
|
+
meeting.breakouts.trigger('LEAVE_BREAKOUT');
|
|
4501
|
+
assert.calledWith(
|
|
4502
|
+
TriggerProxy.trigger,
|
|
4503
|
+
meeting,
|
|
4504
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4505
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_LEAVE
|
|
4506
|
+
);
|
|
4507
|
+
});
|
|
4508
|
+
|
|
4509
|
+
it('listens to the breakout ask for help event and triggers the ask for help event', () => {
|
|
4510
|
+
TriggerProxy.trigger.reset();
|
|
4511
|
+
const helpEvent = {sessionId:'sessionId', participant: 'participant'}
|
|
4512
|
+
meeting.breakouts.trigger('ASK_FOR_HELP', helpEvent);
|
|
4513
|
+
assert.calledWith(
|
|
4514
|
+
TriggerProxy.trigger,
|
|
4515
|
+
meeting,
|
|
4516
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4517
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_FOR_HELP,
|
|
4518
|
+
helpEvent
|
|
4519
|
+
);
|
|
4520
|
+
});
|
|
4521
|
+
|
|
4522
|
+
it('listens to the preAssignments update event from breakouts and triggers the update event', () => {
|
|
4523
|
+
TriggerProxy.trigger.reset();
|
|
4524
|
+
meeting.breakouts.trigger('PRE_ASSIGNMENTS_UPDATE');
|
|
4525
|
+
|
|
4526
|
+
assert.calledWith(
|
|
4527
|
+
TriggerProxy.trigger,
|
|
4528
|
+
meeting,
|
|
4529
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4530
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE
|
|
4531
|
+
);
|
|
4532
|
+
});
|
|
4533
|
+
});
|
|
4534
|
+
|
|
4535
|
+
describe('#setupLocusControlsListener', () => {
|
|
4536
|
+
it('listens to the locus breakouts update event', () => {
|
|
4537
|
+
const locus = {
|
|
4538
|
+
breakout: 'breakout',
|
|
4539
|
+
};
|
|
4540
|
+
|
|
4541
|
+
meeting.breakouts.updateBreakout = sinon.stub();
|
|
4542
|
+
meeting.locusInfo.emit(
|
|
4543
|
+
{function: 'test', file: 'test'},
|
|
4544
|
+
'CONTROLS_MEETING_BREAKOUT_UPDATED',
|
|
4545
|
+
locus
|
|
4546
|
+
);
|
|
4547
|
+
|
|
4548
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateBreakout, locus.breakout);
|
|
4549
|
+
assert.calledWith(
|
|
4550
|
+
TriggerProxy.trigger,
|
|
4551
|
+
meeting,
|
|
4552
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4553
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4554
|
+
);
|
|
4555
|
+
});
|
|
4556
|
+
|
|
4557
|
+
it('listens to CONTROLS_MUTE_ON_ENTRY_CHANGED', async () => {
|
|
4558
|
+
const state = {example: 'value'}
|
|
4559
|
+
|
|
4560
|
+
await meeting.locusInfo.emitScoped(
|
|
4561
|
+
{function: 'test', file: 'test'},
|
|
4562
|
+
LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED,
|
|
4563
|
+
{state}
|
|
4564
|
+
);
|
|
4565
|
+
|
|
4566
|
+
assert.calledWith(
|
|
4567
|
+
TriggerProxy.trigger,
|
|
4568
|
+
meeting,
|
|
4569
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4570
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_MUTE_ON_ENTRY_UPDATED,
|
|
4571
|
+
{state},
|
|
4572
|
+
);
|
|
4573
|
+
});
|
|
4574
|
+
|
|
4575
|
+
it('listens to MEETING_CONTROLS_SHARE_CONTROL_UPDATED', async () => {
|
|
4576
|
+
const state = {example: 'value'}
|
|
4577
|
+
|
|
4578
|
+
await meeting.locusInfo.emitScoped(
|
|
4579
|
+
{function: 'test', file: 'test'},
|
|
4580
|
+
LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED,
|
|
4581
|
+
{state}
|
|
4582
|
+
);
|
|
4583
|
+
|
|
4584
|
+
assert.calledWith(
|
|
4585
|
+
TriggerProxy.trigger,
|
|
4586
|
+
meeting,
|
|
4587
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4588
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_SHARE_CONTROL_UPDATED,
|
|
4589
|
+
{state},
|
|
4590
|
+
);
|
|
4591
|
+
});
|
|
4592
|
+
|
|
4593
|
+
it('listens to MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED', async () => {
|
|
4594
|
+
const state = {example: 'value'}
|
|
4595
|
+
|
|
4596
|
+
await meeting.locusInfo.emitScoped(
|
|
4597
|
+
{function: 'test', file: 'test'},
|
|
4598
|
+
LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED,
|
|
4599
|
+
{state}
|
|
4600
|
+
);
|
|
4601
|
+
|
|
4602
|
+
assert.calledWith(
|
|
4603
|
+
TriggerProxy.trigger,
|
|
4604
|
+
meeting,
|
|
4605
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4606
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED,
|
|
4607
|
+
{state},
|
|
4608
|
+
);
|
|
4609
|
+
});
|
|
3812
4610
|
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
eventListeners[MC.Event.ROAP_MESSAGE_TO_SEND]({
|
|
3816
|
-
roapMessage: {
|
|
3817
|
-
messageType: 'ERROR',
|
|
3818
|
-
seq: 10,
|
|
3819
|
-
errorType,
|
|
3820
|
-
tieBreaker: 12345,
|
|
3821
|
-
},
|
|
3822
|
-
});
|
|
4611
|
+
it('listens to MEETING_CONTROLS_REACTIONS_UPDATED', async () => {
|
|
4612
|
+
const state = {example: 'value'}
|
|
3823
4613
|
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
correlation_id: meeting.correlationId,
|
|
3830
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3831
|
-
sequence: 10,
|
|
3832
|
-
}
|
|
3833
|
-
);
|
|
4614
|
+
await meeting.locusInfo.emitScoped(
|
|
4615
|
+
{function: 'test', file: 'test'},
|
|
4616
|
+
LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED,
|
|
4617
|
+
{state}
|
|
4618
|
+
);
|
|
3834
4619
|
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
});
|
|
3842
|
-
})
|
|
4620
|
+
assert.calledWith(
|
|
4621
|
+
TriggerProxy.trigger,
|
|
4622
|
+
meeting,
|
|
4623
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4624
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REACTIONS_UPDATED,
|
|
4625
|
+
{state},
|
|
3843
4626
|
);
|
|
4627
|
+
});
|
|
3844
4628
|
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
roapMessage: {
|
|
3848
|
-
messageType: 'ERROR',
|
|
3849
|
-
seq: 10,
|
|
3850
|
-
errorType: MC.ErrorType.FAILED,
|
|
3851
|
-
tieBreaker: 12345,
|
|
3852
|
-
},
|
|
3853
|
-
});
|
|
4629
|
+
it('listens to MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED', async () => {
|
|
4630
|
+
const state = {example: 'value'}
|
|
3854
4631
|
|
|
3855
|
-
|
|
4632
|
+
await meeting.locusInfo.emitScoped(
|
|
4633
|
+
{function: 'test', file: 'test'},
|
|
4634
|
+
LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED,
|
|
4635
|
+
{state}
|
|
4636
|
+
);
|
|
3856
4637
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
});
|
|
4638
|
+
assert.calledWith(
|
|
4639
|
+
TriggerProxy.trigger,
|
|
4640
|
+
meeting,
|
|
4641
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4642
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED,
|
|
4643
|
+
{state},
|
|
4644
|
+
);
|
|
3865
4645
|
});
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
meeting.locusInfo.
|
|
3871
|
-
|
|
3872
|
-
|
|
4646
|
+
|
|
4647
|
+
it('listens to MEETING_CONTROLS_RAISE_HAND_UPDATED', async () => {
|
|
4648
|
+
const state = {example: 'value'}
|
|
4649
|
+
|
|
4650
|
+
await meeting.locusInfo.emitScoped(
|
|
4651
|
+
{function: 'test', file: 'test'},
|
|
4652
|
+
LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED,
|
|
4653
|
+
{state}
|
|
4654
|
+
);
|
|
4655
|
+
|
|
3873
4656
|
assert.calledWith(
|
|
3874
4657
|
TriggerProxy.trigger,
|
|
3875
|
-
|
|
3876
|
-
{file: 'meeting/index', function: '
|
|
3877
|
-
|
|
3878
|
-
{
|
|
4658
|
+
meeting,
|
|
4659
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4660
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_RAISE_HAND_UPDATED,
|
|
4661
|
+
{state},
|
|
3879
4662
|
);
|
|
3880
|
-
done();
|
|
3881
4663
|
});
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4664
|
+
|
|
4665
|
+
it('listens to MEETING_CONTROLS_VIDEO_UPDATED', async () => {
|
|
4666
|
+
const state = {example: 'value'}
|
|
4667
|
+
|
|
4668
|
+
await meeting.locusInfo.emitScoped(
|
|
4669
|
+
{function: 'test', file: 'test'},
|
|
4670
|
+
LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED,
|
|
4671
|
+
{state}
|
|
4672
|
+
);
|
|
4673
|
+
|
|
3887
4674
|
assert.calledWith(
|
|
3888
4675
|
TriggerProxy.trigger,
|
|
3889
|
-
|
|
3890
|
-
{file: 'meeting/index', function: '
|
|
3891
|
-
|
|
3892
|
-
{
|
|
4676
|
+
meeting,
|
|
4677
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4678
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIDEO_UPDATED,
|
|
4679
|
+
{state},
|
|
3893
4680
|
);
|
|
3894
|
-
|
|
4681
|
+
});
|
|
4682
|
+
|
|
4683
|
+
it('listens to the timing that user joined into breakout', async () => {
|
|
4684
|
+
const mainLocusUrl = 'mainLocusUrl123';
|
|
4685
|
+
|
|
4686
|
+
meeting.meetingRequest.getLocusStatusByUrl = sinon.stub().returns(Promise.resolve());
|
|
4687
|
+
|
|
4688
|
+
await meeting.locusInfo.emit(
|
|
4689
|
+
{function: 'test', file: 'test'},
|
|
4690
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4691
|
+
{mainLocusUrl}
|
|
4692
|
+
);
|
|
4693
|
+
|
|
4694
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusStatusByUrl, mainLocusUrl);
|
|
4695
|
+
const error = {statusCode: 403};
|
|
4696
|
+
meeting.meetingRequest.getLocusStatusByUrl.rejects(error);
|
|
4697
|
+
meeting.locusInfo.clearMainSessionLocusCache = sinon.stub();
|
|
4698
|
+
await meeting.locusInfo.emit(
|
|
4699
|
+
{function: 'test', file: 'test'},
|
|
4700
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4701
|
+
{mainLocusUrl}
|
|
4702
|
+
);
|
|
4703
|
+
|
|
4704
|
+
assert.calledOnce(meeting.locusInfo.clearMainSessionLocusCache);
|
|
4705
|
+
|
|
4706
|
+
const otherError = new Error('something wrong');
|
|
4707
|
+
meeting.meetingRequest.getLocusStatusByUrl.rejects(otherError);
|
|
4708
|
+
meeting.locusInfo.clearMainSessionLocusCache = sinon.stub();
|
|
4709
|
+
await meeting.locusInfo.emit(
|
|
4710
|
+
{function: 'test', file: 'test'},
|
|
4711
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4712
|
+
{mainLocusUrl}
|
|
4713
|
+
);
|
|
4714
|
+
|
|
4715
|
+
assert.notCalled(meeting.locusInfo.clearMainSessionLocusCache);
|
|
3895
4716
|
});
|
|
3896
4717
|
});
|
|
3897
4718
|
|
|
@@ -3900,6 +4721,11 @@ describe('plugin-meetings', () => {
|
|
|
3900
4721
|
const newLocusUrl = 'newLocusUrl/12345';
|
|
3901
4722
|
|
|
3902
4723
|
meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
|
|
4724
|
+
meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
4725
|
+
meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
4726
|
+
|
|
4727
|
+
meeting.breakouts.locusUrlUpdate = sinon.stub();
|
|
4728
|
+
meeting.annotation.locusUrlUpdate = sinon.stub();
|
|
3903
4729
|
|
|
3904
4730
|
meeting.locusInfo.emit(
|
|
3905
4731
|
{function: 'test', file: 'test'},
|
|
@@ -3907,11 +4733,57 @@ describe('plugin-meetings', () => {
|
|
|
3907
4733
|
newLocusUrl
|
|
3908
4734
|
);
|
|
3909
4735
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
4736
|
+
assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
|
|
4737
|
+
assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
|
|
4738
|
+
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
4739
|
+
assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
|
|
4740
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
|
|
3910
4741
|
assert.equal(meeting.locusUrl, newLocusUrl);
|
|
3911
4742
|
assert(meeting.locusId, '12345');
|
|
3912
4743
|
done();
|
|
3913
4744
|
});
|
|
3914
4745
|
});
|
|
4746
|
+
|
|
4747
|
+
describe('#setUpLocusServicesListener', () => {
|
|
4748
|
+
it('listens to the locus services update event', (done) => {
|
|
4749
|
+
const newLocusServices = {
|
|
4750
|
+
services: {
|
|
4751
|
+
record: {
|
|
4752
|
+
url: 'url',
|
|
4753
|
+
},
|
|
4754
|
+
approval: {
|
|
4755
|
+
url: 'url',
|
|
4756
|
+
},
|
|
4757
|
+
},
|
|
4758
|
+
};
|
|
4759
|
+
|
|
4760
|
+
meeting.recordingController = {
|
|
4761
|
+
setServiceUrl: sinon.stub().returns(undefined),
|
|
4762
|
+
setSessionId: sinon.stub().returns(undefined),
|
|
4763
|
+
};
|
|
4764
|
+
meeting.annotation = {
|
|
4765
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
4766
|
+
};
|
|
4767
|
+
|
|
4768
|
+
meeting.locusInfo.emit(
|
|
4769
|
+
{function: 'test', file: 'test'},
|
|
4770
|
+
'LINKS_SERVICES',
|
|
4771
|
+
newLocusServices
|
|
4772
|
+
);
|
|
4773
|
+
|
|
4774
|
+
assert.calledWith(
|
|
4775
|
+
meeting.recordingController.setServiceUrl,
|
|
4776
|
+
newLocusServices.services.record.url,
|
|
4777
|
+
);
|
|
4778
|
+
assert.calledWith(
|
|
4779
|
+
meeting.annotation.approvalUrlUpdate,
|
|
4780
|
+
newLocusServices.services.approval.url,
|
|
4781
|
+
);
|
|
4782
|
+
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
4783
|
+
done();
|
|
4784
|
+
});
|
|
4785
|
+
});
|
|
4786
|
+
|
|
3915
4787
|
describe('#setUpLocusInfoMediaInactiveListener', () => {
|
|
3916
4788
|
it('listens to disconnect due to un activity ', (done) => {
|
|
3917
4789
|
TriggerProxy.trigger.reset();
|
|
@@ -4057,20 +4929,6 @@ describe('plugin-meetings', () => {
|
|
|
4057
4929
|
assert.calledOnce(meeting.mediaProperties.unsetRemoteTracks);
|
|
4058
4930
|
});
|
|
4059
4931
|
});
|
|
4060
|
-
describe('#unsetLocalVideoTrack', () => {
|
|
4061
|
-
it('should unset the local stream and return null', () => {
|
|
4062
|
-
meeting.mediaProperties.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
4063
|
-
meeting.unsetLocalVideoTrack();
|
|
4064
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalVideoTrack);
|
|
4065
|
-
});
|
|
4066
|
-
});
|
|
4067
|
-
describe('#unsetLocalShareTrack', () => {
|
|
4068
|
-
it('should unset the local share stream and return null', () => {
|
|
4069
|
-
meeting.mediaProperties.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
4070
|
-
meeting.unsetLocalShareTrack();
|
|
4071
|
-
assert.calledOnce(meeting.mediaProperties.unsetLocalShareTrack);
|
|
4072
|
-
});
|
|
4073
|
-
});
|
|
4074
4932
|
// TODO: remove
|
|
4075
4933
|
describe('#setMercuryListener', () => {
|
|
4076
4934
|
it('should listen to mercury events', () => {
|
|
@@ -4237,28 +5095,7 @@ describe('plugin-meetings', () => {
|
|
|
4237
5095
|
checkParseMeetingInfo(expectedInfoToParse);
|
|
4238
5096
|
});
|
|
4239
5097
|
});
|
|
4240
|
-
|
|
4241
|
-
describe('when CALL and participants', () => {
|
|
4242
|
-
beforeEach(() => {
|
|
4243
|
-
meeting.setLocus = sinon.stub().returns(true);
|
|
4244
|
-
MeetingUtil.getLocusPartner = sinon.stub().returns({person: {sipUrl: uuid3}});
|
|
4245
|
-
});
|
|
4246
|
-
it('should parse the locus object and set meeting properties and return null', () => {
|
|
4247
|
-
meeting.type = 'CALL';
|
|
4248
|
-
meeting.parseLocus({url: url1, participants: [{id: uuid1}], self: {id: uuid2}});
|
|
4249
|
-
assert.calledOnce(meeting.setLocus);
|
|
4250
|
-
assert.calledWith(meeting.setLocus, {
|
|
4251
|
-
url: url1,
|
|
4252
|
-
participants: [{id: uuid1}],
|
|
4253
|
-
self: {id: uuid2},
|
|
4254
|
-
});
|
|
4255
|
-
assert.calledOnce(MeetingUtil.getLocusPartner);
|
|
4256
|
-
assert.calledWith(MeetingUtil.getLocusPartner, [{id: uuid1}], {id: uuid2});
|
|
4257
|
-
assert.deepEqual(meeting.partner, {person: {sipUrl: uuid3}});
|
|
4258
|
-
assert.equal(meeting.sipUri, uuid3);
|
|
4259
|
-
});
|
|
4260
|
-
});
|
|
4261
|
-
});
|
|
5098
|
+
|
|
4262
5099
|
describe('#setCorrelationId', () => {
|
|
4263
5100
|
it('should set the correlationId and return undefined', () => {
|
|
4264
5101
|
assert.ok(meeting.correlationId);
|
|
@@ -4319,25 +5156,37 @@ describe('plugin-meetings', () => {
|
|
|
4319
5156
|
let inMeetingActionsSetSpy;
|
|
4320
5157
|
let canUserLockSpy;
|
|
4321
5158
|
let canUserUnlockSpy;
|
|
4322
|
-
let
|
|
5159
|
+
let canUserStartSpy;
|
|
4323
5160
|
let canUserStopSpy;
|
|
4324
5161
|
let canUserPauseSpy;
|
|
4325
5162
|
let canUserResumeSpy;
|
|
5163
|
+
let canSetMuteOnEntrySpy;
|
|
5164
|
+
let canUnsetMuteOnEntrySpy;
|
|
5165
|
+
let canSetDisallowUnmuteSpy;
|
|
5166
|
+
let canUnsetDisallowUnmuteSpy;
|
|
4326
5167
|
let canUserRaiseHandSpy;
|
|
4327
5168
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
4328
5169
|
let canUserLowerAllHandsSpy;
|
|
4329
5170
|
let canUserLowerSomeoneElsesHandSpy;
|
|
4330
5171
|
let waitingForOthersToJoinSpy;
|
|
4331
5172
|
let handleDataChannelUrlChangeSpy;
|
|
5173
|
+
let canSendReactionsSpy;
|
|
5174
|
+
let canUserRenameSelfAndObservedSpy;
|
|
5175
|
+
let canUserRenameOthersSpy;
|
|
5176
|
+
let hasHintsSpy;
|
|
4332
5177
|
|
|
4333
5178
|
beforeEach(() => {
|
|
4334
5179
|
locusInfoOnSpy = sinon.spy(meeting.locusInfo, 'on');
|
|
4335
5180
|
canUserLockSpy = sinon.spy(MeetingUtil, 'canUserLock');
|
|
4336
5181
|
canUserUnlockSpy = sinon.spy(MeetingUtil, 'canUserUnlock');
|
|
4337
|
-
|
|
4338
|
-
canUserStopSpy = sinon.spy(
|
|
4339
|
-
canUserPauseSpy = sinon.spy(
|
|
4340
|
-
canUserResumeSpy = sinon.spy(
|
|
5182
|
+
canUserStartSpy = sinon.spy(RecordingUtil, 'canUserStart');
|
|
5183
|
+
canUserStopSpy = sinon.spy(RecordingUtil, 'canUserStop');
|
|
5184
|
+
canUserPauseSpy = sinon.spy(RecordingUtil, 'canUserPause');
|
|
5185
|
+
canUserResumeSpy = sinon.spy(RecordingUtil, 'canUserResume');
|
|
5186
|
+
canSetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canSetMuteOnEntry');
|
|
5187
|
+
canUnsetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canUnsetMuteOnEntry');
|
|
5188
|
+
canSetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canSetDisallowUnmute');
|
|
5189
|
+
canUnsetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canUnsetDisallowUnmute');
|
|
4341
5190
|
inMeetingActionsSetSpy = sinon.spy(meeting.inMeetingActions, 'set');
|
|
4342
5191
|
canUserRaiseHandSpy = sinon.spy(MeetingUtil, 'canUserRaiseHand');
|
|
4343
5192
|
canUserLowerAllHandsSpy = sinon.spy(MeetingUtil, 'canUserLowerAllHands');
|
|
@@ -4348,6 +5197,9 @@ describe('plugin-meetings', () => {
|
|
|
4348
5197
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
4349
5198
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
4350
5199
|
handleDataChannelUrlChangeSpy = sinon.spy(meeting, 'handleDataChannelUrlChange');
|
|
5200
|
+
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
5201
|
+
canUserRenameSelfAndObservedSpy = sinon.spy(MeetingUtil, 'canUserRenameSelfAndObserved');
|
|
5202
|
+
canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
|
|
4351
5203
|
});
|
|
4352
5204
|
|
|
4353
5205
|
afterEach(() => {
|
|
@@ -4357,6 +5209,10 @@ describe('plugin-meetings', () => {
|
|
|
4357
5209
|
});
|
|
4358
5210
|
|
|
4359
5211
|
it('registers the correct MEETING_INFO_UPDATED event', () => {
|
|
5212
|
+
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
5213
|
+
const restorableHasHints = ControlsOptionsUtil.hasHints;
|
|
5214
|
+
ControlsOptionsUtil.hasHints = sinon.stub().returns(true);
|
|
5215
|
+
|
|
4360
5216
|
meeting.setUpLocusInfoMeetingInfoListener();
|
|
4361
5217
|
|
|
4362
5218
|
assert.calledThrice(locusInfoOnSpy);
|
|
@@ -4377,16 +5233,96 @@ describe('plugin-meetings', () => {
|
|
|
4377
5233
|
|
|
4378
5234
|
assert.calledWith(canUserLockSpy, payload.info.userDisplayHints);
|
|
4379
5235
|
assert.calledWith(canUserUnlockSpy, payload.info.userDisplayHints);
|
|
4380
|
-
assert.calledWith(
|
|
5236
|
+
assert.calledWith(canUserStartSpy, payload.info.userDisplayHints);
|
|
4381
5237
|
assert.calledWith(canUserStopSpy, payload.info.userDisplayHints);
|
|
4382
5238
|
assert.calledWith(canUserPauseSpy, payload.info.userDisplayHints);
|
|
4383
5239
|
assert.calledWith(canUserResumeSpy, payload.info.userDisplayHints);
|
|
5240
|
+
assert.calledWith(canSetMuteOnEntrySpy, payload.info.userDisplayHints);
|
|
5241
|
+
assert.calledWith(canUnsetMuteOnEntrySpy, payload.info.userDisplayHints);
|
|
5242
|
+
assert.calledWith(canSetDisallowUnmuteSpy, payload.info.userDisplayHints);
|
|
5243
|
+
assert.calledWith(canUnsetDisallowUnmuteSpy, payload.info.userDisplayHints);
|
|
4384
5244
|
assert.calledWith(canUserRaiseHandSpy, payload.info.userDisplayHints);
|
|
4385
5245
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, payload.info.userDisplayHints);
|
|
4386
5246
|
assert.calledWith(canUserLowerAllHandsSpy, payload.info.userDisplayHints);
|
|
4387
5247
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, payload.info.userDisplayHints);
|
|
4388
5248
|
assert.calledWith(waitingForOthersToJoinSpy, payload.info.userDisplayHints);
|
|
4389
5249
|
assert.calledWith(handleDataChannelUrlChangeSpy, payload.info.datachannelUrl);
|
|
5250
|
+
assert.calledWith(canSendReactionsSpy, null, payload.info.userDisplayHints);
|
|
5251
|
+
assert.calledWith(canUserRenameSelfAndObservedSpy, payload.info.userDisplayHints);
|
|
5252
|
+
assert.calledWith(canUserRenameOthersSpy, payload.info.userDisplayHints);
|
|
5253
|
+
|
|
5254
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5255
|
+
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
5256
|
+
displayHints: payload.info.userDisplayHints,
|
|
5257
|
+
});
|
|
5258
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5259
|
+
requiredHints: [DISPLAY_HINTS.UNMUTE_ALL],
|
|
5260
|
+
displayHints: payload.info.userDisplayHints,
|
|
5261
|
+
});
|
|
5262
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5263
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_HARD_MUTE],
|
|
5264
|
+
displayHints: payload.info.userDisplayHints,
|
|
5265
|
+
});
|
|
5266
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5267
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_HARD_MUTE],
|
|
5268
|
+
displayHints: payload.info.userDisplayHints,
|
|
5269
|
+
});
|
|
5270
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5271
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY],
|
|
5272
|
+
displayHints: payload.info.userDisplayHints,
|
|
5273
|
+
});
|
|
5274
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5275
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY],
|
|
5276
|
+
displayHints: payload.info.userDisplayHints,
|
|
5277
|
+
});
|
|
5278
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5279
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_REACTIONS],
|
|
5280
|
+
displayHints: payload.info.userDisplayHints,
|
|
5281
|
+
});
|
|
5282
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5283
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_REACTIONS],
|
|
5284
|
+
displayHints: payload.info.userDisplayHints,
|
|
5285
|
+
});
|
|
5286
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5287
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_DISPLAY_NAME],
|
|
5288
|
+
displayHints: payload.info.userDisplayHints,
|
|
5289
|
+
});
|
|
5290
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5291
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_DISPLAY_NAME],
|
|
5292
|
+
displayHints: payload.info.userDisplayHints,
|
|
5293
|
+
});
|
|
5294
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5295
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTROL],
|
|
5296
|
+
displayHints: payload.info.userDisplayHints,
|
|
5297
|
+
});
|
|
5298
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5299
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
5300
|
+
displayHints: payload.info.userDisplayHints,
|
|
5301
|
+
});
|
|
5302
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5303
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
5304
|
+
displayHints: payload.info.userDisplayHints,
|
|
5305
|
+
});
|
|
5306
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5307
|
+
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
|
|
5308
|
+
displayHints: payload.info.userDisplayHints,
|
|
5309
|
+
});
|
|
5310
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5311
|
+
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
5312
|
+
displayHints: payload.info.userDisplayHints,
|
|
5313
|
+
});
|
|
5314
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5315
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CAMERA],
|
|
5316
|
+
displayHints: payload.info.userDisplayHints,
|
|
5317
|
+
});
|
|
5318
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5319
|
+
requiredHints: [DISPLAY_HINTS.SHARE_DESKTOP],
|
|
5320
|
+
displayHints: payload.info.userDisplayHints,
|
|
5321
|
+
});
|
|
5322
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5323
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTENT],
|
|
5324
|
+
displayHints: payload.info.userDisplayHints,
|
|
5325
|
+
});
|
|
4390
5326
|
|
|
4391
5327
|
assert.calledWith(
|
|
4392
5328
|
TriggerProxy.trigger,
|
|
@@ -4404,6 +5340,8 @@ describe('plugin-meetings', () => {
|
|
|
4404
5340
|
callback(payload);
|
|
4405
5341
|
|
|
4406
5342
|
assert.notCalled(TriggerProxy.trigger);
|
|
5343
|
+
|
|
5344
|
+
ControlsOptionsUtil.hasHints = restorableHasHints;
|
|
4407
5345
|
});
|
|
4408
5346
|
});
|
|
4409
5347
|
|
|
@@ -4451,6 +5389,9 @@ describe('plugin-meetings', () => {
|
|
|
4451
5389
|
.stub()
|
|
4452
5390
|
.returns(Promise.resolve('something'));
|
|
4453
5391
|
webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
|
|
5392
|
+
meeting.webex.internal.llm.on = sinon.stub();
|
|
5393
|
+
meeting.webex.internal.llm.off = sinon.stub();
|
|
5394
|
+
meeting.processRelayEvent = sinon.stub();
|
|
4454
5395
|
});
|
|
4455
5396
|
|
|
4456
5397
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -4464,6 +5405,7 @@ describe('plugin-meetings', () => {
|
|
|
4464
5405
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4465
5406
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4466
5407
|
assert.equal(result, undefined);
|
|
5408
|
+
assert.notCalled(meeting.webex.internal.llm.on);
|
|
4467
5409
|
});
|
|
4468
5410
|
|
|
4469
5411
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
@@ -4478,17 +5420,29 @@ describe('plugin-meetings', () => {
|
|
|
4478
5420
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4479
5421
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4480
5422
|
assert.equal(result, undefined);
|
|
5423
|
+
assert.notCalled(meeting.webex.internal.llm.on);
|
|
4481
5424
|
});
|
|
4482
5425
|
|
|
4483
5426
|
it('connects if not already connected', async () => {
|
|
4484
5427
|
meeting.joinedWith = {state: 'JOINED'};
|
|
4485
5428
|
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4486
5429
|
|
|
5430
|
+
|
|
4487
5431
|
const result = await meeting.updateLLMConnection();
|
|
4488
5432
|
|
|
4489
5433
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4490
5434
|
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a datachannel url');
|
|
4491
5435
|
assert.equal(result, 'something');
|
|
5436
|
+
assert.calledOnceWithExactly(
|
|
5437
|
+
meeting.webex.internal.llm.off,
|
|
5438
|
+
'event:relay.event',
|
|
5439
|
+
meeting.processRelayEvent
|
|
5440
|
+
);
|
|
5441
|
+
assert.calledOnceWithExactly(
|
|
5442
|
+
meeting.webex.internal.llm.on,
|
|
5443
|
+
'event:relay.event',
|
|
5444
|
+
meeting.processRelayEvent
|
|
5445
|
+
);
|
|
4492
5446
|
});
|
|
4493
5447
|
|
|
4494
5448
|
it('disconnects if first if the locus url has changed', async () => {
|
|
@@ -4507,6 +5461,19 @@ describe('plugin-meetings', () => {
|
|
|
4507
5461
|
'a datachannel url'
|
|
4508
5462
|
);
|
|
4509
5463
|
assert.equal(result, 'something');
|
|
5464
|
+
assert.calledWithExactly(
|
|
5465
|
+
meeting.webex.internal.llm.off,
|
|
5466
|
+
'event:relay.event',
|
|
5467
|
+
meeting.processRelayEvent
|
|
5468
|
+
);
|
|
5469
|
+
assert.calledTwice(
|
|
5470
|
+
meeting.webex.internal.llm.off
|
|
5471
|
+
);
|
|
5472
|
+
assert.calledOnceWithExactly(
|
|
5473
|
+
meeting.webex.internal.llm.on,
|
|
5474
|
+
'event:relay.event',
|
|
5475
|
+
meeting.processRelayEvent
|
|
5476
|
+
);
|
|
4510
5477
|
});
|
|
4511
5478
|
|
|
4512
5479
|
it('disconnects when the state is not JOINED', async () => {
|
|
@@ -4521,15 +5488,22 @@ describe('plugin-meetings', () => {
|
|
|
4521
5488
|
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
4522
5489
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4523
5490
|
assert.equal(result, undefined);
|
|
5491
|
+
assert.calledOnceWithExactly(
|
|
5492
|
+
meeting.webex.internal.llm.off,
|
|
5493
|
+
'event:relay.event',
|
|
5494
|
+
meeting.processRelayEvent
|
|
5495
|
+
);
|
|
4524
5496
|
});
|
|
5497
|
+
|
|
4525
5498
|
});
|
|
4526
5499
|
|
|
4527
5500
|
describe('#setLocus', () => {
|
|
4528
5501
|
beforeEach(() => {
|
|
4529
5502
|
meeting.locusInfo.initialSetup = sinon.stub().returns(true);
|
|
4530
5503
|
});
|
|
5504
|
+
|
|
4531
5505
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
4532
|
-
meeting.
|
|
5506
|
+
meeting.setLocus({
|
|
4533
5507
|
mediaConnections: [test1],
|
|
4534
5508
|
locusUrl: url1,
|
|
4535
5509
|
locusId: uuid1,
|
|
@@ -4554,6 +5528,7 @@ describe('plugin-meetings', () => {
|
|
|
4554
5528
|
assert.equal(meeting.hostId, uuid4);
|
|
4555
5529
|
});
|
|
4556
5530
|
});
|
|
5531
|
+
|
|
4557
5532
|
describe('preferred video device', () => {
|
|
4558
5533
|
describe('#getVideoDeviceId', () => {
|
|
4559
5534
|
it('returns the preferred video device', () => {
|
|
@@ -4620,14 +5595,55 @@ describe('plugin-meetings', () => {
|
|
|
4620
5595
|
});
|
|
4621
5596
|
});
|
|
4622
5597
|
describe('share scenarios', () => {
|
|
5598
|
+
|
|
5599
|
+
describe('triggerAnnotationInfoEvent', () => {
|
|
5600
|
+
it('check triggerAnnotationInfoEvent event', () => {
|
|
5601
|
+
|
|
5602
|
+
TriggerProxy.trigger.reset();
|
|
5603
|
+
const annotationInfo= {version: '1', policy: 'Approval'}
|
|
5604
|
+
const expectAnnotationInfo = {annotationInfo,meetingId:meeting.id };
|
|
5605
|
+
meeting.webex.meetings ={}
|
|
5606
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{});
|
|
5607
|
+
assert.calledWith(
|
|
5608
|
+
TriggerProxy.trigger,
|
|
5609
|
+
{},
|
|
5610
|
+
{
|
|
5611
|
+
file: 'meeting/index',
|
|
5612
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5613
|
+
},
|
|
5614
|
+
'meeting:updateAnnotationInfo',
|
|
5615
|
+
expectAnnotationInfo
|
|
5616
|
+
);
|
|
5617
|
+
|
|
5618
|
+
TriggerProxy.trigger.reset();
|
|
5619
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{annotation:annotationInfo});
|
|
5620
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5621
|
+
|
|
5622
|
+
TriggerProxy.trigger.reset();
|
|
5623
|
+
const annotationInfoUpdate = {version: '1', policy: 'AnnotationNotAllowed'}
|
|
5624
|
+
const expectAnnotationInfoUpdated = { annotationInfo: annotationInfoUpdate, meetingId:meeting.id };
|
|
5625
|
+
meeting.triggerAnnotationInfoEvent({annotation: annotationInfoUpdate},{annotation:annotationInfo});
|
|
5626
|
+
assert.calledWith(
|
|
5627
|
+
TriggerProxy.trigger,
|
|
5628
|
+
{},
|
|
5629
|
+
{
|
|
5630
|
+
file: 'meeting/index',
|
|
5631
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5632
|
+
},
|
|
5633
|
+
'meeting:updateAnnotationInfo',
|
|
5634
|
+
expectAnnotationInfoUpdated
|
|
5635
|
+
);
|
|
5636
|
+
|
|
5637
|
+
TriggerProxy.trigger.reset();
|
|
5638
|
+
meeting.triggerAnnotationInfoEvent(null,{annotation:annotationInfoUpdate});
|
|
5639
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5640
|
+
|
|
5641
|
+
});
|
|
5642
|
+
});
|
|
5643
|
+
|
|
4623
5644
|
describe('setUpLocusMediaSharesListener', () => {
|
|
4624
5645
|
beforeEach(() => {
|
|
4625
5646
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
4626
|
-
sinon.stub(meeting, 'updateShare').returns(Promise.resolve());
|
|
4627
|
-
});
|
|
4628
|
-
|
|
4629
|
-
afterEach(() => {
|
|
4630
|
-
meeting.updateShare.restore();
|
|
4631
5647
|
});
|
|
4632
5648
|
|
|
4633
5649
|
const USER_IDS = {
|
|
@@ -4643,7 +5659,7 @@ describe('plugin-meetings', () => {
|
|
|
4643
5659
|
'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3',
|
|
4644
5660
|
};
|
|
4645
5661
|
|
|
4646
|
-
const generateContent = (beneficiaryId = null, disposition = null) => ({
|
|
5662
|
+
const generateContent = (beneficiaryId = null, disposition = null,annotation = undefined) => ({
|
|
4647
5663
|
beneficiaryId,
|
|
4648
5664
|
disposition,
|
|
4649
5665
|
});
|
|
@@ -4660,7 +5676,10 @@ describe('plugin-meetings', () => {
|
|
|
4660
5676
|
beneficiaryId,
|
|
4661
5677
|
resourceUrl,
|
|
4662
5678
|
isAccepting,
|
|
4663
|
-
otherBeneficiaryId
|
|
5679
|
+
otherBeneficiaryId,
|
|
5680
|
+
annotation,
|
|
5681
|
+
url,
|
|
5682
|
+
shareInstanceId
|
|
4664
5683
|
) => {
|
|
4665
5684
|
const newPayload = cloneDeep(payload);
|
|
4666
5685
|
|
|
@@ -4686,7 +5705,7 @@ describe('plugin-meetings', () => {
|
|
|
4686
5705
|
if (isGranting) {
|
|
4687
5706
|
if (isContent) {
|
|
4688
5707
|
activeSharingId.content = beneficiaryId;
|
|
4689
|
-
newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED);
|
|
5708
|
+
newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED,annotation);
|
|
4690
5709
|
|
|
4691
5710
|
if (isEqual(newPayload.current, newPayload.previous)) {
|
|
4692
5711
|
eventTrigger.member = null;
|
|
@@ -4740,7 +5759,7 @@ describe('plugin-meetings', () => {
|
|
|
4740
5759
|
eventTrigger.share.push({
|
|
4741
5760
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
4742
5761
|
functionName: 'remoteShare',
|
|
4743
|
-
eventPayload: {memberId: beneficiaryId},
|
|
5762
|
+
eventPayload: {memberId: beneficiaryId, url, shareInstanceId},
|
|
4744
5763
|
});
|
|
4745
5764
|
}
|
|
4746
5765
|
}
|
|
@@ -5312,7 +6331,6 @@ describe('plugin-meetings', () => {
|
|
|
5312
6331
|
payloadTestHelper([data1, data2, data3]);
|
|
5313
6332
|
});
|
|
5314
6333
|
});
|
|
5315
|
-
|
|
5316
6334
|
describe('Desktop A --> Desktop B', () => {
|
|
5317
6335
|
it('Scenario #1: you share desktop A and then share desktop B', () => {
|
|
5318
6336
|
const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
|
|
@@ -5529,7 +6547,7 @@ describe('plugin-meetings', () => {
|
|
|
5529
6547
|
it('should send reaction with the right data and return a promise', async () => {
|
|
5530
6548
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5531
6549
|
|
|
5532
|
-
const reactionPromise = meeting.sendReaction('
|
|
6550
|
+
const reactionPromise = meeting.sendReaction('thumb_down', 'light');
|
|
5533
6551
|
|
|
5534
6552
|
assert.exists(reactionPromise.then);
|
|
5535
6553
|
await reactionPromise;
|
|
@@ -5553,7 +6571,7 @@ describe('plugin-meetings', () => {
|
|
|
5553
6571
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: undefined}};
|
|
5554
6572
|
|
|
5555
6573
|
await assert.isRejected(
|
|
5556
|
-
meeting.sendReaction('
|
|
6574
|
+
meeting.sendReaction('thumb_down', 'light'),
|
|
5557
6575
|
Error,
|
|
5558
6576
|
'Error sending reaction, service url not found.'
|
|
5559
6577
|
);
|
|
@@ -5576,7 +6594,7 @@ describe('plugin-meetings', () => {
|
|
|
5576
6594
|
it('should send a reaction with default skin tone if provided skinToneType is invalid ', async () => {
|
|
5577
6595
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5578
6596
|
|
|
5579
|
-
const reactionPromise = meeting.sendReaction('
|
|
6597
|
+
const reactionPromise = meeting.sendReaction('thumb_down', 'invalid_skin_tone');
|
|
5580
6598
|
|
|
5581
6599
|
assert.exists(reactionPromise.then);
|
|
5582
6600
|
await reactionPromise;
|
|
@@ -5595,7 +6613,7 @@ describe('plugin-meetings', () => {
|
|
|
5595
6613
|
it('should send a reaction with default skin tone if none provided', async () => {
|
|
5596
6614
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5597
6615
|
|
|
5598
|
-
const reactionPromise = meeting.sendReaction('
|
|
6616
|
+
const reactionPromise = meeting.sendReaction('thumb_down');
|
|
5599
6617
|
|
|
5600
6618
|
assert.exists(reactionPromise.then);
|
|
5601
6619
|
await reactionPromise;
|
|
@@ -5674,6 +6692,109 @@ describe('plugin-meetings', () => {
|
|
|
5674
6692
|
});
|
|
5675
6693
|
});
|
|
5676
6694
|
});
|
|
6695
|
+
|
|
6696
|
+
describe('SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED locus event', () => {
|
|
6697
|
+
let spy;
|
|
6698
|
+
|
|
6699
|
+
beforeEach('setup sinon', () => {
|
|
6700
|
+
spy = sinon.spy();
|
|
6701
|
+
});
|
|
6702
|
+
|
|
6703
|
+
const testEmit = async (muted) => {
|
|
6704
|
+
await meeting.locusInfo.emitScoped(
|
|
6705
|
+
{},
|
|
6706
|
+
LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
|
|
6707
|
+
{
|
|
6708
|
+
muted,
|
|
6709
|
+
}
|
|
6710
|
+
);
|
|
6711
|
+
|
|
6712
|
+
assert.calledWith(
|
|
6713
|
+
TriggerProxy.trigger,
|
|
6714
|
+
sinon.match.instanceOf(Meeting),
|
|
6715
|
+
{
|
|
6716
|
+
file: 'meeting/index',
|
|
6717
|
+
function: 'setUpLocusInfoSelfListener',
|
|
6718
|
+
},
|
|
6719
|
+
muted
|
|
6720
|
+
? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
|
|
6721
|
+
: EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
|
|
6722
|
+
{
|
|
6723
|
+
payload: {
|
|
6724
|
+
muted,
|
|
6725
|
+
},
|
|
6726
|
+
}
|
|
6727
|
+
);
|
|
6728
|
+
};
|
|
6729
|
+
|
|
6730
|
+
it('emits the expected event when muted', async () => {
|
|
6731
|
+
await testEmit(true);
|
|
6732
|
+
});
|
|
6733
|
+
|
|
6734
|
+
it('emits the expected event when not muted', async () => {
|
|
6735
|
+
await testEmit(false);
|
|
6736
|
+
});
|
|
6737
|
+
});
|
|
6738
|
+
|
|
6739
|
+
describe('getAnalyzerMetricsPrePayload', () => {
|
|
6740
|
+
it('should have #getAnalyzerMetricsPrePayload', () => {
|
|
6741
|
+
assert.exists(meeting.getAnalyzerMetricsPrePayload);
|
|
6742
|
+
});
|
|
6743
|
+
|
|
6744
|
+
beforeEach(() => {
|
|
6745
|
+
meeting.meetingRequest.getAnalyzerMetricsPrePayload = sinon
|
|
6746
|
+
.stub()
|
|
6747
|
+
.returns(Promise.resolve());
|
|
6748
|
+
meeting.webex.internal = {services: {get: sinon.stub().returns('Locus URL')}};
|
|
6749
|
+
meeting.correlationId = 'correlation-id';
|
|
6750
|
+
});
|
|
6751
|
+
|
|
6752
|
+
it('it should include meetingLookupUrl if provided', () => {
|
|
6753
|
+
const res = meeting.getAnalyzerMetricsPrePayload({
|
|
6754
|
+
meetingLookupUrl: 'https://service-url.com',
|
|
6755
|
+
event: 'client.meetinginfo.response',
|
|
6756
|
+
});
|
|
6757
|
+
|
|
6758
|
+
assert.deepEqual(res.event, {
|
|
6759
|
+
canProceed: true,
|
|
6760
|
+
eventData: {
|
|
6761
|
+
webClientDomain: '',
|
|
6762
|
+
},
|
|
6763
|
+
identifiers: {
|
|
6764
|
+
correlationId: 'correlation-id',
|
|
6765
|
+
deviceId: uuid3,
|
|
6766
|
+
locusUrl: 'Locus URL',
|
|
6767
|
+
meetingLookupUrl: 'https://service-url.com',
|
|
6768
|
+
orgId: undefined,
|
|
6769
|
+
userId: uuid1,
|
|
6770
|
+
},
|
|
6771
|
+
name: 'client.meetinginfo.response',
|
|
6772
|
+
});
|
|
6773
|
+
|
|
6774
|
+
assert.deepEqual(res.origin, {
|
|
6775
|
+
channel: undefined,
|
|
6776
|
+
loginType: undefined,
|
|
6777
|
+
userType: undefined,
|
|
6778
|
+
clientInfo: {
|
|
6779
|
+
browser: '',
|
|
6780
|
+
browserVersion: '',
|
|
6781
|
+
clientType: undefined,
|
|
6782
|
+
clientVersion: 'webex-js-sdk/undefined',
|
|
6783
|
+
localNetworkPrefix: null,
|
|
6784
|
+
os: 'other',
|
|
6785
|
+
osVersion: getOSVersion() || 'unknown',
|
|
6786
|
+
subClientType: undefined,
|
|
6787
|
+
},
|
|
6788
|
+
name: 'endpoint',
|
|
6789
|
+
networkType: 'unknown',
|
|
6790
|
+
userAgent: 'webex-js-sdk/test-undefined client=undefined; (os=linux/5)',
|
|
6791
|
+
});
|
|
6792
|
+
|
|
6793
|
+
assert.deepEqual(res.senderCountryCode, undefined);
|
|
6794
|
+
assert.deepEqual(res.version, 1);
|
|
6795
|
+
|
|
6796
|
+
});
|
|
6797
|
+
});
|
|
5677
6798
|
});
|
|
5678
6799
|
});
|
|
5679
6800
|
});
|