@webex/plugin-meetings 3.0.0-beta.15 → 3.0.0-beta.151
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 +193 -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/browser-detection.js +1 -21
- package/dist/common/browser-detection.js.map +1 -1
- package/dist/common/collection.js +5 -20
- package/dist/common/collection.js.map +1 -1
- package/dist/common/config.js +0 -7
- package/dist/common/config.js.map +1 -1
- package/dist/common/errors/captcha-error.js +0 -21
- package/dist/common/errors/captcha-error.js.map +1 -1
- package/dist/common/errors/intent-to-join.js +0 -21
- package/dist/common/errors/intent-to-join.js.map +1 -1
- package/dist/common/errors/join-meeting.js +0 -21
- package/dist/common/errors/join-meeting.js.map +1 -1
- package/dist/common/errors/media.js +0 -21
- package/dist/common/errors/media.js.map +1 -1
- package/dist/common/errors/parameter.js +0 -28
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/common/errors/password-error.js +0 -21
- package/dist/common/errors/password-error.js.map +1 -1
- package/dist/common/errors/permission.js +0 -21
- package/dist/common/errors/permission.js.map +1 -1
- package/dist/common/errors/reconnection-in-progress.js +0 -17
- package/dist/common/errors/reconnection-in-progress.js.map +1 -1
- package/dist/common/errors/reconnection.js +0 -21
- package/dist/common/errors/reconnection.js.map +1 -1
- package/dist/common/errors/stats.js +0 -21
- package/dist/common/errors/stats.js.map +1 -1
- package/dist/common/errors/webex-errors.js +9 -43
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/errors/webex-meetings-error.js +1 -24
- package/dist/common/errors/webex-meetings-error.js.map +1 -1
- package/dist/common/events/events-scope.js +0 -22
- package/dist/common/events/events-scope.js.map +1 -1
- package/dist/common/events/events.js +0 -23
- package/dist/common/events/events.js.map +1 -1
- package/dist/common/events/trigger-proxy.js +0 -12
- package/dist/common/events/trigger-proxy.js.map +1 -1
- package/dist/common/events/util.js +0 -15
- package/dist/common/events/util.js.map +1 -1
- package/dist/common/logs/logger-config.js +0 -4
- package/dist/common/logs/logger-config.js.map +1 -1
- package/dist/common/logs/logger-proxy.js +1 -8
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/common/logs/request.js +35 -61
- package/dist/common/logs/request.js.map +1 -1
- package/dist/common/queue.js +4 -14
- package/dist/common/queue.js.map +1 -1
- package/dist/config.js +7 -13
- package/dist/config.js.map +1 -1
- package/dist/constants.js +208 -64
- 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 +78 -17
- package/dist/index.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +100 -29
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/embeddedAppsUtils.js +3 -26
- package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js +0 -15
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/hostUtils.js +4 -12
- package/dist/locus-info/hostUtils.js.map +1 -1
- package/dist/locus-info/index.js +387 -208
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +0 -38
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +54 -38
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +90 -126
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js +93 -92
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +70 -219
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +74 -198
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +1 -8
- package/dist/media/util.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +505 -495
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +79 -14
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +2685 -3324
- 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 +243 -185
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +296 -342
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js +0 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/state.js +16 -26
- package/dist/meeting/state.js.map +1 -1
- package/dist/meeting/util.js +446 -585
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/collection.js +3 -25
- package/dist/meeting-info/collection.js.map +1 -1
- package/dist/meeting-info/index.js +8 -31
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +261 -242
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.js +1 -16
- package/dist/meeting-info/request.js.map +1 -1
- package/dist/meeting-info/util.js +98 -183
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +156 -232
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +24 -20
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +526 -372
- 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 +21 -40
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +172 -141
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +58 -57
- 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 +101 -69
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +12 -12
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +123 -162
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +120 -85
- 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 +314 -260
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js +50 -16
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/constants.js +4 -7
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +75 -147
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +170 -50
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +58 -65
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +73 -94
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +55 -74
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +66 -43
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +502 -442
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.js +24 -51
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +3 -38
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/personal-meeting-room/request.js +2 -33
- package/dist/personal-meeting-room/request.js.map +1 -1
- package/dist/personal-meeting-room/util.js +0 -13
- package/dist/personal-meeting-room/util.js.map +1 -1
- package/dist/reachability/index.js +190 -199
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +14 -23
- 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 -4
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js +18 -24
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +356 -476
- 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 +32 -75
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +129 -136
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +143 -103
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +1 -95
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +369 -462
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +144 -94
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/transcription/index.js +9 -44
- package/dist/transcription/index.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 +1509 -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 +104 -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 +163 -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 +71 -1
- 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 +1730 -1768
- 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 +375 -83
- package/src/meetings/meetings.types.ts +9 -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 +210 -45
- 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 +332 -255
- package/test/integration/spec/space-meeting.js +78 -5
- package/test/integration/spec/transcription.js +1 -1
- package/test/unit/spec/annotation/index.ts +436 -0
- package/test/unit/spec/breakouts/breakout.ts +203 -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 +615 -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 +200 -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 +2394 -1381
- 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 +842 -128
- 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 +676 -105
- 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 -22
- package/dist/media/internal-media-core-wrapper.js.map +0 -1
- package/dist/meeting/effectsState.js +0 -334
- package/dist/meeting/effectsState.js.map +0 -1
- package/dist/multistream/multistreamMedia.js +0 -117
- 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
509
|
|
|
753
|
-
assert.calledOnce(Media.getUserMedia);
|
|
754
|
-
|
|
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,8 +683,68 @@ describe('plugin-meetings', () => {
|
|
|
873
683
|
await meeting.join();
|
|
874
684
|
sinon.assert.called(meeting.setCorrelationId);
|
|
875
685
|
});
|
|
876
|
-
|
|
877
|
-
|
|
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
|
+
});
|
|
747
|
+
describe('failure', () => {
|
|
878
748
|
beforeEach(() => {
|
|
879
749
|
sandbox.stub(MeetingUtil, 'joinMeeting').returns(Promise.reject());
|
|
880
750
|
meeting.logger.log = sinon.stub().returns(true);
|
|
@@ -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,627 +1333,853 @@ 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
1336
|
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
it('should acknowledge a non incoming and return a promise', async () => {
|
|
1385
|
-
const ack = meeting.acknowledge(test1, false);
|
|
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';
|
|
1386
1341
|
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
meeting.
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1342
|
+
meeting.meetingState = 'ACTIVE';
|
|
1343
|
+
Media.createMediaConnection.resetHistory();
|
|
1344
|
+
|
|
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,
|
|
1352
|
+
});
|
|
1353
|
+
const media = meeting.addMedia({
|
|
1354
|
+
mediaSettings: {},
|
|
1355
|
+
bundlePolicy: 'bundlePolicy-value',
|
|
1356
|
+
});
|
|
1357
|
+
|
|
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);
|
|
1403
1377
|
});
|
|
1404
1378
|
});
|
|
1405
|
-
describe('#leave', () => {
|
|
1406
|
-
let sandbox;
|
|
1407
1379
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1380
|
+
/* This set of tests are like semi-integration tests, they use real MuteState, Media, LocusMediaRequest and Roap classes.
|
|
1381
|
+
They mock the @webex/internal-media-core and sending of /media http requests to Locus.
|
|
1382
|
+
Their main purpose is to test that we send the right http requests to Locus and make right calls
|
|
1383
|
+
to @webex/internal-media-core when addMedia, updateMedia, publishTracks, unpublishTracks are called
|
|
1384
|
+
in various combinations.
|
|
1385
|
+
*/
|
|
1386
|
+
[true,false].forEach((isMultistream) =>
|
|
1387
|
+
describe(`addMedia/updateMedia semi-integration tests (${isMultistream ? 'multistream' : 'transcoded'})`, () => {
|
|
1388
|
+
const webrtcAudioTrack = {
|
|
1389
|
+
id: 'underlying audio track',
|
|
1390
|
+
getSettings: sinon.stub().returns({deviceId: 'fake device id for audio track'}),
|
|
1391
|
+
};
|
|
1411
1392
|
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1393
|
+
let fakeMicrophoneTrack;
|
|
1394
|
+
let fakeRoapMediaConnection;
|
|
1395
|
+
let fakeMultistreamRoapMediaConnection;
|
|
1396
|
+
let roapMediaConnectionConstructorStub;
|
|
1397
|
+
let multistreamRoapMediaConnectionConstructorStub;
|
|
1398
|
+
let locusMediaRequestStub; // stub for /media requests to Locus
|
|
1417
1399
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1400
|
+
const roapOfferMessage = {messageType: 'OFFER', sdp: 'sdp', seq: '1', tieBreaker: '123'};
|
|
1401
|
+
|
|
1402
|
+
let expectedMediaConnectionConfig;
|
|
1403
|
+
let expectedDebugId;
|
|
1404
|
+
|
|
1405
|
+
let clock;
|
|
1424
1406
|
|
|
1425
1407
|
beforeEach(() => {
|
|
1426
|
-
|
|
1427
|
-
meeting.meetingFiniteStateMachine.ring();
|
|
1428
|
-
meeting.meetingFiniteStateMachine.join();
|
|
1429
|
-
meeting.meetingRequest.leaveMeeting = sinon
|
|
1430
|
-
.stub()
|
|
1431
|
-
.returns(Promise.resolve({body: 'test'}));
|
|
1432
|
-
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
1433
|
-
// the 3 need to be promises because we do closeLocalStream.then(closeLocalShare.then) etc in the src code
|
|
1434
|
-
meeting.closeLocalStream = sinon.stub().returns(Promise.resolve());
|
|
1435
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
1436
|
-
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
1437
|
-
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
1438
|
-
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
1439
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
1440
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
1441
|
-
meeting.unsetRemoteTracks = sinon.stub();
|
|
1442
|
-
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
1443
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
1444
|
-
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
1445
|
-
meeting.logger.error = sinon.stub().returns(true);
|
|
1446
|
-
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
1408
|
+
clock = sinon.useFakeTimers();
|
|
1447
1409
|
|
|
1448
|
-
|
|
1410
|
+
meeting.deviceUrl = 'deviceUrl';
|
|
1411
|
+
meeting.config.deviceType = 'web';
|
|
1412
|
+
meeting.isMultistream = isMultistream;
|
|
1449
1413
|
meeting.meetingState = 'ACTIVE';
|
|
1450
|
-
meeting.
|
|
1451
|
-
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1414
|
+
meeting.mediaId = 'fake media id';
|
|
1415
|
+
meeting.selfUrl = 'selfUrl';
|
|
1416
|
+
meeting.mediaProperties.waitForMediaConnectionConnected = sinon.stub().resolves();
|
|
1417
|
+
meeting.mediaProperties.getCurrentConnectionType = sinon.stub().resolves('udp');
|
|
1418
|
+
meeting.setMercuryListener = sinon.stub();
|
|
1419
|
+
meeting.locusInfo.onFullLocus = sinon.stub();
|
|
1420
|
+
meeting.webex.meetings.reachability = {
|
|
1421
|
+
isAnyClusterReachable: sinon.stub().resolves(true),
|
|
1422
|
+
};
|
|
1423
|
+
meeting.roap.doTurnDiscovery = sinon
|
|
1424
|
+
.stub()
|
|
1425
|
+
.resolves({turnServerInfo: {}, turnDiscoverySkippedReason: 'reachability'});
|
|
1426
|
+
|
|
1427
|
+
StaticConfig.set({bandwidth: {audio: 1234, video: 5678, startBitrate: 9876}});
|
|
1428
|
+
|
|
1429
|
+
Metrics.postEvent = sinon.stub();
|
|
1430
|
+
|
|
1431
|
+
// setup things that are expected to be the same across all the tests and are actually irrelevant for these tests
|
|
1432
|
+
expectedDebugId = `MC-${meeting.id.substring(0, 4)}`;
|
|
1433
|
+
expectedMediaConnectionConfig = {
|
|
1434
|
+
iceServers: [ { urls: undefined, username: '', credential: '' } ],
|
|
1435
|
+
skipInactiveTransceivers: false,
|
|
1436
|
+
requireH264: true,
|
|
1437
|
+
sdpMunging: {
|
|
1438
|
+
convertPort9to0: false,
|
|
1439
|
+
addContentSlides: true,
|
|
1440
|
+
bandwidthLimits: {
|
|
1441
|
+
audio: StaticConfig.meetings.bandwidth.audio,
|
|
1442
|
+
video: StaticConfig.meetings.bandwidth.video,
|
|
1443
|
+
},
|
|
1444
|
+
startBitrate: StaticConfig.meetings.bandwidth.startBitrate,
|
|
1445
|
+
periodicKeyframes: 20,
|
|
1446
|
+
disableExtmap: !meeting.config.enableExtmap,
|
|
1447
|
+
disableRtx: !meeting.config.enableRtx,
|
|
1448
|
+
},
|
|
1449
|
+
};
|
|
1458
1450
|
|
|
1459
|
-
|
|
1460
|
-
|
|
1461
|
-
|
|
1462
|
-
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
});
|
|
1471
|
-
describe('after audio/video is defined', () => {
|
|
1472
|
-
let handleClientRequest;
|
|
1451
|
+
// setup stubs
|
|
1452
|
+
fakeMicrophoneTrack = {
|
|
1453
|
+
id: 'fake mic',
|
|
1454
|
+
on: sinon.stub(),
|
|
1455
|
+
off: sinon.stub(),
|
|
1456
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1457
|
+
setMuted: sinon.stub(),
|
|
1458
|
+
setPublished: sinon.stub(),
|
|
1459
|
+
muted: false,
|
|
1460
|
+
underlyingTrack: webrtcAudioTrack
|
|
1461
|
+
};
|
|
1473
1462
|
|
|
1474
|
-
|
|
1475
|
-
|
|
1463
|
+
fakeRoapMediaConnection = {
|
|
1464
|
+
id: 'roap media connection',
|
|
1465
|
+
close: sinon.stub(),
|
|
1466
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1467
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1468
|
+
update: sinon.stub().resolves({}),
|
|
1469
|
+
on: sinon.stub(),
|
|
1470
|
+
};
|
|
1476
1471
|
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1472
|
+
fakeMultistreamRoapMediaConnection = {
|
|
1473
|
+
id: 'multistream roap media connection',
|
|
1474
|
+
close: sinon.stub(),
|
|
1475
|
+
getConnectionState: sinon.stub().returns(ConnectionState.Connected),
|
|
1476
|
+
initiateOffer: sinon.stub().resolves({}),
|
|
1477
|
+
publishTrack: sinon.stub().resolves({}),
|
|
1478
|
+
unpublishTrack: sinon.stub().resolves({}),
|
|
1479
|
+
on: sinon.stub(),
|
|
1480
|
+
requestMedia: sinon.stub(),
|
|
1481
|
+
createReceiveSlot: sinon.stub().resolves({on: sinon.stub()}),
|
|
1482
|
+
enableMultistreamAudio: sinon.stub(),
|
|
1483
|
+
};
|
|
1480
1484
|
|
|
1481
|
-
|
|
1482
|
-
|
|
1485
|
+
roapMediaConnectionConstructorStub = sinon
|
|
1486
|
+
.stub(internalMediaModule, 'RoapMediaConnection')
|
|
1487
|
+
.returns(fakeRoapMediaConnection);
|
|
1483
1488
|
|
|
1484
|
-
|
|
1485
|
-
|
|
1489
|
+
multistreamRoapMediaConnectionConstructorStub = sinon
|
|
1490
|
+
.stub(internalMediaModule, 'MultistreamRoapMediaConnection')
|
|
1491
|
+
.returns(fakeMultistreamRoapMediaConnection);
|
|
1486
1492
|
|
|
1487
|
-
|
|
1488
|
-
assert.isNull(meeting.video);
|
|
1489
|
-
});
|
|
1493
|
+
locusMediaRequestStub = sinon.stub(WebexPlugin.prototype, 'request').resolves({body: {locus: { fullState: {}}}});
|
|
1490
1494
|
});
|
|
1491
|
-
it('should leave the meeting without leaving resource', async () => {
|
|
1492
|
-
const leave = meeting.leave({resourceId: null});
|
|
1493
1495
|
|
|
1494
|
-
|
|
1495
|
-
|
|
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
|
-
});
|
|
1496
|
+
afterEach(() => {
|
|
1497
|
+
clock.restore();
|
|
1503
1498
|
});
|
|
1504
|
-
it('should leave the meeting on the resource', async () => {
|
|
1505
|
-
const leave = meeting.leave();
|
|
1506
1499
|
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1500
|
+
// helper function that waits until all promises are resolved and any queued up /media requests to Locus are sent out
|
|
1501
|
+
const stableState = async () => {
|
|
1502
|
+
await testUtils.flushPromises();
|
|
1503
|
+
clock.tick(1); // needed because LocusMediaRequest uses Lodash.defer()
|
|
1504
|
+
}
|
|
1505
|
+
|
|
1506
|
+
const resetHistory = () => {
|
|
1507
|
+
locusMediaRequestStub.resetHistory();
|
|
1508
|
+
fakeRoapMediaConnection.update.resetHistory();
|
|
1509
|
+
fakeMultistreamRoapMediaConnection.publishTrack.resetHistory();
|
|
1510
|
+
fakeMultistreamRoapMediaConnection.unpublishTrack.resetHistory();
|
|
1511
|
+
};
|
|
1512
|
+
|
|
1513
|
+
const getRoapListener = () => {
|
|
1514
|
+
const roapMediaConnectionToCheck = isMultistream ? fakeMultistreamRoapMediaConnection : fakeRoapMediaConnection;
|
|
1515
|
+
|
|
1516
|
+
for(let idx = 0; idx < roapMediaConnectionToCheck.on.callCount; idx+= 1) {
|
|
1517
|
+
if (roapMediaConnectionToCheck.on.getCall(idx).args[0] === Event.ROAP_MESSAGE_TO_SEND) {
|
|
1518
|
+
return roapMediaConnectionToCheck.on.getCall(idx).args[1];
|
|
1519
|
+
}
|
|
1520
|
+
}
|
|
1521
|
+
assert.fail('listener for "roap:messageToSend" (Event.ROAP_MESSAGE_TO_SEND) was not registered')
|
|
1522
|
+
}
|
|
1523
|
+
|
|
1524
|
+
// simulates a Roap offer being generated by the RoapMediaConnection
|
|
1525
|
+
const simulateRoapOffer = async () => {
|
|
1526
|
+
const roapListener = getRoapListener();
|
|
1527
|
+
|
|
1528
|
+
await roapListener({roapMessage: roapOfferMessage});
|
|
1529
|
+
await stableState();
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
const checkSdpOfferSent = ({audioMuted, videoMuted}) => {
|
|
1533
|
+
const {sdp, seq, tieBreaker} = roapOfferMessage;
|
|
1534
|
+
|
|
1535
|
+
assert.calledWith(locusMediaRequestStub,
|
|
1536
|
+
{
|
|
1537
|
+
method: 'PUT',
|
|
1538
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1539
|
+
body: {
|
|
1540
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1541
|
+
correlationId: meeting.correlationId,
|
|
1542
|
+
localMedias: [
|
|
1543
|
+
{
|
|
1544
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted},"roapMessage":{"messageType":"OFFER","sdps":["${sdp}"],"version":"2","seq":"${seq}","tieBreaker":"${tieBreaker}"}}`,
|
|
1545
|
+
mediaId: 'fake media id'
|
|
1546
|
+
}
|
|
1547
|
+
],
|
|
1548
|
+
clientMediaPreferences: {
|
|
1549
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1550
|
+
joinCookie: undefined
|
|
1551
|
+
}
|
|
1552
|
+
},
|
|
1553
|
+
});
|
|
1554
|
+
};
|
|
1555
|
+
|
|
1556
|
+
const checkLocalMuteSentToLocus = ({audioMuted, videoMuted}) => {
|
|
1557
|
+
assert.calledWith(locusMediaRequestStub, {
|
|
1558
|
+
method: 'PUT',
|
|
1559
|
+
uri: `${meeting.selfUrl}/media`,
|
|
1560
|
+
body: {
|
|
1561
|
+
device: { url: meeting.deviceUrl, deviceType: meeting.config.deviceType },
|
|
1562
|
+
correlationId: meeting.correlationId,
|
|
1563
|
+
localMedias: [
|
|
1564
|
+
{
|
|
1565
|
+
localSdp: `{"audioMuted":${audioMuted},"videoMuted":${videoMuted}}`,
|
|
1566
|
+
mediaId: 'fake media id'
|
|
1567
|
+
}
|
|
1568
|
+
],
|
|
1569
|
+
clientMediaPreferences: {
|
|
1570
|
+
preferTranscoding: !meeting.isMultistream,
|
|
1571
|
+
},
|
|
1572
|
+
respOnlySdp: true,
|
|
1573
|
+
usingResource: null,
|
|
1574
|
+
},
|
|
1575
|
+
});
|
|
1576
|
+
};
|
|
1577
|
+
|
|
1578
|
+
const checkMediaConnectionCreated = ({mediaConnectionConfig, localTracks, direction, remoteQualityLevel, expectedDebugId}) => {
|
|
1579
|
+
if (isMultistream) {
|
|
1580
|
+
const {iceServers} = mediaConnectionConfig;
|
|
1581
|
+
|
|
1582
|
+
assert.calledOnceWithExactly(multistreamRoapMediaConnectionConstructorStub, {
|
|
1583
|
+
iceServers,
|
|
1584
|
+
enableMainAudio: direction.audio !== 'inactive',
|
|
1585
|
+
enableMainVideo: true
|
|
1586
|
+
}, expectedDebugId);
|
|
1587
|
+
|
|
1588
|
+
Object.values(localTracks).forEach((track) => {
|
|
1589
|
+
if (track) {
|
|
1590
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, track);
|
|
1591
|
+
}
|
|
1592
|
+
})
|
|
1593
|
+
} else {
|
|
1594
|
+
assert.calledOnceWithExactly(roapMediaConnectionConstructorStub, mediaConnectionConfig,
|
|
1595
|
+
{
|
|
1596
|
+
localTracks: {
|
|
1597
|
+
audio: localTracks.audio?.underlyingTrack,
|
|
1598
|
+
video: localTracks.video?.underlyingTrack,
|
|
1599
|
+
screenShareVideo: localTracks.screenShareVideo?.underlyingTrack,
|
|
1600
|
+
},
|
|
1601
|
+
direction,
|
|
1602
|
+
remoteQualityLevel,
|
|
1603
|
+
},
|
|
1604
|
+
expectedDebugId);
|
|
1605
|
+
}
|
|
1606
|
+
}
|
|
1607
|
+
|
|
1608
|
+
it('addMedia() works correctly when media is enabled without tracks to publish', async () => {
|
|
1609
|
+
await meeting.addMedia();
|
|
1610
|
+
await simulateRoapOffer();
|
|
1611
|
+
|
|
1612
|
+
// check RoapMediaConnection was created correctly
|
|
1613
|
+
checkMediaConnectionCreated({
|
|
1614
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1615
|
+
localTracks: {
|
|
1616
|
+
audio: undefined,
|
|
1617
|
+
video: undefined,
|
|
1618
|
+
screenShareVideo: undefined,
|
|
1619
|
+
},
|
|
1620
|
+
direction: {
|
|
1621
|
+
audio: 'sendrecv',
|
|
1622
|
+
video: 'sendrecv',
|
|
1623
|
+
screenShareVideo: 'recvonly',
|
|
1624
|
+
},
|
|
1625
|
+
remoteQualityLevel: 'HIGH',
|
|
1626
|
+
expectedDebugId,
|
|
1515
1627
|
});
|
|
1516
|
-
});
|
|
1517
|
-
});
|
|
1518
|
-
describe('#requestScreenShareFloor', () => {
|
|
1519
|
-
it('should have #requestScreenShareFloor', () => {
|
|
1520
|
-
assert.exists(meeting.requestScreenShareFloor);
|
|
1521
|
-
});
|
|
1522
|
-
beforeEach(() => {
|
|
1523
|
-
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
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();
|
|
1529
1628
|
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1629
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1630
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1631
|
+
|
|
1632
|
+
// and that it was the only /media request that was sent
|
|
1633
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1533
1634
|
});
|
|
1534
|
-
});
|
|
1535
1635
|
|
|
1536
|
-
|
|
1537
|
-
|
|
1636
|
+
it('addMedia() works correctly when media is enabled with tracks to publish', async () => {
|
|
1637
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1638
|
+
await simulateRoapOffer();
|
|
1538
1639
|
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1640
|
+
// check RoapMediaConnection was created correctly
|
|
1641
|
+
checkMediaConnectionCreated({
|
|
1642
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1643
|
+
localTracks: {
|
|
1644
|
+
audio: fakeMicrophoneTrack,
|
|
1645
|
+
video: undefined,
|
|
1646
|
+
screenShareVideo: undefined,
|
|
1647
|
+
},
|
|
1648
|
+
direction: {
|
|
1649
|
+
audio: 'sendrecv',
|
|
1650
|
+
video: 'sendrecv',
|
|
1651
|
+
screenShareVideo: 'recvonly',
|
|
1652
|
+
},
|
|
1653
|
+
remoteQualityLevel: 'HIGH',
|
|
1654
|
+
expectedDebugId
|
|
1655
|
+
});
|
|
1545
1656
|
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
});
|
|
1657
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1658
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1549
1659
|
|
|
1550
|
-
|
|
1551
|
-
assert.
|
|
1660
|
+
// and no other local mute requests were sent to Locus
|
|
1661
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1552
1662
|
});
|
|
1553
1663
|
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
sinon.stub(Media, 'getDisplayMedia').returns(Promise.resolve());
|
|
1557
|
-
sinon.stub(meeting, 'updateShare').returns(Promise.resolve());
|
|
1558
|
-
});
|
|
1664
|
+
it('addMedia() works correctly when media is enabled with tracks to publish and track is muted', async () => {
|
|
1665
|
+
fakeMicrophoneTrack.muted = true;
|
|
1559
1666
|
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1667
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1668
|
+
await simulateRoapOffer();
|
|
1669
|
+
|
|
1670
|
+
// check RoapMediaConnection was created correctly
|
|
1671
|
+
checkMediaConnectionCreated({
|
|
1672
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1673
|
+
localTracks: {
|
|
1674
|
+
audio: fakeMicrophoneTrack,
|
|
1675
|
+
video: undefined,
|
|
1676
|
+
screenShareVideo: undefined,
|
|
1677
|
+
},
|
|
1678
|
+
direction: {
|
|
1679
|
+
audio: 'sendrecv',
|
|
1680
|
+
video: 'sendrecv',
|
|
1681
|
+
screenShareVideo: 'recvonly',
|
|
1682
|
+
},
|
|
1683
|
+
remoteQualityLevel: 'HIGH',
|
|
1684
|
+
expectedDebugId,
|
|
1563
1685
|
});
|
|
1564
1686
|
|
|
1565
|
-
|
|
1566
|
-
|
|
1687
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1688
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1567
1689
|
|
|
1568
|
-
|
|
1569
|
-
|
|
1690
|
+
// and no other local mute requests were sent to Locus
|
|
1691
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1692
|
+
});
|
|
1570
1693
|
|
|
1571
|
-
|
|
1572
|
-
|
|
1694
|
+
it('addMedia() works correctly when media is disabled with tracks to publish', async () => {
|
|
1695
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}, audioEnabled: false});
|
|
1696
|
+
await simulateRoapOffer();
|
|
1573
1697
|
|
|
1574
|
-
|
|
1698
|
+
// check RoapMediaConnection was created correctly
|
|
1699
|
+
checkMediaConnectionCreated({
|
|
1700
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1701
|
+
localTracks: {
|
|
1702
|
+
audio: fakeMicrophoneTrack,
|
|
1703
|
+
video: undefined,
|
|
1704
|
+
screenShareVideo: undefined,
|
|
1705
|
+
},
|
|
1706
|
+
direction: {
|
|
1707
|
+
audio: 'inactive',
|
|
1708
|
+
video: 'sendrecv',
|
|
1709
|
+
screenShareVideo: 'recvonly',
|
|
1710
|
+
},
|
|
1711
|
+
remoteQualityLevel: 'HIGH',
|
|
1712
|
+
expectedDebugId
|
|
1575
1713
|
});
|
|
1576
1714
|
|
|
1577
|
-
|
|
1578
|
-
|
|
1715
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1716
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1579
1717
|
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
sendAudio: false,
|
|
1583
|
-
sharePreferences: {highFrameRate: true},
|
|
1584
|
-
});
|
|
1585
|
-
});
|
|
1718
|
+
// and no other local mute requests were sent to Locus
|
|
1719
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1586
1720
|
});
|
|
1587
1721
|
|
|
1588
|
-
|
|
1589
|
-
|
|
1722
|
+
it('addMedia() works correctly when media is disabled with no tracks to publish', async () => {
|
|
1723
|
+
await meeting.addMedia({audioEnabled: false});
|
|
1724
|
+
await simulateRoapOffer();
|
|
1590
1725
|
|
|
1591
|
-
|
|
1592
|
-
|
|
1726
|
+
// check RoapMediaConnection was created correctly
|
|
1727
|
+
checkMediaConnectionCreated({
|
|
1728
|
+
mediaConnectionConfig: expectedMediaConnectionConfig,
|
|
1729
|
+
localTracks: {
|
|
1730
|
+
audio: undefined,
|
|
1731
|
+
video: undefined,
|
|
1732
|
+
screenShareVideo: undefined,
|
|
1733
|
+
},
|
|
1734
|
+
direction: {
|
|
1735
|
+
audio: 'inactive',
|
|
1736
|
+
video: 'sendrecv',
|
|
1737
|
+
screenShareVideo: 'recvonly',
|
|
1738
|
+
},
|
|
1739
|
+
remoteQualityLevel: 'HIGH',
|
|
1740
|
+
expectedDebugId
|
|
1593
1741
|
});
|
|
1594
1742
|
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
sandbox = null;
|
|
1598
|
-
});
|
|
1743
|
+
// and SDP offer was sent with the right audioMuted/videoMuted values
|
|
1744
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1599
1745
|
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
const stream = 'stream';
|
|
1746
|
+
// and no other local mute requests were sent to Locus
|
|
1747
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1748
|
+
});
|
|
1604
1749
|
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1750
|
+
describe('publishTracks()/unpublishTracks() calls', () => {
|
|
1751
|
+
[
|
|
1752
|
+
{mediaEnabled: true, expected: {direction: 'sendrecv', localMuteSentValue: false}},
|
|
1753
|
+
{mediaEnabled: false, expected: {direction: 'inactive', localMuteSentValue: undefined}}
|
|
1754
|
+
]
|
|
1755
|
+
.forEach(({mediaEnabled, expected}) => {
|
|
1756
|
+
it(`first publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1757
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1758
|
+
await simulateRoapOffer();
|
|
1609
1759
|
|
|
1610
|
-
|
|
1611
|
-
sendShare,
|
|
1612
|
-
receiveShare,
|
|
1613
|
-
stream,
|
|
1614
|
-
skipSignalingCheck: true,
|
|
1615
|
-
});
|
|
1760
|
+
resetHistory();
|
|
1616
1761
|
|
|
1617
|
-
|
|
1618
|
-
|
|
1762
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1763
|
+
await stableState();
|
|
1619
1764
|
|
|
1620
|
-
|
|
1621
|
-
|
|
1622
|
-
|
|
1623
|
-
|
|
1624
|
-
|
|
1625
|
-
|
|
1765
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1766
|
+
// check local mute was sent and it was the only /media request
|
|
1767
|
+
checkLocalMuteSentToLocus({audioMuted: expected.localMuteSentValue, videoMuted: true});
|
|
1768
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1769
|
+
} else {
|
|
1770
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1771
|
+
}
|
|
1772
|
+
if (isMultistream) {
|
|
1773
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack);
|
|
1774
|
+
} else {
|
|
1775
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1776
|
+
localTracks: { audio: webrtcAudioTrack, video: null, screenShareVideo: null },
|
|
1777
|
+
direction: {
|
|
1778
|
+
audio: expected.direction,
|
|
1779
|
+
video: 'sendrecv',
|
|
1780
|
+
screenShareVideo: 'recvonly',
|
|
1781
|
+
},
|
|
1782
|
+
remoteQualityLevel: 'HIGH'
|
|
1783
|
+
});
|
|
1784
|
+
}
|
|
1785
|
+
});
|
|
1626
1786
|
|
|
1627
|
-
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1787
|
+
it(`second publishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1788
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1789
|
+
await simulateRoapOffer();
|
|
1790
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1791
|
+
await stableState();
|
|
1792
|
+
|
|
1793
|
+
resetHistory();
|
|
1794
|
+
|
|
1795
|
+
const webrtcAudioTrack2 = {id: 'underlying audio track 2'};
|
|
1796
|
+
const fakeMicrophoneTrack2 = {
|
|
1797
|
+
id: 'fake mic 2',
|
|
1798
|
+
on: sinon.stub(),
|
|
1799
|
+
off: sinon.stub(),
|
|
1800
|
+
setUnmuteAllowed: sinon.stub(),
|
|
1801
|
+
setMuted: sinon.stub(),
|
|
1802
|
+
setPublished: sinon.stub(),
|
|
1803
|
+
muted: false,
|
|
1804
|
+
underlyingTrack: webrtcAudioTrack2
|
|
1805
|
+
};
|
|
1806
|
+
|
|
1807
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack2});
|
|
1808
|
+
await stableState();
|
|
1809
|
+
|
|
1810
|
+
// only the roap media connection should be updated
|
|
1811
|
+
if (isMultistream) {
|
|
1812
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.publishTrack, fakeMicrophoneTrack2);
|
|
1813
|
+
} else {
|
|
1814
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1815
|
+
localTracks: { audio: webrtcAudioTrack2, video: null, screenShareVideo: null },
|
|
1816
|
+
direction: {
|
|
1817
|
+
audio: expected.direction,
|
|
1818
|
+
video: 'sendrecv',
|
|
1819
|
+
screenShareVideo: 'recvonly',
|
|
1820
|
+
},
|
|
1821
|
+
remoteQualityLevel: 'HIGH'
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1632
1824
|
|
|
1633
|
-
|
|
1825
|
+
// and no other roap messages or local mute requests were sent
|
|
1826
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1827
|
+
});
|
|
1634
1828
|
|
|
1635
|
-
|
|
1636
|
-
|
|
1829
|
+
it(`unpublishTracks() call while media is ${mediaEnabled ? 'enabled' : 'disabled'}`, async () => {
|
|
1830
|
+
await meeting.addMedia({audioEnabled: mediaEnabled});
|
|
1831
|
+
await simulateRoapOffer();
|
|
1832
|
+
await meeting.publishTracks({microphone: fakeMicrophoneTrack});
|
|
1833
|
+
await stableState();
|
|
1637
1834
|
|
|
1638
|
-
|
|
1639
|
-
const args = {
|
|
1640
|
-
abc: 123,
|
|
1641
|
-
receiveShare: false,
|
|
1642
|
-
sendShare: false,
|
|
1643
|
-
};
|
|
1835
|
+
resetHistory();
|
|
1644
1836
|
|
|
1645
|
-
|
|
1646
|
-
|
|
1837
|
+
await meeting.unpublishTracks([fakeMicrophoneTrack]);
|
|
1838
|
+
await stableState();
|
|
1647
1839
|
|
|
1648
|
-
|
|
1840
|
+
// the roap media connection should be updated
|
|
1841
|
+
if (isMultistream) {
|
|
1842
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.unpublishTrack, fakeMicrophoneTrack);
|
|
1843
|
+
} else {
|
|
1844
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1845
|
+
localTracks: { audio: null, video: null, screenShareVideo: null },
|
|
1846
|
+
direction: {
|
|
1847
|
+
audio: expected.direction,
|
|
1848
|
+
video: 'sendrecv',
|
|
1849
|
+
screenShareVideo: 'recvonly',
|
|
1850
|
+
},
|
|
1851
|
+
remoteQualityLevel: 'HIGH'
|
|
1852
|
+
});
|
|
1853
|
+
}
|
|
1649
1854
|
|
|
1650
|
-
|
|
1651
|
-
|
|
1855
|
+
if (expected.localMuteSentValue !== undefined) {
|
|
1856
|
+
// and local mute sent to Locus
|
|
1857
|
+
checkLocalMuteSentToLocus({audioMuted: !expected.localMuteSentValue /* negation, because we're un-publishing */, videoMuted: true});
|
|
1858
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1859
|
+
} else {
|
|
1860
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1861
|
+
}
|
|
1862
|
+
});
|
|
1863
|
+
});
|
|
1652
1864
|
});
|
|
1653
1865
|
|
|
1654
|
-
describe('
|
|
1655
|
-
let sandbox;
|
|
1866
|
+
describe('updateMedia()', () => {
|
|
1656
1867
|
|
|
1657
|
-
|
|
1658
|
-
|
|
1868
|
+
const addMedia = async (enableMedia, track) => {
|
|
1869
|
+
await meeting.addMedia({audioEnabled: enableMedia, localTracks: {microphone: track}});
|
|
1870
|
+
await simulateRoapOffer();
|
|
1871
|
+
|
|
1872
|
+
resetHistory();
|
|
1873
|
+
}
|
|
1874
|
+
|
|
1875
|
+
const checkAudioEnabled = (expectedTrack, expectedDirection) => {
|
|
1876
|
+
if (isMultistream) {
|
|
1877
|
+
assert.calledOnceWithExactly(fakeMultistreamRoapMediaConnection.enableMultistreamAudio, expectedDirection !== 'inactive');
|
|
1878
|
+
} else {
|
|
1879
|
+
assert.calledOnceWithExactly(fakeRoapMediaConnection.update, {
|
|
1880
|
+
localTracks: { audio: expectedTrack, video: null, screenShareVideo: null },
|
|
1881
|
+
direction: {
|
|
1882
|
+
audio: expectedDirection,
|
|
1883
|
+
video: 'sendrecv',
|
|
1884
|
+
screenShareVideo: 'recvonly',
|
|
1885
|
+
},
|
|
1886
|
+
remoteQualityLevel: 'HIGH'
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
|
|
1891
|
+
it('updateMedia() disables media when nothing is published', async () => {
|
|
1892
|
+
await addMedia(true);
|
|
1893
|
+
|
|
1894
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1895
|
+
|
|
1896
|
+
// the roap media connection should be updated
|
|
1897
|
+
checkAudioEnabled(null, 'inactive');
|
|
1898
|
+
|
|
1899
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1900
|
+
await simulateRoapOffer();
|
|
1901
|
+
|
|
1902
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1903
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1904
|
+
|
|
1905
|
+
// and no other local mute requests were sent to Locus
|
|
1906
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1659
1907
|
});
|
|
1660
1908
|
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1909
|
+
it('updateMedia() enables media when nothing is published', async () => {
|
|
1910
|
+
await addMedia(false);
|
|
1911
|
+
|
|
1912
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1913
|
+
|
|
1914
|
+
// the roap media connection should be updated
|
|
1915
|
+
checkAudioEnabled(null, 'sendrecv');
|
|
1916
|
+
|
|
1917
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1918
|
+
await simulateRoapOffer();
|
|
1919
|
+
|
|
1920
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1921
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1922
|
+
|
|
1923
|
+
// and no other local mute requests were sent to Locus
|
|
1924
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1664
1925
|
});
|
|
1665
1926
|
|
|
1666
|
-
it('
|
|
1667
|
-
|
|
1668
|
-
const {EVENT_TYPES} = CONSTANTS;
|
|
1927
|
+
it('updateMedia() disables media when track is published', async () => {
|
|
1928
|
+
await addMedia(true, fakeMicrophoneTrack);
|
|
1669
1929
|
|
|
1670
|
-
|
|
1930
|
+
await meeting.updateMedia({audioEnabled: false});
|
|
1931
|
+
await stableState();
|
|
1671
1932
|
|
|
1672
|
-
|
|
1933
|
+
// the roap media connection should be updated
|
|
1934
|
+
checkAudioEnabled(webrtcAudioTrack, 'inactive');
|
|
1673
1935
|
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
);
|
|
1936
|
+
checkLocalMuteSentToLocus({audioMuted: true, videoMuted: true});
|
|
1937
|
+
|
|
1938
|
+
locusMediaRequestStub.resetHistory();
|
|
1939
|
+
|
|
1940
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1941
|
+
await simulateRoapOffer();
|
|
1942
|
+
|
|
1943
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1944
|
+
checkSdpOfferSent({audioMuted: true, videoMuted: true});
|
|
1945
|
+
|
|
1946
|
+
// and no other local mute requests were sent to Locus
|
|
1947
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1687
1948
|
});
|
|
1688
|
-
});
|
|
1689
|
-
});
|
|
1690
1949
|
|
|
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
|
-
};
|
|
1950
|
+
it('updateMedia() enables media when track is published', async () => {
|
|
1951
|
+
await addMedia(false, fakeMicrophoneTrack);
|
|
1706
1952
|
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
{
|
|
1710
|
-
applyConstraints: () => {},
|
|
1711
|
-
},
|
|
1712
|
-
],
|
|
1713
|
-
};
|
|
1953
|
+
await meeting.updateMedia({audioEnabled: true});
|
|
1954
|
+
await stableState();
|
|
1714
1955
|
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
aspectRatio: config.aspectRatio,
|
|
1718
|
-
frameRate: config.screenFrameRate,
|
|
1719
|
-
width: null,
|
|
1720
|
-
height: null,
|
|
1721
|
-
};
|
|
1956
|
+
// the roap media connection should be updated
|
|
1957
|
+
checkAudioEnabled(webrtcAudioTrack, 'sendrecv');
|
|
1722
1958
|
|
|
1723
|
-
|
|
1724
|
-
const key = getBrowserName().toLowerCase();
|
|
1725
|
-
const defaultKey = 'default';
|
|
1959
|
+
checkLocalMuteSentToLocus({audioMuted: false, videoMuted: true});
|
|
1726
1960
|
|
|
1727
|
-
|
|
1728
|
-
};
|
|
1961
|
+
locusMediaRequestStub.resetHistory();
|
|
1729
1962
|
|
|
1730
|
-
|
|
1731
|
-
|
|
1963
|
+
// and that would trigger a new offer so we simulate it happening
|
|
1964
|
+
await simulateRoapOffer();
|
|
1732
1965
|
|
|
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,
|
|
1966
|
+
// check SDP offer was sent with the right audioMuted/videoMuted values
|
|
1967
|
+
checkSdpOfferSent({audioMuted: false, videoMuted: true});
|
|
1968
|
+
|
|
1969
|
+
// and no other local mute requests were sent to Locus
|
|
1970
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
1744
1971
|
});
|
|
1745
1972
|
});
|
|
1746
1973
|
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1974
|
+
[
|
|
1975
|
+
{mute: true, title: 'muting a track before confluence is created'},
|
|
1976
|
+
{mute: false, title: 'unmuting a track before confluence is created'}
|
|
1977
|
+
].forEach(({mute, title}) =>
|
|
1978
|
+
it(title, async () => {
|
|
1979
|
+
// initialize the microphone mute state to opposite of what we do in the test
|
|
1980
|
+
fakeMicrophoneTrack.muted = !mute;
|
|
1981
|
+
|
|
1982
|
+
await meeting.addMedia({localTracks: {microphone: fakeMicrophoneTrack}});
|
|
1983
|
+
await stableState();
|
|
1984
|
+
|
|
1985
|
+
resetHistory();
|
|
1986
|
+
|
|
1987
|
+
assert.equal(fakeMicrophoneTrack.on.getCall(0).args[0], LocalTrackEvents.Muted);
|
|
1988
|
+
const mutedListener = fakeMicrophoneTrack.on.getCall(0).args[1];
|
|
1989
|
+
// simulate track being muted
|
|
1990
|
+
mutedListener({trackState: {muted: mute}});
|
|
1991
|
+
|
|
1992
|
+
await stableState();
|
|
1993
|
+
|
|
1994
|
+
// nothing should happen
|
|
1995
|
+
assert.notCalled(locusMediaRequestStub);
|
|
1996
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
1997
|
+
|
|
1998
|
+
// now simulate roap offer
|
|
1999
|
+
await simulateRoapOffer();
|
|
2000
|
+
|
|
2001
|
+
// it should be sent with the right mute status
|
|
2002
|
+
checkSdpOfferSent({audioMuted: mute, videoMuted: true});
|
|
2003
|
+
|
|
2004
|
+
// nothing else should happen
|
|
2005
|
+
assert.calledOnce(locusMediaRequestStub);
|
|
2006
|
+
assert.notCalled(fakeRoapMediaConnection.update);
|
|
2007
|
+
})
|
|
2008
|
+
);
|
|
2009
|
+
}));
|
|
2010
|
+
|
|
2011
|
+
describe('#acknowledge', () => {
|
|
2012
|
+
it('should have #acknowledge', () => {
|
|
2013
|
+
assert.exists(meeting.acknowledge);
|
|
1753
2014
|
});
|
|
2015
|
+
beforeEach(() => {
|
|
2016
|
+
meeting.meetingRequest.acknowledgeMeeting = sinon.stub().returns(Promise.resolve());
|
|
2017
|
+
});
|
|
2018
|
+
it('should acknowledge incoming and return a promise', async () => {
|
|
2019
|
+
const ack = meeting.acknowledge('INCOMING', false);
|
|
1754
2020
|
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
maxWidth: SHARE_WIDTH,
|
|
1762
|
-
maxHeight: SHARE_HEIGHT,
|
|
1763
|
-
idealWidth: SHARE_WIDTH,
|
|
1764
|
-
idealHeight: SHARE_HEIGHT,
|
|
1765
|
-
};
|
|
2021
|
+
assert.exists(ack.then);
|
|
2022
|
+
await ack;
|
|
2023
|
+
assert.calledOnce(meeting.meetingRequest.acknowledgeMeeting);
|
|
2024
|
+
});
|
|
2025
|
+
it('should acknowledge a non incoming and return a promise', async () => {
|
|
2026
|
+
const ack = meeting.acknowledge(test1, false);
|
|
1766
2027
|
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
2028
|
+
assert.exists(ack.then);
|
|
2029
|
+
await ack;
|
|
2030
|
+
assert.notCalled(meeting.meetingRequest.acknowledgeMeeting);
|
|
2031
|
+
});
|
|
2032
|
+
});
|
|
2033
|
+
describe('#decline', () => {
|
|
2034
|
+
it('should have #decline', () => {
|
|
2035
|
+
assert.exists(meeting.decline);
|
|
2036
|
+
});
|
|
2037
|
+
beforeEach(() => {
|
|
2038
|
+
meeting.meetingRequest.declineMeeting = sinon.stub().returns(Promise.resolve());
|
|
2039
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
2040
|
+
});
|
|
2041
|
+
it('should decline the meeting and trigger meeting destroy for 1:1', async () => {
|
|
2042
|
+
await meeting.decline();
|
|
2043
|
+
assert.calledOnce(meeting.meetingRequest.declineMeeting);
|
|
2044
|
+
});
|
|
2045
|
+
});
|
|
2046
|
+
describe('#leave', () => {
|
|
2047
|
+
let sandbox;
|
|
1776
2048
|
|
|
1777
|
-
|
|
1778
|
-
assert.
|
|
1779
|
-
navigator.mediaDevices.getDisplayMedia,
|
|
1780
|
-
browserConditionalValue({
|
|
1781
|
-
default: {
|
|
1782
|
-
video: {...shareConstraints},
|
|
1783
|
-
},
|
|
1784
|
-
// Firefox is being handled differently
|
|
1785
|
-
firefox: fireFoxOptions,
|
|
1786
|
-
})
|
|
1787
|
-
);
|
|
2049
|
+
it('should have #leave', () => {
|
|
2050
|
+
assert.exists(meeting.leave);
|
|
1788
2051
|
});
|
|
1789
2052
|
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1794
|
-
|
|
1795
|
-
...shareOptions,
|
|
1796
|
-
sharePreferences: {
|
|
1797
|
-
highFrameRate: true,
|
|
1798
|
-
},
|
|
1799
|
-
},
|
|
1800
|
-
config
|
|
1801
|
-
);
|
|
2053
|
+
it('should reject if meeting is already inactive', async () => {
|
|
2054
|
+
await meeting.leave().catch((err) => {
|
|
2055
|
+
assert.instanceOf(err, MeetingNotActiveError);
|
|
2056
|
+
});
|
|
2057
|
+
});
|
|
1802
2058
|
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
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
|
-
);
|
|
2059
|
+
it('should reject if meeting is already left', async () => {
|
|
2060
|
+
meeting.meetingState = 'ACTIVE';
|
|
2061
|
+
await meeting.leave().catch((err) => {
|
|
2062
|
+
assert.instanceOf(err, UserNotJoinedError);
|
|
2063
|
+
});
|
|
1822
2064
|
});
|
|
1823
2065
|
|
|
1824
|
-
|
|
1825
|
-
|
|
1826
|
-
|
|
1827
|
-
|
|
2066
|
+
beforeEach(() => {
|
|
2067
|
+
sandbox = sinon.createSandbox();
|
|
2068
|
+
meeting.meetingFiniteStateMachine.ring();
|
|
2069
|
+
meeting.meetingFiniteStateMachine.join();
|
|
2070
|
+
meeting.meetingRequest.leaveMeeting = sinon
|
|
2071
|
+
.stub()
|
|
2072
|
+
.returns(Promise.resolve({body: 'test'}));
|
|
2073
|
+
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
2074
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
2075
|
+
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
2076
|
+
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
2077
|
+
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
2078
|
+
meeting.unsetRemoteTracks = sinon.stub();
|
|
2079
|
+
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
2080
|
+
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
2081
|
+
meeting.logger.error = sinon.stub().returns(true);
|
|
2082
|
+
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
2083
|
+
|
|
2084
|
+
// A meeting needs to be joined to leave
|
|
2085
|
+
meeting.meetingState = 'ACTIVE';
|
|
2086
|
+
meeting.state = 'JOINED';
|
|
2087
|
+
});
|
|
2088
|
+
afterEach(() => {
|
|
2089
|
+
sandbox.restore();
|
|
2090
|
+
sandbox = null;
|
|
2091
|
+
});
|
|
2092
|
+
it('should leave the meeting and return promise', async () => {
|
|
2093
|
+
const leave = meeting.leave();
|
|
1828
2094
|
|
|
1829
|
-
|
|
1830
|
-
|
|
1831
|
-
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1837
|
-
height: screenResolution.idealHeight,
|
|
1838
|
-
},
|
|
1839
|
-
},
|
|
1840
|
-
firefox: fireFoxOptions,
|
|
1841
|
-
})
|
|
1842
|
-
);
|
|
2095
|
+
assert.exists(leave.then);
|
|
2096
|
+
await leave;
|
|
2097
|
+
assert.calledOnce(meeting.meetingRequest.leaveMeeting);
|
|
2098
|
+
assert.calledOnce(meeting.cleanupLocalTracks);
|
|
2099
|
+
assert.calledOnce(meeting.closeRemoteTracks);
|
|
2100
|
+
assert.calledOnce(meeting.closePeerConnections);
|
|
2101
|
+
assert.calledOnce(meeting.unsetRemoteTracks);
|
|
2102
|
+
assert.calledOnce(meeting.unsetPeerConnections);
|
|
1843
2103
|
});
|
|
2104
|
+
describe('after audio/video is defined', () => {
|
|
2105
|
+
let handleClientRequest;
|
|
1844
2106
|
|
|
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
|
-
};
|
|
2107
|
+
beforeEach(() => {
|
|
2108
|
+
handleClientRequest = sinon.stub().returns(Promise.resolve(true));
|
|
1858
2109
|
|
|
1859
|
-
|
|
2110
|
+
meeting.audio = {handleClientRequest};
|
|
2111
|
+
meeting.video = {handleClientRequest};
|
|
2112
|
+
});
|
|
1860
2113
|
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
maxWidth: SHARE_WIDTH,
|
|
1871
|
-
maxHeight: SHARE_HEIGHT,
|
|
1872
|
-
idealWidth: SHARE_WIDTH,
|
|
1873
|
-
idealHeight: SHARE_HEIGHT,
|
|
1874
|
-
},
|
|
1875
|
-
},
|
|
1876
|
-
firefox: fireFoxOptions,
|
|
1877
|
-
})
|
|
1878
|
-
);
|
|
2114
|
+
it('should delete audio and video state machines when leaving the meeting', async () => {
|
|
2115
|
+
const leave = meeting.leave();
|
|
2116
|
+
|
|
2117
|
+
assert.exists(leave.then);
|
|
2118
|
+
await leave;
|
|
2119
|
+
|
|
2120
|
+
assert.isNull(meeting.audio);
|
|
2121
|
+
assert.isNull(meeting.video);
|
|
2122
|
+
});
|
|
1879
2123
|
});
|
|
2124
|
+
it('should leave the meeting without leaving resource', async () => {
|
|
2125
|
+
const leave = meeting.leave({resourceId: null});
|
|
1880
2126
|
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
};
|
|
2127
|
+
assert.exists(leave.then);
|
|
2128
|
+
await leave;
|
|
2129
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2130
|
+
locusUrl: meeting.locusUrl,
|
|
2131
|
+
correlationId: meeting.correlationId,
|
|
2132
|
+
selfId: meeting.selfId,
|
|
2133
|
+
resourceId: null,
|
|
2134
|
+
deviceUrl: meeting.deviceUrl,
|
|
2135
|
+
});
|
|
2136
|
+
});
|
|
2137
|
+
it('should leave the meeting on the resource', async () => {
|
|
2138
|
+
const leave = meeting.leave();
|
|
1894
2139
|
|
|
1895
|
-
|
|
2140
|
+
assert.exists(leave.then);
|
|
2141
|
+
await leave;
|
|
2142
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2143
|
+
locusUrl: meeting.locusUrl,
|
|
2144
|
+
correlationId: meeting.correlationId,
|
|
2145
|
+
selfId: meeting.selfId,
|
|
2146
|
+
resourceId: meeting.resourceId,
|
|
2147
|
+
deviceUrl: meeting.deviceUrl
|
|
2148
|
+
});
|
|
2149
|
+
});
|
|
2150
|
+
it('should leave the meeting on the resource with reason', async () => {
|
|
2151
|
+
const leave = meeting.leave({resourceId: meeting.resourceId, reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST});
|
|
1896
2152
|
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
maxWidth: SHARE_WIDTH,
|
|
1908
|
-
maxHeight: SHARE_HEIGHT,
|
|
1909
|
-
idealWidth: SHARE_WIDTH,
|
|
1910
|
-
idealHeight: SHARE_HEIGHT,
|
|
1911
|
-
},
|
|
1912
|
-
},
|
|
1913
|
-
firefox: fireFoxOptions,
|
|
1914
|
-
})
|
|
1915
|
-
);
|
|
2153
|
+
assert.exists(leave.then);
|
|
2154
|
+
await leave;
|
|
2155
|
+
assert.calledWith(meeting.meetingRequest.leaveMeeting, {
|
|
2156
|
+
locusUrl: meeting.locusUrl,
|
|
2157
|
+
correlationId: meeting.correlationId,
|
|
2158
|
+
selfId: meeting.selfId,
|
|
2159
|
+
resourceId: meeting.resourceId,
|
|
2160
|
+
deviceUrl: meeting.deviceUrl,
|
|
2161
|
+
reason: MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST
|
|
2162
|
+
});
|
|
1916
2163
|
});
|
|
1917
2164
|
});
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
assert.exists(meeting.stopShare);
|
|
2165
|
+
describe('#requestScreenShareFloor', () => {
|
|
2166
|
+
it('should have #requestScreenShareFloor', () => {
|
|
2167
|
+
assert.exists(meeting.requestScreenShareFloor);
|
|
1922
2168
|
});
|
|
1923
2169
|
beforeEach(() => {
|
|
1924
|
-
meeting.
|
|
1925
|
-
meeting.
|
|
2170
|
+
meeting.locusInfo.mediaShares = [{name: 'content', url: url1}];
|
|
2171
|
+
meeting.locusInfo.self = {url: url1};
|
|
2172
|
+
meeting.meetingRequest.changeMeetingFloor = sinon.stub().returns(Promise.resolve());
|
|
2173
|
+
meeting.mediaProperties.shareTrack = {}
|
|
2174
|
+
meeting.mediaProperties.mediaDirection.sendShare = true;
|
|
2175
|
+
meeting.state = 'JOINED';
|
|
1926
2176
|
});
|
|
1927
|
-
it('should
|
|
1928
|
-
const share = meeting.
|
|
2177
|
+
it('should send the share', async () => {
|
|
2178
|
+
const share = meeting.requestScreenShareFloor();
|
|
1929
2179
|
|
|
1930
2180
|
assert.exists(share.then);
|
|
1931
2181
|
await share;
|
|
1932
|
-
assert.calledOnce(meeting.
|
|
1933
|
-
});
|
|
1934
|
-
});
|
|
1935
|
-
|
|
1936
|
-
describe('#updateAudio', () => {
|
|
1937
|
-
const FAKE_AUDIO_TRACK = {
|
|
1938
|
-
id: 'fake audio track',
|
|
1939
|
-
getSettings: sinon.stub().returns({}),
|
|
1940
|
-
};
|
|
1941
|
-
|
|
1942
|
-
describe('when canUpdateMedia is true', () => {
|
|
1943
|
-
beforeEach(() => {
|
|
1944
|
-
meeting.canUpdateMedia = sinon.stub().returns(true);
|
|
1945
|
-
});
|
|
1946
|
-
describe('when options are valid', () => {
|
|
1947
|
-
beforeEach(() => {
|
|
1948
|
-
MeetingUtil.validateOptions = sinon.stub().returns(Promise.resolve());
|
|
1949
|
-
meeting.mediaProperties.mediaDirection = {
|
|
1950
|
-
sendAudio: false,
|
|
1951
|
-
sendVideo: true,
|
|
1952
|
-
sendShare: false,
|
|
1953
|
-
receiveAudio: false,
|
|
1954
|
-
receiveVideo: true,
|
|
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
|
-
}));
|
|
1986
|
-
});
|
|
1987
|
-
afterEach(() => {
|
|
1988
|
-
sinon.restore();
|
|
1989
|
-
});
|
|
2182
|
+
assert.calledOnce(meeting.meetingRequest.changeMeetingFloor);
|
|
1990
2183
|
});
|
|
1991
2184
|
});
|
|
1992
2185
|
|
|
@@ -2034,37 +2227,25 @@ describe('plugin-meetings', () => {
|
|
|
2034
2227
|
|
|
2035
2228
|
describe('#updateMedia', () => {
|
|
2036
2229
|
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
2230
|
|
|
2231
|
+
const createFakeLocalTrack = () => ({
|
|
2232
|
+
underlyingTrack: {id: 'fake underlying track'}
|
|
2233
|
+
});
|
|
2054
2234
|
beforeEach(() => {
|
|
2055
2235
|
sandbox = sinon.createSandbox();
|
|
2056
|
-
meeting.
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2236
|
+
meeting.audio = { enable: sinon.stub()};
|
|
2237
|
+
meeting.video = { enable: sinon.stub()};
|
|
2238
|
+
meeting.mediaProperties.audioTrack = createFakeLocalTrack();
|
|
2239
|
+
meeting.mediaProperties.videoTrack = createFakeLocalTrack();
|
|
2240
|
+
meeting.mediaProperties.shareTrack = createFakeLocalTrack();
|
|
2241
|
+
meeting.mediaProperties.mediaDirection = {
|
|
2242
|
+
sendAudio: true,
|
|
2243
|
+
sendVideo: true,
|
|
2244
|
+
sendShare: true,
|
|
2245
|
+
receiveAudio: true,
|
|
2246
|
+
receiveVideo: true,
|
|
2247
|
+
receiveShare: true,
|
|
2248
|
+
}
|
|
2068
2249
|
});
|
|
2069
2250
|
|
|
2070
2251
|
afterEach(() => {
|
|
@@ -2072,36 +2253,45 @@ describe('plugin-meetings', () => {
|
|
|
2072
2253
|
sandbox = null;
|
|
2073
2254
|
});
|
|
2074
2255
|
|
|
2075
|
-
|
|
2076
|
-
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
|
|
2082
|
-
|
|
2083
|
-
|
|
2084
|
-
|
|
2256
|
+
forEach(
|
|
2257
|
+
[
|
|
2258
|
+
{audioEnabled: true, enableMultistreamAudio: true},
|
|
2259
|
+
{audioEnabled: false, enableMultistreamAudio: false},
|
|
2260
|
+
],
|
|
2261
|
+
({audioEnabled, enableMultistreamAudio}) => {
|
|
2262
|
+
it(`should call enableMultistreamAudio with ${enableMultistreamAudio} if it is a multistream connection and audioEnabled: ${audioEnabled}`, async () => {
|
|
2263
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2264
|
+
enableMultistreamAudio: sinon.stub().resolves({}),
|
|
2265
|
+
};
|
|
2266
|
+
meeting.isMultistream = true;
|
|
2267
|
+
|
|
2268
|
+
await meeting.updateMedia({audioEnabled});
|
|
2269
|
+
|
|
2270
|
+
assert.calledOnceWithExactly(
|
|
2271
|
+
meeting.mediaProperties.webrtcMediaConnection.enableMultistreamAudio,
|
|
2272
|
+
enableMultistreamAudio
|
|
2273
|
+
);
|
|
2274
|
+
assert.calledOnceWithExactly(meeting.audio.enable, meeting, enableMultistreamAudio);
|
|
2275
|
+
});
|
|
2276
|
+
}
|
|
2277
|
+
);
|
|
2085
2278
|
|
|
2279
|
+
it('should use a queue if currently busy', async () => {
|
|
2086
2280
|
sandbox.stub(meeting, 'canUpdateMedia').returns(false);
|
|
2087
2281
|
meeting.mediaProperties.webrtcMediaConnection = {
|
|
2088
|
-
|
|
2282
|
+
update: sinon.stub().resolves({}),
|
|
2089
2283
|
};
|
|
2090
2284
|
|
|
2091
2285
|
let myPromiseResolved = false;
|
|
2092
2286
|
|
|
2093
2287
|
meeting
|
|
2094
|
-
.updateMedia({
|
|
2095
|
-
localStream: mockLocalStream,
|
|
2096
|
-
localShare: mockLocalShare,
|
|
2097
|
-
mediaSettings,
|
|
2098
|
-
})
|
|
2288
|
+
.updateMedia({audioEnabled: false, videoEnabled: false})
|
|
2099
2289
|
.then(() => {
|
|
2100
2290
|
myPromiseResolved = true;
|
|
2101
2291
|
});
|
|
2102
2292
|
|
|
2103
2293
|
// verify that nothing was done
|
|
2104
|
-
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.
|
|
2294
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2105
2295
|
|
|
2106
2296
|
// now trigger processing of the queue
|
|
2107
2297
|
meeting.canUpdateMedia.restore();
|
|
@@ -2110,22 +2300,22 @@ describe('plugin-meetings', () => {
|
|
|
2110
2300
|
meeting.processNextQueuedMediaUpdate();
|
|
2111
2301
|
await testUtils.flushPromises();
|
|
2112
2302
|
|
|
2113
|
-
// and check that
|
|
2114
|
-
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.
|
|
2303
|
+
// and check that update is called with the original args
|
|
2304
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.update);
|
|
2115
2305
|
assert.calledWith(
|
|
2116
|
-
meeting.mediaProperties.webrtcMediaConnection.
|
|
2306
|
+
meeting.mediaProperties.webrtcMediaConnection.update,
|
|
2117
2307
|
{
|
|
2118
|
-
|
|
2119
|
-
audio:
|
|
2120
|
-
video:
|
|
2121
|
-
screenShareVideo:
|
|
2308
|
+
localTracks: {
|
|
2309
|
+
audio: meeting.mediaProperties.audioTrack.underlyingTrack,
|
|
2310
|
+
video: meeting.mediaProperties.videoTrack.underlyingTrack,
|
|
2311
|
+
screenShareVideo: meeting.mediaProperties.shareTrack.underlyingTrack,
|
|
2122
2312
|
},
|
|
2123
|
-
|
|
2124
|
-
audio:
|
|
2125
|
-
video:
|
|
2126
|
-
screenShareVideo:
|
|
2127
|
-
remoteQualityLevel: 'HIGH',
|
|
2313
|
+
direction: {
|
|
2314
|
+
audio: 'inactive',
|
|
2315
|
+
video: 'inactive',
|
|
2316
|
+
screenShareVideo: 'sendrecv',
|
|
2128
2317
|
},
|
|
2318
|
+
remoteQualityLevel: 'HIGH',
|
|
2129
2319
|
}
|
|
2130
2320
|
);
|
|
2131
2321
|
assert.isTrue(myPromiseResolved);
|
|
@@ -2144,8 +2334,6 @@ describe('plugin-meetings', () => {
|
|
|
2144
2334
|
sendShare: false,
|
|
2145
2335
|
receiveVideo: true,
|
|
2146
2336
|
};
|
|
2147
|
-
meeting.getMediaStreams = sinon.stub().returns(Promise.resolve([]));
|
|
2148
|
-
meeting.updateVideo = sinon.stub().returns(Promise.resolve());
|
|
2149
2337
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2150
2338
|
meeting.mediaProperties.remoteVideoTrack = sinon
|
|
2151
2339
|
.stub()
|
|
@@ -2382,76 +2570,12 @@ describe('plugin-meetings', () => {
|
|
|
2382
2570
|
});
|
|
2383
2571
|
});
|
|
2384
2572
|
|
|
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
2573
|
describe('#setRemoteQualityLevel', () => {
|
|
2450
2574
|
let mediaDirection;
|
|
2451
2575
|
|
|
2452
2576
|
beforeEach(() => {
|
|
2453
2577
|
mediaDirection = {receiveAudio: true, receiveVideo: true, receiveShare: false};
|
|
2454
|
-
meeting.
|
|
2578
|
+
meeting.updateTranscodedMediaConnection = sinon.stub().returns(Promise.resolve());
|
|
2455
2579
|
meeting.mediaProperties.mediaDirection = mediaDirection;
|
|
2456
2580
|
});
|
|
2457
2581
|
|
|
@@ -2464,9 +2588,9 @@ describe('plugin-meetings', () => {
|
|
|
2464
2588
|
assert.equal(meeting.mediaProperties.remoteQualityLevel, CONSTANTS.QUALITY_LEVELS.LOW);
|
|
2465
2589
|
}));
|
|
2466
2590
|
|
|
2467
|
-
it('should call
|
|
2591
|
+
it('should call Meeting.updateTranscodedMediaConnection()', () =>
|
|
2468
2592
|
meeting.setRemoteQualityLevel(CONSTANTS.QUALITY_LEVELS.LOW).then(() => {
|
|
2469
|
-
assert.calledOnce(meeting.
|
|
2593
|
+
assert.calledOnce(meeting.updateTranscodedMediaConnection);
|
|
2470
2594
|
}));
|
|
2471
2595
|
|
|
2472
2596
|
it('should error if set to a invalid level', () => {
|
|
@@ -2487,7 +2611,6 @@ describe('plugin-meetings', () => {
|
|
|
2487
2611
|
meeting.meetingRequest.dialOut = sinon
|
|
2488
2612
|
.stub()
|
|
2489
2613
|
.returns(Promise.resolve({body: {locus: 'testData'}}));
|
|
2490
|
-
meeting.locusInfo.onFullLocus = sinon.stub().returns(Promise.resolve());
|
|
2491
2614
|
});
|
|
2492
2615
|
|
|
2493
2616
|
it('with no parameters triggers dial-in, delegating request to meetingRequest correctly', async () => {
|
|
@@ -2500,11 +2623,9 @@ describe('plugin-meetings', () => {
|
|
|
2500
2623
|
locusUrl: meeting.locusUrl,
|
|
2501
2624
|
clientUrl: meeting.deviceUrl,
|
|
2502
2625
|
});
|
|
2503
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2504
2626
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
2505
2627
|
|
|
2506
2628
|
meeting.meetingRequest.dialIn.resetHistory();
|
|
2507
|
-
meeting.locusInfo.onFullLocus.resetHistory();
|
|
2508
2629
|
|
|
2509
2630
|
// try again. the dial in urls should match
|
|
2510
2631
|
await meeting.usePhoneAudio();
|
|
@@ -2515,7 +2636,6 @@ describe('plugin-meetings', () => {
|
|
|
2515
2636
|
locusUrl: meeting.locusUrl,
|
|
2516
2637
|
clientUrl: meeting.deviceUrl,
|
|
2517
2638
|
});
|
|
2518
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2519
2639
|
assert.notCalled(meeting.meetingRequest.dialOut);
|
|
2520
2640
|
});
|
|
2521
2641
|
|
|
@@ -2532,11 +2652,9 @@ describe('plugin-meetings', () => {
|
|
|
2532
2652
|
clientUrl: meeting.deviceUrl,
|
|
2533
2653
|
phoneNumber,
|
|
2534
2654
|
});
|
|
2535
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2536
2655
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
2537
2656
|
|
|
2538
2657
|
meeting.meetingRequest.dialOut.resetHistory();
|
|
2539
|
-
meeting.locusInfo.onFullLocus.resetHistory();
|
|
2540
2658
|
|
|
2541
2659
|
// try again. the dial out urls should match
|
|
2542
2660
|
await meeting.usePhoneAudio(phoneNumber);
|
|
@@ -2548,7 +2666,6 @@ describe('plugin-meetings', () => {
|
|
|
2548
2666
|
clientUrl: meeting.deviceUrl,
|
|
2549
2667
|
phoneNumber,
|
|
2550
2668
|
});
|
|
2551
|
-
assert.calledWith(meeting.locusInfo.onFullLocus, 'testData');
|
|
2552
2669
|
assert.notCalled(meeting.meetingRequest.dialIn);
|
|
2553
2670
|
});
|
|
2554
2671
|
|
|
@@ -2593,6 +2710,9 @@ describe('plugin-meetings', () => {
|
|
|
2593
2710
|
const FAKE_CAPTCHA_IMAGE_URL = 'http://captchaimage';
|
|
2594
2711
|
const FAKE_CAPTCHA_AUDIO_URL = 'http://captchaaudio';
|
|
2595
2712
|
const FAKE_CAPTCHA_REFRESH_URL = 'http://captcharefresh';
|
|
2713
|
+
const FAKE_INSTALLED_ORG_ID = '123456';
|
|
2714
|
+
const FAKE_EXTRA_PARAMS = {mtid: 'm9fe0afd8c435e892afcce9ea25b97046', joinTXId: 'TSmrX61wNF'};
|
|
2715
|
+
let FAKE_OPTIONS;
|
|
2596
2716
|
const FAKE_MEETING_INFO = {
|
|
2597
2717
|
conversationUrl: 'some_convo_url',
|
|
2598
2718
|
locusUrl: 'some_locus_url',
|
|
@@ -2600,6 +2720,8 @@ describe('plugin-meetings', () => {
|
|
|
2600
2720
|
meetingNumber: '123456', // this.config.experimental.enableUnifiedMeetings
|
|
2601
2721
|
hostId: 'some_host_id', // this.owner;
|
|
2602
2722
|
};
|
|
2723
|
+
const FAKE_MEETING_INFO_LOOKUP_URL = 'meetingLookupUrl';
|
|
2724
|
+
|
|
2603
2725
|
const FAKE_SDK_CAPTCHA_INFO = {
|
|
2604
2726
|
captchaId: FAKE_CAPTCHA_ID,
|
|
2605
2727
|
verificationImageURL: FAKE_CAPTCHA_IMAGE_URL,
|
|
@@ -2613,18 +2735,26 @@ describe('plugin-meetings', () => {
|
|
|
2613
2735
|
refreshURL: `${FAKE_CAPTCHA_REFRESH_URL}-2`,
|
|
2614
2736
|
};
|
|
2615
2737
|
|
|
2738
|
+
beforeEach(() => {
|
|
2739
|
+
meeting.locusId = 'locus-id';
|
|
2740
|
+
meeting.id = 'meeting-id';
|
|
2741
|
+
FAKE_OPTIONS = {meetingId: meeting.id};
|
|
2742
|
+
});
|
|
2743
|
+
|
|
2616
2744
|
it('calls meetingInfoProvider with all the right parameters and parses the result', async () => {
|
|
2617
2745
|
meeting.attrs.meetingInfoProvider = {
|
|
2618
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2746
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2619
2747
|
};
|
|
2620
2748
|
meeting.requiredCaptcha = FAKE_SDK_CAPTCHA_INFO;
|
|
2621
2749
|
meeting.destination = FAKE_DESTINATION;
|
|
2622
2750
|
meeting.destinationType = FAKE_TYPE;
|
|
2751
|
+
meeting.config.installedOrgID = FAKE_INSTALLED_ORG_ID;
|
|
2623
2752
|
meeting.parseMeetingInfo = sinon.stub().returns(undefined);
|
|
2624
2753
|
|
|
2625
2754
|
await meeting.fetchMeetingInfo({
|
|
2626
2755
|
password: FAKE_PASSWORD,
|
|
2627
2756
|
captchaCode: FAKE_CAPTCHA_CODE,
|
|
2757
|
+
extraParams: FAKE_EXTRA_PARAMS,
|
|
2628
2758
|
});
|
|
2629
2759
|
|
|
2630
2760
|
assert.calledWith(
|
|
@@ -2632,11 +2762,15 @@ describe('plugin-meetings', () => {
|
|
|
2632
2762
|
FAKE_DESTINATION,
|
|
2633
2763
|
FAKE_TYPE,
|
|
2634
2764
|
FAKE_PASSWORD,
|
|
2635
|
-
{code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID}
|
|
2765
|
+
{code: FAKE_CAPTCHA_CODE, id: FAKE_CAPTCHA_ID},
|
|
2766
|
+
FAKE_INSTALLED_ORG_ID,
|
|
2767
|
+
meeting.locusId,
|
|
2768
|
+
FAKE_EXTRA_PARAMS,
|
|
2769
|
+
FAKE_OPTIONS
|
|
2636
2770
|
);
|
|
2637
2771
|
|
|
2638
|
-
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
|
|
2639
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2772
|
+
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}, FAKE_DESTINATION);
|
|
2773
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL});
|
|
2640
2774
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
|
|
2641
2775
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2642
2776
|
assert.equal(meeting.requiredCaptcha, null);
|
|
@@ -2651,7 +2785,7 @@ describe('plugin-meetings', () => {
|
|
|
2651
2785
|
|
|
2652
2786
|
it('calls meetingInfoProvider with all the right parameters and parses the result when random delay is applied', async () => {
|
|
2653
2787
|
meeting.attrs.meetingInfoProvider = {
|
|
2654
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2788
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2655
2789
|
};
|
|
2656
2790
|
meeting.destination = FAKE_DESTINATION;
|
|
2657
2791
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -2674,13 +2808,17 @@ describe('plugin-meetings', () => {
|
|
|
2674
2808
|
FAKE_DESTINATION,
|
|
2675
2809
|
FAKE_TYPE,
|
|
2676
2810
|
null,
|
|
2677
|
-
null
|
|
2811
|
+
null,
|
|
2812
|
+
undefined,
|
|
2813
|
+
meeting.locusId,
|
|
2814
|
+
{},
|
|
2815
|
+
{meetingId: meeting.id}
|
|
2678
2816
|
);
|
|
2679
2817
|
|
|
2680
2818
|
// parseMeeting info
|
|
2681
|
-
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO}, FAKE_DESTINATION);
|
|
2819
|
+
assert.calledWith(meeting.parseMeetingInfo, {body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}, FAKE_DESTINATION);
|
|
2682
2820
|
|
|
2683
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2821
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: FAKE_MEETING_INFO_LOOKUP_URL});
|
|
2684
2822
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2685
2823
|
assert.equal(meeting.requiredCaptcha, null);
|
|
2686
2824
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.NOT_REQUIRED);
|
|
@@ -2696,7 +2834,7 @@ describe('plugin-meetings', () => {
|
|
|
2696
2834
|
|
|
2697
2835
|
it('fails if captchaCode is provided when captcha not needed', async () => {
|
|
2698
2836
|
meeting.attrs.meetingInfoProvider = {
|
|
2699
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2837
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2700
2838
|
};
|
|
2701
2839
|
meeting.requiredCaptcha = null;
|
|
2702
2840
|
meeting.destination = FAKE_DESTINATION;
|
|
@@ -2715,7 +2853,7 @@ describe('plugin-meetings', () => {
|
|
|
2715
2853
|
|
|
2716
2854
|
it('fails if password is provided when not required', async () => {
|
|
2717
2855
|
meeting.attrs.meetingInfoProvider = {
|
|
2718
|
-
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO}),
|
|
2856
|
+
fetchMeetingInfo: sinon.stub().resolves({body: FAKE_MEETING_INFO, url: FAKE_MEETING_INFO_LOOKUP_URL}),
|
|
2719
2857
|
};
|
|
2720
2858
|
meeting.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
2721
2859
|
meeting.destination = FAKE_DESTINATION;
|
|
@@ -2748,10 +2886,15 @@ describe('plugin-meetings', () => {
|
|
|
2748
2886
|
FAKE_DESTINATION,
|
|
2749
2887
|
FAKE_TYPE,
|
|
2750
2888
|
null,
|
|
2751
|
-
null
|
|
2889
|
+
null,
|
|
2890
|
+
undefined,
|
|
2891
|
+
'locus-id',
|
|
2892
|
+
{},
|
|
2893
|
+
{meetingId: meeting.id},
|
|
2752
2894
|
);
|
|
2753
2895
|
|
|
2754
2896
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2897
|
+
assert.equal(meeting.meetingInfoFailureCode, 403004);
|
|
2755
2898
|
assert.equal(
|
|
2756
2899
|
meeting.meetingInfoFailureReason,
|
|
2757
2900
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
@@ -2760,6 +2903,38 @@ describe('plugin-meetings', () => {
|
|
|
2760
2903
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
2761
2904
|
});
|
|
2762
2905
|
|
|
2906
|
+
it('handles meetingInfoProvider policy error', async () => {
|
|
2907
|
+
meeting.destination = FAKE_DESTINATION;
|
|
2908
|
+
meeting.destinationType = FAKE_TYPE;
|
|
2909
|
+
meeting.attrs.meetingInfoProvider = {
|
|
2910
|
+
fetchMeetingInfo: sinon
|
|
2911
|
+
.stub()
|
|
2912
|
+
.throws(new MeetingInfoV2PolicyError(123456, FAKE_MEETING_INFO, 'a message')),
|
|
2913
|
+
};
|
|
2914
|
+
|
|
2915
|
+
await assert.isRejected(meeting.fetchMeetingInfo({}), PermissionError);
|
|
2916
|
+
|
|
2917
|
+
assert.calledWith(
|
|
2918
|
+
meeting.attrs.meetingInfoProvider.fetchMeetingInfo,
|
|
2919
|
+
FAKE_DESTINATION,
|
|
2920
|
+
FAKE_TYPE,
|
|
2921
|
+
null,
|
|
2922
|
+
null,
|
|
2923
|
+
undefined,
|
|
2924
|
+
'locus-id',
|
|
2925
|
+
{},
|
|
2926
|
+
{meetingId: meeting.id},
|
|
2927
|
+
);
|
|
2928
|
+
|
|
2929
|
+
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
2930
|
+
assert.equal(meeting.meetingInfoFailureCode, 123456);
|
|
2931
|
+
assert.equal(
|
|
2932
|
+
meeting.meetingInfoFailureReason,
|
|
2933
|
+
MEETING_INFO_FAILURE_REASON.POLICY
|
|
2934
|
+
);
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
|
|
2763
2938
|
it('handles meetingInfoProvider requiring captcha because of wrong password', async () => {
|
|
2764
2939
|
meeting.destination = FAKE_DESTINATION;
|
|
2765
2940
|
meeting.destinationType = FAKE_TYPE;
|
|
@@ -2782,7 +2957,11 @@ describe('plugin-meetings', () => {
|
|
|
2782
2957
|
FAKE_DESTINATION,
|
|
2783
2958
|
FAKE_TYPE,
|
|
2784
2959
|
'aaa',
|
|
2785
|
-
null
|
|
2960
|
+
null,
|
|
2961
|
+
undefined,
|
|
2962
|
+
'locus-id',
|
|
2963
|
+
{},
|
|
2964
|
+
{meetingId: meeting.id},
|
|
2786
2965
|
);
|
|
2787
2966
|
|
|
2788
2967
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
@@ -2790,6 +2969,7 @@ describe('plugin-meetings', () => {
|
|
|
2790
2969
|
meeting.meetingInfoFailureReason,
|
|
2791
2970
|
MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD
|
|
2792
2971
|
);
|
|
2972
|
+
assert.equal(meeting.meetingInfoFailureCode, 423005);
|
|
2793
2973
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.REQUIRED);
|
|
2794
2974
|
assert.deepEqual(meeting.requiredCaptcha, {
|
|
2795
2975
|
captchaId: FAKE_CAPTCHA_ID,
|
|
@@ -2822,7 +3002,11 @@ describe('plugin-meetings', () => {
|
|
|
2822
3002
|
FAKE_DESTINATION,
|
|
2823
3003
|
FAKE_TYPE,
|
|
2824
3004
|
'aaa',
|
|
2825
|
-
{code: 'bbb', id: FAKE_CAPTCHA_ID}
|
|
3005
|
+
{code: 'bbb', id: FAKE_CAPTCHA_ID},
|
|
3006
|
+
undefined,
|
|
3007
|
+
'locus-id',
|
|
3008
|
+
{},
|
|
3009
|
+
{meetingId: meeting.id}
|
|
2826
3010
|
);
|
|
2827
3011
|
|
|
2828
3012
|
assert.deepEqual(meeting.meetingInfo, {});
|
|
@@ -2851,10 +3035,14 @@ describe('plugin-meetings', () => {
|
|
|
2851
3035
|
FAKE_DESTINATION,
|
|
2852
3036
|
FAKE_TYPE,
|
|
2853
3037
|
'aaa',
|
|
2854
|
-
null
|
|
3038
|
+
null,
|
|
3039
|
+
undefined,
|
|
3040
|
+
'locus-id',
|
|
3041
|
+
{},
|
|
3042
|
+
{meetingId: meeting.id},
|
|
2855
3043
|
);
|
|
2856
3044
|
|
|
2857
|
-
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
3045
|
+
assert.deepEqual(meeting.meetingInfo, {...FAKE_MEETING_INFO, meetingLookupUrl: undefined});
|
|
2858
3046
|
assert.equal(meeting.meetingInfoFailureReason, MEETING_INFO_FAILURE_REASON.NONE);
|
|
2859
3047
|
assert.equal(meeting.passwordStatus, PASSWORD_STATUS.VERIFIED);
|
|
2860
3048
|
assert.equal(meeting.requiredCaptcha, null);
|
|
@@ -2896,7 +3084,11 @@ describe('plugin-meetings', () => {
|
|
|
2896
3084
|
FAKE_DESTINATION,
|
|
2897
3085
|
FAKE_TYPE,
|
|
2898
3086
|
'aaa',
|
|
2899
|
-
{code: 'bbb', id: FAKE_CAPTCHA_ID}
|
|
3087
|
+
{code: 'bbb', id: FAKE_CAPTCHA_ID},
|
|
3088
|
+
undefined,
|
|
3089
|
+
'locus-id',
|
|
3090
|
+
{},
|
|
3091
|
+
{meetingId: meeting.id},
|
|
2900
3092
|
);
|
|
2901
3093
|
|
|
2902
3094
|
assert.deepEqual(meeting.meetingInfo, FAKE_MEETING_INFO);
|
|
@@ -3041,6 +3233,17 @@ describe('plugin-meetings', () => {
|
|
|
3041
3233
|
});
|
|
3042
3234
|
});
|
|
3043
3235
|
|
|
3236
|
+
describe('#postMetrics', () => {
|
|
3237
|
+
it('should have #postMetrics', () => {
|
|
3238
|
+
assert.exists(meeting.postMetrics);
|
|
3239
|
+
});
|
|
3240
|
+
|
|
3241
|
+
it('should trigger `postMetrics`', async () => {
|
|
3242
|
+
await meeting.postMetrics(eventType.LEAVE);
|
|
3243
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.LEAVE});
|
|
3244
|
+
});
|
|
3245
|
+
});
|
|
3246
|
+
|
|
3044
3247
|
describe('#endMeeting for all', () => {
|
|
3045
3248
|
let sandbox;
|
|
3046
3249
|
|
|
@@ -3061,16 +3264,12 @@ describe('plugin-meetings', () => {
|
|
|
3061
3264
|
.stub()
|
|
3062
3265
|
.returns(Promise.resolve({body: 'test'}));
|
|
3063
3266
|
meeting.locusInfo.onFullLocus = sinon.stub().returns(true);
|
|
3064
|
-
meeting.
|
|
3065
|
-
meeting.closeLocalShare = sinon.stub().returns(Promise.resolve());
|
|
3267
|
+
meeting.cleanupLocalTracks = sinon.stub().returns(Promise.resolve());
|
|
3066
3268
|
meeting.closeRemoteStream = sinon.stub().returns(Promise.resolve());
|
|
3067
3269
|
sandbox.stub(meeting, 'closeRemoteTracks').returns(Promise.resolve());
|
|
3068
3270
|
meeting.closePeerConnections = sinon.stub().returns(Promise.resolve());
|
|
3069
|
-
meeting.unsetLocalVideoTrack = sinon.stub().returns(true);
|
|
3070
|
-
meeting.unsetLocalShareTrack = sinon.stub().returns(true);
|
|
3071
3271
|
meeting.unsetRemoteTracks = sinon.stub();
|
|
3072
3272
|
meeting.statsAnalyzer = {stopAnalyzer: sinon.stub().resolves()};
|
|
3073
|
-
meeting.unsetRemoteStream = sinon.stub().returns(true);
|
|
3074
3273
|
meeting.unsetPeerConnections = sinon.stub().returns(true);
|
|
3075
3274
|
meeting.logger.error = sinon.stub().returns(true);
|
|
3076
3275
|
meeting.updateLLMConnection = sinon.stub().returns(Promise.resolve());
|
|
@@ -3089,12 +3288,9 @@ describe('plugin-meetings', () => {
|
|
|
3089
3288
|
assert.exists(endMeetingForAll.then);
|
|
3090
3289
|
await endMeetingForAll;
|
|
3091
3290
|
assert.calledOnce(meeting?.meetingRequest?.endMeetingForAll);
|
|
3092
|
-
assert.calledOnce(meeting?.
|
|
3093
|
-
assert.calledOnce(meeting?.closeLocalShare);
|
|
3291
|
+
assert.calledOnce(meeting?.cleanupLocalTracks);
|
|
3094
3292
|
assert.calledOnce(meeting?.closeRemoteTracks);
|
|
3095
3293
|
assert.calledOnce(meeting?.closePeerConnections);
|
|
3096
|
-
assert.calledOnce(meeting?.unsetLocalVideoTrack);
|
|
3097
|
-
assert.calledOnce(meeting?.unsetLocalShareTrack);
|
|
3098
3294
|
assert.calledOnce(meeting?.unsetRemoteTracks);
|
|
3099
3295
|
assert.calledOnce(meeting?.unsetPeerConnections);
|
|
3100
3296
|
});
|
|
@@ -3105,11 +3301,9 @@ describe('plugin-meetings', () => {
|
|
|
3105
3301
|
|
|
3106
3302
|
beforeEach(() => {
|
|
3107
3303
|
sandbox = sinon.createSandbox();
|
|
3108
|
-
sandbox.stub(meeting, '
|
|
3109
|
-
sandbox.stub(meeting, 'closeLocalShare');
|
|
3304
|
+
sandbox.stub(meeting, 'cleanupLocalTracks');
|
|
3110
3305
|
|
|
3111
3306
|
sandbox.stub(meeting.mediaProperties, 'setMediaDirection');
|
|
3112
|
-
sandbox.stub(meeting.mediaProperties, 'unsetMediaTracks');
|
|
3113
3307
|
|
|
3114
3308
|
sandbox.stub(meeting.reconnectionManager, 'reconnectMedia').returns(Promise.resolve());
|
|
3115
3309
|
sandbox
|
|
@@ -3182,14 +3376,12 @@ describe('plugin-meetings', () => {
|
|
|
3182
3376
|
|
|
3183
3377
|
// beacuse we are calling callback so we need to wait
|
|
3184
3378
|
|
|
3185
|
-
assert.called(meeting.
|
|
3186
|
-
assert.called(meeting.closeLocalShare);
|
|
3379
|
+
assert.called(meeting.cleanupLocalTracks);
|
|
3187
3380
|
|
|
3188
3381
|
// give queued Promise callbacks a chance to run
|
|
3189
3382
|
await Promise.resolve();
|
|
3190
3383
|
|
|
3191
3384
|
assert.called(meeting.mediaProperties.setMediaDirection);
|
|
3192
|
-
assert.called(meeting.mediaProperties.unsetMediaTracks);
|
|
3193
3385
|
|
|
3194
3386
|
assert.calledWith(meeting.reconnectionManager.reconnectMedia, {
|
|
3195
3387
|
mediaDirection: {
|
|
@@ -3268,41 +3460,319 @@ describe('plugin-meetings', () => {
|
|
|
3268
3460
|
}
|
|
3269
3461
|
});
|
|
3270
3462
|
|
|
3271
|
-
it('should postEvent on moveFrom ', async () => {
|
|
3272
|
-
await meeting.moveFrom('resourceId');
|
|
3463
|
+
it('should postEvent on moveFrom ', async () => {
|
|
3464
|
+
await meeting.moveFrom('resourceId');
|
|
3465
|
+
|
|
3466
|
+
assert.calledWithMatch(Metrics.postEvent, {event: eventType.MOVE_MEDIA});
|
|
3467
|
+
});
|
|
3468
|
+
|
|
3469
|
+
it('should call `MeetingUtil.joinMeetingOptions` with resourceId', async () => {
|
|
3470
|
+
sinon.spy(MeetingUtil, 'joinMeetingOptions');
|
|
3471
|
+
await meeting.moveFrom('resourceId');
|
|
3472
|
+
|
|
3473
|
+
assert.calledWith(MeetingUtil.joinMeetingOptions, meeting);
|
|
3474
|
+
assert.calledWith(MeetingUtil.leaveMeeting, meeting, {
|
|
3475
|
+
resourceId: 'resourceId',
|
|
3476
|
+
correlationId: meeting.correlationId,
|
|
3477
|
+
moveMeeting: true,
|
|
3478
|
+
});
|
|
3479
|
+
|
|
3480
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3481
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_FROM_SUCCESS);
|
|
3482
|
+
});
|
|
3483
|
+
|
|
3484
|
+
it('should throw an error if moveFrom call fails', async () => {
|
|
3485
|
+
MeetingUtil.joinMeeting = sinon.stub().returns(Promise.reject());
|
|
3486
|
+
try {
|
|
3487
|
+
await meeting.moveFrom('resourceId');
|
|
3488
|
+
} catch {
|
|
3489
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3490
|
+
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_FROM_FAILURE, {
|
|
3491
|
+
correlation_id: meeting.correlationId,
|
|
3492
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3493
|
+
reason: sinon.match.any,
|
|
3494
|
+
stack: sinon.match.any,
|
|
3495
|
+
});
|
|
3496
|
+
}
|
|
3497
|
+
});
|
|
3498
|
+
});
|
|
3499
|
+
describe('Local tracks publishing', () => {
|
|
3500
|
+
let audioTrack;
|
|
3501
|
+
let videoTrack;
|
|
3502
|
+
let videoShareTrack;
|
|
3503
|
+
let createMuteStateStub;
|
|
3504
|
+
let LocalDisplayTrackConstructorStub;
|
|
3505
|
+
let LocalMicrophoneTrackConstructorStub;
|
|
3506
|
+
let LocalCameraTrackConstructorStub;
|
|
3507
|
+
let fakeLocalDisplayTrack;
|
|
3508
|
+
let fakeLocalMicrophoneTrack;
|
|
3509
|
+
let fakeLocalCameraTrack;
|
|
3510
|
+
|
|
3511
|
+
beforeEach(() => {
|
|
3512
|
+
audioTrack = {
|
|
3513
|
+
id: 'audio track',
|
|
3514
|
+
getSettings: sinon.stub().returns({}),
|
|
3515
|
+
on: sinon.stub(),
|
|
3516
|
+
off: sinon.stub(),
|
|
3517
|
+
};
|
|
3518
|
+
videoTrack = {
|
|
3519
|
+
id: 'video track',
|
|
3520
|
+
getSettings: sinon.stub().returns({}),
|
|
3521
|
+
on: sinon.stub(),
|
|
3522
|
+
off: sinon.stub(),
|
|
3523
|
+
};
|
|
3524
|
+
videoShareTrack = {
|
|
3525
|
+
id: 'share track',
|
|
3526
|
+
on: sinon.stub(),
|
|
3527
|
+
off: sinon.stub(),
|
|
3528
|
+
getSettings: sinon.stub().returns({}),
|
|
3529
|
+
};
|
|
3530
|
+
meeting.requestScreenShareFloor = sinon.stub().resolves({});
|
|
3531
|
+
meeting.releaseScreenShareFloor = sinon.stub().resolves({});
|
|
3532
|
+
meeting.mediaProperties.mediaDirection = {
|
|
3533
|
+
sendAudio: 'fake value', // using non-boolean here so that we can check that these values are untouched in tests
|
|
3534
|
+
sendVideo: 'fake value',
|
|
3535
|
+
sendShare: false,
|
|
3536
|
+
};
|
|
3537
|
+
meeting.isMultistream = true;
|
|
3538
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
3539
|
+
publishTrack: sinon.stub().resolves({}),
|
|
3540
|
+
unpublishTrack: sinon.stub().resolves({}),
|
|
3541
|
+
};
|
|
3542
|
+
meeting.audio = { handleLocalTrackChange: sinon.stub()};
|
|
3543
|
+
meeting.video = { handleLocalTrackChange: sinon.stub()};
|
|
3544
|
+
|
|
3545
|
+
const createFakeLocalTrack = (originalTrack) => ({
|
|
3546
|
+
on: sinon.stub(),
|
|
3547
|
+
off: sinon.stub(),
|
|
3548
|
+
stop: sinon.stub(),
|
|
3549
|
+
originalTrack,
|
|
3550
|
+
});
|
|
3551
|
+
|
|
3552
|
+
// setup mock constructors for webrtc-core local track classes in such a way
|
|
3553
|
+
// that they return the original track correctly (this is needed for unpublish() API tests)
|
|
3554
|
+
LocalDisplayTrackConstructorStub = sinon
|
|
3555
|
+
.stub(InternalMediaCoreModule, 'LocalDisplayTrack')
|
|
3556
|
+
.callsFake((stream) => {
|
|
3557
|
+
fakeLocalDisplayTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3558
|
+
return fakeLocalDisplayTrack;
|
|
3559
|
+
});
|
|
3560
|
+
LocalMicrophoneTrackConstructorStub = sinon
|
|
3561
|
+
.stub(InternalMediaCoreModule, 'LocalMicrophoneTrack')
|
|
3562
|
+
.callsFake((stream) => {
|
|
3563
|
+
fakeLocalMicrophoneTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3564
|
+
return fakeLocalMicrophoneTrack;
|
|
3565
|
+
});
|
|
3566
|
+
LocalCameraTrackConstructorStub = sinon
|
|
3567
|
+
.stub(InternalMediaCoreModule, 'LocalCameraTrack')
|
|
3568
|
+
.callsFake((stream) => {
|
|
3569
|
+
fakeLocalCameraTrack = createFakeLocalTrack(stream.getTracks()[0]);
|
|
3570
|
+
return fakeLocalCameraTrack;
|
|
3571
|
+
});
|
|
3572
|
+
|
|
3573
|
+
createMuteStateStub = sinon
|
|
3574
|
+
.stub(MuteStateModule, 'createMuteState')
|
|
3575
|
+
.returns({id: 'fake mute state instance'});
|
|
3576
|
+
});
|
|
3577
|
+
describe('#publishTracks', () => {
|
|
3578
|
+
it('fails if there is no media connection', async () => {
|
|
3579
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3580
|
+
await assert.isRejected(meeting.publishTracks({audio: {id: 'some audio track'}}));
|
|
3581
|
+
});
|
|
3582
|
+
|
|
3583
|
+
const checkAudioPublished = (track) => {
|
|
3584
|
+
assert.calledOnceWithExactly(meeting.audio.handleLocalTrackChange, meeting);
|
|
3585
|
+
assert.calledWith(
|
|
3586
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3587
|
+
track
|
|
3588
|
+
);
|
|
3589
|
+
assert.equal(meeting.mediaProperties.audioTrack, track);
|
|
3590
|
+
// check that sendAudio hasn't been touched
|
|
3591
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
3592
|
+
};
|
|
3593
|
+
|
|
3594
|
+
const checkVideoPublished = (track) => {
|
|
3595
|
+
assert.calledOnceWithExactly(meeting.video.handleLocalTrackChange, meeting);
|
|
3596
|
+
assert.calledWith(
|
|
3597
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3598
|
+
track
|
|
3599
|
+
);
|
|
3600
|
+
assert.equal(meeting.mediaProperties.videoTrack, track);
|
|
3601
|
+
// check that sendVideo hasn't been touched
|
|
3602
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
3603
|
+
};
|
|
3604
|
+
|
|
3605
|
+
const checkScreenShareVideoPublished = (track) => {
|
|
3606
|
+
assert.calledOnce(meeting.requestScreenShareFloor);
|
|
3607
|
+
|
|
3608
|
+
assert.calledWith(
|
|
3609
|
+
meeting.mediaProperties.webrtcMediaConnection.publishTrack,
|
|
3610
|
+
track
|
|
3611
|
+
);
|
|
3612
|
+
assert.equal(meeting.mediaProperties.shareTrack, track);
|
|
3613
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, true);
|
|
3614
|
+
};
|
|
3615
|
+
|
|
3616
|
+
it('requests screen share floor and publishes the screen share video track', async () => {
|
|
3617
|
+
await meeting.publishTracks({screenShare: {video: videoShareTrack}});
|
|
3618
|
+
|
|
3619
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3620
|
+
checkScreenShareVideoPublished(videoShareTrack);
|
|
3621
|
+
});
|
|
3622
|
+
|
|
3623
|
+
it('updates MuteState instance and publishes the track for main audio', async () => {
|
|
3624
|
+
await meeting.publishTracks({microphone: audioTrack});
|
|
3625
|
+
|
|
3626
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3627
|
+
checkAudioPublished(audioTrack);
|
|
3628
|
+
});
|
|
3629
|
+
|
|
3630
|
+
it('updates MuteState instance and publishes the track for main video', async () => {
|
|
3631
|
+
await meeting.publishTracks({camera: videoTrack});
|
|
3632
|
+
|
|
3633
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3634
|
+
checkVideoPublished(videoTrack);
|
|
3635
|
+
});
|
|
3636
|
+
|
|
3637
|
+
it('publishes audio, video and screen share together', async () => {
|
|
3638
|
+
await meeting.publishTracks({
|
|
3639
|
+
microphone: audioTrack,
|
|
3640
|
+
camera: videoTrack,
|
|
3641
|
+
screenShare: {
|
|
3642
|
+
video: videoShareTrack,
|
|
3643
|
+
},
|
|
3644
|
+
});
|
|
3645
|
+
|
|
3646
|
+
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.publishTrack);
|
|
3647
|
+
checkAudioPublished(audioTrack);
|
|
3648
|
+
checkVideoPublished(videoTrack);
|
|
3649
|
+
checkScreenShareVideoPublished(videoShareTrack);
|
|
3650
|
+
});
|
|
3651
|
+
});
|
|
3652
|
+
it('creates instance and publishes with annotation info', async () => {
|
|
3653
|
+
const annotationInfo = {
|
|
3654
|
+
version: '1',
|
|
3655
|
+
policy: ANNOTATION_POLICY.APPROVAL,
|
|
3656
|
+
};
|
|
3657
|
+
await meeting.publishTracks({annotationInfo});
|
|
3658
|
+
assert.equal(meeting.annotationInfo, annotationInfo);
|
|
3659
|
+
});
|
|
3660
|
+
|
|
3661
|
+
describe('unpublishTracks', () => {
|
|
3662
|
+
beforeEach(async () => {
|
|
3663
|
+
await meeting.publishTracks({
|
|
3664
|
+
microphone: audioTrack,
|
|
3665
|
+
camera: videoTrack,
|
|
3666
|
+
screenShare: {video: videoShareTrack},
|
|
3667
|
+
});
|
|
3668
|
+
});
|
|
3669
|
+
|
|
3670
|
+
const checkAudioUnpublished = () => {
|
|
3671
|
+
assert.calledWith(
|
|
3672
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3673
|
+
audioTrack
|
|
3674
|
+
);
|
|
3675
|
+
|
|
3676
|
+
assert.equal(meeting.mediaProperties.audioTrack, null);
|
|
3677
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendAudio, 'fake value');
|
|
3678
|
+
};
|
|
3679
|
+
|
|
3680
|
+
const checkVideoUnpublished = () => {
|
|
3681
|
+
assert.calledWith(
|
|
3682
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3683
|
+
videoTrack
|
|
3684
|
+
);
|
|
3685
|
+
|
|
3686
|
+
assert.equal(meeting.mediaProperties.videoTrack, null);
|
|
3687
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendVideo, 'fake value');
|
|
3688
|
+
};
|
|
3689
|
+
|
|
3690
|
+
const checkScreenShareVideoUnpublished = () => {
|
|
3691
|
+
assert.calledWith(
|
|
3692
|
+
meeting.mediaProperties.webrtcMediaConnection.unpublishTrack,
|
|
3693
|
+
videoShareTrack
|
|
3694
|
+
);
|
|
3695
|
+
|
|
3696
|
+
assert.calledOnce(meeting.requestScreenShareFloor);
|
|
3697
|
+
|
|
3698
|
+
assert.equal(meeting.mediaProperties.shareTrack, null);
|
|
3699
|
+
assert.equal(meeting.mediaProperties.mediaDirection.sendShare, false);
|
|
3700
|
+
};
|
|
3701
|
+
|
|
3702
|
+
it('fails if there is no media connection', async () => {
|
|
3703
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3704
|
+
await assert.isRejected(
|
|
3705
|
+
meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack])
|
|
3706
|
+
);
|
|
3707
|
+
});
|
|
3708
|
+
|
|
3709
|
+
it('un-publishes the tracks correctly (all 3 together)', async () => {
|
|
3710
|
+
await meeting.unpublishTracks([audioTrack, videoTrack, videoShareTrack]);
|
|
3711
|
+
|
|
3712
|
+
assert.calledThrice(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3713
|
+
checkAudioUnpublished();
|
|
3714
|
+
checkVideoUnpublished();
|
|
3715
|
+
checkScreenShareVideoUnpublished();
|
|
3716
|
+
});
|
|
3717
|
+
|
|
3718
|
+
it('un-publishes the audio track correctly', async () => {
|
|
3719
|
+
await meeting.unpublishTracks([audioTrack]);
|
|
3720
|
+
|
|
3721
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3722
|
+
checkAudioUnpublished();
|
|
3723
|
+
});
|
|
3724
|
+
|
|
3725
|
+
it('un-publishes the video track correctly', async () => {
|
|
3726
|
+
await meeting.unpublishTracks([videoTrack]);
|
|
3273
3727
|
|
|
3274
|
-
|
|
3275
|
-
|
|
3728
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3729
|
+
checkVideoUnpublished();
|
|
3730
|
+
});
|
|
3276
3731
|
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
await meeting.moveFrom('resourceId');
|
|
3732
|
+
it('un-publishes the screen share video track correctly', async () => {
|
|
3733
|
+
await meeting.unpublishTracks([videoShareTrack]);
|
|
3280
3734
|
|
|
3281
|
-
|
|
3282
|
-
|
|
3283
|
-
resourceId: 'resourceId',
|
|
3284
|
-
correlationId: meeting.correlationId,
|
|
3285
|
-
moveMeeting: true,
|
|
3735
|
+
assert.calledOnce(meeting.mediaProperties.webrtcMediaConnection.unpublishTrack);
|
|
3736
|
+
checkScreenShareVideoUnpublished();
|
|
3286
3737
|
});
|
|
3738
|
+
});
|
|
3739
|
+
});
|
|
3740
|
+
});
|
|
3287
3741
|
|
|
3288
|
-
|
|
3289
|
-
|
|
3742
|
+
describe('#enableMusicMode', () => {
|
|
3743
|
+
beforeEach(() => {
|
|
3744
|
+
meeting.isMultistream = true;
|
|
3745
|
+
meeting.mediaProperties.webrtcMediaConnection = {
|
|
3746
|
+
setCodecParameters: sinon.stub().resolves({}),
|
|
3747
|
+
deleteCodecParameters: sinon.stub().resolves({}),
|
|
3748
|
+
};
|
|
3749
|
+
});
|
|
3750
|
+
[
|
|
3751
|
+
{shouldEnableMusicMode: true},
|
|
3752
|
+
{shouldEnableMusicMode: false},
|
|
3753
|
+
].forEach(({shouldEnableMusicMode}) => {
|
|
3754
|
+
it(`fails if there is no media connection for shouldEnableMusicMode: ${shouldEnableMusicMode}`, async () => {
|
|
3755
|
+
meeting.mediaProperties.webrtcMediaConnection = undefined;
|
|
3756
|
+
await assert.isRejected(meeting.enableMusicMode(shouldEnableMusicMode));
|
|
3290
3757
|
});
|
|
3758
|
+
});
|
|
3291
3759
|
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3295
|
-
|
|
3296
|
-
|
|
3297
|
-
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
3298
|
-
assert.calledWith(Metrics.sendBehavioralMetric, BEHAVIORAL_METRICS.MOVE_FROM_FAILURE, {
|
|
3299
|
-
correlation_id: meeting.correlationId,
|
|
3300
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3301
|
-
reason: sinon.match.any,
|
|
3302
|
-
stack: sinon.match.any,
|
|
3303
|
-
});
|
|
3304
|
-
}
|
|
3760
|
+
it('should set the codec parameters when shouldEnableMusicMode is true', async () => {
|
|
3761
|
+
await meeting.enableMusicMode(true);
|
|
3762
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.webrtcMediaConnection.setCodecParameters, MediaType.AudioMain, {
|
|
3763
|
+
maxaveragebitrate: '64000',
|
|
3764
|
+
maxplaybackrate: '48000',
|
|
3305
3765
|
});
|
|
3766
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters);
|
|
3767
|
+
});
|
|
3768
|
+
|
|
3769
|
+
it('should set the codec parameters when shouldEnableMusicMode is false', async () => {
|
|
3770
|
+
await meeting.enableMusicMode(false);
|
|
3771
|
+
assert.calledOnceWithExactly(meeting.mediaProperties.webrtcMediaConnection.deleteCodecParameters, MediaType.AudioMain, [
|
|
3772
|
+
'maxaveragebitrate',
|
|
3773
|
+
'maxplaybackrate',
|
|
3774
|
+
]);
|
|
3775
|
+
assert.notCalled(meeting.mediaProperties.webrtcMediaConnection.setCodecParameters);
|
|
3306
3776
|
});
|
|
3307
3777
|
});
|
|
3308
3778
|
|
|
@@ -3446,75 +3916,6 @@ describe('plugin-meetings', () => {
|
|
|
3446
3916
|
);
|
|
3447
3917
|
});
|
|
3448
3918
|
});
|
|
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);
|
|
3506
|
-
assert.calledWith(
|
|
3507
|
-
TriggerProxy.trigger,
|
|
3508
|
-
sinon.match.instanceOf(Meeting),
|
|
3509
|
-
{file: 'meeting/index', function: 'setLocalShareTrack'},
|
|
3510
|
-
'media:ready'
|
|
3511
|
-
);
|
|
3512
|
-
assert.calledOnce(meeting.mediaProperties.setLocalShareTrack);
|
|
3513
|
-
assert.equal(meeting.mediaProperties.localStream, undefined);
|
|
3514
|
-
meeting.mediaProperties.shareTrack.onended();
|
|
3515
|
-
assert.calledOnce(meeting.stopShare);
|
|
3516
|
-
});
|
|
3517
|
-
});
|
|
3518
3919
|
describe('#setupMediaConnectionListeners', () => {
|
|
3519
3920
|
let eventListeners;
|
|
3520
3921
|
|
|
@@ -3527,48 +3928,49 @@ describe('plugin-meetings', () => {
|
|
|
3527
3928
|
eventListeners[event] = listener;
|
|
3528
3929
|
}),
|
|
3529
3930
|
};
|
|
3931
|
+
MediaUtil.createMediaStream.returns({id: 'stream'});
|
|
3530
3932
|
});
|
|
3531
3933
|
|
|
3532
3934
|
it('should register for all the correct RoapMediaConnection events', () => {
|
|
3533
3935
|
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[
|
|
3936
|
+
assert.isFunction(eventListeners[Event.ROAP_STARTED]);
|
|
3937
|
+
assert.isFunction(eventListeners[Event.ROAP_DONE]);
|
|
3938
|
+
assert.isFunction(eventListeners[Event.ROAP_FAILURE]);
|
|
3939
|
+
assert.isFunction(eventListeners[Event.ROAP_MESSAGE_TO_SEND]);
|
|
3940
|
+
assert.isFunction(eventListeners[Event.REMOTE_TRACK_ADDED]);
|
|
3941
|
+
assert.isFunction(eventListeners[Event.CONNECTION_STATE_CHANGED]);
|
|
3540
3942
|
});
|
|
3541
3943
|
|
|
3542
3944
|
it('should trigger a media:ready event when REMOTE_TRACK_ADDED is fired', () => {
|
|
3543
3945
|
meeting.setupMediaConnectionListeners();
|
|
3544
|
-
eventListeners[
|
|
3946
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3545
3947
|
track: 'track',
|
|
3546
|
-
type:
|
|
3948
|
+
type: RemoteTrackType.AUDIO,
|
|
3547
3949
|
});
|
|
3548
3950
|
assert.equal(TriggerProxy.trigger.getCall(1).args[2], 'media:ready');
|
|
3549
3951
|
assert.deepEqual(TriggerProxy.trigger.getCall(1).args[3], {
|
|
3550
3952
|
type: 'remoteAudio',
|
|
3551
|
-
stream:
|
|
3953
|
+
stream: {id: 'stream'},
|
|
3552
3954
|
});
|
|
3553
3955
|
|
|
3554
|
-
eventListeners[
|
|
3956
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3555
3957
|
track: 'track',
|
|
3556
|
-
type:
|
|
3958
|
+
type: RemoteTrackType.VIDEO,
|
|
3557
3959
|
});
|
|
3558
3960
|
assert.equal(TriggerProxy.trigger.getCall(2).args[2], 'media:ready');
|
|
3559
3961
|
assert.deepEqual(TriggerProxy.trigger.getCall(2).args[3], {
|
|
3560
3962
|
type: 'remoteVideo',
|
|
3561
|
-
stream:
|
|
3963
|
+
stream: {id: 'stream'},
|
|
3562
3964
|
});
|
|
3563
3965
|
|
|
3564
|
-
eventListeners[
|
|
3966
|
+
eventListeners[Event.REMOTE_TRACK_ADDED]({
|
|
3565
3967
|
track: 'track',
|
|
3566
|
-
type:
|
|
3968
|
+
type: RemoteTrackType.SCREENSHARE_VIDEO,
|
|
3567
3969
|
});
|
|
3568
3970
|
assert.equal(TriggerProxy.trigger.getCall(3).args[2], 'media:ready');
|
|
3569
3971
|
assert.deepEqual(TriggerProxy.trigger.getCall(3).args[3], {
|
|
3570
3972
|
type: 'remoteShare',
|
|
3571
|
-
stream:
|
|
3973
|
+
stream: {id: 'stream'},
|
|
3572
3974
|
});
|
|
3573
3975
|
});
|
|
3574
3976
|
|
|
@@ -3613,51 +4015,51 @@ describe('plugin-meetings', () => {
|
|
|
3613
4015
|
};
|
|
3614
4016
|
|
|
3615
4017
|
it('should send metrics for SdpOfferCreationError error', () => {
|
|
3616
|
-
const fakeError = new
|
|
4018
|
+
const fakeError = new Errors.SdpOfferCreationError(fakeErrorMessage, {
|
|
3617
4019
|
name: fakeErrorName,
|
|
3618
4020
|
cause: {name: fakeRootCauseName},
|
|
3619
4021
|
});
|
|
3620
4022
|
|
|
3621
|
-
eventListeners[
|
|
4023
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3622
4024
|
|
|
3623
4025
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3624
4026
|
checkBehavioralMetricSent(
|
|
3625
4027
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3626
|
-
|
|
4028
|
+
Errors.ErrorCode.SdpOfferCreationError,
|
|
3627
4029
|
fakeErrorMessage,
|
|
3628
4030
|
fakeRootCauseName
|
|
3629
4031
|
);
|
|
3630
4032
|
});
|
|
3631
4033
|
|
|
3632
4034
|
it('should send metrics for SdpOfferHandlingError error', () => {
|
|
3633
|
-
const fakeError = new
|
|
4035
|
+
const fakeError = new Errors.SdpOfferHandlingError(fakeErrorMessage, {
|
|
3634
4036
|
name: fakeErrorName,
|
|
3635
4037
|
cause: {name: fakeRootCauseName},
|
|
3636
4038
|
});
|
|
3637
4039
|
|
|
3638
|
-
eventListeners[
|
|
4040
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3639
4041
|
|
|
3640
4042
|
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3641
4043
|
checkBehavioralMetricSent(
|
|
3642
4044
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3643
|
-
|
|
4045
|
+
Errors.ErrorCode.SdpOfferHandlingError,
|
|
3644
4046
|
fakeErrorMessage,
|
|
3645
4047
|
fakeRootCauseName
|
|
3646
4048
|
);
|
|
3647
4049
|
});
|
|
3648
4050
|
|
|
3649
4051
|
it('should send metrics for SdpAnswerHandlingError error', () => {
|
|
3650
|
-
const fakeError = new
|
|
4052
|
+
const fakeError = new Errors.SdpAnswerHandlingError(fakeErrorMessage, {
|
|
3651
4053
|
name: fakeErrorName,
|
|
3652
4054
|
cause: {name: fakeRootCauseName},
|
|
3653
4055
|
});
|
|
3654
4056
|
|
|
3655
|
-
eventListeners[
|
|
4057
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3656
4058
|
|
|
3657
4059
|
checkMetricSent(eventType.REMOTE_SDP_RECEIVED);
|
|
3658
4060
|
checkBehavioralMetricSent(
|
|
3659
4061
|
BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE,
|
|
3660
|
-
|
|
4062
|
+
Errors.ErrorCode.SdpAnswerHandlingError,
|
|
3661
4063
|
fakeErrorMessage,
|
|
3662
4064
|
fakeRootCauseName
|
|
3663
4065
|
);
|
|
@@ -3665,15 +4067,15 @@ describe('plugin-meetings', () => {
|
|
|
3665
4067
|
|
|
3666
4068
|
it('should send metrics for SdpError error', () => {
|
|
3667
4069
|
// SdpError is usually without a cause
|
|
3668
|
-
const fakeError = new
|
|
4070
|
+
const fakeError = new Errors.SdpError(fakeErrorMessage, {name: fakeErrorName});
|
|
3669
4071
|
|
|
3670
|
-
eventListeners[
|
|
4072
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3671
4073
|
|
|
3672
4074
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3673
4075
|
// expectedMetadataType is the error name in this case
|
|
3674
4076
|
checkBehavioralMetricSent(
|
|
3675
4077
|
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
3676
|
-
|
|
4078
|
+
Errors.ErrorCode.SdpError,
|
|
3677
4079
|
fakeErrorMessage,
|
|
3678
4080
|
fakeErrorName
|
|
3679
4081
|
);
|
|
@@ -3681,24 +4083,24 @@ describe('plugin-meetings', () => {
|
|
|
3681
4083
|
|
|
3682
4084
|
it('should send metrics for IceGatheringError error', () => {
|
|
3683
4085
|
// IceGatheringError is usually without a cause
|
|
3684
|
-
const fakeError = new
|
|
4086
|
+
const fakeError = new Errors.IceGatheringError(fakeErrorMessage, {
|
|
3685
4087
|
name: fakeErrorName,
|
|
3686
4088
|
});
|
|
3687
4089
|
|
|
3688
|
-
eventListeners[
|
|
4090
|
+
eventListeners[Event.ROAP_FAILURE](fakeError);
|
|
3689
4091
|
|
|
3690
4092
|
checkMetricSent(eventType.LOCAL_SDP_GENERATED);
|
|
3691
4093
|
// expectedMetadataType is the error name in this case
|
|
3692
4094
|
checkBehavioralMetricSent(
|
|
3693
4095
|
BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE,
|
|
3694
|
-
|
|
4096
|
+
Errors.ErrorCode.IceGatheringError,
|
|
3695
4097
|
fakeErrorMessage,
|
|
3696
4098
|
fakeErrorName
|
|
3697
4099
|
);
|
|
3698
4100
|
});
|
|
3699
4101
|
});
|
|
3700
4102
|
|
|
3701
|
-
describe('handles
|
|
4103
|
+
describe('handles Event.ROAP_MESSAGE_TO_SEND correctly', () => {
|
|
3702
4104
|
let sendRoapOKStub;
|
|
3703
4105
|
let sendRoapMediaRequestStub;
|
|
3704
4106
|
let sendRoapAnswerStub;
|
|
@@ -3716,7 +4118,7 @@ describe('plugin-meetings', () => {
|
|
|
3716
4118
|
});
|
|
3717
4119
|
|
|
3718
4120
|
it('handles OK message correctly', () => {
|
|
3719
|
-
eventListeners[
|
|
4121
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3720
4122
|
roapMessage: {messageType: 'OK', seq: 1},
|
|
3721
4123
|
});
|
|
3722
4124
|
|
|
@@ -3735,7 +4137,7 @@ describe('plugin-meetings', () => {
|
|
|
3735
4137
|
});
|
|
3736
4138
|
|
|
3737
4139
|
it('handles OFFER message correctly', () => {
|
|
3738
|
-
eventListeners[
|
|
4140
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3739
4141
|
roapMessage: {
|
|
3740
4142
|
messageType: 'OFFER',
|
|
3741
4143
|
seq: 1,
|
|
@@ -3761,7 +4163,7 @@ describe('plugin-meetings', () => {
|
|
|
3761
4163
|
});
|
|
3762
4164
|
|
|
3763
4165
|
it('handles ANSWER message correctly', () => {
|
|
3764
|
-
eventListeners[
|
|
4166
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3765
4167
|
roapMessage: {
|
|
3766
4168
|
messageType: 'ANSWER',
|
|
3767
4169
|
seq: 10,
|
|
@@ -3788,7 +4190,7 @@ describe('plugin-meetings', () => {
|
|
|
3788
4190
|
it('sends metrics if fails to send roap ANSWER message', async () => {
|
|
3789
4191
|
sendRoapAnswerStub.rejects(new Error('sending answer failed'));
|
|
3790
4192
|
|
|
3791
|
-
await eventListeners[
|
|
4193
|
+
await eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
3792
4194
|
roapMessage: {
|
|
3793
4195
|
messageType: 'ANSWER',
|
|
3794
4196
|
seq: 10,
|
|
@@ -3810,88 +4212,401 @@ describe('plugin-meetings', () => {
|
|
|
3810
4212
|
);
|
|
3811
4213
|
});
|
|
3812
4214
|
|
|
3813
|
-
[
|
|
3814
|
-
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
3815
|
-
eventListeners[
|
|
3816
|
-
roapMessage: {
|
|
3817
|
-
messageType: 'ERROR',
|
|
3818
|
-
seq: 10,
|
|
3819
|
-
errorType,
|
|
3820
|
-
tieBreaker: 12345,
|
|
3821
|
-
},
|
|
3822
|
-
});
|
|
4215
|
+
[ErrorType.CONFLICT, ErrorType.DOUBLECONFLICT].forEach((errorType) =>
|
|
4216
|
+
it(`handles ERROR message indicating glare condition correctly (errorType=${errorType})`, () => {
|
|
4217
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
4218
|
+
roapMessage: {
|
|
4219
|
+
messageType: 'ERROR',
|
|
4220
|
+
seq: 10,
|
|
4221
|
+
errorType,
|
|
4222
|
+
tieBreaker: 12345,
|
|
4223
|
+
},
|
|
4224
|
+
});
|
|
4225
|
+
|
|
4226
|
+
assert.calledOnce(Metrics.sendBehavioralMetric);
|
|
4227
|
+
assert.calledWithMatch(
|
|
4228
|
+
Metrics.sendBehavioralMetric,
|
|
4229
|
+
BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION,
|
|
4230
|
+
{
|
|
4231
|
+
correlation_id: meeting.correlationId,
|
|
4232
|
+
locus_id: meeting.locusUrl.split('/').pop(),
|
|
4233
|
+
sequence: 10,
|
|
4234
|
+
}
|
|
4235
|
+
);
|
|
4236
|
+
|
|
4237
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
4238
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
4239
|
+
seq: 10,
|
|
4240
|
+
errorType,
|
|
4241
|
+
mediaId: meeting.mediaId,
|
|
4242
|
+
correlationId: meeting.correlationId,
|
|
4243
|
+
});
|
|
4244
|
+
})
|
|
4245
|
+
);
|
|
4246
|
+
|
|
4247
|
+
it('handles ERROR message indicating other errors correctly', () => {
|
|
4248
|
+
eventListeners[Event.ROAP_MESSAGE_TO_SEND]({
|
|
4249
|
+
roapMessage: {
|
|
4250
|
+
messageType: 'ERROR',
|
|
4251
|
+
seq: 10,
|
|
4252
|
+
errorType: ErrorType.FAILED,
|
|
4253
|
+
tieBreaker: 12345,
|
|
4254
|
+
},
|
|
4255
|
+
});
|
|
4256
|
+
|
|
4257
|
+
assert.notCalled(Metrics.sendBehavioralMetric);
|
|
4258
|
+
|
|
4259
|
+
assert.calledOnce(sendRoapErrorStub);
|
|
4260
|
+
assert.calledWith(sendRoapErrorStub, {
|
|
4261
|
+
seq: 10,
|
|
4262
|
+
errorType: ErrorType.FAILED,
|
|
4263
|
+
mediaId: meeting.mediaId,
|
|
4264
|
+
correlationId: meeting.correlationId,
|
|
4265
|
+
});
|
|
4266
|
+
});
|
|
4267
|
+
});
|
|
4268
|
+
});
|
|
4269
|
+
describe('#setUpLocusInfoSelfListener', () => {
|
|
4270
|
+
it('listens to the self unadmitted guest event', (done) => {
|
|
4271
|
+
meeting.startKeepAlive = sinon.stub();
|
|
4272
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_UNADMITTED_GUEST', test1);
|
|
4273
|
+
assert.calledOnceWithExactly(meeting.startKeepAlive);
|
|
4274
|
+
assert.calledTwice(TriggerProxy.trigger);
|
|
4275
|
+
assert.calledWith(
|
|
4276
|
+
TriggerProxy.trigger,
|
|
4277
|
+
sinon.match.instanceOf(Meeting),
|
|
4278
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4279
|
+
'meeting:self:lobbyWaiting',
|
|
4280
|
+
{payload: test1}
|
|
4281
|
+
);
|
|
4282
|
+
done();
|
|
4283
|
+
});
|
|
4284
|
+
it('listens to the self admitted guest event', (done) => {
|
|
4285
|
+
meeting.stopKeepAlive = sinon.stub();
|
|
4286
|
+
meeting.locusInfo.emit({function: 'test', file: 'test'}, 'SELF_ADMITTED_GUEST', test1);
|
|
4287
|
+
assert.calledOnceWithExactly(meeting.stopKeepAlive);
|
|
4288
|
+
assert.calledTwice(TriggerProxy.trigger);
|
|
4289
|
+
assert.calledWith(
|
|
4290
|
+
TriggerProxy.trigger,
|
|
4291
|
+
sinon.match.instanceOf(Meeting),
|
|
4292
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4293
|
+
'meeting:self:guestAdmitted',
|
|
4294
|
+
{payload: test1}
|
|
4295
|
+
);
|
|
4296
|
+
done();
|
|
4297
|
+
});
|
|
4298
|
+
|
|
4299
|
+
it('listens to the breakouts changed event', () => {
|
|
4300
|
+
meeting.breakouts.updateBreakoutSessions = sinon.stub();
|
|
4301
|
+
|
|
4302
|
+
const payload = 'payload';
|
|
4303
|
+
|
|
4304
|
+
meeting.locusInfo.emit(
|
|
4305
|
+
{function: 'test', file: 'test'},
|
|
4306
|
+
'SELF_MEETING_BREAKOUTS_CHANGED',
|
|
4307
|
+
payload
|
|
4308
|
+
);
|
|
4309
|
+
|
|
4310
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateBreakoutSessions, payload);
|
|
4311
|
+
assert.calledWith(
|
|
4312
|
+
TriggerProxy.trigger,
|
|
4313
|
+
meeting,
|
|
4314
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4315
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4316
|
+
);
|
|
4317
|
+
});
|
|
4318
|
+
|
|
4319
|
+
it('listens to the self roles changed event', () => {
|
|
4320
|
+
const payload = {oldRoles: [], newRoles: ['COHOST']};
|
|
4321
|
+
meeting.breakouts.updateCanManageBreakouts = sinon.stub();
|
|
4322
|
+
|
|
4323
|
+
meeting.locusInfo.emit(
|
|
4324
|
+
{function: 'test', file: 'test'},
|
|
4325
|
+
'SELF_ROLES_CHANGED',
|
|
4326
|
+
payload
|
|
4327
|
+
);
|
|
4328
|
+
|
|
4329
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateCanManageBreakouts, true);
|
|
4330
|
+
assert.calledWith(
|
|
4331
|
+
TriggerProxy.trigger,
|
|
4332
|
+
meeting,
|
|
4333
|
+
{file: 'meeting/index', function: 'setUpLocusInfoSelfListener'},
|
|
4334
|
+
EVENT_TRIGGERS.MEETING_SELF_ROLES_CHANGED,
|
|
4335
|
+
{payload}
|
|
4336
|
+
);
|
|
4337
|
+
});
|
|
4338
|
+
});
|
|
4339
|
+
|
|
4340
|
+
describe('#setUpBreakoutsListener', () => {
|
|
4341
|
+
it('listens to the closing event from breakouts and triggers the closing event', () => {
|
|
4342
|
+
TriggerProxy.trigger.reset();
|
|
4343
|
+
meeting.breakouts.trigger('BREAKOUTS_CLOSING');
|
|
4344
|
+
|
|
4345
|
+
assert.calledWith(
|
|
4346
|
+
TriggerProxy.trigger,
|
|
4347
|
+
meeting,
|
|
4348
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4349
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
|
|
4350
|
+
);
|
|
4351
|
+
});
|
|
4352
|
+
|
|
4353
|
+
it('listens to the message event from breakouts and triggers the message event', () => {
|
|
4354
|
+
TriggerProxy.trigger.reset();
|
|
4355
|
+
|
|
4356
|
+
const messageEvent = 'message';
|
|
4357
|
+
|
|
4358
|
+
meeting.breakouts.trigger('MESSAGE', messageEvent);
|
|
4359
|
+
|
|
4360
|
+
assert.calledWith(
|
|
4361
|
+
TriggerProxy.trigger,
|
|
4362
|
+
meeting,
|
|
4363
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4364
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
|
|
4365
|
+
messageEvent
|
|
4366
|
+
);
|
|
4367
|
+
});
|
|
4368
|
+
|
|
4369
|
+
it('listens to the members update event from breakouts and triggers the breakouts update event', () => {
|
|
4370
|
+
TriggerProxy.trigger.reset();
|
|
4371
|
+
meeting.breakouts.trigger('MEMBERS_UPDATE');
|
|
4372
|
+
|
|
4373
|
+
assert.calledWith(
|
|
4374
|
+
TriggerProxy.trigger,
|
|
4375
|
+
meeting,
|
|
4376
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4377
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4378
|
+
);
|
|
4379
|
+
});
|
|
4380
|
+
|
|
4381
|
+
it('listens to the ask return to main event from breakouts and triggers the ask return to main event from meeting', () => {
|
|
4382
|
+
TriggerProxy.trigger.reset();
|
|
4383
|
+
meeting.breakouts.trigger('ASK_RETURN_TO_MAIN');
|
|
4384
|
+
assert.calledWith(
|
|
4385
|
+
TriggerProxy.trigger,
|
|
4386
|
+
meeting,
|
|
4387
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4388
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_RETURN_TO_MAIN
|
|
4389
|
+
);
|
|
4390
|
+
});
|
|
4391
|
+
|
|
4392
|
+
it('listens to the leave event from breakouts and triggers the breakout leave event', () => {
|
|
4393
|
+
TriggerProxy.trigger.reset();
|
|
4394
|
+
meeting.breakouts.trigger('LEAVE_BREAKOUT');
|
|
4395
|
+
assert.calledWith(
|
|
4396
|
+
TriggerProxy.trigger,
|
|
4397
|
+
meeting,
|
|
4398
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4399
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_LEAVE
|
|
4400
|
+
);
|
|
4401
|
+
});
|
|
4402
|
+
|
|
4403
|
+
it('listens to the breakout ask for help event and triggers the ask for help event', () => {
|
|
4404
|
+
TriggerProxy.trigger.reset();
|
|
4405
|
+
const helpEvent = {sessionId:'sessionId', participant: 'participant'}
|
|
4406
|
+
meeting.breakouts.trigger('ASK_FOR_HELP', helpEvent);
|
|
4407
|
+
assert.calledWith(
|
|
4408
|
+
TriggerProxy.trigger,
|
|
4409
|
+
meeting,
|
|
4410
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4411
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_FOR_HELP,
|
|
4412
|
+
helpEvent
|
|
4413
|
+
);
|
|
4414
|
+
});
|
|
4415
|
+
|
|
4416
|
+
it('listens to the preAssignments update event from breakouts and triggers the update event', () => {
|
|
4417
|
+
TriggerProxy.trigger.reset();
|
|
4418
|
+
meeting.breakouts.trigger('PRE_ASSIGNMENTS_UPDATE');
|
|
4419
|
+
|
|
4420
|
+
assert.calledWith(
|
|
4421
|
+
TriggerProxy.trigger,
|
|
4422
|
+
meeting,
|
|
4423
|
+
{file: 'meeting/index', function: 'setUpBreakoutsListener'},
|
|
4424
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE
|
|
4425
|
+
);
|
|
4426
|
+
});
|
|
4427
|
+
});
|
|
4428
|
+
|
|
4429
|
+
describe('#setupLocusControlsListener', () => {
|
|
4430
|
+
it('listens to the locus breakouts update event', () => {
|
|
4431
|
+
const locus = {
|
|
4432
|
+
breakout: 'breakout',
|
|
4433
|
+
};
|
|
4434
|
+
|
|
4435
|
+
meeting.breakouts.updateBreakout = sinon.stub();
|
|
4436
|
+
meeting.locusInfo.emit(
|
|
4437
|
+
{function: 'test', file: 'test'},
|
|
4438
|
+
'CONTROLS_MEETING_BREAKOUT_UPDATED',
|
|
4439
|
+
locus
|
|
4440
|
+
);
|
|
4441
|
+
|
|
4442
|
+
assert.calledOnceWithExactly(meeting.breakouts.updateBreakout, locus.breakout);
|
|
4443
|
+
assert.calledWith(
|
|
4444
|
+
TriggerProxy.trigger,
|
|
4445
|
+
meeting,
|
|
4446
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4447
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
4448
|
+
);
|
|
4449
|
+
});
|
|
4450
|
+
|
|
4451
|
+
it('listens to CONTROLS_MUTE_ON_ENTRY_CHANGED', async () => {
|
|
4452
|
+
const state = {example: 'value'}
|
|
4453
|
+
|
|
4454
|
+
await meeting.locusInfo.emitScoped(
|
|
4455
|
+
{function: 'test', file: 'test'},
|
|
4456
|
+
LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED,
|
|
4457
|
+
{state}
|
|
4458
|
+
);
|
|
4459
|
+
|
|
4460
|
+
assert.calledWith(
|
|
4461
|
+
TriggerProxy.trigger,
|
|
4462
|
+
meeting,
|
|
4463
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4464
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_MUTE_ON_ENTRY_UPDATED,
|
|
4465
|
+
{state},
|
|
4466
|
+
);
|
|
4467
|
+
});
|
|
4468
|
+
|
|
4469
|
+
it('listens to MEETING_CONTROLS_SHARE_CONTROL_UPDATED', async () => {
|
|
4470
|
+
const state = {example: 'value'}
|
|
4471
|
+
|
|
4472
|
+
await meeting.locusInfo.emitScoped(
|
|
4473
|
+
{function: 'test', file: 'test'},
|
|
4474
|
+
LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED,
|
|
4475
|
+
{state}
|
|
4476
|
+
);
|
|
4477
|
+
|
|
4478
|
+
assert.calledWith(
|
|
4479
|
+
TriggerProxy.trigger,
|
|
4480
|
+
meeting,
|
|
4481
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4482
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_SHARE_CONTROL_UPDATED,
|
|
4483
|
+
{state},
|
|
4484
|
+
);
|
|
4485
|
+
});
|
|
4486
|
+
|
|
4487
|
+
it('listens to MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED', async () => {
|
|
4488
|
+
const state = {example: 'value'}
|
|
3823
4489
|
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
correlation_id: meeting.correlationId,
|
|
3830
|
-
locus_id: meeting.locusUrl.split('/').pop(),
|
|
3831
|
-
sequence: 10,
|
|
3832
|
-
}
|
|
3833
|
-
);
|
|
4490
|
+
await meeting.locusInfo.emitScoped(
|
|
4491
|
+
{function: 'test', file: 'test'},
|
|
4492
|
+
LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED,
|
|
4493
|
+
{state}
|
|
4494
|
+
);
|
|
3834
4495
|
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3840
|
-
|
|
3841
|
-
});
|
|
3842
|
-
})
|
|
4496
|
+
assert.calledWith(
|
|
4497
|
+
TriggerProxy.trigger,
|
|
4498
|
+
meeting,
|
|
4499
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4500
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED,
|
|
4501
|
+
{state},
|
|
3843
4502
|
);
|
|
4503
|
+
});
|
|
3844
4504
|
|
|
3845
|
-
|
|
3846
|
-
|
|
3847
|
-
roapMessage: {
|
|
3848
|
-
messageType: 'ERROR',
|
|
3849
|
-
seq: 10,
|
|
3850
|
-
errorType: MC.ErrorType.FAILED,
|
|
3851
|
-
tieBreaker: 12345,
|
|
3852
|
-
},
|
|
3853
|
-
});
|
|
4505
|
+
it('listens to MEETING_CONTROLS_REACTIONS_UPDATED', async () => {
|
|
4506
|
+
const state = {example: 'value'}
|
|
3854
4507
|
|
|
3855
|
-
|
|
4508
|
+
await meeting.locusInfo.emitScoped(
|
|
4509
|
+
{function: 'test', file: 'test'},
|
|
4510
|
+
LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED,
|
|
4511
|
+
{state}
|
|
4512
|
+
);
|
|
3856
4513
|
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3862
|
-
|
|
3863
|
-
|
|
3864
|
-
});
|
|
4514
|
+
assert.calledWith(
|
|
4515
|
+
TriggerProxy.trigger,
|
|
4516
|
+
meeting,
|
|
4517
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4518
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REACTIONS_UPDATED,
|
|
4519
|
+
{state},
|
|
4520
|
+
);
|
|
3865
4521
|
});
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
meeting.locusInfo.
|
|
3871
|
-
|
|
3872
|
-
|
|
4522
|
+
|
|
4523
|
+
it('listens to MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED', async () => {
|
|
4524
|
+
const state = {example: 'value'}
|
|
4525
|
+
|
|
4526
|
+
await meeting.locusInfo.emitScoped(
|
|
4527
|
+
{function: 'test', file: 'test'},
|
|
4528
|
+
LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED,
|
|
4529
|
+
{state}
|
|
4530
|
+
);
|
|
4531
|
+
|
|
3873
4532
|
assert.calledWith(
|
|
3874
4533
|
TriggerProxy.trigger,
|
|
3875
|
-
|
|
3876
|
-
{file: 'meeting/index', function: '
|
|
3877
|
-
|
|
3878
|
-
{
|
|
4534
|
+
meeting,
|
|
4535
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4536
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED,
|
|
4537
|
+
{state},
|
|
3879
4538
|
);
|
|
3880
|
-
done();
|
|
3881
4539
|
});
|
|
3882
|
-
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
|
|
4540
|
+
|
|
4541
|
+
it('listens to MEETING_CONTROLS_RAISE_HAND_UPDATED', async () => {
|
|
4542
|
+
const state = {example: 'value'}
|
|
4543
|
+
|
|
4544
|
+
await meeting.locusInfo.emitScoped(
|
|
4545
|
+
{function: 'test', file: 'test'},
|
|
4546
|
+
LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED,
|
|
4547
|
+
{state}
|
|
4548
|
+
);
|
|
4549
|
+
|
|
3887
4550
|
assert.calledWith(
|
|
3888
4551
|
TriggerProxy.trigger,
|
|
3889
|
-
|
|
3890
|
-
{file: 'meeting/index', function: '
|
|
3891
|
-
|
|
3892
|
-
{
|
|
4552
|
+
meeting,
|
|
4553
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4554
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_RAISE_HAND_UPDATED,
|
|
4555
|
+
{state},
|
|
3893
4556
|
);
|
|
3894
|
-
|
|
4557
|
+
});
|
|
4558
|
+
|
|
4559
|
+
it('listens to MEETING_CONTROLS_VIDEO_UPDATED', async () => {
|
|
4560
|
+
const state = {example: 'value'}
|
|
4561
|
+
|
|
4562
|
+
await meeting.locusInfo.emitScoped(
|
|
4563
|
+
{function: 'test', file: 'test'},
|
|
4564
|
+
LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED,
|
|
4565
|
+
{state}
|
|
4566
|
+
);
|
|
4567
|
+
|
|
4568
|
+
assert.calledWith(
|
|
4569
|
+
TriggerProxy.trigger,
|
|
4570
|
+
meeting,
|
|
4571
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
4572
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIDEO_UPDATED,
|
|
4573
|
+
{state},
|
|
4574
|
+
);
|
|
4575
|
+
});
|
|
4576
|
+
|
|
4577
|
+
it('listens to the timing that user joined into breakout', async () => {
|
|
4578
|
+
const mainLocusUrl = 'mainLocusUrl123';
|
|
4579
|
+
|
|
4580
|
+
meeting.meetingRequest.getLocusStatusByUrl = sinon.stub().returns(Promise.resolve());
|
|
4581
|
+
|
|
4582
|
+
await meeting.locusInfo.emit(
|
|
4583
|
+
{function: 'test', file: 'test'},
|
|
4584
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4585
|
+
{mainLocusUrl}
|
|
4586
|
+
);
|
|
4587
|
+
|
|
4588
|
+
assert.calledOnceWithExactly(meeting.meetingRequest.getLocusStatusByUrl, mainLocusUrl);
|
|
4589
|
+
const error = {statusCode: 403};
|
|
4590
|
+
meeting.meetingRequest.getLocusStatusByUrl.rejects(error);
|
|
4591
|
+
meeting.locusInfo.clearMainSessionLocusCache = sinon.stub();
|
|
4592
|
+
await meeting.locusInfo.emit(
|
|
4593
|
+
{function: 'test', file: 'test'},
|
|
4594
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4595
|
+
{mainLocusUrl}
|
|
4596
|
+
);
|
|
4597
|
+
|
|
4598
|
+
assert.calledOnce(meeting.locusInfo.clearMainSessionLocusCache);
|
|
4599
|
+
|
|
4600
|
+
const otherError = new Error('something wrong');
|
|
4601
|
+
meeting.meetingRequest.getLocusStatusByUrl.rejects(otherError);
|
|
4602
|
+
meeting.locusInfo.clearMainSessionLocusCache = sinon.stub();
|
|
4603
|
+
await meeting.locusInfo.emit(
|
|
4604
|
+
{function: 'test', file: 'test'},
|
|
4605
|
+
'CONTROLS_JOIN_BREAKOUT_FROM_MAIN',
|
|
4606
|
+
{mainLocusUrl}
|
|
4607
|
+
);
|
|
4608
|
+
|
|
4609
|
+
assert.notCalled(meeting.locusInfo.clearMainSessionLocusCache);
|
|
3895
4610
|
});
|
|
3896
4611
|
});
|
|
3897
4612
|
|
|
@@ -3900,6 +4615,11 @@ describe('plugin-meetings', () => {
|
|
|
3900
4615
|
const newLocusUrl = 'newLocusUrl/12345';
|
|
3901
4616
|
|
|
3902
4617
|
meeting.members = {locusUrlUpdate: sinon.stub().returns(Promise.resolve(test1))};
|
|
4618
|
+
meeting.recordingController = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
4619
|
+
meeting.controlsOptionsManager = {setLocusUrl: sinon.stub().returns(undefined)};
|
|
4620
|
+
|
|
4621
|
+
meeting.breakouts.locusUrlUpdate = sinon.stub();
|
|
4622
|
+
meeting.annotation.locusUrlUpdate = sinon.stub();
|
|
3903
4623
|
|
|
3904
4624
|
meeting.locusInfo.emit(
|
|
3905
4625
|
{function: 'test', file: 'test'},
|
|
@@ -3907,11 +4627,57 @@ describe('plugin-meetings', () => {
|
|
|
3907
4627
|
newLocusUrl
|
|
3908
4628
|
);
|
|
3909
4629
|
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
4630
|
+
assert.calledOnceWithExactly(meeting.breakouts.locusUrlUpdate, newLocusUrl);
|
|
4631
|
+
assert.calledOnceWithExactly(meeting.annotation.locusUrlUpdate, newLocusUrl);
|
|
4632
|
+
assert.calledWith(meeting.members.locusUrlUpdate, newLocusUrl);
|
|
4633
|
+
assert.calledWith(meeting.recordingController.setLocusUrl, newLocusUrl);
|
|
4634
|
+
assert.calledWith(meeting.controlsOptionsManager.setLocusUrl, newLocusUrl);
|
|
3910
4635
|
assert.equal(meeting.locusUrl, newLocusUrl);
|
|
3911
4636
|
assert(meeting.locusId, '12345');
|
|
3912
4637
|
done();
|
|
3913
4638
|
});
|
|
3914
4639
|
});
|
|
4640
|
+
|
|
4641
|
+
describe('#setUpLocusServicesListener', () => {
|
|
4642
|
+
it('listens to the locus services update event', (done) => {
|
|
4643
|
+
const newLocusServices = {
|
|
4644
|
+
services: {
|
|
4645
|
+
record: {
|
|
4646
|
+
url: 'url',
|
|
4647
|
+
},
|
|
4648
|
+
approval: {
|
|
4649
|
+
url: 'url',
|
|
4650
|
+
},
|
|
4651
|
+
},
|
|
4652
|
+
};
|
|
4653
|
+
|
|
4654
|
+
meeting.recordingController = {
|
|
4655
|
+
setServiceUrl: sinon.stub().returns(undefined),
|
|
4656
|
+
setSessionId: sinon.stub().returns(undefined),
|
|
4657
|
+
};
|
|
4658
|
+
meeting.annotation = {
|
|
4659
|
+
approvalUrlUpdate: sinon.stub().returns(undefined),
|
|
4660
|
+
};
|
|
4661
|
+
|
|
4662
|
+
meeting.locusInfo.emit(
|
|
4663
|
+
{function: 'test', file: 'test'},
|
|
4664
|
+
'LINKS_SERVICES',
|
|
4665
|
+
newLocusServices
|
|
4666
|
+
);
|
|
4667
|
+
|
|
4668
|
+
assert.calledWith(
|
|
4669
|
+
meeting.recordingController.setServiceUrl,
|
|
4670
|
+
newLocusServices.services.record.url,
|
|
4671
|
+
);
|
|
4672
|
+
assert.calledWith(
|
|
4673
|
+
meeting.annotation.approvalUrlUpdate,
|
|
4674
|
+
newLocusServices.services.approval.url,
|
|
4675
|
+
);
|
|
4676
|
+
assert.calledOnce(meeting.recordingController.setSessionId);
|
|
4677
|
+
done();
|
|
4678
|
+
});
|
|
4679
|
+
});
|
|
4680
|
+
|
|
3915
4681
|
describe('#setUpLocusInfoMediaInactiveListener', () => {
|
|
3916
4682
|
it('listens to disconnect due to un activity ', (done) => {
|
|
3917
4683
|
TriggerProxy.trigger.reset();
|
|
@@ -4057,20 +4823,6 @@ describe('plugin-meetings', () => {
|
|
|
4057
4823
|
assert.calledOnce(meeting.mediaProperties.unsetRemoteTracks);
|
|
4058
4824
|
});
|
|
4059
4825
|
});
|
|
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
4826
|
// TODO: remove
|
|
4075
4827
|
describe('#setMercuryListener', () => {
|
|
4076
4828
|
it('should listen to mercury events', () => {
|
|
@@ -4237,28 +4989,7 @@ describe('plugin-meetings', () => {
|
|
|
4237
4989
|
checkParseMeetingInfo(expectedInfoToParse);
|
|
4238
4990
|
});
|
|
4239
4991
|
});
|
|
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
|
-
});
|
|
4992
|
+
|
|
4262
4993
|
describe('#setCorrelationId', () => {
|
|
4263
4994
|
it('should set the correlationId and return undefined', () => {
|
|
4264
4995
|
assert.ok(meeting.correlationId);
|
|
@@ -4319,25 +5050,37 @@ describe('plugin-meetings', () => {
|
|
|
4319
5050
|
let inMeetingActionsSetSpy;
|
|
4320
5051
|
let canUserLockSpy;
|
|
4321
5052
|
let canUserUnlockSpy;
|
|
4322
|
-
let
|
|
5053
|
+
let canUserStartSpy;
|
|
4323
5054
|
let canUserStopSpy;
|
|
4324
5055
|
let canUserPauseSpy;
|
|
4325
5056
|
let canUserResumeSpy;
|
|
5057
|
+
let canSetMuteOnEntrySpy;
|
|
5058
|
+
let canUnsetMuteOnEntrySpy;
|
|
5059
|
+
let canSetDisallowUnmuteSpy;
|
|
5060
|
+
let canUnsetDisallowUnmuteSpy;
|
|
4326
5061
|
let canUserRaiseHandSpy;
|
|
4327
5062
|
let bothLeaveAndEndMeetingAvailableSpy;
|
|
4328
5063
|
let canUserLowerAllHandsSpy;
|
|
4329
5064
|
let canUserLowerSomeoneElsesHandSpy;
|
|
4330
5065
|
let waitingForOthersToJoinSpy;
|
|
4331
5066
|
let handleDataChannelUrlChangeSpy;
|
|
5067
|
+
let canSendReactionsSpy;
|
|
5068
|
+
let canUserRenameSelfAndObservedSpy;
|
|
5069
|
+
let canUserRenameOthersSpy;
|
|
5070
|
+
let hasHintsSpy;
|
|
4332
5071
|
|
|
4333
5072
|
beforeEach(() => {
|
|
4334
5073
|
locusInfoOnSpy = sinon.spy(meeting.locusInfo, 'on');
|
|
4335
5074
|
canUserLockSpy = sinon.spy(MeetingUtil, 'canUserLock');
|
|
4336
5075
|
canUserUnlockSpy = sinon.spy(MeetingUtil, 'canUserUnlock');
|
|
4337
|
-
|
|
4338
|
-
canUserStopSpy = sinon.spy(
|
|
4339
|
-
canUserPauseSpy = sinon.spy(
|
|
4340
|
-
canUserResumeSpy = sinon.spy(
|
|
5076
|
+
canUserStartSpy = sinon.spy(RecordingUtil, 'canUserStart');
|
|
5077
|
+
canUserStopSpy = sinon.spy(RecordingUtil, 'canUserStop');
|
|
5078
|
+
canUserPauseSpy = sinon.spy(RecordingUtil, 'canUserPause');
|
|
5079
|
+
canUserResumeSpy = sinon.spy(RecordingUtil, 'canUserResume');
|
|
5080
|
+
canSetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canSetMuteOnEntry');
|
|
5081
|
+
canUnsetMuteOnEntrySpy = sinon.spy(ControlsOptionsUtil, 'canUnsetMuteOnEntry');
|
|
5082
|
+
canSetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canSetDisallowUnmute');
|
|
5083
|
+
canUnsetDisallowUnmuteSpy = sinon.spy(ControlsOptionsUtil, 'canUnsetDisallowUnmute');
|
|
4341
5084
|
inMeetingActionsSetSpy = sinon.spy(meeting.inMeetingActions, 'set');
|
|
4342
5085
|
canUserRaiseHandSpy = sinon.spy(MeetingUtil, 'canUserRaiseHand');
|
|
4343
5086
|
canUserLowerAllHandsSpy = sinon.spy(MeetingUtil, 'canUserLowerAllHands');
|
|
@@ -4348,6 +5091,9 @@ describe('plugin-meetings', () => {
|
|
|
4348
5091
|
canUserLowerSomeoneElsesHandSpy = sinon.spy(MeetingUtil, 'canUserLowerSomeoneElsesHand');
|
|
4349
5092
|
waitingForOthersToJoinSpy = sinon.spy(MeetingUtil, 'waitingForOthersToJoin');
|
|
4350
5093
|
handleDataChannelUrlChangeSpy = sinon.spy(meeting, 'handleDataChannelUrlChange');
|
|
5094
|
+
canSendReactionsSpy = sinon.spy(MeetingUtil, 'canSendReactions');
|
|
5095
|
+
canUserRenameSelfAndObservedSpy = sinon.spy(MeetingUtil, 'canUserRenameSelfAndObserved');
|
|
5096
|
+
canUserRenameOthersSpy = sinon.spy(MeetingUtil, 'canUserRenameOthers');
|
|
4351
5097
|
});
|
|
4352
5098
|
|
|
4353
5099
|
afterEach(() => {
|
|
@@ -4357,6 +5103,10 @@ describe('plugin-meetings', () => {
|
|
|
4357
5103
|
});
|
|
4358
5104
|
|
|
4359
5105
|
it('registers the correct MEETING_INFO_UPDATED event', () => {
|
|
5106
|
+
// Due to import tree issues, hasHints must be stubed within the scope of the `it`.
|
|
5107
|
+
const restorableHasHints = ControlsOptionsUtil.hasHints;
|
|
5108
|
+
ControlsOptionsUtil.hasHints = sinon.stub().returns(true);
|
|
5109
|
+
|
|
4360
5110
|
meeting.setUpLocusInfoMeetingInfoListener();
|
|
4361
5111
|
|
|
4362
5112
|
assert.calledThrice(locusInfoOnSpy);
|
|
@@ -4377,16 +5127,96 @@ describe('plugin-meetings', () => {
|
|
|
4377
5127
|
|
|
4378
5128
|
assert.calledWith(canUserLockSpy, payload.info.userDisplayHints);
|
|
4379
5129
|
assert.calledWith(canUserUnlockSpy, payload.info.userDisplayHints);
|
|
4380
|
-
assert.calledWith(
|
|
5130
|
+
assert.calledWith(canUserStartSpy, payload.info.userDisplayHints);
|
|
4381
5131
|
assert.calledWith(canUserStopSpy, payload.info.userDisplayHints);
|
|
4382
5132
|
assert.calledWith(canUserPauseSpy, payload.info.userDisplayHints);
|
|
4383
5133
|
assert.calledWith(canUserResumeSpy, payload.info.userDisplayHints);
|
|
5134
|
+
assert.calledWith(canSetMuteOnEntrySpy, payload.info.userDisplayHints);
|
|
5135
|
+
assert.calledWith(canUnsetMuteOnEntrySpy, payload.info.userDisplayHints);
|
|
5136
|
+
assert.calledWith(canSetDisallowUnmuteSpy, payload.info.userDisplayHints);
|
|
5137
|
+
assert.calledWith(canUnsetDisallowUnmuteSpy, payload.info.userDisplayHints);
|
|
4384
5138
|
assert.calledWith(canUserRaiseHandSpy, payload.info.userDisplayHints);
|
|
4385
5139
|
assert.calledWith(bothLeaveAndEndMeetingAvailableSpy, payload.info.userDisplayHints);
|
|
4386
5140
|
assert.calledWith(canUserLowerAllHandsSpy, payload.info.userDisplayHints);
|
|
4387
5141
|
assert.calledWith(canUserLowerSomeoneElsesHandSpy, payload.info.userDisplayHints);
|
|
4388
5142
|
assert.calledWith(waitingForOthersToJoinSpy, payload.info.userDisplayHints);
|
|
4389
5143
|
assert.calledWith(handleDataChannelUrlChangeSpy, payload.info.datachannelUrl);
|
|
5144
|
+
assert.calledWith(canSendReactionsSpy, null, payload.info.userDisplayHints);
|
|
5145
|
+
assert.calledWith(canUserRenameSelfAndObservedSpy, payload.info.userDisplayHints);
|
|
5146
|
+
assert.calledWith(canUserRenameOthersSpy, payload.info.userDisplayHints);
|
|
5147
|
+
|
|
5148
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5149
|
+
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
5150
|
+
displayHints: payload.info.userDisplayHints,
|
|
5151
|
+
});
|
|
5152
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5153
|
+
requiredHints: [DISPLAY_HINTS.UNMUTE_ALL],
|
|
5154
|
+
displayHints: payload.info.userDisplayHints,
|
|
5155
|
+
});
|
|
5156
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5157
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_HARD_MUTE],
|
|
5158
|
+
displayHints: payload.info.userDisplayHints,
|
|
5159
|
+
});
|
|
5160
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5161
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_HARD_MUTE],
|
|
5162
|
+
displayHints: payload.info.userDisplayHints,
|
|
5163
|
+
});
|
|
5164
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5165
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY],
|
|
5166
|
+
displayHints: payload.info.userDisplayHints,
|
|
5167
|
+
});
|
|
5168
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5169
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY],
|
|
5170
|
+
displayHints: payload.info.userDisplayHints,
|
|
5171
|
+
});
|
|
5172
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5173
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_REACTIONS],
|
|
5174
|
+
displayHints: payload.info.userDisplayHints,
|
|
5175
|
+
});
|
|
5176
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5177
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_REACTIONS],
|
|
5178
|
+
displayHints: payload.info.userDisplayHints,
|
|
5179
|
+
});
|
|
5180
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5181
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_DISPLAY_NAME],
|
|
5182
|
+
displayHints: payload.info.userDisplayHints,
|
|
5183
|
+
});
|
|
5184
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5185
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_DISPLAY_NAME],
|
|
5186
|
+
displayHints: payload.info.userDisplayHints,
|
|
5187
|
+
});
|
|
5188
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5189
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTROL],
|
|
5190
|
+
displayHints: payload.info.userDisplayHints,
|
|
5191
|
+
});
|
|
5192
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5193
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
5194
|
+
displayHints: payload.info.userDisplayHints,
|
|
5195
|
+
});
|
|
5196
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5197
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
5198
|
+
displayHints: payload.info.userDisplayHints,
|
|
5199
|
+
});
|
|
5200
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5201
|
+
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
|
|
5202
|
+
displayHints: payload.info.userDisplayHints,
|
|
5203
|
+
});
|
|
5204
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5205
|
+
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
5206
|
+
displayHints: payload.info.userDisplayHints,
|
|
5207
|
+
});
|
|
5208
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5209
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CAMERA],
|
|
5210
|
+
displayHints: payload.info.userDisplayHints,
|
|
5211
|
+
});
|
|
5212
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5213
|
+
requiredHints: [DISPLAY_HINTS.SHARE_DESKTOP],
|
|
5214
|
+
displayHints: payload.info.userDisplayHints,
|
|
5215
|
+
});
|
|
5216
|
+
assert.calledWith(ControlsOptionsUtil.hasHints, {
|
|
5217
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTENT],
|
|
5218
|
+
displayHints: payload.info.userDisplayHints,
|
|
5219
|
+
});
|
|
4390
5220
|
|
|
4391
5221
|
assert.calledWith(
|
|
4392
5222
|
TriggerProxy.trigger,
|
|
@@ -4404,6 +5234,8 @@ describe('plugin-meetings', () => {
|
|
|
4404
5234
|
callback(payload);
|
|
4405
5235
|
|
|
4406
5236
|
assert.notCalled(TriggerProxy.trigger);
|
|
5237
|
+
|
|
5238
|
+
ControlsOptionsUtil.hasHints = restorableHasHints;
|
|
4407
5239
|
});
|
|
4408
5240
|
});
|
|
4409
5241
|
|
|
@@ -4451,6 +5283,9 @@ describe('plugin-meetings', () => {
|
|
|
4451
5283
|
.stub()
|
|
4452
5284
|
.returns(Promise.resolve('something'));
|
|
4453
5285
|
webex.internal.llm.disconnectLLM = sinon.stub().returns(Promise.resolve());
|
|
5286
|
+
meeting.webex.internal.llm.on = sinon.stub();
|
|
5287
|
+
meeting.webex.internal.llm.off = sinon.stub();
|
|
5288
|
+
meeting.processRelayEvent = sinon.stub();
|
|
4454
5289
|
});
|
|
4455
5290
|
|
|
4456
5291
|
it('does not connect if the call is not joined yet', async () => {
|
|
@@ -4464,6 +5299,7 @@ describe('plugin-meetings', () => {
|
|
|
4464
5299
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4465
5300
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4466
5301
|
assert.equal(result, undefined);
|
|
5302
|
+
assert.notCalled(meeting.webex.internal.llm.on);
|
|
4467
5303
|
});
|
|
4468
5304
|
|
|
4469
5305
|
it('returns undefined if llm is already connected and the locus url is unchanged', async () => {
|
|
@@ -4478,17 +5314,29 @@ describe('plugin-meetings', () => {
|
|
|
4478
5314
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4479
5315
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4480
5316
|
assert.equal(result, undefined);
|
|
5317
|
+
assert.notCalled(meeting.webex.internal.llm.on);
|
|
4481
5318
|
});
|
|
4482
5319
|
|
|
4483
5320
|
it('connects if not already connected', async () => {
|
|
4484
5321
|
meeting.joinedWith = {state: 'JOINED'};
|
|
4485
5322
|
meeting.locusInfo = {url: 'a url', info: {datachannelUrl: 'a datachannel url'}};
|
|
4486
5323
|
|
|
5324
|
+
|
|
4487
5325
|
const result = await meeting.updateLLMConnection();
|
|
4488
5326
|
|
|
4489
5327
|
assert.notCalled(webex.internal.llm.disconnectLLM);
|
|
4490
5328
|
assert.calledWith(webex.internal.llm.registerAndConnect, 'a url', 'a datachannel url');
|
|
4491
5329
|
assert.equal(result, 'something');
|
|
5330
|
+
assert.calledOnceWithExactly(
|
|
5331
|
+
meeting.webex.internal.llm.off,
|
|
5332
|
+
'event:relay.event',
|
|
5333
|
+
meeting.processRelayEvent
|
|
5334
|
+
);
|
|
5335
|
+
assert.calledOnceWithExactly(
|
|
5336
|
+
meeting.webex.internal.llm.on,
|
|
5337
|
+
'event:relay.event',
|
|
5338
|
+
meeting.processRelayEvent
|
|
5339
|
+
);
|
|
4492
5340
|
});
|
|
4493
5341
|
|
|
4494
5342
|
it('disconnects if first if the locus url has changed', async () => {
|
|
@@ -4507,6 +5355,19 @@ describe('plugin-meetings', () => {
|
|
|
4507
5355
|
'a datachannel url'
|
|
4508
5356
|
);
|
|
4509
5357
|
assert.equal(result, 'something');
|
|
5358
|
+
assert.calledWithExactly(
|
|
5359
|
+
meeting.webex.internal.llm.off,
|
|
5360
|
+
'event:relay.event',
|
|
5361
|
+
meeting.processRelayEvent
|
|
5362
|
+
);
|
|
5363
|
+
assert.calledTwice(
|
|
5364
|
+
meeting.webex.internal.llm.off
|
|
5365
|
+
);
|
|
5366
|
+
assert.calledOnceWithExactly(
|
|
5367
|
+
meeting.webex.internal.llm.on,
|
|
5368
|
+
'event:relay.event',
|
|
5369
|
+
meeting.processRelayEvent
|
|
5370
|
+
);
|
|
4510
5371
|
});
|
|
4511
5372
|
|
|
4512
5373
|
it('disconnects when the state is not JOINED', async () => {
|
|
@@ -4521,15 +5382,22 @@ describe('plugin-meetings', () => {
|
|
|
4521
5382
|
assert.calledWith(webex.internal.llm.disconnectLLM);
|
|
4522
5383
|
assert.notCalled(webex.internal.llm.registerAndConnect);
|
|
4523
5384
|
assert.equal(result, undefined);
|
|
5385
|
+
assert.calledOnceWithExactly(
|
|
5386
|
+
meeting.webex.internal.llm.off,
|
|
5387
|
+
'event:relay.event',
|
|
5388
|
+
meeting.processRelayEvent
|
|
5389
|
+
);
|
|
4524
5390
|
});
|
|
5391
|
+
|
|
4525
5392
|
});
|
|
4526
5393
|
|
|
4527
5394
|
describe('#setLocus', () => {
|
|
4528
5395
|
beforeEach(() => {
|
|
4529
5396
|
meeting.locusInfo.initialSetup = sinon.stub().returns(true);
|
|
4530
5397
|
});
|
|
5398
|
+
|
|
4531
5399
|
it('should read the locus object, set on the meeting and return null', () => {
|
|
4532
|
-
meeting.
|
|
5400
|
+
meeting.setLocus({
|
|
4533
5401
|
mediaConnections: [test1],
|
|
4534
5402
|
locusUrl: url1,
|
|
4535
5403
|
locusId: uuid1,
|
|
@@ -4554,6 +5422,7 @@ describe('plugin-meetings', () => {
|
|
|
4554
5422
|
assert.equal(meeting.hostId, uuid4);
|
|
4555
5423
|
});
|
|
4556
5424
|
});
|
|
5425
|
+
|
|
4557
5426
|
describe('preferred video device', () => {
|
|
4558
5427
|
describe('#getVideoDeviceId', () => {
|
|
4559
5428
|
it('returns the preferred video device', () => {
|
|
@@ -4620,14 +5489,53 @@ describe('plugin-meetings', () => {
|
|
|
4620
5489
|
});
|
|
4621
5490
|
});
|
|
4622
5491
|
describe('share scenarios', () => {
|
|
5492
|
+
|
|
5493
|
+
describe('triggerAnnotationInfoEvent', () => {
|
|
5494
|
+
it('check triggerAnnotationInfoEvent event', () => {
|
|
5495
|
+
|
|
5496
|
+
TriggerProxy.trigger.reset();
|
|
5497
|
+
const annotationInfo = {version: '1', policy: 'Approval'};
|
|
5498
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{});
|
|
5499
|
+
|
|
5500
|
+
assert.calledWith(
|
|
5501
|
+
TriggerProxy.trigger,
|
|
5502
|
+
meeting,
|
|
5503
|
+
{
|
|
5504
|
+
file: 'meeting/index',
|
|
5505
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5506
|
+
},
|
|
5507
|
+
'meeting:updateAnnotationInfo',
|
|
5508
|
+
annotationInfo
|
|
5509
|
+
);
|
|
5510
|
+
|
|
5511
|
+
TriggerProxy.trigger.reset();
|
|
5512
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfo},{annotation:annotationInfo});
|
|
5513
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5514
|
+
|
|
5515
|
+
TriggerProxy.trigger.reset();
|
|
5516
|
+
const annotationInfoUpdated = {version: '1', policy: 'AnnotationNotAllowed'};
|
|
5517
|
+
meeting.triggerAnnotationInfoEvent({annotation:annotationInfoUpdated},{annotation:annotationInfo});
|
|
5518
|
+
assert.calledWith(
|
|
5519
|
+
TriggerProxy.trigger,
|
|
5520
|
+
meeting,
|
|
5521
|
+
{
|
|
5522
|
+
file: 'meeting/index',
|
|
5523
|
+
function: 'triggerAnnotationInfoEvent',
|
|
5524
|
+
},
|
|
5525
|
+
'meeting:updateAnnotationInfo',
|
|
5526
|
+
annotationInfoUpdated
|
|
5527
|
+
);
|
|
5528
|
+
|
|
5529
|
+
TriggerProxy.trigger.reset();
|
|
5530
|
+
meeting.triggerAnnotationInfoEvent(null,{annotation:annotationInfoUpdated});
|
|
5531
|
+
assert.notCalled(TriggerProxy.trigger);
|
|
5532
|
+
|
|
5533
|
+
});
|
|
5534
|
+
});
|
|
5535
|
+
|
|
4623
5536
|
describe('setUpLocusMediaSharesListener', () => {
|
|
4624
5537
|
beforeEach(() => {
|
|
4625
5538
|
meeting.selfId = '9528d952-e4de-46cf-8157-fd4823b98377';
|
|
4626
|
-
sinon.stub(meeting, 'updateShare').returns(Promise.resolve());
|
|
4627
|
-
});
|
|
4628
|
-
|
|
4629
|
-
afterEach(() => {
|
|
4630
|
-
meeting.updateShare.restore();
|
|
4631
5539
|
});
|
|
4632
5540
|
|
|
4633
5541
|
const USER_IDS = {
|
|
@@ -4643,7 +5551,7 @@ describe('plugin-meetings', () => {
|
|
|
4643
5551
|
'https://board-a.wbx2.com/board/api/v1/channels/977a7330-54f4-11eb-b1ef-91f5eefc7bf3',
|
|
4644
5552
|
};
|
|
4645
5553
|
|
|
4646
|
-
const generateContent = (beneficiaryId = null, disposition = null) => ({
|
|
5554
|
+
const generateContent = (beneficiaryId = null, disposition = null,annotation = undefined) => ({
|
|
4647
5555
|
beneficiaryId,
|
|
4648
5556
|
disposition,
|
|
4649
5557
|
});
|
|
@@ -4660,7 +5568,10 @@ describe('plugin-meetings', () => {
|
|
|
4660
5568
|
beneficiaryId,
|
|
4661
5569
|
resourceUrl,
|
|
4662
5570
|
isAccepting,
|
|
4663
|
-
otherBeneficiaryId
|
|
5571
|
+
otherBeneficiaryId,
|
|
5572
|
+
annotation,
|
|
5573
|
+
url,
|
|
5574
|
+
shareInstanceId
|
|
4664
5575
|
) => {
|
|
4665
5576
|
const newPayload = cloneDeep(payload);
|
|
4666
5577
|
|
|
@@ -4686,7 +5597,7 @@ describe('plugin-meetings', () => {
|
|
|
4686
5597
|
if (isGranting) {
|
|
4687
5598
|
if (isContent) {
|
|
4688
5599
|
activeSharingId.content = beneficiaryId;
|
|
4689
|
-
newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED);
|
|
5600
|
+
newPayload.current.content = generateContent(beneficiaryId, FLOOR_ACTION.GRANTED,annotation);
|
|
4690
5601
|
|
|
4691
5602
|
if (isEqual(newPayload.current, newPayload.previous)) {
|
|
4692
5603
|
eventTrigger.member = null;
|
|
@@ -4740,7 +5651,7 @@ describe('plugin-meetings', () => {
|
|
|
4740
5651
|
eventTrigger.share.push({
|
|
4741
5652
|
eventName: EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
4742
5653
|
functionName: 'remoteShare',
|
|
4743
|
-
eventPayload: {memberId: beneficiaryId},
|
|
5654
|
+
eventPayload: {memberId: beneficiaryId, url, shareInstanceId},
|
|
4744
5655
|
});
|
|
4745
5656
|
}
|
|
4746
5657
|
}
|
|
@@ -5312,7 +6223,6 @@ describe('plugin-meetings', () => {
|
|
|
5312
6223
|
payloadTestHelper([data1, data2, data3]);
|
|
5313
6224
|
});
|
|
5314
6225
|
});
|
|
5315
|
-
|
|
5316
6226
|
describe('Desktop A --> Desktop B', () => {
|
|
5317
6227
|
it('Scenario #1: you share desktop A and then share desktop B', () => {
|
|
5318
6228
|
const data1 = generateData(blankPayload, true, true, USER_IDS.ME);
|
|
@@ -5529,7 +6439,7 @@ describe('plugin-meetings', () => {
|
|
|
5529
6439
|
it('should send reaction with the right data and return a promise', async () => {
|
|
5530
6440
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5531
6441
|
|
|
5532
|
-
const reactionPromise = meeting.sendReaction('
|
|
6442
|
+
const reactionPromise = meeting.sendReaction('thumb_down', 'light');
|
|
5533
6443
|
|
|
5534
6444
|
assert.exists(reactionPromise.then);
|
|
5535
6445
|
await reactionPromise;
|
|
@@ -5553,7 +6463,7 @@ describe('plugin-meetings', () => {
|
|
|
5553
6463
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: undefined}};
|
|
5554
6464
|
|
|
5555
6465
|
await assert.isRejected(
|
|
5556
|
-
meeting.sendReaction('
|
|
6466
|
+
meeting.sendReaction('thumb_down', 'light'),
|
|
5557
6467
|
Error,
|
|
5558
6468
|
'Error sending reaction, service url not found.'
|
|
5559
6469
|
);
|
|
@@ -5576,7 +6486,7 @@ describe('plugin-meetings', () => {
|
|
|
5576
6486
|
it('should send a reaction with default skin tone if provided skinToneType is invalid ', async () => {
|
|
5577
6487
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5578
6488
|
|
|
5579
|
-
const reactionPromise = meeting.sendReaction('
|
|
6489
|
+
const reactionPromise = meeting.sendReaction('thumb_down', 'invalid_skin_tone');
|
|
5580
6490
|
|
|
5581
6491
|
assert.exists(reactionPromise.then);
|
|
5582
6492
|
await reactionPromise;
|
|
@@ -5595,7 +6505,7 @@ describe('plugin-meetings', () => {
|
|
|
5595
6505
|
it('should send a reaction with default skin tone if none provided', async () => {
|
|
5596
6506
|
meeting.locusInfo.controls = {reactions: {reactionChannelUrl: 'Fake URL'}};
|
|
5597
6507
|
|
|
5598
|
-
const reactionPromise = meeting.sendReaction('
|
|
6508
|
+
const reactionPromise = meeting.sendReaction('thumb_down');
|
|
5599
6509
|
|
|
5600
6510
|
assert.exists(reactionPromise.then);
|
|
5601
6511
|
await reactionPromise;
|
|
@@ -5674,6 +6584,109 @@ describe('plugin-meetings', () => {
|
|
|
5674
6584
|
});
|
|
5675
6585
|
});
|
|
5676
6586
|
});
|
|
6587
|
+
|
|
6588
|
+
describe('SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED locus event', () => {
|
|
6589
|
+
let spy;
|
|
6590
|
+
|
|
6591
|
+
beforeEach('setup sinon', () => {
|
|
6592
|
+
spy = sinon.spy();
|
|
6593
|
+
});
|
|
6594
|
+
|
|
6595
|
+
const testEmit = async (muted) => {
|
|
6596
|
+
await meeting.locusInfo.emitScoped(
|
|
6597
|
+
{},
|
|
6598
|
+
LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED,
|
|
6599
|
+
{
|
|
6600
|
+
muted,
|
|
6601
|
+
}
|
|
6602
|
+
);
|
|
6603
|
+
|
|
6604
|
+
assert.calledWith(
|
|
6605
|
+
TriggerProxy.trigger,
|
|
6606
|
+
sinon.match.instanceOf(Meeting),
|
|
6607
|
+
{
|
|
6608
|
+
file: 'meeting/index',
|
|
6609
|
+
function: 'setUpLocusInfoSelfListener',
|
|
6610
|
+
},
|
|
6611
|
+
muted
|
|
6612
|
+
? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
|
|
6613
|
+
: EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
|
|
6614
|
+
{
|
|
6615
|
+
payload: {
|
|
6616
|
+
muted,
|
|
6617
|
+
},
|
|
6618
|
+
}
|
|
6619
|
+
);
|
|
6620
|
+
};
|
|
6621
|
+
|
|
6622
|
+
it('emits the expected event when muted', async () => {
|
|
6623
|
+
await testEmit(true);
|
|
6624
|
+
});
|
|
6625
|
+
|
|
6626
|
+
it('emits the expected event when not muted', async () => {
|
|
6627
|
+
await testEmit(false);
|
|
6628
|
+
});
|
|
6629
|
+
});
|
|
6630
|
+
|
|
6631
|
+
describe('getAnalyzerMetricsPrePayload', () => {
|
|
6632
|
+
it('should have #getAnalyzerMetricsPrePayload', () => {
|
|
6633
|
+
assert.exists(meeting.getAnalyzerMetricsPrePayload);
|
|
6634
|
+
});
|
|
6635
|
+
|
|
6636
|
+
beforeEach(() => {
|
|
6637
|
+
meeting.meetingRequest.getAnalyzerMetricsPrePayload = sinon
|
|
6638
|
+
.stub()
|
|
6639
|
+
.returns(Promise.resolve());
|
|
6640
|
+
meeting.webex.internal = {services: {get: sinon.stub().returns('Locus URL')}};
|
|
6641
|
+
meeting.correlationId = 'correlation-id';
|
|
6642
|
+
});
|
|
6643
|
+
|
|
6644
|
+
it('it should include meetingLookupUrl if provided', () => {
|
|
6645
|
+
const res = meeting.getAnalyzerMetricsPrePayload({
|
|
6646
|
+
meetingLookupUrl: 'https://service-url.com',
|
|
6647
|
+
event: 'client.meetinginfo.response',
|
|
6648
|
+
});
|
|
6649
|
+
|
|
6650
|
+
assert.deepEqual(res.event, {
|
|
6651
|
+
canProceed: true,
|
|
6652
|
+
eventData: {
|
|
6653
|
+
webClientDomain: '',
|
|
6654
|
+
},
|
|
6655
|
+
identifiers: {
|
|
6656
|
+
correlationId: 'correlation-id',
|
|
6657
|
+
deviceId: uuid3,
|
|
6658
|
+
locusUrl: 'Locus URL',
|
|
6659
|
+
meetingLookupUrl: 'https://service-url.com',
|
|
6660
|
+
orgId: undefined,
|
|
6661
|
+
userId: uuid1,
|
|
6662
|
+
},
|
|
6663
|
+
name: 'client.meetinginfo.response',
|
|
6664
|
+
});
|
|
6665
|
+
|
|
6666
|
+
assert.deepEqual(res.origin, {
|
|
6667
|
+
channel: undefined,
|
|
6668
|
+
loginType: undefined,
|
|
6669
|
+
userType: undefined,
|
|
6670
|
+
clientInfo: {
|
|
6671
|
+
browser: '',
|
|
6672
|
+
browserVersion: '',
|
|
6673
|
+
clientType: undefined,
|
|
6674
|
+
clientVersion: 'webex-js-sdk/undefined',
|
|
6675
|
+
localNetworkPrefix: null,
|
|
6676
|
+
os: 'other',
|
|
6677
|
+
osVersion: getOSVersion() || 'unknown',
|
|
6678
|
+
subClientType: undefined,
|
|
6679
|
+
},
|
|
6680
|
+
name: 'endpoint',
|
|
6681
|
+
networkType: 'unknown',
|
|
6682
|
+
userAgent: 'webex-js-sdk/test-undefined client=undefined; (os=linux/5)',
|
|
6683
|
+
});
|
|
6684
|
+
|
|
6685
|
+
assert.deepEqual(res.senderCountryCode, undefined);
|
|
6686
|
+
assert.deepEqual(res.version, 1);
|
|
6687
|
+
|
|
6688
|
+
});
|
|
6689
|
+
});
|
|
5677
6690
|
});
|
|
5678
6691
|
});
|
|
5679
6692
|
});
|