@webex/plugin-meetings 3.0.0-beta.16 → 3.0.0-beta.161
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 +175 -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/interpretation/collection.js +23 -0
- package/dist/interpretation/collection.js.map +1 -0
- package/dist/interpretation/index.js +214 -0
- package/dist/interpretation/index.js.map +1 -0
- package/dist/interpretation/siLanguage.js +25 -0
- package/dist/interpretation/siLanguage.js.map +1 -0
- package/dist/locus-info/controlsUtils.js +92 -2
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/index.js +317 -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 +97 -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 +2349 -2178
- 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 +444 -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 +43 -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 +97 -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 +987 -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/interpretation/collection.d.ts +5 -0
- package/dist/types/interpretation/index.d.ts +5 -0
- package/dist/types/interpretation/siLanguage.d.ts +5 -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 +1524 -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 +158 -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 +165 -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/interpretation/README.md +51 -0
- package/src/interpretation/collection.ts +19 -0
- package/src/interpretation/index.ts +182 -0
- package/src/interpretation/siLanguage.ts +18 -0
- package/src/locus-info/controlsUtils.ts +110 -0
- package/src/locus-info/index.ts +339 -21
- package/src/locus-info/mediaSharesUtils.ts +48 -0
- package/src/locus-info/parser.ts +2 -1
- package/src/locus-info/selfUtils.ts +86 -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 +1779 -1741
- 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 +422 -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 +42 -0
- package/src/member/types.ts +24 -0
- package/src/member/util.ts +95 -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/interpretation/collection.ts +15 -0
- package/test/unit/spec/interpretation/index.ts +329 -0
- package/test/unit/spec/interpretation/siLanguage.ts +26 -0
- package/test/unit/spec/locus-info/controlsUtils.js +323 -30
- package/test/unit/spec/locus-info/index.js +680 -4
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
- package/test/unit/spec/locus-info/selfConstant.js +48 -0
- package/test/unit/spec/locus-info/selfUtils.js +275 -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 +2695 -1513
- 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 +270 -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 +31 -0
- package/test/unit/spec/member/util.js +408 -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,85 @@ 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,
|
|
91
|
+
INTERPRETATION,
|
|
71
92
|
} from '../constants';
|
|
72
93
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
73
94
|
import ParameterError from '../common/errors/parameter';
|
|
74
|
-
import MediaError from '../common/errors/media';
|
|
75
95
|
import {
|
|
76
96
|
MeetingInfoV2PasswordError,
|
|
77
97
|
MeetingInfoV2CaptchaError,
|
|
98
|
+
MeetingInfoV2PolicyError,
|
|
78
99
|
} from '../meeting-info/meeting-info-v2';
|
|
79
100
|
import BrowserDetection from '../common/browser-detection';
|
|
80
|
-
import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
101
|
+
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
81
102
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
82
103
|
import {
|
|
104
|
+
Configuration as RemoteMediaManagerConfiguration,
|
|
83
105
|
RemoteMediaManager,
|
|
84
106
|
Event as RemoteMediaManagerEvent,
|
|
85
107
|
} from '../multistream/remoteMediaManager';
|
|
86
|
-
import {
|
|
87
|
-
|
|
88
|
-
|
|
108
|
+
import {
|
|
109
|
+
Reaction,
|
|
110
|
+
ReactionServerType,
|
|
111
|
+
SkinToneType,
|
|
112
|
+
ProcessedReaction,
|
|
113
|
+
RelayEvent,
|
|
114
|
+
} from '../reactions/reactions.type';
|
|
115
|
+
import Breakouts from '../breakouts';
|
|
116
|
+
import SimultaneousInterpretation from '../interpretation';
|
|
117
|
+
import Annotation from '../annotation';
|
|
89
118
|
|
|
90
119
|
import InMeetingActions from './in-meeting-actions';
|
|
120
|
+
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
121
|
+
import RecordingController from '../recording-controller';
|
|
122
|
+
import ControlsOptionsManager from '../controls-options-manager';
|
|
123
|
+
import PermissionError from '../common/errors/permission';
|
|
124
|
+
import {LocusMediaRequest} from './locusMediaRequest';
|
|
125
|
+
import {AnnotationInfo} from '../annotation/annotation.types';
|
|
91
126
|
|
|
92
127
|
const {isBrowser} = BrowserDetection();
|
|
93
128
|
|
|
94
|
-
const logRequest = (request: any, {
|
|
95
|
-
LoggerProxy.logger.info(
|
|
129
|
+
const logRequest = (request: any, {logText = ''}) => {
|
|
130
|
+
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
96
131
|
|
|
97
132
|
return request
|
|
98
133
|
.then((arg) => {
|
|
99
|
-
LoggerProxy.logger.info(
|
|
134
|
+
LoggerProxy.logger.info(`${logText} - has been successfully sent`);
|
|
100
135
|
|
|
101
136
|
return arg;
|
|
102
137
|
})
|
|
103
138
|
.catch((error) => {
|
|
104
|
-
LoggerProxy.logger.error(
|
|
139
|
+
LoggerProxy.logger.error(`${logText} - has failed: `, error);
|
|
105
140
|
throw error;
|
|
106
141
|
});
|
|
107
142
|
};
|
|
108
143
|
|
|
144
|
+
export type LocalTracks = {
|
|
145
|
+
microphone?: LocalMicrophoneTrack;
|
|
146
|
+
camera?: LocalCameraTrack;
|
|
147
|
+
screenShare?: {
|
|
148
|
+
audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
|
|
149
|
+
video?: LocalDisplayTrack;
|
|
150
|
+
};
|
|
151
|
+
annotationInfo?: AnnotationInfo;
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
export type AddMediaOptions = {
|
|
155
|
+
localTracks?: LocalTracks;
|
|
156
|
+
audioEnabled?: boolean; // if not specified, default value true is used
|
|
157
|
+
videoEnabled?: boolean; // if not specified, default value true is used
|
|
158
|
+
receiveShare?: boolean; // if not specified, default value true is used
|
|
159
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
|
160
|
+
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
|
161
|
+
};
|
|
162
|
+
|
|
109
163
|
export const MEDIA_UPDATE_TYPE = {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
SHARE: 'SHARE',
|
|
164
|
+
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
165
|
+
SHARE_FLOOR_REQUEST: 'SHARE_FLOOR_REQUEST',
|
|
166
|
+
UPDATE_MEDIA: 'UPDATE_MEDIA',
|
|
114
167
|
};
|
|
115
168
|
|
|
116
169
|
/**
|
|
@@ -125,16 +178,6 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
125
178
|
* @property {boolean} isSharing
|
|
126
179
|
*/
|
|
127
180
|
|
|
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
181
|
/**
|
|
139
182
|
* SharePreferences
|
|
140
183
|
* @typedef {Object} SharePreferences
|
|
@@ -149,18 +192,10 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
149
192
|
* @property {String} [pin]
|
|
150
193
|
* @property {Boolean} [moderator]
|
|
151
194
|
* @property {String|Object} [meetingQuality]
|
|
152
|
-
* @property {String} [meetingQuality.local]
|
|
153
195
|
* @property {String} [meetingQuality.remote]
|
|
154
196
|
* @property {Boolean} [rejoin]
|
|
155
197
|
* @property {Boolean} [enableMultistream]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* SendOptions
|
|
160
|
-
* @typedef {Object} SendOptions
|
|
161
|
-
* @property {Boolean} sendAudio
|
|
162
|
-
* @property {Boolean} sendVideo
|
|
163
|
-
* @property {Boolean} sendShare
|
|
198
|
+
* @property {String} [correlationId]
|
|
164
199
|
*/
|
|
165
200
|
|
|
166
201
|
/**
|
|
@@ -403,12 +438,14 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
403
438
|
export default class Meeting extends StatelessWebexPlugin {
|
|
404
439
|
attrs: any;
|
|
405
440
|
audio: any;
|
|
441
|
+
breakouts: any;
|
|
442
|
+
simultaneousInterpretation: any;
|
|
443
|
+
annotation: any;
|
|
406
444
|
conversationUrl: string;
|
|
407
445
|
correlationId: string;
|
|
408
446
|
destination: string;
|
|
409
447
|
destinationType: string;
|
|
410
448
|
deviceUrl: string;
|
|
411
|
-
effects: any;
|
|
412
449
|
hostId: string;
|
|
413
450
|
id: string;
|
|
414
451
|
isMultistream: boolean;
|
|
@@ -416,7 +453,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
416
453
|
mediaConnections: any[];
|
|
417
454
|
mediaId?: string;
|
|
418
455
|
meetingFiniteStateMachine: any;
|
|
419
|
-
meetingInfo:
|
|
456
|
+
meetingInfo: any;
|
|
420
457
|
meetingRequest: MeetingRequest;
|
|
421
458
|
members: Members;
|
|
422
459
|
options: object;
|
|
@@ -428,6 +465,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
428
465
|
resource: string;
|
|
429
466
|
roap: Roap;
|
|
430
467
|
roapSeq: number;
|
|
468
|
+
selfUrl?: string; // comes from Locus, initialized by updateMeetingObject()
|
|
431
469
|
sipUri: string;
|
|
432
470
|
type: string;
|
|
433
471
|
userId: string;
|
|
@@ -449,32 +487,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
449
487
|
keepAliveTimerId: NodeJS.Timeout;
|
|
450
488
|
lastVideoLayoutInfo: any;
|
|
451
489
|
locusInfo: any;
|
|
452
|
-
|
|
490
|
+
locusMediaRequest?: LocusMediaRequest;
|
|
453
491
|
mediaProperties: MediaProperties;
|
|
454
492
|
mediaRequestManagers: {
|
|
455
493
|
audio: MediaRequestManager;
|
|
456
494
|
video: MediaRequestManager;
|
|
495
|
+
screenShareAudio: MediaRequestManager;
|
|
496
|
+
screenShareVideo: MediaRequestManager;
|
|
457
497
|
};
|
|
458
498
|
|
|
459
499
|
meetingInfoFailureReason: string;
|
|
500
|
+
meetingInfoFailureCode?: number;
|
|
460
501
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
461
502
|
networkStatus: string;
|
|
462
503
|
passwordStatus: string;
|
|
463
504
|
queuedMediaUpdates: any[];
|
|
464
505
|
recording: any;
|
|
465
506
|
remoteMediaManager: RemoteMediaManager | null;
|
|
507
|
+
recordingController: RecordingController;
|
|
508
|
+
controlsOptionsManager: ControlsOptionsManager;
|
|
466
509
|
requiredCaptcha: any;
|
|
467
510
|
receiveSlotManager: ReceiveSlotManager;
|
|
468
511
|
shareStatus: string;
|
|
469
512
|
statsAnalyzer: StatsAnalyzer;
|
|
470
513
|
transcription: Transcription;
|
|
471
514
|
updateMediaConnections: (mediaConnections: any[]) => void;
|
|
472
|
-
|
|
515
|
+
endCallInitJoinReq: any;
|
|
473
516
|
endJoinReqResp: any;
|
|
474
517
|
endLocalSDPGenRemoteSDPRecvDelay: any;
|
|
475
518
|
joinedWith: any;
|
|
476
519
|
locusId: any;
|
|
477
|
-
|
|
520
|
+
startCallInitJoinReq: any;
|
|
478
521
|
startJoinReqResp: any;
|
|
479
522
|
startLocalSDPGenRemoteSDPRecvDelay: any;
|
|
480
523
|
wirelessShare: any;
|
|
@@ -487,8 +530,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
487
530
|
resourceUrl: string;
|
|
488
531
|
selfId: string;
|
|
489
532
|
state: any;
|
|
490
|
-
|
|
533
|
+
localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
534
|
+
localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
535
|
+
underlyingLocalTrackChangeHandler: () => void;
|
|
536
|
+
roles: any[];
|
|
537
|
+
environment: string;
|
|
491
538
|
namespace = MEETINGS;
|
|
539
|
+
annotationInfo: AnnotationInfo;
|
|
492
540
|
|
|
493
541
|
/**
|
|
494
542
|
* @param {Object} attrs
|
|
@@ -523,7 +571,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
523
571
|
*/
|
|
524
572
|
this.id = uuid.v4();
|
|
525
573
|
/**
|
|
526
|
-
* Correlation ID used for network tracking of meeting
|
|
574
|
+
* Correlation ID used for network tracking of meeting
|
|
527
575
|
* @instance
|
|
528
576
|
* @type {String}
|
|
529
577
|
* @readonly
|
|
@@ -573,41 +621,132 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
573
621
|
*/
|
|
574
622
|
// TODO: needs to be defined as a class
|
|
575
623
|
this.meetingInfo = {};
|
|
624
|
+
/**
|
|
625
|
+
* @instance
|
|
626
|
+
* @type {Breakouts}
|
|
627
|
+
* @public
|
|
628
|
+
* @memberof Meeting
|
|
629
|
+
*/
|
|
630
|
+
// @ts-ignore
|
|
631
|
+
this.breakouts = new Breakouts({meetingId: this.id}, {parent: this.webex});
|
|
632
|
+
/**
|
|
633
|
+
* @instance
|
|
634
|
+
* @type {SimultaneousInterpretation}
|
|
635
|
+
* @public
|
|
636
|
+
* @memberof Meeting
|
|
637
|
+
*/
|
|
638
|
+
// @ts-ignore
|
|
639
|
+
this.simultaneousInterpretation = new SimultaneousInterpretation({}, {parent: this.webex});
|
|
640
|
+
/**
|
|
641
|
+
* @instance
|
|
642
|
+
* @type {Annotation}
|
|
643
|
+
* @public
|
|
644
|
+
* @memberof Meeting
|
|
645
|
+
*/
|
|
646
|
+
// @ts-ignore
|
|
647
|
+
this.annotation = new Annotation({parent: this.webex});
|
|
576
648
|
/**
|
|
577
649
|
* helper class for managing receive slots (for multistream media connections)
|
|
578
650
|
*/
|
|
579
|
-
this.receiveSlotManager = new ReceiveSlotManager(
|
|
651
|
+
this.receiveSlotManager = new ReceiveSlotManager(
|
|
652
|
+
(mediaType: MediaType) => {
|
|
653
|
+
if (!this.mediaProperties?.webrtcMediaConnection) {
|
|
654
|
+
return Promise.reject(new Error('Webrtc media connection is missing'));
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
return this.mediaProperties.webrtcMediaConnection.createReceiveSlot(mediaType);
|
|
658
|
+
},
|
|
659
|
+
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
660
|
+
);
|
|
580
661
|
/**
|
|
581
|
-
*
|
|
582
|
-
* All media requests sent out for
|
|
662
|
+
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
663
|
+
* All multistream media requests sent out for this meeting have to go through them.
|
|
583
664
|
*/
|
|
584
665
|
this.mediaRequestManagers = {
|
|
585
|
-
audio: new MediaRequestManager(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
666
|
+
audio: new MediaRequestManager(
|
|
667
|
+
(mediaRequests) => {
|
|
668
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
669
|
+
LoggerProxy.logger.warn(
|
|
670
|
+
'Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created'
|
|
671
|
+
);
|
|
672
|
+
|
|
673
|
+
return;
|
|
674
|
+
}
|
|
675
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
676
|
+
MediaType.AudioMain,
|
|
677
|
+
mediaRequests
|
|
589
678
|
);
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
// @ts-ignore - config coming from registerPlugin
|
|
682
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
683
|
+
kind: 'audio',
|
|
684
|
+
trimRequestsToNumOfSources: false,
|
|
685
|
+
}
|
|
686
|
+
),
|
|
687
|
+
video: new MediaRequestManager(
|
|
688
|
+
(mediaRequests) => {
|
|
689
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
690
|
+
LoggerProxy.logger.warn(
|
|
691
|
+
'Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created'
|
|
692
|
+
);
|
|
590
693
|
|
|
591
|
-
|
|
694
|
+
return;
|
|
695
|
+
}
|
|
696
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
697
|
+
MediaType.VideoMain,
|
|
698
|
+
mediaRequests
|
|
699
|
+
);
|
|
700
|
+
},
|
|
701
|
+
{
|
|
702
|
+
// @ts-ignore - config coming from registerPlugin
|
|
703
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
704
|
+
kind: 'video',
|
|
705
|
+
trimRequestsToNumOfSources: true,
|
|
592
706
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
707
|
+
),
|
|
708
|
+
screenShareAudio: new MediaRequestManager(
|
|
709
|
+
(mediaRequests) => {
|
|
710
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
711
|
+
LoggerProxy.logger.warn(
|
|
712
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare audio media request before media connection was created'
|
|
713
|
+
);
|
|
714
|
+
|
|
715
|
+
return;
|
|
716
|
+
}
|
|
717
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
718
|
+
MediaType.AudioSlides,
|
|
719
|
+
mediaRequests
|
|
602
720
|
);
|
|
721
|
+
},
|
|
722
|
+
{
|
|
723
|
+
// @ts-ignore - config coming from registerPlugin
|
|
724
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
725
|
+
kind: 'audio',
|
|
726
|
+
trimRequestsToNumOfSources: false,
|
|
727
|
+
}
|
|
728
|
+
),
|
|
729
|
+
screenShareVideo: new MediaRequestManager(
|
|
730
|
+
(mediaRequests) => {
|
|
731
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
732
|
+
LoggerProxy.logger.warn(
|
|
733
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare video media request before media connection was created'
|
|
734
|
+
);
|
|
603
735
|
|
|
604
|
-
|
|
736
|
+
return;
|
|
737
|
+
}
|
|
738
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
739
|
+
MediaType.VideoSlides,
|
|
740
|
+
mediaRequests
|
|
741
|
+
);
|
|
742
|
+
},
|
|
743
|
+
{
|
|
744
|
+
// @ts-ignore - config coming from registerPlugin
|
|
745
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
746
|
+
kind: 'video',
|
|
747
|
+
trimRequestsToNumOfSources: false,
|
|
605
748
|
}
|
|
606
|
-
|
|
607
|
-
MC.MediaType.VideoMain,
|
|
608
|
-
mediaRequests
|
|
609
|
-
);
|
|
610
|
-
}),
|
|
749
|
+
),
|
|
611
750
|
};
|
|
612
751
|
/**
|
|
613
752
|
* @instance
|
|
@@ -620,8 +759,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
620
759
|
locusUrl: attrs.locus && attrs.locus.url,
|
|
621
760
|
receiveSlotManager: this.receiveSlotManager,
|
|
622
761
|
mediaRequestManagers: this.mediaRequestManagers,
|
|
623
|
-
|
|
762
|
+
meeting: this,
|
|
624
763
|
},
|
|
764
|
+
// @ts-ignore - Fix type
|
|
625
765
|
{parent: this.webex}
|
|
626
766
|
);
|
|
627
767
|
/**
|
|
@@ -653,7 +793,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
653
793
|
*/
|
|
654
794
|
this.reconnectionManager = new ReconnectionManager(this);
|
|
655
795
|
/**
|
|
656
|
-
* created
|
|
796
|
+
* created with media connection
|
|
657
797
|
* @instance
|
|
658
798
|
* @type {MuteState}
|
|
659
799
|
* @private
|
|
@@ -661,21 +801,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
661
801
|
*/
|
|
662
802
|
this.audio = null;
|
|
663
803
|
/**
|
|
664
|
-
* created
|
|
804
|
+
* created with media connection
|
|
665
805
|
* @instance
|
|
666
806
|
* @type {MuteState}
|
|
667
807
|
* @private
|
|
668
808
|
* @memberof Meeting
|
|
669
809
|
*/
|
|
670
810
|
this.video = null;
|
|
671
|
-
/**
|
|
672
|
-
* created later
|
|
673
|
-
* @instance
|
|
674
|
-
* @type {EffectsState}
|
|
675
|
-
* @private
|
|
676
|
-
* @memberof Meeting
|
|
677
|
-
*/
|
|
678
|
-
this.effects = null;
|
|
679
811
|
/**
|
|
680
812
|
* @instance
|
|
681
813
|
* @type {MeetingStateMachine}
|
|
@@ -770,7 +902,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
770
902
|
* @private
|
|
771
903
|
* @memberof Meeting
|
|
772
904
|
*/
|
|
773
|
-
this.meetingRequest = new MeetingRequest(
|
|
905
|
+
this.meetingRequest = new MeetingRequest(
|
|
906
|
+
{
|
|
907
|
+
meeting: this,
|
|
908
|
+
},
|
|
909
|
+
options
|
|
910
|
+
);
|
|
774
911
|
/**
|
|
775
912
|
* @instance
|
|
776
913
|
* @type {Array}
|
|
@@ -924,6 +1061,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
924
1061
|
*/
|
|
925
1062
|
// @ts-ignore - Fix type
|
|
926
1063
|
this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
|
|
1064
|
+
|
|
927
1065
|
// We had to add listeners first before setting up the locus instance
|
|
928
1066
|
/**
|
|
929
1067
|
* @instance
|
|
@@ -1014,6 +1152,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1014
1152
|
*/
|
|
1015
1153
|
this.meetingInfoFailureReason = undefined;
|
|
1016
1154
|
|
|
1155
|
+
/**
|
|
1156
|
+
* The numeric code, if any, associated with the last failure to obtain the meeting info
|
|
1157
|
+
* @instance
|
|
1158
|
+
* @type {number}
|
|
1159
|
+
* @private
|
|
1160
|
+
* @memberof Meeting
|
|
1161
|
+
*/
|
|
1162
|
+
this.meetingInfoFailureCode = undefined;
|
|
1163
|
+
|
|
1017
1164
|
/**
|
|
1018
1165
|
* Repeating timer used to send keepAlives when in lobby
|
|
1019
1166
|
* @instance
|
|
@@ -1023,16 +1170,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1023
1170
|
*/
|
|
1024
1171
|
this.keepAliveTimerId = null;
|
|
1025
1172
|
|
|
1173
|
+
/**
|
|
1174
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1175
|
+
* @instance
|
|
1176
|
+
* @type {RecordingController}
|
|
1177
|
+
* @public
|
|
1178
|
+
* @memberof Meeting
|
|
1179
|
+
*/
|
|
1180
|
+
this.recordingController = new RecordingController(this.meetingRequest, {
|
|
1181
|
+
serviceUrl: this.locusInfo?.links?.services?.record?.url,
|
|
1182
|
+
sessionId: this.locusInfo?.fullState?.sessionId,
|
|
1183
|
+
locusUrl: this.locusInfo?.url,
|
|
1184
|
+
displayHints: [],
|
|
1185
|
+
});
|
|
1186
|
+
|
|
1187
|
+
/**
|
|
1188
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1189
|
+
* @instance
|
|
1190
|
+
* @type {ControlsOptionsManager}
|
|
1191
|
+
* @public
|
|
1192
|
+
* @memberof Meeting
|
|
1193
|
+
*/
|
|
1194
|
+
this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
|
|
1195
|
+
locusUrl: this.locusInfo?.url,
|
|
1196
|
+
displayHints: [],
|
|
1197
|
+
});
|
|
1198
|
+
|
|
1026
1199
|
this.setUpLocusInfoListeners();
|
|
1027
1200
|
this.locusInfo.init(attrs.locus ? attrs.locus : {});
|
|
1028
1201
|
this.hasJoinedOnce = false;
|
|
1029
1202
|
|
|
1030
|
-
this.media = new MultistreamMedia(this);
|
|
1031
|
-
|
|
1032
1203
|
/**
|
|
1033
1204
|
* helper class for managing remote streams
|
|
1034
1205
|
*/
|
|
1035
1206
|
this.remoteMediaManager = null;
|
|
1207
|
+
|
|
1208
|
+
this.localAudioTrackMuteStateHandler = (event) => {
|
|
1209
|
+
this.audio.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1210
|
+
};
|
|
1211
|
+
|
|
1212
|
+
this.localVideoTrackMuteStateHandler = (event) => {
|
|
1213
|
+
this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1214
|
+
};
|
|
1215
|
+
|
|
1216
|
+
// The handling of underlying track changes should be done inside
|
|
1217
|
+
// @webex/internal-media-core, but for now we have to do it here, because
|
|
1218
|
+
// RoapMediaConnection has to use raw MediaStreamTracks in its API until
|
|
1219
|
+
// the Calling SDK also moves to using webrtc-core tracks
|
|
1220
|
+
this.underlyingLocalTrackChangeHandler = () => {
|
|
1221
|
+
if (!this.isMultistream) {
|
|
1222
|
+
this.updateTranscodedMediaConnection();
|
|
1223
|
+
}
|
|
1224
|
+
};
|
|
1225
|
+
}
|
|
1226
|
+
|
|
1227
|
+
/**
|
|
1228
|
+
* returns meeting is joined
|
|
1229
|
+
* @private
|
|
1230
|
+
* @memberof Meeting
|
|
1231
|
+
* @returns {Boolean}
|
|
1232
|
+
*/
|
|
1233
|
+
private isJoined() {
|
|
1234
|
+
return this.joinedWith?.state === 'JOINED';
|
|
1036
1235
|
}
|
|
1037
1236
|
|
|
1038
1237
|
/**
|
|
@@ -1047,9 +1246,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1047
1246
|
public async fetchMeetingInfo({
|
|
1048
1247
|
password = null,
|
|
1049
1248
|
captchaCode = null,
|
|
1249
|
+
extraParams = {},
|
|
1050
1250
|
}: {
|
|
1051
1251
|
password?: string;
|
|
1052
1252
|
captchaCode?: string;
|
|
1253
|
+
extraParams?: Record<string, any>;
|
|
1053
1254
|
}) {
|
|
1054
1255
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1055
1256
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
@@ -1080,11 +1281,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1080
1281
|
this.destination,
|
|
1081
1282
|
this.destinationType,
|
|
1082
1283
|
password,
|
|
1083
|
-
captchaInfo
|
|
1284
|
+
captchaInfo,
|
|
1285
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1286
|
+
this.config.installedOrgID,
|
|
1287
|
+
this.locusId,
|
|
1288
|
+
extraParams,
|
|
1289
|
+
{meetingId: this.id}
|
|
1084
1290
|
);
|
|
1085
1291
|
|
|
1086
1292
|
this.parseMeetingInfo(info, this.destination);
|
|
1087
|
-
this.meetingInfo = info ? info.body : null;
|
|
1293
|
+
this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
|
|
1088
1294
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1089
1295
|
this.requiredCaptcha = null;
|
|
1090
1296
|
if (
|
|
@@ -1107,9 +1313,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1107
1313
|
|
|
1108
1314
|
return Promise.resolve();
|
|
1109
1315
|
} catch (err) {
|
|
1110
|
-
if (err instanceof
|
|
1111
|
-
|
|
1316
|
+
if (err instanceof MeetingInfoV2PolicyError) {
|
|
1317
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.POLICY;
|
|
1318
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1319
|
+
|
|
1320
|
+
if (err.meetingInfo) {
|
|
1321
|
+
this.meetingInfo = err.meetingInfo;
|
|
1322
|
+
}
|
|
1323
|
+
|
|
1324
|
+
throw new PermissionError();
|
|
1325
|
+
} else if (err instanceof MeetingInfoV2PasswordError) {
|
|
1112
1326
|
LoggerProxy.logger.info(
|
|
1327
|
+
// @ts-ignore
|
|
1113
1328
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`
|
|
1114
1329
|
);
|
|
1115
1330
|
|
|
@@ -1119,6 +1334,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1119
1334
|
this.meetingNumber = err.meetingInfo.meetingNumber;
|
|
1120
1335
|
}
|
|
1121
1336
|
|
|
1337
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1338
|
+
|
|
1122
1339
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1123
1340
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1124
1341
|
if (this.requiredCaptcha) {
|
|
@@ -1128,8 +1345,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1128
1345
|
|
|
1129
1346
|
throw new PasswordError();
|
|
1130
1347
|
} else if (err instanceof MeetingInfoV2CaptchaError) {
|
|
1131
|
-
// @ts-ignore
|
|
1132
1348
|
LoggerProxy.logger.info(
|
|
1349
|
+
// @ts-ignore
|
|
1133
1350
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
|
|
1134
1351
|
);
|
|
1135
1352
|
|
|
@@ -1137,6 +1354,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1137
1354
|
? MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA
|
|
1138
1355
|
: MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1139
1356
|
|
|
1357
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1358
|
+
|
|
1140
1359
|
if (err.isPasswordRequired) {
|
|
1141
1360
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1142
1361
|
}
|
|
@@ -1201,22 +1420,39 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1201
1420
|
// we have to pass the wbxappapi hostname as the siteFullName parameter
|
|
1202
1421
|
const {hostname} = new URL(this.requiredCaptcha.refreshURL);
|
|
1203
1422
|
|
|
1204
|
-
return
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1423
|
+
return (
|
|
1424
|
+
this.meetingRequest
|
|
1425
|
+
// @ts-ignore
|
|
1426
|
+
.refreshCaptcha({
|
|
1427
|
+
captchaRefreshUrl: `${this.requiredCaptcha.refreshURL}&siteFullName=${hostname}`,
|
|
1428
|
+
captchaId: this.requiredCaptcha.captchaId,
|
|
1429
|
+
})
|
|
1430
|
+
.then((response) => {
|
|
1431
|
+
this.requiredCaptcha.captchaId = response.body.captchaID;
|
|
1432
|
+
this.requiredCaptcha.verificationImageURL = response.body.verificationImageURL;
|
|
1433
|
+
this.requiredCaptcha.verificationAudioURL = response.body.verificationAudioURL;
|
|
1434
|
+
})
|
|
1435
|
+
.catch((error) => {
|
|
1436
|
+
LoggerProxy.logger.error(
|
|
1437
|
+
`Meeting:index#refreshCaptcha --> Error Unable to refresh captcha for ${this.destination} - ${error}`
|
|
1438
|
+
);
|
|
1439
|
+
throw error;
|
|
1440
|
+
})
|
|
1441
|
+
);
|
|
1442
|
+
}
|
|
1443
|
+
|
|
1444
|
+
/**
|
|
1445
|
+
* Posts metrics event for this meeting. Allows the app to send Call Analyzer events.
|
|
1446
|
+
* @param {String} eventName - Call Analyzer event, see eventType in src/metrics/config.ts for possible values
|
|
1447
|
+
* @public
|
|
1448
|
+
* @memberof Meeting
|
|
1449
|
+
* @returns {Promise}
|
|
1450
|
+
*/
|
|
1451
|
+
public postMetrics(eventName: string) {
|
|
1452
|
+
Metrics.postEvent({
|
|
1453
|
+
event: eventName,
|
|
1454
|
+
meeting: this,
|
|
1455
|
+
});
|
|
1220
1456
|
}
|
|
1221
1457
|
|
|
1222
1458
|
/**
|
|
@@ -1229,6 +1465,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1229
1465
|
// meeting update listeners
|
|
1230
1466
|
this.setUpLocusInfoSelfListener();
|
|
1231
1467
|
this.setUpLocusInfoMeetingListener();
|
|
1468
|
+
this.setUpLocusServicesListener();
|
|
1232
1469
|
// members update listeners
|
|
1233
1470
|
this.setUpLocusFullStateListener();
|
|
1234
1471
|
this.setUpLocusUrlListener();
|
|
@@ -1241,6 +1478,116 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1241
1478
|
this.setUpLocusInfoMeetingInfoListener();
|
|
1242
1479
|
this.setUpLocusInfoAssignHostListener();
|
|
1243
1480
|
this.setUpLocusInfoMediaInactiveListener();
|
|
1481
|
+
this.setUpBreakoutsListener();
|
|
1482
|
+
this.setUpInterpretationListener();
|
|
1483
|
+
}
|
|
1484
|
+
|
|
1485
|
+
/**
|
|
1486
|
+
* Set up the listeners for breakouts
|
|
1487
|
+
* @returns {undefined}
|
|
1488
|
+
* @private
|
|
1489
|
+
* @memberof Meeting
|
|
1490
|
+
*/
|
|
1491
|
+
setUpBreakoutsListener() {
|
|
1492
|
+
this.breakouts.on(BREAKOUTS.EVENTS.BREAKOUTS_CLOSING, () => {
|
|
1493
|
+
Trigger.trigger(
|
|
1494
|
+
this,
|
|
1495
|
+
{
|
|
1496
|
+
file: 'meeting/index',
|
|
1497
|
+
function: 'setUpBreakoutsListener',
|
|
1498
|
+
},
|
|
1499
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
|
|
1500
|
+
);
|
|
1501
|
+
});
|
|
1502
|
+
|
|
1503
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MESSAGE, (messageEvent) => {
|
|
1504
|
+
Trigger.trigger(
|
|
1505
|
+
this,
|
|
1506
|
+
{
|
|
1507
|
+
file: 'meeting/index',
|
|
1508
|
+
function: 'setUpBreakoutsListener',
|
|
1509
|
+
},
|
|
1510
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
|
|
1511
|
+
messageEvent
|
|
1512
|
+
);
|
|
1513
|
+
});
|
|
1514
|
+
|
|
1515
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MEMBERS_UPDATE, () => {
|
|
1516
|
+
Trigger.trigger(
|
|
1517
|
+
this,
|
|
1518
|
+
{
|
|
1519
|
+
file: 'meeting/index',
|
|
1520
|
+
function: 'setUpBreakoutsListener',
|
|
1521
|
+
},
|
|
1522
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
1523
|
+
);
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_RETURN_TO_MAIN, () => {
|
|
1527
|
+
if (this.isJoined()) {
|
|
1528
|
+
Trigger.trigger(
|
|
1529
|
+
this,
|
|
1530
|
+
{
|
|
1531
|
+
file: 'meeting/index',
|
|
1532
|
+
function: 'setUpBreakoutsListener',
|
|
1533
|
+
},
|
|
1534
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_RETURN_TO_MAIN
|
|
1535
|
+
);
|
|
1536
|
+
}
|
|
1537
|
+
});
|
|
1538
|
+
|
|
1539
|
+
this.breakouts.on(BREAKOUTS.EVENTS.LEAVE_BREAKOUT, () => {
|
|
1540
|
+
Trigger.trigger(
|
|
1541
|
+
this,
|
|
1542
|
+
{
|
|
1543
|
+
file: 'meeting/index',
|
|
1544
|
+
function: 'setUpBreakoutsListener',
|
|
1545
|
+
},
|
|
1546
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_LEAVE
|
|
1547
|
+
);
|
|
1548
|
+
});
|
|
1549
|
+
|
|
1550
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_FOR_HELP, (helpEvent) => {
|
|
1551
|
+
Trigger.trigger(
|
|
1552
|
+
this,
|
|
1553
|
+
{
|
|
1554
|
+
file: 'meeting/index',
|
|
1555
|
+
function: 'setUpBreakoutsListener',
|
|
1556
|
+
},
|
|
1557
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_FOR_HELP,
|
|
1558
|
+
helpEvent
|
|
1559
|
+
);
|
|
1560
|
+
});
|
|
1561
|
+
|
|
1562
|
+
this.breakouts.on(BREAKOUTS.EVENTS.PRE_ASSIGNMENTS_UPDATE, () => {
|
|
1563
|
+
Trigger.trigger(
|
|
1564
|
+
this,
|
|
1565
|
+
{
|
|
1566
|
+
file: 'meeting/index',
|
|
1567
|
+
function: 'setUpBreakoutsListener',
|
|
1568
|
+
},
|
|
1569
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE
|
|
1570
|
+
);
|
|
1571
|
+
});
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
/**
|
|
1575
|
+
* Set up the listeners for interpretation
|
|
1576
|
+
* @returns {undefined}
|
|
1577
|
+
* @private
|
|
1578
|
+
* @memberof Meeting
|
|
1579
|
+
*/
|
|
1580
|
+
private setUpInterpretationListener() {
|
|
1581
|
+
this.simultaneousInterpretation.on(INTERPRETATION.EVENTS.SUPPORT_LANGUAGES_UPDATE, () => {
|
|
1582
|
+
Trigger.trigger(
|
|
1583
|
+
this,
|
|
1584
|
+
{
|
|
1585
|
+
file: 'meeting/index',
|
|
1586
|
+
function: 'setUpInterpretationListener',
|
|
1587
|
+
},
|
|
1588
|
+
EVENT_TRIGGERS.MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE
|
|
1589
|
+
);
|
|
1590
|
+
});
|
|
1244
1591
|
}
|
|
1245
1592
|
|
|
1246
1593
|
/**
|
|
@@ -1356,19 +1703,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1356
1703
|
* @returns {Object}
|
|
1357
1704
|
* @memberof Meeting
|
|
1358
1705
|
*/
|
|
1359
|
-
getAnalyzerMetricsPrePayload(
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1706
|
+
getAnalyzerMetricsPrePayload(options: {
|
|
1707
|
+
type?: string;
|
|
1708
|
+
event: string;
|
|
1709
|
+
trackingId: string;
|
|
1710
|
+
locus: object;
|
|
1711
|
+
mediaConnections?: Array<any>;
|
|
1712
|
+
errors?: object;
|
|
1713
|
+
meetingLookupUrl?: string;
|
|
1714
|
+
clientType?: any;
|
|
1715
|
+
subClientType?: any;
|
|
1716
|
+
[key: string]: any;
|
|
1717
|
+
}) {
|
|
1370
1718
|
if (options) {
|
|
1371
|
-
const {event, trackingId, mediaConnections} = options;
|
|
1719
|
+
const {event, trackingId, mediaConnections, meetingLookupUrl} = options;
|
|
1372
1720
|
|
|
1373
1721
|
if (!event) {
|
|
1374
1722
|
LoggerProxy.logger.error(
|
|
@@ -1407,6 +1755,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1407
1755
|
identifiers.mediaAgentCluster = this.mediaConnections?.[0].mediaAgentCluster;
|
|
1408
1756
|
}
|
|
1409
1757
|
|
|
1758
|
+
if (meetingLookupUrl) {
|
|
1759
|
+
identifiers.meetingLookupUrl = meetingLookupUrl;
|
|
1760
|
+
}
|
|
1761
|
+
|
|
1410
1762
|
if (options.trackingId) {
|
|
1411
1763
|
identifiers.trackingId = trackingId;
|
|
1412
1764
|
}
|
|
@@ -1456,12 +1808,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1456
1808
|
};
|
|
1457
1809
|
}
|
|
1458
1810
|
|
|
1459
|
-
const
|
|
1811
|
+
const callInitJoinReq = this.getCallInitJoinReq();
|
|
1460
1812
|
|
|
1461
|
-
if (
|
|
1813
|
+
if (callInitJoinReq) {
|
|
1462
1814
|
options.joinTimes = {
|
|
1463
1815
|
...options.joinTimes,
|
|
1464
|
-
|
|
1816
|
+
callInitJoinReq,
|
|
1465
1817
|
};
|
|
1466
1818
|
}
|
|
1467
1819
|
|
|
@@ -1474,15 +1826,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1474
1826
|
};
|
|
1475
1827
|
}
|
|
1476
1828
|
|
|
1477
|
-
const
|
|
1829
|
+
const totalJmt = this.getTotalJmt();
|
|
1478
1830
|
|
|
1479
|
-
if (
|
|
1831
|
+
if (totalJmt) {
|
|
1480
1832
|
options.joinTimes = {
|
|
1481
1833
|
...options.joinTimes,
|
|
1482
|
-
|
|
1834
|
+
totalJmt,
|
|
1483
1835
|
};
|
|
1484
1836
|
}
|
|
1485
1837
|
|
|
1838
|
+
const curUserType = this.getCurUserType();
|
|
1839
|
+
|
|
1840
|
+
if (curUserType) {
|
|
1841
|
+
options.userType = curUserType;
|
|
1842
|
+
}
|
|
1843
|
+
|
|
1844
|
+
const curLoginType = this.getCurLoginType();
|
|
1845
|
+
|
|
1846
|
+
if (curLoginType) {
|
|
1847
|
+
options.loginType = curLoginType;
|
|
1848
|
+
}
|
|
1849
|
+
|
|
1850
|
+
if (this.environment) {
|
|
1851
|
+
options.environment = this.environment;
|
|
1852
|
+
}
|
|
1853
|
+
|
|
1486
1854
|
if (options.type === MQA_STATS.CA_TYPE) {
|
|
1487
1855
|
payload = Metrics.initMediaPayload(options.event, identifiers, options);
|
|
1488
1856
|
} else {
|
|
@@ -1596,11 +1964,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1596
1964
|
this.pstnUpdate(payload);
|
|
1597
1965
|
|
|
1598
1966
|
// 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
|
-
}
|
|
1967
|
+
this.requestScreenShareFloorIfPending();
|
|
1604
1968
|
});
|
|
1605
1969
|
}
|
|
1606
1970
|
|
|
@@ -1786,6 +2150,43 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1786
2150
|
}
|
|
1787
2151
|
);
|
|
1788
2152
|
|
|
2153
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED, ({breakout}) => {
|
|
2154
|
+
this.breakouts.updateBreakout(breakout);
|
|
2155
|
+
Trigger.trigger(
|
|
2156
|
+
this,
|
|
2157
|
+
{
|
|
2158
|
+
file: 'meeting/index',
|
|
2159
|
+
function: 'setupLocusControlsListener',
|
|
2160
|
+
},
|
|
2161
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2162
|
+
);
|
|
2163
|
+
});
|
|
2164
|
+
|
|
2165
|
+
this.locusInfo.on(
|
|
2166
|
+
LOCUSINFO.EVENTS.CONTROLS_MEETING_INTERPRETATION_UPDATED,
|
|
2167
|
+
({interpretation}) => {
|
|
2168
|
+
this.simultaneousInterpretation.updateInterpretation(interpretation);
|
|
2169
|
+
Trigger.trigger(
|
|
2170
|
+
this,
|
|
2171
|
+
{
|
|
2172
|
+
file: 'meeting/index',
|
|
2173
|
+
function: 'setupLocusControlsListener',
|
|
2174
|
+
},
|
|
2175
|
+
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
2176
|
+
);
|
|
2177
|
+
}
|
|
2178
|
+
);
|
|
2179
|
+
|
|
2180
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
|
|
2181
|
+
this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
|
|
2182
|
+
// clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
|
|
2183
|
+
// which means main session is not active for the attendee
|
|
2184
|
+
if (error?.statusCode === 403) {
|
|
2185
|
+
this.locusInfo.clearMainSessionLocusCache();
|
|
2186
|
+
}
|
|
2187
|
+
});
|
|
2188
|
+
});
|
|
2189
|
+
|
|
1789
2190
|
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
|
|
1790
2191
|
Trigger.trigger(
|
|
1791
2192
|
this,
|
|
@@ -1797,6 +2198,96 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1797
2198
|
{entryExitTone}
|
|
1798
2199
|
);
|
|
1799
2200
|
});
|
|
2201
|
+
|
|
2202
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED, ({state}) => {
|
|
2203
|
+
Trigger.trigger(
|
|
2204
|
+
this,
|
|
2205
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2206
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_MUTE_ON_ENTRY_UPDATED,
|
|
2207
|
+
{state}
|
|
2208
|
+
);
|
|
2209
|
+
});
|
|
2210
|
+
|
|
2211
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED, ({state}) => {
|
|
2212
|
+
Trigger.trigger(
|
|
2213
|
+
this,
|
|
2214
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2215
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_SHARE_CONTROL_UPDATED,
|
|
2216
|
+
{state}
|
|
2217
|
+
);
|
|
2218
|
+
});
|
|
2219
|
+
|
|
2220
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED, ({state}) => {
|
|
2221
|
+
Trigger.trigger(
|
|
2222
|
+
this,
|
|
2223
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2224
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED,
|
|
2225
|
+
{state}
|
|
2226
|
+
);
|
|
2227
|
+
});
|
|
2228
|
+
|
|
2229
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED, ({state}) => {
|
|
2230
|
+
Trigger.trigger(
|
|
2231
|
+
this,
|
|
2232
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2233
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REACTIONS_UPDATED,
|
|
2234
|
+
{state}
|
|
2235
|
+
);
|
|
2236
|
+
});
|
|
2237
|
+
|
|
2238
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED, ({state}) => {
|
|
2239
|
+
Trigger.trigger(
|
|
2240
|
+
this,
|
|
2241
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2242
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED,
|
|
2243
|
+
{state}
|
|
2244
|
+
);
|
|
2245
|
+
});
|
|
2246
|
+
|
|
2247
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED, ({state}) => {
|
|
2248
|
+
Trigger.trigger(
|
|
2249
|
+
this,
|
|
2250
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2251
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_RAISE_HAND_UPDATED,
|
|
2252
|
+
{state}
|
|
2253
|
+
);
|
|
2254
|
+
});
|
|
2255
|
+
|
|
2256
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED, ({state}) => {
|
|
2257
|
+
Trigger.trigger(
|
|
2258
|
+
this,
|
|
2259
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2260
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIDEO_UPDATED,
|
|
2261
|
+
{state}
|
|
2262
|
+
);
|
|
2263
|
+
});
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
/**
|
|
2267
|
+
* Trigger annotation info update event
|
|
2268
|
+
@returns {undefined}
|
|
2269
|
+
@param {object} contentShare
|
|
2270
|
+
@param {object} previousContentShare
|
|
2271
|
+
*/
|
|
2272
|
+
private triggerAnnotationInfoEvent(contentShare, previousContentShare) {
|
|
2273
|
+
if (
|
|
2274
|
+
contentShare?.annotation &&
|
|
2275
|
+
!isEqual(contentShare?.annotation, previousContentShare?.annotation)
|
|
2276
|
+
) {
|
|
2277
|
+
Trigger.trigger(
|
|
2278
|
+
// @ts-ignore
|
|
2279
|
+
this.webex.meetings,
|
|
2280
|
+
{
|
|
2281
|
+
file: 'meeting/index',
|
|
2282
|
+
function: 'triggerAnnotationInfoEvent',
|
|
2283
|
+
},
|
|
2284
|
+
EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
|
|
2285
|
+
{
|
|
2286
|
+
annotationInfo: contentShare?.annotation,
|
|
2287
|
+
meetingId: this.id,
|
|
2288
|
+
}
|
|
2289
|
+
);
|
|
2290
|
+
}
|
|
1800
2291
|
}
|
|
1801
2292
|
|
|
1802
2293
|
/**
|
|
@@ -1809,11 +2300,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1809
2300
|
*/
|
|
1810
2301
|
private setUpLocusMediaSharesListener() {
|
|
1811
2302
|
// Will get triggered on local and remote share
|
|
1812
|
-
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, (payload) => {
|
|
2303
|
+
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, async (payload) => {
|
|
1813
2304
|
const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
|
|
1814
2305
|
const previousContentShare = payload.previous?.content;
|
|
1815
2306
|
const previousWhiteboardShare = payload.previous?.whiteboard;
|
|
1816
2307
|
|
|
2308
|
+
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2309
|
+
|
|
1817
2310
|
if (
|
|
1818
2311
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
1819
2312
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
@@ -1841,19 +2334,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1841
2334
|
this.selfId === contentShare.beneficiaryId &&
|
|
1842
2335
|
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
1843
2336
|
) {
|
|
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
|
-
}
|
|
2337
|
+
// CONTENT - sharing content local
|
|
2338
|
+
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
1857
2339
|
}
|
|
1858
2340
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
1859
2341
|
// There is no concept of local/remote share for whiteboard
|
|
@@ -1937,23 +2419,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1937
2419
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
1938
2420
|
{
|
|
1939
2421
|
memberId: contentShare.beneficiaryId,
|
|
2422
|
+
url: contentShare.url,
|
|
2423
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
1940
2424
|
}
|
|
1941
2425
|
);
|
|
1942
2426
|
};
|
|
1943
2427
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2428
|
+
try {
|
|
2429
|
+
// if a remote participant is stealing the presentation from us
|
|
2430
|
+
if (
|
|
2431
|
+
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2432
|
+
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2433
|
+
) {
|
|
2434
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2435
|
+
}
|
|
2436
|
+
} finally {
|
|
1949
2437
|
sendStartedSharingRemote();
|
|
1950
|
-
} else {
|
|
1951
|
-
this.updateShare({
|
|
1952
|
-
sendShare: false,
|
|
1953
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
1954
|
-
}).finally(() => {
|
|
1955
|
-
sendStartedSharingRemote();
|
|
1956
|
-
});
|
|
1957
2438
|
}
|
|
1958
2439
|
break;
|
|
1959
2440
|
}
|
|
@@ -2007,6 +2488,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2007
2488
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
2008
2489
|
{
|
|
2009
2490
|
memberId: contentShare.beneficiaryId,
|
|
2491
|
+
url: contentShare.url,
|
|
2492
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
2010
2493
|
}
|
|
2011
2494
|
);
|
|
2012
2495
|
this.members.locusMediaSharesUpdate(payload);
|
|
@@ -2041,8 +2524,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2041
2524
|
private setUpLocusUrlListener() {
|
|
2042
2525
|
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
|
|
2043
2526
|
this.members.locusUrlUpdate(payload);
|
|
2527
|
+
this.breakouts.locusUrlUpdate(payload);
|
|
2528
|
+
this.simultaneousInterpretation.locusUrlUpdate(payload);
|
|
2529
|
+
this.annotation.locusUrlUpdate(payload);
|
|
2044
2530
|
this.locusUrl = payload;
|
|
2045
2531
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2532
|
+
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2533
|
+
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2534
|
+
});
|
|
2535
|
+
}
|
|
2536
|
+
|
|
2537
|
+
/**
|
|
2538
|
+
* Set up the locus info service link listener
|
|
2539
|
+
* update the locusInfo for recording controller
|
|
2540
|
+
* does not currently re-emit the event as it's internal only
|
|
2541
|
+
* payload is unused
|
|
2542
|
+
* @returns {undefined}
|
|
2543
|
+
* @private
|
|
2544
|
+
* @memberof Meeting
|
|
2545
|
+
*/
|
|
2546
|
+
private setUpLocusServicesListener() {
|
|
2547
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
|
|
2548
|
+
this.recordingController.setServiceUrl(payload?.services?.record?.url);
|
|
2549
|
+
this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
|
|
2550
|
+
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2551
|
+
this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2046
2552
|
});
|
|
2047
2553
|
}
|
|
2048
2554
|
|
|
@@ -2092,10 +2598,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2092
2598
|
canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
|
|
2093
2599
|
canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
|
|
2094
2600
|
canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2601
|
+
canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(
|
|
2602
|
+
payload.info.userDisplayHints
|
|
2603
|
+
),
|
|
2604
|
+
canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(
|
|
2605
|
+
payload.info.userDisplayHints
|
|
2606
|
+
),
|
|
2607
|
+
canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(payload.info.userDisplayHints),
|
|
2608
|
+
canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
|
|
2609
|
+
payload.info.userDisplayHints
|
|
2610
|
+
),
|
|
2611
|
+
canSetMuted: ControlsOptionsUtil.canSetMuted(payload.info.userDisplayHints),
|
|
2612
|
+
canUnsetMuted: ControlsOptionsUtil.canUnsetMuted(payload.info.userDisplayHints),
|
|
2613
|
+
canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
|
|
2614
|
+
canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
|
|
2615
|
+
canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
|
|
2616
|
+
canResumeRecording: RecordingUtil.canUserResume(payload.info.userDisplayHints),
|
|
2099
2617
|
canRaiseHand: MeetingUtil.canUserRaiseHand(payload.info.userDisplayHints),
|
|
2100
2618
|
canLowerAllHands: MeetingUtil.canUserLowerAllHands(payload.info.userDisplayHints),
|
|
2101
2619
|
canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(
|
|
@@ -2108,6 +2626,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2108
2626
|
canStartTranscribing: MeetingUtil.canStartTranscribing(payload.info.userDisplayHints),
|
|
2109
2627
|
canStopTranscribing: MeetingUtil.canStopTranscribing(payload.info.userDisplayHints),
|
|
2110
2628
|
isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(payload.info.userDisplayHints),
|
|
2629
|
+
isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(
|
|
2630
|
+
payload.info.userDisplayHints
|
|
2631
|
+
),
|
|
2111
2632
|
isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(payload.info.userDisplayHints),
|
|
2112
2633
|
canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(payload.info.userDisplayHints),
|
|
2113
2634
|
isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
|
|
@@ -2117,8 +2638,118 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2117
2638
|
payload.info.userDisplayHints
|
|
2118
2639
|
),
|
|
2119
2640
|
waitingForOthersToJoin: MeetingUtil.waitingForOthersToJoin(payload.info.userDisplayHints),
|
|
2641
|
+
canSendReactions: MeetingUtil.canSendReactions(
|
|
2642
|
+
this.inMeetingActions.canSendReactions,
|
|
2643
|
+
payload.info.userDisplayHints
|
|
2644
|
+
),
|
|
2645
|
+
canManageBreakout: MeetingUtil.canManageBreakout(payload.info.userDisplayHints),
|
|
2646
|
+
canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
|
|
2647
|
+
payload.info.userDisplayHints
|
|
2648
|
+
),
|
|
2649
|
+
canAdmitLobbyToBreakout: MeetingUtil.canAdmitLobbyToBreakout(
|
|
2650
|
+
payload.info.userDisplayHints
|
|
2651
|
+
),
|
|
2652
|
+
isBreakoutPreassignmentsEnabled: MeetingUtil.isBreakoutPreassignmentsEnabled(
|
|
2653
|
+
payload.info.userDisplayHints
|
|
2654
|
+
),
|
|
2655
|
+
canUserAskForHelp: MeetingUtil.canUserAskForHelp(payload.info.userDisplayHints),
|
|
2656
|
+
canUserRenameSelfAndObserved: MeetingUtil.canUserRenameSelfAndObserved(
|
|
2657
|
+
payload.info.userDisplayHints
|
|
2658
|
+
),
|
|
2659
|
+
canUserRenameOthers: MeetingUtil.canUserRenameOthers(payload.info.userDisplayHints),
|
|
2660
|
+
canMuteAll: ControlsOptionsUtil.hasHints({
|
|
2661
|
+
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
2662
|
+
displayHints: payload.info.userDisplayHints,
|
|
2663
|
+
}),
|
|
2664
|
+
canUnmuteAll: ControlsOptionsUtil.hasHints({
|
|
2665
|
+
requiredHints: [DISPLAY_HINTS.UNMUTE_ALL],
|
|
2666
|
+
displayHints: payload.info.userDisplayHints,
|
|
2667
|
+
}),
|
|
2668
|
+
canEnableHardMute: ControlsOptionsUtil.hasHints({
|
|
2669
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_HARD_MUTE],
|
|
2670
|
+
displayHints: payload.info.userDisplayHints,
|
|
2671
|
+
}),
|
|
2672
|
+
canDisableHardMute: ControlsOptionsUtil.hasHints({
|
|
2673
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_HARD_MUTE],
|
|
2674
|
+
displayHints: payload.info.userDisplayHints,
|
|
2675
|
+
}),
|
|
2676
|
+
canEnableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2677
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY],
|
|
2678
|
+
displayHints: payload.info.userDisplayHints,
|
|
2679
|
+
}),
|
|
2680
|
+
canDisableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2681
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY],
|
|
2682
|
+
displayHints: payload.info.userDisplayHints,
|
|
2683
|
+
}),
|
|
2684
|
+
canEnableReactions: ControlsOptionsUtil.hasHints({
|
|
2685
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_REACTIONS],
|
|
2686
|
+
displayHints: payload.info.userDisplayHints,
|
|
2687
|
+
}),
|
|
2688
|
+
canDisableReactions: ControlsOptionsUtil.hasHints({
|
|
2689
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_REACTIONS],
|
|
2690
|
+
displayHints: payload.info.userDisplayHints,
|
|
2691
|
+
}),
|
|
2692
|
+
canEnableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2693
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_DISPLAY_NAME],
|
|
2694
|
+
displayHints: payload.info.userDisplayHints,
|
|
2695
|
+
}),
|
|
2696
|
+
canDisableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2697
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_DISPLAY_NAME],
|
|
2698
|
+
displayHints: payload.info.userDisplayHints,
|
|
2699
|
+
}),
|
|
2700
|
+
canUpdateShareControl: ControlsOptionsUtil.hasHints({
|
|
2701
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTROL],
|
|
2702
|
+
displayHints: payload.info.userDisplayHints,
|
|
2703
|
+
}),
|
|
2704
|
+
canEnableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2705
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2706
|
+
displayHints: payload.info.userDisplayHints,
|
|
2707
|
+
}),
|
|
2708
|
+
canDisableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2709
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2710
|
+
displayHints: payload.info.userDisplayHints,
|
|
2711
|
+
}),
|
|
2712
|
+
canEnableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2713
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_RAISE_HAND],
|
|
2714
|
+
displayHints: payload.info.userDisplayHints,
|
|
2715
|
+
}),
|
|
2716
|
+
canDisableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2717
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_RAISE_HAND],
|
|
2718
|
+
displayHints: payload.info.userDisplayHints,
|
|
2719
|
+
}),
|
|
2720
|
+
canEnableVideo: ControlsOptionsUtil.hasHints({
|
|
2721
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIDEO],
|
|
2722
|
+
displayHints: payload.info.userDisplayHints,
|
|
2723
|
+
}),
|
|
2724
|
+
canDisableVideo: ControlsOptionsUtil.hasHints({
|
|
2725
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIDEO],
|
|
2726
|
+
displayHints: payload.info.userDisplayHints,
|
|
2727
|
+
}),
|
|
2728
|
+
canShareFile: ControlsOptionsUtil.hasHints({
|
|
2729
|
+
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
|
|
2730
|
+
displayHints: payload.info.userDisplayHints,
|
|
2731
|
+
}),
|
|
2732
|
+
canShareApplication: ControlsOptionsUtil.hasHints({
|
|
2733
|
+
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
2734
|
+
displayHints: payload.info.userDisplayHints,
|
|
2735
|
+
}),
|
|
2736
|
+
canShareCamera: ControlsOptionsUtil.hasHints({
|
|
2737
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CAMERA],
|
|
2738
|
+
displayHints: payload.info.userDisplayHints,
|
|
2739
|
+
}),
|
|
2740
|
+
canShareDesktop: ControlsOptionsUtil.hasHints({
|
|
2741
|
+
requiredHints: [DISPLAY_HINTS.SHARE_DESKTOP],
|
|
2742
|
+
displayHints: payload.info.userDisplayHints,
|
|
2743
|
+
}),
|
|
2744
|
+
canShareContent: ControlsOptionsUtil.hasHints({
|
|
2745
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTENT],
|
|
2746
|
+
displayHints: payload.info.userDisplayHints,
|
|
2747
|
+
}),
|
|
2120
2748
|
});
|
|
2121
2749
|
|
|
2750
|
+
this.recordingController.setDisplayHints(payload.info.userDisplayHints);
|
|
2751
|
+
this.controlsOptionsManager.setDisplayHints(payload.info.userDisplayHints);
|
|
2752
|
+
|
|
2122
2753
|
if (changed) {
|
|
2123
2754
|
Trigger.trigger(
|
|
2124
2755
|
this,
|
|
@@ -2141,7 +2772,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2141
2772
|
* @param {String} datachannelUrl
|
|
2142
2773
|
* @returns {void}
|
|
2143
2774
|
*/
|
|
2775
|
+
|
|
2144
2776
|
handleDataChannelUrlChange(datachannelUrl) {
|
|
2777
|
+
// @ts-ignore - config coming from registerPlugin
|
|
2145
2778
|
if (datachannelUrl && this.config.enableAutomaticLLM) {
|
|
2146
2779
|
// Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
|
|
2147
2780
|
// after the MEETING_INFO_UPDATED callback finishes
|
|
@@ -2196,10 +2829,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2196
2829
|
);
|
|
2197
2830
|
}
|
|
2198
2831
|
});
|
|
2832
|
+
|
|
2833
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED, (payload) => {
|
|
2834
|
+
if (payload) {
|
|
2835
|
+
if (this.video) {
|
|
2836
|
+
payload.muted = payload.muted ?? this.video.isRemotelyMuted();
|
|
2837
|
+
payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
|
|
2838
|
+
this.video.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2839
|
+
}
|
|
2840
|
+
Trigger.trigger(
|
|
2841
|
+
this,
|
|
2842
|
+
{
|
|
2843
|
+
file: 'meeting/index',
|
|
2844
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2845
|
+
},
|
|
2846
|
+
payload.muted
|
|
2847
|
+
? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
|
|
2848
|
+
: EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
|
|
2849
|
+
{
|
|
2850
|
+
payload,
|
|
2851
|
+
}
|
|
2852
|
+
);
|
|
2853
|
+
}
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2199
2856
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
|
|
2200
2857
|
if (payload) {
|
|
2201
2858
|
if (this.audio) {
|
|
2202
|
-
this.audio.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
|
|
2859
|
+
this.audio.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2203
2860
|
}
|
|
2204
2861
|
// with "mute on entry" server will send us remote mute even if we don't have media configured,
|
|
2205
2862
|
// so if being muted by others, always send the notification,
|
|
@@ -2322,6 +2979,51 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2322
2979
|
);
|
|
2323
2980
|
});
|
|
2324
2981
|
|
|
2982
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED, (payload) => {
|
|
2983
|
+
this.breakouts.updateBreakoutSessions(payload);
|
|
2984
|
+
Trigger.trigger(
|
|
2985
|
+
this,
|
|
2986
|
+
{
|
|
2987
|
+
file: 'meeting/index',
|
|
2988
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2989
|
+
},
|
|
2990
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2991
|
+
);
|
|
2992
|
+
});
|
|
2993
|
+
|
|
2994
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_INTERPRETATION_CHANGED, (payload) => {
|
|
2995
|
+
this.simultaneousInterpretation.updateSelfInterpretation(payload);
|
|
2996
|
+
Trigger.trigger(
|
|
2997
|
+
this,
|
|
2998
|
+
{
|
|
2999
|
+
file: 'meeting/index',
|
|
3000
|
+
function: 'setUpLocusInfoSelfListener',
|
|
3001
|
+
},
|
|
3002
|
+
EVENT_TRIGGERS.MEETING_INTERPRETATION_UPDATE
|
|
3003
|
+
);
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
|
|
3007
|
+
const isModeratorOrCohost =
|
|
3008
|
+
payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
|
|
3009
|
+
payload.newRoles?.includes(SELF_ROLES.COHOST);
|
|
3010
|
+
this.breakouts.updateCanManageBreakouts(isModeratorOrCohost);
|
|
3011
|
+
this.simultaneousInterpretation.updateCanManageInterpreters(
|
|
3012
|
+
payload.newRoles?.includes(SELF_ROLES.MODERATOR)
|
|
3013
|
+
);
|
|
3014
|
+
Trigger.trigger(
|
|
3015
|
+
this,
|
|
3016
|
+
{
|
|
3017
|
+
file: 'meeting/index',
|
|
3018
|
+
function: 'setUpLocusInfoSelfListener',
|
|
3019
|
+
},
|
|
3020
|
+
EVENT_TRIGGERS.MEETING_SELF_ROLES_CHANGED,
|
|
3021
|
+
{
|
|
3022
|
+
payload,
|
|
3023
|
+
}
|
|
3024
|
+
);
|
|
3025
|
+
});
|
|
3026
|
+
|
|
2325
3027
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_IS_SHARING_BLOCKED_CHANGE, (payload) => {
|
|
2326
3028
|
Trigger.trigger(
|
|
2327
3029
|
this,
|
|
@@ -2357,19 +3059,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2357
3059
|
.catch((error) => {
|
|
2358
3060
|
// @ts-ignore
|
|
2359
3061
|
LoggerProxy.logger.error(
|
|
2360
|
-
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this
|
|
3062
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
2361
3063
|
);
|
|
2362
3064
|
});
|
|
2363
3065
|
}
|
|
2364
3066
|
});
|
|
2365
|
-
this.locusInfo.on(EVENTS.DESTROY_MEETING, (payload) => {
|
|
3067
|
+
this.locusInfo.on(EVENTS.DESTROY_MEETING, async (payload) => {
|
|
2366
3068
|
// if self state is NOT left
|
|
2367
3069
|
|
|
2368
3070
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
2369
3071
|
if (this.wirelessShare) {
|
|
2370
3072
|
if (this.mediaProperties.shareTrack) {
|
|
2371
|
-
this.
|
|
2372
|
-
this.mediaProperties.shareTrack.stop();
|
|
3073
|
+
await this.setLocalShareTrack(undefined);
|
|
2373
3074
|
}
|
|
2374
3075
|
}
|
|
2375
3076
|
// when multiple WEB deviceType join with same user
|
|
@@ -2383,18 +3084,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2383
3084
|
if (payload.shouldLeave) {
|
|
2384
3085
|
// TODO: We should do cleaning of meeting object if the shouldLeave: false because there might be meeting object which we are not cleaning
|
|
2385
3086
|
|
|
2386
|
-
|
|
2387
|
-
.
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
3087
|
+
try {
|
|
3088
|
+
await this.leave({reason: payload.reason});
|
|
3089
|
+
|
|
3090
|
+
LoggerProxy.logger.warn(
|
|
3091
|
+
'Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. The meeting has been left, but has not been destroyed, you should see a later event for leave.'
|
|
3092
|
+
);
|
|
3093
|
+
} catch (error) {
|
|
3094
|
+
// @ts-ignore
|
|
3095
|
+
LoggerProxy.logger.error(
|
|
3096
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
3097
|
+
);
|
|
3098
|
+
}
|
|
2398
3099
|
} else {
|
|
2399
3100
|
LoggerProxy.logger.info(
|
|
2400
3101
|
'Meeting:index#setUpLocusInfoMeetingListener --> MEETING_REMOVED_REASON',
|
|
@@ -2472,14 +3173,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2472
3173
|
}
|
|
2473
3174
|
|
|
2474
3175
|
/**
|
|
2475
|
-
* Admit the guest(s) to the call once they are waiting
|
|
3176
|
+
* Admit the guest(s) to the call once they are waiting.
|
|
3177
|
+
* If the host/cohost is in a breakout session, the locus url
|
|
3178
|
+
* of the session must be provided as the authorizingLocusUrl.
|
|
3179
|
+
* Regardless of host/cohost location, the locus Id (lid) in
|
|
3180
|
+
* the path should be the locus Id of the main, which means the
|
|
3181
|
+
* locus url of the api call must be from the main session.
|
|
3182
|
+
* If these loucs urls are not provided, the function will do the check.
|
|
2476
3183
|
* @param {Array} memberIds
|
|
3184
|
+
* @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
|
|
2477
3185
|
* @returns {Promise} see #members.admitMembers
|
|
2478
3186
|
* @public
|
|
2479
3187
|
* @memberof Meeting
|
|
2480
3188
|
*/
|
|
2481
|
-
public admit(
|
|
2482
|
-
|
|
3189
|
+
public admit(
|
|
3190
|
+
memberIds: Array<any>,
|
|
3191
|
+
sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
|
|
3192
|
+
) {
|
|
3193
|
+
let locusUrls = sessionLocusUrls;
|
|
3194
|
+
if (!locusUrls) {
|
|
3195
|
+
const {locusUrl, mainLocusUrl} = this.breakouts;
|
|
3196
|
+
if (locusUrl && mainLocusUrl) {
|
|
3197
|
+
locusUrls = {authorizingLocusUrl: locusUrl, mainLocusUrl};
|
|
3198
|
+
}
|
|
3199
|
+
}
|
|
3200
|
+
|
|
3201
|
+
return this.members.admitMembers(memberIds, locusUrls);
|
|
2483
3202
|
}
|
|
2484
3203
|
|
|
2485
3204
|
/**
|
|
@@ -2527,66 +3246,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2527
3246
|
return this.members;
|
|
2528
3247
|
}
|
|
2529
3248
|
|
|
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
3249
|
/**
|
|
2591
3250
|
* Sets the meeting info on the class instance
|
|
2592
3251
|
* @param {Object} meetingInfo
|
|
@@ -2634,6 +3293,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2634
3293
|
this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
|
|
2635
3294
|
// @ts-ignore - config coming from registerPlugin
|
|
2636
3295
|
this.setSipUri(
|
|
3296
|
+
// @ts-ignore
|
|
2637
3297
|
this.config.experimental.enableUnifiedMeetings
|
|
2638
3298
|
? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
|
|
2639
3299
|
: locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
|
|
@@ -2650,35 +3310,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2650
3310
|
webexMeetingInfo?.hostId ||
|
|
2651
3311
|
this.owner;
|
|
2652
3312
|
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
|
-
}
|
|
3313
|
+
// Need to populate environment when sending CA event
|
|
3314
|
+
this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
|
|
2682
3315
|
}
|
|
2683
3316
|
}
|
|
2684
3317
|
|
|
@@ -2708,7 +3341,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2708
3341
|
* @private
|
|
2709
3342
|
* @memberof Meeting
|
|
2710
3343
|
*/
|
|
2711
|
-
|
|
3344
|
+
setLocus(
|
|
2712
3345
|
locus:
|
|
2713
3346
|
| {
|
|
2714
3347
|
mediaConnections: Array<any>;
|
|
@@ -2743,21 +3376,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2743
3376
|
Trigger.trigger(this, options, EVENTS.REQUEST_UPLOAD_LOGS, this);
|
|
2744
3377
|
}
|
|
2745
3378
|
|
|
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
3379
|
/**
|
|
2762
3380
|
* Removes remote audio, video and share tracks from class instance's mediaProperties
|
|
2763
3381
|
* @returns {undefined}
|
|
@@ -2842,257 +3460,124 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2842
3460
|
}
|
|
2843
3461
|
|
|
2844
3462
|
/**
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2848
|
-
* @
|
|
3463
|
+
* Stores the reference to a new microphone track, sets up the required event listeners
|
|
3464
|
+
* on it, cleans up previous track, etc.
|
|
3465
|
+
*
|
|
3466
|
+
* @param {LocalMicrophoneTrack | null} localTrack local microphone track
|
|
3467
|
+
* @returns {Promise<void>}
|
|
2849
3468
|
*/
|
|
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
|
-
}
|
|
3469
|
+
private async setLocalAudioTrack(localTrack?: LocalMicrophoneTrack) {
|
|
3470
|
+
const oldTrack = this.mediaProperties.audioTrack;
|
|
2867
3471
|
|
|
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();
|
|
3472
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3473
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2879
3474
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
noiseSuppression: settings.noiseSuppression,
|
|
2883
|
-
});
|
|
3475
|
+
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3476
|
+
this.mediaProperties.setLocalAudioTrack(localTrack);
|
|
2884
3477
|
|
|
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
|
-
}
|
|
3478
|
+
this.audio.handleLocalTrackChange(this);
|
|
2892
3479
|
|
|
2893
|
-
|
|
2894
|
-
|
|
3480
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3481
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3482
|
+
|
|
3483
|
+
if (!this.isMultistream || !localTrack) {
|
|
3484
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3485
|
+
await this.unpublishTrack(oldTrack);
|
|
2895
3486
|
}
|
|
3487
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
2896
3488
|
}
|
|
2897
3489
|
|
|
2898
3490
|
/**
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
2902
|
-
* @
|
|
2903
|
-
* @
|
|
2904
|
-
* @memberof Meeting
|
|
3491
|
+
* Stores the reference to a new camera track, sets up the required event listeners
|
|
3492
|
+
* on it, cleans up previous track, etc.
|
|
3493
|
+
*
|
|
3494
|
+
* @param {LocalCameraTrack | null} localTrack local camera track
|
|
3495
|
+
* @returns {Promise<void>}
|
|
2905
3496
|
*/
|
|
2906
|
-
private setLocalVideoTrack(
|
|
2907
|
-
|
|
2908
|
-
const {aspectRatio, frameRate, height, width, deviceId} = videoTrack.getSettings();
|
|
2909
|
-
|
|
2910
|
-
const {localQualityLevel} = this.mediaProperties;
|
|
3497
|
+
private async setLocalVideoTrack(localTrack?: LocalCameraTrack) {
|
|
3498
|
+
const oldTrack = this.mediaProperties.videoTrack;
|
|
2911
3499
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
.warn(`Meeting:index#setLocalVideoTrack --> Local video quality of ${localQualityLevel} not supported,
|
|
2915
|
-
downscaling to highest possible resolution of ${height}p`);
|
|
3500
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3501
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2916
3502
|
|
|
2917
|
-
|
|
2918
|
-
|
|
3503
|
+
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3504
|
+
this.mediaProperties.setLocalVideoTrack(localTrack);
|
|
2919
3505
|
|
|
2920
|
-
|
|
2921
|
-
if (this.video) this.video.applyClientStateLocally(this);
|
|
3506
|
+
this.video.handleLocalTrackChange(this);
|
|
2922
3507
|
|
|
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
|
-
}
|
|
3508
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3509
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2938
3510
|
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
3511
|
+
if (!this.isMultistream || !localTrack) {
|
|
3512
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3513
|
+
await this.unpublishTrack(oldTrack);
|
|
2941
3514
|
}
|
|
3515
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
2942
3516
|
}
|
|
2943
3517
|
|
|
2944
3518
|
/**
|
|
2945
|
-
*
|
|
2946
|
-
*
|
|
2947
|
-
*
|
|
2948
|
-
*
|
|
2949
|
-
* @
|
|
3519
|
+
* Stores the reference to a new screen share track, sets up the required event listeners
|
|
3520
|
+
* on it, cleans up previous track, etc.
|
|
3521
|
+
* It also sends the floor grant/release request.
|
|
3522
|
+
*
|
|
3523
|
+
* @param {LocalDisplayTrack | undefined} localDisplayTrack local camera track
|
|
3524
|
+
* @returns {Promise<void>}
|
|
2950
3525
|
*/
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const {audioTrack, videoTrack} = MeetingUtil.getTrack(localStream);
|
|
3526
|
+
private async setLocalShareTrack(localDisplayTrack?: LocalDisplayTrack) {
|
|
3527
|
+
const oldTrack = this.mediaProperties.shareTrack;
|
|
2954
3528
|
|
|
2955
|
-
|
|
2956
|
-
|
|
3529
|
+
oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3530
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2957
3531
|
|
|
2958
|
-
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
3532
|
+
this.mediaProperties.setLocalShareTrack(localDisplayTrack);
|
|
2961
3533
|
|
|
2962
|
-
|
|
2963
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
* @memberof Meeting
|
|
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
|
-
}
|
|
3534
|
+
localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3535
|
+
localDisplayTrack?.on(
|
|
3536
|
+
LocalTrackEvents.UnderlyingTrackChange,
|
|
3537
|
+
this.underlyingLocalTrackChangeHandler
|
|
3538
|
+
);
|
|
2991
3539
|
|
|
2992
|
-
|
|
3540
|
+
this.mediaProperties.mediaDirection.sendShare = !!localDisplayTrack;
|
|
2993
3541
|
|
|
2994
|
-
|
|
2995
|
-
|
|
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
|
-
);
|
|
3542
|
+
if (!this.isMultistream || !localDisplayTrack) {
|
|
3543
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3544
|
+
await this.unpublishTrack(oldTrack);
|
|
3006
3545
|
}
|
|
3546
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
3007
3547
|
}
|
|
3008
3548
|
|
|
3009
3549
|
/**
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
*
|
|
3013
|
-
* @
|
|
3014
|
-
* @
|
|
3550
|
+
* Removes references to local tracks. This function should be called
|
|
3551
|
+
* on cleanup when we leave the meeting etc.
|
|
3552
|
+
*
|
|
3553
|
+
* @internal
|
|
3554
|
+
* @returns {void}
|
|
3015
3555
|
*/
|
|
3016
|
-
public
|
|
3017
|
-
const {audioTrack, videoTrack} = this.mediaProperties;
|
|
3556
|
+
public cleanupLocalTracks() {
|
|
3557
|
+
const {audioTrack, videoTrack, shareTrack} = this.mediaProperties;
|
|
3018
3558
|
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
.then(() => {
|
|
3022
|
-
const audioStopped = audioTrack && audioTrack.readyState === ENDED;
|
|
3023
|
-
const videoStopped = videoTrack && videoTrack.readyState === ENDED;
|
|
3559
|
+
audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3560
|
+
audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3024
3561
|
|
|
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
|
-
}
|
|
3562
|
+
videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3563
|
+
videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3045
3564
|
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
* @returns {undefined}
|
|
3049
|
-
* @event media:stopped
|
|
3050
|
-
* @public
|
|
3051
|
-
* @memberof Meeting
|
|
3052
|
-
*/
|
|
3053
|
-
public closeLocalShare() {
|
|
3054
|
-
const track = this.mediaProperties.shareTrack;
|
|
3565
|
+
shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3566
|
+
shareTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3055
3567
|
|
|
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
|
-
}
|
|
3568
|
+
this.mediaProperties.setLocalAudioTrack(undefined);
|
|
3569
|
+
this.mediaProperties.setLocalVideoTrack(undefined);
|
|
3570
|
+
this.mediaProperties.setLocalShareTrack(undefined);
|
|
3077
3571
|
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
* @public
|
|
3082
|
-
* @memberof Meeting
|
|
3083
|
-
*/
|
|
3084
|
-
public unsetLocalVideoTrack() {
|
|
3085
|
-
this.mediaProperties.unsetLocalVideoTrack();
|
|
3086
|
-
}
|
|
3572
|
+
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3573
|
+
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3574
|
+
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3087
3575
|
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
*/
|
|
3094
|
-
public unsetLocalShareTrack() {
|
|
3095
|
-
this.mediaProperties.unsetLocalShareTrack();
|
|
3576
|
+
// WCME doesn't unpublish tracks when multistream connection is closed, so we do it here
|
|
3577
|
+
// (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
|
|
3578
|
+
audioTrack?.setPublished(false);
|
|
3579
|
+
videoTrack?.setPublished(false);
|
|
3580
|
+
shareTrack?.setPublished(false);
|
|
3096
3581
|
}
|
|
3097
3582
|
|
|
3098
3583
|
/**
|
|
@@ -3143,6 +3628,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3143
3628
|
* @memberof Meeting
|
|
3144
3629
|
*/
|
|
3145
3630
|
public closePeerConnections() {
|
|
3631
|
+
this.locusMediaRequest = undefined;
|
|
3632
|
+
|
|
3146
3633
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
3147
3634
|
if (this.remoteMediaManager) {
|
|
3148
3635
|
this.remoteMediaManager.stop();
|
|
@@ -3157,6 +3644,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3157
3644
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3158
3645
|
}
|
|
3159
3646
|
|
|
3647
|
+
this.audio = null;
|
|
3648
|
+
this.video = null;
|
|
3649
|
+
|
|
3160
3650
|
return Promise.resolve();
|
|
3161
3651
|
}
|
|
3162
3652
|
|
|
@@ -3187,264 +3677,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3187
3677
|
this.correlationId = id;
|
|
3188
3678
|
}
|
|
3189
3679
|
|
|
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
3680
|
/**
|
|
3401
3681
|
* Shorthand function to join AND set up media
|
|
3402
3682
|
* @param {Object} options - options to join with media
|
|
3403
3683
|
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
3404
|
-
* @param {MediaDirection} options.
|
|
3405
|
-
* @
|
|
3406
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia(), local: see getMediaStreams()}
|
|
3684
|
+
* @param {MediaDirection} [options.mediaOptions] - see #addMedia()
|
|
3685
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
3407
3686
|
* @public
|
|
3408
3687
|
* @memberof Meeting
|
|
3409
3688
|
* @example
|
|
3410
3689
|
* joinWithMedia({
|
|
3411
3690
|
* 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
|
-
* }})
|
|
3691
|
+
* mediaOptions: {
|
|
3692
|
+
* localTracks: { microphone: microphoneTrack, camera: cameraTrack }
|
|
3693
|
+
* }
|
|
3694
|
+
* })
|
|
3424
3695
|
*/
|
|
3425
3696
|
public joinWithMedia(
|
|
3426
3697
|
options: {
|
|
3427
3698
|
joinOptions?: any;
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
} = {} as any
|
|
3699
|
+
mediaOptions?: AddMediaOptions;
|
|
3700
|
+
} = {}
|
|
3431
3701
|
) {
|
|
3432
|
-
|
|
3433
|
-
const {mediaSettings, joinOptions, audioVideoOptions} = options;
|
|
3702
|
+
const {mediaOptions, joinOptions} = options;
|
|
3434
3703
|
|
|
3435
3704
|
return this.join(joinOptions)
|
|
3436
3705
|
.then((joinResponse) =>
|
|
3437
|
-
this.
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
localStream,
|
|
3442
|
-
}).then((mediaResponse) => ({
|
|
3443
|
-
join: joinResponse,
|
|
3444
|
-
media: mediaResponse,
|
|
3445
|
-
local: [localStream, localShare],
|
|
3446
|
-
}))
|
|
3447
|
-
)
|
|
3706
|
+
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
3707
|
+
join: joinResponse,
|
|
3708
|
+
media: mediaResponse,
|
|
3709
|
+
}))
|
|
3448
3710
|
)
|
|
3449
3711
|
.catch((error) => {
|
|
3450
3712
|
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
@@ -3582,6 +3844,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3582
3844
|
return false;
|
|
3583
3845
|
}
|
|
3584
3846
|
|
|
3847
|
+
/**
|
|
3848
|
+
* Check if the meeting supports the Reactions
|
|
3849
|
+
* @returns {boolean}
|
|
3850
|
+
*/
|
|
3851
|
+
isReactionsSupported() {
|
|
3852
|
+
if (this.locusInfo?.controls?.reactions.enabled) {
|
|
3853
|
+
return true;
|
|
3854
|
+
}
|
|
3855
|
+
|
|
3856
|
+
LoggerProxy.logger.error('Meeting:index#isReactionsSupported --> Reactions is not supported');
|
|
3857
|
+
|
|
3858
|
+
return false;
|
|
3859
|
+
}
|
|
3860
|
+
|
|
3585
3861
|
/**
|
|
3586
3862
|
* Monitor the Low-Latency Mercury (LLM) web socket connection on `onError` and `onClose` states
|
|
3587
3863
|
* @private
|
|
@@ -3633,6 +3909,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3633
3909
|
// @ts-ignore - fix type
|
|
3634
3910
|
const {
|
|
3635
3911
|
body: {webSocketUrl},
|
|
3912
|
+
// @ts-ignore
|
|
3636
3913
|
} = await this.request({
|
|
3637
3914
|
method: HTTP_VERBS.POST,
|
|
3638
3915
|
uri: datachannelUrl,
|
|
@@ -3682,6 +3959,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3682
3959
|
}
|
|
3683
3960
|
}
|
|
3684
3961
|
|
|
3962
|
+
/**
|
|
3963
|
+
* Callback called when a relay event is received from meeting LLM Connection
|
|
3964
|
+
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
3965
|
+
* @private
|
|
3966
|
+
* @returns {void}
|
|
3967
|
+
*/
|
|
3968
|
+
private processRelayEvent = (e: RelayEvent): void => {
|
|
3969
|
+
switch (e.data.relayType) {
|
|
3970
|
+
case REACTION_RELAY_TYPES.REACTION:
|
|
3971
|
+
if (
|
|
3972
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3973
|
+
(this.config.receiveReactions || options.receiveReactions) &&
|
|
3974
|
+
this.isReactionsSupported()
|
|
3975
|
+
) {
|
|
3976
|
+
const {name} = this.members.membersCollection.get(e.data.sender.participantId);
|
|
3977
|
+
const processedReaction: ProcessedReaction = {
|
|
3978
|
+
reaction: e.data.reaction,
|
|
3979
|
+
sender: {
|
|
3980
|
+
id: e.data.sender.participantId,
|
|
3981
|
+
name,
|
|
3982
|
+
},
|
|
3983
|
+
};
|
|
3984
|
+
Trigger.trigger(
|
|
3985
|
+
this,
|
|
3986
|
+
{
|
|
3987
|
+
file: 'meeting/index',
|
|
3988
|
+
function: 'join',
|
|
3989
|
+
},
|
|
3990
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
3991
|
+
processedReaction
|
|
3992
|
+
);
|
|
3993
|
+
}
|
|
3994
|
+
break;
|
|
3995
|
+
default:
|
|
3996
|
+
break;
|
|
3997
|
+
}
|
|
3998
|
+
};
|
|
3999
|
+
|
|
3685
4000
|
/**
|
|
3686
4001
|
* stop recieving Transcription by closing
|
|
3687
4002
|
* the web socket connection properly
|
|
@@ -3753,9 +4068,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3753
4068
|
joinSuccess = resolve;
|
|
3754
4069
|
});
|
|
3755
4070
|
|
|
4071
|
+
if (options.correlationId) {
|
|
4072
|
+
this.setCorrelationId(options.correlationId);
|
|
4073
|
+
LoggerProxy.logger.log(
|
|
4074
|
+
`Meeting:index#join --> Using a new correlation id from app ${this.correlationId}`
|
|
4075
|
+
);
|
|
4076
|
+
}
|
|
4077
|
+
|
|
3756
4078
|
if (!this.hasJoinedOnce) {
|
|
3757
4079
|
this.hasJoinedOnce = true;
|
|
3758
|
-
} else {
|
|
4080
|
+
} else if (!options.correlationId) {
|
|
3759
4081
|
LoggerProxy.logger.log(
|
|
3760
4082
|
`Meeting:index#join --> Generating a new correlation id for meeting ${this.id}`
|
|
3761
4083
|
);
|
|
@@ -3776,10 +4098,25 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3776
4098
|
data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
|
|
3777
4099
|
});
|
|
3778
4100
|
|
|
3779
|
-
|
|
4101
|
+
if (!isEmpty(this.meetingInfo)) {
|
|
4102
|
+
Metrics.postEvent({
|
|
4103
|
+
event: eventType.MEETING_INFO_REQUEST,
|
|
4104
|
+
meeting: this,
|
|
4105
|
+
});
|
|
3780
4106
|
|
|
3781
|
-
|
|
3782
|
-
|
|
4107
|
+
Metrics.postEvent({
|
|
4108
|
+
event: eventType.MEETING_INFO_RESPONSE,
|
|
4109
|
+
meeting: this,
|
|
4110
|
+
data: {
|
|
4111
|
+
meetingLookupUrl: this.meetingInfo?.meetingLookupUrl,
|
|
4112
|
+
},
|
|
4113
|
+
});
|
|
4114
|
+
}
|
|
4115
|
+
|
|
4116
|
+
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
4117
|
+
|
|
4118
|
+
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
4119
|
+
this.meetingFiniteStateMachine.reset();
|
|
3783
4120
|
}
|
|
3784
4121
|
if (this.meetingFiniteStateMachine.state !== MEETING_STATE_MACHINE.STATES.RINGING) {
|
|
3785
4122
|
this.meetingFiniteStateMachine.ring(_JOIN_);
|
|
@@ -3804,18 +4141,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3804
4141
|
return Promise.reject(error);
|
|
3805
4142
|
}
|
|
3806
4143
|
|
|
3807
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality);
|
|
3808
4144
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality);
|
|
3809
4145
|
}
|
|
3810
4146
|
|
|
3811
4147
|
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`;
|
|
4148
|
+
if (!QUALITY_LEVELS[options.meetingQuality.remote]) {
|
|
4149
|
+
const errorMessage = `Meeting:index#join --> ${options.meetingQuality.remote} not defined`;
|
|
3819
4150
|
|
|
3820
4151
|
LoggerProxy.logger.error(errorMessage);
|
|
3821
4152
|
|
|
@@ -3827,9 +4158,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3827
4158
|
return Promise.reject(new Error(errorMessage));
|
|
3828
4159
|
}
|
|
3829
4160
|
|
|
3830
|
-
if (options.meetingQuality.local) {
|
|
3831
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality.local);
|
|
3832
|
-
}
|
|
3833
4161
|
if (options.meetingQuality.remote) {
|
|
3834
4162
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality.remote);
|
|
3835
4163
|
}
|
|
@@ -3855,6 +4183,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3855
4183
|
return join;
|
|
3856
4184
|
})
|
|
3857
4185
|
.then(async (join) => {
|
|
4186
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3858
4187
|
if (this.config.enableAutomaticLLM) {
|
|
3859
4188
|
await this.updateLLMConnection();
|
|
3860
4189
|
}
|
|
@@ -3923,22 +4252,41 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3923
4252
|
* @returns {Promise}
|
|
3924
4253
|
*/
|
|
3925
4254
|
async updateLLMConnection() {
|
|
4255
|
+
// @ts-ignore - Fix type
|
|
3926
4256
|
const {url, info: {datachannelUrl} = {}} = this.locusInfo;
|
|
3927
4257
|
|
|
3928
|
-
const isJoined = this.
|
|
4258
|
+
const isJoined = this.isJoined();
|
|
3929
4259
|
|
|
4260
|
+
// @ts-ignore - Fix type
|
|
3930
4261
|
if (this.webex.internal.llm.isConnected()) {
|
|
4262
|
+
// @ts-ignore - Fix type
|
|
3931
4263
|
if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
|
|
3932
4264
|
return undefined;
|
|
3933
4265
|
}
|
|
4266
|
+
// @ts-ignore - Fix type
|
|
3934
4267
|
await this.webex.internal.llm.disconnectLLM();
|
|
4268
|
+
// @ts-ignore - Fix type
|
|
4269
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
3935
4270
|
}
|
|
3936
4271
|
|
|
3937
4272
|
if (!isJoined) {
|
|
3938
4273
|
return undefined;
|
|
3939
4274
|
}
|
|
3940
4275
|
|
|
3941
|
-
|
|
4276
|
+
// @ts-ignore - Fix type
|
|
4277
|
+
return this.webex.internal.llm
|
|
4278
|
+
.registerAndConnect(url, datachannelUrl)
|
|
4279
|
+
.then((registerAndConnectResult) => {
|
|
4280
|
+
// @ts-ignore - Fix type
|
|
4281
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
4282
|
+
// @ts-ignore - Fix type
|
|
4283
|
+
this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
|
|
4284
|
+
LoggerProxy.logger.info(
|
|
4285
|
+
'Meeting:index#updateLLMConnection --> enabled to receive relay events!'
|
|
4286
|
+
);
|
|
4287
|
+
|
|
4288
|
+
return Promise.resolve(registerAndConnectResult);
|
|
4289
|
+
});
|
|
3942
4290
|
}
|
|
3943
4291
|
|
|
3944
4292
|
/**
|
|
@@ -3980,28 +4328,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3980
4328
|
|
|
3981
4329
|
if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
|
|
3982
4330
|
|
|
3983
|
-
return
|
|
3984
|
-
.
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
});
|
|
4331
|
+
return (
|
|
4332
|
+
this.meetingRequest
|
|
4333
|
+
// @ts-ignore
|
|
4334
|
+
.dialIn({
|
|
4335
|
+
correlationId,
|
|
4336
|
+
dialInUrl: this.dialInUrl,
|
|
4337
|
+
locusUrl,
|
|
4338
|
+
clientUrl: this.deviceUrl,
|
|
4339
|
+
})
|
|
4340
|
+
.catch((error) => {
|
|
4341
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
|
|
4342
|
+
correlation_id: this.correlationId,
|
|
4343
|
+
dial_in_url: this.dialInUrl,
|
|
4344
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4345
|
+
client_url: this.deviceUrl,
|
|
4346
|
+
reason: error.error?.message,
|
|
4347
|
+
stack: error.stack,
|
|
4348
|
+
});
|
|
4002
4349
|
|
|
4003
|
-
|
|
4004
|
-
|
|
4350
|
+
return Promise.reject(error);
|
|
4351
|
+
})
|
|
4352
|
+
);
|
|
4005
4353
|
}
|
|
4006
4354
|
|
|
4007
4355
|
/**
|
|
@@ -4018,29 +4366,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4018
4366
|
|
|
4019
4367
|
if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
|
|
4020
4368
|
|
|
4021
|
-
return
|
|
4022
|
-
.
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
});
|
|
4369
|
+
return (
|
|
4370
|
+
this.meetingRequest
|
|
4371
|
+
// @ts-ignore
|
|
4372
|
+
.dialOut({
|
|
4373
|
+
correlationId,
|
|
4374
|
+
dialOutUrl: this.dialOutUrl,
|
|
4375
|
+
phoneNumber,
|
|
4376
|
+
locusUrl,
|
|
4377
|
+
clientUrl: this.deviceUrl,
|
|
4378
|
+
})
|
|
4379
|
+
.catch((error) => {
|
|
4380
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
|
|
4381
|
+
correlation_id: this.correlationId,
|
|
4382
|
+
dial_out_url: this.dialOutUrl,
|
|
4383
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4384
|
+
client_url: this.deviceUrl,
|
|
4385
|
+
reason: error.error?.message,
|
|
4386
|
+
stack: error.stack,
|
|
4387
|
+
});
|
|
4041
4388
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4389
|
+
return Promise.reject(error);
|
|
4390
|
+
})
|
|
4391
|
+
);
|
|
4044
4392
|
}
|
|
4045
4393
|
|
|
4046
4394
|
/**
|
|
@@ -4116,14 +4464,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4116
4464
|
},
|
|
4117
4465
|
};
|
|
4118
4466
|
|
|
4119
|
-
|
|
4120
|
-
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4121
|
-
|
|
4122
|
-
// close the existing local tracks
|
|
4123
|
-
await this.closeLocalStream();
|
|
4124
|
-
await this.closeLocalShare();
|
|
4467
|
+
this.cleanupLocalTracks();
|
|
4125
4468
|
|
|
4126
|
-
this.mediaProperties.
|
|
4469
|
+
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4470
|
+
this.mediaProperties.unsetRemoteMedia();
|
|
4127
4471
|
|
|
4128
4472
|
// 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
4473
|
// once the DX answers we establish connection back the media server with only receiveShare enabled
|
|
@@ -4206,165 +4550,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4206
4550
|
});
|
|
4207
4551
|
}
|
|
4208
4552
|
|
|
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
4553
|
/**
|
|
4369
4554
|
* Handles ROAP_FAILURE event from the webrtc media connection
|
|
4370
4555
|
*
|
|
@@ -4387,7 +4572,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4387
4572
|
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
4388
4573
|
};
|
|
4389
4574
|
|
|
4390
|
-
if (error instanceof
|
|
4575
|
+
if (error instanceof Errors.SdpOfferCreationError) {
|
|
4391
4576
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4392
4577
|
|
|
4393
4578
|
Metrics.postEvent({
|
|
@@ -4401,8 +4586,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4401
4586
|
},
|
|
4402
4587
|
});
|
|
4403
4588
|
} else if (
|
|
4404
|
-
error instanceof
|
|
4405
|
-
error instanceof
|
|
4589
|
+
error instanceof Errors.SdpOfferHandlingError ||
|
|
4590
|
+
error instanceof Errors.SdpAnswerHandlingError
|
|
4406
4591
|
) {
|
|
4407
4592
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4408
4593
|
|
|
@@ -4416,8 +4601,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4416
4601
|
],
|
|
4417
4602
|
},
|
|
4418
4603
|
});
|
|
4419
|
-
} else if (error instanceof
|
|
4420
|
-
// this covers also the case of
|
|
4604
|
+
} else if (error instanceof Errors.SdpError) {
|
|
4605
|
+
// this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
|
|
4421
4606
|
sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
|
|
4422
4607
|
|
|
4423
4608
|
Metrics.postEvent({
|
|
@@ -4434,20 +4619,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4434
4619
|
};
|
|
4435
4620
|
|
|
4436
4621
|
setupMediaConnectionListeners = () => {
|
|
4437
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4622
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_STARTED, () => {
|
|
4438
4623
|
this.isRoapInProgress = true;
|
|
4439
4624
|
});
|
|
4440
4625
|
|
|
4441
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4626
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
|
|
4442
4627
|
this.mediaNegotiatedEvent();
|
|
4443
4628
|
this.isRoapInProgress = false;
|
|
4444
4629
|
this.processNextQueuedMediaUpdate();
|
|
4445
4630
|
});
|
|
4446
4631
|
|
|
4447
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4632
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_FAILURE, this.handleRoapFailure);
|
|
4448
4633
|
|
|
4449
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4450
|
-
const LOG_HEADER =
|
|
4634
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, (event) => {
|
|
4635
|
+
const LOG_HEADER = `Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND --> correlationId=${this.correlationId}`;
|
|
4451
4636
|
|
|
4452
4637
|
switch (event.roapMessage.messageType) {
|
|
4453
4638
|
case 'OK':
|
|
@@ -4463,9 +4648,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4463
4648
|
correlationId: this.correlationId,
|
|
4464
4649
|
}),
|
|
4465
4650
|
{
|
|
4466
|
-
|
|
4467
|
-
success: `${LOG_HEADER} Successfully send roap OK`,
|
|
4468
|
-
failure: `${LOG_HEADER} Error joining the call on send roap OK, `,
|
|
4651
|
+
logText: `${LOG_HEADER} Roap OK`,
|
|
4469
4652
|
}
|
|
4470
4653
|
);
|
|
4471
4654
|
break;
|
|
@@ -4485,9 +4668,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4485
4668
|
reconnect: this.reconnectionManager.isReconnectInProgress(),
|
|
4486
4669
|
}),
|
|
4487
4670
|
{
|
|
4488
|
-
|
|
4489
|
-
success: `${LOG_HEADER} Successfully send roap offer`,
|
|
4490
|
-
failure: `${LOG_HEADER} Error joining the call on send roap offer, `,
|
|
4671
|
+
logText: `${LOG_HEADER} Roap Offer`,
|
|
4491
4672
|
}
|
|
4492
4673
|
);
|
|
4493
4674
|
break;
|
|
@@ -4506,9 +4687,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4506
4687
|
correlationId: this.correlationId,
|
|
4507
4688
|
}),
|
|
4508
4689
|
{
|
|
4509
|
-
|
|
4510
|
-
success: `${LOG_HEADER} Successfully send roap answer`,
|
|
4511
|
-
failure: `${LOG_HEADER} Error joining the call on send roap answer, `,
|
|
4690
|
+
logText: `${LOG_HEADER} Roap Answer`,
|
|
4512
4691
|
}
|
|
4513
4692
|
).catch((error) => {
|
|
4514
4693
|
const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
|
|
@@ -4528,8 +4707,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4528
4707
|
|
|
4529
4708
|
case 'ERROR':
|
|
4530
4709
|
if (
|
|
4531
|
-
event.roapMessage.errorType ===
|
|
4532
|
-
event.roapMessage.errorType ===
|
|
4710
|
+
event.roapMessage.errorType === ErrorType.CONFLICT ||
|
|
4711
|
+
event.roapMessage.errorType === ErrorType.DOUBLECONFLICT
|
|
4533
4712
|
) {
|
|
4534
4713
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
|
|
4535
4714
|
correlation_id: this.correlationId,
|
|
@@ -4545,9 +4724,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4545
4724
|
correlationId: this.correlationId,
|
|
4546
4725
|
}),
|
|
4547
4726
|
{
|
|
4548
|
-
|
|
4549
|
-
success: `${LOG_HEADER} Successfully send roap error`,
|
|
4550
|
-
failure: `${LOG_HEADER} Failed to send roap error, `,
|
|
4727
|
+
logText: `${LOG_HEADER} Roap Error (${event.roapMessage.errorType})`,
|
|
4551
4728
|
}
|
|
4552
4729
|
);
|
|
4553
4730
|
break;
|
|
@@ -4561,7 +4738,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4561
4738
|
});
|
|
4562
4739
|
|
|
4563
4740
|
// eslint-disable-next-line no-param-reassign
|
|
4564
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4741
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.REMOTE_TRACK_ADDED, (event) => {
|
|
4565
4742
|
LoggerProxy.logger.log(
|
|
4566
4743
|
`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(
|
|
4567
4744
|
event
|
|
@@ -4574,15 +4751,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4574
4751
|
let eventType;
|
|
4575
4752
|
|
|
4576
4753
|
switch (event.type) {
|
|
4577
|
-
case
|
|
4754
|
+
case RemoteTrackType.AUDIO:
|
|
4578
4755
|
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
4579
4756
|
this.mediaProperties.setRemoteAudioTrack(event.track);
|
|
4580
4757
|
break;
|
|
4581
|
-
case
|
|
4758
|
+
case RemoteTrackType.VIDEO:
|
|
4582
4759
|
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
4583
4760
|
this.mediaProperties.setRemoteVideoTrack(event.track);
|
|
4584
4761
|
break;
|
|
4585
|
-
case
|
|
4762
|
+
case RemoteTrackType.SCREENSHARE_VIDEO:
|
|
4586
4763
|
if (event.track) {
|
|
4587
4764
|
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
4588
4765
|
this.mediaProperties.setRemoteShare(event.track);
|
|
@@ -4595,10 +4772,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4595
4772
|
}
|
|
4596
4773
|
}
|
|
4597
4774
|
|
|
4598
|
-
// start stats here the stats are coming null if you dont receive streams
|
|
4599
|
-
|
|
4600
|
-
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4601
|
-
|
|
4602
4775
|
if (eventType && mediaTrack) {
|
|
4603
4776
|
Trigger.trigger(
|
|
4604
4777
|
this,
|
|
@@ -4615,7 +4788,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4615
4788
|
}
|
|
4616
4789
|
});
|
|
4617
4790
|
|
|
4618
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4791
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
4619
4792
|
const connectionFailed = () => {
|
|
4620
4793
|
// we know the media connection failed and browser will not attempt to recover it any more
|
|
4621
4794
|
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
@@ -4645,13 +4818,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4645
4818
|
};
|
|
4646
4819
|
|
|
4647
4820
|
LoggerProxy.logger.info(
|
|
4648
|
-
`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`
|
|
4821
|
+
`Meeting:index#setupMediaConnectionListeners --> correlationId=${this.correlationId} connection state changed to ${event.state}`
|
|
4649
4822
|
);
|
|
4650
4823
|
switch (event.state) {
|
|
4651
|
-
case
|
|
4824
|
+
case ConnectionState.Connecting:
|
|
4652
4825
|
Metrics.postEvent({event: eventType.ICE_START, meeting: this});
|
|
4653
4826
|
break;
|
|
4654
|
-
case
|
|
4827
|
+
case ConnectionState.Connected:
|
|
4655
4828
|
Metrics.postEvent({event: eventType.ICE_END, meeting: this});
|
|
4656
4829
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
4657
4830
|
correlation_id: this.correlationId,
|
|
@@ -4659,8 +4832,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4832
|
});
|
|
4660
4833
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
4661
4834
|
this.reconnectionManager.iceReconnected();
|
|
4835
|
+
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4662
4836
|
break;
|
|
4663
|
-
case
|
|
4837
|
+
case ConnectionState.Disconnected:
|
|
4664
4838
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
4665
4839
|
this.reconnectionManager.waitForIceReconnect().catch(() => {
|
|
4666
4840
|
LoggerProxy.logger.info(
|
|
@@ -4670,7 +4844,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4670
4844
|
connectionFailed();
|
|
4671
4845
|
});
|
|
4672
4846
|
break;
|
|
4673
|
-
case
|
|
4847
|
+
case ConnectionState.Failed:
|
|
4674
4848
|
connectionFailed();
|
|
4675
4849
|
break;
|
|
4676
4850
|
default:
|
|
@@ -4678,7 +4852,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4678
4852
|
}
|
|
4679
4853
|
});
|
|
4680
4854
|
|
|
4681
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4855
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (msg) => {
|
|
4682
4856
|
Trigger.trigger(
|
|
4683
4857
|
this,
|
|
4684
4858
|
{
|
|
@@ -4689,6 +4863,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4689
4863
|
{
|
|
4690
4864
|
seqNum: msg.seqNum,
|
|
4691
4865
|
memberIds: msg.csis
|
|
4866
|
+
// @ts-ignore
|
|
4692
4867
|
.map((csi) => this.members.findMemberByCsi(csi)?.id)
|
|
4693
4868
|
.filter((item) => item !== undefined),
|
|
4694
4869
|
}
|
|
@@ -4696,8 +4871,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4696
4871
|
});
|
|
4697
4872
|
|
|
4698
4873
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4699
|
-
|
|
4700
|
-
(numTotalSources, numLiveSources) => {
|
|
4874
|
+
Event.VIDEO_SOURCES_COUNT_CHANGED,
|
|
4875
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4701
4876
|
Trigger.trigger(
|
|
4702
4877
|
this,
|
|
4703
4878
|
{
|
|
@@ -4708,14 +4883,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4708
4883
|
{
|
|
4709
4884
|
numTotalSources,
|
|
4710
4885
|
numLiveSources,
|
|
4886
|
+
mediaContent,
|
|
4711
4887
|
}
|
|
4712
4888
|
);
|
|
4889
|
+
|
|
4890
|
+
if (mediaContent === MediaContent.Main) {
|
|
4891
|
+
this.mediaRequestManagers.video.setNumCurrentSources(numTotalSources, numLiveSources);
|
|
4892
|
+
}
|
|
4713
4893
|
}
|
|
4714
4894
|
);
|
|
4715
4895
|
|
|
4716
4896
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4717
|
-
|
|
4718
|
-
(numTotalSources, numLiveSources) => {
|
|
4897
|
+
Event.AUDIO_SOURCES_COUNT_CHANGED,
|
|
4898
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4719
4899
|
Trigger.trigger(
|
|
4720
4900
|
this,
|
|
4721
4901
|
{
|
|
@@ -4726,6 +4906,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4726
4906
|
{
|
|
4727
4907
|
numTotalSources,
|
|
4728
4908
|
numLiveSources,
|
|
4909
|
+
mediaContent,
|
|
4729
4910
|
}
|
|
4730
4911
|
);
|
|
4731
4912
|
}
|
|
@@ -4744,6 +4925,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4744
4925
|
// Add ip address info if geo hint is present
|
|
4745
4926
|
// @ts-ignore fix type
|
|
4746
4927
|
options.data.intervalMetadata.peerReflexiveIP =
|
|
4928
|
+
// @ts-ignore
|
|
4747
4929
|
this.webex.meetings.geoHintInfo?.clientAddress ||
|
|
4748
4930
|
options.data.intervalMetadata.peerReflexiveIP ||
|
|
4749
4931
|
MQA_STATS.DEFAULT_IP;
|
|
@@ -4813,7 +4995,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4813
4995
|
return `MC-${this.id.substring(0, 4)}`;
|
|
4814
4996
|
}
|
|
4815
4997
|
|
|
4816
|
-
|
|
4998
|
+
/**
|
|
4999
|
+
* Creates a webrtc media connection and publishes tracks to it
|
|
5000
|
+
*
|
|
5001
|
+
* @param {Object} turnServerInfo TURN server information
|
|
5002
|
+
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
5003
|
+
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
5004
|
+
*/
|
|
5005
|
+
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
5006
|
+
// create the actual media connection
|
|
4817
5007
|
const mc = Media.createMediaConnection(this.isMultistream, this.getMediaConnectionDebugId(), {
|
|
4818
5008
|
mediaProperties: this.mediaProperties,
|
|
4819
5009
|
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
@@ -4822,11 +5012,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4822
5012
|
// @ts-ignore - config coming from registerPlugin
|
|
4823
5013
|
enableExtmap: this.config.enableExtmap,
|
|
4824
5014
|
turnServerInfo,
|
|
5015
|
+
bundlePolicy,
|
|
4825
5016
|
});
|
|
4826
5017
|
|
|
4827
5018
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
4828
5019
|
this.setupMediaConnectionListeners();
|
|
4829
5020
|
|
|
5021
|
+
// publish the tracks
|
|
5022
|
+
if (this.mediaProperties.audioTrack) {
|
|
5023
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
5024
|
+
}
|
|
5025
|
+
if (this.mediaProperties.videoTrack) {
|
|
5026
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
5027
|
+
}
|
|
5028
|
+
if (this.mediaProperties.shareTrack) {
|
|
5029
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
5030
|
+
}
|
|
5031
|
+
|
|
4830
5032
|
return mc;
|
|
4831
5033
|
}
|
|
4832
5034
|
|
|
@@ -4854,23 +5056,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4854
5056
|
}
|
|
4855
5057
|
|
|
4856
5058
|
/**
|
|
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
|
|
5059
|
+
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
5060
|
+
*
|
|
5061
|
+
* @param {AddMediaOptions} options
|
|
4864
5062
|
* @returns {Promise}
|
|
4865
5063
|
* @public
|
|
4866
5064
|
* @memberof Meeting
|
|
4867
5065
|
*/
|
|
4868
|
-
addMedia(options:
|
|
5066
|
+
addMedia(options: AddMediaOptions = {}) {
|
|
4869
5067
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
4870
5068
|
|
|
4871
5069
|
let turnDiscoverySkippedReason;
|
|
4872
5070
|
let turnServerUsed = false;
|
|
4873
5071
|
|
|
5072
|
+
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
5073
|
+
|
|
4874
5074
|
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
4875
5075
|
return Promise.reject(new MeetingNotActiveError());
|
|
4876
5076
|
}
|
|
@@ -4884,9 +5084,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4884
5084
|
return Promise.reject(new UserInLobbyError());
|
|
4885
5085
|
}
|
|
4886
5086
|
|
|
4887
|
-
const {
|
|
4888
|
-
|
|
4889
|
-
|
|
5087
|
+
const {
|
|
5088
|
+
localTracks,
|
|
5089
|
+
audioEnabled = true,
|
|
5090
|
+
videoEnabled = true,
|
|
5091
|
+
receiveShare = true,
|
|
5092
|
+
remoteMediaManagerConfig,
|
|
5093
|
+
bundlePolicy,
|
|
5094
|
+
} = options;
|
|
4890
5095
|
|
|
4891
5096
|
Metrics.postEvent({
|
|
4892
5097
|
event: eventType.MEDIA_CAPABILITIES,
|
|
@@ -4911,17 +5116,61 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4911
5116
|
},
|
|
4912
5117
|
});
|
|
4913
5118
|
|
|
4914
|
-
|
|
5119
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any tracks are published
|
|
5120
|
+
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5121
|
+
this.mediaProperties.setMediaDirection({
|
|
5122
|
+
sendAudio: audioEnabled,
|
|
5123
|
+
sendVideo: videoEnabled,
|
|
5124
|
+
sendShare: false,
|
|
5125
|
+
receiveAudio: audioEnabled,
|
|
5126
|
+
receiveVideo: videoEnabled,
|
|
5127
|
+
receiveShare,
|
|
5128
|
+
});
|
|
5129
|
+
|
|
5130
|
+
this.locusMediaRequest = new LocusMediaRequest(
|
|
5131
|
+
{
|
|
5132
|
+
correlationId: this.correlationId,
|
|
5133
|
+
device: {
|
|
5134
|
+
url: this.deviceUrl,
|
|
5135
|
+
// @ts-ignore
|
|
5136
|
+
deviceType: this.config.deviceType,
|
|
5137
|
+
},
|
|
5138
|
+
preferTranscoding: !this.isMultistream,
|
|
5139
|
+
},
|
|
5140
|
+
{
|
|
5141
|
+
// @ts-ignore
|
|
5142
|
+
parent: this.webex,
|
|
5143
|
+
}
|
|
5144
|
+
);
|
|
5145
|
+
|
|
5146
|
+
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5147
|
+
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5148
|
+
|
|
5149
|
+
this.annotationInfo = localTracks?.annotationInfo;
|
|
5150
|
+
|
|
5151
|
+
const promises = [];
|
|
5152
|
+
|
|
5153
|
+
// setup all the references to local tracks in this.mediaProperties before creating media connection
|
|
5154
|
+
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5155
|
+
if (localTracks?.microphone) {
|
|
5156
|
+
promises.push(this.setLocalAudioTrack(localTracks.microphone));
|
|
5157
|
+
}
|
|
5158
|
+
if (localTracks?.camera) {
|
|
5159
|
+
promises.push(this.setLocalVideoTrack(localTracks.camera));
|
|
5160
|
+
}
|
|
5161
|
+
if (localTracks?.screenShare?.video) {
|
|
5162
|
+
promises.push(this.setLocalShareTrack(localTracks.screenShare.video));
|
|
5163
|
+
}
|
|
5164
|
+
|
|
5165
|
+
return Promise.all(promises)
|
|
4915
5166
|
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
4916
|
-
.then((turnDiscoveryObject) => {
|
|
5167
|
+
.then(async (turnDiscoveryObject) => {
|
|
4917
5168
|
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
4918
5169
|
turnServerUsed = !turnDiscoverySkippedReason;
|
|
4919
5170
|
|
|
4920
5171
|
const {turnServerInfo} = turnDiscoveryObject;
|
|
4921
5172
|
|
|
4922
|
-
this.
|
|
4923
|
-
|
|
4924
|
-
const mc = this.createMediaConnection(turnServerInfo);
|
|
5173
|
+
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
4925
5174
|
|
|
4926
5175
|
if (this.isMultistream) {
|
|
4927
5176
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -4946,18 +5195,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4946
5195
|
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
4947
5196
|
);
|
|
4948
5197
|
|
|
4949
|
-
|
|
5198
|
+
await this.remoteMediaManager.start();
|
|
4950
5199
|
}
|
|
4951
5200
|
|
|
4952
|
-
|
|
5201
|
+
await mc.initiateOffer();
|
|
4953
5202
|
})
|
|
4954
5203
|
.then(() => {
|
|
4955
5204
|
this.setMercuryListener();
|
|
4956
5205
|
})
|
|
4957
|
-
.then(
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
5206
|
+
.then(
|
|
5207
|
+
() =>
|
|
5208
|
+
getDevices()
|
|
5209
|
+
.then((devices) => {
|
|
5210
|
+
MeetingUtil.handleDeviceLogging(devices);
|
|
5211
|
+
})
|
|
5212
|
+
.catch(() => {}) // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
4961
5213
|
)
|
|
4962
5214
|
.then(() => {
|
|
4963
5215
|
this.handleMediaLogging(this.mediaProperties);
|
|
@@ -4967,8 +5219,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4967
5219
|
if (this.config.stats.enableStatsAnalyzer) {
|
|
4968
5220
|
// @ts-ignore - config coming from registerPlugin
|
|
4969
5221
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
4970
|
-
|
|
4971
|
-
|
|
5222
|
+
this.statsAnalyzer = new StatsAnalyzer(
|
|
5223
|
+
// @ts-ignore - config coming from registerPlugin
|
|
5224
|
+
this.config.stats,
|
|
5225
|
+
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5226
|
+
this.networkQualityMonitor
|
|
5227
|
+
);
|
|
4972
5228
|
this.setupStatsAnalyzerEventHandlers();
|
|
4973
5229
|
this.networkQualityMonitor.on(
|
|
4974
5230
|
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
@@ -4991,7 +5247,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4991
5247
|
|
|
4992
5248
|
// eslint-disable-next-line func-names
|
|
4993
5249
|
// eslint-disable-next-line prefer-arrow-callback
|
|
4994
|
-
if (this.type === _CALL_) {
|
|
5250
|
+
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
4995
5251
|
resolve();
|
|
4996
5252
|
}
|
|
4997
5253
|
const joiningTimer = setInterval(() => {
|
|
@@ -5010,21 +5266,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5010
5266
|
)
|
|
5011
5267
|
.then(() =>
|
|
5012
5268
|
this.mediaProperties.waitForMediaConnectionConnected().catch(() => {
|
|
5013
|
-
throw
|
|
5269
|
+
throw new Error(
|
|
5270
|
+
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5271
|
+
);
|
|
5014
5272
|
})
|
|
5015
5273
|
)
|
|
5016
5274
|
.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;
|
|
5275
|
+
if (localTracks?.screenShare?.video) {
|
|
5276
|
+
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
|
|
5025
5277
|
}
|
|
5026
|
-
|
|
5027
|
-
return {};
|
|
5028
5278
|
})
|
|
5029
5279
|
.then(() => this.mediaProperties.getCurrentConnectionType())
|
|
5030
5280
|
.then((connectionType) => {
|
|
@@ -5032,9 +5282,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5032
5282
|
correlation_id: this.correlationId,
|
|
5033
5283
|
locus_id: this.locusUrl.split('/').pop(),
|
|
5034
5284
|
connectionType,
|
|
5285
|
+
isMultistream: this.isMultistream,
|
|
5035
5286
|
});
|
|
5036
5287
|
})
|
|
5037
5288
|
.catch((error) => {
|
|
5289
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5290
|
+
correlation_id: this.correlationId,
|
|
5291
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5292
|
+
reason: error.message,
|
|
5293
|
+
stack: error.stack,
|
|
5294
|
+
code: error.code,
|
|
5295
|
+
turnDiscoverySkippedReason,
|
|
5296
|
+
turnServerUsed,
|
|
5297
|
+
isMultistream: this.isMultistream,
|
|
5298
|
+
signalingState:
|
|
5299
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5300
|
+
?.signalingState ||
|
|
5301
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
5302
|
+
'unknown',
|
|
5303
|
+
connectionState:
|
|
5304
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5305
|
+
?.connectionState ||
|
|
5306
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
5307
|
+
'unknown',
|
|
5308
|
+
iceConnectionState:
|
|
5309
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5310
|
+
?.iceConnectionState ||
|
|
5311
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
5312
|
+
'unknown',
|
|
5313
|
+
});
|
|
5314
|
+
|
|
5038
5315
|
// Clean up stats analyzer, peer connection, and turn off listeners
|
|
5039
5316
|
const stopStatsAnalyzer = this.statsAnalyzer
|
|
5040
5317
|
? this.statsAnalyzer.stopAnalyzer()
|
|
@@ -5053,16 +5330,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5053
5330
|
error
|
|
5054
5331
|
);
|
|
5055
5332
|
|
|
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
5333
|
// Upload logs on error while adding media
|
|
5067
5334
|
Trigger.trigger(
|
|
5068
5335
|
this,
|
|
@@ -5074,7 +5341,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5074
5341
|
this
|
|
5075
5342
|
);
|
|
5076
5343
|
|
|
5077
|
-
if (error instanceof
|
|
5344
|
+
if (error instanceof Errors.SdpError) {
|
|
5078
5345
|
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
5079
5346
|
}
|
|
5080
5347
|
|
|
@@ -5102,7 +5369,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5102
5369
|
* @private
|
|
5103
5370
|
* @memberof Meeting
|
|
5104
5371
|
*/
|
|
5105
|
-
private enqueueMediaUpdate(mediaUpdateType: string, options:
|
|
5372
|
+
private enqueueMediaUpdate(mediaUpdateType: string, options: any = {}): Promise<void> {
|
|
5373
|
+
const canUpdateMediaNow = this.canUpdateMedia();
|
|
5374
|
+
|
|
5106
5375
|
return new Promise((resolve, reject) => {
|
|
5107
5376
|
const queueItem = {
|
|
5108
5377
|
pendingPromiseResolve: resolve,
|
|
@@ -5115,6 +5384,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5115
5384
|
`Meeting:index#enqueueMediaUpdate --> enqueuing media update type=${mediaUpdateType}`
|
|
5116
5385
|
);
|
|
5117
5386
|
this.queuedMediaUpdates.push(queueItem);
|
|
5387
|
+
|
|
5388
|
+
if (canUpdateMediaNow) {
|
|
5389
|
+
this.processNextQueuedMediaUpdate();
|
|
5390
|
+
}
|
|
5118
5391
|
});
|
|
5119
5392
|
}
|
|
5120
5393
|
|
|
@@ -5154,18 +5427,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5154
5427
|
LoggerProxy.logger.log(
|
|
5155
5428
|
`Meeting:index#processNextQueuedMediaUpdate --> performing delayed media update type=${mediaUpdateType}`
|
|
5156
5429
|
);
|
|
5430
|
+
let mediaUpdate = Promise.resolve();
|
|
5431
|
+
|
|
5157
5432
|
switch (mediaUpdateType) {
|
|
5158
|
-
case MEDIA_UPDATE_TYPE.
|
|
5159
|
-
this.
|
|
5160
|
-
break;
|
|
5161
|
-
case MEDIA_UPDATE_TYPE.AUDIO:
|
|
5162
|
-
this.updateAudio(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5433
|
+
case MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION:
|
|
5434
|
+
mediaUpdate = this.updateTranscodedMediaConnection();
|
|
5163
5435
|
break;
|
|
5164
|
-
case MEDIA_UPDATE_TYPE.
|
|
5165
|
-
this.
|
|
5436
|
+
case MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST:
|
|
5437
|
+
mediaUpdate = this.requestScreenShareFloor();
|
|
5166
5438
|
break;
|
|
5167
|
-
case MEDIA_UPDATE_TYPE.
|
|
5168
|
-
this.
|
|
5439
|
+
case MEDIA_UPDATE_TYPE.UPDATE_MEDIA:
|
|
5440
|
+
mediaUpdate = this.updateMedia(options);
|
|
5169
5441
|
break;
|
|
5170
5442
|
default:
|
|
5171
5443
|
LoggerProxy.logger.error(
|
|
@@ -5173,329 +5445,84 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5173
5445
|
);
|
|
5174
5446
|
break;
|
|
5175
5447
|
}
|
|
5448
|
+
|
|
5449
|
+
mediaUpdate
|
|
5450
|
+
.then(pendingPromiseResolve, pendingPromiseReject)
|
|
5451
|
+
.then(() => this.processNextQueuedMediaUpdate());
|
|
5176
5452
|
}
|
|
5177
5453
|
};
|
|
5178
5454
|
|
|
5179
5455
|
/**
|
|
5180
|
-
*
|
|
5181
|
-
*
|
|
5456
|
+
* Updates the media connection - it allows to enable/disable all audio/video/share in the meeting.
|
|
5457
|
+
* This does not affect the published tracks, so for example if a microphone track is published and
|
|
5458
|
+
* updateMedia({audioEnabled: false}) is called, the audio will not be sent or received anymore,
|
|
5459
|
+
* but the track's "published" state is not changed and when updateMedia({audioEnabled: true}) is called,
|
|
5460
|
+
* the sending of the audio from the same track will resume.
|
|
5461
|
+
*
|
|
5182
5462
|
* @param {Object} options
|
|
5183
|
-
* @param {
|
|
5184
|
-
* @param {
|
|
5185
|
-
* @param {
|
|
5463
|
+
* @param {boolean} options.audioEnabled [optional] enables/disables receiving and sending of main audio in the meeting
|
|
5464
|
+
* @param {boolean} options.videoEnabled [optional] enables/disables receiving and sending of main video in the meeting
|
|
5465
|
+
* @param {boolean} options.shareEnabled [optional] enables/disables receiving and sending of screen share in the meeting
|
|
5186
5466
|
* @returns {Promise}
|
|
5187
5467
|
* @public
|
|
5188
5468
|
* @memberof Meeting
|
|
5189
5469
|
*/
|
|
5190
|
-
public updateMedia(
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
) {
|
|
5197
|
-
const LOG_HEADER = 'Meeting:index#updateMedia -->';
|
|
5470
|
+
public async updateMedia(options: {
|
|
5471
|
+
audioEnabled?: boolean;
|
|
5472
|
+
videoEnabled?: boolean;
|
|
5473
|
+
receiveShare?: boolean;
|
|
5474
|
+
}) {
|
|
5475
|
+
this.checkMediaConnection();
|
|
5198
5476
|
|
|
5199
|
-
|
|
5200
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.ALL, options);
|
|
5201
|
-
}
|
|
5202
|
-
const {localStream, localShare, mediaSettings} = options;
|
|
5477
|
+
const {audioEnabled, videoEnabled, receiveShare} = options;
|
|
5203
5478
|
|
|
5204
|
-
|
|
5479
|
+
LoggerProxy.logger.log(
|
|
5480
|
+
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
5481
|
+
);
|
|
5205
5482
|
|
|
5206
|
-
if (!this.
|
|
5207
|
-
return
|
|
5483
|
+
if (!this.canUpdateMedia()) {
|
|
5484
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
5208
5485
|
}
|
|
5209
5486
|
|
|
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
|
-
}
|
|
5487
|
+
if (this.isMultistream) {
|
|
5488
|
+
if (videoEnabled !== undefined) {
|
|
5489
|
+
throw new Error(
|
|
5490
|
+
'enabling/disabling video in a meeting is not supported for multistream, it can only be done upfront when calling addMedia()'
|
|
5491
|
+
);
|
|
5492
|
+
}
|
|
5267
5493
|
|
|
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);
|
|
5494
|
+
if (receiveShare !== undefined) {
|
|
5495
|
+
throw new Error(
|
|
5496
|
+
'toggling receiveShare in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5497
|
+
);
|
|
5498
|
+
}
|
|
5288
5499
|
}
|
|
5289
|
-
const {sendAudio, receiveAudio, stream} = options;
|
|
5290
|
-
let track = MeetingUtil.getTrack(stream).audioTrack;
|
|
5291
5500
|
|
|
5292
|
-
if (
|
|
5293
|
-
|
|
5501
|
+
if (audioEnabled !== undefined) {
|
|
5502
|
+
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5503
|
+
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5504
|
+
this.audio.enable(this, audioEnabled);
|
|
5294
5505
|
}
|
|
5295
5506
|
|
|
5296
|
-
if (
|
|
5297
|
-
|
|
5507
|
+
if (videoEnabled !== undefined) {
|
|
5508
|
+
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5509
|
+
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5510
|
+
this.video.enable(this, videoEnabled);
|
|
5298
5511
|
}
|
|
5299
5512
|
|
|
5300
|
-
if (
|
|
5301
|
-
|
|
5513
|
+
if (receiveShare !== undefined) {
|
|
5514
|
+
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
5515
|
+
}
|
|
5302
5516
|
|
|
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');
|
|
5517
|
+
if (this.isMultistream) {
|
|
5518
|
+
if (audioEnabled !== undefined) {
|
|
5519
|
+
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5311
5520
|
}
|
|
5521
|
+
} else {
|
|
5522
|
+
await this.updateTranscodedMediaConnection();
|
|
5312
5523
|
}
|
|
5313
5524
|
|
|
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
|
-
});
|
|
5336
|
-
}
|
|
5337
|
-
|
|
5338
|
-
/**
|
|
5339
|
-
* Update the main video track with new parameters
|
|
5340
|
-
*
|
|
5341
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
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}
|
|
5348
|
-
* @public
|
|
5349
|
-
* @memberof Meeting
|
|
5350
|
-
*/
|
|
5351
|
-
public updateVideo(options: {sendVideo: boolean; receiveVideo: boolean; stream: MediaStream}) {
|
|
5352
|
-
if (!this.canUpdateMedia()) {
|
|
5353
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.VIDEO, options);
|
|
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'));
|
|
5360
|
-
}
|
|
5361
|
-
|
|
5362
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
5363
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
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);
|
|
5525
|
+
return undefined;
|
|
5499
5526
|
}
|
|
5500
5527
|
|
|
5501
5528
|
/**
|
|
@@ -5563,13 +5590,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5563
5590
|
* @memberof Meeting
|
|
5564
5591
|
*/
|
|
5565
5592
|
public leave(options: {resourceId?: string; reason?: any} = {} as any) {
|
|
5593
|
+
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5566
5594
|
Metrics.postEvent({
|
|
5567
5595
|
event: eventType.LEAVE,
|
|
5568
5596
|
meeting: this,
|
|
5569
|
-
data: {trigger: trigger.USER_INTERACTION, canProceed: false},
|
|
5597
|
+
data: {trigger: trigger.USER_INTERACTION, canProceed: false, reason: leaveReason},
|
|
5570
5598
|
});
|
|
5571
|
-
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5572
|
-
|
|
5573
5599
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
5574
5600
|
|
|
5575
5601
|
return MeetingUtil.leaveMeeting(this, options)
|
|
@@ -5738,55 +5764,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5738
5764
|
* @memberof Meeting
|
|
5739
5765
|
*/
|
|
5740
5766
|
private requestScreenShareFloor() {
|
|
5741
|
-
|
|
5767
|
+
if (!this.mediaProperties.shareTrack || !this.mediaProperties.mediaDirection.sendShare) {
|
|
5768
|
+
LoggerProxy.logger.log(
|
|
5769
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareTrack=${
|
|
5770
|
+
this.mediaProperties.shareTrack ? 'yes' : 'no'
|
|
5771
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
5772
|
+
);
|
|
5742
5773
|
|
|
5743
|
-
|
|
5744
|
-
|
|
5774
|
+
return Promise.resolve({});
|
|
5775
|
+
}
|
|
5776
|
+
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
5777
|
+
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5778
|
+
|
|
5779
|
+
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
5780
|
+
Metrics.postEvent({event: eventType.SHARE_INITIATED, meeting: this});
|
|
5781
|
+
|
|
5782
|
+
return this.meetingRequest
|
|
5783
|
+
.changeMeetingFloor({
|
|
5784
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
5785
|
+
personUrl: this.locusInfo.self.url,
|
|
5786
|
+
deviceUrl: this.deviceUrl,
|
|
5787
|
+
uri: content.url,
|
|
5788
|
+
resourceUrl: this.resourceUrl,
|
|
5789
|
+
annotationInfo: this.annotationInfo,
|
|
5790
|
+
})
|
|
5791
|
+
.then(() => {
|
|
5792
|
+
this.isSharing = true;
|
|
5745
5793
|
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
deviceUrl: this.deviceUrl,
|
|
5751
|
-
uri: content.url,
|
|
5752
|
-
resourceUrl: this.resourceUrl,
|
|
5753
|
-
})
|
|
5754
|
-
.then(() => {
|
|
5755
|
-
this.isSharing = true;
|
|
5794
|
+
return Promise.resolve();
|
|
5795
|
+
})
|
|
5796
|
+
.catch((error) => {
|
|
5797
|
+
LoggerProxy.logger.error('Meeting:index#share --> Error ', error);
|
|
5756
5798
|
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5799
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_FAILURE, {
|
|
5800
|
+
correlation_id: this.correlationId,
|
|
5801
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5802
|
+
reason: error.message,
|
|
5803
|
+
stack: error.stack,
|
|
5804
|
+
});
|
|
5761
5805
|
|
|
5762
|
-
|
|
5763
|
-
correlation_id: this.correlationId,
|
|
5764
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5765
|
-
reason: error.message,
|
|
5766
|
-
stack: error.stack,
|
|
5806
|
+
return Promise.reject(error);
|
|
5767
5807
|
});
|
|
5808
|
+
}
|
|
5768
5809
|
|
|
5769
|
-
|
|
5770
|
-
});
|
|
5810
|
+
return Promise.reject(new ParameterError('Cannot share without content.'));
|
|
5771
5811
|
}
|
|
5812
|
+
this.floorGrantPending = true;
|
|
5772
5813
|
|
|
5773
|
-
return Promise.
|
|
5814
|
+
return Promise.resolve({});
|
|
5774
5815
|
}
|
|
5775
5816
|
|
|
5776
5817
|
/**
|
|
5777
|
-
*
|
|
5778
|
-
*
|
|
5779
|
-
*
|
|
5780
|
-
* @
|
|
5818
|
+
* Requests screen share floor if such request is pending.
|
|
5819
|
+
* It should be called whenever meeting state changes to JOINED
|
|
5820
|
+
*
|
|
5821
|
+
* @returns {void}
|
|
5781
5822
|
*/
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
...options,
|
|
5789
|
-
});
|
|
5823
|
+
private requestScreenShareFloorIfPending() {
|
|
5824
|
+
if (this.floorGrantPending && this.state === MEETING_STATE.STATES.JOINED) {
|
|
5825
|
+
this.requestScreenShareFloor().then(() => {
|
|
5826
|
+
this.floorGrantPending = false;
|
|
5827
|
+
});
|
|
5828
|
+
}
|
|
5790
5829
|
}
|
|
5791
5830
|
|
|
5792
5831
|
/**
|
|
@@ -5798,11 +5837,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5798
5837
|
private releaseScreenShareFloor() {
|
|
5799
5838
|
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5800
5839
|
|
|
5801
|
-
if (content
|
|
5840
|
+
if (content) {
|
|
5802
5841
|
Metrics.postEvent({event: eventType.SHARE_STOPPED, meeting: this});
|
|
5803
|
-
Media.stopTracks(this.mediaProperties.shareTrack);
|
|
5804
5842
|
|
|
5805
|
-
if (content.floor
|
|
5843
|
+
if (content.floor?.beneficiary.id !== this.selfId) {
|
|
5806
5844
|
// remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
|
|
5807
5845
|
this.isSharing = false;
|
|
5808
5846
|
|
|
@@ -5834,7 +5872,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5834
5872
|
});
|
|
5835
5873
|
}
|
|
5836
5874
|
|
|
5837
|
-
|
|
5875
|
+
// according to Locus there is no content, so we don't need to release the floor (it's probably already been released)
|
|
5876
|
+
this.isSharing = false;
|
|
5877
|
+
|
|
5878
|
+
return Promise.resolve();
|
|
5838
5879
|
}
|
|
5839
5880
|
|
|
5840
5881
|
/**
|
|
@@ -5844,7 +5885,50 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5844
5885
|
* @memberof Meeting
|
|
5845
5886
|
*/
|
|
5846
5887
|
public startRecording() {
|
|
5847
|
-
return
|
|
5888
|
+
return this.recordingController.startRecording();
|
|
5889
|
+
}
|
|
5890
|
+
|
|
5891
|
+
/**
|
|
5892
|
+
* set the mute on entry flag for participants if you're the host
|
|
5893
|
+
* @returns {Promise}
|
|
5894
|
+
* @param {boolean} enabled
|
|
5895
|
+
* @public
|
|
5896
|
+
* @memberof Meeting
|
|
5897
|
+
*/
|
|
5898
|
+
public setMuteOnEntry(enabled: boolean) {
|
|
5899
|
+
return this.controlsOptionsManager.setMuteOnEntry(enabled);
|
|
5900
|
+
}
|
|
5901
|
+
|
|
5902
|
+
/**
|
|
5903
|
+
* set the disallow unmute flag for participants if you're the host
|
|
5904
|
+
* @returns {Promise}
|
|
5905
|
+
* @param {boolean} enabled
|
|
5906
|
+
* @public
|
|
5907
|
+
* @memberof Meeting
|
|
5908
|
+
*/
|
|
5909
|
+
public setDisallowUnmute(enabled: boolean) {
|
|
5910
|
+
return this.controlsOptionsManager.setDisallowUnmute(enabled);
|
|
5911
|
+
}
|
|
5912
|
+
|
|
5913
|
+
/**
|
|
5914
|
+
* set the mute all flag for participants if you're the host
|
|
5915
|
+
* @returns {Promise}
|
|
5916
|
+
* @param {boolean} mutedEnabled
|
|
5917
|
+
* @param {boolean} disallowUnmuteEnabled
|
|
5918
|
+
* @param {boolean} muteOnEntryEnabled
|
|
5919
|
+
* @public
|
|
5920
|
+
* @memberof Meeting
|
|
5921
|
+
*/
|
|
5922
|
+
public setMuteAll(
|
|
5923
|
+
mutedEnabled: boolean,
|
|
5924
|
+
disallowUnmuteEnabled: boolean,
|
|
5925
|
+
muteOnEntryEnabled: boolean
|
|
5926
|
+
) {
|
|
5927
|
+
return this.controlsOptionsManager.setMuteAll(
|
|
5928
|
+
mutedEnabled,
|
|
5929
|
+
disallowUnmuteEnabled,
|
|
5930
|
+
muteOnEntryEnabled
|
|
5931
|
+
);
|
|
5848
5932
|
}
|
|
5849
5933
|
|
|
5850
5934
|
/**
|
|
@@ -5854,7 +5938,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5854
5938
|
* @memberof Meeting
|
|
5855
5939
|
*/
|
|
5856
5940
|
public stopRecording() {
|
|
5857
|
-
return
|
|
5941
|
+
return this.recordingController.stopRecording();
|
|
5858
5942
|
}
|
|
5859
5943
|
|
|
5860
5944
|
/**
|
|
@@ -5864,7 +5948,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5864
5948
|
* @memberof Meeting
|
|
5865
5949
|
*/
|
|
5866
5950
|
public pauseRecording() {
|
|
5867
|
-
return
|
|
5951
|
+
return this.recordingController.pauseRecording();
|
|
5868
5952
|
}
|
|
5869
5953
|
|
|
5870
5954
|
/**
|
|
@@ -5874,7 +5958,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5874
5958
|
* @memberof Meeting
|
|
5875
5959
|
*/
|
|
5876
5960
|
public resumeRecording() {
|
|
5877
|
-
return
|
|
5961
|
+
return this.recordingController.resumeRecording();
|
|
5878
5962
|
}
|
|
5879
5963
|
|
|
5880
5964
|
/**
|
|
@@ -6048,11 +6132,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6048
6132
|
main: layoutInfo.main,
|
|
6049
6133
|
content: layoutInfo.content,
|
|
6050
6134
|
})
|
|
6051
|
-
.then((response) => {
|
|
6052
|
-
if (response && response.body && response.body.locus) {
|
|
6053
|
-
this.locusInfo.onFullLocus(response.body.locus);
|
|
6054
|
-
}
|
|
6055
|
-
})
|
|
6056
6135
|
.catch((error) => {
|
|
6057
6136
|
LoggerProxy.logger.error('Meeting:index#changeVideoLayout --> Error ', error);
|
|
6058
6137
|
|
|
@@ -6060,62 +6139,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6060
6139
|
});
|
|
6061
6140
|
}
|
|
6062
6141
|
|
|
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
6142
|
/**
|
|
6120
6143
|
* Sets the quality level of the remote incoming media
|
|
6121
6144
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
@@ -6151,129 +6174,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6151
6174
|
// Set the quality level in properties
|
|
6152
6175
|
this.mediaProperties.setRemoteQualityLevel(level);
|
|
6153
6176
|
|
|
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
|
-
});
|
|
6177
|
+
return this.updateTranscodedMediaConnection();
|
|
6277
6178
|
}
|
|
6278
6179
|
|
|
6279
6180
|
/**
|
|
@@ -6283,20 +6184,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6283
6184
|
* @param {MediaStream} localShare
|
|
6284
6185
|
* @returns {undefined}
|
|
6285
6186
|
*/
|
|
6286
|
-
private handleShareTrackEnded(
|
|
6187
|
+
private handleShareTrackEnded = async () => {
|
|
6287
6188
|
if (this.wirelessShare) {
|
|
6288
6189
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6289
6190
|
} else {
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
skipSignalingCheck: true,
|
|
6294
|
-
}).catch((error) => {
|
|
6191
|
+
try {
|
|
6192
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo: screen share audio (SPARK-399690)
|
|
6193
|
+
} catch (error) {
|
|
6295
6194
|
LoggerProxy.logger.log(
|
|
6296
6195
|
'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
|
|
6297
6196
|
error
|
|
6298
6197
|
);
|
|
6299
|
-
}
|
|
6198
|
+
}
|
|
6300
6199
|
}
|
|
6301
6200
|
|
|
6302
6201
|
Trigger.trigger(
|
|
@@ -6307,11 +6206,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6307
6206
|
},
|
|
6308
6207
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
6309
6208
|
{
|
|
6310
|
-
|
|
6311
|
-
stream: localShare,
|
|
6209
|
+
reason: SHARE_STOPPED_REASON.TRACK_ENDED,
|
|
6312
6210
|
}
|
|
6313
6211
|
);
|
|
6314
|
-
}
|
|
6212
|
+
};
|
|
6315
6213
|
|
|
6316
6214
|
/**
|
|
6317
6215
|
* Emits the 'network:quality' event
|
|
@@ -6341,14 +6239,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6341
6239
|
|
|
6342
6240
|
/**
|
|
6343
6241
|
* Handle logging the media
|
|
6344
|
-
* @param {Object}
|
|
6345
|
-
* @param {Object} videoTrack The video track
|
|
6242
|
+
* @param {Object} mediaProperties
|
|
6346
6243
|
* @private
|
|
6347
6244
|
* @returns {undefined}
|
|
6348
6245
|
*/
|
|
6349
|
-
private handleMediaLogging(
|
|
6350
|
-
|
|
6351
|
-
|
|
6246
|
+
private handleMediaLogging(mediaProperties: {
|
|
6247
|
+
audioTrack?: LocalMicrophoneTrack;
|
|
6248
|
+
videoTrack?: LocalCameraTrack;
|
|
6249
|
+
}) {
|
|
6250
|
+
MeetingUtil.handleVideoLogging(mediaProperties.videoTrack);
|
|
6251
|
+
MeetingUtil.handleAudioLogging(mediaProperties.audioTrack);
|
|
6352
6252
|
}
|
|
6353
6253
|
|
|
6354
6254
|
/**
|
|
@@ -6437,7 +6337,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6437
6337
|
const end = this.endLocalSDPGenRemoteSDPRecvDelay;
|
|
6438
6338
|
|
|
6439
6339
|
if (start && end) {
|
|
6440
|
-
const calculatedDelay = end - start;
|
|
6340
|
+
const calculatedDelay = Math.round(end - start);
|
|
6441
6341
|
|
|
6442
6342
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6443
6343
|
}
|
|
@@ -6449,26 +6349,26 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6449
6349
|
*
|
|
6450
6350
|
* @returns {undefined}
|
|
6451
6351
|
*/
|
|
6452
|
-
|
|
6453
|
-
this.
|
|
6454
|
-
this.
|
|
6352
|
+
setStartCallInitJoinReq() {
|
|
6353
|
+
this.startCallInitJoinReq = performance.now();
|
|
6354
|
+
this.endCallInitJoinReq = undefined;
|
|
6455
6355
|
}
|
|
6456
6356
|
|
|
6457
6357
|
/**
|
|
6458
6358
|
*
|
|
6459
6359
|
* @returns {undefined}
|
|
6460
6360
|
*/
|
|
6461
|
-
|
|
6462
|
-
this.
|
|
6361
|
+
setEndCallInitJoinReq() {
|
|
6362
|
+
this.endCallInitJoinReq = performance.now();
|
|
6463
6363
|
}
|
|
6464
6364
|
|
|
6465
6365
|
/**
|
|
6466
6366
|
*
|
|
6467
6367
|
* @returns {string} duration between call initiate and sending join request to locus
|
|
6468
6368
|
*/
|
|
6469
|
-
|
|
6470
|
-
const start = this.
|
|
6471
|
-
const end = this.
|
|
6369
|
+
getCallInitJoinReq() {
|
|
6370
|
+
const start = this.startCallInitJoinReq;
|
|
6371
|
+
const end = this.endCallInitJoinReq;
|
|
6472
6372
|
|
|
6473
6373
|
if (start && end) {
|
|
6474
6374
|
const calculatedDelay = end - start;
|
|
@@ -6505,7 +6405,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6505
6405
|
const end = this.endJoinReqResp;
|
|
6506
6406
|
|
|
6507
6407
|
if (start && end) {
|
|
6508
|
-
const calculatedDelay = end - start;
|
|
6408
|
+
const calculatedDelay = Math.round(end - start);
|
|
6509
6409
|
|
|
6510
6410
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6511
6411
|
}
|
|
@@ -6518,10 +6418,45 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6518
6418
|
* @returns {string} duration between call initiate and successful locus join (even if it is in lobby)
|
|
6519
6419
|
*/
|
|
6520
6420
|
getTotalJmt() {
|
|
6521
|
-
const start = this.
|
|
6421
|
+
const start = this.startCallInitJoinReq;
|
|
6522
6422
|
const end = this.endJoinReqResp;
|
|
6523
6423
|
|
|
6524
|
-
return start && end ? end - start : undefined;
|
|
6424
|
+
return start && end ? Math.round(end - start) : undefined;
|
|
6425
|
+
}
|
|
6426
|
+
|
|
6427
|
+
/**
|
|
6428
|
+
*
|
|
6429
|
+
* @returns {string} one of 'attendee','host','cohost', returns the user type of the current user
|
|
6430
|
+
*/
|
|
6431
|
+
getCurUserType() {
|
|
6432
|
+
const {roles} = this;
|
|
6433
|
+
if (roles) {
|
|
6434
|
+
if (roles.includes(SELF_ROLES.MODERATOR)) {
|
|
6435
|
+
return 'host';
|
|
6436
|
+
}
|
|
6437
|
+
if (roles.includes(SELF_ROLES.COHOST)) {
|
|
6438
|
+
return 'cohost';
|
|
6439
|
+
}
|
|
6440
|
+
if (roles.includes(SELF_ROLES.ATTENDEE)) {
|
|
6441
|
+
return 'attendee';
|
|
6442
|
+
}
|
|
6443
|
+
}
|
|
6444
|
+
|
|
6445
|
+
return null;
|
|
6446
|
+
}
|
|
6447
|
+
|
|
6448
|
+
/**
|
|
6449
|
+
*
|
|
6450
|
+
* @returns {string} one of 'login-ci','unverified-guest', returns the login type of the current user
|
|
6451
|
+
*/
|
|
6452
|
+
getCurLoginType() {
|
|
6453
|
+
// @ts-ignore
|
|
6454
|
+
if (this.webex.canAuthorize) {
|
|
6455
|
+
// @ts-ignore
|
|
6456
|
+
return this.webex.credentials.isUnverifiedGuest ? 'unverified-guest' : 'login-ci';
|
|
6457
|
+
}
|
|
6458
|
+
|
|
6459
|
+
return null;
|
|
6525
6460
|
}
|
|
6526
6461
|
|
|
6527
6462
|
/**
|
|
@@ -6611,121 +6546,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6611
6546
|
}
|
|
6612
6547
|
};
|
|
6613
6548
|
|
|
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
6549
|
/**
|
|
6730
6550
|
* starts keepAlives being sent
|
|
6731
6551
|
* @returns {void}
|
|
@@ -6791,13 +6611,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6791
6611
|
/**
|
|
6792
6612
|
* Send a reaction inside the meeting.
|
|
6793
6613
|
*
|
|
6794
|
-
* @param {
|
|
6614
|
+
* @param {ReactionServerType} reactionType - type of reaction to be sent. Example: "thumbs_up"
|
|
6795
6615
|
* @param {SkinToneType} skinToneType - skin tone for the reaction. Example: "medium_dark"
|
|
6796
6616
|
* @returns {Promise}
|
|
6797
6617
|
* @public
|
|
6798
6618
|
* @memberof Meeting
|
|
6799
6619
|
*/
|
|
6800
|
-
public sendReaction(reactionType:
|
|
6620
|
+
public sendReaction(reactionType: ReactionServerType, skinToneType?: SkinToneType) {
|
|
6801
6621
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
6802
6622
|
const participantId = this.members.selfId;
|
|
6803
6623
|
|
|
@@ -6840,4 +6660,222 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6840
6660
|
requestingParticipantId: this.members.selfId,
|
|
6841
6661
|
});
|
|
6842
6662
|
}
|
|
6663
|
+
|
|
6664
|
+
/**
|
|
6665
|
+
* Throws if we don't have a media connection created
|
|
6666
|
+
*
|
|
6667
|
+
* @returns {void}
|
|
6668
|
+
*/
|
|
6669
|
+
private checkMediaConnection() {
|
|
6670
|
+
if (this.mediaProperties?.webrtcMediaConnection) {
|
|
6671
|
+
return;
|
|
6672
|
+
}
|
|
6673
|
+
throw new NoMediaEstablishedYetError();
|
|
6674
|
+
}
|
|
6675
|
+
|
|
6676
|
+
/**
|
|
6677
|
+
* Method to enable or disable the 'Music mode' effect on audio track
|
|
6678
|
+
*
|
|
6679
|
+
* @param {boolean} shouldEnableMusicMode
|
|
6680
|
+
* @returns {Promise}
|
|
6681
|
+
*/
|
|
6682
|
+
async enableMusicMode(shouldEnableMusicMode: boolean) {
|
|
6683
|
+
this.checkMediaConnection();
|
|
6684
|
+
|
|
6685
|
+
if (!this.isMultistream) {
|
|
6686
|
+
throw new Error('enableMusicMode() only supported with multistream');
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6689
|
+
if (shouldEnableMusicMode) {
|
|
6690
|
+
await this.mediaProperties.webrtcMediaConnection.setCodecParameters(MediaType.AudioMain, {
|
|
6691
|
+
maxaveragebitrate: '64000',
|
|
6692
|
+
maxplaybackrate: '48000',
|
|
6693
|
+
});
|
|
6694
|
+
} else {
|
|
6695
|
+
await this.mediaProperties.webrtcMediaConnection.deleteCodecParameters(MediaType.AudioMain, [
|
|
6696
|
+
'maxaveragebitrate',
|
|
6697
|
+
'maxplaybackrate',
|
|
6698
|
+
]);
|
|
6699
|
+
}
|
|
6700
|
+
}
|
|
6701
|
+
|
|
6702
|
+
/** Updates the tracks being sent on the transcoded media connection
|
|
6703
|
+
*
|
|
6704
|
+
* @returns {Promise<void>}
|
|
6705
|
+
*/
|
|
6706
|
+
private updateTranscodedMediaConnection(): Promise<void> {
|
|
6707
|
+
const LOG_HEADER = 'Meeting:index#updateTranscodedMediaConnection -->';
|
|
6708
|
+
|
|
6709
|
+
LoggerProxy.logger.info(`${LOG_HEADER} starting`);
|
|
6710
|
+
|
|
6711
|
+
if (!this.canUpdateMedia()) {
|
|
6712
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION);
|
|
6713
|
+
}
|
|
6714
|
+
|
|
6715
|
+
return this.mediaProperties.webrtcMediaConnection
|
|
6716
|
+
.update({
|
|
6717
|
+
localTracks: {
|
|
6718
|
+
audio: this.mediaProperties.audioTrack?.underlyingTrack || null,
|
|
6719
|
+
video: this.mediaProperties.videoTrack?.underlyingTrack || null,
|
|
6720
|
+
screenShareVideo: this.mediaProperties.shareTrack?.underlyingTrack || null,
|
|
6721
|
+
},
|
|
6722
|
+
direction: {
|
|
6723
|
+
audio: Media.getDirection(
|
|
6724
|
+
true,
|
|
6725
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6726
|
+
this.mediaProperties.mediaDirection.sendAudio
|
|
6727
|
+
),
|
|
6728
|
+
video: Media.getDirection(
|
|
6729
|
+
true,
|
|
6730
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6731
|
+
this.mediaProperties.mediaDirection.sendVideo
|
|
6732
|
+
),
|
|
6733
|
+
screenShareVideo: Media.getDirection(
|
|
6734
|
+
false,
|
|
6735
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
6736
|
+
this.mediaProperties.mediaDirection.sendShare
|
|
6737
|
+
),
|
|
6738
|
+
},
|
|
6739
|
+
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6740
|
+
})
|
|
6741
|
+
.then(() => {
|
|
6742
|
+
LoggerProxy.logger.info(`${LOG_HEADER} done`);
|
|
6743
|
+
})
|
|
6744
|
+
.catch((error) => {
|
|
6745
|
+
LoggerProxy.logger.error(`${LOG_HEADER} Error: `, error);
|
|
6746
|
+
|
|
6747
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
6748
|
+
correlation_id: this.correlationId,
|
|
6749
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6750
|
+
reason: error.message,
|
|
6751
|
+
stack: error.stack,
|
|
6752
|
+
});
|
|
6753
|
+
|
|
6754
|
+
throw error;
|
|
6755
|
+
});
|
|
6756
|
+
}
|
|
6757
|
+
|
|
6758
|
+
/**
|
|
6759
|
+
* Publishes a track.
|
|
6760
|
+
*
|
|
6761
|
+
* @param {LocalTrack} track to publish
|
|
6762
|
+
* @returns {Promise}
|
|
6763
|
+
*/
|
|
6764
|
+
private async publishTrack(track?: LocalTrack) {
|
|
6765
|
+
if (!track) {
|
|
6766
|
+
return;
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6770
|
+
if (this.isMultistream) {
|
|
6771
|
+
await this.mediaProperties.webrtcMediaConnection.publishTrack(track);
|
|
6772
|
+
} else {
|
|
6773
|
+
track.setPublished(true); // for multistream, this call is done by WCME
|
|
6774
|
+
}
|
|
6775
|
+
}
|
|
6776
|
+
}
|
|
6777
|
+
|
|
6778
|
+
/**
|
|
6779
|
+
* Un-publishes a track.
|
|
6780
|
+
*
|
|
6781
|
+
* @param {LocalTrack} track to unpublish
|
|
6782
|
+
* @returns {Promise}
|
|
6783
|
+
*/
|
|
6784
|
+
private async unpublishTrack(track?: LocalTrack) {
|
|
6785
|
+
if (!track) {
|
|
6786
|
+
return;
|
|
6787
|
+
}
|
|
6788
|
+
|
|
6789
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
6790
|
+
await this.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
|
|
6791
|
+
} else {
|
|
6792
|
+
track.setPublished(false); // for multistream, this call is done by WCME
|
|
6793
|
+
}
|
|
6794
|
+
}
|
|
6795
|
+
|
|
6796
|
+
/**
|
|
6797
|
+
* Publishes specified local tracks in the meeting
|
|
6798
|
+
*
|
|
6799
|
+
* @param {Object} tracks
|
|
6800
|
+
* @returns {Promise}
|
|
6801
|
+
*/
|
|
6802
|
+
async publishTracks(tracks: LocalTracks): Promise<void> {
|
|
6803
|
+
this.checkMediaConnection();
|
|
6804
|
+
|
|
6805
|
+
this.annotationInfo = tracks.annotationInfo;
|
|
6806
|
+
|
|
6807
|
+
if (
|
|
6808
|
+
!tracks.microphone &&
|
|
6809
|
+
!tracks.camera &&
|
|
6810
|
+
!tracks.screenShare?.audio &&
|
|
6811
|
+
!tracks.screenShare?.video
|
|
6812
|
+
) {
|
|
6813
|
+
// nothing to do
|
|
6814
|
+
return;
|
|
6815
|
+
}
|
|
6816
|
+
|
|
6817
|
+
let floorRequestNeeded = false;
|
|
6818
|
+
|
|
6819
|
+
if (tracks.screenShare?.video) {
|
|
6820
|
+
await this.setLocalShareTrack(tracks.screenShare?.video);
|
|
6821
|
+
|
|
6822
|
+
floorRequestNeeded = true;
|
|
6823
|
+
}
|
|
6824
|
+
|
|
6825
|
+
if (tracks.microphone) {
|
|
6826
|
+
await this.setLocalAudioTrack(tracks.microphone);
|
|
6827
|
+
}
|
|
6828
|
+
|
|
6829
|
+
if (tracks.camera) {
|
|
6830
|
+
await this.setLocalVideoTrack(tracks.camera);
|
|
6831
|
+
}
|
|
6832
|
+
|
|
6833
|
+
if (!this.isMultistream) {
|
|
6834
|
+
await this.updateTranscodedMediaConnection();
|
|
6835
|
+
}
|
|
6836
|
+
|
|
6837
|
+
if (floorRequestNeeded) {
|
|
6838
|
+
// we're sending the http request to Locus to request the screen share floor
|
|
6839
|
+
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
6840
|
+
// and also if sharing from the start, we need confluence to have been created
|
|
6841
|
+
await this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE_FLOOR_REQUEST);
|
|
6842
|
+
}
|
|
6843
|
+
}
|
|
6844
|
+
|
|
6845
|
+
/**
|
|
6846
|
+
* Un-publishes specified local tracks in the meeting
|
|
6847
|
+
*
|
|
6848
|
+
* @param {Array<MediaStreamTrack>} tracks
|
|
6849
|
+
* @returns {Promise}
|
|
6850
|
+
*/
|
|
6851
|
+
async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
|
|
6852
|
+
this.checkMediaConnection();
|
|
6853
|
+
|
|
6854
|
+
const promises = [];
|
|
6855
|
+
|
|
6856
|
+
for (const track of tracks.filter((t) => !!t)) {
|
|
6857
|
+
if (track === this.mediaProperties.shareTrack) {
|
|
6858
|
+
try {
|
|
6859
|
+
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
6860
|
+
} catch (e) {
|
|
6861
|
+
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
6862
|
+
}
|
|
6863
|
+
promises.push(this.setLocalShareTrack(undefined));
|
|
6864
|
+
}
|
|
6865
|
+
|
|
6866
|
+
if (track === this.mediaProperties.audioTrack) {
|
|
6867
|
+
promises.push(this.setLocalAudioTrack(undefined));
|
|
6868
|
+
}
|
|
6869
|
+
|
|
6870
|
+
if (track === this.mediaProperties.videoTrack) {
|
|
6871
|
+
promises.push(this.setLocalVideoTrack(undefined));
|
|
6872
|
+
}
|
|
6873
|
+
}
|
|
6874
|
+
|
|
6875
|
+
if (!this.isMultistream) {
|
|
6876
|
+
promises.push(this.updateTranscodedMediaConnection());
|
|
6877
|
+
}
|
|
6878
|
+
|
|
6879
|
+
await Promise.all(promises);
|
|
6880
|
+
}
|
|
6843
6881
|
}
|