@webex/plugin-meetings 3.0.0-beta.16 → 3.0.0-beta.160
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/annotation/annotation.types.js +7 -0
- package/dist/annotation/annotation.types.js.map +1 -0
- package/dist/annotation/constants.js +49 -0
- package/dist/annotation/constants.js.map +1 -0
- package/dist/annotation/index.js +359 -0
- package/dist/annotation/index.js.map +1 -0
- package/dist/breakouts/breakout.js +212 -0
- package/dist/breakouts/breakout.js.map +1 -0
- package/dist/breakouts/collection.js +23 -0
- package/dist/breakouts/collection.js.map +1 -0
- package/dist/breakouts/edit-lock-error.js +52 -0
- package/dist/breakouts/edit-lock-error.js.map +1 -0
- package/dist/breakouts/events.js +43 -0
- package/dist/breakouts/events.js.map +1 -0
- package/dist/breakouts/index.js +1046 -0
- package/dist/breakouts/index.js.map +1 -0
- package/dist/breakouts/request.js +78 -0
- package/dist/breakouts/request.js.map +1 -0
- package/dist/breakouts/utils.js +67 -0
- package/dist/breakouts/utils.js.map +1 -0
- package/dist/common/errors/webex-errors.js +3 -2
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/logs/logger-proxy.js +1 -1
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/config.js +6 -8
- package/dist/config.js.map +1 -1
- package/dist/constants.js +165 -26
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +14 -0
- package/dist/controls-options-manager/constants.js.map +1 -0
- package/dist/controls-options-manager/enums.js +27 -0
- package/dist/controls-options-manager/enums.js.map +1 -0
- package/dist/controls-options-manager/index.js +297 -0
- package/dist/controls-options-manager/index.js.map +1 -0
- package/dist/controls-options-manager/types.js +7 -0
- package/dist/controls-options-manager/types.js.map +1 -0
- package/dist/controls-options-manager/util.js +300 -0
- package/dist/controls-options-manager/util.js.map +1 -0
- package/dist/index.js +77 -0
- package/dist/index.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +91 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +298 -24
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +43 -1
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +2 -1
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js +88 -14
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +39 -134
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +19 -97
- package/dist/media/properties.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +505 -493
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +79 -1
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +2275 -2152
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +291 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -0
- package/dist/meeting/muteState.js +229 -124
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +191 -167
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/util.js +443 -443
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +157 -49
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/utilv2.js +20 -5
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +22 -0
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +365 -73
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +7 -0
- package/dist/meetings/meetings.types.js.map +1 -0
- package/dist/meetings/request.js +16 -12
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +88 -1
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +41 -0
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js +15 -0
- package/dist/member/types.js.map +1 -0
- package/dist/member/util.js +86 -3
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +10 -0
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +94 -11
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +109 -39
- package/dist/members/request.js.map +1 -1
- package/dist/members/types.js +15 -0
- package/dist/members/types.js.map +1 -0
- package/dist/members/util.js +316 -233
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js +50 -14
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/constants.js +3 -5
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +48 -29
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +265 -36
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +52 -19
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +53 -33
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +44 -18
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +60 -3
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +322 -103
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.js +4 -2
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/reachability/index.js +117 -60
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +12 -5
- package/dist/reachability/request.js.map +1 -1
- package/dist/reactions/constants.js +13 -0
- package/dist/reactions/constants.js.map +1 -0
- package/dist/reactions/reactions.js +2 -2
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js +18 -18
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +190 -145
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/enums.js +17 -0
- package/dist/recording-controller/enums.js.map +1 -0
- package/dist/recording-controller/index.js +343 -0
- package/dist/recording-controller/index.js.map +1 -0
- package/dist/recording-controller/util.js +63 -0
- package/dist/recording-controller/util.js.map +1 -0
- package/dist/roap/index.js +21 -29
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +127 -92
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +135 -53
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +1 -93
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +329 -314
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +103 -54
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/types/annotation/annotation.types.d.ts +43 -0
- package/dist/types/annotation/constants.d.ts +31 -0
- package/dist/types/annotation/index.d.ts +124 -0
- package/dist/types/breakouts/breakout.d.ts +8 -0
- package/dist/types/breakouts/collection.d.ts +5 -0
- package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
- package/dist/types/breakouts/events.d.ts +2 -0
- package/dist/types/breakouts/index.d.ts +5 -0
- package/dist/types/breakouts/request.d.ts +22 -0
- package/dist/types/breakouts/utils.d.ts +15 -0
- package/dist/types/common/browser-detection.d.ts +9 -0
- package/dist/types/common/collection.d.ts +48 -0
- package/dist/types/common/config.d.ts +2 -0
- package/dist/types/common/errors/captcha-error.d.ts +15 -0
- package/dist/types/common/errors/intent-to-join.d.ts +16 -0
- package/dist/types/common/errors/join-meeting.d.ts +17 -0
- package/dist/types/common/errors/media.d.ts +15 -0
- package/dist/types/common/errors/parameter.d.ts +15 -0
- package/dist/types/common/errors/password-error.d.ts +15 -0
- package/dist/types/common/errors/permission.d.ts +14 -0
- package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
- package/dist/types/common/errors/reconnection.d.ts +15 -0
- package/dist/types/common/errors/stats.d.ts +15 -0
- package/dist/types/common/errors/webex-errors.d.ts +69 -0
- package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
- package/dist/types/common/events/events-scope.d.ts +17 -0
- package/dist/types/common/events/events.d.ts +12 -0
- package/dist/types/common/events/trigger-proxy.d.ts +2 -0
- package/dist/types/common/events/util.d.ts +2 -0
- package/dist/types/common/logs/logger-config.d.ts +2 -0
- package/dist/types/common/logs/logger-proxy.d.ts +2 -0
- package/dist/types/common/logs/request.d.ts +34 -0
- package/dist/types/common/queue.d.ts +32 -0
- package/dist/types/config.d.ts +72 -0
- package/dist/types/constants.d.ts +978 -0
- package/dist/types/controls-options-manager/constants.d.ts +4 -0
- package/dist/types/controls-options-manager/enums.d.ts +15 -0
- package/dist/types/controls-options-manager/index.d.ts +136 -0
- package/dist/types/controls-options-manager/types.d.ts +43 -0
- package/dist/types/controls-options-manager/util.d.ts +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/locus-info/controlsUtils.d.ts +2 -0
- package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
- package/dist/types/locus-info/fullState.d.ts +2 -0
- package/dist/types/locus-info/hostUtils.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +315 -0
- package/dist/types/locus-info/infoUtils.d.ts +2 -0
- package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
- package/dist/types/locus-info/parser.d.ts +212 -0
- package/dist/types/locus-info/selfUtils.d.ts +2 -0
- package/dist/types/media/index.d.ts +34 -0
- package/dist/types/media/properties.d.ts +86 -0
- package/dist/types/media/util.d.ts +2 -0
- package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +149 -0
- package/dist/types/meeting/index.d.ts +1516 -0
- package/dist/types/meeting/locusMediaRequest.d.ts +70 -0
- package/dist/types/meeting/muteState.d.ts +184 -0
- package/dist/types/meeting/request.d.ts +270 -0
- package/dist/types/meeting/request.type.d.ts +11 -0
- package/dist/types/meeting/state.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +75 -0
- package/dist/types/meeting-info/collection.d.ts +20 -0
- package/dist/types/meeting-info/index.d.ts +57 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +122 -0
- package/dist/types/meeting-info/request.d.ts +22 -0
- package/dist/types/meeting-info/util.d.ts +2 -0
- package/dist/types/meeting-info/utilv2.d.ts +2 -0
- package/dist/types/meetings/collection.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +364 -0
- package/dist/types/meetings/meetings.types.d.ts +4 -0
- package/dist/types/meetings/request.d.ts +27 -0
- package/dist/types/meetings/util.d.ts +18 -0
- package/dist/types/member/index.d.ts +157 -0
- package/dist/types/member/types.d.ts +21 -0
- package/dist/types/member/util.d.ts +2 -0
- package/dist/types/members/collection.d.ts +29 -0
- package/dist/types/members/index.d.ts +353 -0
- package/dist/types/members/request.d.ts +114 -0
- package/dist/types/members/types.d.ts +24 -0
- package/dist/types/members/util.d.ts +210 -0
- package/dist/types/metrics/config.d.ts +195 -0
- package/dist/types/metrics/constants.d.ts +55 -0
- package/dist/types/metrics/index.d.ts +169 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +118 -0
- package/dist/types/multistream/receiveSlot.d.ts +68 -0
- package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
- package/dist/types/multistream/remoteMedia.d.ts +72 -0
- package/dist/types/multistream/remoteMediaGroup.d.ts +47 -0
- package/dist/types/multistream/remoteMediaManager.d.ts +277 -0
- package/dist/types/networkQualityMonitor/index.d.ts +70 -0
- package/dist/types/personal-meeting-room/index.d.ts +47 -0
- package/dist/types/personal-meeting-room/request.d.ts +14 -0
- package/dist/types/personal-meeting-room/util.d.ts +2 -0
- package/dist/types/reachability/index.d.ts +152 -0
- package/dist/types/reachability/request.d.ts +37 -0
- package/dist/types/reactions/constants.d.ts +3 -0
- package/dist/types/reactions/reactions.d.ts +4 -0
- package/dist/types/reactions/reactions.type.d.ts +52 -0
- package/dist/types/reconnection-manager/index.d.ts +126 -0
- package/dist/types/recording-controller/enums.d.ts +7 -0
- package/dist/types/recording-controller/index.d.ts +193 -0
- package/dist/types/recording-controller/util.d.ts +13 -0
- package/dist/types/roap/index.d.ts +77 -0
- package/dist/types/roap/request.d.ts +36 -0
- package/dist/types/roap/turnDiscovery.d.ts +91 -0
- package/dist/types/statsAnalyzer/global.d.ts +36 -0
- package/dist/types/statsAnalyzer/index.d.ts +200 -0
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
- package/dist/types/transcription/index.d.ts +64 -0
- package/package.json +28 -21
- package/src/annotation/annotation.types.ts +52 -0
- package/src/annotation/constants.ts +36 -0
- package/src/annotation/index.ts +343 -0
- package/src/breakouts/README.md +220 -0
- package/src/breakouts/breakout.ts +180 -0
- package/src/breakouts/collection.ts +19 -0
- package/src/breakouts/edit-lock-error.ts +25 -0
- package/src/breakouts/events.ts +37 -0
- package/src/breakouts/index.ts +921 -0
- package/src/breakouts/request.ts +55 -0
- package/src/breakouts/utils.ts +57 -0
- package/src/common/errors/webex-errors.ts +6 -2
- package/src/common/logs/logger-proxy.ts +1 -1
- package/src/config.ts +5 -7
- package/src/constants.ts +155 -20
- package/src/controls-options-manager/constants.ts +5 -0
- package/src/controls-options-manager/enums.ts +18 -0
- package/src/controls-options-manager/index.ts +278 -0
- package/src/controls-options-manager/types.ts +59 -0
- package/src/controls-options-manager/util.ts +286 -0
- package/src/index.ts +34 -0
- package/src/locus-info/controlsUtils.ts +108 -0
- package/src/locus-info/index.ts +310 -21
- package/src/locus-info/mediaSharesUtils.ts +48 -0
- package/src/locus-info/parser.ts +2 -1
- package/src/locus-info/selfUtils.ts +80 -2
- package/src/media/index.ts +70 -142
- package/src/media/properties.ts +41 -104
- package/src/mediaQualityMetrics/config.ts +379 -377
- package/src/meeting/in-meeting-actions.ts +156 -0
- package/src/meeting/index.ts +1744 -1767
- package/src/meeting/locusMediaRequest.ts +309 -0
- package/src/meeting/muteState.ts +228 -132
- package/src/meeting/request.ts +100 -91
- package/src/meeting/request.type.ts +2 -0
- package/src/meeting/util.ts +421 -421
- package/src/meeting-info/meeting-info-v2.ts +134 -13
- package/src/meeting-info/utilv2.ts +13 -3
- package/src/meetings/collection.ts +20 -0
- package/src/meetings/index.ts +385 -83
- package/src/meetings/meetings.types.ts +12 -0
- package/src/meetings/request.ts +3 -1
- package/src/meetings/util.ts +103 -4
- package/src/member/index.ts +40 -0
- package/src/member/types.ts +24 -0
- package/src/member/util.ts +81 -1
- package/src/members/collection.ts +8 -0
- package/src/members/index.ts +108 -6
- package/src/members/request.ts +98 -17
- package/src/members/types.ts +28 -0
- package/src/members/util.ts +319 -240
- package/src/metrics/config.ts +49 -10
- package/src/metrics/constants.ts +2 -4
- package/src/metrics/index.ts +43 -27
- package/src/multistream/mediaRequestManager.ts +337 -63
- package/src/multistream/receiveSlot.ts +68 -26
- package/src/multistream/receiveSlotManager.ts +61 -38
- package/src/multistream/remoteMedia.ts +29 -3
- package/src/multistream/remoteMediaGroup.ts +61 -2
- package/src/multistream/remoteMediaManager.ts +260 -66
- package/src/networkQualityMonitor/index.ts +6 -6
- package/src/reachability/index.ts +75 -25
- package/src/reachability/request.ts +10 -5
- package/src/reactions/constants.ts +4 -0
- package/src/reactions/reactions.ts +4 -4
- package/src/reactions/reactions.type.ts +28 -3
- package/src/reconnection-manager/index.ts +53 -32
- package/src/recording-controller/enums.ts +8 -0
- package/src/recording-controller/index.ts +315 -0
- package/src/recording-controller/util.ts +58 -0
- package/src/roap/index.ts +21 -30
- package/src/roap/request.ts +51 -52
- package/src/roap/turnDiscovery.ts +51 -27
- package/src/statsAnalyzer/global.ts +1 -94
- package/src/statsAnalyzer/index.ts +380 -390
- package/src/statsAnalyzer/mqaUtil.ts +106 -99
- package/test/integration/spec/converged-space-meetings.js +233 -0
- package/test/integration/spec/journey.js +331 -254
- package/test/integration/spec/space-meeting.js +77 -4
- package/test/unit/spec/annotation/index.ts +436 -0
- package/test/unit/spec/breakouts/breakout.ts +233 -0
- package/test/unit/spec/breakouts/collection.ts +15 -0
- package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
- package/test/unit/spec/breakouts/events.ts +77 -0
- package/test/unit/spec/breakouts/index.ts +1790 -0
- package/test/unit/spec/breakouts/request.ts +104 -0
- package/test/unit/spec/breakouts/utils.js +72 -0
- package/test/unit/spec/controls-options-manager/index.js +287 -0
- package/test/unit/spec/controls-options-manager/util.js +518 -0
- package/test/unit/spec/fixture/locus.js +1 -0
- package/test/unit/spec/locus-info/controlsUtils.js +303 -30
- package/test/unit/spec/locus-info/index.js +616 -4
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
- package/test/unit/spec/locus-info/selfConstant.js +38 -0
- package/test/unit/spec/locus-info/selfUtils.js +249 -0
- package/test/unit/spec/media/index.ts +118 -22
- package/test/unit/spec/media/properties.ts +9 -9
- package/test/unit/spec/meeting/in-meeting-actions.ts +76 -0
- package/test/unit/spec/meeting/index.js +2496 -1375
- package/test/unit/spec/meeting/locusMediaRequest.ts +436 -0
- package/test/unit/spec/meeting/muteState.js +370 -208
- package/test/unit/spec/meeting/request.js +354 -42
- package/test/unit/spec/meeting/utils.js +268 -156
- package/test/unit/spec/meeting-info/meetinginfov2.js +383 -5
- package/test/unit/spec/meeting-info/utilv2.js +21 -0
- package/test/unit/spec/meetings/collection.js +14 -0
- package/test/unit/spec/meetings/index.js +866 -120
- package/test/unit/spec/meetings/utils.js +206 -2
- package/test/unit/spec/member/index.js +24 -0
- package/test/unit/spec/member/util.js +384 -32
- package/test/unit/spec/members/index.js +320 -1
- package/test/unit/spec/members/request.js +206 -27
- package/test/unit/spec/members/utils.js +184 -0
- package/test/unit/spec/metrics/index.js +98 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +1012 -109
- package/test/unit/spec/multistream/receiveSlot.ts +77 -18
- package/test/unit/spec/multistream/receiveSlotManager.ts +69 -39
- package/test/unit/spec/multistream/remoteMedia.ts +32 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +271 -5
- package/test/unit/spec/multistream/remoteMediaManager.ts +672 -65
- package/test/unit/spec/networkQualityMonitor/index.js +4 -4
- package/test/unit/spec/reachability/index.ts +176 -25
- package/test/unit/spec/reachability/request.js +66 -0
- package/test/unit/spec/reconnection-manager/index.js +46 -13
- package/test/unit/spec/recording-controller/index.js +231 -0
- package/test/unit/spec/recording-controller/util.js +102 -0
- package/test/unit/spec/roap/index.ts +21 -51
- package/test/unit/spec/roap/request.ts +187 -0
- package/test/unit/spec/roap/turnDiscovery.ts +73 -34
- package/test/unit/spec/stats-analyzer/index.js +94 -43
- package/test/utils/constants.js +9 -0
- package/test/utils/integrationTestUtils.js +46 -0
- package/test/utils/testUtils.js +0 -45
- package/test/utils/webex-config.js +4 -0
- package/test/utils/webex-test-users.js +7 -3
- package/tsconfig.json +6 -0
- package/dist/media/internal-media-core-wrapper.js +0 -18
- package/dist/media/internal-media-core-wrapper.js.map +0 -1
- package/dist/meeting/effectsState.js +0 -262
- package/dist/meeting/effectsState.js.map +0 -1
- package/dist/multistream/multistreamMedia.js +0 -106
- package/dist/multistream/multistreamMedia.js.map +0 -1
- package/src/index.js +0 -15
- package/src/media/internal-media-core-wrapper.ts +0 -9
- package/src/meeting/effectsState.ts +0 -211
- package/src/multistream/multistreamMedia.ts +0 -93
- package/test/unit/spec/meeting/effectsState.js +0 -281
package/src/meeting/index.ts
CHANGED
|
@@ -1,12 +1,29 @@
|
|
|
1
1
|
import uuid from 'uuid';
|
|
2
|
-
import {cloneDeep, isEqual, pick,
|
|
2
|
+
import {cloneDeep, isEqual, pick, defer, isEmpty} from 'lodash';
|
|
3
3
|
// @ts-ignore - Fix this
|
|
4
4
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
ConnectionState,
|
|
7
|
+
Errors,
|
|
8
|
+
ErrorType,
|
|
9
|
+
Event,
|
|
10
|
+
MediaContent,
|
|
11
|
+
MediaType,
|
|
12
|
+
RemoteTrackType,
|
|
13
|
+
} from '@webex/internal-media-core';
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
getDevices,
|
|
17
|
+
LocalTrack,
|
|
18
|
+
LocalCameraTrack,
|
|
19
|
+
LocalDisplayTrack,
|
|
20
|
+
LocalMicrophoneTrack,
|
|
21
|
+
LocalTrackEvents,
|
|
22
|
+
TrackMuteEvent,
|
|
23
|
+
} from '@webex/media-helpers';
|
|
6
24
|
|
|
7
25
|
import {
|
|
8
26
|
MeetingNotActiveError,
|
|
9
|
-
createMeetingsError,
|
|
10
27
|
UserInLobbyError,
|
|
11
28
|
NoMediaEstablishedYetError,
|
|
12
29
|
UserNotJoinedError,
|
|
@@ -16,20 +33,22 @@ import NetworkQualityMonitor from '../networkQualityMonitor';
|
|
|
16
33
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
17
34
|
import Trigger from '../common/events/trigger-proxy';
|
|
18
35
|
import Roap from '../roap/index';
|
|
19
|
-
import Media from '../media';
|
|
36
|
+
import Media, {type BundlePolicy} from '../media';
|
|
20
37
|
import MediaProperties from '../media/properties';
|
|
21
38
|
import MeetingStateMachine from './state';
|
|
22
|
-
import createMuteState from './muteState';
|
|
23
|
-
import createEffectsState from './effectsState';
|
|
39
|
+
import {createMuteState} from './muteState';
|
|
24
40
|
import LocusInfo from '../locus-info';
|
|
25
41
|
import Metrics from '../metrics';
|
|
26
|
-
import {trigger,
|
|
42
|
+
import {trigger, error as MetricsError, eventType} from '../metrics/config';
|
|
27
43
|
import ReconnectionManager from '../reconnection-manager';
|
|
28
44
|
import MeetingRequest from './request';
|
|
29
45
|
import Members from '../members/index';
|
|
30
46
|
import MeetingUtil from './util';
|
|
47
|
+
import RecordingUtil from '../recording-controller/util';
|
|
48
|
+
import ControlsOptionsUtil from '../controls-options-manager/util';
|
|
31
49
|
import MediaUtil from '../media/util';
|
|
32
50
|
import Transcription from '../transcription';
|
|
51
|
+
import {Reactions, SkinTones} from '../reactions/reactions';
|
|
33
52
|
import PasswordError from '../common/errors/password-error';
|
|
34
53
|
import CaptchaError from '../common/errors/captcha-error';
|
|
35
54
|
import ReconnectionError from '../common/errors/reconnection';
|
|
@@ -40,10 +59,12 @@ import {
|
|
|
40
59
|
_JOIN_,
|
|
41
60
|
AUDIO,
|
|
42
61
|
CONTENT,
|
|
62
|
+
DISPLAY_HINTS,
|
|
43
63
|
ENDED,
|
|
44
64
|
EVENT_TRIGGERS,
|
|
45
65
|
EVENT_TYPES,
|
|
46
66
|
EVENTS,
|
|
67
|
+
BREAKOUTS,
|
|
47
68
|
FLOOR_ACTION,
|
|
48
69
|
FULL_STATE,
|
|
49
70
|
LAYOUT_TYPES,
|
|
@@ -64,53 +85,83 @@ import {
|
|
|
64
85
|
RECORDING_STATE,
|
|
65
86
|
SHARE_STATUS,
|
|
66
87
|
SHARE_STOPPED_REASON,
|
|
67
|
-
VIDEO_RESOLUTIONS,
|
|
68
88
|
VIDEO,
|
|
69
|
-
BNR_STATUS,
|
|
70
89
|
HTTP_VERBS,
|
|
90
|
+
SELF_ROLES,
|
|
71
91
|
} from '../constants';
|
|
72
92
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
73
93
|
import ParameterError from '../common/errors/parameter';
|
|
74
|
-
import MediaError from '../common/errors/media';
|
|
75
94
|
import {
|
|
76
95
|
MeetingInfoV2PasswordError,
|
|
77
96
|
MeetingInfoV2CaptchaError,
|
|
97
|
+
MeetingInfoV2PolicyError,
|
|
78
98
|
} from '../meeting-info/meeting-info-v2';
|
|
79
99
|
import BrowserDetection from '../common/browser-detection';
|
|
80
|
-
import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
100
|
+
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
81
101
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
82
102
|
import {
|
|
103
|
+
Configuration as RemoteMediaManagerConfiguration,
|
|
83
104
|
RemoteMediaManager,
|
|
84
105
|
Event as RemoteMediaManagerEvent,
|
|
85
106
|
} from '../multistream/remoteMediaManager';
|
|
86
|
-
import {
|
|
87
|
-
|
|
88
|
-
|
|
107
|
+
import {
|
|
108
|
+
Reaction,
|
|
109
|
+
ReactionServerType,
|
|
110
|
+
SkinToneType,
|
|
111
|
+
ProcessedReaction,
|
|
112
|
+
RelayEvent,
|
|
113
|
+
} from '../reactions/reactions.type';
|
|
114
|
+
import Breakouts from '../breakouts';
|
|
115
|
+
import Annotation from '../annotation';
|
|
89
116
|
|
|
90
117
|
import InMeetingActions from './in-meeting-actions';
|
|
118
|
+
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
119
|
+
import RecordingController from '../recording-controller';
|
|
120
|
+
import ControlsOptionsManager from '../controls-options-manager';
|
|
121
|
+
import PermissionError from '../common/errors/permission';
|
|
122
|
+
import {LocusMediaRequest} from './locusMediaRequest';
|
|
123
|
+
import {AnnotationInfo} from '../annotation/annotation.types';
|
|
91
124
|
|
|
92
125
|
const {isBrowser} = BrowserDetection();
|
|
93
126
|
|
|
94
|
-
const logRequest = (request: any, {
|
|
95
|
-
LoggerProxy.logger.info(
|
|
127
|
+
const logRequest = (request: any, {logText = ''}) => {
|
|
128
|
+
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
96
129
|
|
|
97
130
|
return request
|
|
98
131
|
.then((arg) => {
|
|
99
|
-
LoggerProxy.logger.info(
|
|
132
|
+
LoggerProxy.logger.info(`${logText} - has been successfully sent`);
|
|
100
133
|
|
|
101
134
|
return arg;
|
|
102
135
|
})
|
|
103
136
|
.catch((error) => {
|
|
104
|
-
LoggerProxy.logger.error(
|
|
137
|
+
LoggerProxy.logger.error(`${logText} - has failed: `, error);
|
|
105
138
|
throw error;
|
|
106
139
|
});
|
|
107
140
|
};
|
|
108
141
|
|
|
142
|
+
export type LocalTracks = {
|
|
143
|
+
microphone?: LocalMicrophoneTrack;
|
|
144
|
+
camera?: LocalCameraTrack;
|
|
145
|
+
screenShare?: {
|
|
146
|
+
audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
|
|
147
|
+
video?: LocalDisplayTrack;
|
|
148
|
+
};
|
|
149
|
+
annotationInfo?: AnnotationInfo;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
export type AddMediaOptions = {
|
|
153
|
+
localTracks?: LocalTracks;
|
|
154
|
+
audioEnabled?: boolean; // if not specified, default value true is used
|
|
155
|
+
videoEnabled?: boolean; // if not specified, default value true is used
|
|
156
|
+
receiveShare?: boolean; // if not specified, default value true is used
|
|
157
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
|
158
|
+
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
|
159
|
+
};
|
|
160
|
+
|
|
109
161
|
export const MEDIA_UPDATE_TYPE = {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
SHARE: 'SHARE',
|
|
162
|
+
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
163
|
+
SHARE_FLOOR_REQUEST: 'SHARE_FLOOR_REQUEST',
|
|
164
|
+
UPDATE_MEDIA: 'UPDATE_MEDIA',
|
|
114
165
|
};
|
|
115
166
|
|
|
116
167
|
/**
|
|
@@ -125,16 +176,6 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
125
176
|
* @property {boolean} isSharing
|
|
126
177
|
*/
|
|
127
178
|
|
|
128
|
-
/**
|
|
129
|
-
* AudioVideo
|
|
130
|
-
* @typedef {Object} AudioVideo
|
|
131
|
-
* @property {Object} audio
|
|
132
|
-
* @property {String} audio.deviceId
|
|
133
|
-
* @property {Object} video
|
|
134
|
-
* @property {String} video.deviceId
|
|
135
|
-
* @property {String} video.localVideoQuality // [240p, 360p, 480p, 720p, 1080p]
|
|
136
|
-
*/
|
|
137
|
-
|
|
138
179
|
/**
|
|
139
180
|
* SharePreferences
|
|
140
181
|
* @typedef {Object} SharePreferences
|
|
@@ -149,18 +190,10 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
149
190
|
* @property {String} [pin]
|
|
150
191
|
* @property {Boolean} [moderator]
|
|
151
192
|
* @property {String|Object} [meetingQuality]
|
|
152
|
-
* @property {String} [meetingQuality.local]
|
|
153
193
|
* @property {String} [meetingQuality.remote]
|
|
154
194
|
* @property {Boolean} [rejoin]
|
|
155
195
|
* @property {Boolean} [enableMultistream]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* SendOptions
|
|
160
|
-
* @typedef {Object} SendOptions
|
|
161
|
-
* @property {Boolean} sendAudio
|
|
162
|
-
* @property {Boolean} sendVideo
|
|
163
|
-
* @property {Boolean} sendShare
|
|
196
|
+
* @property {String} [correlationId]
|
|
164
197
|
*/
|
|
165
198
|
|
|
166
199
|
/**
|
|
@@ -403,12 +436,13 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
403
436
|
export default class Meeting extends StatelessWebexPlugin {
|
|
404
437
|
attrs: any;
|
|
405
438
|
audio: any;
|
|
439
|
+
breakouts: any;
|
|
440
|
+
annotation: any;
|
|
406
441
|
conversationUrl: string;
|
|
407
442
|
correlationId: string;
|
|
408
443
|
destination: string;
|
|
409
444
|
destinationType: string;
|
|
410
445
|
deviceUrl: string;
|
|
411
|
-
effects: any;
|
|
412
446
|
hostId: string;
|
|
413
447
|
id: string;
|
|
414
448
|
isMultistream: boolean;
|
|
@@ -416,7 +450,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
416
450
|
mediaConnections: any[];
|
|
417
451
|
mediaId?: string;
|
|
418
452
|
meetingFiniteStateMachine: any;
|
|
419
|
-
meetingInfo:
|
|
453
|
+
meetingInfo: any;
|
|
420
454
|
meetingRequest: MeetingRequest;
|
|
421
455
|
members: Members;
|
|
422
456
|
options: object;
|
|
@@ -428,6 +462,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
428
462
|
resource: string;
|
|
429
463
|
roap: Roap;
|
|
430
464
|
roapSeq: number;
|
|
465
|
+
selfUrl?: string; // comes from Locus, initialized by updateMeetingObject()
|
|
431
466
|
sipUri: string;
|
|
432
467
|
type: string;
|
|
433
468
|
userId: string;
|
|
@@ -449,32 +484,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
449
484
|
keepAliveTimerId: NodeJS.Timeout;
|
|
450
485
|
lastVideoLayoutInfo: any;
|
|
451
486
|
locusInfo: any;
|
|
452
|
-
|
|
487
|
+
locusMediaRequest?: LocusMediaRequest;
|
|
453
488
|
mediaProperties: MediaProperties;
|
|
454
489
|
mediaRequestManagers: {
|
|
455
490
|
audio: MediaRequestManager;
|
|
456
491
|
video: MediaRequestManager;
|
|
492
|
+
screenShareAudio: MediaRequestManager;
|
|
493
|
+
screenShareVideo: MediaRequestManager;
|
|
457
494
|
};
|
|
458
495
|
|
|
459
496
|
meetingInfoFailureReason: string;
|
|
497
|
+
meetingInfoFailureCode?: number;
|
|
460
498
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
461
499
|
networkStatus: string;
|
|
462
500
|
passwordStatus: string;
|
|
463
501
|
queuedMediaUpdates: any[];
|
|
464
502
|
recording: any;
|
|
465
503
|
remoteMediaManager: RemoteMediaManager | null;
|
|
504
|
+
recordingController: RecordingController;
|
|
505
|
+
controlsOptionsManager: ControlsOptionsManager;
|
|
466
506
|
requiredCaptcha: any;
|
|
467
507
|
receiveSlotManager: ReceiveSlotManager;
|
|
468
508
|
shareStatus: string;
|
|
469
509
|
statsAnalyzer: StatsAnalyzer;
|
|
470
510
|
transcription: Transcription;
|
|
471
511
|
updateMediaConnections: (mediaConnections: any[]) => void;
|
|
472
|
-
|
|
512
|
+
endCallInitJoinReq: any;
|
|
473
513
|
endJoinReqResp: any;
|
|
474
514
|
endLocalSDPGenRemoteSDPRecvDelay: any;
|
|
475
515
|
joinedWith: any;
|
|
476
516
|
locusId: any;
|
|
477
|
-
|
|
517
|
+
startCallInitJoinReq: any;
|
|
478
518
|
startJoinReqResp: any;
|
|
479
519
|
startLocalSDPGenRemoteSDPRecvDelay: any;
|
|
480
520
|
wirelessShare: any;
|
|
@@ -487,8 +527,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
487
527
|
resourceUrl: string;
|
|
488
528
|
selfId: string;
|
|
489
529
|
state: any;
|
|
490
|
-
|
|
530
|
+
localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
531
|
+
localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
532
|
+
underlyingLocalTrackChangeHandler: () => void;
|
|
533
|
+
roles: any[];
|
|
534
|
+
environment: string;
|
|
491
535
|
namespace = MEETINGS;
|
|
536
|
+
annotationInfo: AnnotationInfo;
|
|
492
537
|
|
|
493
538
|
/**
|
|
494
539
|
* @param {Object} attrs
|
|
@@ -523,7 +568,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
523
568
|
*/
|
|
524
569
|
this.id = uuid.v4();
|
|
525
570
|
/**
|
|
526
|
-
* Correlation ID used for network tracking of meeting
|
|
571
|
+
* Correlation ID used for network tracking of meeting
|
|
527
572
|
* @instance
|
|
528
573
|
* @type {String}
|
|
529
574
|
* @readonly
|
|
@@ -573,41 +618,124 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
573
618
|
*/
|
|
574
619
|
// TODO: needs to be defined as a class
|
|
575
620
|
this.meetingInfo = {};
|
|
621
|
+
/**
|
|
622
|
+
* @instance
|
|
623
|
+
* @type {Breakouts}
|
|
624
|
+
* @public
|
|
625
|
+
* @memberof Meeting
|
|
626
|
+
*/
|
|
627
|
+
// @ts-ignore
|
|
628
|
+
this.breakouts = new Breakouts({meetingId: this.id}, {parent: this.webex});
|
|
629
|
+
/**
|
|
630
|
+
* @instance
|
|
631
|
+
* @type {Annotation}
|
|
632
|
+
* @public
|
|
633
|
+
* @memberof Meeting
|
|
634
|
+
*/
|
|
635
|
+
// @ts-ignore
|
|
636
|
+
this.annotation = new Annotation({parent: this.webex});
|
|
576
637
|
/**
|
|
577
638
|
* helper class for managing receive slots (for multistream media connections)
|
|
578
639
|
*/
|
|
579
|
-
this.receiveSlotManager = new ReceiveSlotManager(
|
|
640
|
+
this.receiveSlotManager = new ReceiveSlotManager(
|
|
641
|
+
(mediaType: MediaType) => {
|
|
642
|
+
if (!this.mediaProperties?.webrtcMediaConnection) {
|
|
643
|
+
return Promise.reject(new Error('Webrtc media connection is missing'));
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
return this.mediaProperties.webrtcMediaConnection.createReceiveSlot(mediaType);
|
|
647
|
+
},
|
|
648
|
+
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
649
|
+
);
|
|
580
650
|
/**
|
|
581
|
-
*
|
|
582
|
-
* All media requests sent out for
|
|
651
|
+
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
652
|
+
* All multistream media requests sent out for this meeting have to go through them.
|
|
583
653
|
*/
|
|
584
654
|
this.mediaRequestManagers = {
|
|
585
|
-
audio: new MediaRequestManager(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
655
|
+
audio: new MediaRequestManager(
|
|
656
|
+
(mediaRequests) => {
|
|
657
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
658
|
+
LoggerProxy.logger.warn(
|
|
659
|
+
'Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created'
|
|
660
|
+
);
|
|
661
|
+
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
665
|
+
MediaType.AudioMain,
|
|
666
|
+
mediaRequests
|
|
589
667
|
);
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
// @ts-ignore - config coming from registerPlugin
|
|
671
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
672
|
+
kind: 'audio',
|
|
673
|
+
trimRequestsToNumOfSources: false,
|
|
674
|
+
}
|
|
675
|
+
),
|
|
676
|
+
video: new MediaRequestManager(
|
|
677
|
+
(mediaRequests) => {
|
|
678
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
679
|
+
LoggerProxy.logger.warn(
|
|
680
|
+
'Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created'
|
|
681
|
+
);
|
|
590
682
|
|
|
591
|
-
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
686
|
+
MediaType.VideoMain,
|
|
687
|
+
mediaRequests
|
|
688
|
+
);
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
// @ts-ignore - config coming from registerPlugin
|
|
692
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
693
|
+
kind: 'video',
|
|
694
|
+
trimRequestsToNumOfSources: true,
|
|
592
695
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
696
|
+
),
|
|
697
|
+
screenShareAudio: new MediaRequestManager(
|
|
698
|
+
(mediaRequests) => {
|
|
699
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
700
|
+
LoggerProxy.logger.warn(
|
|
701
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare audio media request before media connection was created'
|
|
702
|
+
);
|
|
703
|
+
|
|
704
|
+
return;
|
|
705
|
+
}
|
|
706
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
707
|
+
MediaType.AudioSlides,
|
|
708
|
+
mediaRequests
|
|
602
709
|
);
|
|
710
|
+
},
|
|
711
|
+
{
|
|
712
|
+
// @ts-ignore - config coming from registerPlugin
|
|
713
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
714
|
+
kind: 'audio',
|
|
715
|
+
trimRequestsToNumOfSources: false,
|
|
716
|
+
}
|
|
717
|
+
),
|
|
718
|
+
screenShareVideo: new MediaRequestManager(
|
|
719
|
+
(mediaRequests) => {
|
|
720
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
721
|
+
LoggerProxy.logger.warn(
|
|
722
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare video media request before media connection was created'
|
|
723
|
+
);
|
|
603
724
|
|
|
604
|
-
|
|
725
|
+
return;
|
|
726
|
+
}
|
|
727
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
728
|
+
MediaType.VideoSlides,
|
|
729
|
+
mediaRequests
|
|
730
|
+
);
|
|
731
|
+
},
|
|
732
|
+
{
|
|
733
|
+
// @ts-ignore - config coming from registerPlugin
|
|
734
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
735
|
+
kind: 'video',
|
|
736
|
+
trimRequestsToNumOfSources: false,
|
|
605
737
|
}
|
|
606
|
-
|
|
607
|
-
MC.MediaType.VideoMain,
|
|
608
|
-
mediaRequests
|
|
609
|
-
);
|
|
610
|
-
}),
|
|
738
|
+
),
|
|
611
739
|
};
|
|
612
740
|
/**
|
|
613
741
|
* @instance
|
|
@@ -620,8 +748,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
620
748
|
locusUrl: attrs.locus && attrs.locus.url,
|
|
621
749
|
receiveSlotManager: this.receiveSlotManager,
|
|
622
750
|
mediaRequestManagers: this.mediaRequestManagers,
|
|
623
|
-
|
|
751
|
+
meeting: this,
|
|
624
752
|
},
|
|
753
|
+
// @ts-ignore - Fix type
|
|
625
754
|
{parent: this.webex}
|
|
626
755
|
);
|
|
627
756
|
/**
|
|
@@ -653,7 +782,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
653
782
|
*/
|
|
654
783
|
this.reconnectionManager = new ReconnectionManager(this);
|
|
655
784
|
/**
|
|
656
|
-
* created
|
|
785
|
+
* created with media connection
|
|
657
786
|
* @instance
|
|
658
787
|
* @type {MuteState}
|
|
659
788
|
* @private
|
|
@@ -661,21 +790,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
661
790
|
*/
|
|
662
791
|
this.audio = null;
|
|
663
792
|
/**
|
|
664
|
-
* created
|
|
793
|
+
* created with media connection
|
|
665
794
|
* @instance
|
|
666
795
|
* @type {MuteState}
|
|
667
796
|
* @private
|
|
668
797
|
* @memberof Meeting
|
|
669
798
|
*/
|
|
670
799
|
this.video = null;
|
|
671
|
-
/**
|
|
672
|
-
* created later
|
|
673
|
-
* @instance
|
|
674
|
-
* @type {EffectsState}
|
|
675
|
-
* @private
|
|
676
|
-
* @memberof Meeting
|
|
677
|
-
*/
|
|
678
|
-
this.effects = null;
|
|
679
800
|
/**
|
|
680
801
|
* @instance
|
|
681
802
|
* @type {MeetingStateMachine}
|
|
@@ -770,7 +891,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
770
891
|
* @private
|
|
771
892
|
* @memberof Meeting
|
|
772
893
|
*/
|
|
773
|
-
this.meetingRequest = new MeetingRequest(
|
|
894
|
+
this.meetingRequest = new MeetingRequest(
|
|
895
|
+
{
|
|
896
|
+
meeting: this,
|
|
897
|
+
},
|
|
898
|
+
options
|
|
899
|
+
);
|
|
774
900
|
/**
|
|
775
901
|
* @instance
|
|
776
902
|
* @type {Array}
|
|
@@ -924,6 +1050,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
924
1050
|
*/
|
|
925
1051
|
// @ts-ignore - Fix type
|
|
926
1052
|
this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
|
|
1053
|
+
|
|
927
1054
|
// We had to add listeners first before setting up the locus instance
|
|
928
1055
|
/**
|
|
929
1056
|
* @instance
|
|
@@ -1014,6 +1141,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1014
1141
|
*/
|
|
1015
1142
|
this.meetingInfoFailureReason = undefined;
|
|
1016
1143
|
|
|
1144
|
+
/**
|
|
1145
|
+
* The numeric code, if any, associated with the last failure to obtain the meeting info
|
|
1146
|
+
* @instance
|
|
1147
|
+
* @type {number}
|
|
1148
|
+
* @private
|
|
1149
|
+
* @memberof Meeting
|
|
1150
|
+
*/
|
|
1151
|
+
this.meetingInfoFailureCode = undefined;
|
|
1152
|
+
|
|
1017
1153
|
/**
|
|
1018
1154
|
* Repeating timer used to send keepAlives when in lobby
|
|
1019
1155
|
* @instance
|
|
@@ -1023,16 +1159,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1023
1159
|
*/
|
|
1024
1160
|
this.keepAliveTimerId = null;
|
|
1025
1161
|
|
|
1162
|
+
/**
|
|
1163
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1164
|
+
* @instance
|
|
1165
|
+
* @type {RecordingController}
|
|
1166
|
+
* @public
|
|
1167
|
+
* @memberof Meeting
|
|
1168
|
+
*/
|
|
1169
|
+
this.recordingController = new RecordingController(this.meetingRequest, {
|
|
1170
|
+
serviceUrl: this.locusInfo?.links?.services?.record?.url,
|
|
1171
|
+
sessionId: this.locusInfo?.fullState?.sessionId,
|
|
1172
|
+
locusUrl: this.locusInfo?.url,
|
|
1173
|
+
displayHints: [],
|
|
1174
|
+
});
|
|
1175
|
+
|
|
1176
|
+
/**
|
|
1177
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1178
|
+
* @instance
|
|
1179
|
+
* @type {ControlsOptionsManager}
|
|
1180
|
+
* @public
|
|
1181
|
+
* @memberof Meeting
|
|
1182
|
+
*/
|
|
1183
|
+
this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
|
|
1184
|
+
locusUrl: this.locusInfo?.url,
|
|
1185
|
+
displayHints: [],
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1026
1188
|
this.setUpLocusInfoListeners();
|
|
1027
1189
|
this.locusInfo.init(attrs.locus ? attrs.locus : {});
|
|
1028
1190
|
this.hasJoinedOnce = false;
|
|
1029
1191
|
|
|
1030
|
-
this.media = new MultistreamMedia(this);
|
|
1031
|
-
|
|
1032
1192
|
/**
|
|
1033
1193
|
* helper class for managing remote streams
|
|
1034
1194
|
*/
|
|
1035
1195
|
this.remoteMediaManager = null;
|
|
1196
|
+
|
|
1197
|
+
this.localAudioTrackMuteStateHandler = (event) => {
|
|
1198
|
+
this.audio.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1199
|
+
};
|
|
1200
|
+
|
|
1201
|
+
this.localVideoTrackMuteStateHandler = (event) => {
|
|
1202
|
+
this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1203
|
+
};
|
|
1204
|
+
|
|
1205
|
+
// The handling of underlying track changes should be done inside
|
|
1206
|
+
// @webex/internal-media-core, but for now we have to do it here, because
|
|
1207
|
+
// RoapMediaConnection has to use raw MediaStreamTracks in its API until
|
|
1208
|
+
// the Calling SDK also moves to using webrtc-core tracks
|
|
1209
|
+
this.underlyingLocalTrackChangeHandler = () => {
|
|
1210
|
+
if (!this.isMultistream) {
|
|
1211
|
+
this.updateTranscodedMediaConnection();
|
|
1212
|
+
}
|
|
1213
|
+
};
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1216
|
+
/**
|
|
1217
|
+
* returns meeting is joined
|
|
1218
|
+
* @private
|
|
1219
|
+
* @memberof Meeting
|
|
1220
|
+
* @returns {Boolean}
|
|
1221
|
+
*/
|
|
1222
|
+
private isJoined() {
|
|
1223
|
+
return this.joinedWith?.state === 'JOINED';
|
|
1036
1224
|
}
|
|
1037
1225
|
|
|
1038
1226
|
/**
|
|
@@ -1047,9 +1235,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1047
1235
|
public async fetchMeetingInfo({
|
|
1048
1236
|
password = null,
|
|
1049
1237
|
captchaCode = null,
|
|
1238
|
+
extraParams = {},
|
|
1050
1239
|
}: {
|
|
1051
1240
|
password?: string;
|
|
1052
1241
|
captchaCode?: string;
|
|
1242
|
+
extraParams?: Record<string, any>;
|
|
1053
1243
|
}) {
|
|
1054
1244
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1055
1245
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
@@ -1080,11 +1270,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1080
1270
|
this.destination,
|
|
1081
1271
|
this.destinationType,
|
|
1082
1272
|
password,
|
|
1083
|
-
captchaInfo
|
|
1273
|
+
captchaInfo,
|
|
1274
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1275
|
+
this.config.installedOrgID,
|
|
1276
|
+
this.locusId,
|
|
1277
|
+
extraParams,
|
|
1278
|
+
{meetingId: this.id}
|
|
1084
1279
|
);
|
|
1085
1280
|
|
|
1086
1281
|
this.parseMeetingInfo(info, this.destination);
|
|
1087
|
-
this.meetingInfo = info ? info.body : null;
|
|
1282
|
+
this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
|
|
1088
1283
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1089
1284
|
this.requiredCaptcha = null;
|
|
1090
1285
|
if (
|
|
@@ -1107,9 +1302,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1107
1302
|
|
|
1108
1303
|
return Promise.resolve();
|
|
1109
1304
|
} catch (err) {
|
|
1110
|
-
if (err instanceof
|
|
1111
|
-
|
|
1305
|
+
if (err instanceof MeetingInfoV2PolicyError) {
|
|
1306
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.POLICY;
|
|
1307
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1308
|
+
|
|
1309
|
+
if (err.meetingInfo) {
|
|
1310
|
+
this.meetingInfo = err.meetingInfo;
|
|
1311
|
+
}
|
|
1312
|
+
|
|
1313
|
+
throw new PermissionError();
|
|
1314
|
+
} else if (err instanceof MeetingInfoV2PasswordError) {
|
|
1112
1315
|
LoggerProxy.logger.info(
|
|
1316
|
+
// @ts-ignore
|
|
1113
1317
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`
|
|
1114
1318
|
);
|
|
1115
1319
|
|
|
@@ -1119,6 +1323,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1119
1323
|
this.meetingNumber = err.meetingInfo.meetingNumber;
|
|
1120
1324
|
}
|
|
1121
1325
|
|
|
1326
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1327
|
+
|
|
1122
1328
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1123
1329
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1124
1330
|
if (this.requiredCaptcha) {
|
|
@@ -1128,8 +1334,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1128
1334
|
|
|
1129
1335
|
throw new PasswordError();
|
|
1130
1336
|
} else if (err instanceof MeetingInfoV2CaptchaError) {
|
|
1131
|
-
// @ts-ignore
|
|
1132
1337
|
LoggerProxy.logger.info(
|
|
1338
|
+
// @ts-ignore
|
|
1133
1339
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
|
|
1134
1340
|
);
|
|
1135
1341
|
|
|
@@ -1137,6 +1343,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1137
1343
|
? MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA
|
|
1138
1344
|
: MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1139
1345
|
|
|
1346
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1347
|
+
|
|
1140
1348
|
if (err.isPasswordRequired) {
|
|
1141
1349
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1142
1350
|
}
|
|
@@ -1201,22 +1409,39 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1201
1409
|
// we have to pass the wbxappapi hostname as the siteFullName parameter
|
|
1202
1410
|
const {hostname} = new URL(this.requiredCaptcha.refreshURL);
|
|
1203
1411
|
|
|
1204
|
-
return
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1412
|
+
return (
|
|
1413
|
+
this.meetingRequest
|
|
1414
|
+
// @ts-ignore
|
|
1415
|
+
.refreshCaptcha({
|
|
1416
|
+
captchaRefreshUrl: `${this.requiredCaptcha.refreshURL}&siteFullName=${hostname}`,
|
|
1417
|
+
captchaId: this.requiredCaptcha.captchaId,
|
|
1418
|
+
})
|
|
1419
|
+
.then((response) => {
|
|
1420
|
+
this.requiredCaptcha.captchaId = response.body.captchaID;
|
|
1421
|
+
this.requiredCaptcha.verificationImageURL = response.body.verificationImageURL;
|
|
1422
|
+
this.requiredCaptcha.verificationAudioURL = response.body.verificationAudioURL;
|
|
1423
|
+
})
|
|
1424
|
+
.catch((error) => {
|
|
1425
|
+
LoggerProxy.logger.error(
|
|
1426
|
+
`Meeting:index#refreshCaptcha --> Error Unable to refresh captcha for ${this.destination} - ${error}`
|
|
1427
|
+
);
|
|
1428
|
+
throw error;
|
|
1429
|
+
})
|
|
1430
|
+
);
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
/**
|
|
1434
|
+
* Posts metrics event for this meeting. Allows the app to send Call Analyzer events.
|
|
1435
|
+
* @param {String} eventName - Call Analyzer event, see eventType in src/metrics/config.ts for possible values
|
|
1436
|
+
* @public
|
|
1437
|
+
* @memberof Meeting
|
|
1438
|
+
* @returns {Promise}
|
|
1439
|
+
*/
|
|
1440
|
+
public postMetrics(eventName: string) {
|
|
1441
|
+
Metrics.postEvent({
|
|
1442
|
+
event: eventName,
|
|
1443
|
+
meeting: this,
|
|
1444
|
+
});
|
|
1220
1445
|
}
|
|
1221
1446
|
|
|
1222
1447
|
/**
|
|
@@ -1229,6 +1454,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1229
1454
|
// meeting update listeners
|
|
1230
1455
|
this.setUpLocusInfoSelfListener();
|
|
1231
1456
|
this.setUpLocusInfoMeetingListener();
|
|
1457
|
+
this.setUpLocusServicesListener();
|
|
1232
1458
|
// members update listeners
|
|
1233
1459
|
this.setUpLocusFullStateListener();
|
|
1234
1460
|
this.setUpLocusUrlListener();
|
|
@@ -1241,6 +1467,96 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1241
1467
|
this.setUpLocusInfoMeetingInfoListener();
|
|
1242
1468
|
this.setUpLocusInfoAssignHostListener();
|
|
1243
1469
|
this.setUpLocusInfoMediaInactiveListener();
|
|
1470
|
+
this.setUpBreakoutsListener();
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
/**
|
|
1474
|
+
* Set up the listeners for breakouts
|
|
1475
|
+
* @returns {undefined}
|
|
1476
|
+
* @private
|
|
1477
|
+
* @memberof Meeting
|
|
1478
|
+
*/
|
|
1479
|
+
setUpBreakoutsListener() {
|
|
1480
|
+
this.breakouts.on(BREAKOUTS.EVENTS.BREAKOUTS_CLOSING, () => {
|
|
1481
|
+
Trigger.trigger(
|
|
1482
|
+
this,
|
|
1483
|
+
{
|
|
1484
|
+
file: 'meeting/index',
|
|
1485
|
+
function: 'setUpBreakoutsListener',
|
|
1486
|
+
},
|
|
1487
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
|
|
1488
|
+
);
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MESSAGE, (messageEvent) => {
|
|
1492
|
+
Trigger.trigger(
|
|
1493
|
+
this,
|
|
1494
|
+
{
|
|
1495
|
+
file: 'meeting/index',
|
|
1496
|
+
function: 'setUpBreakoutsListener',
|
|
1497
|
+
},
|
|
1498
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
|
|
1499
|
+
messageEvent
|
|
1500
|
+
);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MEMBERS_UPDATE, () => {
|
|
1504
|
+
Trigger.trigger(
|
|
1505
|
+
this,
|
|
1506
|
+
{
|
|
1507
|
+
file: 'meeting/index',
|
|
1508
|
+
function: 'setUpBreakoutsListener',
|
|
1509
|
+
},
|
|
1510
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
1511
|
+
);
|
|
1512
|
+
});
|
|
1513
|
+
|
|
1514
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_RETURN_TO_MAIN, () => {
|
|
1515
|
+
if (this.isJoined()) {
|
|
1516
|
+
Trigger.trigger(
|
|
1517
|
+
this,
|
|
1518
|
+
{
|
|
1519
|
+
file: 'meeting/index',
|
|
1520
|
+
function: 'setUpBreakoutsListener',
|
|
1521
|
+
},
|
|
1522
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_RETURN_TO_MAIN
|
|
1523
|
+
);
|
|
1524
|
+
}
|
|
1525
|
+
});
|
|
1526
|
+
|
|
1527
|
+
this.breakouts.on(BREAKOUTS.EVENTS.LEAVE_BREAKOUT, () => {
|
|
1528
|
+
Trigger.trigger(
|
|
1529
|
+
this,
|
|
1530
|
+
{
|
|
1531
|
+
file: 'meeting/index',
|
|
1532
|
+
function: 'setUpBreakoutsListener',
|
|
1533
|
+
},
|
|
1534
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_LEAVE
|
|
1535
|
+
);
|
|
1536
|
+
});
|
|
1537
|
+
|
|
1538
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_FOR_HELP, (helpEvent) => {
|
|
1539
|
+
Trigger.trigger(
|
|
1540
|
+
this,
|
|
1541
|
+
{
|
|
1542
|
+
file: 'meeting/index',
|
|
1543
|
+
function: 'setUpBreakoutsListener',
|
|
1544
|
+
},
|
|
1545
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_FOR_HELP,
|
|
1546
|
+
helpEvent
|
|
1547
|
+
);
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
this.breakouts.on(BREAKOUTS.EVENTS.PRE_ASSIGNMENTS_UPDATE, () => {
|
|
1551
|
+
Trigger.trigger(
|
|
1552
|
+
this,
|
|
1553
|
+
{
|
|
1554
|
+
file: 'meeting/index',
|
|
1555
|
+
function: 'setUpBreakoutsListener',
|
|
1556
|
+
},
|
|
1557
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE
|
|
1558
|
+
);
|
|
1559
|
+
});
|
|
1244
1560
|
}
|
|
1245
1561
|
|
|
1246
1562
|
/**
|
|
@@ -1356,19 +1672,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1356
1672
|
* @returns {Object}
|
|
1357
1673
|
* @memberof Meeting
|
|
1358
1674
|
*/
|
|
1359
|
-
getAnalyzerMetricsPrePayload(
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1675
|
+
getAnalyzerMetricsPrePayload(options: {
|
|
1676
|
+
type?: string;
|
|
1677
|
+
event: string;
|
|
1678
|
+
trackingId: string;
|
|
1679
|
+
locus: object;
|
|
1680
|
+
mediaConnections?: Array<any>;
|
|
1681
|
+
errors?: object;
|
|
1682
|
+
meetingLookupUrl?: string;
|
|
1683
|
+
clientType?: any;
|
|
1684
|
+
subClientType?: any;
|
|
1685
|
+
[key: string]: any;
|
|
1686
|
+
}) {
|
|
1370
1687
|
if (options) {
|
|
1371
|
-
const {event, trackingId, mediaConnections} = options;
|
|
1688
|
+
const {event, trackingId, mediaConnections, meetingLookupUrl} = options;
|
|
1372
1689
|
|
|
1373
1690
|
if (!event) {
|
|
1374
1691
|
LoggerProxy.logger.error(
|
|
@@ -1407,6 +1724,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1407
1724
|
identifiers.mediaAgentCluster = this.mediaConnections?.[0].mediaAgentCluster;
|
|
1408
1725
|
}
|
|
1409
1726
|
|
|
1727
|
+
if (meetingLookupUrl) {
|
|
1728
|
+
identifiers.meetingLookupUrl = meetingLookupUrl;
|
|
1729
|
+
}
|
|
1730
|
+
|
|
1410
1731
|
if (options.trackingId) {
|
|
1411
1732
|
identifiers.trackingId = trackingId;
|
|
1412
1733
|
}
|
|
@@ -1456,12 +1777,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1456
1777
|
};
|
|
1457
1778
|
}
|
|
1458
1779
|
|
|
1459
|
-
const
|
|
1780
|
+
const callInitJoinReq = this.getCallInitJoinReq();
|
|
1460
1781
|
|
|
1461
|
-
if (
|
|
1782
|
+
if (callInitJoinReq) {
|
|
1462
1783
|
options.joinTimes = {
|
|
1463
1784
|
...options.joinTimes,
|
|
1464
|
-
|
|
1785
|
+
callInitJoinReq,
|
|
1465
1786
|
};
|
|
1466
1787
|
}
|
|
1467
1788
|
|
|
@@ -1474,15 +1795,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1474
1795
|
};
|
|
1475
1796
|
}
|
|
1476
1797
|
|
|
1477
|
-
const
|
|
1798
|
+
const totalJmt = this.getTotalJmt();
|
|
1478
1799
|
|
|
1479
|
-
if (
|
|
1800
|
+
if (totalJmt) {
|
|
1480
1801
|
options.joinTimes = {
|
|
1481
1802
|
...options.joinTimes,
|
|
1482
|
-
|
|
1803
|
+
totalJmt,
|
|
1483
1804
|
};
|
|
1484
1805
|
}
|
|
1485
1806
|
|
|
1807
|
+
const curUserType = this.getCurUserType();
|
|
1808
|
+
|
|
1809
|
+
if (curUserType) {
|
|
1810
|
+
options.userType = curUserType;
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
const curLoginType = this.getCurLoginType();
|
|
1814
|
+
|
|
1815
|
+
if (curLoginType) {
|
|
1816
|
+
options.loginType = curLoginType;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
if (this.environment) {
|
|
1820
|
+
options.environment = this.environment;
|
|
1821
|
+
}
|
|
1822
|
+
|
|
1486
1823
|
if (options.type === MQA_STATS.CA_TYPE) {
|
|
1487
1824
|
payload = Metrics.initMediaPayload(options.event, identifiers, options);
|
|
1488
1825
|
} else {
|
|
@@ -1596,11 +1933,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1596
1933
|
this.pstnUpdate(payload);
|
|
1597
1934
|
|
|
1598
1935
|
// If user moved to a JOINED state and there is a pending floor grant trigger it
|
|
1599
|
-
|
|
1600
|
-
this.requestScreenShareFloor().then(() => {
|
|
1601
|
-
this.floorGrantPending = false;
|
|
1602
|
-
});
|
|
1603
|
-
}
|
|
1936
|
+
this.requestScreenShareFloorIfPending();
|
|
1604
1937
|
});
|
|
1605
1938
|
}
|
|
1606
1939
|
|
|
@@ -1786,6 +2119,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1786
2119
|
}
|
|
1787
2120
|
);
|
|
1788
2121
|
|
|
2122
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED, ({breakout}) => {
|
|
2123
|
+
this.breakouts.updateBreakout(breakout);
|
|
2124
|
+
Trigger.trigger(
|
|
2125
|
+
this,
|
|
2126
|
+
{
|
|
2127
|
+
file: 'meeting/index',
|
|
2128
|
+
function: 'setupLocusControlsListener',
|
|
2129
|
+
},
|
|
2130
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2131
|
+
);
|
|
2132
|
+
});
|
|
2133
|
+
|
|
2134
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
|
|
2135
|
+
this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
|
|
2136
|
+
// clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
|
|
2137
|
+
// which means main session is not active for the attendee
|
|
2138
|
+
if (error?.statusCode === 403) {
|
|
2139
|
+
this.locusInfo.clearMainSessionLocusCache();
|
|
2140
|
+
}
|
|
2141
|
+
});
|
|
2142
|
+
});
|
|
2143
|
+
|
|
1789
2144
|
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
|
|
1790
2145
|
Trigger.trigger(
|
|
1791
2146
|
this,
|
|
@@ -1797,23 +2152,115 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1797
2152
|
{entryExitTone}
|
|
1798
2153
|
);
|
|
1799
2154
|
});
|
|
1800
|
-
}
|
|
1801
2155
|
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
2156
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED, ({state}) => {
|
|
2157
|
+
Trigger.trigger(
|
|
2158
|
+
this,
|
|
2159
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2160
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_MUTE_ON_ENTRY_UPDATED,
|
|
2161
|
+
{state}
|
|
2162
|
+
);
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED, ({state}) => {
|
|
2166
|
+
Trigger.trigger(
|
|
2167
|
+
this,
|
|
2168
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2169
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_SHARE_CONTROL_UPDATED,
|
|
2170
|
+
{state}
|
|
2171
|
+
);
|
|
2172
|
+
});
|
|
2173
|
+
|
|
2174
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED, ({state}) => {
|
|
2175
|
+
Trigger.trigger(
|
|
2176
|
+
this,
|
|
2177
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2178
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED,
|
|
2179
|
+
{state}
|
|
2180
|
+
);
|
|
2181
|
+
});
|
|
2182
|
+
|
|
2183
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED, ({state}) => {
|
|
2184
|
+
Trigger.trigger(
|
|
2185
|
+
this,
|
|
2186
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2187
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REACTIONS_UPDATED,
|
|
2188
|
+
{state}
|
|
2189
|
+
);
|
|
2190
|
+
});
|
|
2191
|
+
|
|
2192
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED, ({state}) => {
|
|
2193
|
+
Trigger.trigger(
|
|
2194
|
+
this,
|
|
2195
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2196
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED,
|
|
2197
|
+
{state}
|
|
2198
|
+
);
|
|
2199
|
+
});
|
|
2200
|
+
|
|
2201
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED, ({state}) => {
|
|
2202
|
+
Trigger.trigger(
|
|
2203
|
+
this,
|
|
2204
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2205
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_RAISE_HAND_UPDATED,
|
|
2206
|
+
{state}
|
|
2207
|
+
);
|
|
2208
|
+
});
|
|
2209
|
+
|
|
2210
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED, ({state}) => {
|
|
2211
|
+
Trigger.trigger(
|
|
2212
|
+
this,
|
|
2213
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2214
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIDEO_UPDATED,
|
|
2215
|
+
{state}
|
|
2216
|
+
);
|
|
2217
|
+
});
|
|
2218
|
+
}
|
|
2219
|
+
|
|
2220
|
+
/**
|
|
2221
|
+
* Trigger annotation info update event
|
|
2222
|
+
@returns {undefined}
|
|
2223
|
+
@param {object} contentShare
|
|
2224
|
+
@param {object} previousContentShare
|
|
2225
|
+
*/
|
|
2226
|
+
private triggerAnnotationInfoEvent(contentShare, previousContentShare) {
|
|
2227
|
+
if (
|
|
2228
|
+
contentShare?.annotation &&
|
|
2229
|
+
!isEqual(contentShare?.annotation, previousContentShare?.annotation)
|
|
2230
|
+
) {
|
|
2231
|
+
Trigger.trigger(
|
|
2232
|
+
// @ts-ignore
|
|
2233
|
+
this.webex.meetings,
|
|
2234
|
+
{
|
|
2235
|
+
file: 'meeting/index',
|
|
2236
|
+
function: 'triggerAnnotationInfoEvent',
|
|
2237
|
+
},
|
|
2238
|
+
EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
|
|
2239
|
+
{
|
|
2240
|
+
annotationInfo: contentShare?.annotation,
|
|
2241
|
+
meetingId: this.id,
|
|
2242
|
+
}
|
|
2243
|
+
);
|
|
2244
|
+
}
|
|
2245
|
+
}
|
|
2246
|
+
|
|
2247
|
+
/**
|
|
2248
|
+
* Set up the locus info media shares listener
|
|
2249
|
+
* update content and whiteboard sharing id value for members, and updates the member
|
|
2250
|
+
* notifies consumer with members:content:update {activeContentSharingId, endedContentSharingId}
|
|
1806
2251
|
* @returns {undefined}
|
|
1807
2252
|
* @private
|
|
1808
2253
|
* @memberof Meeting
|
|
1809
2254
|
*/
|
|
1810
2255
|
private setUpLocusMediaSharesListener() {
|
|
1811
2256
|
// Will get triggered on local and remote share
|
|
1812
|
-
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
|
|
2257
|
+
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, async (payload) => {
|
|
1813
2258
|
const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
|
|
1814
2259
|
const previousContentShare = payload.previous?.content;
|
|
1815
2260
|
const previousWhiteboardShare = payload.previous?.whiteboard;
|
|
1816
2261
|
|
|
2262
|
+
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2263
|
+
|
|
1817
2264
|
if (
|
|
1818
2265
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
1819
2266
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
@@ -1841,19 +2288,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1841
2288
|
this.selfId === contentShare.beneficiaryId &&
|
|
1842
2289
|
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
1843
2290
|
) {
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
skipSignalingCheck: true,
|
|
1847
|
-
}).catch((error) => {
|
|
1848
|
-
LoggerProxy.logger.log(
|
|
1849
|
-
'Meeting:index#setUpLocusMediaSharesListener --> Error stopping share: ',
|
|
1850
|
-
error
|
|
1851
|
-
);
|
|
1852
|
-
});
|
|
1853
|
-
} else {
|
|
1854
|
-
// CONTENT - sharing content local
|
|
1855
|
-
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
1856
|
-
}
|
|
2291
|
+
// CONTENT - sharing content local
|
|
2292
|
+
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
1857
2293
|
}
|
|
1858
2294
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
1859
2295
|
// There is no concept of local/remote share for whiteboard
|
|
@@ -1937,23 +2373,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1937
2373
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
1938
2374
|
{
|
|
1939
2375
|
memberId: contentShare.beneficiaryId,
|
|
2376
|
+
url: contentShare.url,
|
|
2377
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
1940
2378
|
}
|
|
1941
2379
|
);
|
|
1942
2380
|
};
|
|
1943
2381
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2382
|
+
try {
|
|
2383
|
+
// if a remote participant is stealing the presentation from us
|
|
2384
|
+
if (
|
|
2385
|
+
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2386
|
+
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2387
|
+
) {
|
|
2388
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2389
|
+
}
|
|
2390
|
+
} finally {
|
|
1949
2391
|
sendStartedSharingRemote();
|
|
1950
|
-
} else {
|
|
1951
|
-
this.updateShare({
|
|
1952
|
-
sendShare: false,
|
|
1953
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
1954
|
-
}).finally(() => {
|
|
1955
|
-
sendStartedSharingRemote();
|
|
1956
|
-
});
|
|
1957
2392
|
}
|
|
1958
2393
|
break;
|
|
1959
2394
|
}
|
|
@@ -2007,6 +2442,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2007
2442
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
2008
2443
|
{
|
|
2009
2444
|
memberId: contentShare.beneficiaryId,
|
|
2445
|
+
url: contentShare.url,
|
|
2446
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
2010
2447
|
}
|
|
2011
2448
|
);
|
|
2012
2449
|
this.members.locusMediaSharesUpdate(payload);
|
|
@@ -2041,8 +2478,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2041
2478
|
private setUpLocusUrlListener() {
|
|
2042
2479
|
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
|
|
2043
2480
|
this.members.locusUrlUpdate(payload);
|
|
2481
|
+
this.breakouts.locusUrlUpdate(payload);
|
|
2482
|
+
this.annotation.locusUrlUpdate(payload);
|
|
2044
2483
|
this.locusUrl = payload;
|
|
2045
2484
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2485
|
+
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2486
|
+
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2487
|
+
});
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
/**
|
|
2491
|
+
* Set up the locus info service link listener
|
|
2492
|
+
* update the locusInfo for recording controller
|
|
2493
|
+
* does not currently re-emit the event as it's internal only
|
|
2494
|
+
* payload is unused
|
|
2495
|
+
* @returns {undefined}
|
|
2496
|
+
* @private
|
|
2497
|
+
* @memberof Meeting
|
|
2498
|
+
*/
|
|
2499
|
+
private setUpLocusServicesListener() {
|
|
2500
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
|
|
2501
|
+
this.recordingController.setServiceUrl(payload?.services?.record?.url);
|
|
2502
|
+
this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
|
|
2503
|
+
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2504
|
+
this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2046
2505
|
});
|
|
2047
2506
|
}
|
|
2048
2507
|
|
|
@@ -2092,10 +2551,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2092
2551
|
canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
|
|
2093
2552
|
canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
|
|
2094
2553
|
canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2554
|
+
canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(
|
|
2555
|
+
payload.info.userDisplayHints
|
|
2556
|
+
),
|
|
2557
|
+
canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(
|
|
2558
|
+
payload.info.userDisplayHints
|
|
2559
|
+
),
|
|
2560
|
+
canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(payload.info.userDisplayHints),
|
|
2561
|
+
canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
|
|
2562
|
+
payload.info.userDisplayHints
|
|
2563
|
+
),
|
|
2564
|
+
canSetMuted: ControlsOptionsUtil.canSetMuted(payload.info.userDisplayHints),
|
|
2565
|
+
canUnsetMuted: ControlsOptionsUtil.canUnsetMuted(payload.info.userDisplayHints),
|
|
2566
|
+
canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
|
|
2567
|
+
canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
|
|
2568
|
+
canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
|
|
2569
|
+
canResumeRecording: RecordingUtil.canUserResume(payload.info.userDisplayHints),
|
|
2099
2570
|
canRaiseHand: MeetingUtil.canUserRaiseHand(payload.info.userDisplayHints),
|
|
2100
2571
|
canLowerAllHands: MeetingUtil.canUserLowerAllHands(payload.info.userDisplayHints),
|
|
2101
2572
|
canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(
|
|
@@ -2108,6 +2579,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2108
2579
|
canStartTranscribing: MeetingUtil.canStartTranscribing(payload.info.userDisplayHints),
|
|
2109
2580
|
canStopTranscribing: MeetingUtil.canStopTranscribing(payload.info.userDisplayHints),
|
|
2110
2581
|
isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(payload.info.userDisplayHints),
|
|
2582
|
+
isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(
|
|
2583
|
+
payload.info.userDisplayHints
|
|
2584
|
+
),
|
|
2111
2585
|
isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(payload.info.userDisplayHints),
|
|
2112
2586
|
canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(payload.info.userDisplayHints),
|
|
2113
2587
|
isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
|
|
@@ -2117,8 +2591,118 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2117
2591
|
payload.info.userDisplayHints
|
|
2118
2592
|
),
|
|
2119
2593
|
waitingForOthersToJoin: MeetingUtil.waitingForOthersToJoin(payload.info.userDisplayHints),
|
|
2594
|
+
canSendReactions: MeetingUtil.canSendReactions(
|
|
2595
|
+
this.inMeetingActions.canSendReactions,
|
|
2596
|
+
payload.info.userDisplayHints
|
|
2597
|
+
),
|
|
2598
|
+
canManageBreakout: MeetingUtil.canManageBreakout(payload.info.userDisplayHints),
|
|
2599
|
+
canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
|
|
2600
|
+
payload.info.userDisplayHints
|
|
2601
|
+
),
|
|
2602
|
+
canAdmitLobbyToBreakout: MeetingUtil.canAdmitLobbyToBreakout(
|
|
2603
|
+
payload.info.userDisplayHints
|
|
2604
|
+
),
|
|
2605
|
+
isBreakoutPreassignmentsEnabled: MeetingUtil.isBreakoutPreassignmentsEnabled(
|
|
2606
|
+
payload.info.userDisplayHints
|
|
2607
|
+
),
|
|
2608
|
+
canUserAskForHelp: MeetingUtil.canUserAskForHelp(payload.info.userDisplayHints),
|
|
2609
|
+
canUserRenameSelfAndObserved: MeetingUtil.canUserRenameSelfAndObserved(
|
|
2610
|
+
payload.info.userDisplayHints
|
|
2611
|
+
),
|
|
2612
|
+
canUserRenameOthers: MeetingUtil.canUserRenameOthers(payload.info.userDisplayHints),
|
|
2613
|
+
canMuteAll: ControlsOptionsUtil.hasHints({
|
|
2614
|
+
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
2615
|
+
displayHints: payload.info.userDisplayHints,
|
|
2616
|
+
}),
|
|
2617
|
+
canUnmuteAll: ControlsOptionsUtil.hasHints({
|
|
2618
|
+
requiredHints: [DISPLAY_HINTS.UNMUTE_ALL],
|
|
2619
|
+
displayHints: payload.info.userDisplayHints,
|
|
2620
|
+
}),
|
|
2621
|
+
canEnableHardMute: ControlsOptionsUtil.hasHints({
|
|
2622
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_HARD_MUTE],
|
|
2623
|
+
displayHints: payload.info.userDisplayHints,
|
|
2624
|
+
}),
|
|
2625
|
+
canDisableHardMute: ControlsOptionsUtil.hasHints({
|
|
2626
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_HARD_MUTE],
|
|
2627
|
+
displayHints: payload.info.userDisplayHints,
|
|
2628
|
+
}),
|
|
2629
|
+
canEnableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2630
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY],
|
|
2631
|
+
displayHints: payload.info.userDisplayHints,
|
|
2632
|
+
}),
|
|
2633
|
+
canDisableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2634
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY],
|
|
2635
|
+
displayHints: payload.info.userDisplayHints,
|
|
2636
|
+
}),
|
|
2637
|
+
canEnableReactions: ControlsOptionsUtil.hasHints({
|
|
2638
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_REACTIONS],
|
|
2639
|
+
displayHints: payload.info.userDisplayHints,
|
|
2640
|
+
}),
|
|
2641
|
+
canDisableReactions: ControlsOptionsUtil.hasHints({
|
|
2642
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_REACTIONS],
|
|
2643
|
+
displayHints: payload.info.userDisplayHints,
|
|
2644
|
+
}),
|
|
2645
|
+
canEnableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2646
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_DISPLAY_NAME],
|
|
2647
|
+
displayHints: payload.info.userDisplayHints,
|
|
2648
|
+
}),
|
|
2649
|
+
canDisableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2650
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_DISPLAY_NAME],
|
|
2651
|
+
displayHints: payload.info.userDisplayHints,
|
|
2652
|
+
}),
|
|
2653
|
+
canUpdateShareControl: ControlsOptionsUtil.hasHints({
|
|
2654
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTROL],
|
|
2655
|
+
displayHints: payload.info.userDisplayHints,
|
|
2656
|
+
}),
|
|
2657
|
+
canEnableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2658
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2659
|
+
displayHints: payload.info.userDisplayHints,
|
|
2660
|
+
}),
|
|
2661
|
+
canDisableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2662
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2663
|
+
displayHints: payload.info.userDisplayHints,
|
|
2664
|
+
}),
|
|
2665
|
+
canEnableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2666
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_RAISE_HAND],
|
|
2667
|
+
displayHints: payload.info.userDisplayHints,
|
|
2668
|
+
}),
|
|
2669
|
+
canDisableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2670
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_RAISE_HAND],
|
|
2671
|
+
displayHints: payload.info.userDisplayHints,
|
|
2672
|
+
}),
|
|
2673
|
+
canEnableVideo: ControlsOptionsUtil.hasHints({
|
|
2674
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIDEO],
|
|
2675
|
+
displayHints: payload.info.userDisplayHints,
|
|
2676
|
+
}),
|
|
2677
|
+
canDisableVideo: ControlsOptionsUtil.hasHints({
|
|
2678
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIDEO],
|
|
2679
|
+
displayHints: payload.info.userDisplayHints,
|
|
2680
|
+
}),
|
|
2681
|
+
canShareFile: ControlsOptionsUtil.hasHints({
|
|
2682
|
+
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
|
|
2683
|
+
displayHints: payload.info.userDisplayHints,
|
|
2684
|
+
}),
|
|
2685
|
+
canShareApplication: ControlsOptionsUtil.hasHints({
|
|
2686
|
+
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
2687
|
+
displayHints: payload.info.userDisplayHints,
|
|
2688
|
+
}),
|
|
2689
|
+
canShareCamera: ControlsOptionsUtil.hasHints({
|
|
2690
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CAMERA],
|
|
2691
|
+
displayHints: payload.info.userDisplayHints,
|
|
2692
|
+
}),
|
|
2693
|
+
canShareDesktop: ControlsOptionsUtil.hasHints({
|
|
2694
|
+
requiredHints: [DISPLAY_HINTS.SHARE_DESKTOP],
|
|
2695
|
+
displayHints: payload.info.userDisplayHints,
|
|
2696
|
+
}),
|
|
2697
|
+
canShareContent: ControlsOptionsUtil.hasHints({
|
|
2698
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTENT],
|
|
2699
|
+
displayHints: payload.info.userDisplayHints,
|
|
2700
|
+
}),
|
|
2120
2701
|
});
|
|
2121
2702
|
|
|
2703
|
+
this.recordingController.setDisplayHints(payload.info.userDisplayHints);
|
|
2704
|
+
this.controlsOptionsManager.setDisplayHints(payload.info.userDisplayHints);
|
|
2705
|
+
|
|
2122
2706
|
if (changed) {
|
|
2123
2707
|
Trigger.trigger(
|
|
2124
2708
|
this,
|
|
@@ -2141,7 +2725,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2141
2725
|
* @param {String} datachannelUrl
|
|
2142
2726
|
* @returns {void}
|
|
2143
2727
|
*/
|
|
2728
|
+
|
|
2144
2729
|
handleDataChannelUrlChange(datachannelUrl) {
|
|
2730
|
+
// @ts-ignore - config coming from registerPlugin
|
|
2145
2731
|
if (datachannelUrl && this.config.enableAutomaticLLM) {
|
|
2146
2732
|
// Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
|
|
2147
2733
|
// after the MEETING_INFO_UPDATED callback finishes
|
|
@@ -2196,10 +2782,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2196
2782
|
);
|
|
2197
2783
|
}
|
|
2198
2784
|
});
|
|
2785
|
+
|
|
2786
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED, (payload) => {
|
|
2787
|
+
if (payload) {
|
|
2788
|
+
if (this.video) {
|
|
2789
|
+
payload.muted = payload.muted ?? this.video.isRemotelyMuted();
|
|
2790
|
+
payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
|
|
2791
|
+
this.video.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2792
|
+
}
|
|
2793
|
+
Trigger.trigger(
|
|
2794
|
+
this,
|
|
2795
|
+
{
|
|
2796
|
+
file: 'meeting/index',
|
|
2797
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2798
|
+
},
|
|
2799
|
+
payload.muted
|
|
2800
|
+
? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
|
|
2801
|
+
: EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
|
|
2802
|
+
{
|
|
2803
|
+
payload,
|
|
2804
|
+
}
|
|
2805
|
+
);
|
|
2806
|
+
}
|
|
2807
|
+
});
|
|
2808
|
+
|
|
2199
2809
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
|
|
2200
2810
|
if (payload) {
|
|
2201
2811
|
if (this.audio) {
|
|
2202
|
-
this.audio.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
|
|
2812
|
+
this.audio.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2203
2813
|
}
|
|
2204
2814
|
// with "mute on entry" server will send us remote mute even if we don't have media configured,
|
|
2205
2815
|
// so if being muted by others, always send the notification,
|
|
@@ -2322,6 +2932,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2322
2932
|
);
|
|
2323
2933
|
});
|
|
2324
2934
|
|
|
2935
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED, (payload) => {
|
|
2936
|
+
this.breakouts.updateBreakoutSessions(payload);
|
|
2937
|
+
Trigger.trigger(
|
|
2938
|
+
this,
|
|
2939
|
+
{
|
|
2940
|
+
file: 'meeting/index',
|
|
2941
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2942
|
+
},
|
|
2943
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2944
|
+
);
|
|
2945
|
+
});
|
|
2946
|
+
|
|
2947
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
|
|
2948
|
+
const isModeratorOrCohost =
|
|
2949
|
+
payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
|
|
2950
|
+
payload.newRoles?.includes(SELF_ROLES.COHOST);
|
|
2951
|
+
this.breakouts.updateCanManageBreakouts(isModeratorOrCohost);
|
|
2952
|
+
|
|
2953
|
+
Trigger.trigger(
|
|
2954
|
+
this,
|
|
2955
|
+
{
|
|
2956
|
+
file: 'meeting/index',
|
|
2957
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2958
|
+
},
|
|
2959
|
+
EVENT_TRIGGERS.MEETING_SELF_ROLES_CHANGED,
|
|
2960
|
+
{
|
|
2961
|
+
payload,
|
|
2962
|
+
}
|
|
2963
|
+
);
|
|
2964
|
+
});
|
|
2965
|
+
|
|
2325
2966
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_IS_SHARING_BLOCKED_CHANGE, (payload) => {
|
|
2326
2967
|
Trigger.trigger(
|
|
2327
2968
|
this,
|
|
@@ -2357,19 +2998,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2357
2998
|
.catch((error) => {
|
|
2358
2999
|
// @ts-ignore
|
|
2359
3000
|
LoggerProxy.logger.error(
|
|
2360
|
-
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this
|
|
3001
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
2361
3002
|
);
|
|
2362
3003
|
});
|
|
2363
3004
|
}
|
|
2364
3005
|
});
|
|
2365
|
-
this.locusInfo.on(EVENTS.DESTROY_MEETING, (payload) => {
|
|
3006
|
+
this.locusInfo.on(EVENTS.DESTROY_MEETING, async (payload) => {
|
|
2366
3007
|
// if self state is NOT left
|
|
2367
3008
|
|
|
2368
3009
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
2369
3010
|
if (this.wirelessShare) {
|
|
2370
3011
|
if (this.mediaProperties.shareTrack) {
|
|
2371
|
-
this.
|
|
2372
|
-
this.mediaProperties.shareTrack.stop();
|
|
3012
|
+
await this.setLocalShareTrack(undefined);
|
|
2373
3013
|
}
|
|
2374
3014
|
}
|
|
2375
3015
|
// when multiple WEB deviceType join with same user
|
|
@@ -2383,18 +3023,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2383
3023
|
if (payload.shouldLeave) {
|
|
2384
3024
|
// TODO: We should do cleaning of meeting object if the shouldLeave: false because there might be meeting object which we are not cleaning
|
|
2385
3025
|
|
|
2386
|
-
|
|
2387
|
-
.
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
3026
|
+
try {
|
|
3027
|
+
await this.leave({reason: payload.reason});
|
|
3028
|
+
|
|
3029
|
+
LoggerProxy.logger.warn(
|
|
3030
|
+
'Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. The meeting has been left, but has not been destroyed, you should see a later event for leave.'
|
|
3031
|
+
);
|
|
3032
|
+
} catch (error) {
|
|
3033
|
+
// @ts-ignore
|
|
3034
|
+
LoggerProxy.logger.error(
|
|
3035
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
2398
3038
|
} else {
|
|
2399
3039
|
LoggerProxy.logger.info(
|
|
2400
3040
|
'Meeting:index#setUpLocusInfoMeetingListener --> MEETING_REMOVED_REASON',
|
|
@@ -2472,14 +3112,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2472
3112
|
}
|
|
2473
3113
|
|
|
2474
3114
|
/**
|
|
2475
|
-
* Admit the guest(s) to the call once they are waiting
|
|
3115
|
+
* Admit the guest(s) to the call once they are waiting.
|
|
3116
|
+
* If the host/cohost is in a breakout session, the locus url
|
|
3117
|
+
* of the session must be provided as the authorizingLocusUrl.
|
|
3118
|
+
* Regardless of host/cohost location, the locus Id (lid) in
|
|
3119
|
+
* the path should be the locus Id of the main, which means the
|
|
3120
|
+
* locus url of the api call must be from the main session.
|
|
3121
|
+
* If these loucs urls are not provided, the function will do the check.
|
|
2476
3122
|
* @param {Array} memberIds
|
|
3123
|
+
* @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
|
|
2477
3124
|
* @returns {Promise} see #members.admitMembers
|
|
2478
3125
|
* @public
|
|
2479
3126
|
* @memberof Meeting
|
|
2480
3127
|
*/
|
|
2481
|
-
public admit(
|
|
2482
|
-
|
|
3128
|
+
public admit(
|
|
3129
|
+
memberIds: Array<any>,
|
|
3130
|
+
sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
|
|
3131
|
+
) {
|
|
3132
|
+
let locusUrls = sessionLocusUrls;
|
|
3133
|
+
if (!locusUrls) {
|
|
3134
|
+
const {locusUrl, mainLocusUrl} = this.breakouts;
|
|
3135
|
+
if (locusUrl && mainLocusUrl) {
|
|
3136
|
+
locusUrls = {authorizingLocusUrl: locusUrl, mainLocusUrl};
|
|
3137
|
+
}
|
|
3138
|
+
}
|
|
3139
|
+
|
|
3140
|
+
return this.members.admitMembers(memberIds, locusUrls);
|
|
2483
3141
|
}
|
|
2484
3142
|
|
|
2485
3143
|
/**
|
|
@@ -2527,66 +3185,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2527
3185
|
return this.members;
|
|
2528
3186
|
}
|
|
2529
3187
|
|
|
2530
|
-
/**
|
|
2531
|
-
* Truthy when a meeting has an audio connection established
|
|
2532
|
-
* @returns {Boolean} true if meeting audio is connected otherwise false
|
|
2533
|
-
* @public
|
|
2534
|
-
* @memberof Meeting
|
|
2535
|
-
*/
|
|
2536
|
-
public isAudioConnected() {
|
|
2537
|
-
return !!this.audio;
|
|
2538
|
-
}
|
|
2539
|
-
|
|
2540
|
-
/**
|
|
2541
|
-
* Convenience function to tell whether a meeting is muted
|
|
2542
|
-
* @returns {Boolean} if meeting audio muted or not
|
|
2543
|
-
* @public
|
|
2544
|
-
* @memberof Meeting
|
|
2545
|
-
*/
|
|
2546
|
-
public isAudioMuted() {
|
|
2547
|
-
return this.audio && this.audio.isMuted();
|
|
2548
|
-
}
|
|
2549
|
-
|
|
2550
|
-
/**
|
|
2551
|
-
* Convenience function to tell if the end user last changed the audio state
|
|
2552
|
-
* @returns {Boolean} if audio was manipulated by the end user
|
|
2553
|
-
* @public
|
|
2554
|
-
* @memberof Meeting
|
|
2555
|
-
*/
|
|
2556
|
-
public isAudioSelf() {
|
|
2557
|
-
return this.audio && this.audio.isSelf();
|
|
2558
|
-
}
|
|
2559
|
-
|
|
2560
|
-
/**
|
|
2561
|
-
* Truthy when a meeting has a video connection established
|
|
2562
|
-
* @returns {Boolean} true if meeting video connected otherwise false
|
|
2563
|
-
* @public
|
|
2564
|
-
* @memberof Meeting
|
|
2565
|
-
*/
|
|
2566
|
-
public isVideoConnected() {
|
|
2567
|
-
return !!this.video;
|
|
2568
|
-
}
|
|
2569
|
-
|
|
2570
|
-
/**
|
|
2571
|
-
* Convenience function to tell whether video is muted
|
|
2572
|
-
* @returns {Boolean} if meeting video is muted or not
|
|
2573
|
-
* @public
|
|
2574
|
-
* @memberof Meeting
|
|
2575
|
-
*/
|
|
2576
|
-
public isVideoMuted() {
|
|
2577
|
-
return this.video && this.video.isMuted();
|
|
2578
|
-
}
|
|
2579
|
-
|
|
2580
|
-
/**
|
|
2581
|
-
* Convenience function to tell whether the end user changed the video state
|
|
2582
|
-
* @returns {Boolean} if meeting video is muted or not
|
|
2583
|
-
* @public
|
|
2584
|
-
* @memberof Meeting
|
|
2585
|
-
*/
|
|
2586
|
-
public isVideoSelf() {
|
|
2587
|
-
return this.video && this.video.isSelf();
|
|
2588
|
-
}
|
|
2589
|
-
|
|
2590
3188
|
/**
|
|
2591
3189
|
* Sets the meeting info on the class instance
|
|
2592
3190
|
* @param {Object} meetingInfo
|
|
@@ -2634,6 +3232,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2634
3232
|
this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
|
|
2635
3233
|
// @ts-ignore - config coming from registerPlugin
|
|
2636
3234
|
this.setSipUri(
|
|
3235
|
+
// @ts-ignore
|
|
2637
3236
|
this.config.experimental.enableUnifiedMeetings
|
|
2638
3237
|
? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
|
|
2639
3238
|
: locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
|
|
@@ -2650,35 +3249,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2650
3249
|
webexMeetingInfo?.hostId ||
|
|
2651
3250
|
this.owner;
|
|
2652
3251
|
this.permissionToken = webexMeetingInfo?.permissionToken;
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
/**
|
|
2657
|
-
* Sets the first locus info on the class instance
|
|
2658
|
-
* @param {Object} locus
|
|
2659
|
-
* @param {String} locus.url
|
|
2660
|
-
* @param {Array} locus.participants
|
|
2661
|
-
* @param {Object} locus.self
|
|
2662
|
-
* @returns {undefined}
|
|
2663
|
-
* @private
|
|
2664
|
-
* @memberof Meeting
|
|
2665
|
-
*/
|
|
2666
|
-
private parseLocus(locus: {url: string; participants: Array<any>; self: object}) {
|
|
2667
|
-
if (locus) {
|
|
2668
|
-
this.locusUrl = locus.url;
|
|
2669
|
-
// TODO: move this to parse participants module
|
|
2670
|
-
this.setLocus(locus);
|
|
2671
|
-
|
|
2672
|
-
// check if we can extract this info from partner
|
|
2673
|
-
// Parsing of locus object must be finished at this state
|
|
2674
|
-
if (locus.participants && locus.self) {
|
|
2675
|
-
this.partner = MeetingUtil.getLocusPartner(locus.participants, locus.self);
|
|
2676
|
-
}
|
|
2677
|
-
|
|
2678
|
-
// For webex meeting the sipUrl gets updated in info parser
|
|
2679
|
-
if (!this.sipUri && this.partner && this.type === _CALL_) {
|
|
2680
|
-
this.setSipUri(this.partner.person.sipUrl || this.partner.person.id);
|
|
2681
|
-
}
|
|
3252
|
+
// Need to populate environment when sending CA event
|
|
3253
|
+
this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
|
|
2682
3254
|
}
|
|
2683
3255
|
}
|
|
2684
3256
|
|
|
@@ -2708,7 +3280,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2708
3280
|
* @private
|
|
2709
3281
|
* @memberof Meeting
|
|
2710
3282
|
*/
|
|
2711
|
-
|
|
3283
|
+
setLocus(
|
|
2712
3284
|
locus:
|
|
2713
3285
|
| {
|
|
2714
3286
|
mediaConnections: Array<any>;
|
|
@@ -2743,21 +3315,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2743
3315
|
Trigger.trigger(this, options, EVENTS.REQUEST_UPLOAD_LOGS, this);
|
|
2744
3316
|
}
|
|
2745
3317
|
|
|
2746
|
-
/**
|
|
2747
|
-
* Removes remote audio and video stream on the class instance and triggers an event
|
|
2748
|
-
* to developers
|
|
2749
|
-
* @returns {undefined}
|
|
2750
|
-
* @public
|
|
2751
|
-
* @memberof Meeting
|
|
2752
|
-
* @deprecated after v1.89.3
|
|
2753
|
-
*/
|
|
2754
|
-
public unsetRemoteStream() {
|
|
2755
|
-
LoggerProxy.logger.warn(
|
|
2756
|
-
'Meeting:index#unsetRemoteStream --> [DEPRECATION WARNING]: unsetRemoteStream has been deprecated after v1.89.3'
|
|
2757
|
-
);
|
|
2758
|
-
this.mediaProperties.unsetRemoteMedia();
|
|
2759
|
-
}
|
|
2760
|
-
|
|
2761
3318
|
/**
|
|
2762
3319
|
* Removes remote audio, video and share tracks from class instance's mediaProperties
|
|
2763
3320
|
* @returns {undefined}
|
|
@@ -2842,257 +3399,124 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2842
3399
|
}
|
|
2843
3400
|
|
|
2844
3401
|
/**
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2848
|
-
* @
|
|
3402
|
+
* Stores the reference to a new microphone track, sets up the required event listeners
|
|
3403
|
+
* on it, cleans up previous track, etc.
|
|
3404
|
+
*
|
|
3405
|
+
* @param {LocalMicrophoneTrack | null} localTrack local microphone track
|
|
3406
|
+
* @returns {Promise<void>}
|
|
2849
3407
|
*/
|
|
2850
|
-
private
|
|
2851
|
-
|
|
2852
|
-
this,
|
|
2853
|
-
{
|
|
2854
|
-
file: 'meeting/index',
|
|
2855
|
-
function: 'setLocalTracks',
|
|
2856
|
-
},
|
|
2857
|
-
EVENT_TRIGGERS.MEDIA_READY,
|
|
2858
|
-
{
|
|
2859
|
-
type: EVENT_TYPES.LOCAL,
|
|
2860
|
-
stream: MediaUtil.createMediaStream([
|
|
2861
|
-
this.mediaProperties.audioTrack,
|
|
2862
|
-
this.mediaProperties.videoTrack,
|
|
2863
|
-
]),
|
|
2864
|
-
}
|
|
2865
|
-
);
|
|
2866
|
-
}
|
|
3408
|
+
private async setLocalAudioTrack(localTrack?: LocalMicrophoneTrack) {
|
|
3409
|
+
const oldTrack = this.mediaProperties.audioTrack;
|
|
2867
3410
|
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
* @param {MediaStreamTrack} audioTrack
|
|
2871
|
-
* @param {Boolean} emitEvent if true, a media ready event is emitted to the developer
|
|
2872
|
-
* @returns {undefined}
|
|
2873
|
-
* @private
|
|
2874
|
-
* @memberof Meeting
|
|
2875
|
-
*/
|
|
2876
|
-
private setLocalAudioTrack(audioTrack: MediaStreamTrack, emitEvent = true) {
|
|
2877
|
-
if (audioTrack) {
|
|
2878
|
-
const settings = audioTrack.getSettings();
|
|
3411
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3412
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2879
3413
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
noiseSuppression: settings.noiseSuppression,
|
|
2883
|
-
});
|
|
3414
|
+
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3415
|
+
this.mediaProperties.setLocalAudioTrack(localTrack);
|
|
2884
3416
|
|
|
2885
|
-
|
|
2886
|
-
'Meeting:index#setLocalAudioTrack --> Audio settings.',
|
|
2887
|
-
JSON.stringify(this.mediaProperties.mediaSettings.audio)
|
|
2888
|
-
);
|
|
2889
|
-
this.mediaProperties.setLocalAudioTrack(audioTrack);
|
|
2890
|
-
if (this.audio) this.audio.applyClientStateLocally(this);
|
|
2891
|
-
}
|
|
3417
|
+
this.audio.handleLocalTrackChange(this);
|
|
2892
3418
|
|
|
2893
|
-
|
|
2894
|
-
|
|
3419
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3420
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3421
|
+
|
|
3422
|
+
if (!this.isMultistream || !localTrack) {
|
|
3423
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3424
|
+
await this.unpublishTrack(oldTrack);
|
|
2895
3425
|
}
|
|
3426
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
2896
3427
|
}
|
|
2897
3428
|
|
|
2898
3429
|
/**
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
2902
|
-
* @
|
|
2903
|
-
* @
|
|
2904
|
-
* @memberof Meeting
|
|
3430
|
+
* Stores the reference to a new camera track, sets up the required event listeners
|
|
3431
|
+
* on it, cleans up previous track, etc.
|
|
3432
|
+
*
|
|
3433
|
+
* @param {LocalCameraTrack | null} localTrack local camera track
|
|
3434
|
+
* @returns {Promise<void>}
|
|
2905
3435
|
*/
|
|
2906
|
-
private setLocalVideoTrack(
|
|
2907
|
-
|
|
2908
|
-
const {aspectRatio, frameRate, height, width, deviceId} = videoTrack.getSettings();
|
|
2909
|
-
|
|
2910
|
-
const {localQualityLevel} = this.mediaProperties;
|
|
3436
|
+
private async setLocalVideoTrack(localTrack?: LocalCameraTrack) {
|
|
3437
|
+
const oldTrack = this.mediaProperties.videoTrack;
|
|
2911
3438
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
.warn(`Meeting:index#setLocalVideoTrack --> Local video quality of ${localQualityLevel} not supported,
|
|
2915
|
-
downscaling to highest possible resolution of ${height}p`);
|
|
3439
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3440
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2916
3441
|
|
|
2917
|
-
|
|
2918
|
-
|
|
3442
|
+
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3443
|
+
this.mediaProperties.setLocalVideoTrack(localTrack);
|
|
2919
3444
|
|
|
2920
|
-
|
|
2921
|
-
if (this.video) this.video.applyClientStateLocally(this);
|
|
3445
|
+
this.video.handleLocalTrackChange(this);
|
|
2922
3446
|
|
|
2923
|
-
|
|
2924
|
-
|
|
2925
|
-
frameRate,
|
|
2926
|
-
height,
|
|
2927
|
-
width,
|
|
2928
|
-
});
|
|
2929
|
-
// store and save the selected video input device
|
|
2930
|
-
if (deviceId) {
|
|
2931
|
-
this.mediaProperties.setVideoDeviceId(deviceId);
|
|
2932
|
-
}
|
|
2933
|
-
LoggerProxy.logger.log(
|
|
2934
|
-
'Meeting:index#setLocalVideoTrack --> Video settings.',
|
|
2935
|
-
JSON.stringify(this.mediaProperties.mediaSettings.video)
|
|
2936
|
-
);
|
|
2937
|
-
}
|
|
3447
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3448
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2938
3449
|
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
3450
|
+
if (!this.isMultistream || !localTrack) {
|
|
3451
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3452
|
+
await this.unpublishTrack(oldTrack);
|
|
2941
3453
|
}
|
|
3454
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
2942
3455
|
}
|
|
2943
3456
|
|
|
2944
3457
|
/**
|
|
2945
|
-
*
|
|
2946
|
-
*
|
|
2947
|
-
*
|
|
2948
|
-
*
|
|
2949
|
-
* @
|
|
3458
|
+
* Stores the reference to a new screen share track, sets up the required event listeners
|
|
3459
|
+
* on it, cleans up previous track, etc.
|
|
3460
|
+
* It also sends the floor grant/release request.
|
|
3461
|
+
*
|
|
3462
|
+
* @param {LocalDisplayTrack | undefined} localDisplayTrack local camera track
|
|
3463
|
+
* @returns {Promise<void>}
|
|
2950
3464
|
*/
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const {audioTrack, videoTrack} = MeetingUtil.getTrack(localStream);
|
|
3465
|
+
private async setLocalShareTrack(localDisplayTrack?: LocalDisplayTrack) {
|
|
3466
|
+
const oldTrack = this.mediaProperties.shareTrack;
|
|
2954
3467
|
|
|
2955
|
-
|
|
2956
|
-
|
|
3468
|
+
oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3469
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2957
3470
|
|
|
2958
|
-
|
|
3471
|
+
this.mediaProperties.setLocalShareTrack(localDisplayTrack);
|
|
3472
|
+
|
|
3473
|
+
localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3474
|
+
localDisplayTrack?.on(
|
|
3475
|
+
LocalTrackEvents.UnderlyingTrackChange,
|
|
3476
|
+
this.underlyingLocalTrackChangeHandler
|
|
3477
|
+
);
|
|
3478
|
+
|
|
3479
|
+
this.mediaProperties.mediaDirection.sendShare = !!localDisplayTrack;
|
|
3480
|
+
|
|
3481
|
+
if (!this.isMultistream || !localDisplayTrack) {
|
|
3482
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3483
|
+
await this.unpublishTrack(oldTrack);
|
|
2959
3484
|
}
|
|
3485
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
2960
3486
|
}
|
|
2961
3487
|
|
|
2962
3488
|
/**
|
|
2963
|
-
*
|
|
2964
|
-
*
|
|
2965
|
-
*
|
|
2966
|
-
* @
|
|
2967
|
-
* @
|
|
2968
|
-
*/
|
|
2969
|
-
public setLocalShareTrack(localShare: MediaStream) {
|
|
2970
|
-
let settings = null;
|
|
2971
|
-
|
|
2972
|
-
if (localShare) {
|
|
2973
|
-
this.mediaProperties.setLocalShareTrack(MeetingUtil.getTrack(localShare).videoTrack);
|
|
2974
|
-
const contentTracks = this.mediaProperties.shareTrack;
|
|
2975
|
-
|
|
2976
|
-
if (contentTracks) {
|
|
2977
|
-
settings = contentTracks.getSettings();
|
|
2978
|
-
this.mediaProperties.setMediaSettings('screen', {
|
|
2979
|
-
aspectRatio: settings.aspectRatio,
|
|
2980
|
-
frameRate: settings.frameRate,
|
|
2981
|
-
height: settings.height,
|
|
2982
|
-
width: settings.width,
|
|
2983
|
-
displaySurface: settings.displaySurface,
|
|
2984
|
-
cursor: settings.cursor,
|
|
2985
|
-
});
|
|
2986
|
-
LoggerProxy.logger.log(
|
|
2987
|
-
'Meeting:index#setLocalShareTrack --> Screen settings.',
|
|
2988
|
-
JSON.stringify(this.mediaProperties.mediaSettings.screen)
|
|
2989
|
-
);
|
|
2990
|
-
}
|
|
2991
|
-
|
|
2992
|
-
contentTracks.onended = () => this.handleShareTrackEnded(localShare);
|
|
2993
|
-
|
|
2994
|
-
Trigger.trigger(
|
|
2995
|
-
this,
|
|
2996
|
-
{
|
|
2997
|
-
file: 'meeting/index',
|
|
2998
|
-
function: 'setLocalShareTrack',
|
|
2999
|
-
},
|
|
3000
|
-
EVENT_TRIGGERS.MEDIA_READY,
|
|
3001
|
-
{
|
|
3002
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
3003
|
-
stream: localShare,
|
|
3004
|
-
}
|
|
3005
|
-
);
|
|
3006
|
-
}
|
|
3007
|
-
}
|
|
3008
|
-
|
|
3009
|
-
/**
|
|
3010
|
-
* Closes the local stream from the class and emits an event to the developer
|
|
3011
|
-
* @returns {undefined}
|
|
3012
|
-
* @event media:stopped
|
|
3013
|
-
* @public
|
|
3014
|
-
* @memberof Meeting
|
|
3489
|
+
* Removes references to local tracks. This function should be called
|
|
3490
|
+
* on cleanup when we leave the meeting etc.
|
|
3491
|
+
*
|
|
3492
|
+
* @internal
|
|
3493
|
+
* @returns {void}
|
|
3015
3494
|
*/
|
|
3016
|
-
public
|
|
3017
|
-
const {audioTrack, videoTrack} = this.mediaProperties;
|
|
3495
|
+
public cleanupLocalTracks() {
|
|
3496
|
+
const {audioTrack, videoTrack, shareTrack} = this.mediaProperties;
|
|
3018
3497
|
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
.then(() => {
|
|
3022
|
-
const audioStopped = audioTrack && audioTrack.readyState === ENDED;
|
|
3023
|
-
const videoStopped = videoTrack && videoTrack.readyState === ENDED;
|
|
3498
|
+
audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3499
|
+
audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3024
3500
|
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
Trigger.trigger(
|
|
3028
|
-
this,
|
|
3029
|
-
{
|
|
3030
|
-
file: 'meeting/index',
|
|
3031
|
-
function: 'closeLocalStream',
|
|
3032
|
-
},
|
|
3033
|
-
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3034
|
-
{
|
|
3035
|
-
type: EVENT_TYPES.LOCAL,
|
|
3036
|
-
}
|
|
3037
|
-
);
|
|
3038
|
-
} else if (audioTrack || videoTrack) {
|
|
3039
|
-
LoggerProxy.logger.warn(
|
|
3040
|
-
'Meeting:index#closeLocalStream --> Warning: track might already been ended or unavaliable.'
|
|
3041
|
-
);
|
|
3042
|
-
}
|
|
3043
|
-
});
|
|
3044
|
-
}
|
|
3501
|
+
videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3502
|
+
videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3045
3503
|
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
* @returns {undefined}
|
|
3049
|
-
* @event media:stopped
|
|
3050
|
-
* @public
|
|
3051
|
-
* @memberof Meeting
|
|
3052
|
-
*/
|
|
3053
|
-
public closeLocalShare() {
|
|
3054
|
-
const track = this.mediaProperties.shareTrack;
|
|
3504
|
+
shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3505
|
+
shareTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3055
3506
|
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
this,
|
|
3060
|
-
{
|
|
3061
|
-
file: 'meeting/index',
|
|
3062
|
-
function: 'closeLocalShare',
|
|
3063
|
-
},
|
|
3064
|
-
EVENT_TRIGGERS.MEDIA_STOPPED,
|
|
3065
|
-
{
|
|
3066
|
-
type: EVENT_TYPES.LOCAL_SHARE,
|
|
3067
|
-
}
|
|
3068
|
-
);
|
|
3069
|
-
} else if (track) {
|
|
3070
|
-
// Track exists but with wrong readyState
|
|
3071
|
-
LoggerProxy.logger.warn(
|
|
3072
|
-
`Meeting:index#closeLocalShare --> Error: MediaStreamTrack.readyState is ${track.readyState} for localShare`
|
|
3073
|
-
);
|
|
3074
|
-
}
|
|
3075
|
-
});
|
|
3076
|
-
}
|
|
3507
|
+
this.mediaProperties.setLocalAudioTrack(undefined);
|
|
3508
|
+
this.mediaProperties.setLocalVideoTrack(undefined);
|
|
3509
|
+
this.mediaProperties.setLocalShareTrack(undefined);
|
|
3077
3510
|
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
* @public
|
|
3082
|
-
* @memberof Meeting
|
|
3083
|
-
*/
|
|
3084
|
-
public unsetLocalVideoTrack() {
|
|
3085
|
-
this.mediaProperties.unsetLocalVideoTrack();
|
|
3086
|
-
}
|
|
3511
|
+
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3512
|
+
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3513
|
+
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3087
3514
|
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
*/
|
|
3094
|
-
public unsetLocalShareTrack() {
|
|
3095
|
-
this.mediaProperties.unsetLocalShareTrack();
|
|
3515
|
+
// WCME doesn't unpublish tracks when multistream connection is closed, so we do it here
|
|
3516
|
+
// (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
|
|
3517
|
+
audioTrack?.setPublished(false);
|
|
3518
|
+
videoTrack?.setPublished(false);
|
|
3519
|
+
shareTrack?.setPublished(false);
|
|
3096
3520
|
}
|
|
3097
3521
|
|
|
3098
3522
|
/**
|
|
@@ -3143,6 +3567,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3143
3567
|
* @memberof Meeting
|
|
3144
3568
|
*/
|
|
3145
3569
|
public closePeerConnections() {
|
|
3570
|
+
this.locusMediaRequest = undefined;
|
|
3571
|
+
|
|
3146
3572
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
3147
3573
|
if (this.remoteMediaManager) {
|
|
3148
3574
|
this.remoteMediaManager.stop();
|
|
@@ -3157,6 +3583,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3157
3583
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3158
3584
|
}
|
|
3159
3585
|
|
|
3586
|
+
this.audio = null;
|
|
3587
|
+
this.video = null;
|
|
3588
|
+
|
|
3160
3589
|
return Promise.resolve();
|
|
3161
3590
|
}
|
|
3162
3591
|
|
|
@@ -3187,264 +3616,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3187
3616
|
this.correlationId = id;
|
|
3188
3617
|
}
|
|
3189
3618
|
|
|
3190
|
-
/**
|
|
3191
|
-
* Mute the audio for a meeting
|
|
3192
|
-
* @returns {Promise} resolves the data from muting audio {mute, self} or rejects if there is no audio set
|
|
3193
|
-
* @public
|
|
3194
|
-
* @memberof Meeting
|
|
3195
|
-
*/
|
|
3196
|
-
public muteAudio() {
|
|
3197
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3198
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3199
|
-
}
|
|
3200
|
-
|
|
3201
|
-
// @ts-ignore
|
|
3202
|
-
if (!this.mediaId) {
|
|
3203
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3204
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3205
|
-
}
|
|
3206
|
-
|
|
3207
|
-
if (!this.audio) {
|
|
3208
|
-
return Promise.reject(new ParameterError('no audio control associated to the meeting'));
|
|
3209
|
-
}
|
|
3210
|
-
|
|
3211
|
-
const LOG_HEADER = 'Meeting:index#muteAudio -->';
|
|
3212
|
-
|
|
3213
|
-
// First, stop sending the local audio media
|
|
3214
|
-
return logRequest(
|
|
3215
|
-
this.audio
|
|
3216
|
-
.handleClientRequest(this, true)
|
|
3217
|
-
.then(() => {
|
|
3218
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.audioTrack);
|
|
3219
|
-
Metrics.postEvent({
|
|
3220
|
-
event: eventType.MUTED,
|
|
3221
|
-
meeting: this,
|
|
3222
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: mediaType.AUDIO},
|
|
3223
|
-
});
|
|
3224
|
-
})
|
|
3225
|
-
.catch((error) => {
|
|
3226
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MUTE_AUDIO_FAILURE, {
|
|
3227
|
-
correlation_id: this.correlationId,
|
|
3228
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3229
|
-
reason: error.message,
|
|
3230
|
-
stack: error.stack,
|
|
3231
|
-
});
|
|
3232
|
-
|
|
3233
|
-
throw error;
|
|
3234
|
-
}),
|
|
3235
|
-
{
|
|
3236
|
-
header: `${LOG_HEADER} muting audio`,
|
|
3237
|
-
success: `${LOG_HEADER} muted audio successfully`,
|
|
3238
|
-
failure: `${LOG_HEADER} muting audio failed, `,
|
|
3239
|
-
}
|
|
3240
|
-
);
|
|
3241
|
-
}
|
|
3242
|
-
|
|
3243
|
-
/**
|
|
3244
|
-
* Unmute meeting audio
|
|
3245
|
-
* @returns {Promise} resolves data from muting audio {mute, self} or rejects if there is no audio set
|
|
3246
|
-
* @public
|
|
3247
|
-
* @memberof Meeting
|
|
3248
|
-
*/
|
|
3249
|
-
public unmuteAudio() {
|
|
3250
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3251
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3252
|
-
}
|
|
3253
|
-
|
|
3254
|
-
// @ts-ignore
|
|
3255
|
-
if (!this.mediaId) {
|
|
3256
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3257
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3258
|
-
}
|
|
3259
|
-
|
|
3260
|
-
if (!this.audio) {
|
|
3261
|
-
return Promise.reject(new ParameterError('no audio control associated to the meeting'));
|
|
3262
|
-
}
|
|
3263
|
-
|
|
3264
|
-
const LOG_HEADER = 'Meeting:index#unmuteAudio -->';
|
|
3265
|
-
|
|
3266
|
-
// First, send the control to unmute the participant on the server
|
|
3267
|
-
return logRequest(
|
|
3268
|
-
this.audio
|
|
3269
|
-
.handleClientRequest(this, false)
|
|
3270
|
-
.then(() => {
|
|
3271
|
-
MeetingUtil.handleAudioLogging(this.mediaProperties.audioTrack);
|
|
3272
|
-
Metrics.postEvent({
|
|
3273
|
-
event: eventType.UNMUTED,
|
|
3274
|
-
meeting: this,
|
|
3275
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: mediaType.AUDIO},
|
|
3276
|
-
});
|
|
3277
|
-
})
|
|
3278
|
-
.catch((error) => {
|
|
3279
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UNMUTE_AUDIO_FAILURE, {
|
|
3280
|
-
correlation_id: this.correlationId,
|
|
3281
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3282
|
-
reason: error.message,
|
|
3283
|
-
stack: error.stack,
|
|
3284
|
-
});
|
|
3285
|
-
|
|
3286
|
-
throw error;
|
|
3287
|
-
}),
|
|
3288
|
-
{
|
|
3289
|
-
header: `${LOG_HEADER} unmuting audio`,
|
|
3290
|
-
success: `${LOG_HEADER} unmuted audio successfully`,
|
|
3291
|
-
failure: `${LOG_HEADER} unmuting audio failed, `,
|
|
3292
|
-
}
|
|
3293
|
-
);
|
|
3294
|
-
}
|
|
3295
|
-
|
|
3296
|
-
/**
|
|
3297
|
-
* Mute the video for a meeting
|
|
3298
|
-
* @returns {Promise} resolves data from muting video {mute, self} or rejects if there is no video set
|
|
3299
|
-
* @public
|
|
3300
|
-
* @memberof Meeting
|
|
3301
|
-
*/
|
|
3302
|
-
public muteVideo() {
|
|
3303
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3304
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3305
|
-
}
|
|
3306
|
-
|
|
3307
|
-
// @ts-ignore
|
|
3308
|
-
if (!this.mediaId) {
|
|
3309
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3310
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3311
|
-
}
|
|
3312
|
-
|
|
3313
|
-
if (!this.video) {
|
|
3314
|
-
return Promise.reject(new ParameterError('no video control associated to the meeting'));
|
|
3315
|
-
}
|
|
3316
|
-
|
|
3317
|
-
const LOG_HEADER = 'Meeting:index#muteVideo -->';
|
|
3318
|
-
|
|
3319
|
-
return logRequest(
|
|
3320
|
-
this.video
|
|
3321
|
-
.handleClientRequest(this, true)
|
|
3322
|
-
.then(() => {
|
|
3323
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.videoTrack);
|
|
3324
|
-
Metrics.postEvent({
|
|
3325
|
-
event: eventType.MUTED,
|
|
3326
|
-
meeting: this,
|
|
3327
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: mediaType.VIDEO},
|
|
3328
|
-
});
|
|
3329
|
-
})
|
|
3330
|
-
.catch((error) => {
|
|
3331
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MUTE_VIDEO_FAILURE, {
|
|
3332
|
-
correlation_id: this.correlationId,
|
|
3333
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3334
|
-
reason: error.message,
|
|
3335
|
-
stack: error.stack,
|
|
3336
|
-
});
|
|
3337
|
-
|
|
3338
|
-
throw error;
|
|
3339
|
-
}),
|
|
3340
|
-
{
|
|
3341
|
-
header: `${LOG_HEADER} muting video`,
|
|
3342
|
-
success: `${LOG_HEADER} muted video successfully`,
|
|
3343
|
-
failure: `${LOG_HEADER} muting video failed, `,
|
|
3344
|
-
}
|
|
3345
|
-
);
|
|
3346
|
-
}
|
|
3347
|
-
|
|
3348
|
-
/**
|
|
3349
|
-
* Unmute meeting video
|
|
3350
|
-
* @returns {Promise} resolves data from muting video {mute, self} or rejects if there is no video set
|
|
3351
|
-
* @public
|
|
3352
|
-
* @memberof Meeting
|
|
3353
|
-
*/
|
|
3354
|
-
public unmuteVideo() {
|
|
3355
|
-
if (!MeetingUtil.isUserInJoinedState(this.locusInfo)) {
|
|
3356
|
-
return Promise.reject(new UserNotJoinedError());
|
|
3357
|
-
}
|
|
3358
|
-
|
|
3359
|
-
// @ts-ignore
|
|
3360
|
-
if (!this.mediaId) {
|
|
3361
|
-
// Happens when addMedia and mute are triggered in succession
|
|
3362
|
-
return Promise.reject(new NoMediaEstablishedYetError());
|
|
3363
|
-
}
|
|
3364
|
-
|
|
3365
|
-
if (!this.video) {
|
|
3366
|
-
return Promise.reject(new ParameterError('no audio control associated to the meeting'));
|
|
3367
|
-
}
|
|
3368
|
-
|
|
3369
|
-
const LOG_HEADER = 'Meeting:index#unmuteVideo -->';
|
|
3370
|
-
|
|
3371
|
-
return logRequest(
|
|
3372
|
-
this.video
|
|
3373
|
-
.handleClientRequest(this, false)
|
|
3374
|
-
.then(() => {
|
|
3375
|
-
MeetingUtil.handleVideoLogging(this.mediaProperties.videoTrack);
|
|
3376
|
-
Metrics.postEvent({
|
|
3377
|
-
event: eventType.UNMUTED,
|
|
3378
|
-
meeting: this,
|
|
3379
|
-
data: {trigger: trigger.USER_INTERACTION, mediaType: mediaType.VIDEO},
|
|
3380
|
-
});
|
|
3381
|
-
})
|
|
3382
|
-
.catch((error) => {
|
|
3383
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UNMUTE_VIDEO_FAILURE, {
|
|
3384
|
-
correlation_id: this.correlationId,
|
|
3385
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
3386
|
-
reason: error.message,
|
|
3387
|
-
stack: error.stack,
|
|
3388
|
-
});
|
|
3389
|
-
|
|
3390
|
-
throw error;
|
|
3391
|
-
}),
|
|
3392
|
-
{
|
|
3393
|
-
header: `${LOG_HEADER} unmuting video`,
|
|
3394
|
-
success: `${LOG_HEADER} unmuted video successfully`,
|
|
3395
|
-
failure: `${LOG_HEADER} unmuting video failed, `,
|
|
3396
|
-
}
|
|
3397
|
-
);
|
|
3398
|
-
}
|
|
3399
|
-
|
|
3400
3619
|
/**
|
|
3401
3620
|
* Shorthand function to join AND set up media
|
|
3402
3621
|
* @param {Object} options - options to join with media
|
|
3403
3622
|
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
3404
|
-
* @param {MediaDirection} options.
|
|
3405
|
-
* @
|
|
3406
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia(), local: see getMediaStreams()}
|
|
3623
|
+
* @param {MediaDirection} [options.mediaOptions] - see #addMedia()
|
|
3624
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
3407
3625
|
* @public
|
|
3408
3626
|
* @memberof Meeting
|
|
3409
3627
|
* @example
|
|
3410
3628
|
* joinWithMedia({
|
|
3411
3629
|
* joinOptions: {resourceId: 'resourceId' },
|
|
3412
|
-
*
|
|
3413
|
-
*
|
|
3414
|
-
*
|
|
3415
|
-
*
|
|
3416
|
-
* receiveVideo:true,
|
|
3417
|
-
* receiveAudio: true,
|
|
3418
|
-
* receiveShare: true
|
|
3419
|
-
* }
|
|
3420
|
-
* audioVideoOptions: {
|
|
3421
|
-
* audio: 'audioDeviceId',
|
|
3422
|
-
* video: 'videoDeviceId'
|
|
3423
|
-
* }})
|
|
3630
|
+
* mediaOptions: {
|
|
3631
|
+
* localTracks: { microphone: microphoneTrack, camera: cameraTrack }
|
|
3632
|
+
* }
|
|
3633
|
+
* })
|
|
3424
3634
|
*/
|
|
3425
3635
|
public joinWithMedia(
|
|
3426
3636
|
options: {
|
|
3427
3637
|
joinOptions?: any;
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
} = {} as any
|
|
3638
|
+
mediaOptions?: AddMediaOptions;
|
|
3639
|
+
} = {}
|
|
3431
3640
|
) {
|
|
3432
|
-
|
|
3433
|
-
const {mediaSettings, joinOptions, audioVideoOptions} = options;
|
|
3641
|
+
const {mediaOptions, joinOptions} = options;
|
|
3434
3642
|
|
|
3435
3643
|
return this.join(joinOptions)
|
|
3436
3644
|
.then((joinResponse) =>
|
|
3437
|
-
this.
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
localStream,
|
|
3442
|
-
}).then((mediaResponse) => ({
|
|
3443
|
-
join: joinResponse,
|
|
3444
|
-
media: mediaResponse,
|
|
3445
|
-
local: [localStream, localShare],
|
|
3446
|
-
}))
|
|
3447
|
-
)
|
|
3645
|
+
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
3646
|
+
join: joinResponse,
|
|
3647
|
+
media: mediaResponse,
|
|
3648
|
+
}))
|
|
3448
3649
|
)
|
|
3449
3650
|
.catch((error) => {
|
|
3450
3651
|
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
@@ -3582,6 +3783,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3582
3783
|
return false;
|
|
3583
3784
|
}
|
|
3584
3785
|
|
|
3786
|
+
/**
|
|
3787
|
+
* Check if the meeting supports the Reactions
|
|
3788
|
+
* @returns {boolean}
|
|
3789
|
+
*/
|
|
3790
|
+
isReactionsSupported() {
|
|
3791
|
+
if (this.locusInfo?.controls?.reactions.enabled) {
|
|
3792
|
+
return true;
|
|
3793
|
+
}
|
|
3794
|
+
|
|
3795
|
+
LoggerProxy.logger.error('Meeting:index#isReactionsSupported --> Reactions is not supported');
|
|
3796
|
+
|
|
3797
|
+
return false;
|
|
3798
|
+
}
|
|
3799
|
+
|
|
3585
3800
|
/**
|
|
3586
3801
|
* Monitor the Low-Latency Mercury (LLM) web socket connection on `onError` and `onClose` states
|
|
3587
3802
|
* @private
|
|
@@ -3633,6 +3848,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3633
3848
|
// @ts-ignore - fix type
|
|
3634
3849
|
const {
|
|
3635
3850
|
body: {webSocketUrl},
|
|
3851
|
+
// @ts-ignore
|
|
3636
3852
|
} = await this.request({
|
|
3637
3853
|
method: HTTP_VERBS.POST,
|
|
3638
3854
|
uri: datachannelUrl,
|
|
@@ -3682,6 +3898,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3682
3898
|
}
|
|
3683
3899
|
}
|
|
3684
3900
|
|
|
3901
|
+
/**
|
|
3902
|
+
* Callback called when a relay event is received from meeting LLM Connection
|
|
3903
|
+
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
3904
|
+
* @private
|
|
3905
|
+
* @returns {void}
|
|
3906
|
+
*/
|
|
3907
|
+
private processRelayEvent = (e: RelayEvent): void => {
|
|
3908
|
+
switch (e.data.relayType) {
|
|
3909
|
+
case REACTION_RELAY_TYPES.REACTION:
|
|
3910
|
+
if (
|
|
3911
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3912
|
+
(this.config.receiveReactions || options.receiveReactions) &&
|
|
3913
|
+
this.isReactionsSupported()
|
|
3914
|
+
) {
|
|
3915
|
+
const {name} = this.members.membersCollection.get(e.data.sender.participantId);
|
|
3916
|
+
const processedReaction: ProcessedReaction = {
|
|
3917
|
+
reaction: e.data.reaction,
|
|
3918
|
+
sender: {
|
|
3919
|
+
id: e.data.sender.participantId,
|
|
3920
|
+
name,
|
|
3921
|
+
},
|
|
3922
|
+
};
|
|
3923
|
+
Trigger.trigger(
|
|
3924
|
+
this,
|
|
3925
|
+
{
|
|
3926
|
+
file: 'meeting/index',
|
|
3927
|
+
function: 'join',
|
|
3928
|
+
},
|
|
3929
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
3930
|
+
processedReaction
|
|
3931
|
+
);
|
|
3932
|
+
}
|
|
3933
|
+
break;
|
|
3934
|
+
default:
|
|
3935
|
+
break;
|
|
3936
|
+
}
|
|
3937
|
+
};
|
|
3938
|
+
|
|
3685
3939
|
/**
|
|
3686
3940
|
* stop recieving Transcription by closing
|
|
3687
3941
|
* the web socket connection properly
|
|
@@ -3753,9 +4007,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3753
4007
|
joinSuccess = resolve;
|
|
3754
4008
|
});
|
|
3755
4009
|
|
|
4010
|
+
if (options.correlationId) {
|
|
4011
|
+
this.setCorrelationId(options.correlationId);
|
|
4012
|
+
LoggerProxy.logger.log(
|
|
4013
|
+
`Meeting:index#join --> Using a new correlation id from app ${this.correlationId}`
|
|
4014
|
+
);
|
|
4015
|
+
}
|
|
4016
|
+
|
|
3756
4017
|
if (!this.hasJoinedOnce) {
|
|
3757
4018
|
this.hasJoinedOnce = true;
|
|
3758
|
-
} else {
|
|
4019
|
+
} else if (!options.correlationId) {
|
|
3759
4020
|
LoggerProxy.logger.log(
|
|
3760
4021
|
`Meeting:index#join --> Generating a new correlation id for meeting ${this.id}`
|
|
3761
4022
|
);
|
|
@@ -3776,6 +4037,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3776
4037
|
data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
|
|
3777
4038
|
});
|
|
3778
4039
|
|
|
4040
|
+
if (!isEmpty(this.meetingInfo)) {
|
|
4041
|
+
Metrics.postEvent({
|
|
4042
|
+
event: eventType.MEETING_INFO_REQUEST,
|
|
4043
|
+
meeting: this,
|
|
4044
|
+
});
|
|
4045
|
+
|
|
4046
|
+
Metrics.postEvent({
|
|
4047
|
+
event: eventType.MEETING_INFO_RESPONSE,
|
|
4048
|
+
meeting: this,
|
|
4049
|
+
data: {
|
|
4050
|
+
meetingLookupUrl: this.meetingInfo?.meetingLookupUrl,
|
|
4051
|
+
},
|
|
4052
|
+
});
|
|
4053
|
+
}
|
|
4054
|
+
|
|
3779
4055
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
3780
4056
|
|
|
3781
4057
|
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
@@ -3804,18 +4080,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3804
4080
|
return Promise.reject(error);
|
|
3805
4081
|
}
|
|
3806
4082
|
|
|
3807
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality);
|
|
3808
4083
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality);
|
|
3809
4084
|
}
|
|
3810
4085
|
|
|
3811
4086
|
if (typeof options.meetingQuality === 'object') {
|
|
3812
|
-
if (
|
|
3813
|
-
|
|
3814
|
-
!QUALITY_LEVELS[options.meetingQuality.remote]
|
|
3815
|
-
) {
|
|
3816
|
-
const errorMessage = `Meeting:index#join --> ${
|
|
3817
|
-
options.meetingQuality.local || options.meetingQuality.remote
|
|
3818
|
-
} not defined`;
|
|
4087
|
+
if (!QUALITY_LEVELS[options.meetingQuality.remote]) {
|
|
4088
|
+
const errorMessage = `Meeting:index#join --> ${options.meetingQuality.remote} not defined`;
|
|
3819
4089
|
|
|
3820
4090
|
LoggerProxy.logger.error(errorMessage);
|
|
3821
4091
|
|
|
@@ -3827,9 +4097,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3827
4097
|
return Promise.reject(new Error(errorMessage));
|
|
3828
4098
|
}
|
|
3829
4099
|
|
|
3830
|
-
if (options.meetingQuality.local) {
|
|
3831
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality.local);
|
|
3832
|
-
}
|
|
3833
4100
|
if (options.meetingQuality.remote) {
|
|
3834
4101
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality.remote);
|
|
3835
4102
|
}
|
|
@@ -3855,6 +4122,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3855
4122
|
return join;
|
|
3856
4123
|
})
|
|
3857
4124
|
.then(async (join) => {
|
|
4125
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3858
4126
|
if (this.config.enableAutomaticLLM) {
|
|
3859
4127
|
await this.updateLLMConnection();
|
|
3860
4128
|
}
|
|
@@ -3923,22 +4191,41 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3923
4191
|
* @returns {Promise}
|
|
3924
4192
|
*/
|
|
3925
4193
|
async updateLLMConnection() {
|
|
4194
|
+
// @ts-ignore - Fix type
|
|
3926
4195
|
const {url, info: {datachannelUrl} = {}} = this.locusInfo;
|
|
3927
4196
|
|
|
3928
|
-
const isJoined = this.
|
|
4197
|
+
const isJoined = this.isJoined();
|
|
3929
4198
|
|
|
4199
|
+
// @ts-ignore - Fix type
|
|
3930
4200
|
if (this.webex.internal.llm.isConnected()) {
|
|
4201
|
+
// @ts-ignore - Fix type
|
|
3931
4202
|
if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
|
|
3932
4203
|
return undefined;
|
|
3933
4204
|
}
|
|
4205
|
+
// @ts-ignore - Fix type
|
|
3934
4206
|
await this.webex.internal.llm.disconnectLLM();
|
|
4207
|
+
// @ts-ignore - Fix type
|
|
4208
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
3935
4209
|
}
|
|
3936
4210
|
|
|
3937
4211
|
if (!isJoined) {
|
|
3938
4212
|
return undefined;
|
|
3939
4213
|
}
|
|
3940
4214
|
|
|
3941
|
-
|
|
4215
|
+
// @ts-ignore - Fix type
|
|
4216
|
+
return this.webex.internal.llm
|
|
4217
|
+
.registerAndConnect(url, datachannelUrl)
|
|
4218
|
+
.then((registerAndConnectResult) => {
|
|
4219
|
+
// @ts-ignore - Fix type
|
|
4220
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
4221
|
+
// @ts-ignore - Fix type
|
|
4222
|
+
this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
|
|
4223
|
+
LoggerProxy.logger.info(
|
|
4224
|
+
'Meeting:index#updateLLMConnection --> enabled to receive relay events!'
|
|
4225
|
+
);
|
|
4226
|
+
|
|
4227
|
+
return Promise.resolve(registerAndConnectResult);
|
|
4228
|
+
});
|
|
3942
4229
|
}
|
|
3943
4230
|
|
|
3944
4231
|
/**
|
|
@@ -3980,28 +4267,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3980
4267
|
|
|
3981
4268
|
if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
|
|
3982
4269
|
|
|
3983
|
-
return
|
|
3984
|
-
.
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
});
|
|
4270
|
+
return (
|
|
4271
|
+
this.meetingRequest
|
|
4272
|
+
// @ts-ignore
|
|
4273
|
+
.dialIn({
|
|
4274
|
+
correlationId,
|
|
4275
|
+
dialInUrl: this.dialInUrl,
|
|
4276
|
+
locusUrl,
|
|
4277
|
+
clientUrl: this.deviceUrl,
|
|
4278
|
+
})
|
|
4279
|
+
.catch((error) => {
|
|
4280
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
|
|
4281
|
+
correlation_id: this.correlationId,
|
|
4282
|
+
dial_in_url: this.dialInUrl,
|
|
4283
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4284
|
+
client_url: this.deviceUrl,
|
|
4285
|
+
reason: error.error?.message,
|
|
4286
|
+
stack: error.stack,
|
|
4287
|
+
});
|
|
4002
4288
|
|
|
4003
|
-
|
|
4004
|
-
|
|
4289
|
+
return Promise.reject(error);
|
|
4290
|
+
})
|
|
4291
|
+
);
|
|
4005
4292
|
}
|
|
4006
4293
|
|
|
4007
4294
|
/**
|
|
@@ -4018,29 +4305,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4018
4305
|
|
|
4019
4306
|
if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
|
|
4020
4307
|
|
|
4021
|
-
return
|
|
4022
|
-
.
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
});
|
|
4308
|
+
return (
|
|
4309
|
+
this.meetingRequest
|
|
4310
|
+
// @ts-ignore
|
|
4311
|
+
.dialOut({
|
|
4312
|
+
correlationId,
|
|
4313
|
+
dialOutUrl: this.dialOutUrl,
|
|
4314
|
+
phoneNumber,
|
|
4315
|
+
locusUrl,
|
|
4316
|
+
clientUrl: this.deviceUrl,
|
|
4317
|
+
})
|
|
4318
|
+
.catch((error) => {
|
|
4319
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
|
|
4320
|
+
correlation_id: this.correlationId,
|
|
4321
|
+
dial_out_url: this.dialOutUrl,
|
|
4322
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4323
|
+
client_url: this.deviceUrl,
|
|
4324
|
+
reason: error.error?.message,
|
|
4325
|
+
stack: error.stack,
|
|
4326
|
+
});
|
|
4041
4327
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4328
|
+
return Promise.reject(error);
|
|
4329
|
+
})
|
|
4330
|
+
);
|
|
4044
4331
|
}
|
|
4045
4332
|
|
|
4046
4333
|
/**
|
|
@@ -4116,14 +4403,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4116
4403
|
},
|
|
4117
4404
|
};
|
|
4118
4405
|
|
|
4119
|
-
|
|
4120
|
-
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4121
|
-
|
|
4122
|
-
// close the existing local tracks
|
|
4123
|
-
await this.closeLocalStream();
|
|
4124
|
-
await this.closeLocalShare();
|
|
4406
|
+
this.cleanupLocalTracks();
|
|
4125
4407
|
|
|
4126
|
-
this.mediaProperties.
|
|
4408
|
+
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4409
|
+
this.mediaProperties.unsetRemoteMedia();
|
|
4127
4410
|
|
|
4128
4411
|
// when a move to is intiated by the client , Locus delets the existing media node from the server as soon the DX answers the meeting
|
|
4129
4412
|
// once the DX answers we establish connection back the media server with only receiveShare enabled
|
|
@@ -4206,165 +4489,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4206
4489
|
});
|
|
4207
4490
|
}
|
|
4208
4491
|
|
|
4209
|
-
/**
|
|
4210
|
-
* Get local media streams based on options passed
|
|
4211
|
-
*
|
|
4212
|
-
* NOTE: this method can only be used with transcoded meetings, not with multistream meetings
|
|
4213
|
-
*
|
|
4214
|
-
* @param {MediaDirection} mediaDirection A configurable options object for joining a meeting
|
|
4215
|
-
* @param {AudioVideo} [audioVideo] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
4216
|
-
* @param {SharePreferences} [sharePreferences] audio/video object to set audioinput and videoinput devices, see #Media.getUserMedia
|
|
4217
|
-
* @returns {Promise} see #Media.getUserMedia
|
|
4218
|
-
* @public
|
|
4219
|
-
* @todo should be static, or moved so can be called outside of a meeting
|
|
4220
|
-
* @memberof Meeting
|
|
4221
|
-
*/
|
|
4222
|
-
getMediaStreams = (
|
|
4223
|
-
mediaDirection: any,
|
|
4224
|
-
// This return an OBJECT {video: {height, widght}}
|
|
4225
|
-
// eslint-disable-next-line default-param-last
|
|
4226
|
-
audioVideo: any = VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel],
|
|
4227
|
-
sharePreferences?: any
|
|
4228
|
-
) => {
|
|
4229
|
-
if (
|
|
4230
|
-
mediaDirection &&
|
|
4231
|
-
(mediaDirection.sendAudio || mediaDirection.sendVideo || mediaDirection.sendShare)
|
|
4232
|
-
) {
|
|
4233
|
-
if (
|
|
4234
|
-
mediaDirection &&
|
|
4235
|
-
mediaDirection.sendAudio &&
|
|
4236
|
-
mediaDirection.sendVideo &&
|
|
4237
|
-
mediaDirection.sendShare &&
|
|
4238
|
-
isBrowser('safari')
|
|
4239
|
-
) {
|
|
4240
|
-
LoggerProxy.logger.warn(
|
|
4241
|
-
'Meeting:index#getMediaStreams --> Setting `sendShare` to FALSE, due to complications with Safari'
|
|
4242
|
-
);
|
|
4243
|
-
|
|
4244
|
-
mediaDirection.sendShare = false;
|
|
4245
|
-
|
|
4246
|
-
LoggerProxy.logger.warn(
|
|
4247
|
-
'Meeting:index#getMediaStreams --> Enabling `sendShare` along with `sendAudio` & `sendVideo`, on Safari, causes a failure while setting up a screen share at the same time as the camera+mic stream'
|
|
4248
|
-
);
|
|
4249
|
-
LoggerProxy.logger.warn(
|
|
4250
|
-
'Meeting:index#getMediaStreams --> Please use `meeting.shareScreen()` to manually start the screen share after successfully joining the meeting'
|
|
4251
|
-
);
|
|
4252
|
-
}
|
|
4253
|
-
|
|
4254
|
-
if (audioVideo && isString(audioVideo)) {
|
|
4255
|
-
if (Object.keys(VIDEO_RESOLUTIONS).includes(audioVideo)) {
|
|
4256
|
-
this.mediaProperties.setLocalQualityLevel(audioVideo);
|
|
4257
|
-
audioVideo = {video: VIDEO_RESOLUTIONS[audioVideo].video};
|
|
4258
|
-
} else {
|
|
4259
|
-
throw new ParameterError(
|
|
4260
|
-
`${audioVideo} not supported. Either pass level from pre-defined resolutions or pass complete audioVideo object`
|
|
4261
|
-
);
|
|
4262
|
-
}
|
|
4263
|
-
}
|
|
4264
|
-
|
|
4265
|
-
if (!audioVideo.video) {
|
|
4266
|
-
audioVideo = {
|
|
4267
|
-
...audioVideo,
|
|
4268
|
-
video: {
|
|
4269
|
-
...audioVideo.video,
|
|
4270
|
-
...VIDEO_RESOLUTIONS[this.mediaProperties.localQualityLevel].video,
|
|
4271
|
-
},
|
|
4272
|
-
};
|
|
4273
|
-
}
|
|
4274
|
-
// extract deviceId if exists otherwise default to null.
|
|
4275
|
-
const {deviceId: preferredVideoDevice} = (audioVideo && audioVideo.video) || {deviceId: null};
|
|
4276
|
-
const lastVideoDeviceId = this.mediaProperties.getVideoDeviceId();
|
|
4277
|
-
|
|
4278
|
-
if (preferredVideoDevice) {
|
|
4279
|
-
// Store new preferred video input device
|
|
4280
|
-
this.mediaProperties.setVideoDeviceId(preferredVideoDevice);
|
|
4281
|
-
} else if (lastVideoDeviceId) {
|
|
4282
|
-
// no new video preference specified so use last stored value,
|
|
4283
|
-
// works with empty object {} or media constraint.
|
|
4284
|
-
// eslint-disable-next-line no-param-reassign
|
|
4285
|
-
audioVideo = {
|
|
4286
|
-
...audioVideo,
|
|
4287
|
-
video: {
|
|
4288
|
-
...audioVideo.video,
|
|
4289
|
-
deviceId: lastVideoDeviceId,
|
|
4290
|
-
},
|
|
4291
|
-
};
|
|
4292
|
-
}
|
|
4293
|
-
|
|
4294
|
-
return Media.getSupportedDevice({
|
|
4295
|
-
sendAudio: mediaDirection.sendAudio,
|
|
4296
|
-
sendVideo: mediaDirection.sendVideo,
|
|
4297
|
-
})
|
|
4298
|
-
.catch((error) =>
|
|
4299
|
-
Promise.reject(
|
|
4300
|
-
new MediaError(
|
|
4301
|
-
'Given constraints do not match permission set for either camera or microphone',
|
|
4302
|
-
error
|
|
4303
|
-
)
|
|
4304
|
-
)
|
|
4305
|
-
)
|
|
4306
|
-
.then((devicePermissions) =>
|
|
4307
|
-
Media.getUserMedia(
|
|
4308
|
-
{
|
|
4309
|
-
...mediaDirection,
|
|
4310
|
-
sendAudio: devicePermissions.sendAudio,
|
|
4311
|
-
sendVideo: devicePermissions.sendVideo,
|
|
4312
|
-
isSharing: this.shareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE,
|
|
4313
|
-
},
|
|
4314
|
-
audioVideo,
|
|
4315
|
-
sharePreferences,
|
|
4316
|
-
// @ts-ignore - config coming from registerPlugin
|
|
4317
|
-
this.config
|
|
4318
|
-
).catch((error) => {
|
|
4319
|
-
// Whenever there is a failure when trying to access a user's device
|
|
4320
|
-
// report it as an Behavioral metric
|
|
4321
|
-
// This gives visibility into common errors and can help
|
|
4322
|
-
// with further troubleshooting
|
|
4323
|
-
const metricName = BEHAVIORAL_METRICS.GET_USER_MEDIA_FAILURE;
|
|
4324
|
-
const data = {
|
|
4325
|
-
correlation_id: this.correlationId,
|
|
4326
|
-
locus_id: this.locusUrl?.split('/').pop(),
|
|
4327
|
-
reason: error.message,
|
|
4328
|
-
stack: error.stack,
|
|
4329
|
-
};
|
|
4330
|
-
const metadata = {
|
|
4331
|
-
type: error.name,
|
|
4332
|
-
};
|
|
4333
|
-
|
|
4334
|
-
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
4335
|
-
throw new MediaError('Unable to retrieve media streams', error);
|
|
4336
|
-
})
|
|
4337
|
-
);
|
|
4338
|
-
}
|
|
4339
|
-
|
|
4340
|
-
return Promise.reject(
|
|
4341
|
-
new MediaError('At least one of the mediaDirection value should be true')
|
|
4342
|
-
);
|
|
4343
|
-
};
|
|
4344
|
-
|
|
4345
|
-
/**
|
|
4346
|
-
* Checks if the machine has at least one audio or video device
|
|
4347
|
-
* @param {Object} options
|
|
4348
|
-
* @param {Boolean} options.sendAudio
|
|
4349
|
-
* @param {Boolean} options.sendVideo
|
|
4350
|
-
* @returns {Object}
|
|
4351
|
-
* @memberof Meetings
|
|
4352
|
-
*/
|
|
4353
|
-
getSupportedDevices = ({
|
|
4354
|
-
sendAudio = true,
|
|
4355
|
-
sendVideo = true,
|
|
4356
|
-
}: {
|
|
4357
|
-
sendAudio: boolean;
|
|
4358
|
-
sendVideo: boolean;
|
|
4359
|
-
}) => Media.getSupportedDevice({sendAudio, sendVideo});
|
|
4360
|
-
|
|
4361
|
-
/**
|
|
4362
|
-
* Get the devices from the Media module
|
|
4363
|
-
* @returns {Promise} resolves to an array of DeviceInfo
|
|
4364
|
-
* @memberof Meetings
|
|
4365
|
-
*/
|
|
4366
|
-
getDevices = () => Media.getDevices();
|
|
4367
|
-
|
|
4368
4492
|
/**
|
|
4369
4493
|
* Handles ROAP_FAILURE event from the webrtc media connection
|
|
4370
4494
|
*
|
|
@@ -4387,7 +4511,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4387
4511
|
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
4388
4512
|
};
|
|
4389
4513
|
|
|
4390
|
-
if (error instanceof
|
|
4514
|
+
if (error instanceof Errors.SdpOfferCreationError) {
|
|
4391
4515
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4392
4516
|
|
|
4393
4517
|
Metrics.postEvent({
|
|
@@ -4401,8 +4525,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4401
4525
|
},
|
|
4402
4526
|
});
|
|
4403
4527
|
} else if (
|
|
4404
|
-
error instanceof
|
|
4405
|
-
error instanceof
|
|
4528
|
+
error instanceof Errors.SdpOfferHandlingError ||
|
|
4529
|
+
error instanceof Errors.SdpAnswerHandlingError
|
|
4406
4530
|
) {
|
|
4407
4531
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4408
4532
|
|
|
@@ -4416,8 +4540,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4416
4540
|
],
|
|
4417
4541
|
},
|
|
4418
4542
|
});
|
|
4419
|
-
} else if (error instanceof
|
|
4420
|
-
// this covers also the case of
|
|
4543
|
+
} else if (error instanceof Errors.SdpError) {
|
|
4544
|
+
// this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
|
|
4421
4545
|
sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
|
|
4422
4546
|
|
|
4423
4547
|
Metrics.postEvent({
|
|
@@ -4434,20 +4558,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4434
4558
|
};
|
|
4435
4559
|
|
|
4436
4560
|
setupMediaConnectionListeners = () => {
|
|
4437
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4561
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_STARTED, () => {
|
|
4438
4562
|
this.isRoapInProgress = true;
|
|
4439
4563
|
});
|
|
4440
4564
|
|
|
4441
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4565
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
|
|
4442
4566
|
this.mediaNegotiatedEvent();
|
|
4443
4567
|
this.isRoapInProgress = false;
|
|
4444
4568
|
this.processNextQueuedMediaUpdate();
|
|
4445
4569
|
});
|
|
4446
4570
|
|
|
4447
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4571
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_FAILURE, this.handleRoapFailure);
|
|
4448
4572
|
|
|
4449
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4450
|
-
const LOG_HEADER =
|
|
4573
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, (event) => {
|
|
4574
|
+
const LOG_HEADER = `Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND --> correlationId=${this.correlationId}`;
|
|
4451
4575
|
|
|
4452
4576
|
switch (event.roapMessage.messageType) {
|
|
4453
4577
|
case 'OK':
|
|
@@ -4463,9 +4587,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4463
4587
|
correlationId: this.correlationId,
|
|
4464
4588
|
}),
|
|
4465
4589
|
{
|
|
4466
|
-
|
|
4467
|
-
success: `${LOG_HEADER} Successfully send roap OK`,
|
|
4468
|
-
failure: `${LOG_HEADER} Error joining the call on send roap OK, `,
|
|
4590
|
+
logText: `${LOG_HEADER} Roap OK`,
|
|
4469
4591
|
}
|
|
4470
4592
|
);
|
|
4471
4593
|
break;
|
|
@@ -4485,9 +4607,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4485
4607
|
reconnect: this.reconnectionManager.isReconnectInProgress(),
|
|
4486
4608
|
}),
|
|
4487
4609
|
{
|
|
4488
|
-
|
|
4489
|
-
success: `${LOG_HEADER} Successfully send roap offer`,
|
|
4490
|
-
failure: `${LOG_HEADER} Error joining the call on send roap offer, `,
|
|
4610
|
+
logText: `${LOG_HEADER} Roap Offer`,
|
|
4491
4611
|
}
|
|
4492
4612
|
);
|
|
4493
4613
|
break;
|
|
@@ -4506,9 +4626,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4506
4626
|
correlationId: this.correlationId,
|
|
4507
4627
|
}),
|
|
4508
4628
|
{
|
|
4509
|
-
|
|
4510
|
-
success: `${LOG_HEADER} Successfully send roap answer`,
|
|
4511
|
-
failure: `${LOG_HEADER} Error joining the call on send roap answer, `,
|
|
4629
|
+
logText: `${LOG_HEADER} Roap Answer`,
|
|
4512
4630
|
}
|
|
4513
4631
|
).catch((error) => {
|
|
4514
4632
|
const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
|
|
@@ -4528,8 +4646,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4528
4646
|
|
|
4529
4647
|
case 'ERROR':
|
|
4530
4648
|
if (
|
|
4531
|
-
event.roapMessage.errorType ===
|
|
4532
|
-
event.roapMessage.errorType ===
|
|
4649
|
+
event.roapMessage.errorType === ErrorType.CONFLICT ||
|
|
4650
|
+
event.roapMessage.errorType === ErrorType.DOUBLECONFLICT
|
|
4533
4651
|
) {
|
|
4534
4652
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
|
|
4535
4653
|
correlation_id: this.correlationId,
|
|
@@ -4545,9 +4663,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4545
4663
|
correlationId: this.correlationId,
|
|
4546
4664
|
}),
|
|
4547
4665
|
{
|
|
4548
|
-
|
|
4549
|
-
success: `${LOG_HEADER} Successfully send roap error`,
|
|
4550
|
-
failure: `${LOG_HEADER} Failed to send roap error, `,
|
|
4666
|
+
logText: `${LOG_HEADER} Roap Error (${event.roapMessage.errorType})`,
|
|
4551
4667
|
}
|
|
4552
4668
|
);
|
|
4553
4669
|
break;
|
|
@@ -4561,7 +4677,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4561
4677
|
});
|
|
4562
4678
|
|
|
4563
4679
|
// eslint-disable-next-line no-param-reassign
|
|
4564
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4680
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.REMOTE_TRACK_ADDED, (event) => {
|
|
4565
4681
|
LoggerProxy.logger.log(
|
|
4566
4682
|
`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(
|
|
4567
4683
|
event
|
|
@@ -4574,15 +4690,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4574
4690
|
let eventType;
|
|
4575
4691
|
|
|
4576
4692
|
switch (event.type) {
|
|
4577
|
-
case
|
|
4693
|
+
case RemoteTrackType.AUDIO:
|
|
4578
4694
|
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
4579
4695
|
this.mediaProperties.setRemoteAudioTrack(event.track);
|
|
4580
4696
|
break;
|
|
4581
|
-
case
|
|
4697
|
+
case RemoteTrackType.VIDEO:
|
|
4582
4698
|
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
4583
4699
|
this.mediaProperties.setRemoteVideoTrack(event.track);
|
|
4584
4700
|
break;
|
|
4585
|
-
case
|
|
4701
|
+
case RemoteTrackType.SCREENSHARE_VIDEO:
|
|
4586
4702
|
if (event.track) {
|
|
4587
4703
|
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
4588
4704
|
this.mediaProperties.setRemoteShare(event.track);
|
|
@@ -4595,10 +4711,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4595
4711
|
}
|
|
4596
4712
|
}
|
|
4597
4713
|
|
|
4598
|
-
// start stats here the stats are coming null if you dont receive streams
|
|
4599
|
-
|
|
4600
|
-
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4601
|
-
|
|
4602
4714
|
if (eventType && mediaTrack) {
|
|
4603
4715
|
Trigger.trigger(
|
|
4604
4716
|
this,
|
|
@@ -4615,7 +4727,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4615
4727
|
}
|
|
4616
4728
|
});
|
|
4617
4729
|
|
|
4618
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4730
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
4619
4731
|
const connectionFailed = () => {
|
|
4620
4732
|
// we know the media connection failed and browser will not attempt to recover it any more
|
|
4621
4733
|
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
@@ -4645,13 +4757,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4645
4757
|
};
|
|
4646
4758
|
|
|
4647
4759
|
LoggerProxy.logger.info(
|
|
4648
|
-
`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`
|
|
4760
|
+
`Meeting:index#setupMediaConnectionListeners --> correlationId=${this.correlationId} connection state changed to ${event.state}`
|
|
4649
4761
|
);
|
|
4650
4762
|
switch (event.state) {
|
|
4651
|
-
case
|
|
4763
|
+
case ConnectionState.Connecting:
|
|
4652
4764
|
Metrics.postEvent({event: eventType.ICE_START, meeting: this});
|
|
4653
4765
|
break;
|
|
4654
|
-
case
|
|
4766
|
+
case ConnectionState.Connected:
|
|
4655
4767
|
Metrics.postEvent({event: eventType.ICE_END, meeting: this});
|
|
4656
4768
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
4657
4769
|
correlation_id: this.correlationId,
|
|
@@ -4659,8 +4771,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4771
|
});
|
|
4660
4772
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
4661
4773
|
this.reconnectionManager.iceReconnected();
|
|
4774
|
+
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4662
4775
|
break;
|
|
4663
|
-
case
|
|
4776
|
+
case ConnectionState.Disconnected:
|
|
4664
4777
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
4665
4778
|
this.reconnectionManager.waitForIceReconnect().catch(() => {
|
|
4666
4779
|
LoggerProxy.logger.info(
|
|
@@ -4670,7 +4783,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4670
4783
|
connectionFailed();
|
|
4671
4784
|
});
|
|
4672
4785
|
break;
|
|
4673
|
-
case
|
|
4786
|
+
case ConnectionState.Failed:
|
|
4674
4787
|
connectionFailed();
|
|
4675
4788
|
break;
|
|
4676
4789
|
default:
|
|
@@ -4678,7 +4791,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4678
4791
|
}
|
|
4679
4792
|
});
|
|
4680
4793
|
|
|
4681
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4794
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (msg) => {
|
|
4682
4795
|
Trigger.trigger(
|
|
4683
4796
|
this,
|
|
4684
4797
|
{
|
|
@@ -4689,6 +4802,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4689
4802
|
{
|
|
4690
4803
|
seqNum: msg.seqNum,
|
|
4691
4804
|
memberIds: msg.csis
|
|
4805
|
+
// @ts-ignore
|
|
4692
4806
|
.map((csi) => this.members.findMemberByCsi(csi)?.id)
|
|
4693
4807
|
.filter((item) => item !== undefined),
|
|
4694
4808
|
}
|
|
@@ -4696,8 +4810,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4696
4810
|
});
|
|
4697
4811
|
|
|
4698
4812
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4699
|
-
|
|
4700
|
-
(numTotalSources, numLiveSources) => {
|
|
4813
|
+
Event.VIDEO_SOURCES_COUNT_CHANGED,
|
|
4814
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4701
4815
|
Trigger.trigger(
|
|
4702
4816
|
this,
|
|
4703
4817
|
{
|
|
@@ -4708,14 +4822,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4708
4822
|
{
|
|
4709
4823
|
numTotalSources,
|
|
4710
4824
|
numLiveSources,
|
|
4825
|
+
mediaContent,
|
|
4711
4826
|
}
|
|
4712
4827
|
);
|
|
4828
|
+
|
|
4829
|
+
if (mediaContent === MediaContent.Main) {
|
|
4830
|
+
this.mediaRequestManagers.video.setNumCurrentSources(numTotalSources, numLiveSources);
|
|
4831
|
+
}
|
|
4713
4832
|
}
|
|
4714
4833
|
);
|
|
4715
4834
|
|
|
4716
4835
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4717
|
-
|
|
4718
|
-
(numTotalSources, numLiveSources) => {
|
|
4836
|
+
Event.AUDIO_SOURCES_COUNT_CHANGED,
|
|
4837
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4719
4838
|
Trigger.trigger(
|
|
4720
4839
|
this,
|
|
4721
4840
|
{
|
|
@@ -4726,6 +4845,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4726
4845
|
{
|
|
4727
4846
|
numTotalSources,
|
|
4728
4847
|
numLiveSources,
|
|
4848
|
+
mediaContent,
|
|
4729
4849
|
}
|
|
4730
4850
|
);
|
|
4731
4851
|
}
|
|
@@ -4744,6 +4864,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4744
4864
|
// Add ip address info if geo hint is present
|
|
4745
4865
|
// @ts-ignore fix type
|
|
4746
4866
|
options.data.intervalMetadata.peerReflexiveIP =
|
|
4867
|
+
// @ts-ignore
|
|
4747
4868
|
this.webex.meetings.geoHintInfo?.clientAddress ||
|
|
4748
4869
|
options.data.intervalMetadata.peerReflexiveIP ||
|
|
4749
4870
|
MQA_STATS.DEFAULT_IP;
|
|
@@ -4813,7 +4934,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4813
4934
|
return `MC-${this.id.substring(0, 4)}`;
|
|
4814
4935
|
}
|
|
4815
4936
|
|
|
4816
|
-
|
|
4937
|
+
/**
|
|
4938
|
+
* Creates a webrtc media connection and publishes tracks to it
|
|
4939
|
+
*
|
|
4940
|
+
* @param {Object} turnServerInfo TURN server information
|
|
4941
|
+
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
4942
|
+
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
4943
|
+
*/
|
|
4944
|
+
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
4945
|
+
// create the actual media connection
|
|
4817
4946
|
const mc = Media.createMediaConnection(this.isMultistream, this.getMediaConnectionDebugId(), {
|
|
4818
4947
|
mediaProperties: this.mediaProperties,
|
|
4819
4948
|
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
@@ -4822,11 +4951,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4822
4951
|
// @ts-ignore - config coming from registerPlugin
|
|
4823
4952
|
enableExtmap: this.config.enableExtmap,
|
|
4824
4953
|
turnServerInfo,
|
|
4954
|
+
bundlePolicy,
|
|
4825
4955
|
});
|
|
4826
4956
|
|
|
4827
4957
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
4828
4958
|
this.setupMediaConnectionListeners();
|
|
4829
4959
|
|
|
4960
|
+
// publish the tracks
|
|
4961
|
+
if (this.mediaProperties.audioTrack) {
|
|
4962
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
4963
|
+
}
|
|
4964
|
+
if (this.mediaProperties.videoTrack) {
|
|
4965
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
4966
|
+
}
|
|
4967
|
+
if (this.mediaProperties.shareTrack) {
|
|
4968
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4830
4971
|
return mc;
|
|
4831
4972
|
}
|
|
4832
4973
|
|
|
@@ -4854,23 +4995,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4854
4995
|
}
|
|
4855
4996
|
|
|
4856
4997
|
/**
|
|
4857
|
-
*
|
|
4858
|
-
*
|
|
4859
|
-
* @param {
|
|
4860
|
-
* @param {MediaDirection} options.mediaSettings pass media options
|
|
4861
|
-
* @param {MediaStream} options.localStream
|
|
4862
|
-
* @param {MediaStream} options.localShare
|
|
4863
|
-
* @param {RemoteMediaManagerConfig} options.remoteMediaManagerConfig only applies if multistream is enabled
|
|
4998
|
+
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
4999
|
+
*
|
|
5000
|
+
* @param {AddMediaOptions} options
|
|
4864
5001
|
* @returns {Promise}
|
|
4865
5002
|
* @public
|
|
4866
5003
|
* @memberof Meeting
|
|
4867
5004
|
*/
|
|
4868
|
-
addMedia(options:
|
|
5005
|
+
addMedia(options: AddMediaOptions = {}) {
|
|
4869
5006
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
4870
5007
|
|
|
4871
5008
|
let turnDiscoverySkippedReason;
|
|
4872
5009
|
let turnServerUsed = false;
|
|
4873
5010
|
|
|
5011
|
+
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
5012
|
+
|
|
4874
5013
|
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
4875
5014
|
return Promise.reject(new MeetingNotActiveError());
|
|
4876
5015
|
}
|
|
@@ -4884,9 +5023,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4884
5023
|
return Promise.reject(new UserInLobbyError());
|
|
4885
5024
|
}
|
|
4886
5025
|
|
|
4887
|
-
const {
|
|
4888
|
-
|
|
4889
|
-
|
|
5026
|
+
const {
|
|
5027
|
+
localTracks,
|
|
5028
|
+
audioEnabled = true,
|
|
5029
|
+
videoEnabled = true,
|
|
5030
|
+
receiveShare = true,
|
|
5031
|
+
remoteMediaManagerConfig,
|
|
5032
|
+
bundlePolicy,
|
|
5033
|
+
} = options;
|
|
4890
5034
|
|
|
4891
5035
|
Metrics.postEvent({
|
|
4892
5036
|
event: eventType.MEDIA_CAPABILITIES,
|
|
@@ -4911,17 +5055,61 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4911
5055
|
},
|
|
4912
5056
|
});
|
|
4913
5057
|
|
|
4914
|
-
|
|
5058
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any tracks are published
|
|
5059
|
+
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5060
|
+
this.mediaProperties.setMediaDirection({
|
|
5061
|
+
sendAudio: audioEnabled,
|
|
5062
|
+
sendVideo: videoEnabled,
|
|
5063
|
+
sendShare: false,
|
|
5064
|
+
receiveAudio: audioEnabled,
|
|
5065
|
+
receiveVideo: videoEnabled,
|
|
5066
|
+
receiveShare,
|
|
5067
|
+
});
|
|
5068
|
+
|
|
5069
|
+
this.locusMediaRequest = new LocusMediaRequest(
|
|
5070
|
+
{
|
|
5071
|
+
correlationId: this.correlationId,
|
|
5072
|
+
device: {
|
|
5073
|
+
url: this.deviceUrl,
|
|
5074
|
+
// @ts-ignore
|
|
5075
|
+
deviceType: this.config.deviceType,
|
|
5076
|
+
},
|
|
5077
|
+
preferTranscoding: !this.isMultistream,
|
|
5078
|
+
},
|
|
5079
|
+
{
|
|
5080
|
+
// @ts-ignore
|
|
5081
|
+
parent: this.webex,
|
|
5082
|
+
}
|
|
5083
|
+
);
|
|
5084
|
+
|
|
5085
|
+
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5086
|
+
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5087
|
+
|
|
5088
|
+
this.annotationInfo = localTracks?.annotationInfo;
|
|
5089
|
+
|
|
5090
|
+
const promises = [];
|
|
5091
|
+
|
|
5092
|
+
// setup all the references to local tracks in this.mediaProperties before creating media connection
|
|
5093
|
+
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5094
|
+
if (localTracks?.microphone) {
|
|
5095
|
+
promises.push(this.setLocalAudioTrack(localTracks.microphone));
|
|
5096
|
+
}
|
|
5097
|
+
if (localTracks?.camera) {
|
|
5098
|
+
promises.push(this.setLocalVideoTrack(localTracks.camera));
|
|
5099
|
+
}
|
|
5100
|
+
if (localTracks?.screenShare?.video) {
|
|
5101
|
+
promises.push(this.setLocalShareTrack(localTracks.screenShare.video));
|
|
5102
|
+
}
|
|
5103
|
+
|
|
5104
|
+
return Promise.all(promises)
|
|
4915
5105
|
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
4916
|
-
.then((turnDiscoveryObject) => {
|
|
5106
|
+
.then(async (turnDiscoveryObject) => {
|
|
4917
5107
|
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
4918
5108
|
turnServerUsed = !turnDiscoverySkippedReason;
|
|
4919
5109
|
|
|
4920
5110
|
const {turnServerInfo} = turnDiscoveryObject;
|
|
4921
5111
|
|
|
4922
|
-
this.
|
|
4923
|
-
|
|
4924
|
-
const mc = this.createMediaConnection(turnServerInfo);
|
|
5112
|
+
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
4925
5113
|
|
|
4926
5114
|
if (this.isMultistream) {
|
|
4927
5115
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -4946,18 +5134,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4946
5134
|
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
4947
5135
|
);
|
|
4948
5136
|
|
|
4949
|
-
|
|
5137
|
+
await this.remoteMediaManager.start();
|
|
4950
5138
|
}
|
|
4951
5139
|
|
|
4952
|
-
|
|
5140
|
+
await mc.initiateOffer();
|
|
4953
5141
|
})
|
|
4954
5142
|
.then(() => {
|
|
4955
5143
|
this.setMercuryListener();
|
|
4956
5144
|
})
|
|
4957
|
-
.then(
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
5145
|
+
.then(
|
|
5146
|
+
() =>
|
|
5147
|
+
getDevices()
|
|
5148
|
+
.then((devices) => {
|
|
5149
|
+
MeetingUtil.handleDeviceLogging(devices);
|
|
5150
|
+
})
|
|
5151
|
+
.catch(() => {}) // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
4961
5152
|
)
|
|
4962
5153
|
.then(() => {
|
|
4963
5154
|
this.handleMediaLogging(this.mediaProperties);
|
|
@@ -4967,8 +5158,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4967
5158
|
if (this.config.stats.enableStatsAnalyzer) {
|
|
4968
5159
|
// @ts-ignore - config coming from registerPlugin
|
|
4969
5160
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
4970
|
-
|
|
4971
|
-
|
|
5161
|
+
this.statsAnalyzer = new StatsAnalyzer(
|
|
5162
|
+
// @ts-ignore - config coming from registerPlugin
|
|
5163
|
+
this.config.stats,
|
|
5164
|
+
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5165
|
+
this.networkQualityMonitor
|
|
5166
|
+
);
|
|
4972
5167
|
this.setupStatsAnalyzerEventHandlers();
|
|
4973
5168
|
this.networkQualityMonitor.on(
|
|
4974
5169
|
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
@@ -4991,7 +5186,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4991
5186
|
|
|
4992
5187
|
// eslint-disable-next-line func-names
|
|
4993
5188
|
// eslint-disable-next-line prefer-arrow-callback
|
|
4994
|
-
if (this.type === _CALL_) {
|
|
5189
|
+
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
4995
5190
|
resolve();
|
|
4996
5191
|
}
|
|
4997
5192
|
const joiningTimer = setInterval(() => {
|
|
@@ -5010,21 +5205,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5010
5205
|
)
|
|
5011
5206
|
.then(() =>
|
|
5012
5207
|
this.mediaProperties.waitForMediaConnectionConnected().catch(() => {
|
|
5013
|
-
throw
|
|
5208
|
+
throw new Error(
|
|
5209
|
+
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5210
|
+
);
|
|
5014
5211
|
})
|
|
5015
5212
|
)
|
|
5016
5213
|
.then(() => {
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
5020
|
-
return this.requestScreenShareFloor();
|
|
5021
|
-
}
|
|
5022
|
-
|
|
5023
|
-
// When the self state changes to JOINED then request the floor
|
|
5024
|
-
this.floorGrantPending = true;
|
|
5214
|
+
if (localTracks?.screenShare?.video) {
|
|
5215
|
+
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
|
|
5025
5216
|
}
|
|
5026
|
-
|
|
5027
|
-
return {};
|
|
5028
5217
|
})
|
|
5029
5218
|
.then(() => this.mediaProperties.getCurrentConnectionType())
|
|
5030
5219
|
.then((connectionType) => {
|
|
@@ -5032,9 +5221,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5032
5221
|
correlation_id: this.correlationId,
|
|
5033
5222
|
locus_id: this.locusUrl.split('/').pop(),
|
|
5034
5223
|
connectionType,
|
|
5224
|
+
isMultistream: this.isMultistream,
|
|
5035
5225
|
});
|
|
5036
5226
|
})
|
|
5037
5227
|
.catch((error) => {
|
|
5228
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5229
|
+
correlation_id: this.correlationId,
|
|
5230
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5231
|
+
reason: error.message,
|
|
5232
|
+
stack: error.stack,
|
|
5233
|
+
code: error.code,
|
|
5234
|
+
turnDiscoverySkippedReason,
|
|
5235
|
+
turnServerUsed,
|
|
5236
|
+
isMultistream: this.isMultistream,
|
|
5237
|
+
signalingState:
|
|
5238
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5239
|
+
?.signalingState ||
|
|
5240
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
5241
|
+
'unknown',
|
|
5242
|
+
connectionState:
|
|
5243
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5244
|
+
?.connectionState ||
|
|
5245
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
5246
|
+
'unknown',
|
|
5247
|
+
iceConnectionState:
|
|
5248
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5249
|
+
?.iceConnectionState ||
|
|
5250
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
5251
|
+
'unknown',
|
|
5252
|
+
});
|
|
5253
|
+
|
|
5038
5254
|
// Clean up stats analyzer, peer connection, and turn off listeners
|
|
5039
5255
|
const stopStatsAnalyzer = this.statsAnalyzer
|
|
5040
5256
|
? this.statsAnalyzer.stopAnalyzer()
|
|
@@ -5053,16 +5269,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5053
5269
|
error
|
|
5054
5270
|
);
|
|
5055
5271
|
|
|
5056
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5057
|
-
correlation_id: this.correlationId,
|
|
5058
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5059
|
-
reason: error.message,
|
|
5060
|
-
stack: error.stack,
|
|
5061
|
-
code: error.code,
|
|
5062
|
-
turnDiscoverySkippedReason,
|
|
5063
|
-
turnServerUsed,
|
|
5064
|
-
});
|
|
5065
|
-
|
|
5066
5272
|
// Upload logs on error while adding media
|
|
5067
5273
|
Trigger.trigger(
|
|
5068
5274
|
this,
|
|
@@ -5074,7 +5280,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5074
5280
|
this
|
|
5075
5281
|
);
|
|
5076
5282
|
|
|
5077
|
-
if (error instanceof
|
|
5283
|
+
if (error instanceof Errors.SdpError) {
|
|
5078
5284
|
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
5079
5285
|
}
|
|
5080
5286
|
|
|
@@ -5102,7 +5308,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5102
5308
|
* @private
|
|
5103
5309
|
* @memberof Meeting
|
|
5104
5310
|
*/
|
|
5105
|
-
private enqueueMediaUpdate(mediaUpdateType: string, options:
|
|
5311
|
+
private enqueueMediaUpdate(mediaUpdateType: string, options: any = {}): Promise<void> {
|
|
5312
|
+
const canUpdateMediaNow = this.canUpdateMedia();
|
|
5313
|
+
|
|
5106
5314
|
return new Promise((resolve, reject) => {
|
|
5107
5315
|
const queueItem = {
|
|
5108
5316
|
pendingPromiseResolve: resolve,
|
|
@@ -5115,6 +5323,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5115
5323
|
`Meeting:index#enqueueMediaUpdate --> enqueuing media update type=${mediaUpdateType}`
|
|
5116
5324
|
);
|
|
5117
5325
|
this.queuedMediaUpdates.push(queueItem);
|
|
5326
|
+
|
|
5327
|
+
if (canUpdateMediaNow) {
|
|
5328
|
+
this.processNextQueuedMediaUpdate();
|
|
5329
|
+
}
|
|
5118
5330
|
});
|
|
5119
5331
|
}
|
|
5120
5332
|
|
|
@@ -5154,18 +5366,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5154
5366
|
LoggerProxy.logger.log(
|
|
5155
5367
|
`Meeting:index#processNextQueuedMediaUpdate --> performing delayed media update type=${mediaUpdateType}`
|
|
5156
5368
|
);
|
|
5369
|
+
let mediaUpdate = Promise.resolve();
|
|
5370
|
+
|
|
5157
5371
|
switch (mediaUpdateType) {
|
|
5158
|
-
case MEDIA_UPDATE_TYPE.
|
|
5159
|
-
this.
|
|
5160
|
-
break;
|
|
5161
|
-
case MEDIA_UPDATE_TYPE.AUDIO:
|
|
5162
|
-
this.updateAudio(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5372
|
+
case MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION:
|
|
5373
|
+
mediaUpdate = this.updateTranscodedMediaConnection();
|
|
5163
5374
|
break;
|
|
5164
|
-
case MEDIA_UPDATE_TYPE.
|
|
5165
|
-
this.
|
|
5375
|
+
case MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST:
|
|
5376
|
+
mediaUpdate = this.requestScreenShareFloor();
|
|
5166
5377
|
break;
|
|
5167
|
-
case MEDIA_UPDATE_TYPE.
|
|
5168
|
-
this.
|
|
5378
|
+
case MEDIA_UPDATE_TYPE.UPDATE_MEDIA:
|
|
5379
|
+
mediaUpdate = this.updateMedia(options);
|
|
5169
5380
|
break;
|
|
5170
5381
|
default:
|
|
5171
5382
|
LoggerProxy.logger.error(
|
|
@@ -5173,358 +5384,113 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5173
5384
|
);
|
|
5174
5385
|
break;
|
|
5175
5386
|
}
|
|
5387
|
+
|
|
5388
|
+
mediaUpdate
|
|
5389
|
+
.then(pendingPromiseResolve, pendingPromiseReject)
|
|
5390
|
+
.then(() => this.processNextQueuedMediaUpdate());
|
|
5176
5391
|
}
|
|
5177
5392
|
};
|
|
5178
5393
|
|
|
5179
5394
|
/**
|
|
5180
|
-
*
|
|
5181
|
-
*
|
|
5395
|
+
* Updates the media connection - it allows to enable/disable all audio/video/share in the meeting.
|
|
5396
|
+
* This does not affect the published tracks, so for example if a microphone track is published and
|
|
5397
|
+
* updateMedia({audioEnabled: false}) is called, the audio will not be sent or received anymore,
|
|
5398
|
+
* but the track's "published" state is not changed and when updateMedia({audioEnabled: true}) is called,
|
|
5399
|
+
* the sending of the audio from the same track will resume.
|
|
5400
|
+
*
|
|
5182
5401
|
* @param {Object} options
|
|
5183
|
-
* @param {
|
|
5184
|
-
* @param {
|
|
5185
|
-
* @param {
|
|
5402
|
+
* @param {boolean} options.audioEnabled [optional] enables/disables receiving and sending of main audio in the meeting
|
|
5403
|
+
* @param {boolean} options.videoEnabled [optional] enables/disables receiving and sending of main video in the meeting
|
|
5404
|
+
* @param {boolean} options.shareEnabled [optional] enables/disables receiving and sending of screen share in the meeting
|
|
5186
5405
|
* @returns {Promise}
|
|
5187
5406
|
* @public
|
|
5188
5407
|
* @memberof Meeting
|
|
5189
5408
|
*/
|
|
5190
|
-
public updateMedia(
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
) {
|
|
5197
|
-
const LOG_HEADER = 'Meeting:index#updateMedia -->';
|
|
5409
|
+
public async updateMedia(options: {
|
|
5410
|
+
audioEnabled?: boolean;
|
|
5411
|
+
videoEnabled?: boolean;
|
|
5412
|
+
receiveShare?: boolean;
|
|
5413
|
+
}) {
|
|
5414
|
+
this.checkMediaConnection();
|
|
5198
5415
|
|
|
5199
|
-
|
|
5200
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.ALL, options);
|
|
5201
|
-
}
|
|
5202
|
-
const {localStream, localShare, mediaSettings} = options;
|
|
5416
|
+
const {audioEnabled, videoEnabled, receiveShare} = options;
|
|
5203
5417
|
|
|
5204
|
-
|
|
5418
|
+
LoggerProxy.logger.log(
|
|
5419
|
+
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
5420
|
+
);
|
|
5205
5421
|
|
|
5206
|
-
if (!this.
|
|
5207
|
-
return
|
|
5422
|
+
if (!this.canUpdateMedia()) {
|
|
5423
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
5208
5424
|
}
|
|
5209
5425
|
|
|
5210
|
-
|
|
5211
|
-
|
|
5212
|
-
|
|
5213
|
-
|
|
5214
|
-
|
|
5215
|
-
|
|
5216
|
-
audio: this.mediaProperties.mediaDirection.sendAudio
|
|
5217
|
-
? this.mediaProperties.audioTrack
|
|
5218
|
-
: null,
|
|
5219
|
-
video: this.mediaProperties.mediaDirection.sendVideo
|
|
5220
|
-
? this.mediaProperties.videoTrack
|
|
5221
|
-
: null,
|
|
5222
|
-
screenShareVideo: this.mediaProperties.mediaDirection.sendShare
|
|
5223
|
-
? this.mediaProperties.shareTrack
|
|
5224
|
-
: null,
|
|
5225
|
-
},
|
|
5226
|
-
receive: {
|
|
5227
|
-
audio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5228
|
-
video: this.mediaProperties.mediaDirection.receiveVideo,
|
|
5229
|
-
screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
|
|
5230
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5231
|
-
},
|
|
5232
|
-
})
|
|
5233
|
-
.then(() => {
|
|
5234
|
-
LoggerProxy.logger.info(
|
|
5235
|
-
`${LOG_HEADER} webrtcMediaConnection.updateSendReceiveOptions done`
|
|
5236
|
-
);
|
|
5237
|
-
})
|
|
5238
|
-
.catch((error) => {
|
|
5239
|
-
LoggerProxy.logger.error(`${LOG_HEADER} Error updatedMedia, `, error);
|
|
5240
|
-
|
|
5241
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
5242
|
-
correlation_id: this.correlationId,
|
|
5243
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5244
|
-
reason: error.message,
|
|
5245
|
-
stack: error.stack,
|
|
5246
|
-
});
|
|
5247
|
-
|
|
5248
|
-
throw error;
|
|
5249
|
-
})
|
|
5250
|
-
// todo: the following code used to be called always after sending the roap message with the new SDP
|
|
5251
|
-
// now it's called independently from the roap message (so might be before it), check if that's OK
|
|
5252
|
-
// if not, ensure it's called after (now it's called after roap message is sent out, but we're not
|
|
5253
|
-
// waiting for sendRoapMediaRequest() to be resolved)
|
|
5254
|
-
.then(() => this.checkForStopShare(mediaSettings.sendShare, previousSendShareStatus))
|
|
5255
|
-
.then((startShare) => {
|
|
5256
|
-
// This is a special case if we do an /floor grant followed by /media
|
|
5257
|
-
// we actually get a OFFER from the server and a GLAR condition happens
|
|
5258
|
-
if (startShare) {
|
|
5259
|
-
// We are assuming that the clients are connected when doing an update
|
|
5260
|
-
return this.requestScreenShareFloor();
|
|
5261
|
-
}
|
|
5262
|
-
|
|
5263
|
-
return Promise.resolve();
|
|
5264
|
-
})
|
|
5265
|
-
);
|
|
5266
|
-
}
|
|
5426
|
+
if (this.isMultistream) {
|
|
5427
|
+
if (videoEnabled !== undefined) {
|
|
5428
|
+
throw new Error(
|
|
5429
|
+
'enabling/disabling video in a meeting is not supported for multistream, it can only be done upfront when calling addMedia()'
|
|
5430
|
+
);
|
|
5431
|
+
}
|
|
5267
5432
|
|
|
5268
|
-
|
|
5269
|
-
|
|
5270
|
-
|
|
5271
|
-
|
|
5272
|
-
|
|
5273
|
-
* @param {Object} options
|
|
5274
|
-
* @param {boolean} options.sendAudio
|
|
5275
|
-
* @param {boolean} options.receiveAudio
|
|
5276
|
-
* @param {MediaStream} options.stream Stream that contains the audio track to update
|
|
5277
|
-
* @returns {Promise}
|
|
5278
|
-
* @public
|
|
5279
|
-
* @memberof Meeting
|
|
5280
|
-
*/
|
|
5281
|
-
public async updateAudio(options: {
|
|
5282
|
-
sendAudio: boolean;
|
|
5283
|
-
receiveAudio: boolean;
|
|
5284
|
-
stream: MediaStream;
|
|
5285
|
-
}) {
|
|
5286
|
-
if (!this.canUpdateMedia()) {
|
|
5287
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.AUDIO, options);
|
|
5433
|
+
if (receiveShare !== undefined) {
|
|
5434
|
+
throw new Error(
|
|
5435
|
+
'toggling receiveShare in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5436
|
+
);
|
|
5437
|
+
}
|
|
5288
5438
|
}
|
|
5289
|
-
const {sendAudio, receiveAudio, stream} = options;
|
|
5290
|
-
let track = MeetingUtil.getTrack(stream).audioTrack;
|
|
5291
5439
|
|
|
5292
|
-
if (
|
|
5293
|
-
|
|
5440
|
+
if (audioEnabled !== undefined) {
|
|
5441
|
+
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5442
|
+
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5443
|
+
this.audio.enable(this, audioEnabled);
|
|
5294
5444
|
}
|
|
5295
5445
|
|
|
5296
|
-
if (
|
|
5297
|
-
|
|
5446
|
+
if (videoEnabled !== undefined) {
|
|
5447
|
+
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5448
|
+
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5449
|
+
this.video.enable(this, videoEnabled);
|
|
5298
5450
|
}
|
|
5299
5451
|
|
|
5300
|
-
if (
|
|
5301
|
-
|
|
5452
|
+
if (receiveShare !== undefined) {
|
|
5453
|
+
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
5454
|
+
}
|
|
5302
5455
|
|
|
5303
|
-
|
|
5304
|
-
|
|
5305
|
-
|
|
5306
|
-
(bnrEnabled === BNR_STATUS.ENABLED || bnrEnabled === BNR_STATUS.SHOULD_ENABLE)
|
|
5307
|
-
) {
|
|
5308
|
-
LoggerProxy.logger.info('Meeting:index#updateAudio. Calling WebRTC enable bnr method');
|
|
5309
|
-
track = await this.internal_enableBNR(track);
|
|
5310
|
-
LoggerProxy.logger.info('Meeting:index#updateAudio. WebRTC enable bnr request completed');
|
|
5456
|
+
if (this.isMultistream) {
|
|
5457
|
+
if (audioEnabled !== undefined) {
|
|
5458
|
+
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5311
5459
|
}
|
|
5460
|
+
} else {
|
|
5461
|
+
await this.updateTranscodedMediaConnection();
|
|
5312
5462
|
}
|
|
5313
5463
|
|
|
5314
|
-
return
|
|
5315
|
-
.then(() =>
|
|
5316
|
-
this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
|
|
5317
|
-
send: {audio: track},
|
|
5318
|
-
receive: {
|
|
5319
|
-
audio: options.receiveAudio,
|
|
5320
|
-
video: this.mediaProperties.mediaDirection.receiveVideo,
|
|
5321
|
-
screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
|
|
5322
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5323
|
-
},
|
|
5324
|
-
})
|
|
5325
|
-
)
|
|
5326
|
-
.then(() => {
|
|
5327
|
-
this.setLocalAudioTrack(track);
|
|
5328
|
-
// todo: maybe this.mediaProperties.mediaDirection could be removed? it's duplicating stuff from webrtcMediaConnection
|
|
5329
|
-
this.mediaProperties.mediaDirection.sendAudio = sendAudio;
|
|
5330
|
-
this.mediaProperties.mediaDirection.receiveAudio = receiveAudio;
|
|
5331
|
-
|
|
5332
|
-
// audio state could be undefined if you have not sent audio before
|
|
5333
|
-
this.audio =
|
|
5334
|
-
this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
|
|
5335
|
-
});
|
|
5464
|
+
return undefined;
|
|
5336
5465
|
}
|
|
5337
5466
|
|
|
5338
5467
|
/**
|
|
5339
|
-
*
|
|
5340
|
-
*
|
|
5341
|
-
*
|
|
5342
|
-
*
|
|
5343
|
-
* @param {Object} options
|
|
5344
|
-
* @param {boolean} options.sendVideo
|
|
5345
|
-
* @param {boolean} options.receiveVideo
|
|
5346
|
-
* @param {MediaStream} options.stream Stream that contains the video track to update
|
|
5347
|
-
* @returns {Promise}
|
|
5468
|
+
* Acknowledge the meeting, outgoing or incoming
|
|
5469
|
+
* @param {String} type
|
|
5470
|
+
* @returns {Promise} resolve {message, ringing, response}
|
|
5348
5471
|
* @public
|
|
5349
5472
|
* @memberof Meeting
|
|
5350
5473
|
*/
|
|
5351
|
-
public
|
|
5352
|
-
if (!
|
|
5353
|
-
return
|
|
5354
|
-
}
|
|
5355
|
-
const {sendVideo, receiveVideo, stream} = options;
|
|
5356
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
5357
|
-
|
|
5358
|
-
if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
|
|
5359
|
-
return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
|
|
5474
|
+
public acknowledge(type: string) {
|
|
5475
|
+
if (!type) {
|
|
5476
|
+
return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
|
|
5360
5477
|
}
|
|
5478
|
+
if (type === _INCOMING_) {
|
|
5479
|
+
return this.meetingRequest
|
|
5480
|
+
.acknowledgeMeeting({
|
|
5481
|
+
locusUrl: this.locusUrl,
|
|
5482
|
+
deviceUrl: this.deviceUrl,
|
|
5483
|
+
correlationId: this.correlationId,
|
|
5484
|
+
})
|
|
5485
|
+
.then((response) => Promise.resolve(response))
|
|
5486
|
+
.then((response) => {
|
|
5487
|
+
this.meetingFiniteStateMachine.ring(type);
|
|
5488
|
+
Metrics.postEvent({event: eventType.ALERT_DISPLAYED, meeting: this});
|
|
5361
5489
|
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
return MeetingUtil.validateOptions({sendVideo, localStream: stream})
|
|
5367
|
-
.then(() =>
|
|
5368
|
-
this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
|
|
5369
|
-
send: {video: track},
|
|
5370
|
-
receive: {
|
|
5371
|
-
audio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5372
|
-
video: options.receiveVideo,
|
|
5373
|
-
screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
|
|
5374
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5375
|
-
},
|
|
5376
|
-
})
|
|
5377
|
-
)
|
|
5378
|
-
.then(() => {
|
|
5379
|
-
this.setLocalVideoTrack(track);
|
|
5380
|
-
this.mediaProperties.mediaDirection.sendVideo = sendVideo;
|
|
5381
|
-
this.mediaProperties.mediaDirection.receiveVideo = receiveVideo;
|
|
5382
|
-
|
|
5383
|
-
// video state could be undefined if you have not sent video before
|
|
5384
|
-
this.video =
|
|
5385
|
-
this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
|
|
5386
|
-
});
|
|
5387
|
-
}
|
|
5388
|
-
|
|
5389
|
-
/**
|
|
5390
|
-
* Internal function when stopping a share stream, cleanup
|
|
5391
|
-
* @param {boolean} sendShare
|
|
5392
|
-
* @param {boolean} previousShareStatus
|
|
5393
|
-
* @returns {Promise}
|
|
5394
|
-
* @private
|
|
5395
|
-
* @memberof Meeting
|
|
5396
|
-
*/
|
|
5397
|
-
private checkForStopShare(sendShare: boolean, previousShareStatus: boolean) {
|
|
5398
|
-
if (sendShare && !previousShareStatus) {
|
|
5399
|
-
// When user starts sharing
|
|
5400
|
-
return Promise.resolve(true);
|
|
5401
|
-
}
|
|
5402
|
-
|
|
5403
|
-
if (!sendShare && previousShareStatus) {
|
|
5404
|
-
// When user stops sharing
|
|
5405
|
-
return this.releaseScreenShareFloor().then(() => Promise.resolve(false));
|
|
5406
|
-
}
|
|
5407
|
-
|
|
5408
|
-
return Promise.resolve();
|
|
5409
|
-
}
|
|
5410
|
-
|
|
5411
|
-
/**
|
|
5412
|
-
* Update the share streams, can be used to start sharing
|
|
5413
|
-
*
|
|
5414
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
5415
|
-
*
|
|
5416
|
-
* @param {Object} options
|
|
5417
|
-
* @param {boolean} options.sendShare
|
|
5418
|
-
* @param {boolean} options.receiveShare
|
|
5419
|
-
* @returns {Promise}
|
|
5420
|
-
* @public
|
|
5421
|
-
* @memberof Meeting
|
|
5422
|
-
*/
|
|
5423
|
-
public updateShare(options: {
|
|
5424
|
-
sendShare?: boolean;
|
|
5425
|
-
receiveShare?: boolean;
|
|
5426
|
-
stream?: any;
|
|
5427
|
-
skipSignalingCheck?: boolean;
|
|
5428
|
-
}) {
|
|
5429
|
-
if (!options.skipSignalingCheck && !this.canUpdateMedia()) {
|
|
5430
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
|
|
5431
|
-
}
|
|
5432
|
-
const {sendShare, receiveShare, stream} = options;
|
|
5433
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
5434
|
-
|
|
5435
|
-
if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
|
|
5436
|
-
return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
|
|
5437
|
-
}
|
|
5438
|
-
|
|
5439
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
5440
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
5441
|
-
}
|
|
5442
|
-
|
|
5443
|
-
const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
|
|
5444
|
-
|
|
5445
|
-
this.setLocalShareTrack(stream);
|
|
5446
|
-
|
|
5447
|
-
return MeetingUtil.validateOptions({sendShare, localShare: stream})
|
|
5448
|
-
.then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
|
|
5449
|
-
.then((startShare) =>
|
|
5450
|
-
this.mediaProperties.webrtcMediaConnection
|
|
5451
|
-
.updateSendReceiveOptions({
|
|
5452
|
-
send: {screenShareVideo: track},
|
|
5453
|
-
receive: {
|
|
5454
|
-
audio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5455
|
-
video: this.mediaProperties.mediaDirection.receiveVideo,
|
|
5456
|
-
screenShareVideo: options.receiveShare,
|
|
5457
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5458
|
-
},
|
|
5459
|
-
})
|
|
5460
|
-
.then(() => {
|
|
5461
|
-
if (startShare) {
|
|
5462
|
-
return this.requestScreenShareFloor();
|
|
5463
|
-
}
|
|
5464
|
-
|
|
5465
|
-
return Promise.resolve();
|
|
5466
|
-
})
|
|
5467
|
-
)
|
|
5468
|
-
.then(() => {
|
|
5469
|
-
this.mediaProperties.mediaDirection.sendShare = sendShare;
|
|
5470
|
-
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
5471
|
-
})
|
|
5472
|
-
.catch((error) => {
|
|
5473
|
-
this.unsetLocalShareTrack();
|
|
5474
|
-
throw error;
|
|
5475
|
-
});
|
|
5476
|
-
}
|
|
5477
|
-
|
|
5478
|
-
/**
|
|
5479
|
-
* Do all the attach media pre set up before executing the actual attach
|
|
5480
|
-
* @param {MediaStream} localStream
|
|
5481
|
-
* @param {MediaStream} localShare
|
|
5482
|
-
* @param {MediaDirection} mediaSettings
|
|
5483
|
-
* @returns {undefined}
|
|
5484
|
-
* @private
|
|
5485
|
-
* @memberof Meeting
|
|
5486
|
-
*/
|
|
5487
|
-
private preMedia(localStream: MediaStream, localShare: MediaStream, mediaSettings: any) {
|
|
5488
|
-
// eslint-disable-next-line no-warning-comments
|
|
5489
|
-
// TODO wire into default config. There's currently an issue with the stateless plugin or how we register
|
|
5490
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5491
|
-
this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
|
|
5492
|
-
// add a setup a function move the create and setup media in future
|
|
5493
|
-
// TODO: delete old audio and video if stale
|
|
5494
|
-
this.audio = this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
|
|
5495
|
-
this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
|
|
5496
|
-
// Validation is already done in addMedia so no need to check if the lenght is greater then 0
|
|
5497
|
-
this.setLocalTracks(localStream);
|
|
5498
|
-
this.setLocalShareTrack(localShare);
|
|
5499
|
-
}
|
|
5500
|
-
|
|
5501
|
-
/**
|
|
5502
|
-
* Acknowledge the meeting, outgoing or incoming
|
|
5503
|
-
* @param {String} type
|
|
5504
|
-
* @returns {Promise} resolve {message, ringing, response}
|
|
5505
|
-
* @public
|
|
5506
|
-
* @memberof Meeting
|
|
5507
|
-
*/
|
|
5508
|
-
public acknowledge(type: string) {
|
|
5509
|
-
if (!type) {
|
|
5510
|
-
return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
|
|
5511
|
-
}
|
|
5512
|
-
if (type === _INCOMING_) {
|
|
5513
|
-
return this.meetingRequest
|
|
5514
|
-
.acknowledgeMeeting({
|
|
5515
|
-
locusUrl: this.locusUrl,
|
|
5516
|
-
deviceUrl: this.deviceUrl,
|
|
5517
|
-
correlationId: this.correlationId,
|
|
5518
|
-
})
|
|
5519
|
-
.then((response) => Promise.resolve(response))
|
|
5520
|
-
.then((response) => {
|
|
5521
|
-
this.meetingFiniteStateMachine.ring(type);
|
|
5522
|
-
Metrics.postEvent({event: eventType.ALERT_DISPLAYED, meeting: this});
|
|
5523
|
-
|
|
5524
|
-
return Promise.resolve({
|
|
5525
|
-
response,
|
|
5526
|
-
});
|
|
5527
|
-
});
|
|
5490
|
+
return Promise.resolve({
|
|
5491
|
+
response,
|
|
5492
|
+
});
|
|
5493
|
+
});
|
|
5528
5494
|
}
|
|
5529
5495
|
|
|
5530
5496
|
// TODO: outside of 1:1 incoming, and all outgoing calls
|
|
@@ -5563,13 +5529,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5563
5529
|
* @memberof Meeting
|
|
5564
5530
|
*/
|
|
5565
5531
|
public leave(options: {resourceId?: string; reason?: any} = {} as any) {
|
|
5532
|
+
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5566
5533
|
Metrics.postEvent({
|
|
5567
5534
|
event: eventType.LEAVE,
|
|
5568
5535
|
meeting: this,
|
|
5569
|
-
data: {trigger: trigger.USER_INTERACTION, canProceed: false},
|
|
5536
|
+
data: {trigger: trigger.USER_INTERACTION, canProceed: false, reason: leaveReason},
|
|
5570
5537
|
});
|
|
5571
|
-
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5572
|
-
|
|
5573
5538
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
5574
5539
|
|
|
5575
5540
|
return MeetingUtil.leaveMeeting(this, options)
|
|
@@ -5738,55 +5703,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5738
5703
|
* @memberof Meeting
|
|
5739
5704
|
*/
|
|
5740
5705
|
private requestScreenShareFloor() {
|
|
5741
|
-
|
|
5706
|
+
if (!this.mediaProperties.shareTrack || !this.mediaProperties.mediaDirection.sendShare) {
|
|
5707
|
+
LoggerProxy.logger.log(
|
|
5708
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareTrack=${
|
|
5709
|
+
this.mediaProperties.shareTrack ? 'yes' : 'no'
|
|
5710
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
5711
|
+
);
|
|
5742
5712
|
|
|
5743
|
-
|
|
5744
|
-
|
|
5713
|
+
return Promise.resolve({});
|
|
5714
|
+
}
|
|
5715
|
+
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
5716
|
+
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5717
|
+
|
|
5718
|
+
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
5719
|
+
Metrics.postEvent({event: eventType.SHARE_INITIATED, meeting: this});
|
|
5720
|
+
|
|
5721
|
+
return this.meetingRequest
|
|
5722
|
+
.changeMeetingFloor({
|
|
5723
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
5724
|
+
personUrl: this.locusInfo.self.url,
|
|
5725
|
+
deviceUrl: this.deviceUrl,
|
|
5726
|
+
uri: content.url,
|
|
5727
|
+
resourceUrl: this.resourceUrl,
|
|
5728
|
+
annotationInfo: this.annotationInfo,
|
|
5729
|
+
})
|
|
5730
|
+
.then(() => {
|
|
5731
|
+
this.isSharing = true;
|
|
5745
5732
|
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
deviceUrl: this.deviceUrl,
|
|
5751
|
-
uri: content.url,
|
|
5752
|
-
resourceUrl: this.resourceUrl,
|
|
5753
|
-
})
|
|
5754
|
-
.then(() => {
|
|
5755
|
-
this.isSharing = true;
|
|
5733
|
+
return Promise.resolve();
|
|
5734
|
+
})
|
|
5735
|
+
.catch((error) => {
|
|
5736
|
+
LoggerProxy.logger.error('Meeting:index#share --> Error ', error);
|
|
5756
5737
|
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5738
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_FAILURE, {
|
|
5739
|
+
correlation_id: this.correlationId,
|
|
5740
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5741
|
+
reason: error.message,
|
|
5742
|
+
stack: error.stack,
|
|
5743
|
+
});
|
|
5761
5744
|
|
|
5762
|
-
|
|
5763
|
-
correlation_id: this.correlationId,
|
|
5764
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5765
|
-
reason: error.message,
|
|
5766
|
-
stack: error.stack,
|
|
5745
|
+
return Promise.reject(error);
|
|
5767
5746
|
});
|
|
5747
|
+
}
|
|
5768
5748
|
|
|
5769
|
-
|
|
5770
|
-
});
|
|
5749
|
+
return Promise.reject(new ParameterError('Cannot share without content.'));
|
|
5771
5750
|
}
|
|
5751
|
+
this.floorGrantPending = true;
|
|
5772
5752
|
|
|
5773
|
-
return Promise.
|
|
5753
|
+
return Promise.resolve({});
|
|
5774
5754
|
}
|
|
5775
5755
|
|
|
5776
5756
|
/**
|
|
5777
|
-
*
|
|
5778
|
-
*
|
|
5779
|
-
*
|
|
5780
|
-
* @
|
|
5757
|
+
* Requests screen share floor if such request is pending.
|
|
5758
|
+
* It should be called whenever meeting state changes to JOINED
|
|
5759
|
+
*
|
|
5760
|
+
* @returns {void}
|
|
5781
5761
|
*/
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
...options,
|
|
5789
|
-
});
|
|
5762
|
+
private requestScreenShareFloorIfPending() {
|
|
5763
|
+
if (this.floorGrantPending && this.state === MEETING_STATE.STATES.JOINED) {
|
|
5764
|
+
this.requestScreenShareFloor().then(() => {
|
|
5765
|
+
this.floorGrantPending = false;
|
|
5766
|
+
});
|
|
5767
|
+
}
|
|
5790
5768
|
}
|
|
5791
5769
|
|
|
5792
5770
|
/**
|
|
@@ -5798,11 +5776,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5798
5776
|
private releaseScreenShareFloor() {
|
|
5799
5777
|
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5800
5778
|
|
|
5801
|
-
if (content
|
|
5779
|
+
if (content) {
|
|
5802
5780
|
Metrics.postEvent({event: eventType.SHARE_STOPPED, meeting: this});
|
|
5803
|
-
Media.stopTracks(this.mediaProperties.shareTrack);
|
|
5804
5781
|
|
|
5805
|
-
if (content.floor
|
|
5782
|
+
if (content.floor?.beneficiary.id !== this.selfId) {
|
|
5806
5783
|
// remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
|
|
5807
5784
|
this.isSharing = false;
|
|
5808
5785
|
|
|
@@ -5834,7 +5811,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5834
5811
|
});
|
|
5835
5812
|
}
|
|
5836
5813
|
|
|
5837
|
-
|
|
5814
|
+
// according to Locus there is no content, so we don't need to release the floor (it's probably already been released)
|
|
5815
|
+
this.isSharing = false;
|
|
5816
|
+
|
|
5817
|
+
return Promise.resolve();
|
|
5838
5818
|
}
|
|
5839
5819
|
|
|
5840
5820
|
/**
|
|
@@ -5844,7 +5824,50 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5844
5824
|
* @memberof Meeting
|
|
5845
5825
|
*/
|
|
5846
5826
|
public startRecording() {
|
|
5847
|
-
return
|
|
5827
|
+
return this.recordingController.startRecording();
|
|
5828
|
+
}
|
|
5829
|
+
|
|
5830
|
+
/**
|
|
5831
|
+
* set the mute on entry flag for participants if you're the host
|
|
5832
|
+
* @returns {Promise}
|
|
5833
|
+
* @param {boolean} enabled
|
|
5834
|
+
* @public
|
|
5835
|
+
* @memberof Meeting
|
|
5836
|
+
*/
|
|
5837
|
+
public setMuteOnEntry(enabled: boolean) {
|
|
5838
|
+
return this.controlsOptionsManager.setMuteOnEntry(enabled);
|
|
5839
|
+
}
|
|
5840
|
+
|
|
5841
|
+
/**
|
|
5842
|
+
* set the disallow unmute flag for participants if you're the host
|
|
5843
|
+
* @returns {Promise}
|
|
5844
|
+
* @param {boolean} enabled
|
|
5845
|
+
* @public
|
|
5846
|
+
* @memberof Meeting
|
|
5847
|
+
*/
|
|
5848
|
+
public setDisallowUnmute(enabled: boolean) {
|
|
5849
|
+
return this.controlsOptionsManager.setDisallowUnmute(enabled);
|
|
5850
|
+
}
|
|
5851
|
+
|
|
5852
|
+
/**
|
|
5853
|
+
* set the mute all flag for participants if you're the host
|
|
5854
|
+
* @returns {Promise}
|
|
5855
|
+
* @param {boolean} mutedEnabled
|
|
5856
|
+
* @param {boolean} disallowUnmuteEnabled
|
|
5857
|
+
* @param {boolean} muteOnEntryEnabled
|
|
5858
|
+
* @public
|
|
5859
|
+
* @memberof Meeting
|
|
5860
|
+
*/
|
|
5861
|
+
public setMuteAll(
|
|
5862
|
+
mutedEnabled: boolean,
|
|
5863
|
+
disallowUnmuteEnabled: boolean,
|
|
5864
|
+
muteOnEntryEnabled: boolean
|
|
5865
|
+
) {
|
|
5866
|
+
return this.controlsOptionsManager.setMuteAll(
|
|
5867
|
+
mutedEnabled,
|
|
5868
|
+
disallowUnmuteEnabled,
|
|
5869
|
+
muteOnEntryEnabled
|
|
5870
|
+
);
|
|
5848
5871
|
}
|
|
5849
5872
|
|
|
5850
5873
|
/**
|
|
@@ -5854,7 +5877,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5854
5877
|
* @memberof Meeting
|
|
5855
5878
|
*/
|
|
5856
5879
|
public stopRecording() {
|
|
5857
|
-
return
|
|
5880
|
+
return this.recordingController.stopRecording();
|
|
5858
5881
|
}
|
|
5859
5882
|
|
|
5860
5883
|
/**
|
|
@@ -5864,7 +5887,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5864
5887
|
* @memberof Meeting
|
|
5865
5888
|
*/
|
|
5866
5889
|
public pauseRecording() {
|
|
5867
|
-
return
|
|
5890
|
+
return this.recordingController.pauseRecording();
|
|
5868
5891
|
}
|
|
5869
5892
|
|
|
5870
5893
|
/**
|
|
@@ -5874,7 +5897,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5874
5897
|
* @memberof Meeting
|
|
5875
5898
|
*/
|
|
5876
5899
|
public resumeRecording() {
|
|
5877
|
-
return
|
|
5900
|
+
return this.recordingController.resumeRecording();
|
|
5878
5901
|
}
|
|
5879
5902
|
|
|
5880
5903
|
/**
|
|
@@ -6048,11 +6071,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6048
6071
|
main: layoutInfo.main,
|
|
6049
6072
|
content: layoutInfo.content,
|
|
6050
6073
|
})
|
|
6051
|
-
.then((response) => {
|
|
6052
|
-
if (response && response.body && response.body.locus) {
|
|
6053
|
-
this.locusInfo.onFullLocus(response.body.locus);
|
|
6054
|
-
}
|
|
6055
|
-
})
|
|
6056
6074
|
.catch((error) => {
|
|
6057
6075
|
LoggerProxy.logger.error('Meeting:index#changeVideoLayout --> Error ', error);
|
|
6058
6076
|
|
|
@@ -6060,62 +6078,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6060
6078
|
});
|
|
6061
6079
|
}
|
|
6062
6080
|
|
|
6063
|
-
/**
|
|
6064
|
-
* Sets the quality of the local video stream
|
|
6065
|
-
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
6066
|
-
* @returns {Promise<MediaStream>} localStream
|
|
6067
|
-
*/
|
|
6068
|
-
setLocalVideoQuality(level: string) {
|
|
6069
|
-
LoggerProxy.logger.log(`Meeting:index#setLocalVideoQuality --> Setting quality to ${level}`);
|
|
6070
|
-
|
|
6071
|
-
if (!VIDEO_RESOLUTIONS[level]) {
|
|
6072
|
-
return this.rejectWithErrorLog(`Meeting:index#setLocalVideoQuality --> ${level} not defined`);
|
|
6073
|
-
}
|
|
6074
|
-
|
|
6075
|
-
if (!this.mediaProperties.mediaDirection.sendVideo) {
|
|
6076
|
-
return this.rejectWithErrorLog(
|
|
6077
|
-
'Meeting:index#setLocalVideoQuality --> unable to change video quality, sendVideo is disabled'
|
|
6078
|
-
);
|
|
6079
|
-
}
|
|
6080
|
-
|
|
6081
|
-
// If level is already the same, don't do anything
|
|
6082
|
-
if (level === this.mediaProperties.localQualityLevel) {
|
|
6083
|
-
LoggerProxy.logger.warn(
|
|
6084
|
-
`Meeting:index#setLocalQualityLevel --> Quality already set to ${level}`
|
|
6085
|
-
);
|
|
6086
|
-
|
|
6087
|
-
return Promise.resolve();
|
|
6088
|
-
}
|
|
6089
|
-
|
|
6090
|
-
// Set the quality level in properties
|
|
6091
|
-
this.mediaProperties.setLocalQualityLevel(level);
|
|
6092
|
-
|
|
6093
|
-
const mediaDirection = {
|
|
6094
|
-
sendAudio: this.mediaProperties.mediaDirection.sendAudio,
|
|
6095
|
-
sendVideo: this.mediaProperties.mediaDirection.sendVideo,
|
|
6096
|
-
sendShare: this.mediaProperties.mediaDirection.sendShare,
|
|
6097
|
-
};
|
|
6098
|
-
|
|
6099
|
-
// When changing local video quality level
|
|
6100
|
-
// Need to stop current track first as chrome doesn't support resolution upscaling(for eg. changing 480p to 720p)
|
|
6101
|
-
// Without feeding it a new track
|
|
6102
|
-
// open bug link: https://bugs.chromium.org/p/chromium/issues/detail?id=943469
|
|
6103
|
-
if (isBrowser('chrome') && this.mediaProperties.videoTrack)
|
|
6104
|
-
Media.stopTracks(this.mediaProperties.videoTrack);
|
|
6105
|
-
|
|
6106
|
-
return this.getMediaStreams(mediaDirection, VIDEO_RESOLUTIONS[level]).then(
|
|
6107
|
-
async ([localStream]) => {
|
|
6108
|
-
await this.updateVideo({
|
|
6109
|
-
sendVideo: true,
|
|
6110
|
-
receiveVideo: true,
|
|
6111
|
-
stream: localStream,
|
|
6112
|
-
});
|
|
6113
|
-
|
|
6114
|
-
return localStream;
|
|
6115
|
-
}
|
|
6116
|
-
);
|
|
6117
|
-
}
|
|
6118
|
-
|
|
6119
6081
|
/**
|
|
6120
6082
|
* Sets the quality level of the remote incoming media
|
|
6121
6083
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
@@ -6151,129 +6113,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6151
6113
|
// Set the quality level in properties
|
|
6152
6114
|
this.mediaProperties.setRemoteQualityLevel(level);
|
|
6153
6115
|
|
|
6154
|
-
return this.
|
|
6155
|
-
}
|
|
6156
|
-
|
|
6157
|
-
/**
|
|
6158
|
-
* This is deprecated, please use setLocalVideoQuality for setting local and setRemoteQualityLevel for remote
|
|
6159
|
-
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
6160
|
-
* @returns {Promise}
|
|
6161
|
-
* @deprecated After FHD support
|
|
6162
|
-
*/
|
|
6163
|
-
setMeetingQuality(level: string) {
|
|
6164
|
-
LoggerProxy.logger.log(`Meeting:index#setMeetingQuality --> Setting quality to ${level}`);
|
|
6165
|
-
|
|
6166
|
-
if (!QUALITY_LEVELS[level]) {
|
|
6167
|
-
return this.rejectWithErrorLog(`Meeting:index#setMeetingQuality --> ${level} not defined`);
|
|
6168
|
-
}
|
|
6169
|
-
|
|
6170
|
-
const previousLevel = {
|
|
6171
|
-
local: this.mediaProperties.localQualityLevel,
|
|
6172
|
-
remote: this.mediaProperties.remoteQualityLevel,
|
|
6173
|
-
};
|
|
6174
|
-
|
|
6175
|
-
// If level is already the same, don't do anything
|
|
6176
|
-
if (
|
|
6177
|
-
level === this.mediaProperties.localQualityLevel &&
|
|
6178
|
-
level === this.mediaProperties.remoteQualityLevel
|
|
6179
|
-
) {
|
|
6180
|
-
LoggerProxy.logger.warn(
|
|
6181
|
-
`Meeting:index#setMeetingQuality --> Quality already set to ${level}`
|
|
6182
|
-
);
|
|
6183
|
-
|
|
6184
|
-
return Promise.resolve();
|
|
6185
|
-
}
|
|
6186
|
-
|
|
6187
|
-
// Determine the direction of our current media
|
|
6188
|
-
const {receiveAudio, receiveVideo, sendVideo} = this.mediaProperties.mediaDirection;
|
|
6189
|
-
|
|
6190
|
-
return (sendVideo ? this.setLocalVideoQuality(level) : Promise.resolve())
|
|
6191
|
-
.then(() =>
|
|
6192
|
-
receiveAudio || receiveVideo ? this.setRemoteQualityLevel(level) : Promise.resolve()
|
|
6193
|
-
)
|
|
6194
|
-
.catch((error) => {
|
|
6195
|
-
// From troubleshooting it seems that the stream itself doesn't change the max-fs if the peer connection isn't stable
|
|
6196
|
-
this.mediaProperties.setLocalQualityLevel(previousLevel.local);
|
|
6197
|
-
this.mediaProperties.setRemoteQualityLevel(previousLevel.remote);
|
|
6198
|
-
|
|
6199
|
-
LoggerProxy.logger.error(`Meeting:index#setMeetingQuality --> ${error.message}`);
|
|
6200
|
-
|
|
6201
|
-
Metrics.sendBehavioralMetric(
|
|
6202
|
-
BEHAVIORAL_METRICS.SET_MEETING_QUALITY_FAILURE,
|
|
6203
|
-
{
|
|
6204
|
-
correlation_id: this.correlationId,
|
|
6205
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
6206
|
-
reason: error.message,
|
|
6207
|
-
stack: error.stack,
|
|
6208
|
-
},
|
|
6209
|
-
{
|
|
6210
|
-
type: error.name,
|
|
6211
|
-
}
|
|
6212
|
-
);
|
|
6213
|
-
|
|
6214
|
-
return Promise.reject(error);
|
|
6215
|
-
});
|
|
6216
|
-
}
|
|
6217
|
-
|
|
6218
|
-
/**
|
|
6219
|
-
*
|
|
6220
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream use publishTrack()
|
|
6221
|
-
*
|
|
6222
|
-
* @param {Object} options parameter
|
|
6223
|
-
* @param {Boolean} options.sendAudio send audio from the display share
|
|
6224
|
-
* @param {Boolean} options.sendShare send video from the display share
|
|
6225
|
-
* @param {Object} options.sharePreferences
|
|
6226
|
-
* @param {MediaTrackConstraints} options.sharePreferences.shareConstraints constraints to apply to video
|
|
6227
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints}
|
|
6228
|
-
* @param {Boolean} options.sharePreferences.highFrameRate if shareConstraints isn't provided, set default values based off of this boolean
|
|
6229
|
-
* @returns {Promise}
|
|
6230
|
-
*/
|
|
6231
|
-
shareScreen(
|
|
6232
|
-
options: {
|
|
6233
|
-
sendAudio: boolean;
|
|
6234
|
-
sendShare: boolean;
|
|
6235
|
-
sharePreferences: {shareConstraints: MediaTrackConstraints};
|
|
6236
|
-
} = {} as any
|
|
6237
|
-
) {
|
|
6238
|
-
LoggerProxy.logger.log('Meeting:index#shareScreen --> Getting local share');
|
|
6239
|
-
|
|
6240
|
-
const shareConstraints = {
|
|
6241
|
-
sendShare: true,
|
|
6242
|
-
sendAudio: false,
|
|
6243
|
-
...options,
|
|
6244
|
-
};
|
|
6245
|
-
|
|
6246
|
-
// @ts-ignore - config coming from registerPlugin
|
|
6247
|
-
return Media.getDisplayMedia(shareConstraints, this.config)
|
|
6248
|
-
.then((shareStream) =>
|
|
6249
|
-
this.updateShare({
|
|
6250
|
-
sendShare: true,
|
|
6251
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
6252
|
-
stream: shareStream,
|
|
6253
|
-
})
|
|
6254
|
-
)
|
|
6255
|
-
.catch((error) => {
|
|
6256
|
-
// Whenever there is a failure when trying to access a user's display
|
|
6257
|
-
// report it as an Behavioral metric
|
|
6258
|
-
// This gives visibility into common errors and can help
|
|
6259
|
-
// with further troubleshooting
|
|
6260
|
-
|
|
6261
|
-
// This metrics will get erros for getDisplayMedia and share errors for now
|
|
6262
|
-
// TODO: The getDisplayMedia errors need to be moved inside `media.getDisplayMedia`
|
|
6263
|
-
const metricName = BEHAVIORAL_METRICS.GET_DISPLAY_MEDIA_FAILURE;
|
|
6264
|
-
const data = {
|
|
6265
|
-
correlation_id: this.correlationId,
|
|
6266
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
6267
|
-
reason: error.message,
|
|
6268
|
-
stack: error.stack,
|
|
6269
|
-
};
|
|
6270
|
-
const metadata = {
|
|
6271
|
-
type: error.name,
|
|
6272
|
-
};
|
|
6273
|
-
|
|
6274
|
-
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
6275
|
-
throw new MediaError('Unable to retrieve display media stream', error);
|
|
6276
|
-
});
|
|
6116
|
+
return this.updateTranscodedMediaConnection();
|
|
6277
6117
|
}
|
|
6278
6118
|
|
|
6279
6119
|
/**
|
|
@@ -6283,20 +6123,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6283
6123
|
* @param {MediaStream} localShare
|
|
6284
6124
|
* @returns {undefined}
|
|
6285
6125
|
*/
|
|
6286
|
-
private handleShareTrackEnded(
|
|
6126
|
+
private handleShareTrackEnded = async () => {
|
|
6287
6127
|
if (this.wirelessShare) {
|
|
6288
6128
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6289
6129
|
} else {
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
skipSignalingCheck: true,
|
|
6294
|
-
}).catch((error) => {
|
|
6130
|
+
try {
|
|
6131
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo: screen share audio (SPARK-399690)
|
|
6132
|
+
} catch (error) {
|
|
6295
6133
|
LoggerProxy.logger.log(
|
|
6296
6134
|
'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
|
|
6297
6135
|
error
|
|
6298
6136
|
);
|
|
6299
|
-
}
|
|
6137
|
+
}
|
|
6300
6138
|
}
|
|
6301
6139
|
|
|
6302
6140
|
Trigger.trigger(
|
|
@@ -6307,11 +6145,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6307
6145
|
},
|
|
6308
6146
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
6309
6147
|
{
|
|
6310
|
-
|
|
6311
|
-
stream: localShare,
|
|
6148
|
+
reason: SHARE_STOPPED_REASON.TRACK_ENDED,
|
|
6312
6149
|
}
|
|
6313
6150
|
);
|
|
6314
|
-
}
|
|
6151
|
+
};
|
|
6315
6152
|
|
|
6316
6153
|
/**
|
|
6317
6154
|
* Emits the 'network:quality' event
|
|
@@ -6341,14 +6178,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6341
6178
|
|
|
6342
6179
|
/**
|
|
6343
6180
|
* Handle logging the media
|
|
6344
|
-
* @param {Object}
|
|
6345
|
-
* @param {Object} videoTrack The video track
|
|
6181
|
+
* @param {Object} mediaProperties
|
|
6346
6182
|
* @private
|
|
6347
6183
|
* @returns {undefined}
|
|
6348
6184
|
*/
|
|
6349
|
-
private handleMediaLogging(
|
|
6350
|
-
|
|
6351
|
-
|
|
6185
|
+
private handleMediaLogging(mediaProperties: {
|
|
6186
|
+
audioTrack?: LocalMicrophoneTrack;
|
|
6187
|
+
videoTrack?: LocalCameraTrack;
|
|
6188
|
+
}) {
|
|
6189
|
+
MeetingUtil.handleVideoLogging(mediaProperties.videoTrack);
|
|
6190
|
+
MeetingUtil.handleAudioLogging(mediaProperties.audioTrack);
|
|
6352
6191
|
}
|
|
6353
6192
|
|
|
6354
6193
|
/**
|
|
@@ -6437,7 +6276,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6437
6276
|
const end = this.endLocalSDPGenRemoteSDPRecvDelay;
|
|
6438
6277
|
|
|
6439
6278
|
if (start && end) {
|
|
6440
|
-
const calculatedDelay = end - start;
|
|
6279
|
+
const calculatedDelay = Math.round(end - start);
|
|
6441
6280
|
|
|
6442
6281
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6443
6282
|
}
|
|
@@ -6449,26 +6288,26 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6449
6288
|
*
|
|
6450
6289
|
* @returns {undefined}
|
|
6451
6290
|
*/
|
|
6452
|
-
|
|
6453
|
-
this.
|
|
6454
|
-
this.
|
|
6291
|
+
setStartCallInitJoinReq() {
|
|
6292
|
+
this.startCallInitJoinReq = performance.now();
|
|
6293
|
+
this.endCallInitJoinReq = undefined;
|
|
6455
6294
|
}
|
|
6456
6295
|
|
|
6457
6296
|
/**
|
|
6458
6297
|
*
|
|
6459
6298
|
* @returns {undefined}
|
|
6460
6299
|
*/
|
|
6461
|
-
|
|
6462
|
-
this.
|
|
6300
|
+
setEndCallInitJoinReq() {
|
|
6301
|
+
this.endCallInitJoinReq = performance.now();
|
|
6463
6302
|
}
|
|
6464
6303
|
|
|
6465
6304
|
/**
|
|
6466
6305
|
*
|
|
6467
6306
|
* @returns {string} duration between call initiate and sending join request to locus
|
|
6468
6307
|
*/
|
|
6469
|
-
|
|
6470
|
-
const start = this.
|
|
6471
|
-
const end = this.
|
|
6308
|
+
getCallInitJoinReq() {
|
|
6309
|
+
const start = this.startCallInitJoinReq;
|
|
6310
|
+
const end = this.endCallInitJoinReq;
|
|
6472
6311
|
|
|
6473
6312
|
if (start && end) {
|
|
6474
6313
|
const calculatedDelay = end - start;
|
|
@@ -6505,7 +6344,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6505
6344
|
const end = this.endJoinReqResp;
|
|
6506
6345
|
|
|
6507
6346
|
if (start && end) {
|
|
6508
|
-
const calculatedDelay = end - start;
|
|
6347
|
+
const calculatedDelay = Math.round(end - start);
|
|
6509
6348
|
|
|
6510
6349
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6511
6350
|
}
|
|
@@ -6518,10 +6357,45 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6518
6357
|
* @returns {string} duration between call initiate and successful locus join (even if it is in lobby)
|
|
6519
6358
|
*/
|
|
6520
6359
|
getTotalJmt() {
|
|
6521
|
-
const start = this.
|
|
6360
|
+
const start = this.startCallInitJoinReq;
|
|
6522
6361
|
const end = this.endJoinReqResp;
|
|
6523
6362
|
|
|
6524
|
-
return start && end ? end - start : undefined;
|
|
6363
|
+
return start && end ? Math.round(end - start) : undefined;
|
|
6364
|
+
}
|
|
6365
|
+
|
|
6366
|
+
/**
|
|
6367
|
+
*
|
|
6368
|
+
* @returns {string} one of 'attendee','host','cohost', returns the user type of the current user
|
|
6369
|
+
*/
|
|
6370
|
+
getCurUserType() {
|
|
6371
|
+
const {roles} = this;
|
|
6372
|
+
if (roles) {
|
|
6373
|
+
if (roles.includes(SELF_ROLES.MODERATOR)) {
|
|
6374
|
+
return 'host';
|
|
6375
|
+
}
|
|
6376
|
+
if (roles.includes(SELF_ROLES.COHOST)) {
|
|
6377
|
+
return 'cohost';
|
|
6378
|
+
}
|
|
6379
|
+
if (roles.includes(SELF_ROLES.ATTENDEE)) {
|
|
6380
|
+
return 'attendee';
|
|
6381
|
+
}
|
|
6382
|
+
}
|
|
6383
|
+
|
|
6384
|
+
return null;
|
|
6385
|
+
}
|
|
6386
|
+
|
|
6387
|
+
/**
|
|
6388
|
+
*
|
|
6389
|
+
* @returns {string} one of 'login-ci','unverified-guest', returns the login type of the current user
|
|
6390
|
+
*/
|
|
6391
|
+
getCurLoginType() {
|
|
6392
|
+
// @ts-ignore
|
|
6393
|
+
if (this.webex.canAuthorize) {
|
|
6394
|
+
// @ts-ignore
|
|
6395
|
+
return this.webex.credentials.isUnverifiedGuest ? 'unverified-guest' : 'login-ci';
|
|
6396
|
+
}
|
|
6397
|
+
|
|
6398
|
+
return null;
|
|
6525
6399
|
}
|
|
6526
6400
|
|
|
6527
6401
|
/**
|
|
@@ -6611,121 +6485,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6611
6485
|
}
|
|
6612
6486
|
};
|
|
6613
6487
|
|
|
6614
|
-
/**
|
|
6615
|
-
* Internal API to return status of BNR
|
|
6616
|
-
* @returns {Boolean}
|
|
6617
|
-
* @public
|
|
6618
|
-
* @memberof Meeting
|
|
6619
|
-
*/
|
|
6620
|
-
public isBnrEnabled() {
|
|
6621
|
-
return this.effects && this.effects.isBnrEnabled();
|
|
6622
|
-
}
|
|
6623
|
-
|
|
6624
|
-
/**
|
|
6625
|
-
* Internal API to obtain BNR enabled MediaStream
|
|
6626
|
-
* @returns {Promise<MediaStreamTrack>}
|
|
6627
|
-
* @private
|
|
6628
|
-
* @param {MedaiStreamTrack} audioTrack from updateAudio
|
|
6629
|
-
* @memberof Meeting
|
|
6630
|
-
*/
|
|
6631
|
-
private async internal_enableBNR(audioTrack: any) {
|
|
6632
|
-
try {
|
|
6633
|
-
LoggerProxy.logger.info('Meeting:index#internal_enableBNR. Internal enable BNR called');
|
|
6634
|
-
const bnrAudioTrack = await WebRTCMedia.Effects.BNR.enableBNR(audioTrack);
|
|
6635
|
-
|
|
6636
|
-
LoggerProxy.logger.info(
|
|
6637
|
-
'Meeting:index#internal_enableBNR. BNR enabled track obtained from WebRTC & returned as stream'
|
|
6638
|
-
);
|
|
6639
|
-
|
|
6640
|
-
return bnrAudioTrack;
|
|
6641
|
-
} catch (error) {
|
|
6642
|
-
LoggerProxy.logger.error('Meeting:index#internal_enableBNR.', error);
|
|
6643
|
-
throw error;
|
|
6644
|
-
}
|
|
6645
|
-
}
|
|
6646
|
-
|
|
6647
|
-
/**
|
|
6648
|
-
* Enable the audio track with BNR for a meeting
|
|
6649
|
-
* @returns {Promise} resolves the data from enable bnr or rejects if there is no audio or audio is muted
|
|
6650
|
-
* @public
|
|
6651
|
-
* @memberof Meeting
|
|
6652
|
-
*/
|
|
6653
|
-
public enableBNR() {
|
|
6654
|
-
if (
|
|
6655
|
-
typeof this.mediaProperties === 'undefined' ||
|
|
6656
|
-
typeof this.mediaProperties.audioTrack === 'undefined'
|
|
6657
|
-
) {
|
|
6658
|
-
return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
|
|
6659
|
-
}
|
|
6660
|
-
|
|
6661
|
-
if (this.isAudioMuted()) {
|
|
6662
|
-
return Promise.reject(new Error('Cannot enable BNR while meeting is muted'));
|
|
6663
|
-
}
|
|
6664
|
-
|
|
6665
|
-
this.effects = this.effects || createEffectsState('BNR');
|
|
6666
|
-
|
|
6667
|
-
const LOG_HEADER = 'Meeting:index#enableBNR -->';
|
|
6668
|
-
|
|
6669
|
-
return logRequest(
|
|
6670
|
-
this.effects
|
|
6671
|
-
.handleClientRequest(true, this)
|
|
6672
|
-
.then((res) => {
|
|
6673
|
-
LoggerProxy.logger.info('Meeting:index#enableBNR. Enable bnr completed');
|
|
6674
|
-
|
|
6675
|
-
return res;
|
|
6676
|
-
})
|
|
6677
|
-
.catch((error) => {
|
|
6678
|
-
throw error;
|
|
6679
|
-
}),
|
|
6680
|
-
{
|
|
6681
|
-
header: `${LOG_HEADER} enable bnr`,
|
|
6682
|
-
success: `${LOG_HEADER} enable bnr success`,
|
|
6683
|
-
failure: `${LOG_HEADER} enable bnr failure, `,
|
|
6684
|
-
}
|
|
6685
|
-
);
|
|
6686
|
-
}
|
|
6687
|
-
|
|
6688
|
-
/**
|
|
6689
|
-
* Disable the BNR for an audio track
|
|
6690
|
-
* @returns {Promise} resolves the data from disable bnr or rejects if there is no audio set
|
|
6691
|
-
* @public
|
|
6692
|
-
* @memberof Meeting
|
|
6693
|
-
*/
|
|
6694
|
-
public disableBNR() {
|
|
6695
|
-
if (
|
|
6696
|
-
typeof this.mediaProperties === 'undefined' ||
|
|
6697
|
-
typeof this.mediaProperties.audioTrack === 'undefined'
|
|
6698
|
-
) {
|
|
6699
|
-
return Promise.reject(new Error("Meeting doesn't have an audioTrack attached"));
|
|
6700
|
-
}
|
|
6701
|
-
|
|
6702
|
-
if (!this.isBnrEnabled()) {
|
|
6703
|
-
return Promise.reject(new Error('Can not disable as BNR is not enabled'));
|
|
6704
|
-
}
|
|
6705
|
-
|
|
6706
|
-
this.effects = this.effects || createEffectsState('BNR');
|
|
6707
|
-
|
|
6708
|
-
const LOG_HEADER = 'Meeting:index#disableBNR -->';
|
|
6709
|
-
|
|
6710
|
-
return logRequest(
|
|
6711
|
-
this.effects
|
|
6712
|
-
.handleClientRequest(false, this)
|
|
6713
|
-
.then((res) => {
|
|
6714
|
-
LoggerProxy.logger.info('Meeting:index#disableBNR. Disable bnr completed');
|
|
6715
|
-
|
|
6716
|
-
return res;
|
|
6717
|
-
})
|
|
6718
|
-
.catch((error) => {
|
|
6719
|
-
throw error;
|
|
6720
|
-
}),
|
|
6721
|
-
{
|
|
6722
|
-
header: `${LOG_HEADER} disable bnr`,
|
|
6723
|
-
success: `${LOG_HEADER} disable bnr success`,
|
|
6724
|
-
failure: `${LOG_HEADER} disable bnr failure, `,
|
|
6725
|
-
}
|
|
6726
|
-
);
|
|
6727
|
-
}
|
|
6728
|
-
|
|
6729
6488
|
/**
|
|
6730
6489
|
* starts keepAlives being sent
|
|
6731
6490
|
* @returns {void}
|
|
@@ -6791,13 +6550,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6791
6550
|
/**
|
|
6792
6551
|
* Send a reaction inside the meeting.
|
|
6793
6552
|
*
|
|
6794
|
-
* @param {
|
|
6553
|
+
* @param {ReactionServerType} reactionType - type of reaction to be sent. Example: "thumbs_up"
|
|
6795
6554
|
* @param {SkinToneType} skinToneType - skin tone for the reaction. Example: "medium_dark"
|
|
6796
6555
|
* @returns {Promise}
|
|
6797
6556
|
* @public
|
|
6798
6557
|
* @memberof Meeting
|
|
6799
6558
|
*/
|
|
6800
|
-
public sendReaction(reactionType:
|
|
6559
|
+
public sendReaction(reactionType: ReactionServerType, skinToneType?: SkinToneType) {
|
|
6801
6560
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
6802
6561
|
const participantId = this.members.selfId;
|
|
6803
6562
|
|
|
@@ -6840,4 +6599,222 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6840
6599
|
requestingParticipantId: this.members.selfId,
|
|
6841
6600
|
});
|
|
6842
6601
|
}
|
|
6602
|
+
|
|
6603
|
+
/**
|
|
6604
|
+
* Throws if we don't have a media connection created
|
|
6605
|
+
*
|
|
6606
|
+
* @returns {void}
|
|
6607
|
+
*/
|
|
6608
|
+
private checkMediaConnection() {
|
|
6609
|
+
if (this.mediaProperties?.webrtcMediaConnection) {
|
|
6610
|
+
return;
|
|
6611
|
+
}
|
|
6612
|
+
throw new NoMediaEstablishedYetError();
|
|
6613
|
+
}
|
|
6614
|
+
|
|
6615
|
+
/**
|
|
6616
|
+
* Method to enable or disable the 'Music mode' effect on audio track
|
|
6617
|
+
*
|
|
6618
|
+
* @param {boolean} shouldEnableMusicMode
|
|
6619
|
+
* @returns {Promise}
|
|
6620
|
+
*/
|
|
6621
|
+
async enableMusicMode(shouldEnableMusicMode: boolean) {
|
|
6622
|
+
this.checkMediaConnection();
|
|
6623
|
+
|
|
6624
|
+
if (!this.isMultistream) {
|
|
6625
|
+
throw new Error('enableMusicMode() only supported with multistream');
|
|
6626
|
+
}
|
|
6627
|
+
|
|
6628
|
+
if (shouldEnableMusicMode) {
|
|
6629
|
+
await this.mediaProperties.webrtcMediaConnection.setCodecParameters(MediaType.AudioMain, {
|
|
6630
|
+
maxaveragebitrate: '64000',
|
|
6631
|
+
maxplaybackrate: '48000',
|
|
6632
|
+
});
|
|
6633
|
+
} else {
|
|
6634
|
+
await this.mediaProperties.webrtcMediaConnection.deleteCodecParameters(MediaType.AudioMain, [
|
|
6635
|
+
'maxaveragebitrate',
|
|
6636
|
+
'maxplaybackrate',
|
|
6637
|
+
]);
|
|
6638
|
+
}
|
|
6639
|
+
}
|
|
6640
|
+
|
|
6641
|
+
/** Updates the tracks being sent on the transcoded media connection
|
|
6642
|
+
*
|
|
6643
|
+
* @returns {Promise<void>}
|
|
6644
|
+
*/
|
|
6645
|
+
private updateTranscodedMediaConnection(): Promise<void> {
|
|
6646
|
+
const LOG_HEADER = 'Meeting:index#updateTranscodedMediaConnection -->';
|
|
6647
|
+
|
|
6648
|
+
LoggerProxy.logger.info(`${LOG_HEADER} starting`);
|
|
6649
|
+
|
|
6650
|
+
if (!this.canUpdateMedia()) {
|
|
6651
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION);
|
|
6652
|
+
}
|
|
6653
|
+
|
|
6654
|
+
return this.mediaProperties.webrtcMediaConnection
|
|
6655
|
+
.update({
|
|
6656
|
+
localTracks: {
|
|
6657
|
+
audio: this.mediaProperties.audioTrack?.underlyingTrack || null,
|
|
6658
|
+
video: this.mediaProperties.videoTrack?.underlyingTrack || null,
|
|
6659
|
+
screenShareVideo: this.mediaProperties.shareTrack?.underlyingTrack || null,
|
|
6660
|
+
},
|
|
6661
|
+
direction: {
|
|
6662
|
+
audio: Media.getDirection(
|
|
6663
|
+
true,
|
|
6664
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6665
|
+
this.mediaProperties.mediaDirection.sendAudio
|
|
6666
|
+
),
|
|
6667
|
+
video: Media.getDirection(
|
|
6668
|
+
true,
|
|
6669
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6670
|
+
this.mediaProperties.mediaDirection.sendVideo
|
|
6671
|
+
),
|
|
6672
|
+
screenShareVideo: Media.getDirection(
|
|
6673
|
+
false,
|
|
6674
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
6675
|
+
this.mediaProperties.mediaDirection.sendShare
|
|
6676
|
+
),
|
|
6677
|
+
},
|
|
6678
|
+
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6679
|
+
})
|
|
6680
|
+
.then(() => {
|
|
6681
|
+
LoggerProxy.logger.info(`${LOG_HEADER} done`);
|
|
6682
|
+
})
|
|
6683
|
+
.catch((error) => {
|
|
6684
|
+
LoggerProxy.logger.error(`${LOG_HEADER} Error: `, error);
|
|
6685
|
+
|
|
6686
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
6687
|
+
correlation_id: this.correlationId,
|
|
6688
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6689
|
+
reason: error.message,
|
|
6690
|
+
stack: error.stack,
|
|
6691
|
+
});
|
|
6692
|
+
|
|
6693
|
+
throw error;
|
|
6694
|
+
});
|
|
6695
|
+
}
|
|
6696
|
+
|
|
6697
|
+
/**
|
|
6698
|
+
* Publishes a track.
|
|
6699
|
+
*
|
|
6700
|
+
* @param {LocalTrack} track to publish
|
|
6701
|
+
* @returns {Promise}
|
|
6702
|
+
*/
|
|
6703
|
+
private async publishTrack(track?: LocalTrack) {
|
|
6704
|
+
if (!track) {
|
|
6705
|
+
return;
|
|
6706
|
+
}
|
|
6707
|
+
|
|
6708
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6709
|
+
if (this.isMultistream) {
|
|
6710
|
+
await this.mediaProperties.webrtcMediaConnection.publishTrack(track);
|
|
6711
|
+
} else {
|
|
6712
|
+
track.setPublished(true); // for multistream, this call is done by WCME
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
}
|
|
6716
|
+
|
|
6717
|
+
/**
|
|
6718
|
+
* Un-publishes a track.
|
|
6719
|
+
*
|
|
6720
|
+
* @param {LocalTrack} track to unpublish
|
|
6721
|
+
* @returns {Promise}
|
|
6722
|
+
*/
|
|
6723
|
+
private async unpublishTrack(track?: LocalTrack) {
|
|
6724
|
+
if (!track) {
|
|
6725
|
+
return;
|
|
6726
|
+
}
|
|
6727
|
+
|
|
6728
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
6729
|
+
await this.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
|
|
6730
|
+
} else {
|
|
6731
|
+
track.setPublished(false); // for multistream, this call is done by WCME
|
|
6732
|
+
}
|
|
6733
|
+
}
|
|
6734
|
+
|
|
6735
|
+
/**
|
|
6736
|
+
* Publishes specified local tracks in the meeting
|
|
6737
|
+
*
|
|
6738
|
+
* @param {Object} tracks
|
|
6739
|
+
* @returns {Promise}
|
|
6740
|
+
*/
|
|
6741
|
+
async publishTracks(tracks: LocalTracks): Promise<void> {
|
|
6742
|
+
this.checkMediaConnection();
|
|
6743
|
+
|
|
6744
|
+
this.annotationInfo = tracks.annotationInfo;
|
|
6745
|
+
|
|
6746
|
+
if (
|
|
6747
|
+
!tracks.microphone &&
|
|
6748
|
+
!tracks.camera &&
|
|
6749
|
+
!tracks.screenShare?.audio &&
|
|
6750
|
+
!tracks.screenShare?.video
|
|
6751
|
+
) {
|
|
6752
|
+
// nothing to do
|
|
6753
|
+
return;
|
|
6754
|
+
}
|
|
6755
|
+
|
|
6756
|
+
let floorRequestNeeded = false;
|
|
6757
|
+
|
|
6758
|
+
if (tracks.screenShare?.video) {
|
|
6759
|
+
await this.setLocalShareTrack(tracks.screenShare?.video);
|
|
6760
|
+
|
|
6761
|
+
floorRequestNeeded = true;
|
|
6762
|
+
}
|
|
6763
|
+
|
|
6764
|
+
if (tracks.microphone) {
|
|
6765
|
+
await this.setLocalAudioTrack(tracks.microphone);
|
|
6766
|
+
}
|
|
6767
|
+
|
|
6768
|
+
if (tracks.camera) {
|
|
6769
|
+
await this.setLocalVideoTrack(tracks.camera);
|
|
6770
|
+
}
|
|
6771
|
+
|
|
6772
|
+
if (!this.isMultistream) {
|
|
6773
|
+
await this.updateTranscodedMediaConnection();
|
|
6774
|
+
}
|
|
6775
|
+
|
|
6776
|
+
if (floorRequestNeeded) {
|
|
6777
|
+
// we're sending the http request to Locus to request the screen share floor
|
|
6778
|
+
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
6779
|
+
// and also if sharing from the start, we need confluence to have been created
|
|
6780
|
+
await this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
|
|
6781
|
+
}
|
|
6782
|
+
}
|
|
6783
|
+
|
|
6784
|
+
/**
|
|
6785
|
+
* Un-publishes specified local tracks in the meeting
|
|
6786
|
+
*
|
|
6787
|
+
* @param {Array<MediaStreamTrack>} tracks
|
|
6788
|
+
* @returns {Promise}
|
|
6789
|
+
*/
|
|
6790
|
+
async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
|
|
6791
|
+
this.checkMediaConnection();
|
|
6792
|
+
|
|
6793
|
+
const promises = [];
|
|
6794
|
+
|
|
6795
|
+
for (const track of tracks.filter((t) => !!t)) {
|
|
6796
|
+
if (track === this.mediaProperties.shareTrack) {
|
|
6797
|
+
try {
|
|
6798
|
+
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
6799
|
+
} catch (e) {
|
|
6800
|
+
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
6801
|
+
}
|
|
6802
|
+
promises.push(this.setLocalShareTrack(undefined));
|
|
6803
|
+
}
|
|
6804
|
+
|
|
6805
|
+
if (track === this.mediaProperties.audioTrack) {
|
|
6806
|
+
promises.push(this.setLocalAudioTrack(undefined));
|
|
6807
|
+
}
|
|
6808
|
+
|
|
6809
|
+
if (track === this.mediaProperties.videoTrack) {
|
|
6810
|
+
promises.push(this.setLocalVideoTrack(undefined));
|
|
6811
|
+
}
|
|
6812
|
+
}
|
|
6813
|
+
|
|
6814
|
+
if (!this.isMultistream) {
|
|
6815
|
+
promises.push(this.updateTranscodedMediaConnection());
|
|
6816
|
+
}
|
|
6817
|
+
|
|
6818
|
+
await Promise.all(promises);
|
|
6819
|
+
}
|
|
6843
6820
|
}
|