@webex/plugin-meetings 3.0.0-beta.15 → 3.0.0-beta.151
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +45 -1
- package/dist/annotation/annotation.types.js +7 -0
- package/dist/annotation/annotation.types.js.map +1 -0
- package/dist/annotation/constants.js +49 -0
- package/dist/annotation/constants.js.map +1 -0
- package/dist/annotation/index.js +359 -0
- package/dist/annotation/index.js.map +1 -0
- package/dist/breakouts/breakout.js +193 -0
- package/dist/breakouts/breakout.js.map +1 -0
- package/dist/breakouts/collection.js +23 -0
- package/dist/breakouts/collection.js.map +1 -0
- package/dist/breakouts/edit-lock-error.js +52 -0
- package/dist/breakouts/edit-lock-error.js.map +1 -0
- package/dist/breakouts/events.js +43 -0
- package/dist/breakouts/events.js.map +1 -0
- package/dist/breakouts/index.js +1046 -0
- package/dist/breakouts/index.js.map +1 -0
- package/dist/breakouts/request.js +78 -0
- package/dist/breakouts/request.js.map +1 -0
- package/dist/breakouts/utils.js +67 -0
- package/dist/breakouts/utils.js.map +1 -0
- package/dist/common/browser-detection.js +1 -21
- package/dist/common/browser-detection.js.map +1 -1
- package/dist/common/collection.js +5 -20
- package/dist/common/collection.js.map +1 -1
- package/dist/common/config.js +0 -7
- package/dist/common/config.js.map +1 -1
- package/dist/common/errors/captcha-error.js +0 -21
- package/dist/common/errors/captcha-error.js.map +1 -1
- package/dist/common/errors/intent-to-join.js +0 -21
- package/dist/common/errors/intent-to-join.js.map +1 -1
- package/dist/common/errors/join-meeting.js +0 -21
- package/dist/common/errors/join-meeting.js.map +1 -1
- package/dist/common/errors/media.js +0 -21
- package/dist/common/errors/media.js.map +1 -1
- package/dist/common/errors/parameter.js +0 -28
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/common/errors/password-error.js +0 -21
- package/dist/common/errors/password-error.js.map +1 -1
- package/dist/common/errors/permission.js +0 -21
- package/dist/common/errors/permission.js.map +1 -1
- package/dist/common/errors/reconnection-in-progress.js +0 -17
- package/dist/common/errors/reconnection-in-progress.js.map +1 -1
- package/dist/common/errors/reconnection.js +0 -21
- package/dist/common/errors/reconnection.js.map +1 -1
- package/dist/common/errors/stats.js +0 -21
- package/dist/common/errors/stats.js.map +1 -1
- package/dist/common/errors/webex-errors.js +9 -43
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/errors/webex-meetings-error.js +1 -24
- package/dist/common/errors/webex-meetings-error.js.map +1 -1
- package/dist/common/events/events-scope.js +0 -22
- package/dist/common/events/events-scope.js.map +1 -1
- package/dist/common/events/events.js +0 -23
- package/dist/common/events/events.js.map +1 -1
- package/dist/common/events/trigger-proxy.js +0 -12
- package/dist/common/events/trigger-proxy.js.map +1 -1
- package/dist/common/events/util.js +0 -15
- package/dist/common/events/util.js.map +1 -1
- package/dist/common/logs/logger-config.js +0 -4
- package/dist/common/logs/logger-config.js.map +1 -1
- package/dist/common/logs/logger-proxy.js +1 -8
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/common/logs/request.js +35 -61
- package/dist/common/logs/request.js.map +1 -1
- package/dist/common/queue.js +4 -14
- package/dist/common/queue.js.map +1 -1
- package/dist/config.js +7 -13
- package/dist/config.js.map +1 -1
- package/dist/constants.js +208 -64
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +14 -0
- package/dist/controls-options-manager/constants.js.map +1 -0
- package/dist/controls-options-manager/enums.js +27 -0
- package/dist/controls-options-manager/enums.js.map +1 -0
- package/dist/controls-options-manager/index.js +297 -0
- package/dist/controls-options-manager/index.js.map +1 -0
- package/dist/controls-options-manager/types.js +7 -0
- package/dist/controls-options-manager/types.js.map +1 -0
- package/dist/controls-options-manager/util.js +300 -0
- package/dist/controls-options-manager/util.js.map +1 -0
- package/dist/index.js +78 -17
- package/dist/index.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +100 -29
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/embeddedAppsUtils.js +3 -26
- package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js +0 -15
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/hostUtils.js +4 -12
- package/dist/locus-info/hostUtils.js.map +1 -1
- package/dist/locus-info/index.js +387 -208
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +0 -38
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +54 -38
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/locus-info/parser.js +90 -126
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js +93 -92
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +70 -219
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +74 -198
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +1 -8
- package/dist/media/util.js.map +1 -1
- package/dist/mediaQualityMetrics/config.js +505 -495
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/meeting/in-meeting-actions.js +79 -14
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/meeting/index.js +2685 -3324
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +291 -0
- package/dist/meeting/locusMediaRequest.js.map +1 -0
- package/dist/meeting/muteState.js +243 -185
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/meeting/request.js +296 -342
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/request.type.js +0 -1
- package/dist/meeting/request.type.js.map +1 -1
- package/dist/meeting/state.js +16 -26
- package/dist/meeting/state.js.map +1 -1
- package/dist/meeting/util.js +446 -585
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting-info/collection.js +3 -25
- package/dist/meeting-info/collection.js.map +1 -1
- package/dist/meeting-info/index.js +8 -31
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/meeting-info/meeting-info-v2.js +261 -242
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.js +1 -16
- package/dist/meeting-info/request.js.map +1 -1
- package/dist/meeting-info/util.js +98 -183
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +156 -232
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/meetings/collection.js +24 -20
- package/dist/meetings/collection.js.map +1 -1
- package/dist/meetings/index.js +526 -372
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/meetings.types.js +7 -0
- package/dist/meetings/meetings.types.js.map +1 -0
- package/dist/meetings/request.js +21 -40
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +172 -141
- package/dist/meetings/util.js.map +1 -1
- package/dist/member/index.js +58 -57
- package/dist/member/index.js.map +1 -1
- package/dist/member/types.js +15 -0
- package/dist/member/types.js.map +1 -0
- package/dist/member/util.js +101 -69
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +12 -12
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +123 -162
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +120 -85
- package/dist/members/request.js.map +1 -1
- package/dist/members/types.js +15 -0
- package/dist/members/types.js.map +1 -0
- package/dist/members/util.js +314 -260
- package/dist/members/util.js.map +1 -1
- package/dist/metrics/config.js +50 -16
- package/dist/metrics/config.js.map +1 -1
- package/dist/metrics/constants.js +4 -7
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +75 -147
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +170 -50
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +58 -65
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +73 -94
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +55 -74
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +66 -43
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +502 -442
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.js +24 -51
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +3 -38
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/personal-meeting-room/request.js +2 -33
- package/dist/personal-meeting-room/request.js.map +1 -1
- package/dist/personal-meeting-room/util.js +0 -13
- package/dist/personal-meeting-room/util.js.map +1 -1
- package/dist/reachability/index.js +190 -199
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +14 -23
- package/dist/reachability/request.js.map +1 -1
- package/dist/reactions/constants.js +13 -0
- package/dist/reactions/constants.js.map +1 -0
- package/dist/reactions/reactions.js +2 -4
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js +18 -24
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/reconnection-manager/index.js +356 -476
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/enums.js +17 -0
- package/dist/recording-controller/enums.js.map +1 -0
- package/dist/recording-controller/index.js +343 -0
- package/dist/recording-controller/index.js.map +1 -0
- package/dist/recording-controller/util.js +63 -0
- package/dist/recording-controller/util.js.map +1 -0
- package/dist/roap/index.js +32 -75
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +129 -136
- package/dist/roap/request.js.map +1 -1
- package/dist/roap/turnDiscovery.js +143 -103
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/statsAnalyzer/global.js +1 -95
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/statsAnalyzer/index.js +369 -462
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.js +144 -94
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/transcription/index.js +9 -44
- package/dist/transcription/index.js.map +1 -1
- package/dist/types/annotation/annotation.types.d.ts +43 -0
- package/dist/types/annotation/constants.d.ts +31 -0
- package/dist/types/annotation/index.d.ts +124 -0
- package/dist/types/breakouts/breakout.d.ts +8 -0
- package/dist/types/breakouts/collection.d.ts +5 -0
- package/dist/types/breakouts/edit-lock-error.d.ts +15 -0
- package/dist/types/breakouts/events.d.ts +2 -0
- package/dist/types/breakouts/index.d.ts +5 -0
- package/dist/types/breakouts/request.d.ts +22 -0
- package/dist/types/breakouts/utils.d.ts +15 -0
- package/dist/types/common/browser-detection.d.ts +9 -0
- package/dist/types/common/collection.d.ts +48 -0
- package/dist/types/common/config.d.ts +2 -0
- package/dist/types/common/errors/captcha-error.d.ts +15 -0
- package/dist/types/common/errors/intent-to-join.d.ts +16 -0
- package/dist/types/common/errors/join-meeting.d.ts +17 -0
- package/dist/types/common/errors/media.d.ts +15 -0
- package/dist/types/common/errors/parameter.d.ts +15 -0
- package/dist/types/common/errors/password-error.d.ts +15 -0
- package/dist/types/common/errors/permission.d.ts +14 -0
- package/dist/types/common/errors/reconnection-in-progress.d.ts +9 -0
- package/dist/types/common/errors/reconnection.d.ts +15 -0
- package/dist/types/common/errors/stats.d.ts +15 -0
- package/dist/types/common/errors/webex-errors.d.ts +69 -0
- package/dist/types/common/errors/webex-meetings-error.d.ts +20 -0
- package/dist/types/common/events/events-scope.d.ts +17 -0
- package/dist/types/common/events/events.d.ts +12 -0
- package/dist/types/common/events/trigger-proxy.d.ts +2 -0
- package/dist/types/common/events/util.d.ts +2 -0
- package/dist/types/common/logs/logger-config.d.ts +2 -0
- package/dist/types/common/logs/logger-proxy.d.ts +2 -0
- package/dist/types/common/logs/request.d.ts +34 -0
- package/dist/types/common/queue.d.ts +32 -0
- package/dist/types/config.d.ts +72 -0
- package/dist/types/constants.d.ts +978 -0
- package/dist/types/controls-options-manager/constants.d.ts +4 -0
- package/dist/types/controls-options-manager/enums.d.ts +15 -0
- package/dist/types/controls-options-manager/index.d.ts +136 -0
- package/dist/types/controls-options-manager/types.d.ts +43 -0
- package/dist/types/controls-options-manager/util.d.ts +1 -0
- package/dist/types/index.d.ts +7 -0
- package/dist/types/locus-info/controlsUtils.d.ts +2 -0
- package/dist/types/locus-info/embeddedAppsUtils.d.ts +2 -0
- package/dist/types/locus-info/fullState.d.ts +2 -0
- package/dist/types/locus-info/hostUtils.d.ts +2 -0
- package/dist/types/locus-info/index.d.ts +315 -0
- package/dist/types/locus-info/infoUtils.d.ts +2 -0
- package/dist/types/locus-info/mediaSharesUtils.d.ts +2 -0
- package/dist/types/locus-info/parser.d.ts +212 -0
- package/dist/types/locus-info/selfUtils.d.ts +2 -0
- package/dist/types/media/index.d.ts +34 -0
- package/dist/types/media/properties.d.ts +86 -0
- package/dist/types/media/util.d.ts +2 -0
- package/dist/types/mediaQualityMetrics/config.d.ts +365 -0
- package/dist/types/meeting/in-meeting-actions.d.ts +149 -0
- package/dist/types/meeting/index.d.ts +1509 -0
- package/dist/types/meeting/locusMediaRequest.d.ts +70 -0
- package/dist/types/meeting/muteState.d.ts +184 -0
- package/dist/types/meeting/request.d.ts +270 -0
- package/dist/types/meeting/request.type.d.ts +11 -0
- package/dist/types/meeting/state.d.ts +9 -0
- package/dist/types/meeting/util.d.ts +75 -0
- package/dist/types/meeting-info/collection.d.ts +20 -0
- package/dist/types/meeting-info/index.d.ts +57 -0
- package/dist/types/meeting-info/meeting-info-v2.d.ts +122 -0
- package/dist/types/meeting-info/request.d.ts +22 -0
- package/dist/types/meeting-info/util.d.ts +2 -0
- package/dist/types/meeting-info/utilv2.d.ts +2 -0
- package/dist/types/meetings/collection.d.ts +31 -0
- package/dist/types/meetings/index.d.ts +364 -0
- package/dist/types/meetings/meetings.types.d.ts +4 -0
- package/dist/types/meetings/request.d.ts +27 -0
- package/dist/types/meetings/util.d.ts +18 -0
- package/dist/types/member/index.d.ts +157 -0
- package/dist/types/member/types.d.ts +21 -0
- package/dist/types/member/util.d.ts +2 -0
- package/dist/types/members/collection.d.ts +29 -0
- package/dist/types/members/index.d.ts +353 -0
- package/dist/types/members/request.d.ts +114 -0
- package/dist/types/members/types.d.ts +24 -0
- package/dist/types/members/util.d.ts +210 -0
- package/dist/types/metrics/config.d.ts +195 -0
- package/dist/types/metrics/constants.d.ts +55 -0
- package/dist/types/metrics/index.d.ts +169 -0
- package/dist/types/multistream/mediaRequestManager.d.ts +104 -0
- package/dist/types/multistream/receiveSlot.d.ts +68 -0
- package/dist/types/multistream/receiveSlotManager.d.ts +56 -0
- package/dist/types/multistream/remoteMedia.d.ts +72 -0
- package/dist/types/multistream/remoteMediaGroup.d.ts +47 -0
- package/dist/types/multistream/remoteMediaManager.d.ts +277 -0
- package/dist/types/networkQualityMonitor/index.d.ts +70 -0
- package/dist/types/personal-meeting-room/index.d.ts +47 -0
- package/dist/types/personal-meeting-room/request.d.ts +14 -0
- package/dist/types/personal-meeting-room/util.d.ts +2 -0
- package/dist/types/reachability/index.d.ts +152 -0
- package/dist/types/reachability/request.d.ts +37 -0
- package/dist/types/reactions/constants.d.ts +3 -0
- package/dist/types/reactions/reactions.d.ts +4 -0
- package/dist/types/reactions/reactions.type.d.ts +52 -0
- package/dist/types/reconnection-manager/index.d.ts +126 -0
- package/dist/types/recording-controller/enums.d.ts +7 -0
- package/dist/types/recording-controller/index.d.ts +193 -0
- package/dist/types/recording-controller/util.d.ts +13 -0
- package/dist/types/roap/index.d.ts +77 -0
- package/dist/types/roap/request.d.ts +36 -0
- package/dist/types/roap/turnDiscovery.d.ts +91 -0
- package/dist/types/statsAnalyzer/global.d.ts +36 -0
- package/dist/types/statsAnalyzer/index.d.ts +200 -0
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +24 -0
- package/dist/types/transcription/index.d.ts +64 -0
- package/package.json +28 -21
- package/src/annotation/annotation.types.ts +52 -0
- package/src/annotation/constants.ts +36 -0
- package/src/annotation/index.ts +343 -0
- package/src/breakouts/README.md +220 -0
- package/src/breakouts/breakout.ts +163 -0
- package/src/breakouts/collection.ts +19 -0
- package/src/breakouts/edit-lock-error.ts +25 -0
- package/src/breakouts/events.ts +37 -0
- package/src/breakouts/index.ts +921 -0
- package/src/breakouts/request.ts +55 -0
- package/src/breakouts/utils.ts +57 -0
- package/src/common/errors/webex-errors.ts +6 -2
- package/src/common/logs/logger-proxy.ts +1 -1
- package/src/config.ts +5 -7
- package/src/constants.ts +155 -20
- package/src/controls-options-manager/constants.ts +5 -0
- package/src/controls-options-manager/enums.ts +18 -0
- package/src/controls-options-manager/index.ts +278 -0
- package/src/controls-options-manager/types.ts +59 -0
- package/src/controls-options-manager/util.ts +286 -0
- package/src/index.ts +34 -0
- package/src/locus-info/controlsUtils.ts +108 -0
- package/src/locus-info/index.ts +310 -21
- package/src/locus-info/mediaSharesUtils.ts +48 -0
- package/src/locus-info/parser.ts +2 -1
- package/src/locus-info/selfUtils.ts +71 -1
- package/src/media/index.ts +70 -142
- package/src/media/properties.ts +41 -104
- package/src/mediaQualityMetrics/config.ts +379 -377
- package/src/meeting/in-meeting-actions.ts +156 -0
- package/src/meeting/index.ts +1730 -1768
- package/src/meeting/locusMediaRequest.ts +309 -0
- package/src/meeting/muteState.ts +228 -132
- package/src/meeting/request.ts +100 -91
- package/src/meeting/request.type.ts +2 -0
- package/src/meeting/util.ts +421 -421
- package/src/meeting-info/meeting-info-v2.ts +134 -13
- package/src/meeting-info/utilv2.ts +13 -3
- package/src/meetings/collection.ts +20 -0
- package/src/meetings/index.ts +375 -83
- package/src/meetings/meetings.types.ts +9 -0
- package/src/meetings/request.ts +3 -1
- package/src/meetings/util.ts +103 -4
- package/src/member/index.ts +40 -0
- package/src/member/types.ts +24 -0
- package/src/member/util.ts +81 -1
- package/src/members/collection.ts +8 -0
- package/src/members/index.ts +108 -6
- package/src/members/request.ts +98 -17
- package/src/members/types.ts +28 -0
- package/src/members/util.ts +319 -240
- package/src/metrics/config.ts +49 -10
- package/src/metrics/constants.ts +2 -4
- package/src/metrics/index.ts +43 -27
- package/src/multistream/mediaRequestManager.ts +210 -45
- package/src/multistream/receiveSlot.ts +68 -26
- package/src/multistream/receiveSlotManager.ts +61 -38
- package/src/multistream/remoteMedia.ts +29 -3
- package/src/multistream/remoteMediaGroup.ts +61 -2
- package/src/multistream/remoteMediaManager.ts +260 -66
- package/src/networkQualityMonitor/index.ts +6 -6
- package/src/reachability/index.ts +75 -25
- package/src/reachability/request.ts +10 -5
- package/src/reactions/constants.ts +4 -0
- package/src/reactions/reactions.ts +4 -4
- package/src/reactions/reactions.type.ts +28 -3
- package/src/reconnection-manager/index.ts +53 -32
- package/src/recording-controller/enums.ts +8 -0
- package/src/recording-controller/index.ts +315 -0
- package/src/recording-controller/util.ts +58 -0
- package/src/roap/index.ts +21 -30
- package/src/roap/request.ts +51 -52
- package/src/roap/turnDiscovery.ts +51 -27
- package/src/statsAnalyzer/global.ts +1 -94
- package/src/statsAnalyzer/index.ts +380 -390
- package/src/statsAnalyzer/mqaUtil.ts +106 -99
- package/test/integration/spec/converged-space-meetings.js +233 -0
- package/test/integration/spec/journey.js +332 -255
- package/test/integration/spec/space-meeting.js +78 -5
- package/test/integration/spec/transcription.js +1 -1
- package/test/unit/spec/annotation/index.ts +436 -0
- package/test/unit/spec/breakouts/breakout.ts +203 -0
- package/test/unit/spec/breakouts/collection.ts +15 -0
- package/test/unit/spec/breakouts/edit-lock-error.ts +30 -0
- package/test/unit/spec/breakouts/events.ts +77 -0
- package/test/unit/spec/breakouts/index.ts +1790 -0
- package/test/unit/spec/breakouts/request.ts +104 -0
- package/test/unit/spec/breakouts/utils.js +72 -0
- package/test/unit/spec/controls-options-manager/index.js +287 -0
- package/test/unit/spec/controls-options-manager/util.js +518 -0
- package/test/unit/spec/fixture/locus.js +1 -0
- package/test/unit/spec/locus-info/controlsUtils.js +303 -30
- package/test/unit/spec/locus-info/index.js +615 -4
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +22 -0
- package/test/unit/spec/locus-info/selfConstant.js +38 -0
- package/test/unit/spec/locus-info/selfUtils.js +200 -0
- package/test/unit/spec/media/index.ts +118 -22
- package/test/unit/spec/media/properties.ts +9 -9
- package/test/unit/spec/meeting/in-meeting-actions.ts +76 -0
- package/test/unit/spec/meeting/index.js +2394 -1381
- package/test/unit/spec/meeting/locusMediaRequest.ts +436 -0
- package/test/unit/spec/meeting/muteState.js +370 -208
- package/test/unit/spec/meeting/request.js +354 -42
- package/test/unit/spec/meeting/utils.js +268 -156
- package/test/unit/spec/meeting-info/meetinginfov2.js +383 -5
- package/test/unit/spec/meeting-info/utilv2.js +21 -0
- package/test/unit/spec/meetings/collection.js +14 -0
- package/test/unit/spec/meetings/index.js +842 -128
- package/test/unit/spec/meetings/utils.js +206 -2
- package/test/unit/spec/member/index.js +24 -0
- package/test/unit/spec/member/util.js +384 -32
- package/test/unit/spec/members/index.js +320 -1
- package/test/unit/spec/members/request.js +206 -27
- package/test/unit/spec/members/utils.js +184 -0
- package/test/unit/spec/metrics/index.js +98 -0
- package/test/unit/spec/multistream/mediaRequestManager.ts +676 -105
- package/test/unit/spec/multistream/receiveSlot.ts +77 -18
- package/test/unit/spec/multistream/receiveSlotManager.ts +69 -39
- package/test/unit/spec/multistream/remoteMedia.ts +32 -2
- package/test/unit/spec/multistream/remoteMediaGroup.ts +271 -5
- package/test/unit/spec/multistream/remoteMediaManager.ts +672 -65
- package/test/unit/spec/networkQualityMonitor/index.js +4 -4
- package/test/unit/spec/reachability/index.ts +176 -25
- package/test/unit/spec/reachability/request.js +66 -0
- package/test/unit/spec/reconnection-manager/index.js +46 -13
- package/test/unit/spec/recording-controller/index.js +231 -0
- package/test/unit/spec/recording-controller/util.js +102 -0
- package/test/unit/spec/roap/index.ts +21 -51
- package/test/unit/spec/roap/request.ts +187 -0
- package/test/unit/spec/roap/turnDiscovery.ts +73 -34
- package/test/unit/spec/stats-analyzer/index.js +94 -43
- package/test/utils/constants.js +9 -0
- package/test/utils/integrationTestUtils.js +46 -0
- package/test/utils/testUtils.js +0 -45
- package/test/utils/webex-config.js +4 -0
- package/test/utils/webex-test-users.js +7 -3
- package/tsconfig.json +6 -0
- package/dist/media/internal-media-core-wrapper.js +0 -22
- package/dist/media/internal-media-core-wrapper.js.map +0 -1
- package/dist/meeting/effectsState.js +0 -334
- package/dist/meeting/effectsState.js.map +0 -1
- package/dist/multistream/multistreamMedia.js +0 -117
- package/dist/multistream/multistreamMedia.js.map +0 -1
- package/src/index.js +0 -15
- package/src/media/internal-media-core-wrapper.ts +0 -9
- package/src/meeting/effectsState.ts +0 -211
- package/src/multistream/multistreamMedia.ts +0 -93
- package/test/unit/spec/meeting/effectsState.js +0 -281
package/src/meeting/index.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
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
|
+
MediaType,
|
|
11
|
+
RemoteTrackType,
|
|
12
|
+
} from '@webex/internal-media-core';
|
|
13
|
+
|
|
14
|
+
import {
|
|
15
|
+
getDevices,
|
|
16
|
+
LocalTrack,
|
|
17
|
+
LocalCameraTrack,
|
|
18
|
+
LocalDisplayTrack,
|
|
19
|
+
LocalMicrophoneTrack,
|
|
20
|
+
LocalTrackEvents,
|
|
21
|
+
TrackMuteEvent,
|
|
22
|
+
} from '@webex/media-helpers';
|
|
6
23
|
|
|
7
24
|
import {
|
|
8
25
|
MeetingNotActiveError,
|
|
9
|
-
createMeetingsError,
|
|
10
26
|
UserInLobbyError,
|
|
11
27
|
NoMediaEstablishedYetError,
|
|
12
28
|
UserNotJoinedError,
|
|
@@ -16,20 +32,22 @@ import NetworkQualityMonitor from '../networkQualityMonitor';
|
|
|
16
32
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
17
33
|
import Trigger from '../common/events/trigger-proxy';
|
|
18
34
|
import Roap from '../roap/index';
|
|
19
|
-
import Media from '../media';
|
|
35
|
+
import Media, {type BundlePolicy} from '../media';
|
|
20
36
|
import MediaProperties from '../media/properties';
|
|
21
37
|
import MeetingStateMachine from './state';
|
|
22
|
-
import createMuteState from './muteState';
|
|
23
|
-
import createEffectsState from './effectsState';
|
|
38
|
+
import {createMuteState} from './muteState';
|
|
24
39
|
import LocusInfo from '../locus-info';
|
|
25
40
|
import Metrics from '../metrics';
|
|
26
|
-
import {trigger,
|
|
41
|
+
import {trigger, error as MetricsError, eventType} from '../metrics/config';
|
|
27
42
|
import ReconnectionManager from '../reconnection-manager';
|
|
28
43
|
import MeetingRequest from './request';
|
|
29
44
|
import Members from '../members/index';
|
|
30
45
|
import MeetingUtil from './util';
|
|
46
|
+
import RecordingUtil from '../recording-controller/util';
|
|
47
|
+
import ControlsOptionsUtil from '../controls-options-manager/util';
|
|
31
48
|
import MediaUtil from '../media/util';
|
|
32
49
|
import Transcription from '../transcription';
|
|
50
|
+
import {Reactions, SkinTones} from '../reactions/reactions';
|
|
33
51
|
import PasswordError from '../common/errors/password-error';
|
|
34
52
|
import CaptchaError from '../common/errors/captcha-error';
|
|
35
53
|
import ReconnectionError from '../common/errors/reconnection';
|
|
@@ -40,10 +58,12 @@ import {
|
|
|
40
58
|
_JOIN_,
|
|
41
59
|
AUDIO,
|
|
42
60
|
CONTENT,
|
|
61
|
+
DISPLAY_HINTS,
|
|
43
62
|
ENDED,
|
|
44
63
|
EVENT_TRIGGERS,
|
|
45
64
|
EVENT_TYPES,
|
|
46
65
|
EVENTS,
|
|
66
|
+
BREAKOUTS,
|
|
47
67
|
FLOOR_ACTION,
|
|
48
68
|
FULL_STATE,
|
|
49
69
|
LAYOUT_TYPES,
|
|
@@ -64,53 +84,83 @@ import {
|
|
|
64
84
|
RECORDING_STATE,
|
|
65
85
|
SHARE_STATUS,
|
|
66
86
|
SHARE_STOPPED_REASON,
|
|
67
|
-
VIDEO_RESOLUTIONS,
|
|
68
87
|
VIDEO,
|
|
69
|
-
BNR_STATUS,
|
|
70
88
|
HTTP_VERBS,
|
|
89
|
+
SELF_ROLES,
|
|
71
90
|
} from '../constants';
|
|
72
91
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
73
92
|
import ParameterError from '../common/errors/parameter';
|
|
74
|
-
import MediaError from '../common/errors/media';
|
|
75
93
|
import {
|
|
76
94
|
MeetingInfoV2PasswordError,
|
|
77
95
|
MeetingInfoV2CaptchaError,
|
|
96
|
+
MeetingInfoV2PolicyError,
|
|
78
97
|
} from '../meeting-info/meeting-info-v2';
|
|
79
98
|
import BrowserDetection from '../common/browser-detection';
|
|
80
|
-
import {ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
99
|
+
import {CSI, ReceiveSlotManager} from '../multistream/receiveSlotManager';
|
|
81
100
|
import {MediaRequestManager} from '../multistream/mediaRequestManager';
|
|
82
101
|
import {
|
|
102
|
+
Configuration as RemoteMediaManagerConfiguration,
|
|
83
103
|
RemoteMediaManager,
|
|
84
104
|
Event as RemoteMediaManagerEvent,
|
|
85
105
|
} from '../multistream/remoteMediaManager';
|
|
86
|
-
import {
|
|
87
|
-
|
|
88
|
-
|
|
106
|
+
import {
|
|
107
|
+
Reaction,
|
|
108
|
+
ReactionServerType,
|
|
109
|
+
SkinToneType,
|
|
110
|
+
ProcessedReaction,
|
|
111
|
+
RelayEvent,
|
|
112
|
+
} from '../reactions/reactions.type';
|
|
113
|
+
import Breakouts from '../breakouts';
|
|
114
|
+
import Annotation from '../annotation';
|
|
89
115
|
|
|
90
116
|
import InMeetingActions from './in-meeting-actions';
|
|
117
|
+
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
118
|
+
import RecordingController from '../recording-controller';
|
|
119
|
+
import ControlsOptionsManager from '../controls-options-manager';
|
|
120
|
+
import PermissionError from '../common/errors/permission';
|
|
121
|
+
import {LocusMediaRequest} from './locusMediaRequest';
|
|
122
|
+
import {AnnotationInfo} from '../annotation/annotation.types';
|
|
91
123
|
|
|
92
124
|
const {isBrowser} = BrowserDetection();
|
|
93
125
|
|
|
94
|
-
const logRequest = (request: any, {
|
|
95
|
-
LoggerProxy.logger.info(
|
|
126
|
+
const logRequest = (request: any, {logText = ''}) => {
|
|
127
|
+
LoggerProxy.logger.info(`${logText} - sending request`);
|
|
96
128
|
|
|
97
129
|
return request
|
|
98
130
|
.then((arg) => {
|
|
99
|
-
LoggerProxy.logger.info(
|
|
131
|
+
LoggerProxy.logger.info(`${logText} - has been successfully sent`);
|
|
100
132
|
|
|
101
133
|
return arg;
|
|
102
134
|
})
|
|
103
135
|
.catch((error) => {
|
|
104
|
-
LoggerProxy.logger.error(
|
|
136
|
+
LoggerProxy.logger.error(`${logText} - has failed: `, error);
|
|
105
137
|
throw error;
|
|
106
138
|
});
|
|
107
139
|
};
|
|
108
140
|
|
|
141
|
+
export type LocalTracks = {
|
|
142
|
+
microphone?: LocalMicrophoneTrack;
|
|
143
|
+
camera?: LocalCameraTrack;
|
|
144
|
+
screenShare?: {
|
|
145
|
+
audio?: LocalTrack; // todo: for now screen share audio is not supported (will be done in SPARK-399690)
|
|
146
|
+
video?: LocalDisplayTrack;
|
|
147
|
+
};
|
|
148
|
+
annotationInfo?: AnnotationInfo;
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
export type AddMediaOptions = {
|
|
152
|
+
localTracks?: LocalTracks;
|
|
153
|
+
audioEnabled?: boolean; // if not specified, default value true is used
|
|
154
|
+
videoEnabled?: boolean; // if not specified, default value true is used
|
|
155
|
+
receiveShare?: boolean; // if not specified, default value true is used
|
|
156
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration; // applies only to multistream meetings
|
|
157
|
+
bundlePolicy?: BundlePolicy; // applies only to multistream meetings
|
|
158
|
+
};
|
|
159
|
+
|
|
109
160
|
export const MEDIA_UPDATE_TYPE = {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
SHARE: 'SHARE',
|
|
161
|
+
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
162
|
+
LAMBDA: 'LAMBDA',
|
|
163
|
+
UPDATE_MEDIA: 'UPDATE_MEDIA',
|
|
114
164
|
};
|
|
115
165
|
|
|
116
166
|
/**
|
|
@@ -125,16 +175,6 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
125
175
|
* @property {boolean} isSharing
|
|
126
176
|
*/
|
|
127
177
|
|
|
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
178
|
/**
|
|
139
179
|
* SharePreferences
|
|
140
180
|
* @typedef {Object} SharePreferences
|
|
@@ -149,18 +189,10 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
149
189
|
* @property {String} [pin]
|
|
150
190
|
* @property {Boolean} [moderator]
|
|
151
191
|
* @property {String|Object} [meetingQuality]
|
|
152
|
-
* @property {String} [meetingQuality.local]
|
|
153
192
|
* @property {String} [meetingQuality.remote]
|
|
154
193
|
* @property {Boolean} [rejoin]
|
|
155
194
|
* @property {Boolean} [enableMultistream]
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* SendOptions
|
|
160
|
-
* @typedef {Object} SendOptions
|
|
161
|
-
* @property {Boolean} sendAudio
|
|
162
|
-
* @property {Boolean} sendVideo
|
|
163
|
-
* @property {Boolean} sendShare
|
|
195
|
+
* @property {String} [correlationId]
|
|
164
196
|
*/
|
|
165
197
|
|
|
166
198
|
/**
|
|
@@ -403,12 +435,13 @@ export const MEDIA_UPDATE_TYPE = {
|
|
|
403
435
|
export default class Meeting extends StatelessWebexPlugin {
|
|
404
436
|
attrs: any;
|
|
405
437
|
audio: any;
|
|
438
|
+
breakouts: any;
|
|
439
|
+
annotation: any;
|
|
406
440
|
conversationUrl: string;
|
|
407
441
|
correlationId: string;
|
|
408
442
|
destination: string;
|
|
409
443
|
destinationType: string;
|
|
410
444
|
deviceUrl: string;
|
|
411
|
-
effects: any;
|
|
412
445
|
hostId: string;
|
|
413
446
|
id: string;
|
|
414
447
|
isMultistream: boolean;
|
|
@@ -416,7 +449,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
416
449
|
mediaConnections: any[];
|
|
417
450
|
mediaId?: string;
|
|
418
451
|
meetingFiniteStateMachine: any;
|
|
419
|
-
meetingInfo:
|
|
452
|
+
meetingInfo: any;
|
|
420
453
|
meetingRequest: MeetingRequest;
|
|
421
454
|
members: Members;
|
|
422
455
|
options: object;
|
|
@@ -428,6 +461,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
428
461
|
resource: string;
|
|
429
462
|
roap: Roap;
|
|
430
463
|
roapSeq: number;
|
|
464
|
+
selfUrl?: string; // comes from Locus, initialized by updateMeetingObject()
|
|
431
465
|
sipUri: string;
|
|
432
466
|
type: string;
|
|
433
467
|
userId: string;
|
|
@@ -449,32 +483,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
449
483
|
keepAliveTimerId: NodeJS.Timeout;
|
|
450
484
|
lastVideoLayoutInfo: any;
|
|
451
485
|
locusInfo: any;
|
|
452
|
-
|
|
486
|
+
locusMediaRequest?: LocusMediaRequest;
|
|
453
487
|
mediaProperties: MediaProperties;
|
|
454
488
|
mediaRequestManagers: {
|
|
455
489
|
audio: MediaRequestManager;
|
|
456
490
|
video: MediaRequestManager;
|
|
491
|
+
screenShareAudio: MediaRequestManager;
|
|
492
|
+
screenShareVideo: MediaRequestManager;
|
|
457
493
|
};
|
|
458
494
|
|
|
459
495
|
meetingInfoFailureReason: string;
|
|
496
|
+
meetingInfoFailureCode?: number;
|
|
460
497
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
461
498
|
networkStatus: string;
|
|
462
499
|
passwordStatus: string;
|
|
463
500
|
queuedMediaUpdates: any[];
|
|
464
501
|
recording: any;
|
|
465
502
|
remoteMediaManager: RemoteMediaManager | null;
|
|
503
|
+
recordingController: RecordingController;
|
|
504
|
+
controlsOptionsManager: ControlsOptionsManager;
|
|
466
505
|
requiredCaptcha: any;
|
|
467
506
|
receiveSlotManager: ReceiveSlotManager;
|
|
468
507
|
shareStatus: string;
|
|
469
508
|
statsAnalyzer: StatsAnalyzer;
|
|
470
509
|
transcription: Transcription;
|
|
471
510
|
updateMediaConnections: (mediaConnections: any[]) => void;
|
|
472
|
-
|
|
511
|
+
endCallInitJoinReq: any;
|
|
473
512
|
endJoinReqResp: any;
|
|
474
513
|
endLocalSDPGenRemoteSDPRecvDelay: any;
|
|
475
514
|
joinedWith: any;
|
|
476
515
|
locusId: any;
|
|
477
|
-
|
|
516
|
+
startCallInitJoinReq: any;
|
|
478
517
|
startJoinReqResp: any;
|
|
479
518
|
startLocalSDPGenRemoteSDPRecvDelay: any;
|
|
480
519
|
wirelessShare: any;
|
|
@@ -487,8 +526,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
487
526
|
resourceUrl: string;
|
|
488
527
|
selfId: string;
|
|
489
528
|
state: any;
|
|
490
|
-
|
|
529
|
+
localAudioTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
530
|
+
localVideoTrackMuteStateHandler: (event: TrackMuteEvent) => void;
|
|
531
|
+
underlyingLocalTrackChangeHandler: () => void;
|
|
532
|
+
roles: any[];
|
|
533
|
+
environment: string;
|
|
491
534
|
namespace = MEETINGS;
|
|
535
|
+
annotationInfo: AnnotationInfo;
|
|
492
536
|
|
|
493
537
|
/**
|
|
494
538
|
* @param {Object} attrs
|
|
@@ -523,7 +567,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
523
567
|
*/
|
|
524
568
|
this.id = uuid.v4();
|
|
525
569
|
/**
|
|
526
|
-
* Correlation ID used for network tracking of meeting
|
|
570
|
+
* Correlation ID used for network tracking of meeting
|
|
527
571
|
* @instance
|
|
528
572
|
* @type {String}
|
|
529
573
|
* @readonly
|
|
@@ -573,41 +617,120 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
573
617
|
*/
|
|
574
618
|
// TODO: needs to be defined as a class
|
|
575
619
|
this.meetingInfo = {};
|
|
620
|
+
/**
|
|
621
|
+
* @instance
|
|
622
|
+
* @type {Breakouts}
|
|
623
|
+
* @public
|
|
624
|
+
* @memberof Meeting
|
|
625
|
+
*/
|
|
626
|
+
// @ts-ignore
|
|
627
|
+
this.breakouts = new Breakouts({meetingId: this.id}, {parent: this.webex});
|
|
628
|
+
/**
|
|
629
|
+
* @instance
|
|
630
|
+
* @type {Annotation}
|
|
631
|
+
* @public
|
|
632
|
+
* @memberof Meeting
|
|
633
|
+
*/
|
|
634
|
+
// @ts-ignore
|
|
635
|
+
this.annotation = new Annotation({parent: this.webex});
|
|
576
636
|
/**
|
|
577
637
|
* helper class for managing receive slots (for multistream media connections)
|
|
578
638
|
*/
|
|
579
|
-
this.receiveSlotManager = new ReceiveSlotManager(
|
|
639
|
+
this.receiveSlotManager = new ReceiveSlotManager(
|
|
640
|
+
(mediaType: MediaType) => {
|
|
641
|
+
if (!this.mediaProperties?.webrtcMediaConnection) {
|
|
642
|
+
return Promise.reject(new Error('Webrtc media connection is missing'));
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
return this.mediaProperties.webrtcMediaConnection.createReceiveSlot(mediaType);
|
|
646
|
+
},
|
|
647
|
+
(csi: CSI) => (this.members.findMemberByCsi(csi) as any)?.id
|
|
648
|
+
);
|
|
580
649
|
/**
|
|
581
|
-
*
|
|
582
|
-
* All media requests sent out for
|
|
650
|
+
* Object containing helper classes for managing media requests for audio/video/screenshare (for multistream media connections)
|
|
651
|
+
* All multistream media requests sent out for this meeting have to go through them.
|
|
583
652
|
*/
|
|
584
653
|
this.mediaRequestManagers = {
|
|
585
|
-
audio: new MediaRequestManager(
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
654
|
+
audio: new MediaRequestManager(
|
|
655
|
+
(mediaRequests) => {
|
|
656
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
657
|
+
LoggerProxy.logger.warn(
|
|
658
|
+
'Meeting:index#mediaRequestManager --> trying to send audio media request before media connection was created'
|
|
659
|
+
);
|
|
660
|
+
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
664
|
+
MediaType.AudioMain,
|
|
665
|
+
mediaRequests
|
|
589
666
|
);
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
// @ts-ignore - config coming from registerPlugin
|
|
670
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
671
|
+
kind: 'audio',
|
|
672
|
+
}
|
|
673
|
+
),
|
|
674
|
+
video: new MediaRequestManager(
|
|
675
|
+
(mediaRequests) => {
|
|
676
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
677
|
+
LoggerProxy.logger.warn(
|
|
678
|
+
'Meeting:index#mediaRequestManager --> trying to send video media request before media connection was created'
|
|
679
|
+
);
|
|
590
680
|
|
|
591
|
-
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
684
|
+
MediaType.VideoMain,
|
|
685
|
+
mediaRequests
|
|
686
|
+
);
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
// @ts-ignore - config coming from registerPlugin
|
|
690
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
691
|
+
kind: 'video',
|
|
592
692
|
}
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
693
|
+
),
|
|
694
|
+
screenShareAudio: new MediaRequestManager(
|
|
695
|
+
(mediaRequests) => {
|
|
696
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
697
|
+
LoggerProxy.logger.warn(
|
|
698
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare audio media request before media connection was created'
|
|
699
|
+
);
|
|
700
|
+
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
704
|
+
MediaType.AudioSlides,
|
|
705
|
+
mediaRequests
|
|
602
706
|
);
|
|
707
|
+
},
|
|
708
|
+
{
|
|
709
|
+
// @ts-ignore - config coming from registerPlugin
|
|
710
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
711
|
+
kind: 'audio',
|
|
712
|
+
}
|
|
713
|
+
),
|
|
714
|
+
screenShareVideo: new MediaRequestManager(
|
|
715
|
+
(mediaRequests) => {
|
|
716
|
+
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
717
|
+
LoggerProxy.logger.warn(
|
|
718
|
+
'Meeting:index#mediaRequestManager --> trying to send screenshare video media request before media connection was created'
|
|
719
|
+
);
|
|
603
720
|
|
|
604
|
-
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
this.mediaProperties.webrtcMediaConnection.requestMedia(
|
|
724
|
+
MediaType.VideoSlides,
|
|
725
|
+
mediaRequests
|
|
726
|
+
);
|
|
727
|
+
},
|
|
728
|
+
{
|
|
729
|
+
// @ts-ignore - config coming from registerPlugin
|
|
730
|
+
degradationPreferences: this.config.degradationPreferences,
|
|
731
|
+
kind: 'video',
|
|
605
732
|
}
|
|
606
|
-
|
|
607
|
-
MC.MediaType.VideoMain,
|
|
608
|
-
mediaRequests
|
|
609
|
-
);
|
|
610
|
-
}),
|
|
733
|
+
),
|
|
611
734
|
};
|
|
612
735
|
/**
|
|
613
736
|
* @instance
|
|
@@ -620,8 +743,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
620
743
|
locusUrl: attrs.locus && attrs.locus.url,
|
|
621
744
|
receiveSlotManager: this.receiveSlotManager,
|
|
622
745
|
mediaRequestManagers: this.mediaRequestManagers,
|
|
623
|
-
|
|
746
|
+
meeting: this,
|
|
624
747
|
},
|
|
748
|
+
// @ts-ignore - Fix type
|
|
625
749
|
{parent: this.webex}
|
|
626
750
|
);
|
|
627
751
|
/**
|
|
@@ -653,7 +777,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
653
777
|
*/
|
|
654
778
|
this.reconnectionManager = new ReconnectionManager(this);
|
|
655
779
|
/**
|
|
656
|
-
* created
|
|
780
|
+
* created with media connection
|
|
657
781
|
* @instance
|
|
658
782
|
* @type {MuteState}
|
|
659
783
|
* @private
|
|
@@ -661,21 +785,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
661
785
|
*/
|
|
662
786
|
this.audio = null;
|
|
663
787
|
/**
|
|
664
|
-
* created
|
|
788
|
+
* created with media connection
|
|
665
789
|
* @instance
|
|
666
790
|
* @type {MuteState}
|
|
667
791
|
* @private
|
|
668
792
|
* @memberof Meeting
|
|
669
793
|
*/
|
|
670
794
|
this.video = null;
|
|
671
|
-
/**
|
|
672
|
-
* created later
|
|
673
|
-
* @instance
|
|
674
|
-
* @type {EffectsState}
|
|
675
|
-
* @private
|
|
676
|
-
* @memberof Meeting
|
|
677
|
-
*/
|
|
678
|
-
this.effects = null;
|
|
679
795
|
/**
|
|
680
796
|
* @instance
|
|
681
797
|
* @type {MeetingStateMachine}
|
|
@@ -770,7 +886,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
770
886
|
* @private
|
|
771
887
|
* @memberof Meeting
|
|
772
888
|
*/
|
|
773
|
-
this.meetingRequest = new MeetingRequest(
|
|
889
|
+
this.meetingRequest = new MeetingRequest(
|
|
890
|
+
{
|
|
891
|
+
meeting: this,
|
|
892
|
+
},
|
|
893
|
+
options
|
|
894
|
+
);
|
|
774
895
|
/**
|
|
775
896
|
* @instance
|
|
776
897
|
* @type {Array}
|
|
@@ -924,6 +1045,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
924
1045
|
*/
|
|
925
1046
|
// @ts-ignore - Fix type
|
|
926
1047
|
this.locusInfo = new LocusInfo(this.updateMeetingObject.bind(this), this.webex, this.id);
|
|
1048
|
+
|
|
927
1049
|
// We had to add listeners first before setting up the locus instance
|
|
928
1050
|
/**
|
|
929
1051
|
* @instance
|
|
@@ -1014,6 +1136,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1014
1136
|
*/
|
|
1015
1137
|
this.meetingInfoFailureReason = undefined;
|
|
1016
1138
|
|
|
1139
|
+
/**
|
|
1140
|
+
* The numeric code, if any, associated with the last failure to obtain the meeting info
|
|
1141
|
+
* @instance
|
|
1142
|
+
* @type {number}
|
|
1143
|
+
* @private
|
|
1144
|
+
* @memberof Meeting
|
|
1145
|
+
*/
|
|
1146
|
+
this.meetingInfoFailureCode = undefined;
|
|
1147
|
+
|
|
1017
1148
|
/**
|
|
1018
1149
|
* Repeating timer used to send keepAlives when in lobby
|
|
1019
1150
|
* @instance
|
|
@@ -1023,16 +1154,58 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1023
1154
|
*/
|
|
1024
1155
|
this.keepAliveTimerId = null;
|
|
1025
1156
|
|
|
1157
|
+
/**
|
|
1158
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1159
|
+
* @instance
|
|
1160
|
+
* @type {RecordingController}
|
|
1161
|
+
* @public
|
|
1162
|
+
* @memberof Meeting
|
|
1163
|
+
*/
|
|
1164
|
+
this.recordingController = new RecordingController(this.meetingRequest, {
|
|
1165
|
+
serviceUrl: this.locusInfo?.links?.services?.record?.url,
|
|
1166
|
+
sessionId: this.locusInfo?.fullState?.sessionId,
|
|
1167
|
+
locusUrl: this.locusInfo?.url,
|
|
1168
|
+
displayHints: [],
|
|
1169
|
+
});
|
|
1170
|
+
|
|
1171
|
+
/**
|
|
1172
|
+
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1173
|
+
* @instance
|
|
1174
|
+
* @type {ControlsOptionsManager}
|
|
1175
|
+
* @public
|
|
1176
|
+
* @memberof Meeting
|
|
1177
|
+
*/
|
|
1178
|
+
this.controlsOptionsManager = new ControlsOptionsManager(this.meetingRequest, {
|
|
1179
|
+
locusUrl: this.locusInfo?.url,
|
|
1180
|
+
displayHints: [],
|
|
1181
|
+
});
|
|
1182
|
+
|
|
1026
1183
|
this.setUpLocusInfoListeners();
|
|
1027
1184
|
this.locusInfo.init(attrs.locus ? attrs.locus : {});
|
|
1028
1185
|
this.hasJoinedOnce = false;
|
|
1029
1186
|
|
|
1030
|
-
this.media = new MultistreamMedia(this);
|
|
1031
|
-
|
|
1032
1187
|
/**
|
|
1033
1188
|
* helper class for managing remote streams
|
|
1034
1189
|
*/
|
|
1035
1190
|
this.remoteMediaManager = null;
|
|
1191
|
+
|
|
1192
|
+
this.localAudioTrackMuteStateHandler = (event) => {
|
|
1193
|
+
this.audio.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1194
|
+
};
|
|
1195
|
+
|
|
1196
|
+
this.localVideoTrackMuteStateHandler = (event) => {
|
|
1197
|
+
this.video.handleLocalTrackMuteStateChange(this, event.trackState.muted);
|
|
1198
|
+
};
|
|
1199
|
+
|
|
1200
|
+
// The handling of underlying track changes should be done inside
|
|
1201
|
+
// @webex/internal-media-core, but for now we have to do it here, because
|
|
1202
|
+
// RoapMediaConnection has to use raw MediaStreamTracks in its API until
|
|
1203
|
+
// the Calling SDK also moves to using webrtc-core tracks
|
|
1204
|
+
this.underlyingLocalTrackChangeHandler = () => {
|
|
1205
|
+
if (!this.isMultistream) {
|
|
1206
|
+
this.updateTranscodedMediaConnection();
|
|
1207
|
+
}
|
|
1208
|
+
};
|
|
1036
1209
|
}
|
|
1037
1210
|
|
|
1038
1211
|
/**
|
|
@@ -1047,9 +1220,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1047
1220
|
public async fetchMeetingInfo({
|
|
1048
1221
|
password = null,
|
|
1049
1222
|
captchaCode = null,
|
|
1223
|
+
extraParams = {},
|
|
1050
1224
|
}: {
|
|
1051
1225
|
password?: string;
|
|
1052
1226
|
captchaCode?: string;
|
|
1227
|
+
extraParams?: Record<string, any>;
|
|
1053
1228
|
}) {
|
|
1054
1229
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1055
1230
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
@@ -1080,11 +1255,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1080
1255
|
this.destination,
|
|
1081
1256
|
this.destinationType,
|
|
1082
1257
|
password,
|
|
1083
|
-
captchaInfo
|
|
1258
|
+
captchaInfo,
|
|
1259
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1260
|
+
this.config.installedOrgID,
|
|
1261
|
+
this.locusId,
|
|
1262
|
+
extraParams,
|
|
1263
|
+
{meetingId: this.id}
|
|
1084
1264
|
);
|
|
1085
1265
|
|
|
1086
1266
|
this.parseMeetingInfo(info, this.destination);
|
|
1087
|
-
this.meetingInfo = info ? info.body : null;
|
|
1267
|
+
this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
|
|
1088
1268
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1089
1269
|
this.requiredCaptcha = null;
|
|
1090
1270
|
if (
|
|
@@ -1107,9 +1287,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1107
1287
|
|
|
1108
1288
|
return Promise.resolve();
|
|
1109
1289
|
} catch (err) {
|
|
1110
|
-
if (err instanceof
|
|
1111
|
-
|
|
1290
|
+
if (err instanceof MeetingInfoV2PolicyError) {
|
|
1291
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.POLICY;
|
|
1292
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1293
|
+
|
|
1294
|
+
if (err.meetingInfo) {
|
|
1295
|
+
this.meetingInfo = err.meetingInfo;
|
|
1296
|
+
}
|
|
1297
|
+
|
|
1298
|
+
throw new PermissionError();
|
|
1299
|
+
} else if (err instanceof MeetingInfoV2PasswordError) {
|
|
1112
1300
|
LoggerProxy.logger.info(
|
|
1301
|
+
// @ts-ignore
|
|
1113
1302
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - password required (code=${err?.body?.code}).`
|
|
1114
1303
|
);
|
|
1115
1304
|
|
|
@@ -1119,6 +1308,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1119
1308
|
this.meetingNumber = err.meetingInfo.meetingNumber;
|
|
1120
1309
|
}
|
|
1121
1310
|
|
|
1311
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1312
|
+
|
|
1122
1313
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1123
1314
|
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1124
1315
|
if (this.requiredCaptcha) {
|
|
@@ -1128,8 +1319,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1128
1319
|
|
|
1129
1320
|
throw new PasswordError();
|
|
1130
1321
|
} else if (err instanceof MeetingInfoV2CaptchaError) {
|
|
1131
|
-
// @ts-ignore
|
|
1132
1322
|
LoggerProxy.logger.info(
|
|
1323
|
+
// @ts-ignore
|
|
1133
1324
|
`Meeting:index#fetchMeetingInfo --> Info Unable to fetch meeting info for ${this.destination} - captcha required (code=${err?.body?.code}).`
|
|
1134
1325
|
);
|
|
1135
1326
|
|
|
@@ -1137,6 +1328,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1137
1328
|
? MEETING_INFO_FAILURE_REASON.WRONG_CAPTCHA
|
|
1138
1329
|
: MEETING_INFO_FAILURE_REASON.WRONG_PASSWORD;
|
|
1139
1330
|
|
|
1331
|
+
this.meetingInfoFailureCode = err.wbxAppApiCode;
|
|
1332
|
+
|
|
1140
1333
|
if (err.isPasswordRequired) {
|
|
1141
1334
|
this.passwordStatus = PASSWORD_STATUS.REQUIRED;
|
|
1142
1335
|
}
|
|
@@ -1201,22 +1394,39 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1201
1394
|
// we have to pass the wbxappapi hostname as the siteFullName parameter
|
|
1202
1395
|
const {hostname} = new URL(this.requiredCaptcha.refreshURL);
|
|
1203
1396
|
|
|
1204
|
-
return
|
|
1205
|
-
.
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1397
|
+
return (
|
|
1398
|
+
this.meetingRequest
|
|
1399
|
+
// @ts-ignore
|
|
1400
|
+
.refreshCaptcha({
|
|
1401
|
+
captchaRefreshUrl: `${this.requiredCaptcha.refreshURL}&siteFullName=${hostname}`,
|
|
1402
|
+
captchaId: this.requiredCaptcha.captchaId,
|
|
1403
|
+
})
|
|
1404
|
+
.then((response) => {
|
|
1405
|
+
this.requiredCaptcha.captchaId = response.body.captchaID;
|
|
1406
|
+
this.requiredCaptcha.verificationImageURL = response.body.verificationImageURL;
|
|
1407
|
+
this.requiredCaptcha.verificationAudioURL = response.body.verificationAudioURL;
|
|
1408
|
+
})
|
|
1409
|
+
.catch((error) => {
|
|
1410
|
+
LoggerProxy.logger.error(
|
|
1411
|
+
`Meeting:index#refreshCaptcha --> Error Unable to refresh captcha for ${this.destination} - ${error}`
|
|
1412
|
+
);
|
|
1413
|
+
throw error;
|
|
1414
|
+
})
|
|
1415
|
+
);
|
|
1416
|
+
}
|
|
1417
|
+
|
|
1418
|
+
/**
|
|
1419
|
+
* Posts metrics event for this meeting. Allows the app to send Call Analyzer events.
|
|
1420
|
+
* @param {String} eventName - Call Analyzer event, see eventType in src/metrics/config.ts for possible values
|
|
1421
|
+
* @public
|
|
1422
|
+
* @memberof Meeting
|
|
1423
|
+
* @returns {Promise}
|
|
1424
|
+
*/
|
|
1425
|
+
public postMetrics(eventName: string) {
|
|
1426
|
+
Metrics.postEvent({
|
|
1427
|
+
event: eventName,
|
|
1428
|
+
meeting: this,
|
|
1429
|
+
});
|
|
1220
1430
|
}
|
|
1221
1431
|
|
|
1222
1432
|
/**
|
|
@@ -1229,6 +1439,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1229
1439
|
// meeting update listeners
|
|
1230
1440
|
this.setUpLocusInfoSelfListener();
|
|
1231
1441
|
this.setUpLocusInfoMeetingListener();
|
|
1442
|
+
this.setUpLocusServicesListener();
|
|
1232
1443
|
// members update listeners
|
|
1233
1444
|
this.setUpLocusFullStateListener();
|
|
1234
1445
|
this.setUpLocusUrlListener();
|
|
@@ -1241,6 +1452,94 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1241
1452
|
this.setUpLocusInfoMeetingInfoListener();
|
|
1242
1453
|
this.setUpLocusInfoAssignHostListener();
|
|
1243
1454
|
this.setUpLocusInfoMediaInactiveListener();
|
|
1455
|
+
this.setUpBreakoutsListener();
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Set up the listeners for breakouts
|
|
1460
|
+
* @returns {undefined}
|
|
1461
|
+
* @private
|
|
1462
|
+
* @memberof Meeting
|
|
1463
|
+
*/
|
|
1464
|
+
setUpBreakoutsListener() {
|
|
1465
|
+
this.breakouts.on(BREAKOUTS.EVENTS.BREAKOUTS_CLOSING, () => {
|
|
1466
|
+
Trigger.trigger(
|
|
1467
|
+
this,
|
|
1468
|
+
{
|
|
1469
|
+
file: 'meeting/index',
|
|
1470
|
+
function: 'setUpBreakoutsListener',
|
|
1471
|
+
},
|
|
1472
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_CLOSING
|
|
1473
|
+
);
|
|
1474
|
+
});
|
|
1475
|
+
|
|
1476
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MESSAGE, (messageEvent) => {
|
|
1477
|
+
Trigger.trigger(
|
|
1478
|
+
this,
|
|
1479
|
+
{
|
|
1480
|
+
file: 'meeting/index',
|
|
1481
|
+
function: 'setUpBreakoutsListener',
|
|
1482
|
+
},
|
|
1483
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_MESSAGE,
|
|
1484
|
+
messageEvent
|
|
1485
|
+
);
|
|
1486
|
+
});
|
|
1487
|
+
|
|
1488
|
+
this.breakouts.on(BREAKOUTS.EVENTS.MEMBERS_UPDATE, () => {
|
|
1489
|
+
Trigger.trigger(
|
|
1490
|
+
this,
|
|
1491
|
+
{
|
|
1492
|
+
file: 'meeting/index',
|
|
1493
|
+
function: 'setUpBreakoutsListener',
|
|
1494
|
+
},
|
|
1495
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
1496
|
+
);
|
|
1497
|
+
});
|
|
1498
|
+
|
|
1499
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_RETURN_TO_MAIN, () => {
|
|
1500
|
+
Trigger.trigger(
|
|
1501
|
+
this,
|
|
1502
|
+
{
|
|
1503
|
+
file: 'meeting/index',
|
|
1504
|
+
function: 'setUpBreakoutsListener',
|
|
1505
|
+
},
|
|
1506
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_RETURN_TO_MAIN
|
|
1507
|
+
);
|
|
1508
|
+
});
|
|
1509
|
+
|
|
1510
|
+
this.breakouts.on(BREAKOUTS.EVENTS.LEAVE_BREAKOUT, () => {
|
|
1511
|
+
Trigger.trigger(
|
|
1512
|
+
this,
|
|
1513
|
+
{
|
|
1514
|
+
file: 'meeting/index',
|
|
1515
|
+
function: 'setUpBreakoutsListener',
|
|
1516
|
+
},
|
|
1517
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_LEAVE
|
|
1518
|
+
);
|
|
1519
|
+
});
|
|
1520
|
+
|
|
1521
|
+
this.breakouts.on(BREAKOUTS.EVENTS.ASK_FOR_HELP, (helpEvent) => {
|
|
1522
|
+
Trigger.trigger(
|
|
1523
|
+
this,
|
|
1524
|
+
{
|
|
1525
|
+
file: 'meeting/index',
|
|
1526
|
+
function: 'setUpBreakoutsListener',
|
|
1527
|
+
},
|
|
1528
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_ASK_FOR_HELP,
|
|
1529
|
+
helpEvent
|
|
1530
|
+
);
|
|
1531
|
+
});
|
|
1532
|
+
|
|
1533
|
+
this.breakouts.on(BREAKOUTS.EVENTS.PRE_ASSIGNMENTS_UPDATE, () => {
|
|
1534
|
+
Trigger.trigger(
|
|
1535
|
+
this,
|
|
1536
|
+
{
|
|
1537
|
+
file: 'meeting/index',
|
|
1538
|
+
function: 'setUpBreakoutsListener',
|
|
1539
|
+
},
|
|
1540
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_PRE_ASSIGNMENTS_UPDATE
|
|
1541
|
+
);
|
|
1542
|
+
});
|
|
1244
1543
|
}
|
|
1245
1544
|
|
|
1246
1545
|
/**
|
|
@@ -1356,19 +1655,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1356
1655
|
* @returns {Object}
|
|
1357
1656
|
* @memberof Meeting
|
|
1358
1657
|
*/
|
|
1359
|
-
getAnalyzerMetricsPrePayload(
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1658
|
+
getAnalyzerMetricsPrePayload(options: {
|
|
1659
|
+
type?: string;
|
|
1660
|
+
event: string;
|
|
1661
|
+
trackingId: string;
|
|
1662
|
+
locus: object;
|
|
1663
|
+
mediaConnections?: Array<any>;
|
|
1664
|
+
errors?: object;
|
|
1665
|
+
meetingLookupUrl?: string;
|
|
1666
|
+
clientType?: any;
|
|
1667
|
+
subClientType?: any;
|
|
1668
|
+
[key: string]: any;
|
|
1669
|
+
}) {
|
|
1370
1670
|
if (options) {
|
|
1371
|
-
const {event, trackingId, mediaConnections} = options;
|
|
1671
|
+
const {event, trackingId, mediaConnections, meetingLookupUrl} = options;
|
|
1372
1672
|
|
|
1373
1673
|
if (!event) {
|
|
1374
1674
|
LoggerProxy.logger.error(
|
|
@@ -1407,6 +1707,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1407
1707
|
identifiers.mediaAgentCluster = this.mediaConnections?.[0].mediaAgentCluster;
|
|
1408
1708
|
}
|
|
1409
1709
|
|
|
1710
|
+
if (meetingLookupUrl) {
|
|
1711
|
+
identifiers.meetingLookupUrl = meetingLookupUrl;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1410
1714
|
if (options.trackingId) {
|
|
1411
1715
|
identifiers.trackingId = trackingId;
|
|
1412
1716
|
}
|
|
@@ -1456,12 +1760,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1456
1760
|
};
|
|
1457
1761
|
}
|
|
1458
1762
|
|
|
1459
|
-
const
|
|
1763
|
+
const callInitJoinReq = this.getCallInitJoinReq();
|
|
1460
1764
|
|
|
1461
|
-
if (
|
|
1765
|
+
if (callInitJoinReq) {
|
|
1462
1766
|
options.joinTimes = {
|
|
1463
1767
|
...options.joinTimes,
|
|
1464
|
-
|
|
1768
|
+
callInitJoinReq,
|
|
1465
1769
|
};
|
|
1466
1770
|
}
|
|
1467
1771
|
|
|
@@ -1474,15 +1778,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1474
1778
|
};
|
|
1475
1779
|
}
|
|
1476
1780
|
|
|
1477
|
-
const
|
|
1781
|
+
const totalJmt = this.getTotalJmt();
|
|
1478
1782
|
|
|
1479
|
-
if (
|
|
1783
|
+
if (totalJmt) {
|
|
1480
1784
|
options.joinTimes = {
|
|
1481
1785
|
...options.joinTimes,
|
|
1482
|
-
|
|
1786
|
+
totalJmt,
|
|
1483
1787
|
};
|
|
1484
1788
|
}
|
|
1485
1789
|
|
|
1790
|
+
const curUserType = this.getCurUserType();
|
|
1791
|
+
|
|
1792
|
+
if (curUserType) {
|
|
1793
|
+
options.userType = curUserType;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
const curLoginType = this.getCurLoginType();
|
|
1797
|
+
|
|
1798
|
+
if (curLoginType) {
|
|
1799
|
+
options.loginType = curLoginType;
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
if (this.environment) {
|
|
1803
|
+
options.environment = this.environment;
|
|
1804
|
+
}
|
|
1805
|
+
|
|
1486
1806
|
if (options.type === MQA_STATS.CA_TYPE) {
|
|
1487
1807
|
payload = Metrics.initMediaPayload(options.event, identifiers, options);
|
|
1488
1808
|
} else {
|
|
@@ -1596,11 +1916,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1596
1916
|
this.pstnUpdate(payload);
|
|
1597
1917
|
|
|
1598
1918
|
// 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
|
-
}
|
|
1919
|
+
this.requestScreenShareFloorIfPending();
|
|
1604
1920
|
});
|
|
1605
1921
|
}
|
|
1606
1922
|
|
|
@@ -1786,6 +2102,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1786
2102
|
}
|
|
1787
2103
|
);
|
|
1788
2104
|
|
|
2105
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MEETING_BREAKOUT_UPDATED, ({breakout}) => {
|
|
2106
|
+
this.breakouts.updateBreakout(breakout);
|
|
2107
|
+
Trigger.trigger(
|
|
2108
|
+
this,
|
|
2109
|
+
{
|
|
2110
|
+
file: 'meeting/index',
|
|
2111
|
+
function: 'setupLocusControlsListener',
|
|
2112
|
+
},
|
|
2113
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2114
|
+
);
|
|
2115
|
+
});
|
|
2116
|
+
|
|
2117
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
|
|
2118
|
+
this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
|
|
2119
|
+
// clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
|
|
2120
|
+
// which means main session is not active for the attendee
|
|
2121
|
+
if (error?.statusCode === 403) {
|
|
2122
|
+
this.locusInfo.clearMainSessionLocusCache();
|
|
2123
|
+
}
|
|
2124
|
+
});
|
|
2125
|
+
});
|
|
2126
|
+
|
|
1789
2127
|
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
|
|
1790
2128
|
Trigger.trigger(
|
|
1791
2129
|
this,
|
|
@@ -1797,23 +2135,111 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1797
2135
|
{entryExitTone}
|
|
1798
2136
|
);
|
|
1799
2137
|
});
|
|
1800
|
-
}
|
|
1801
2138
|
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
|
|
1811
|
-
|
|
1812
|
-
|
|
1813
|
-
|
|
1814
|
-
|
|
2139
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_MUTE_ON_ENTRY_CHANGED, ({state}) => {
|
|
2140
|
+
Trigger.trigger(
|
|
2141
|
+
this,
|
|
2142
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2143
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_MUTE_ON_ENTRY_UPDATED,
|
|
2144
|
+
{state}
|
|
2145
|
+
);
|
|
2146
|
+
});
|
|
2147
|
+
|
|
2148
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_SHARE_CONTROL_CHANGED, ({state}) => {
|
|
2149
|
+
Trigger.trigger(
|
|
2150
|
+
this,
|
|
2151
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2152
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_SHARE_CONTROL_UPDATED,
|
|
2153
|
+
{state}
|
|
2154
|
+
);
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_DISALLOW_UNMUTE_CHANGED, ({state}) => {
|
|
2158
|
+
Trigger.trigger(
|
|
2159
|
+
this,
|
|
2160
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2161
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_DISALLOW_UNMUTE_UPDATED,
|
|
2162
|
+
{state}
|
|
2163
|
+
);
|
|
2164
|
+
});
|
|
2165
|
+
|
|
2166
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_REACTIONS_CHANGED, ({state}) => {
|
|
2167
|
+
Trigger.trigger(
|
|
2168
|
+
this,
|
|
2169
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2170
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_REACTIONS_UPDATED,
|
|
2171
|
+
{state}
|
|
2172
|
+
);
|
|
2173
|
+
});
|
|
2174
|
+
|
|
2175
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIEW_THE_PARTICIPANTS_LIST_CHANGED, ({state}) => {
|
|
2176
|
+
Trigger.trigger(
|
|
2177
|
+
this,
|
|
2178
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2179
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIEW_THE_PARTICIPANTS_LIST_UPDATED,
|
|
2180
|
+
{state}
|
|
2181
|
+
);
|
|
2182
|
+
});
|
|
2183
|
+
|
|
2184
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_RAISE_HAND_CHANGED, ({state}) => {
|
|
2185
|
+
Trigger.trigger(
|
|
2186
|
+
this,
|
|
2187
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2188
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_RAISE_HAND_UPDATED,
|
|
2189
|
+
{state}
|
|
2190
|
+
);
|
|
2191
|
+
});
|
|
2192
|
+
|
|
2193
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_VIDEO_CHANGED, ({state}) => {
|
|
2194
|
+
Trigger.trigger(
|
|
2195
|
+
this,
|
|
2196
|
+
{file: 'meeting/index', function: 'setupLocusControlsListener'},
|
|
2197
|
+
EVENT_TRIGGERS.MEETING_CONTROLS_VIDEO_UPDATED,
|
|
2198
|
+
{state}
|
|
2199
|
+
);
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
|
|
2203
|
+
/**
|
|
2204
|
+
* Trigger annotation info update event
|
|
2205
|
+
@returns {undefined}
|
|
2206
|
+
@param {object} contentShare
|
|
2207
|
+
@param {object} previousContentShare
|
|
2208
|
+
*/
|
|
2209
|
+
private triggerAnnotationInfoEvent(contentShare, previousContentShare) {
|
|
2210
|
+
if (
|
|
2211
|
+
contentShare?.annotation &&
|
|
2212
|
+
!isEqual(contentShare?.annotation, previousContentShare?.annotation)
|
|
2213
|
+
) {
|
|
2214
|
+
Trigger.trigger(
|
|
2215
|
+
this,
|
|
2216
|
+
{
|
|
2217
|
+
file: 'meeting/index',
|
|
2218
|
+
function: 'triggerAnnotationInfoEvent',
|
|
2219
|
+
},
|
|
2220
|
+
EVENT_TRIGGERS.MEETING_UPDATE_ANNOTATION_INFO,
|
|
2221
|
+
contentShare.annotation
|
|
2222
|
+
);
|
|
2223
|
+
}
|
|
2224
|
+
}
|
|
2225
|
+
|
|
2226
|
+
/**
|
|
2227
|
+
* Set up the locus info media shares listener
|
|
2228
|
+
* update content and whiteboard sharing id value for members, and updates the member
|
|
2229
|
+
* notifies consumer with members:content:update {activeContentSharingId, endedContentSharingId}
|
|
2230
|
+
* @returns {undefined}
|
|
2231
|
+
* @private
|
|
2232
|
+
* @memberof Meeting
|
|
2233
|
+
*/
|
|
2234
|
+
private setUpLocusMediaSharesListener() {
|
|
2235
|
+
// Will get triggered on local and remote share
|
|
2236
|
+
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_MEDIA_SHARES, async (payload) => {
|
|
2237
|
+
const {content: contentShare, whiteboard: whiteboardShare} = payload.current;
|
|
2238
|
+
const previousContentShare = payload.previous?.content;
|
|
1815
2239
|
const previousWhiteboardShare = payload.previous?.whiteboard;
|
|
1816
2240
|
|
|
2241
|
+
this.triggerAnnotationInfoEvent(contentShare, previousContentShare);
|
|
2242
|
+
|
|
1817
2243
|
if (
|
|
1818
2244
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
1819
2245
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
@@ -1841,19 +2267,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1841
2267
|
this.selfId === contentShare.beneficiaryId &&
|
|
1842
2268
|
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
1843
2269
|
) {
|
|
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
|
-
}
|
|
2270
|
+
// CONTENT - sharing content local
|
|
2271
|
+
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
1857
2272
|
}
|
|
1858
2273
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
1859
2274
|
// There is no concept of local/remote share for whiteboard
|
|
@@ -1937,23 +2352,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1937
2352
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
1938
2353
|
{
|
|
1939
2354
|
memberId: contentShare.beneficiaryId,
|
|
2355
|
+
url: contentShare.url,
|
|
2356
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
1940
2357
|
}
|
|
1941
2358
|
);
|
|
1942
2359
|
};
|
|
1943
2360
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1948
|
-
|
|
2361
|
+
try {
|
|
2362
|
+
// if a remote participant is stealing the presentation from us
|
|
2363
|
+
if (
|
|
2364
|
+
this.mediaProperties.mediaDirection?.sendShare &&
|
|
2365
|
+
oldShareStatus === SHARE_STATUS.LOCAL_SHARE_ACTIVE
|
|
2366
|
+
) {
|
|
2367
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo screen share audio (SPARK-399690)
|
|
2368
|
+
}
|
|
2369
|
+
} finally {
|
|
1949
2370
|
sendStartedSharingRemote();
|
|
1950
|
-
} else {
|
|
1951
|
-
this.updateShare({
|
|
1952
|
-
sendShare: false,
|
|
1953
|
-
receiveShare: this.mediaProperties.mediaDirection.receiveShare,
|
|
1954
|
-
}).finally(() => {
|
|
1955
|
-
sendStartedSharingRemote();
|
|
1956
|
-
});
|
|
1957
2371
|
}
|
|
1958
2372
|
break;
|
|
1959
2373
|
}
|
|
@@ -2007,6 +2421,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2007
2421
|
EVENT_TRIGGERS.MEETING_STARTED_SHARING_REMOTE,
|
|
2008
2422
|
{
|
|
2009
2423
|
memberId: contentShare.beneficiaryId,
|
|
2424
|
+
url: contentShare.url,
|
|
2425
|
+
shareInstanceId: contentShare.shareInstanceId,
|
|
2010
2426
|
}
|
|
2011
2427
|
);
|
|
2012
2428
|
this.members.locusMediaSharesUpdate(payload);
|
|
@@ -2041,8 +2457,30 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2041
2457
|
private setUpLocusUrlListener() {
|
|
2042
2458
|
this.locusInfo.on(EVENTS.LOCUS_INFO_UPDATE_URL, (payload) => {
|
|
2043
2459
|
this.members.locusUrlUpdate(payload);
|
|
2460
|
+
this.breakouts.locusUrlUpdate(payload);
|
|
2461
|
+
this.annotation.locusUrlUpdate(payload);
|
|
2044
2462
|
this.locusUrl = payload;
|
|
2045
2463
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2464
|
+
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2465
|
+
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2466
|
+
});
|
|
2467
|
+
}
|
|
2468
|
+
|
|
2469
|
+
/**
|
|
2470
|
+
* Set up the locus info service link listener
|
|
2471
|
+
* update the locusInfo for recording controller
|
|
2472
|
+
* does not currently re-emit the event as it's internal only
|
|
2473
|
+
* payload is unused
|
|
2474
|
+
* @returns {undefined}
|
|
2475
|
+
* @private
|
|
2476
|
+
* @memberof Meeting
|
|
2477
|
+
*/
|
|
2478
|
+
private setUpLocusServicesListener() {
|
|
2479
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.LINKS_SERVICES, (payload) => {
|
|
2480
|
+
this.recordingController.setServiceUrl(payload?.services?.record?.url);
|
|
2481
|
+
this.recordingController.setSessionId(this.locusInfo?.fullState?.sessionId);
|
|
2482
|
+
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2483
|
+
this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2046
2484
|
});
|
|
2047
2485
|
}
|
|
2048
2486
|
|
|
@@ -2092,10 +2530,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2092
2530
|
canAdmitParticipant: MeetingUtil.canAdmitParticipant(payload.info.userDisplayHints),
|
|
2093
2531
|
canLock: MeetingUtil.canUserLock(payload.info.userDisplayHints),
|
|
2094
2532
|
canUnlock: MeetingUtil.canUserUnlock(payload.info.userDisplayHints),
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2533
|
+
canSetDisallowUnmute: ControlsOptionsUtil.canSetDisallowUnmute(
|
|
2534
|
+
payload.info.userDisplayHints
|
|
2535
|
+
),
|
|
2536
|
+
canUnsetDisallowUnmute: ControlsOptionsUtil.canUnsetDisallowUnmute(
|
|
2537
|
+
payload.info.userDisplayHints
|
|
2538
|
+
),
|
|
2539
|
+
canSetMuteOnEntry: ControlsOptionsUtil.canSetMuteOnEntry(payload.info.userDisplayHints),
|
|
2540
|
+
canUnsetMuteOnEntry: ControlsOptionsUtil.canUnsetMuteOnEntry(
|
|
2541
|
+
payload.info.userDisplayHints
|
|
2542
|
+
),
|
|
2543
|
+
canSetMuted: ControlsOptionsUtil.canSetMuted(payload.info.userDisplayHints),
|
|
2544
|
+
canUnsetMuted: ControlsOptionsUtil.canUnsetMuted(payload.info.userDisplayHints),
|
|
2545
|
+
canStartRecording: RecordingUtil.canUserStart(payload.info.userDisplayHints),
|
|
2546
|
+
canStopRecording: RecordingUtil.canUserStop(payload.info.userDisplayHints),
|
|
2547
|
+
canPauseRecording: RecordingUtil.canUserPause(payload.info.userDisplayHints),
|
|
2548
|
+
canResumeRecording: RecordingUtil.canUserResume(payload.info.userDisplayHints),
|
|
2099
2549
|
canRaiseHand: MeetingUtil.canUserRaiseHand(payload.info.userDisplayHints),
|
|
2100
2550
|
canLowerAllHands: MeetingUtil.canUserLowerAllHands(payload.info.userDisplayHints),
|
|
2101
2551
|
canLowerSomeoneElsesHand: MeetingUtil.canUserLowerSomeoneElsesHand(
|
|
@@ -2108,6 +2558,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2108
2558
|
canStartTranscribing: MeetingUtil.canStartTranscribing(payload.info.userDisplayHints),
|
|
2109
2559
|
canStopTranscribing: MeetingUtil.canStopTranscribing(payload.info.userDisplayHints),
|
|
2110
2560
|
isClosedCaptionActive: MeetingUtil.isClosedCaptionActive(payload.info.userDisplayHints),
|
|
2561
|
+
isSaveTranscriptsEnabled: MeetingUtil.isSaveTranscriptsEnabled(
|
|
2562
|
+
payload.info.userDisplayHints
|
|
2563
|
+
),
|
|
2111
2564
|
isWebexAssistantActive: MeetingUtil.isWebexAssistantActive(payload.info.userDisplayHints),
|
|
2112
2565
|
canViewCaptionPanel: MeetingUtil.canViewCaptionPanel(payload.info.userDisplayHints),
|
|
2113
2566
|
isRealTimeTranslationEnabled: MeetingUtil.isRealTimeTranslationEnabled(
|
|
@@ -2117,8 +2570,118 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2117
2570
|
payload.info.userDisplayHints
|
|
2118
2571
|
),
|
|
2119
2572
|
waitingForOthersToJoin: MeetingUtil.waitingForOthersToJoin(payload.info.userDisplayHints),
|
|
2573
|
+
canSendReactions: MeetingUtil.canSendReactions(
|
|
2574
|
+
this.inMeetingActions.canSendReactions,
|
|
2575
|
+
payload.info.userDisplayHints
|
|
2576
|
+
),
|
|
2577
|
+
canManageBreakout: MeetingUtil.canManageBreakout(payload.info.userDisplayHints),
|
|
2578
|
+
canBroadcastMessageToBreakout: MeetingUtil.canBroadcastMessageToBreakout(
|
|
2579
|
+
payload.info.userDisplayHints
|
|
2580
|
+
),
|
|
2581
|
+
canAdmitLobbyToBreakout: MeetingUtil.canAdmitLobbyToBreakout(
|
|
2582
|
+
payload.info.userDisplayHints
|
|
2583
|
+
),
|
|
2584
|
+
isBreakoutPreassignmentsEnabled: MeetingUtil.isBreakoutPreassignmentsEnabled(
|
|
2585
|
+
payload.info.userDisplayHints
|
|
2586
|
+
),
|
|
2587
|
+
canUserAskForHelp: MeetingUtil.canUserAskForHelp(payload.info.userDisplayHints),
|
|
2588
|
+
canUserRenameSelfAndObserved: MeetingUtil.canUserRenameSelfAndObserved(
|
|
2589
|
+
payload.info.userDisplayHints
|
|
2590
|
+
),
|
|
2591
|
+
canUserRenameOthers: MeetingUtil.canUserRenameOthers(payload.info.userDisplayHints),
|
|
2592
|
+
canMuteAll: ControlsOptionsUtil.hasHints({
|
|
2593
|
+
requiredHints: [DISPLAY_HINTS.MUTE_ALL],
|
|
2594
|
+
displayHints: payload.info.userDisplayHints,
|
|
2595
|
+
}),
|
|
2596
|
+
canUnmuteAll: ControlsOptionsUtil.hasHints({
|
|
2597
|
+
requiredHints: [DISPLAY_HINTS.UNMUTE_ALL],
|
|
2598
|
+
displayHints: payload.info.userDisplayHints,
|
|
2599
|
+
}),
|
|
2600
|
+
canEnableHardMute: ControlsOptionsUtil.hasHints({
|
|
2601
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_HARD_MUTE],
|
|
2602
|
+
displayHints: payload.info.userDisplayHints,
|
|
2603
|
+
}),
|
|
2604
|
+
canDisableHardMute: ControlsOptionsUtil.hasHints({
|
|
2605
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_HARD_MUTE],
|
|
2606
|
+
displayHints: payload.info.userDisplayHints,
|
|
2607
|
+
}),
|
|
2608
|
+
canEnableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2609
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_MUTE_ON_ENTRY],
|
|
2610
|
+
displayHints: payload.info.userDisplayHints,
|
|
2611
|
+
}),
|
|
2612
|
+
canDisableMuteOnEntry: ControlsOptionsUtil.hasHints({
|
|
2613
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_MUTE_ON_ENTRY],
|
|
2614
|
+
displayHints: payload.info.userDisplayHints,
|
|
2615
|
+
}),
|
|
2616
|
+
canEnableReactions: ControlsOptionsUtil.hasHints({
|
|
2617
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_REACTIONS],
|
|
2618
|
+
displayHints: payload.info.userDisplayHints,
|
|
2619
|
+
}),
|
|
2620
|
+
canDisableReactions: ControlsOptionsUtil.hasHints({
|
|
2621
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_REACTIONS],
|
|
2622
|
+
displayHints: payload.info.userDisplayHints,
|
|
2623
|
+
}),
|
|
2624
|
+
canEnableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2625
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_SHOW_DISPLAY_NAME],
|
|
2626
|
+
displayHints: payload.info.userDisplayHints,
|
|
2627
|
+
}),
|
|
2628
|
+
canDisableReactionDisplayNames: ControlsOptionsUtil.hasHints({
|
|
2629
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_SHOW_DISPLAY_NAME],
|
|
2630
|
+
displayHints: payload.info.userDisplayHints,
|
|
2631
|
+
}),
|
|
2632
|
+
canUpdateShareControl: ControlsOptionsUtil.hasHints({
|
|
2633
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTROL],
|
|
2634
|
+
displayHints: payload.info.userDisplayHints,
|
|
2635
|
+
}),
|
|
2636
|
+
canEnableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2637
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2638
|
+
displayHints: payload.info.userDisplayHints,
|
|
2639
|
+
}),
|
|
2640
|
+
canDisableViewTheParticipantsList: ControlsOptionsUtil.hasHints({
|
|
2641
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIEW_THE_PARTICIPANT_LIST],
|
|
2642
|
+
displayHints: payload.info.userDisplayHints,
|
|
2643
|
+
}),
|
|
2644
|
+
canEnableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2645
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_RAISE_HAND],
|
|
2646
|
+
displayHints: payload.info.userDisplayHints,
|
|
2647
|
+
}),
|
|
2648
|
+
canDisableRaiseHand: ControlsOptionsUtil.hasHints({
|
|
2649
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_RAISE_HAND],
|
|
2650
|
+
displayHints: payload.info.userDisplayHints,
|
|
2651
|
+
}),
|
|
2652
|
+
canEnableVideo: ControlsOptionsUtil.hasHints({
|
|
2653
|
+
requiredHints: [DISPLAY_HINTS.ENABLE_VIDEO],
|
|
2654
|
+
displayHints: payload.info.userDisplayHints,
|
|
2655
|
+
}),
|
|
2656
|
+
canDisableVideo: ControlsOptionsUtil.hasHints({
|
|
2657
|
+
requiredHints: [DISPLAY_HINTS.DISABLE_VIDEO],
|
|
2658
|
+
displayHints: payload.info.userDisplayHints,
|
|
2659
|
+
}),
|
|
2660
|
+
canShareFile: ControlsOptionsUtil.hasHints({
|
|
2661
|
+
requiredHints: [DISPLAY_HINTS.SHARE_FILE],
|
|
2662
|
+
displayHints: payload.info.userDisplayHints,
|
|
2663
|
+
}),
|
|
2664
|
+
canShareApplication: ControlsOptionsUtil.hasHints({
|
|
2665
|
+
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
2666
|
+
displayHints: payload.info.userDisplayHints,
|
|
2667
|
+
}),
|
|
2668
|
+
canShareCamera: ControlsOptionsUtil.hasHints({
|
|
2669
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CAMERA],
|
|
2670
|
+
displayHints: payload.info.userDisplayHints,
|
|
2671
|
+
}),
|
|
2672
|
+
canShareDesktop: ControlsOptionsUtil.hasHints({
|
|
2673
|
+
requiredHints: [DISPLAY_HINTS.SHARE_DESKTOP],
|
|
2674
|
+
displayHints: payload.info.userDisplayHints,
|
|
2675
|
+
}),
|
|
2676
|
+
canShareContent: ControlsOptionsUtil.hasHints({
|
|
2677
|
+
requiredHints: [DISPLAY_HINTS.SHARE_CONTENT],
|
|
2678
|
+
displayHints: payload.info.userDisplayHints,
|
|
2679
|
+
}),
|
|
2120
2680
|
});
|
|
2121
2681
|
|
|
2682
|
+
this.recordingController.setDisplayHints(payload.info.userDisplayHints);
|
|
2683
|
+
this.controlsOptionsManager.setDisplayHints(payload.info.userDisplayHints);
|
|
2684
|
+
|
|
2122
2685
|
if (changed) {
|
|
2123
2686
|
Trigger.trigger(
|
|
2124
2687
|
this,
|
|
@@ -2141,7 +2704,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2141
2704
|
* @param {String} datachannelUrl
|
|
2142
2705
|
* @returns {void}
|
|
2143
2706
|
*/
|
|
2707
|
+
|
|
2144
2708
|
handleDataChannelUrlChange(datachannelUrl) {
|
|
2709
|
+
// @ts-ignore - config coming from registerPlugin
|
|
2145
2710
|
if (datachannelUrl && this.config.enableAutomaticLLM) {
|
|
2146
2711
|
// Defer this as updateLLMConnection relies upon this.locusInfo.url which is only set
|
|
2147
2712
|
// after the MEETING_INFO_UPDATED callback finishes
|
|
@@ -2196,10 +2761,34 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2196
2761
|
);
|
|
2197
2762
|
}
|
|
2198
2763
|
});
|
|
2764
|
+
|
|
2765
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_VIDEO_MUTE_STATUS_UPDATED, (payload) => {
|
|
2766
|
+
if (payload) {
|
|
2767
|
+
if (this.video) {
|
|
2768
|
+
payload.muted = payload.muted ?? this.video.isRemotelyMuted();
|
|
2769
|
+
payload.unmuteAllowed = payload.unmuteAllowed ?? this.video.isUnmuteAllowed();
|
|
2770
|
+
this.video.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2771
|
+
}
|
|
2772
|
+
Trigger.trigger(
|
|
2773
|
+
this,
|
|
2774
|
+
{
|
|
2775
|
+
file: 'meeting/index',
|
|
2776
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2777
|
+
},
|
|
2778
|
+
payload.muted
|
|
2779
|
+
? EVENT_TRIGGERS.MEETING_SELF_VIDEO_MUTED_BY_OTHERS
|
|
2780
|
+
: EVENT_TRIGGERS.MEETING_SELF_VIDEO_UNMUTED_BY_OTHERS,
|
|
2781
|
+
{
|
|
2782
|
+
payload,
|
|
2783
|
+
}
|
|
2784
|
+
);
|
|
2785
|
+
}
|
|
2786
|
+
});
|
|
2787
|
+
|
|
2199
2788
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_REMOTE_MUTE_STATUS_UPDATED, (payload) => {
|
|
2200
2789
|
if (payload) {
|
|
2201
2790
|
if (this.audio) {
|
|
2202
|
-
this.audio.handleServerRemoteMuteUpdate(payload.muted, payload.unmuteAllowed);
|
|
2791
|
+
this.audio.handleServerRemoteMuteUpdate(this, payload.muted, payload.unmuteAllowed);
|
|
2203
2792
|
}
|
|
2204
2793
|
// with "mute on entry" server will send us remote mute even if we don't have media configured,
|
|
2205
2794
|
// so if being muted by others, always send the notification,
|
|
@@ -2322,6 +2911,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2322
2911
|
);
|
|
2323
2912
|
});
|
|
2324
2913
|
|
|
2914
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_MEETING_BREAKOUTS_CHANGED, (payload) => {
|
|
2915
|
+
this.breakouts.updateBreakoutSessions(payload);
|
|
2916
|
+
Trigger.trigger(
|
|
2917
|
+
this,
|
|
2918
|
+
{
|
|
2919
|
+
file: 'meeting/index',
|
|
2920
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2921
|
+
},
|
|
2922
|
+
EVENT_TRIGGERS.MEETING_BREAKOUTS_UPDATE
|
|
2923
|
+
);
|
|
2924
|
+
});
|
|
2925
|
+
|
|
2926
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ROLES_CHANGED, (payload) => {
|
|
2927
|
+
const isModeratorOrCohost =
|
|
2928
|
+
payload.newRoles?.includes(SELF_ROLES.MODERATOR) ||
|
|
2929
|
+
payload.newRoles?.includes(SELF_ROLES.COHOST);
|
|
2930
|
+
this.breakouts.updateCanManageBreakouts(isModeratorOrCohost);
|
|
2931
|
+
|
|
2932
|
+
Trigger.trigger(
|
|
2933
|
+
this,
|
|
2934
|
+
{
|
|
2935
|
+
file: 'meeting/index',
|
|
2936
|
+
function: 'setUpLocusInfoSelfListener',
|
|
2937
|
+
},
|
|
2938
|
+
EVENT_TRIGGERS.MEETING_SELF_ROLES_CHANGED,
|
|
2939
|
+
{
|
|
2940
|
+
payload,
|
|
2941
|
+
}
|
|
2942
|
+
);
|
|
2943
|
+
});
|
|
2944
|
+
|
|
2325
2945
|
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_IS_SHARING_BLOCKED_CHANGE, (payload) => {
|
|
2326
2946
|
Trigger.trigger(
|
|
2327
2947
|
this,
|
|
@@ -2357,19 +2977,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2357
2977
|
.catch((error) => {
|
|
2358
2978
|
// @ts-ignore
|
|
2359
2979
|
LoggerProxy.logger.error(
|
|
2360
|
-
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this
|
|
2980
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> REMOTE_RESPONSE. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
2361
2981
|
);
|
|
2362
2982
|
});
|
|
2363
2983
|
}
|
|
2364
2984
|
});
|
|
2365
|
-
this.locusInfo.on(EVENTS.DESTROY_MEETING, (payload) => {
|
|
2985
|
+
this.locusInfo.on(EVENTS.DESTROY_MEETING, async (payload) => {
|
|
2366
2986
|
// if self state is NOT left
|
|
2367
2987
|
|
|
2368
2988
|
// TODO: Handle sharing and wireless sharing when meeting end
|
|
2369
2989
|
if (this.wirelessShare) {
|
|
2370
2990
|
if (this.mediaProperties.shareTrack) {
|
|
2371
|
-
this.
|
|
2372
|
-
this.mediaProperties.shareTrack.stop();
|
|
2991
|
+
await this.setLocalShareTrack(undefined);
|
|
2373
2992
|
}
|
|
2374
2993
|
}
|
|
2375
2994
|
// when multiple WEB deviceType join with same user
|
|
@@ -2383,18 +3002,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2383
3002
|
if (payload.shouldLeave) {
|
|
2384
3003
|
// TODO: We should do cleaning of meeting object if the shouldLeave: false because there might be meeting object which we are not cleaning
|
|
2385
3004
|
|
|
2386
|
-
|
|
2387
|
-
.
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2394
|
-
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
3005
|
+
try {
|
|
3006
|
+
await this.leave({reason: payload.reason});
|
|
3007
|
+
|
|
3008
|
+
LoggerProxy.logger.warn(
|
|
3009
|
+
'Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. The meeting has been left, but has not been destroyed, you should see a later event for leave.'
|
|
3010
|
+
);
|
|
3011
|
+
} catch (error) {
|
|
3012
|
+
// @ts-ignore
|
|
3013
|
+
LoggerProxy.logger.error(
|
|
3014
|
+
`Meeting:index#setUpLocusInfoMeetingListener --> DESTROY_MEETING. Issue with leave for meeting, meeting still in collection: ${this}, error: ${error}`
|
|
3015
|
+
);
|
|
3016
|
+
}
|
|
2398
3017
|
} else {
|
|
2399
3018
|
LoggerProxy.logger.info(
|
|
2400
3019
|
'Meeting:index#setUpLocusInfoMeetingListener --> MEETING_REMOVED_REASON',
|
|
@@ -2472,14 +3091,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2472
3091
|
}
|
|
2473
3092
|
|
|
2474
3093
|
/**
|
|
2475
|
-
* Admit the guest(s) to the call once they are waiting
|
|
3094
|
+
* Admit the guest(s) to the call once they are waiting.
|
|
3095
|
+
* If the host/cohost is in a breakout session, the locus url
|
|
3096
|
+
* of the session must be provided as the authorizingLocusUrl.
|
|
3097
|
+
* Regardless of host/cohost location, the locus Id (lid) in
|
|
3098
|
+
* the path should be the locus Id of the main, which means the
|
|
3099
|
+
* locus url of the api call must be from the main session.
|
|
3100
|
+
* If these loucs urls are not provided, the function will do the check.
|
|
2476
3101
|
* @param {Array} memberIds
|
|
3102
|
+
* @param {Object} sessionLocusUrls: {authorizingLocusUrl, mainLocusUrl}
|
|
2477
3103
|
* @returns {Promise} see #members.admitMembers
|
|
2478
3104
|
* @public
|
|
2479
3105
|
* @memberof Meeting
|
|
2480
3106
|
*/
|
|
2481
|
-
public admit(
|
|
2482
|
-
|
|
3107
|
+
public admit(
|
|
3108
|
+
memberIds: Array<any>,
|
|
3109
|
+
sessionLocusUrls?: {authorizingLocusUrl: string; mainLocusUrl: string}
|
|
3110
|
+
) {
|
|
3111
|
+
let locusUrls = sessionLocusUrls;
|
|
3112
|
+
if (!locusUrls) {
|
|
3113
|
+
const {locusUrl, mainLocusUrl} = this.breakouts;
|
|
3114
|
+
if (locusUrl && mainLocusUrl) {
|
|
3115
|
+
locusUrls = {authorizingLocusUrl: locusUrl, mainLocusUrl};
|
|
3116
|
+
}
|
|
3117
|
+
}
|
|
3118
|
+
|
|
3119
|
+
return this.members.admitMembers(memberIds, locusUrls);
|
|
2483
3120
|
}
|
|
2484
3121
|
|
|
2485
3122
|
/**
|
|
@@ -2527,66 +3164,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2527
3164
|
return this.members;
|
|
2528
3165
|
}
|
|
2529
3166
|
|
|
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
3167
|
/**
|
|
2591
3168
|
* Sets the meeting info on the class instance
|
|
2592
3169
|
* @param {Object} meetingInfo
|
|
@@ -2634,6 +3211,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2634
3211
|
this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
|
|
2635
3212
|
// @ts-ignore - config coming from registerPlugin
|
|
2636
3213
|
this.setSipUri(
|
|
3214
|
+
// @ts-ignore
|
|
2637
3215
|
this.config.experimental.enableUnifiedMeetings
|
|
2638
3216
|
? locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipUrl
|
|
2639
3217
|
: locusMeetingObject?.info.sipUri || webexMeetingInfo?.sipMeetingUri || this.sipUri
|
|
@@ -2650,35 +3228,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2650
3228
|
webexMeetingInfo?.hostId ||
|
|
2651
3229
|
this.owner;
|
|
2652
3230
|
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
|
-
}
|
|
3231
|
+
// Need to populate environment when sending CA event
|
|
3232
|
+
this.environment = locusMeetingObject?.info.channel || webexMeetingInfo?.channel;
|
|
2682
3233
|
}
|
|
2683
3234
|
}
|
|
2684
3235
|
|
|
@@ -2708,7 +3259,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2708
3259
|
* @private
|
|
2709
3260
|
* @memberof Meeting
|
|
2710
3261
|
*/
|
|
2711
|
-
|
|
3262
|
+
setLocus(
|
|
2712
3263
|
locus:
|
|
2713
3264
|
| {
|
|
2714
3265
|
mediaConnections: Array<any>;
|
|
@@ -2743,21 +3294,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2743
3294
|
Trigger.trigger(this, options, EVENTS.REQUEST_UPLOAD_LOGS, this);
|
|
2744
3295
|
}
|
|
2745
3296
|
|
|
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
3297
|
/**
|
|
2762
3298
|
* Removes remote audio, video and share tracks from class instance's mediaProperties
|
|
2763
3299
|
* @returns {undefined}
|
|
@@ -2842,257 +3378,124 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2842
3378
|
}
|
|
2843
3379
|
|
|
2844
3380
|
/**
|
|
2845
|
-
*
|
|
2846
|
-
*
|
|
2847
|
-
*
|
|
2848
|
-
* @
|
|
3381
|
+
* Stores the reference to a new microphone track, sets up the required event listeners
|
|
3382
|
+
* on it, cleans up previous track, etc.
|
|
3383
|
+
*
|
|
3384
|
+
* @param {LocalMicrophoneTrack | null} localTrack local microphone track
|
|
3385
|
+
* @returns {Promise<void>}
|
|
2849
3386
|
*/
|
|
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
|
-
}
|
|
3387
|
+
private async setLocalAudioTrack(localTrack?: LocalMicrophoneTrack) {
|
|
3388
|
+
const oldTrack = this.mediaProperties.audioTrack;
|
|
2867
3389
|
|
|
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();
|
|
3390
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3391
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2879
3392
|
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
noiseSuppression: settings.noiseSuppression,
|
|
2883
|
-
});
|
|
3393
|
+
// we don't update this.mediaProperties.mediaDirection.sendAudio, because we always keep it as true to avoid extra SDP exchanges
|
|
3394
|
+
this.mediaProperties.setLocalAudioTrack(localTrack);
|
|
2884
3395
|
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
this.mediaProperties.setLocalAudioTrack(audioTrack);
|
|
2890
|
-
if (this.audio) this.audio.applyClientStateLocally(this);
|
|
2891
|
-
}
|
|
3396
|
+
this.audio.handleLocalTrackChange(this);
|
|
3397
|
+
|
|
3398
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3399
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2892
3400
|
|
|
2893
|
-
if (
|
|
2894
|
-
|
|
3401
|
+
if (!this.isMultistream || !localTrack) {
|
|
3402
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3403
|
+
await this.unpublishTrack(oldTrack);
|
|
2895
3404
|
}
|
|
3405
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
2896
3406
|
}
|
|
2897
3407
|
|
|
2898
3408
|
/**
|
|
2899
|
-
*
|
|
2900
|
-
*
|
|
2901
|
-
*
|
|
2902
|
-
* @
|
|
2903
|
-
* @
|
|
2904
|
-
* @memberof Meeting
|
|
3409
|
+
* Stores the reference to a new camera track, sets up the required event listeners
|
|
3410
|
+
* on it, cleans up previous track, etc.
|
|
3411
|
+
*
|
|
3412
|
+
* @param {LocalCameraTrack | null} localTrack local camera track
|
|
3413
|
+
* @returns {Promise<void>}
|
|
2905
3414
|
*/
|
|
2906
|
-
private setLocalVideoTrack(
|
|
2907
|
-
|
|
2908
|
-
const {aspectRatio, frameRate, height, width, deviceId} = videoTrack.getSettings();
|
|
3415
|
+
private async setLocalVideoTrack(localTrack?: LocalCameraTrack) {
|
|
3416
|
+
const oldTrack = this.mediaProperties.videoTrack;
|
|
2909
3417
|
|
|
2910
|
-
|
|
3418
|
+
oldTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3419
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2911
3420
|
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
.warn(`Meeting:index#setLocalVideoTrack --> Local video quality of ${localQualityLevel} not supported,
|
|
2915
|
-
downscaling to highest possible resolution of ${height}p`);
|
|
2916
|
-
|
|
2917
|
-
this.mediaProperties.setLocalQualityLevel(`${height}p`);
|
|
2918
|
-
}
|
|
3421
|
+
// we don't update this.mediaProperties.mediaDirection.sendVideo, because we always keep it as true to avoid extra SDP exchanges
|
|
3422
|
+
this.mediaProperties.setLocalVideoTrack(localTrack);
|
|
2919
3423
|
|
|
2920
|
-
|
|
2921
|
-
if (this.video) this.video.applyClientStateLocally(this);
|
|
3424
|
+
this.video.handleLocalTrackChange(this);
|
|
2922
3425
|
|
|
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
|
-
}
|
|
3426
|
+
localTrack?.on(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3427
|
+
localTrack?.on(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2938
3428
|
|
|
2939
|
-
if (
|
|
2940
|
-
|
|
3429
|
+
if (!this.isMultistream || !localTrack) {
|
|
3430
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3431
|
+
await this.unpublishTrack(oldTrack);
|
|
2941
3432
|
}
|
|
3433
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
2942
3434
|
}
|
|
2943
3435
|
|
|
2944
3436
|
/**
|
|
2945
|
-
*
|
|
2946
|
-
*
|
|
2947
|
-
*
|
|
2948
|
-
*
|
|
2949
|
-
* @
|
|
3437
|
+
* Stores the reference to a new screen share track, sets up the required event listeners
|
|
3438
|
+
* on it, cleans up previous track, etc.
|
|
3439
|
+
* It also sends the floor grant/release request.
|
|
3440
|
+
*
|
|
3441
|
+
* @param {LocalDisplayTrack | undefined} localDisplayTrack local camera track
|
|
3442
|
+
* @returns {Promise<void>}
|
|
2950
3443
|
*/
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
const {audioTrack, videoTrack} = MeetingUtil.getTrack(localStream);
|
|
3444
|
+
private async setLocalShareTrack(localDisplayTrack?: LocalDisplayTrack) {
|
|
3445
|
+
const oldTrack = this.mediaProperties.shareTrack;
|
|
2954
3446
|
|
|
2955
|
-
|
|
2956
|
-
|
|
3447
|
+
oldTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3448
|
+
oldTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
2957
3449
|
|
|
2958
|
-
|
|
2959
|
-
}
|
|
2960
|
-
}
|
|
3450
|
+
this.mediaProperties.setLocalShareTrack(localDisplayTrack);
|
|
2961
3451
|
|
|
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
|
-
}
|
|
3452
|
+
localDisplayTrack?.on(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3453
|
+
localDisplayTrack?.on(
|
|
3454
|
+
LocalTrackEvents.UnderlyingTrackChange,
|
|
3455
|
+
this.underlyingLocalTrackChangeHandler
|
|
3456
|
+
);
|
|
2991
3457
|
|
|
2992
|
-
|
|
3458
|
+
this.mediaProperties.mediaDirection.sendShare = !!localDisplayTrack;
|
|
2993
3459
|
|
|
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
|
-
);
|
|
3460
|
+
if (!this.isMultistream || !localDisplayTrack) {
|
|
3461
|
+
// for multistream WCME automatically un-publishes the old track when we publish a new one
|
|
3462
|
+
await this.unpublishTrack(oldTrack);
|
|
3006
3463
|
}
|
|
3464
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
3007
3465
|
}
|
|
3008
3466
|
|
|
3009
3467
|
/**
|
|
3010
|
-
*
|
|
3011
|
-
*
|
|
3012
|
-
*
|
|
3013
|
-
* @
|
|
3014
|
-
* @
|
|
3468
|
+
* Removes references to local tracks. This function should be called
|
|
3469
|
+
* on cleanup when we leave the meeting etc.
|
|
3470
|
+
*
|
|
3471
|
+
* @internal
|
|
3472
|
+
* @returns {void}
|
|
3015
3473
|
*/
|
|
3016
|
-
public
|
|
3017
|
-
const {audioTrack, videoTrack} = this.mediaProperties;
|
|
3474
|
+
public cleanupLocalTracks() {
|
|
3475
|
+
const {audioTrack, videoTrack, shareTrack} = this.mediaProperties;
|
|
3018
3476
|
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
.then(() => {
|
|
3022
|
-
const audioStopped = audioTrack && audioTrack.readyState === ENDED;
|
|
3023
|
-
const videoStopped = videoTrack && videoTrack.readyState === ENDED;
|
|
3477
|
+
audioTrack?.off(LocalTrackEvents.Muted, this.localAudioTrackMuteStateHandler);
|
|
3478
|
+
audioTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3024
3479
|
|
|
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
|
-
}
|
|
3480
|
+
videoTrack?.off(LocalTrackEvents.Muted, this.localVideoTrackMuteStateHandler);
|
|
3481
|
+
videoTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3045
3482
|
|
|
3046
|
-
|
|
3047
|
-
|
|
3048
|
-
* @returns {undefined}
|
|
3049
|
-
* @event media:stopped
|
|
3050
|
-
* @public
|
|
3051
|
-
* @memberof Meeting
|
|
3052
|
-
*/
|
|
3053
|
-
public closeLocalShare() {
|
|
3054
|
-
const track = this.mediaProperties.shareTrack;
|
|
3483
|
+
shareTrack?.off(LocalTrackEvents.Ended, this.handleShareTrackEnded);
|
|
3484
|
+
shareTrack?.off(LocalTrackEvents.UnderlyingTrackChange, this.underlyingLocalTrackChangeHandler);
|
|
3055
3485
|
|
|
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
|
-
}
|
|
3486
|
+
this.mediaProperties.setLocalAudioTrack(undefined);
|
|
3487
|
+
this.mediaProperties.setLocalVideoTrack(undefined);
|
|
3488
|
+
this.mediaProperties.setLocalShareTrack(undefined);
|
|
3077
3489
|
|
|
3078
|
-
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
* @public
|
|
3082
|
-
* @memberof Meeting
|
|
3083
|
-
*/
|
|
3084
|
-
public unsetLocalVideoTrack() {
|
|
3085
|
-
this.mediaProperties.unsetLocalVideoTrack();
|
|
3086
|
-
}
|
|
3490
|
+
this.mediaProperties.mediaDirection.sendAudio = false;
|
|
3491
|
+
this.mediaProperties.mediaDirection.sendVideo = false;
|
|
3492
|
+
this.mediaProperties.mediaDirection.sendShare = false;
|
|
3087
3493
|
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
*/
|
|
3094
|
-
public unsetLocalShareTrack() {
|
|
3095
|
-
this.mediaProperties.unsetLocalShareTrack();
|
|
3494
|
+
// WCME doesn't unpublish tracks when multistream connection is closed, so we do it here
|
|
3495
|
+
// (we have to do it for transcoded meetings anyway, so we might as well do for multistream too)
|
|
3496
|
+
audioTrack?.setPublished(false);
|
|
3497
|
+
videoTrack?.setPublished(false);
|
|
3498
|
+
shareTrack?.setPublished(false);
|
|
3096
3499
|
}
|
|
3097
3500
|
|
|
3098
3501
|
/**
|
|
@@ -3143,6 +3546,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3143
3546
|
* @memberof Meeting
|
|
3144
3547
|
*/
|
|
3145
3548
|
public closePeerConnections() {
|
|
3549
|
+
this.locusMediaRequest = undefined;
|
|
3550
|
+
|
|
3146
3551
|
if (this.mediaProperties.webrtcMediaConnection) {
|
|
3147
3552
|
if (this.remoteMediaManager) {
|
|
3148
3553
|
this.remoteMediaManager.stop();
|
|
@@ -3157,6 +3562,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3157
3562
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3158
3563
|
}
|
|
3159
3564
|
|
|
3565
|
+
this.audio = null;
|
|
3566
|
+
this.video = null;
|
|
3567
|
+
|
|
3160
3568
|
return Promise.resolve();
|
|
3161
3569
|
}
|
|
3162
3570
|
|
|
@@ -3187,264 +3595,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3187
3595
|
this.correlationId = id;
|
|
3188
3596
|
}
|
|
3189
3597
|
|
|
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
3598
|
/**
|
|
3401
3599
|
* Shorthand function to join AND set up media
|
|
3402
3600
|
* @param {Object} options - options to join with media
|
|
3403
3601
|
* @param {JoinOptions} [options.joinOptions] - see #join()
|
|
3404
|
-
* @param {MediaDirection} options.
|
|
3405
|
-
* @
|
|
3406
|
-
* @returns {Promise} -- {join: see join(), media: see addMedia(), local: see getMediaStreams()}
|
|
3602
|
+
* @param {MediaDirection} [options.mediaOptions] - see #addMedia()
|
|
3603
|
+
* @returns {Promise} -- {join: see join(), media: see addMedia()}
|
|
3407
3604
|
* @public
|
|
3408
3605
|
* @memberof Meeting
|
|
3409
3606
|
* @example
|
|
3410
3607
|
* joinWithMedia({
|
|
3411
3608
|
* 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
|
-
* }})
|
|
3609
|
+
* mediaOptions: {
|
|
3610
|
+
* localTracks: { microphone: microphoneTrack, camera: cameraTrack }
|
|
3611
|
+
* }
|
|
3612
|
+
* })
|
|
3424
3613
|
*/
|
|
3425
3614
|
public joinWithMedia(
|
|
3426
3615
|
options: {
|
|
3427
3616
|
joinOptions?: any;
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
} = {} as any
|
|
3617
|
+
mediaOptions?: AddMediaOptions;
|
|
3618
|
+
} = {}
|
|
3431
3619
|
) {
|
|
3432
|
-
|
|
3433
|
-
const {mediaSettings, joinOptions, audioVideoOptions} = options;
|
|
3620
|
+
const {mediaOptions, joinOptions} = options;
|
|
3434
3621
|
|
|
3435
3622
|
return this.join(joinOptions)
|
|
3436
3623
|
.then((joinResponse) =>
|
|
3437
|
-
this.
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
localStream,
|
|
3442
|
-
}).then((mediaResponse) => ({
|
|
3443
|
-
join: joinResponse,
|
|
3444
|
-
media: mediaResponse,
|
|
3445
|
-
local: [localStream, localShare],
|
|
3446
|
-
}))
|
|
3447
|
-
)
|
|
3624
|
+
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
3625
|
+
join: joinResponse,
|
|
3626
|
+
media: mediaResponse,
|
|
3627
|
+
}))
|
|
3448
3628
|
)
|
|
3449
3629
|
.catch((error) => {
|
|
3450
3630
|
LoggerProxy.logger.error('Meeting:index#joinWithMedia --> ', error);
|
|
@@ -3582,6 +3762,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3582
3762
|
return false;
|
|
3583
3763
|
}
|
|
3584
3764
|
|
|
3765
|
+
/**
|
|
3766
|
+
* Check if the meeting supports the Reactions
|
|
3767
|
+
* @returns {boolean}
|
|
3768
|
+
*/
|
|
3769
|
+
isReactionsSupported() {
|
|
3770
|
+
if (this.locusInfo?.controls?.reactions.enabled) {
|
|
3771
|
+
return true;
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3774
|
+
LoggerProxy.logger.error('Meeting:index#isReactionsSupported --> Reactions is not supported');
|
|
3775
|
+
|
|
3776
|
+
return false;
|
|
3777
|
+
}
|
|
3778
|
+
|
|
3585
3779
|
/**
|
|
3586
3780
|
* Monitor the Low-Latency Mercury (LLM) web socket connection on `onError` and `onClose` states
|
|
3587
3781
|
* @private
|
|
@@ -3633,6 +3827,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3633
3827
|
// @ts-ignore - fix type
|
|
3634
3828
|
const {
|
|
3635
3829
|
body: {webSocketUrl},
|
|
3830
|
+
// @ts-ignore
|
|
3636
3831
|
} = await this.request({
|
|
3637
3832
|
method: HTTP_VERBS.POST,
|
|
3638
3833
|
uri: datachannelUrl,
|
|
@@ -3682,6 +3877,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3682
3877
|
}
|
|
3683
3878
|
}
|
|
3684
3879
|
|
|
3880
|
+
/**
|
|
3881
|
+
* Callback called when a relay event is received from meeting LLM Connection
|
|
3882
|
+
* @param {RelayEvent} e Event object coming from LLM Connection
|
|
3883
|
+
* @private
|
|
3884
|
+
* @returns {void}
|
|
3885
|
+
*/
|
|
3886
|
+
private processRelayEvent = (e: RelayEvent): void => {
|
|
3887
|
+
switch (e.data.relayType) {
|
|
3888
|
+
case REACTION_RELAY_TYPES.REACTION:
|
|
3889
|
+
if (
|
|
3890
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3891
|
+
(this.config.receiveReactions || options.receiveReactions) &&
|
|
3892
|
+
this.isReactionsSupported()
|
|
3893
|
+
) {
|
|
3894
|
+
const {name} = this.members.membersCollection.get(e.data.sender.participantId);
|
|
3895
|
+
const processedReaction: ProcessedReaction = {
|
|
3896
|
+
reaction: e.data.reaction,
|
|
3897
|
+
sender: {
|
|
3898
|
+
id: e.data.sender.participantId,
|
|
3899
|
+
name,
|
|
3900
|
+
},
|
|
3901
|
+
};
|
|
3902
|
+
Trigger.trigger(
|
|
3903
|
+
this,
|
|
3904
|
+
{
|
|
3905
|
+
file: 'meeting/index',
|
|
3906
|
+
function: 'join',
|
|
3907
|
+
},
|
|
3908
|
+
EVENT_TRIGGERS.MEETING_RECEIVE_REACTIONS,
|
|
3909
|
+
processedReaction
|
|
3910
|
+
);
|
|
3911
|
+
}
|
|
3912
|
+
break;
|
|
3913
|
+
default:
|
|
3914
|
+
break;
|
|
3915
|
+
}
|
|
3916
|
+
};
|
|
3917
|
+
|
|
3685
3918
|
/**
|
|
3686
3919
|
* stop recieving Transcription by closing
|
|
3687
3920
|
* the web socket connection properly
|
|
@@ -3753,9 +3986,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3753
3986
|
joinSuccess = resolve;
|
|
3754
3987
|
});
|
|
3755
3988
|
|
|
3989
|
+
if (options.correlationId) {
|
|
3990
|
+
this.setCorrelationId(options.correlationId);
|
|
3991
|
+
LoggerProxy.logger.log(
|
|
3992
|
+
`Meeting:index#join --> Using a new correlation id from app ${this.correlationId}`
|
|
3993
|
+
);
|
|
3994
|
+
}
|
|
3995
|
+
|
|
3756
3996
|
if (!this.hasJoinedOnce) {
|
|
3757
3997
|
this.hasJoinedOnce = true;
|
|
3758
|
-
} else {
|
|
3998
|
+
} else if (!options.correlationId) {
|
|
3759
3999
|
LoggerProxy.logger.log(
|
|
3760
4000
|
`Meeting:index#join --> Generating a new correlation id for meeting ${this.id}`
|
|
3761
4001
|
);
|
|
@@ -3776,6 +4016,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3776
4016
|
data: {trigger: trigger.USER_INTERACTION, isRoapCallEnabled: true},
|
|
3777
4017
|
});
|
|
3778
4018
|
|
|
4019
|
+
if (!isEmpty(this.meetingInfo)) {
|
|
4020
|
+
Metrics.postEvent({
|
|
4021
|
+
event: eventType.MEETING_INFO_REQUEST,
|
|
4022
|
+
meeting: this,
|
|
4023
|
+
});
|
|
4024
|
+
|
|
4025
|
+
Metrics.postEvent({
|
|
4026
|
+
event: eventType.MEETING_INFO_RESPONSE,
|
|
4027
|
+
meeting: this,
|
|
4028
|
+
data: {
|
|
4029
|
+
meetingLookupUrl: this.meetingInfo?.meetingLookupUrl,
|
|
4030
|
+
},
|
|
4031
|
+
});
|
|
4032
|
+
}
|
|
4033
|
+
|
|
3779
4034
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
3780
4035
|
|
|
3781
4036
|
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
@@ -3804,18 +4059,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3804
4059
|
return Promise.reject(error);
|
|
3805
4060
|
}
|
|
3806
4061
|
|
|
3807
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality);
|
|
3808
4062
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality);
|
|
3809
4063
|
}
|
|
3810
4064
|
|
|
3811
4065
|
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`;
|
|
4066
|
+
if (!QUALITY_LEVELS[options.meetingQuality.remote]) {
|
|
4067
|
+
const errorMessage = `Meeting:index#join --> ${options.meetingQuality.remote} not defined`;
|
|
3819
4068
|
|
|
3820
4069
|
LoggerProxy.logger.error(errorMessage);
|
|
3821
4070
|
|
|
@@ -3827,9 +4076,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3827
4076
|
return Promise.reject(new Error(errorMessage));
|
|
3828
4077
|
}
|
|
3829
4078
|
|
|
3830
|
-
if (options.meetingQuality.local) {
|
|
3831
|
-
this.mediaProperties.setLocalQualityLevel(options.meetingQuality.local);
|
|
3832
|
-
}
|
|
3833
4079
|
if (options.meetingQuality.remote) {
|
|
3834
4080
|
this.mediaProperties.setRemoteQualityLevel(options.meetingQuality.remote);
|
|
3835
4081
|
}
|
|
@@ -3855,6 +4101,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3855
4101
|
return join;
|
|
3856
4102
|
})
|
|
3857
4103
|
.then(async (join) => {
|
|
4104
|
+
// @ts-ignore - config coming from registerPlugin
|
|
3858
4105
|
if (this.config.enableAutomaticLLM) {
|
|
3859
4106
|
await this.updateLLMConnection();
|
|
3860
4107
|
}
|
|
@@ -3923,22 +4170,41 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3923
4170
|
* @returns {Promise}
|
|
3924
4171
|
*/
|
|
3925
4172
|
async updateLLMConnection() {
|
|
4173
|
+
// @ts-ignore - Fix type
|
|
3926
4174
|
const {url, info: {datachannelUrl} = {}} = this.locusInfo;
|
|
3927
4175
|
|
|
3928
4176
|
const isJoined = this.joinedWith && this.joinedWith.state === 'JOINED';
|
|
3929
4177
|
|
|
4178
|
+
// @ts-ignore - Fix type
|
|
3930
4179
|
if (this.webex.internal.llm.isConnected()) {
|
|
4180
|
+
// @ts-ignore - Fix type
|
|
3931
4181
|
if (url === this.webex.internal.llm.getLocusUrl() && isJoined) {
|
|
3932
4182
|
return undefined;
|
|
3933
4183
|
}
|
|
4184
|
+
// @ts-ignore - Fix type
|
|
3934
4185
|
await this.webex.internal.llm.disconnectLLM();
|
|
4186
|
+
// @ts-ignore - Fix type
|
|
4187
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
3935
4188
|
}
|
|
3936
4189
|
|
|
3937
4190
|
if (!isJoined) {
|
|
3938
4191
|
return undefined;
|
|
3939
4192
|
}
|
|
3940
4193
|
|
|
3941
|
-
|
|
4194
|
+
// @ts-ignore - Fix type
|
|
4195
|
+
return this.webex.internal.llm
|
|
4196
|
+
.registerAndConnect(url, datachannelUrl)
|
|
4197
|
+
.then((registerAndConnectResult) => {
|
|
4198
|
+
// @ts-ignore - Fix type
|
|
4199
|
+
this.webex.internal.llm.off('event:relay.event', this.processRelayEvent);
|
|
4200
|
+
// @ts-ignore - Fix type
|
|
4201
|
+
this.webex.internal.llm.on('event:relay.event', this.processRelayEvent);
|
|
4202
|
+
LoggerProxy.logger.info(
|
|
4203
|
+
'Meeting:index#updateLLMConnection --> enabled to receive relay events!'
|
|
4204
|
+
);
|
|
4205
|
+
|
|
4206
|
+
return Promise.resolve(registerAndConnectResult);
|
|
4207
|
+
});
|
|
3942
4208
|
}
|
|
3943
4209
|
|
|
3944
4210
|
/**
|
|
@@ -3980,28 +4246,28 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3980
4246
|
|
|
3981
4247
|
if (!this.dialInUrl) this.dialInUrl = `dialin:///${uuid.v4()}`;
|
|
3982
4248
|
|
|
3983
|
-
return
|
|
3984
|
-
.
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
3990
|
-
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
|
|
3998
|
-
|
|
3999
|
-
|
|
4000
|
-
|
|
4001
|
-
});
|
|
4249
|
+
return (
|
|
4250
|
+
this.meetingRequest
|
|
4251
|
+
// @ts-ignore
|
|
4252
|
+
.dialIn({
|
|
4253
|
+
correlationId,
|
|
4254
|
+
dialInUrl: this.dialInUrl,
|
|
4255
|
+
locusUrl,
|
|
4256
|
+
clientUrl: this.deviceUrl,
|
|
4257
|
+
})
|
|
4258
|
+
.catch((error) => {
|
|
4259
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_IN_FAILURE, {
|
|
4260
|
+
correlation_id: this.correlationId,
|
|
4261
|
+
dial_in_url: this.dialInUrl,
|
|
4262
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4263
|
+
client_url: this.deviceUrl,
|
|
4264
|
+
reason: error.error?.message,
|
|
4265
|
+
stack: error.stack,
|
|
4266
|
+
});
|
|
4002
4267
|
|
|
4003
|
-
|
|
4004
|
-
|
|
4268
|
+
return Promise.reject(error);
|
|
4269
|
+
})
|
|
4270
|
+
);
|
|
4005
4271
|
}
|
|
4006
4272
|
|
|
4007
4273
|
/**
|
|
@@ -4018,29 +4284,29 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4018
4284
|
|
|
4019
4285
|
if (!this.dialOutUrl) this.dialOutUrl = `dialout:///${uuid.v4()}`;
|
|
4020
4286
|
|
|
4021
|
-
return
|
|
4022
|
-
.
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
});
|
|
4287
|
+
return (
|
|
4288
|
+
this.meetingRequest
|
|
4289
|
+
// @ts-ignore
|
|
4290
|
+
.dialOut({
|
|
4291
|
+
correlationId,
|
|
4292
|
+
dialOutUrl: this.dialOutUrl,
|
|
4293
|
+
phoneNumber,
|
|
4294
|
+
locusUrl,
|
|
4295
|
+
clientUrl: this.deviceUrl,
|
|
4296
|
+
})
|
|
4297
|
+
.catch((error) => {
|
|
4298
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_DIAL_OUT_FAILURE, {
|
|
4299
|
+
correlation_id: this.correlationId,
|
|
4300
|
+
dial_out_url: this.dialOutUrl,
|
|
4301
|
+
locus_id: locusUrl.split('/').pop(),
|
|
4302
|
+
client_url: this.deviceUrl,
|
|
4303
|
+
reason: error.error?.message,
|
|
4304
|
+
stack: error.stack,
|
|
4305
|
+
});
|
|
4041
4306
|
|
|
4042
|
-
|
|
4043
|
-
|
|
4307
|
+
return Promise.reject(error);
|
|
4308
|
+
})
|
|
4309
|
+
);
|
|
4044
4310
|
}
|
|
4045
4311
|
|
|
4046
4312
|
/**
|
|
@@ -4116,14 +4382,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4116
4382
|
},
|
|
4117
4383
|
};
|
|
4118
4384
|
|
|
4119
|
-
|
|
4120
|
-
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4121
|
-
|
|
4122
|
-
// close the existing local tracks
|
|
4123
|
-
await this.closeLocalStream();
|
|
4124
|
-
await this.closeLocalShare();
|
|
4385
|
+
this.cleanupLocalTracks();
|
|
4125
4386
|
|
|
4126
|
-
this.mediaProperties.
|
|
4387
|
+
this.mediaProperties.setMediaDirection(mediaSettings.mediaDirection);
|
|
4388
|
+
this.mediaProperties.unsetRemoteMedia();
|
|
4127
4389
|
|
|
4128
4390
|
// 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
4391
|
// once the DX answers we establish connection back the media server with only receiveShare enabled
|
|
@@ -4206,165 +4468,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4206
4468
|
});
|
|
4207
4469
|
}
|
|
4208
4470
|
|
|
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
4471
|
/**
|
|
4369
4472
|
* Handles ROAP_FAILURE event from the webrtc media connection
|
|
4370
4473
|
*
|
|
@@ -4387,7 +4490,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4387
4490
|
Metrics.sendBehavioralMetric(metricName, data, metadata);
|
|
4388
4491
|
};
|
|
4389
4492
|
|
|
4390
|
-
if (error instanceof
|
|
4493
|
+
if (error instanceof Errors.SdpOfferCreationError) {
|
|
4391
4494
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4392
4495
|
|
|
4393
4496
|
Metrics.postEvent({
|
|
@@ -4401,8 +4504,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4401
4504
|
},
|
|
4402
4505
|
});
|
|
4403
4506
|
} else if (
|
|
4404
|
-
error instanceof
|
|
4405
|
-
error instanceof
|
|
4507
|
+
error instanceof Errors.SdpOfferHandlingError ||
|
|
4508
|
+
error instanceof Errors.SdpAnswerHandlingError
|
|
4406
4509
|
) {
|
|
4407
4510
|
sendBehavioralMetric(BEHAVIORAL_METRICS.PEERCONNECTION_FAILURE, error, this.id);
|
|
4408
4511
|
|
|
@@ -4416,8 +4519,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4416
4519
|
],
|
|
4417
4520
|
},
|
|
4418
4521
|
});
|
|
4419
|
-
} else if (error instanceof
|
|
4420
|
-
// this covers also the case of
|
|
4522
|
+
} else if (error instanceof Errors.SdpError) {
|
|
4523
|
+
// this covers also the case of Errors.IceGatheringError which extends Errors.SdpError
|
|
4421
4524
|
sendBehavioralMetric(BEHAVIORAL_METRICS.INVALID_ICE_CANDIDATE, error, this.id);
|
|
4422
4525
|
|
|
4423
4526
|
Metrics.postEvent({
|
|
@@ -4434,20 +4537,20 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4434
4537
|
};
|
|
4435
4538
|
|
|
4436
4539
|
setupMediaConnectionListeners = () => {
|
|
4437
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4540
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_STARTED, () => {
|
|
4438
4541
|
this.isRoapInProgress = true;
|
|
4439
4542
|
});
|
|
4440
4543
|
|
|
4441
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4544
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
|
|
4442
4545
|
this.mediaNegotiatedEvent();
|
|
4443
4546
|
this.isRoapInProgress = false;
|
|
4444
4547
|
this.processNextQueuedMediaUpdate();
|
|
4445
4548
|
});
|
|
4446
4549
|
|
|
4447
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4550
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_FAILURE, this.handleRoapFailure);
|
|
4448
4551
|
|
|
4449
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4450
|
-
const LOG_HEADER =
|
|
4552
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, (event) => {
|
|
4553
|
+
const LOG_HEADER = `Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND --> correlationId=${this.correlationId}`;
|
|
4451
4554
|
|
|
4452
4555
|
switch (event.roapMessage.messageType) {
|
|
4453
4556
|
case 'OK':
|
|
@@ -4463,9 +4566,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4463
4566
|
correlationId: this.correlationId,
|
|
4464
4567
|
}),
|
|
4465
4568
|
{
|
|
4466
|
-
|
|
4467
|
-
success: `${LOG_HEADER} Successfully send roap OK`,
|
|
4468
|
-
failure: `${LOG_HEADER} Error joining the call on send roap OK, `,
|
|
4569
|
+
logText: `${LOG_HEADER} Roap OK`,
|
|
4469
4570
|
}
|
|
4470
4571
|
);
|
|
4471
4572
|
break;
|
|
@@ -4485,9 +4586,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4485
4586
|
reconnect: this.reconnectionManager.isReconnectInProgress(),
|
|
4486
4587
|
}),
|
|
4487
4588
|
{
|
|
4488
|
-
|
|
4489
|
-
success: `${LOG_HEADER} Successfully send roap offer`,
|
|
4490
|
-
failure: `${LOG_HEADER} Error joining the call on send roap offer, `,
|
|
4589
|
+
logText: `${LOG_HEADER} Roap Offer`,
|
|
4491
4590
|
}
|
|
4492
4591
|
);
|
|
4493
4592
|
break;
|
|
@@ -4506,9 +4605,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4506
4605
|
correlationId: this.correlationId,
|
|
4507
4606
|
}),
|
|
4508
4607
|
{
|
|
4509
|
-
|
|
4510
|
-
success: `${LOG_HEADER} Successfully send roap answer`,
|
|
4511
|
-
failure: `${LOG_HEADER} Error joining the call on send roap answer, `,
|
|
4608
|
+
logText: `${LOG_HEADER} Roap Answer`,
|
|
4512
4609
|
}
|
|
4513
4610
|
).catch((error) => {
|
|
4514
4611
|
const metricName = BEHAVIORAL_METRICS.ROAP_ANSWER_FAILURE;
|
|
@@ -4528,8 +4625,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4528
4625
|
|
|
4529
4626
|
case 'ERROR':
|
|
4530
4627
|
if (
|
|
4531
|
-
event.roapMessage.errorType ===
|
|
4532
|
-
event.roapMessage.errorType ===
|
|
4628
|
+
event.roapMessage.errorType === ErrorType.CONFLICT ||
|
|
4629
|
+
event.roapMessage.errorType === ErrorType.DOUBLECONFLICT
|
|
4533
4630
|
) {
|
|
4534
4631
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_GLARE_CONDITION, {
|
|
4535
4632
|
correlation_id: this.correlationId,
|
|
@@ -4545,9 +4642,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4545
4642
|
correlationId: this.correlationId,
|
|
4546
4643
|
}),
|
|
4547
4644
|
{
|
|
4548
|
-
|
|
4549
|
-
success: `${LOG_HEADER} Successfully send roap error`,
|
|
4550
|
-
failure: `${LOG_HEADER} Failed to send roap error, `,
|
|
4645
|
+
logText: `${LOG_HEADER} Roap Error (${event.roapMessage.errorType})`,
|
|
4551
4646
|
}
|
|
4552
4647
|
);
|
|
4553
4648
|
break;
|
|
@@ -4561,7 +4656,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4561
4656
|
});
|
|
4562
4657
|
|
|
4563
4658
|
// eslint-disable-next-line no-param-reassign
|
|
4564
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4659
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.REMOTE_TRACK_ADDED, (event) => {
|
|
4565
4660
|
LoggerProxy.logger.log(
|
|
4566
4661
|
`Meeting:index#setupMediaConnectionListeners --> REMOTE_TRACK_ADDED event received for webrtcMediaConnection: ${JSON.stringify(
|
|
4567
4662
|
event
|
|
@@ -4574,15 +4669,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4574
4669
|
let eventType;
|
|
4575
4670
|
|
|
4576
4671
|
switch (event.type) {
|
|
4577
|
-
case
|
|
4672
|
+
case RemoteTrackType.AUDIO:
|
|
4578
4673
|
eventType = EVENT_TYPES.REMOTE_AUDIO;
|
|
4579
4674
|
this.mediaProperties.setRemoteAudioTrack(event.track);
|
|
4580
4675
|
break;
|
|
4581
|
-
case
|
|
4676
|
+
case RemoteTrackType.VIDEO:
|
|
4582
4677
|
eventType = EVENT_TYPES.REMOTE_VIDEO;
|
|
4583
4678
|
this.mediaProperties.setRemoteVideoTrack(event.track);
|
|
4584
4679
|
break;
|
|
4585
|
-
case
|
|
4680
|
+
case RemoteTrackType.SCREENSHARE_VIDEO:
|
|
4586
4681
|
if (event.track) {
|
|
4587
4682
|
eventType = EVENT_TYPES.REMOTE_SHARE;
|
|
4588
4683
|
this.mediaProperties.setRemoteShare(event.track);
|
|
@@ -4595,10 +4690,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4595
4690
|
}
|
|
4596
4691
|
}
|
|
4597
4692
|
|
|
4598
|
-
// start stats here the stats are coming null if you dont receive streams
|
|
4599
|
-
|
|
4600
|
-
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4601
|
-
|
|
4602
4693
|
if (eventType && mediaTrack) {
|
|
4603
4694
|
Trigger.trigger(
|
|
4604
4695
|
this,
|
|
@@ -4615,7 +4706,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4615
4706
|
}
|
|
4616
4707
|
});
|
|
4617
4708
|
|
|
4618
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4709
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
4619
4710
|
const connectionFailed = () => {
|
|
4620
4711
|
// we know the media connection failed and browser will not attempt to recover it any more
|
|
4621
4712
|
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
@@ -4645,13 +4736,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4645
4736
|
};
|
|
4646
4737
|
|
|
4647
4738
|
LoggerProxy.logger.info(
|
|
4648
|
-
`Meeting:index#setupMediaConnectionListeners --> connection state changed to ${event.state}`
|
|
4739
|
+
`Meeting:index#setupMediaConnectionListeners --> correlationId=${this.correlationId} connection state changed to ${event.state}`
|
|
4649
4740
|
);
|
|
4650
4741
|
switch (event.state) {
|
|
4651
|
-
case
|
|
4742
|
+
case ConnectionState.Connecting:
|
|
4652
4743
|
Metrics.postEvent({event: eventType.ICE_START, meeting: this});
|
|
4653
4744
|
break;
|
|
4654
|
-
case
|
|
4745
|
+
case ConnectionState.Connected:
|
|
4655
4746
|
Metrics.postEvent({event: eventType.ICE_END, meeting: this});
|
|
4656
4747
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
4657
4748
|
correlation_id: this.correlationId,
|
|
@@ -4659,8 +4750,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4659
4750
|
});
|
|
4660
4751
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
4661
4752
|
this.reconnectionManager.iceReconnected();
|
|
4753
|
+
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
4662
4754
|
break;
|
|
4663
|
-
case
|
|
4755
|
+
case ConnectionState.Disconnected:
|
|
4664
4756
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
4665
4757
|
this.reconnectionManager.waitForIceReconnect().catch(() => {
|
|
4666
4758
|
LoggerProxy.logger.info(
|
|
@@ -4670,7 +4762,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4670
4762
|
connectionFailed();
|
|
4671
4763
|
});
|
|
4672
4764
|
break;
|
|
4673
|
-
case
|
|
4765
|
+
case ConnectionState.Failed:
|
|
4674
4766
|
connectionFailed();
|
|
4675
4767
|
break;
|
|
4676
4768
|
default:
|
|
@@ -4678,7 +4770,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4678
4770
|
}
|
|
4679
4771
|
});
|
|
4680
4772
|
|
|
4681
|
-
this.mediaProperties.webrtcMediaConnection.on(
|
|
4773
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ACTIVE_SPEAKERS_CHANGED, (msg) => {
|
|
4682
4774
|
Trigger.trigger(
|
|
4683
4775
|
this,
|
|
4684
4776
|
{
|
|
@@ -4689,6 +4781,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4689
4781
|
{
|
|
4690
4782
|
seqNum: msg.seqNum,
|
|
4691
4783
|
memberIds: msg.csis
|
|
4784
|
+
// @ts-ignore
|
|
4692
4785
|
.map((csi) => this.members.findMemberByCsi(csi)?.id)
|
|
4693
4786
|
.filter((item) => item !== undefined),
|
|
4694
4787
|
}
|
|
@@ -4696,8 +4789,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4696
4789
|
});
|
|
4697
4790
|
|
|
4698
4791
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4699
|
-
|
|
4700
|
-
(numTotalSources, numLiveSources) => {
|
|
4792
|
+
Event.VIDEO_SOURCES_COUNT_CHANGED,
|
|
4793
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4701
4794
|
Trigger.trigger(
|
|
4702
4795
|
this,
|
|
4703
4796
|
{
|
|
@@ -4708,14 +4801,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4708
4801
|
{
|
|
4709
4802
|
numTotalSources,
|
|
4710
4803
|
numLiveSources,
|
|
4804
|
+
mediaContent,
|
|
4711
4805
|
}
|
|
4712
4806
|
);
|
|
4713
4807
|
}
|
|
4714
4808
|
);
|
|
4715
4809
|
|
|
4716
4810
|
this.mediaProperties.webrtcMediaConnection.on(
|
|
4717
|
-
|
|
4718
|
-
(numTotalSources, numLiveSources) => {
|
|
4811
|
+
Event.AUDIO_SOURCES_COUNT_CHANGED,
|
|
4812
|
+
(numTotalSources, numLiveSources, mediaContent) => {
|
|
4719
4813
|
Trigger.trigger(
|
|
4720
4814
|
this,
|
|
4721
4815
|
{
|
|
@@ -4726,6 +4820,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4726
4820
|
{
|
|
4727
4821
|
numTotalSources,
|
|
4728
4822
|
numLiveSources,
|
|
4823
|
+
mediaContent,
|
|
4729
4824
|
}
|
|
4730
4825
|
);
|
|
4731
4826
|
}
|
|
@@ -4744,6 +4839,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4744
4839
|
// Add ip address info if geo hint is present
|
|
4745
4840
|
// @ts-ignore fix type
|
|
4746
4841
|
options.data.intervalMetadata.peerReflexiveIP =
|
|
4842
|
+
// @ts-ignore
|
|
4747
4843
|
this.webex.meetings.geoHintInfo?.clientAddress ||
|
|
4748
4844
|
options.data.intervalMetadata.peerReflexiveIP ||
|
|
4749
4845
|
MQA_STATS.DEFAULT_IP;
|
|
@@ -4813,7 +4909,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4813
4909
|
return `MC-${this.id.substring(0, 4)}`;
|
|
4814
4910
|
}
|
|
4815
4911
|
|
|
4816
|
-
|
|
4912
|
+
/**
|
|
4913
|
+
* Creates a webrtc media connection and publishes tracks to it
|
|
4914
|
+
*
|
|
4915
|
+
* @param {Object} turnServerInfo TURN server information
|
|
4916
|
+
* @param {BundlePolicy} [bundlePolicy] Bundle policy settings
|
|
4917
|
+
* @returns {RoapMediaConnection | MultistreamRoapMediaConnection}
|
|
4918
|
+
*/
|
|
4919
|
+
private async createMediaConnection(turnServerInfo, bundlePolicy?: BundlePolicy) {
|
|
4920
|
+
// create the actual media connection
|
|
4817
4921
|
const mc = Media.createMediaConnection(this.isMultistream, this.getMediaConnectionDebugId(), {
|
|
4818
4922
|
mediaProperties: this.mediaProperties,
|
|
4819
4923
|
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
@@ -4822,11 +4926,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4822
4926
|
// @ts-ignore - config coming from registerPlugin
|
|
4823
4927
|
enableExtmap: this.config.enableExtmap,
|
|
4824
4928
|
turnServerInfo,
|
|
4929
|
+
bundlePolicy,
|
|
4825
4930
|
});
|
|
4826
4931
|
|
|
4827
4932
|
this.mediaProperties.setMediaPeerConnection(mc);
|
|
4828
4933
|
this.setupMediaConnectionListeners();
|
|
4829
4934
|
|
|
4935
|
+
// publish the tracks
|
|
4936
|
+
if (this.mediaProperties.audioTrack) {
|
|
4937
|
+
await this.publishTrack(this.mediaProperties.audioTrack);
|
|
4938
|
+
}
|
|
4939
|
+
if (this.mediaProperties.videoTrack) {
|
|
4940
|
+
await this.publishTrack(this.mediaProperties.videoTrack);
|
|
4941
|
+
}
|
|
4942
|
+
if (this.mediaProperties.shareTrack) {
|
|
4943
|
+
await this.publishTrack(this.mediaProperties.shareTrack);
|
|
4944
|
+
}
|
|
4945
|
+
|
|
4830
4946
|
return mc;
|
|
4831
4947
|
}
|
|
4832
4948
|
|
|
@@ -4854,23 +4970,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4854
4970
|
}
|
|
4855
4971
|
|
|
4856
4972
|
/**
|
|
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
|
|
4973
|
+
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
4974
|
+
*
|
|
4975
|
+
* @param {AddMediaOptions} options
|
|
4864
4976
|
* @returns {Promise}
|
|
4865
4977
|
* @public
|
|
4866
4978
|
* @memberof Meeting
|
|
4867
4979
|
*/
|
|
4868
|
-
addMedia(options:
|
|
4980
|
+
addMedia(options: AddMediaOptions = {}) {
|
|
4869
4981
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
4870
4982
|
|
|
4871
4983
|
let turnDiscoverySkippedReason;
|
|
4872
4984
|
let turnServerUsed = false;
|
|
4873
4985
|
|
|
4986
|
+
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
4987
|
+
|
|
4874
4988
|
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
4875
4989
|
return Promise.reject(new MeetingNotActiveError());
|
|
4876
4990
|
}
|
|
@@ -4884,9 +4998,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4884
4998
|
return Promise.reject(new UserInLobbyError());
|
|
4885
4999
|
}
|
|
4886
5000
|
|
|
4887
|
-
const {
|
|
4888
|
-
|
|
4889
|
-
|
|
5001
|
+
const {
|
|
5002
|
+
localTracks,
|
|
5003
|
+
audioEnabled = true,
|
|
5004
|
+
videoEnabled = true,
|
|
5005
|
+
receiveShare = true,
|
|
5006
|
+
remoteMediaManagerConfig,
|
|
5007
|
+
bundlePolicy,
|
|
5008
|
+
} = options;
|
|
4890
5009
|
|
|
4891
5010
|
Metrics.postEvent({
|
|
4892
5011
|
event: eventType.MEDIA_CAPABILITIES,
|
|
@@ -4911,17 +5030,61 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4911
5030
|
},
|
|
4912
5031
|
});
|
|
4913
5032
|
|
|
4914
|
-
|
|
5033
|
+
// when audioEnabled/videoEnabled is true, we set sendAudio/sendVideo to true even before any tracks are published
|
|
5034
|
+
// to avoid doing an extra SDP exchange when they are published for the first time
|
|
5035
|
+
this.mediaProperties.setMediaDirection({
|
|
5036
|
+
sendAudio: audioEnabled,
|
|
5037
|
+
sendVideo: videoEnabled,
|
|
5038
|
+
sendShare: false,
|
|
5039
|
+
receiveAudio: audioEnabled,
|
|
5040
|
+
receiveVideo: videoEnabled,
|
|
5041
|
+
receiveShare,
|
|
5042
|
+
});
|
|
5043
|
+
|
|
5044
|
+
this.locusMediaRequest = new LocusMediaRequest(
|
|
5045
|
+
{
|
|
5046
|
+
correlationId: this.correlationId,
|
|
5047
|
+
device: {
|
|
5048
|
+
url: this.deviceUrl,
|
|
5049
|
+
// @ts-ignore
|
|
5050
|
+
deviceType: this.config.deviceType,
|
|
5051
|
+
},
|
|
5052
|
+
preferTranscoding: !this.isMultistream,
|
|
5053
|
+
},
|
|
5054
|
+
{
|
|
5055
|
+
// @ts-ignore
|
|
5056
|
+
parent: this.webex,
|
|
5057
|
+
}
|
|
5058
|
+
);
|
|
5059
|
+
|
|
5060
|
+
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5061
|
+
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5062
|
+
|
|
5063
|
+
this.annotationInfo = localTracks?.annotationInfo;
|
|
5064
|
+
|
|
5065
|
+
const promises = [];
|
|
5066
|
+
|
|
5067
|
+
// setup all the references to local tracks in this.mediaProperties before creating media connection
|
|
5068
|
+
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5069
|
+
if (localTracks?.microphone) {
|
|
5070
|
+
promises.push(this.setLocalAudioTrack(localTracks.microphone));
|
|
5071
|
+
}
|
|
5072
|
+
if (localTracks?.camera) {
|
|
5073
|
+
promises.push(this.setLocalVideoTrack(localTracks.camera));
|
|
5074
|
+
}
|
|
5075
|
+
if (localTracks?.screenShare?.video) {
|
|
5076
|
+
promises.push(this.setLocalShareTrack(localTracks.screenShare.video));
|
|
5077
|
+
}
|
|
5078
|
+
|
|
5079
|
+
return Promise.all(promises)
|
|
4915
5080
|
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
4916
|
-
.then((turnDiscoveryObject) => {
|
|
5081
|
+
.then(async (turnDiscoveryObject) => {
|
|
4917
5082
|
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
4918
5083
|
turnServerUsed = !turnDiscoverySkippedReason;
|
|
4919
5084
|
|
|
4920
5085
|
const {turnServerInfo} = turnDiscoveryObject;
|
|
4921
5086
|
|
|
4922
|
-
this.
|
|
4923
|
-
|
|
4924
|
-
const mc = this.createMediaConnection(turnServerInfo);
|
|
5087
|
+
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
4925
5088
|
|
|
4926
5089
|
if (this.isMultistream) {
|
|
4927
5090
|
this.remoteMediaManager = new RemoteMediaManager(
|
|
@@ -4946,16 +5109,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4946
5109
|
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
4947
5110
|
);
|
|
4948
5111
|
|
|
4949
|
-
|
|
5112
|
+
await this.remoteMediaManager.start();
|
|
4950
5113
|
}
|
|
4951
5114
|
|
|
4952
|
-
|
|
5115
|
+
await mc.initiateOffer();
|
|
4953
5116
|
})
|
|
4954
5117
|
.then(() => {
|
|
4955
5118
|
this.setMercuryListener();
|
|
4956
5119
|
})
|
|
4957
5120
|
.then(() =>
|
|
4958
|
-
|
|
5121
|
+
getDevices().then((devices) => {
|
|
4959
5122
|
MeetingUtil.handleDeviceLogging(devices);
|
|
4960
5123
|
})
|
|
4961
5124
|
)
|
|
@@ -4967,8 +5130,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4967
5130
|
if (this.config.stats.enableStatsAnalyzer) {
|
|
4968
5131
|
// @ts-ignore - config coming from registerPlugin
|
|
4969
5132
|
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
4970
|
-
|
|
4971
|
-
|
|
5133
|
+
this.statsAnalyzer = new StatsAnalyzer(
|
|
5134
|
+
// @ts-ignore - config coming from registerPlugin
|
|
5135
|
+
this.config.stats,
|
|
5136
|
+
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5137
|
+
this.networkQualityMonitor
|
|
5138
|
+
);
|
|
4972
5139
|
this.setupStatsAnalyzerEventHandlers();
|
|
4973
5140
|
this.networkQualityMonitor.on(
|
|
4974
5141
|
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
@@ -4991,7 +5158,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4991
5158
|
|
|
4992
5159
|
// eslint-disable-next-line func-names
|
|
4993
5160
|
// eslint-disable-next-line prefer-arrow-callback
|
|
4994
|
-
if (this.type === _CALL_) {
|
|
5161
|
+
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
4995
5162
|
resolve();
|
|
4996
5163
|
}
|
|
4997
5164
|
const joiningTimer = setInterval(() => {
|
|
@@ -5010,21 +5177,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5010
5177
|
)
|
|
5011
5178
|
.then(() =>
|
|
5012
5179
|
this.mediaProperties.waitForMediaConnectionConnected().catch(() => {
|
|
5013
|
-
throw
|
|
5180
|
+
throw new Error(
|
|
5181
|
+
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5182
|
+
);
|
|
5014
5183
|
})
|
|
5015
5184
|
)
|
|
5016
5185
|
.then(() => {
|
|
5017
|
-
|
|
5018
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
// When the self state changes to JOINED then request the floor
|
|
5024
|
-
this.floorGrantPending = true;
|
|
5186
|
+
if (localTracks?.screenShare?.video) {
|
|
5187
|
+
this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
5188
|
+
lambda: async () => {
|
|
5189
|
+
return this.requestScreenShareFloor();
|
|
5190
|
+
},
|
|
5191
|
+
});
|
|
5025
5192
|
}
|
|
5026
|
-
|
|
5027
|
-
return {};
|
|
5028
5193
|
})
|
|
5029
5194
|
.then(() => this.mediaProperties.getCurrentConnectionType())
|
|
5030
5195
|
.then((connectionType) => {
|
|
@@ -5032,9 +5197,36 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5032
5197
|
correlation_id: this.correlationId,
|
|
5033
5198
|
locus_id: this.locusUrl.split('/').pop(),
|
|
5034
5199
|
connectionType,
|
|
5200
|
+
isMultistream: this.isMultistream,
|
|
5035
5201
|
});
|
|
5036
5202
|
})
|
|
5037
5203
|
.catch((error) => {
|
|
5204
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5205
|
+
correlation_id: this.correlationId,
|
|
5206
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5207
|
+
reason: error.message,
|
|
5208
|
+
stack: error.stack,
|
|
5209
|
+
code: error.code,
|
|
5210
|
+
turnDiscoverySkippedReason,
|
|
5211
|
+
turnServerUsed,
|
|
5212
|
+
isMultistream: this.isMultistream,
|
|
5213
|
+
signalingState:
|
|
5214
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5215
|
+
?.signalingState ||
|
|
5216
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
5217
|
+
'unknown',
|
|
5218
|
+
connectionState:
|
|
5219
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5220
|
+
?.connectionState ||
|
|
5221
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
5222
|
+
'unknown',
|
|
5223
|
+
iceConnectionState:
|
|
5224
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5225
|
+
?.iceConnectionState ||
|
|
5226
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
5227
|
+
'unknown',
|
|
5228
|
+
});
|
|
5229
|
+
|
|
5038
5230
|
// Clean up stats analyzer, peer connection, and turn off listeners
|
|
5039
5231
|
const stopStatsAnalyzer = this.statsAnalyzer
|
|
5040
5232
|
? this.statsAnalyzer.stopAnalyzer()
|
|
@@ -5053,16 +5245,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5053
5245
|
error
|
|
5054
5246
|
);
|
|
5055
5247
|
|
|
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
5248
|
// Upload logs on error while adding media
|
|
5067
5249
|
Trigger.trigger(
|
|
5068
5250
|
this,
|
|
@@ -5074,7 +5256,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5074
5256
|
this
|
|
5075
5257
|
);
|
|
5076
5258
|
|
|
5077
|
-
if (error instanceof
|
|
5259
|
+
if (error instanceof Errors.SdpError) {
|
|
5078
5260
|
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
5079
5261
|
}
|
|
5080
5262
|
|
|
@@ -5102,7 +5284,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5102
5284
|
* @private
|
|
5103
5285
|
* @memberof Meeting
|
|
5104
5286
|
*/
|
|
5105
|
-
private enqueueMediaUpdate(mediaUpdateType: string, options:
|
|
5287
|
+
private enqueueMediaUpdate(mediaUpdateType: string, options: any): Promise<void> {
|
|
5288
|
+
if (mediaUpdateType === MEDIA_UPDATE_TYPE.LAMBDA && typeof options?.lambda !== 'function') {
|
|
5289
|
+
return Promise.reject(
|
|
5290
|
+
new Error('lambda must be specified when enqueuing MEDIA_UPDATE_TYPE.LAMBDA')
|
|
5291
|
+
);
|
|
5292
|
+
}
|
|
5293
|
+
const canUpdateMediaNow = this.canUpdateMedia();
|
|
5294
|
+
|
|
5106
5295
|
return new Promise((resolve, reject) => {
|
|
5107
5296
|
const queueItem = {
|
|
5108
5297
|
pendingPromiseResolve: resolve,
|
|
@@ -5115,6 +5304,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5115
5304
|
`Meeting:index#enqueueMediaUpdate --> enqueuing media update type=${mediaUpdateType}`
|
|
5116
5305
|
);
|
|
5117
5306
|
this.queuedMediaUpdates.push(queueItem);
|
|
5307
|
+
|
|
5308
|
+
if (canUpdateMediaNow) {
|
|
5309
|
+
this.processNextQueuedMediaUpdate();
|
|
5310
|
+
}
|
|
5118
5311
|
});
|
|
5119
5312
|
}
|
|
5120
5313
|
|
|
@@ -5154,18 +5347,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5154
5347
|
LoggerProxy.logger.log(
|
|
5155
5348
|
`Meeting:index#processNextQueuedMediaUpdate --> performing delayed media update type=${mediaUpdateType}`
|
|
5156
5349
|
);
|
|
5350
|
+
let mediaUpdate = Promise.resolve();
|
|
5351
|
+
|
|
5157
5352
|
switch (mediaUpdateType) {
|
|
5158
|
-
case MEDIA_UPDATE_TYPE.
|
|
5159
|
-
this.
|
|
5353
|
+
case MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION:
|
|
5354
|
+
mediaUpdate = this.updateTranscodedMediaConnection();
|
|
5160
5355
|
break;
|
|
5161
|
-
case MEDIA_UPDATE_TYPE.
|
|
5162
|
-
|
|
5356
|
+
case MEDIA_UPDATE_TYPE.LAMBDA:
|
|
5357
|
+
mediaUpdate = options.lambda();
|
|
5163
5358
|
break;
|
|
5164
|
-
case MEDIA_UPDATE_TYPE.
|
|
5165
|
-
this.
|
|
5166
|
-
break;
|
|
5167
|
-
case MEDIA_UPDATE_TYPE.SHARE:
|
|
5168
|
-
this.updateShare(options).then(pendingPromiseResolve, pendingPromiseReject);
|
|
5359
|
+
case MEDIA_UPDATE_TYPE.UPDATE_MEDIA:
|
|
5360
|
+
mediaUpdate = this.updateMedia(options);
|
|
5169
5361
|
break;
|
|
5170
5362
|
default:
|
|
5171
5363
|
LoggerProxy.logger.error(
|
|
@@ -5173,358 +5365,113 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5173
5365
|
);
|
|
5174
5366
|
break;
|
|
5175
5367
|
}
|
|
5368
|
+
|
|
5369
|
+
mediaUpdate
|
|
5370
|
+
.then(pendingPromiseResolve, pendingPromiseReject)
|
|
5371
|
+
.then(() => this.processNextQueuedMediaUpdate());
|
|
5176
5372
|
}
|
|
5177
5373
|
};
|
|
5178
5374
|
|
|
5179
5375
|
/**
|
|
5180
|
-
*
|
|
5181
|
-
*
|
|
5376
|
+
* Updates the media connection - it allows to enable/disable all audio/video/share in the meeting.
|
|
5377
|
+
* This does not affect the published tracks, so for example if a microphone track is published and
|
|
5378
|
+
* updateMedia({audioEnabled: false}) is called, the audio will not be sent or received anymore,
|
|
5379
|
+
* but the track's "published" state is not changed and when updateMedia({audioEnabled: true}) is called,
|
|
5380
|
+
* the sending of the audio from the same track will resume.
|
|
5381
|
+
*
|
|
5182
5382
|
* @param {Object} options
|
|
5183
|
-
* @param {
|
|
5184
|
-
* @param {
|
|
5185
|
-
* @param {
|
|
5383
|
+
* @param {boolean} options.audioEnabled [optional] enables/disables receiving and sending of main audio in the meeting
|
|
5384
|
+
* @param {boolean} options.videoEnabled [optional] enables/disables receiving and sending of main video in the meeting
|
|
5385
|
+
* @param {boolean} options.shareEnabled [optional] enables/disables receiving and sending of screen share in the meeting
|
|
5186
5386
|
* @returns {Promise}
|
|
5187
5387
|
* @public
|
|
5188
5388
|
* @memberof Meeting
|
|
5189
5389
|
*/
|
|
5190
|
-
public updateMedia(
|
|
5191
|
-
|
|
5192
|
-
|
|
5193
|
-
|
|
5194
|
-
|
|
5195
|
-
|
|
5196
|
-
) {
|
|
5197
|
-
const LOG_HEADER = 'Meeting:index#updateMedia -->';
|
|
5390
|
+
public async updateMedia(options: {
|
|
5391
|
+
audioEnabled?: boolean;
|
|
5392
|
+
videoEnabled?: boolean;
|
|
5393
|
+
receiveShare?: boolean;
|
|
5394
|
+
}) {
|
|
5395
|
+
this.checkMediaConnection();
|
|
5198
5396
|
|
|
5199
|
-
|
|
5200
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.ALL, options);
|
|
5201
|
-
}
|
|
5202
|
-
const {localStream, localShare, mediaSettings} = options;
|
|
5397
|
+
const {audioEnabled, videoEnabled, receiveShare} = options;
|
|
5203
5398
|
|
|
5204
|
-
|
|
5399
|
+
LoggerProxy.logger.log(
|
|
5400
|
+
`Meeting:index#updateMedia --> called with options=${JSON.stringify(options)}`
|
|
5401
|
+
);
|
|
5205
5402
|
|
|
5206
|
-
if (!this.
|
|
5207
|
-
return
|
|
5403
|
+
if (!this.canUpdateMedia()) {
|
|
5404
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
5208
5405
|
}
|
|
5209
5406
|
|
|
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
|
-
}
|
|
5407
|
+
if (this.isMultistream) {
|
|
5408
|
+
if (videoEnabled !== undefined) {
|
|
5409
|
+
throw new Error(
|
|
5410
|
+
'enabling/disabling video in a meeting is not supported for multistream, it can only be done upfront when calling addMedia()'
|
|
5411
|
+
);
|
|
5412
|
+
}
|
|
5267
5413
|
|
|
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);
|
|
5414
|
+
if (receiveShare !== undefined) {
|
|
5415
|
+
throw new Error(
|
|
5416
|
+
'toggling receiveShare in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
5417
|
+
);
|
|
5418
|
+
}
|
|
5288
5419
|
}
|
|
5289
|
-
const {sendAudio, receiveAudio, stream} = options;
|
|
5290
|
-
let track = MeetingUtil.getTrack(stream).audioTrack;
|
|
5291
5420
|
|
|
5292
|
-
if (
|
|
5293
|
-
|
|
5421
|
+
if (audioEnabled !== undefined) {
|
|
5422
|
+
this.mediaProperties.mediaDirection.sendAudio = audioEnabled;
|
|
5423
|
+
this.mediaProperties.mediaDirection.receiveAudio = audioEnabled;
|
|
5424
|
+
this.audio.enable(this, audioEnabled);
|
|
5294
5425
|
}
|
|
5295
5426
|
|
|
5296
|
-
if (
|
|
5297
|
-
|
|
5427
|
+
if (videoEnabled !== undefined) {
|
|
5428
|
+
this.mediaProperties.mediaDirection.sendVideo = videoEnabled;
|
|
5429
|
+
this.mediaProperties.mediaDirection.receiveVideo = videoEnabled;
|
|
5430
|
+
this.video.enable(this, videoEnabled);
|
|
5298
5431
|
}
|
|
5299
5432
|
|
|
5300
|
-
if (
|
|
5301
|
-
|
|
5433
|
+
if (receiveShare !== undefined) {
|
|
5434
|
+
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
5435
|
+
}
|
|
5302
5436
|
|
|
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');
|
|
5437
|
+
if (this.isMultistream) {
|
|
5438
|
+
if (audioEnabled !== undefined) {
|
|
5439
|
+
await this.mediaProperties.webrtcMediaConnection.enableMultistreamAudio(audioEnabled);
|
|
5311
5440
|
}
|
|
5441
|
+
} else {
|
|
5442
|
+
await this.updateTranscodedMediaConnection();
|
|
5312
5443
|
}
|
|
5313
5444
|
|
|
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
|
-
});
|
|
5445
|
+
return undefined;
|
|
5336
5446
|
}
|
|
5337
5447
|
|
|
5338
5448
|
/**
|
|
5339
|
-
*
|
|
5340
|
-
*
|
|
5341
|
-
*
|
|
5342
|
-
*
|
|
5343
|
-
* @param {Object} options
|
|
5344
|
-
* @param {boolean} options.sendVideo
|
|
5345
|
-
* @param {boolean} options.receiveVideo
|
|
5346
|
-
* @param {MediaStream} options.stream Stream that contains the video track to update
|
|
5347
|
-
* @returns {Promise}
|
|
5449
|
+
* Acknowledge the meeting, outgoing or incoming
|
|
5450
|
+
* @param {String} type
|
|
5451
|
+
* @returns {Promise} resolve {message, ringing, response}
|
|
5348
5452
|
* @public
|
|
5349
5453
|
* @memberof Meeting
|
|
5350
5454
|
*/
|
|
5351
|
-
public
|
|
5352
|
-
if (!
|
|
5353
|
-
return
|
|
5354
|
-
}
|
|
5355
|
-
const {sendVideo, receiveVideo, stream} = options;
|
|
5356
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
5357
|
-
|
|
5358
|
-
if (typeof sendVideo !== 'boolean' || typeof receiveVideo !== 'boolean') {
|
|
5359
|
-
return Promise.reject(new ParameterError('Pass sendVideo and receiveVideo parameter'));
|
|
5455
|
+
public acknowledge(type: string) {
|
|
5456
|
+
if (!type) {
|
|
5457
|
+
return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
|
|
5360
5458
|
}
|
|
5459
|
+
if (type === _INCOMING_) {
|
|
5460
|
+
return this.meetingRequest
|
|
5461
|
+
.acknowledgeMeeting({
|
|
5462
|
+
locusUrl: this.locusUrl,
|
|
5463
|
+
deviceUrl: this.deviceUrl,
|
|
5464
|
+
correlationId: this.correlationId,
|
|
5465
|
+
})
|
|
5466
|
+
.then((response) => Promise.resolve(response))
|
|
5467
|
+
.then((response) => {
|
|
5468
|
+
this.meetingFiniteStateMachine.ring(type);
|
|
5469
|
+
Metrics.postEvent({event: eventType.ALERT_DISPLAYED, meeting: this});
|
|
5361
5470
|
|
|
5362
|
-
|
|
5363
|
-
|
|
5364
|
-
|
|
5365
|
-
|
|
5366
|
-
return MeetingUtil.validateOptions({sendVideo, localStream: stream})
|
|
5367
|
-
.then(() =>
|
|
5368
|
-
this.mediaProperties.webrtcMediaConnection.updateSendReceiveOptions({
|
|
5369
|
-
send: {video: track},
|
|
5370
|
-
receive: {
|
|
5371
|
-
audio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5372
|
-
video: options.receiveVideo,
|
|
5373
|
-
screenShareVideo: this.mediaProperties.mediaDirection.receiveShare,
|
|
5374
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5375
|
-
},
|
|
5376
|
-
})
|
|
5377
|
-
)
|
|
5378
|
-
.then(() => {
|
|
5379
|
-
this.setLocalVideoTrack(track);
|
|
5380
|
-
this.mediaProperties.mediaDirection.sendVideo = sendVideo;
|
|
5381
|
-
this.mediaProperties.mediaDirection.receiveVideo = receiveVideo;
|
|
5382
|
-
|
|
5383
|
-
// video state could be undefined if you have not sent video before
|
|
5384
|
-
this.video =
|
|
5385
|
-
this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
|
|
5386
|
-
});
|
|
5387
|
-
}
|
|
5388
|
-
|
|
5389
|
-
/**
|
|
5390
|
-
* Internal function when stopping a share stream, cleanup
|
|
5391
|
-
* @param {boolean} sendShare
|
|
5392
|
-
* @param {boolean} previousShareStatus
|
|
5393
|
-
* @returns {Promise}
|
|
5394
|
-
* @private
|
|
5395
|
-
* @memberof Meeting
|
|
5396
|
-
*/
|
|
5397
|
-
private checkForStopShare(sendShare: boolean, previousShareStatus: boolean) {
|
|
5398
|
-
if (sendShare && !previousShareStatus) {
|
|
5399
|
-
// When user starts sharing
|
|
5400
|
-
return Promise.resolve(true);
|
|
5401
|
-
}
|
|
5402
|
-
|
|
5403
|
-
if (!sendShare && previousShareStatus) {
|
|
5404
|
-
// When user stops sharing
|
|
5405
|
-
return this.releaseScreenShareFloor().then(() => Promise.resolve(false));
|
|
5406
|
-
}
|
|
5407
|
-
|
|
5408
|
-
return Promise.resolve();
|
|
5409
|
-
}
|
|
5410
|
-
|
|
5411
|
-
/**
|
|
5412
|
-
* Update the share streams, can be used to start sharing
|
|
5413
|
-
*
|
|
5414
|
-
* NOTE: this method can only be used with transcoded meetings, for multistream meetings use publishTrack()
|
|
5415
|
-
*
|
|
5416
|
-
* @param {Object} options
|
|
5417
|
-
* @param {boolean} options.sendShare
|
|
5418
|
-
* @param {boolean} options.receiveShare
|
|
5419
|
-
* @returns {Promise}
|
|
5420
|
-
* @public
|
|
5421
|
-
* @memberof Meeting
|
|
5422
|
-
*/
|
|
5423
|
-
public updateShare(options: {
|
|
5424
|
-
sendShare?: boolean;
|
|
5425
|
-
receiveShare?: boolean;
|
|
5426
|
-
stream?: any;
|
|
5427
|
-
skipSignalingCheck?: boolean;
|
|
5428
|
-
}) {
|
|
5429
|
-
if (!options.skipSignalingCheck && !this.canUpdateMedia()) {
|
|
5430
|
-
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.SHARE, options);
|
|
5431
|
-
}
|
|
5432
|
-
const {sendShare, receiveShare, stream} = options;
|
|
5433
|
-
const track = MeetingUtil.getTrack(stream).videoTrack;
|
|
5434
|
-
|
|
5435
|
-
if (typeof sendShare !== 'boolean' || typeof receiveShare !== 'boolean') {
|
|
5436
|
-
return Promise.reject(new ParameterError('Pass sendShare and receiveShare parameter'));
|
|
5437
|
-
}
|
|
5438
|
-
|
|
5439
|
-
if (!this.mediaProperties.webrtcMediaConnection) {
|
|
5440
|
-
return Promise.reject(new Error('media connection not established, call addMedia() first'));
|
|
5441
|
-
}
|
|
5442
|
-
|
|
5443
|
-
const previousSendShareStatus = this.mediaProperties.mediaDirection.sendShare;
|
|
5444
|
-
|
|
5445
|
-
this.setLocalShareTrack(stream);
|
|
5446
|
-
|
|
5447
|
-
return MeetingUtil.validateOptions({sendShare, localShare: stream})
|
|
5448
|
-
.then(() => this.checkForStopShare(sendShare, previousSendShareStatus))
|
|
5449
|
-
.then((startShare) =>
|
|
5450
|
-
this.mediaProperties.webrtcMediaConnection
|
|
5451
|
-
.updateSendReceiveOptions({
|
|
5452
|
-
send: {screenShareVideo: track},
|
|
5453
|
-
receive: {
|
|
5454
|
-
audio: this.mediaProperties.mediaDirection.receiveAudio,
|
|
5455
|
-
video: this.mediaProperties.mediaDirection.receiveVideo,
|
|
5456
|
-
screenShareVideo: options.receiveShare,
|
|
5457
|
-
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
5458
|
-
},
|
|
5459
|
-
})
|
|
5460
|
-
.then(() => {
|
|
5461
|
-
if (startShare) {
|
|
5462
|
-
return this.requestScreenShareFloor();
|
|
5463
|
-
}
|
|
5464
|
-
|
|
5465
|
-
return Promise.resolve();
|
|
5466
|
-
})
|
|
5467
|
-
)
|
|
5468
|
-
.then(() => {
|
|
5469
|
-
this.mediaProperties.mediaDirection.sendShare = sendShare;
|
|
5470
|
-
this.mediaProperties.mediaDirection.receiveShare = receiveShare;
|
|
5471
|
-
})
|
|
5472
|
-
.catch((error) => {
|
|
5473
|
-
this.unsetLocalShareTrack();
|
|
5474
|
-
throw error;
|
|
5475
|
-
});
|
|
5476
|
-
}
|
|
5477
|
-
|
|
5478
|
-
/**
|
|
5479
|
-
* Do all the attach media pre set up before executing the actual attach
|
|
5480
|
-
* @param {MediaStream} localStream
|
|
5481
|
-
* @param {MediaStream} localShare
|
|
5482
|
-
* @param {MediaDirection} mediaSettings
|
|
5483
|
-
* @returns {undefined}
|
|
5484
|
-
* @private
|
|
5485
|
-
* @memberof Meeting
|
|
5486
|
-
*/
|
|
5487
|
-
private preMedia(localStream: MediaStream, localShare: MediaStream, mediaSettings: any) {
|
|
5488
|
-
// eslint-disable-next-line no-warning-comments
|
|
5489
|
-
// TODO wire into default config. There's currently an issue with the stateless plugin or how we register
|
|
5490
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5491
|
-
this.mediaProperties.setMediaDirection(Object.assign(this.config.mediaSettings, mediaSettings));
|
|
5492
|
-
// add a setup a function move the create and setup media in future
|
|
5493
|
-
// TODO: delete old audio and video if stale
|
|
5494
|
-
this.audio = this.audio || createMuteState(AUDIO, this, this.mediaProperties.mediaDirection);
|
|
5495
|
-
this.video = this.video || createMuteState(VIDEO, this, this.mediaProperties.mediaDirection);
|
|
5496
|
-
// Validation is already done in addMedia so no need to check if the lenght is greater then 0
|
|
5497
|
-
this.setLocalTracks(localStream);
|
|
5498
|
-
this.setLocalShareTrack(localShare);
|
|
5499
|
-
}
|
|
5500
|
-
|
|
5501
|
-
/**
|
|
5502
|
-
* Acknowledge the meeting, outgoing or incoming
|
|
5503
|
-
* @param {String} type
|
|
5504
|
-
* @returns {Promise} resolve {message, ringing, response}
|
|
5505
|
-
* @public
|
|
5506
|
-
* @memberof Meeting
|
|
5507
|
-
*/
|
|
5508
|
-
public acknowledge(type: string) {
|
|
5509
|
-
if (!type) {
|
|
5510
|
-
return Promise.reject(new ParameterError('Type must be set to acknowledge the meeting.'));
|
|
5511
|
-
}
|
|
5512
|
-
if (type === _INCOMING_) {
|
|
5513
|
-
return this.meetingRequest
|
|
5514
|
-
.acknowledgeMeeting({
|
|
5515
|
-
locusUrl: this.locusUrl,
|
|
5516
|
-
deviceUrl: this.deviceUrl,
|
|
5517
|
-
correlationId: this.correlationId,
|
|
5518
|
-
})
|
|
5519
|
-
.then((response) => Promise.resolve(response))
|
|
5520
|
-
.then((response) => {
|
|
5521
|
-
this.meetingFiniteStateMachine.ring(type);
|
|
5522
|
-
Metrics.postEvent({event: eventType.ALERT_DISPLAYED, meeting: this});
|
|
5523
|
-
|
|
5524
|
-
return Promise.resolve({
|
|
5525
|
-
response,
|
|
5526
|
-
});
|
|
5527
|
-
});
|
|
5471
|
+
return Promise.resolve({
|
|
5472
|
+
response,
|
|
5473
|
+
});
|
|
5474
|
+
});
|
|
5528
5475
|
}
|
|
5529
5476
|
|
|
5530
5477
|
// TODO: outside of 1:1 incoming, and all outgoing calls
|
|
@@ -5563,13 +5510,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5563
5510
|
* @memberof Meeting
|
|
5564
5511
|
*/
|
|
5565
5512
|
public leave(options: {resourceId?: string; reason?: any} = {} as any) {
|
|
5513
|
+
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5566
5514
|
Metrics.postEvent({
|
|
5567
5515
|
event: eventType.LEAVE,
|
|
5568
5516
|
meeting: this,
|
|
5569
|
-
data: {trigger: trigger.USER_INTERACTION, canProceed: false},
|
|
5517
|
+
data: {trigger: trigger.USER_INTERACTION, canProceed: false, reason: leaveReason},
|
|
5570
5518
|
});
|
|
5571
|
-
const leaveReason = options.reason || MEETING_REMOVED_REASON.CLIENT_LEAVE_REQUEST;
|
|
5572
|
-
|
|
5573
5519
|
LoggerProxy.logger.log('Meeting:index#leave --> Leaving a meeting');
|
|
5574
5520
|
|
|
5575
5521
|
return MeetingUtil.leaveMeeting(this, options)
|
|
@@ -5738,55 +5684,68 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5738
5684
|
* @memberof Meeting
|
|
5739
5685
|
*/
|
|
5740
5686
|
private requestScreenShareFloor() {
|
|
5741
|
-
|
|
5687
|
+
if (!this.mediaProperties.shareTrack || !this.mediaProperties.mediaDirection.sendShare) {
|
|
5688
|
+
LoggerProxy.logger.log(
|
|
5689
|
+
`Meeting:index#requestScreenShareFloor --> NOT requesting floor, because we don't have the share track anymore (shareTrack=${
|
|
5690
|
+
this.mediaProperties.shareTrack ? 'yes' : 'no'
|
|
5691
|
+
}, sendShare=${this.mediaProperties.mediaDirection.sendShare})`
|
|
5692
|
+
);
|
|
5742
5693
|
|
|
5743
|
-
|
|
5744
|
-
|
|
5694
|
+
return Promise.resolve({});
|
|
5695
|
+
}
|
|
5696
|
+
if (this.state === MEETING_STATE.STATES.JOINED) {
|
|
5697
|
+
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5698
|
+
|
|
5699
|
+
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
5700
|
+
Metrics.postEvent({event: eventType.SHARE_INITIATED, meeting: this});
|
|
5701
|
+
|
|
5702
|
+
return this.meetingRequest
|
|
5703
|
+
.changeMeetingFloor({
|
|
5704
|
+
disposition: FLOOR_ACTION.GRANTED,
|
|
5705
|
+
personUrl: this.locusInfo.self.url,
|
|
5706
|
+
deviceUrl: this.deviceUrl,
|
|
5707
|
+
uri: content.url,
|
|
5708
|
+
resourceUrl: this.resourceUrl,
|
|
5709
|
+
annotationInfo: this.annotationInfo,
|
|
5710
|
+
})
|
|
5711
|
+
.then(() => {
|
|
5712
|
+
this.isSharing = true;
|
|
5745
5713
|
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
deviceUrl: this.deviceUrl,
|
|
5751
|
-
uri: content.url,
|
|
5752
|
-
resourceUrl: this.resourceUrl,
|
|
5753
|
-
})
|
|
5754
|
-
.then(() => {
|
|
5755
|
-
this.isSharing = true;
|
|
5714
|
+
return Promise.resolve();
|
|
5715
|
+
})
|
|
5716
|
+
.catch((error) => {
|
|
5717
|
+
LoggerProxy.logger.error('Meeting:index#share --> Error ', error);
|
|
5756
5718
|
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5719
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_FAILURE, {
|
|
5720
|
+
correlation_id: this.correlationId,
|
|
5721
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
5722
|
+
reason: error.message,
|
|
5723
|
+
stack: error.stack,
|
|
5724
|
+
});
|
|
5761
5725
|
|
|
5762
|
-
|
|
5763
|
-
correlation_id: this.correlationId,
|
|
5764
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5765
|
-
reason: error.message,
|
|
5766
|
-
stack: error.stack,
|
|
5726
|
+
return Promise.reject(error);
|
|
5767
5727
|
});
|
|
5728
|
+
}
|
|
5768
5729
|
|
|
5769
|
-
|
|
5770
|
-
});
|
|
5730
|
+
return Promise.reject(new ParameterError('Cannot share without content.'));
|
|
5771
5731
|
}
|
|
5732
|
+
this.floorGrantPending = true;
|
|
5772
5733
|
|
|
5773
|
-
return Promise.
|
|
5734
|
+
return Promise.resolve({});
|
|
5774
5735
|
}
|
|
5775
5736
|
|
|
5776
5737
|
/**
|
|
5777
|
-
*
|
|
5778
|
-
*
|
|
5779
|
-
*
|
|
5780
|
-
* @
|
|
5738
|
+
* Requests screen share floor if such request is pending.
|
|
5739
|
+
* It should be called whenever meeting state changes to JOINED
|
|
5740
|
+
*
|
|
5741
|
+
* @returns {void}
|
|
5781
5742
|
*/
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
5785
|
-
|
|
5786
|
-
|
|
5787
|
-
|
|
5788
|
-
...options,
|
|
5789
|
-
});
|
|
5743
|
+
private requestScreenShareFloorIfPending() {
|
|
5744
|
+
if (this.floorGrantPending && this.state === MEETING_STATE.STATES.JOINED) {
|
|
5745
|
+
this.requestScreenShareFloor().then(() => {
|
|
5746
|
+
this.floorGrantPending = false;
|
|
5747
|
+
});
|
|
5748
|
+
}
|
|
5790
5749
|
}
|
|
5791
5750
|
|
|
5792
5751
|
/**
|
|
@@ -5798,11 +5757,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5798
5757
|
private releaseScreenShareFloor() {
|
|
5799
5758
|
const content = this.locusInfo.mediaShares.find((element) => element.name === CONTENT);
|
|
5800
5759
|
|
|
5801
|
-
if (content
|
|
5760
|
+
if (content) {
|
|
5802
5761
|
Metrics.postEvent({event: eventType.SHARE_STOPPED, meeting: this});
|
|
5803
|
-
Media.stopTracks(this.mediaProperties.shareTrack);
|
|
5804
5762
|
|
|
5805
|
-
if (content.floor
|
|
5763
|
+
if (content.floor?.beneficiary.id !== this.selfId) {
|
|
5806
5764
|
// remote participant started sharing and caused our sharing to stop, we don't want to send any floor action request in that case
|
|
5807
5765
|
this.isSharing = false;
|
|
5808
5766
|
|
|
@@ -5834,7 +5792,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5834
5792
|
});
|
|
5835
5793
|
}
|
|
5836
5794
|
|
|
5837
|
-
|
|
5795
|
+
// according to Locus there is no content, so we don't need to release the floor (it's probably already been released)
|
|
5796
|
+
this.isSharing = false;
|
|
5797
|
+
|
|
5798
|
+
return Promise.resolve();
|
|
5838
5799
|
}
|
|
5839
5800
|
|
|
5840
5801
|
/**
|
|
@@ -5844,7 +5805,50 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5844
5805
|
* @memberof Meeting
|
|
5845
5806
|
*/
|
|
5846
5807
|
public startRecording() {
|
|
5847
|
-
return
|
|
5808
|
+
return this.recordingController.startRecording();
|
|
5809
|
+
}
|
|
5810
|
+
|
|
5811
|
+
/**
|
|
5812
|
+
* set the mute on entry flag for participants if you're the host
|
|
5813
|
+
* @returns {Promise}
|
|
5814
|
+
* @param {boolean} enabled
|
|
5815
|
+
* @public
|
|
5816
|
+
* @memberof Meeting
|
|
5817
|
+
*/
|
|
5818
|
+
public setMuteOnEntry(enabled: boolean) {
|
|
5819
|
+
return this.controlsOptionsManager.setMuteOnEntry(enabled);
|
|
5820
|
+
}
|
|
5821
|
+
|
|
5822
|
+
/**
|
|
5823
|
+
* set the disallow unmute flag for participants if you're the host
|
|
5824
|
+
* @returns {Promise}
|
|
5825
|
+
* @param {boolean} enabled
|
|
5826
|
+
* @public
|
|
5827
|
+
* @memberof Meeting
|
|
5828
|
+
*/
|
|
5829
|
+
public setDisallowUnmute(enabled: boolean) {
|
|
5830
|
+
return this.controlsOptionsManager.setDisallowUnmute(enabled);
|
|
5831
|
+
}
|
|
5832
|
+
|
|
5833
|
+
/**
|
|
5834
|
+
* set the mute all flag for participants if you're the host
|
|
5835
|
+
* @returns {Promise}
|
|
5836
|
+
* @param {boolean} mutedEnabled
|
|
5837
|
+
* @param {boolean} disallowUnmuteEnabled
|
|
5838
|
+
* @param {boolean} muteOnEntryEnabled
|
|
5839
|
+
* @public
|
|
5840
|
+
* @memberof Meeting
|
|
5841
|
+
*/
|
|
5842
|
+
public setMuteAll(
|
|
5843
|
+
mutedEnabled: boolean,
|
|
5844
|
+
disallowUnmuteEnabled: boolean,
|
|
5845
|
+
muteOnEntryEnabled: boolean
|
|
5846
|
+
) {
|
|
5847
|
+
return this.controlsOptionsManager.setMuteAll(
|
|
5848
|
+
mutedEnabled,
|
|
5849
|
+
disallowUnmuteEnabled,
|
|
5850
|
+
muteOnEntryEnabled
|
|
5851
|
+
);
|
|
5848
5852
|
}
|
|
5849
5853
|
|
|
5850
5854
|
/**
|
|
@@ -5854,7 +5858,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5854
5858
|
* @memberof Meeting
|
|
5855
5859
|
*/
|
|
5856
5860
|
public stopRecording() {
|
|
5857
|
-
return
|
|
5861
|
+
return this.recordingController.stopRecording();
|
|
5858
5862
|
}
|
|
5859
5863
|
|
|
5860
5864
|
/**
|
|
@@ -5864,7 +5868,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5864
5868
|
* @memberof Meeting
|
|
5865
5869
|
*/
|
|
5866
5870
|
public pauseRecording() {
|
|
5867
|
-
return
|
|
5871
|
+
return this.recordingController.pauseRecording();
|
|
5868
5872
|
}
|
|
5869
5873
|
|
|
5870
5874
|
/**
|
|
@@ -5874,7 +5878,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5874
5878
|
* @memberof Meeting
|
|
5875
5879
|
*/
|
|
5876
5880
|
public resumeRecording() {
|
|
5877
|
-
return
|
|
5881
|
+
return this.recordingController.resumeRecording();
|
|
5878
5882
|
}
|
|
5879
5883
|
|
|
5880
5884
|
/**
|
|
@@ -6048,11 +6052,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6048
6052
|
main: layoutInfo.main,
|
|
6049
6053
|
content: layoutInfo.content,
|
|
6050
6054
|
})
|
|
6051
|
-
.then((response) => {
|
|
6052
|
-
if (response && response.body && response.body.locus) {
|
|
6053
|
-
this.locusInfo.onFullLocus(response.body.locus);
|
|
6054
|
-
}
|
|
6055
|
-
})
|
|
6056
6055
|
.catch((error) => {
|
|
6057
6056
|
LoggerProxy.logger.error('Meeting:index#changeVideoLayout --> Error ', error);
|
|
6058
6057
|
|
|
@@ -6060,62 +6059,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6060
6059
|
});
|
|
6061
6060
|
}
|
|
6062
6061
|
|
|
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
6062
|
/**
|
|
6120
6063
|
* Sets the quality level of the remote incoming media
|
|
6121
6064
|
* @param {String} level {LOW|MEDIUM|HIGH}
|
|
@@ -6151,129 +6094,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6151
6094
|
// Set the quality level in properties
|
|
6152
6095
|
this.mediaProperties.setRemoteQualityLevel(level);
|
|
6153
6096
|
|
|
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
|
-
});
|
|
6097
|
+
return this.updateTranscodedMediaConnection();
|
|
6277
6098
|
}
|
|
6278
6099
|
|
|
6279
6100
|
/**
|
|
@@ -6283,20 +6104,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6283
6104
|
* @param {MediaStream} localShare
|
|
6284
6105
|
* @returns {undefined}
|
|
6285
6106
|
*/
|
|
6286
|
-
private handleShareTrackEnded(
|
|
6107
|
+
private handleShareTrackEnded = async () => {
|
|
6287
6108
|
if (this.wirelessShare) {
|
|
6288
6109
|
this.leave({reason: MEETING_REMOVED_REASON.USER_ENDED_SHARE_STREAMS});
|
|
6289
6110
|
} else {
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
skipSignalingCheck: true,
|
|
6294
|
-
}).catch((error) => {
|
|
6111
|
+
try {
|
|
6112
|
+
await this.unpublishTracks([this.mediaProperties.shareTrack]); // todo: screen share audio (SPARK-399690)
|
|
6113
|
+
} catch (error) {
|
|
6295
6114
|
LoggerProxy.logger.log(
|
|
6296
6115
|
'Meeting:index#handleShareTrackEnded --> Error stopping share: ',
|
|
6297
6116
|
error
|
|
6298
6117
|
);
|
|
6299
|
-
}
|
|
6118
|
+
}
|
|
6300
6119
|
}
|
|
6301
6120
|
|
|
6302
6121
|
Trigger.trigger(
|
|
@@ -6307,11 +6126,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6307
6126
|
},
|
|
6308
6127
|
EVENT_TRIGGERS.MEETING_STOPPED_SHARING_LOCAL,
|
|
6309
6128
|
{
|
|
6310
|
-
|
|
6311
|
-
stream: localShare,
|
|
6129
|
+
reason: SHARE_STOPPED_REASON.TRACK_ENDED,
|
|
6312
6130
|
}
|
|
6313
6131
|
);
|
|
6314
|
-
}
|
|
6132
|
+
};
|
|
6315
6133
|
|
|
6316
6134
|
/**
|
|
6317
6135
|
* Emits the 'network:quality' event
|
|
@@ -6341,14 +6159,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6341
6159
|
|
|
6342
6160
|
/**
|
|
6343
6161
|
* Handle logging the media
|
|
6344
|
-
* @param {Object}
|
|
6345
|
-
* @param {Object} videoTrack The video track
|
|
6162
|
+
* @param {Object} mediaProperties
|
|
6346
6163
|
* @private
|
|
6347
6164
|
* @returns {undefined}
|
|
6348
6165
|
*/
|
|
6349
|
-
private handleMediaLogging(
|
|
6350
|
-
|
|
6351
|
-
|
|
6166
|
+
private handleMediaLogging(mediaProperties: {
|
|
6167
|
+
audioTrack?: LocalMicrophoneTrack;
|
|
6168
|
+
videoTrack?: LocalCameraTrack;
|
|
6169
|
+
}) {
|
|
6170
|
+
MeetingUtil.handleVideoLogging(mediaProperties.videoTrack);
|
|
6171
|
+
MeetingUtil.handleAudioLogging(mediaProperties.audioTrack);
|
|
6352
6172
|
}
|
|
6353
6173
|
|
|
6354
6174
|
/**
|
|
@@ -6437,7 +6257,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6437
6257
|
const end = this.endLocalSDPGenRemoteSDPRecvDelay;
|
|
6438
6258
|
|
|
6439
6259
|
if (start && end) {
|
|
6440
|
-
const calculatedDelay = end - start;
|
|
6260
|
+
const calculatedDelay = Math.round(end - start);
|
|
6441
6261
|
|
|
6442
6262
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6443
6263
|
}
|
|
@@ -6449,26 +6269,26 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6449
6269
|
*
|
|
6450
6270
|
* @returns {undefined}
|
|
6451
6271
|
*/
|
|
6452
|
-
|
|
6453
|
-
this.
|
|
6454
|
-
this.
|
|
6272
|
+
setStartCallInitJoinReq() {
|
|
6273
|
+
this.startCallInitJoinReq = performance.now();
|
|
6274
|
+
this.endCallInitJoinReq = undefined;
|
|
6455
6275
|
}
|
|
6456
6276
|
|
|
6457
6277
|
/**
|
|
6458
6278
|
*
|
|
6459
6279
|
* @returns {undefined}
|
|
6460
6280
|
*/
|
|
6461
|
-
|
|
6462
|
-
this.
|
|
6281
|
+
setEndCallInitJoinReq() {
|
|
6282
|
+
this.endCallInitJoinReq = performance.now();
|
|
6463
6283
|
}
|
|
6464
6284
|
|
|
6465
6285
|
/**
|
|
6466
6286
|
*
|
|
6467
6287
|
* @returns {string} duration between call initiate and sending join request to locus
|
|
6468
6288
|
*/
|
|
6469
|
-
|
|
6470
|
-
const start = this.
|
|
6471
|
-
const end = this.
|
|
6289
|
+
getCallInitJoinReq() {
|
|
6290
|
+
const start = this.startCallInitJoinReq;
|
|
6291
|
+
const end = this.endCallInitJoinReq;
|
|
6472
6292
|
|
|
6473
6293
|
if (start && end) {
|
|
6474
6294
|
const calculatedDelay = end - start;
|
|
@@ -6505,7 +6325,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6505
6325
|
const end = this.endJoinReqResp;
|
|
6506
6326
|
|
|
6507
6327
|
if (start && end) {
|
|
6508
|
-
const calculatedDelay = end - start;
|
|
6328
|
+
const calculatedDelay = Math.round(end - start);
|
|
6509
6329
|
|
|
6510
6330
|
return calculatedDelay > METRICS_JOIN_TIMES_MAX_DURATION ? undefined : calculatedDelay;
|
|
6511
6331
|
}
|
|
@@ -6518,10 +6338,45 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6518
6338
|
* @returns {string} duration between call initiate and successful locus join (even if it is in lobby)
|
|
6519
6339
|
*/
|
|
6520
6340
|
getTotalJmt() {
|
|
6521
|
-
const start = this.
|
|
6341
|
+
const start = this.startCallInitJoinReq;
|
|
6522
6342
|
const end = this.endJoinReqResp;
|
|
6523
6343
|
|
|
6524
|
-
return start && end ? end - start : undefined;
|
|
6344
|
+
return start && end ? Math.round(end - start) : undefined;
|
|
6345
|
+
}
|
|
6346
|
+
|
|
6347
|
+
/**
|
|
6348
|
+
*
|
|
6349
|
+
* @returns {string} one of 'attendee','host','cohost', returns the user type of the current user
|
|
6350
|
+
*/
|
|
6351
|
+
getCurUserType() {
|
|
6352
|
+
const {roles} = this;
|
|
6353
|
+
if (roles) {
|
|
6354
|
+
if (roles.includes(SELF_ROLES.MODERATOR)) {
|
|
6355
|
+
return 'host';
|
|
6356
|
+
}
|
|
6357
|
+
if (roles.includes(SELF_ROLES.COHOST)) {
|
|
6358
|
+
return 'cohost';
|
|
6359
|
+
}
|
|
6360
|
+
if (roles.includes(SELF_ROLES.ATTENDEE)) {
|
|
6361
|
+
return 'attendee';
|
|
6362
|
+
}
|
|
6363
|
+
}
|
|
6364
|
+
|
|
6365
|
+
return null;
|
|
6366
|
+
}
|
|
6367
|
+
|
|
6368
|
+
/**
|
|
6369
|
+
*
|
|
6370
|
+
* @returns {string} one of 'login-ci','unverified-guest', returns the login type of the current user
|
|
6371
|
+
*/
|
|
6372
|
+
getCurLoginType() {
|
|
6373
|
+
// @ts-ignore
|
|
6374
|
+
if (this.webex.canAuthorize) {
|
|
6375
|
+
// @ts-ignore
|
|
6376
|
+
return this.webex.credentials.isUnverifiedGuest ? 'unverified-guest' : 'login-ci';
|
|
6377
|
+
}
|
|
6378
|
+
|
|
6379
|
+
return null;
|
|
6525
6380
|
}
|
|
6526
6381
|
|
|
6527
6382
|
/**
|
|
@@ -6611,121 +6466,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6611
6466
|
}
|
|
6612
6467
|
};
|
|
6613
6468
|
|
|
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
6469
|
/**
|
|
6730
6470
|
* starts keepAlives being sent
|
|
6731
6471
|
* @returns {void}
|
|
@@ -6791,13 +6531,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6791
6531
|
/**
|
|
6792
6532
|
* Send a reaction inside the meeting.
|
|
6793
6533
|
*
|
|
6794
|
-
* @param {
|
|
6534
|
+
* @param {ReactionServerType} reactionType - type of reaction to be sent. Example: "thumbs_up"
|
|
6795
6535
|
* @param {SkinToneType} skinToneType - skin tone for the reaction. Example: "medium_dark"
|
|
6796
6536
|
* @returns {Promise}
|
|
6797
6537
|
* @public
|
|
6798
6538
|
* @memberof Meeting
|
|
6799
6539
|
*/
|
|
6800
|
-
public sendReaction(reactionType:
|
|
6540
|
+
public sendReaction(reactionType: ReactionServerType, skinToneType?: SkinToneType) {
|
|
6801
6541
|
const reactionChannelUrl = this.locusInfo?.controls?.reactions?.reactionChannelUrl as string;
|
|
6802
6542
|
const participantId = this.members.selfId;
|
|
6803
6543
|
|
|
@@ -6840,4 +6580,226 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6840
6580
|
requestingParticipantId: this.members.selfId,
|
|
6841
6581
|
});
|
|
6842
6582
|
}
|
|
6583
|
+
|
|
6584
|
+
/**
|
|
6585
|
+
* Throws if we don't have a media connection created
|
|
6586
|
+
*
|
|
6587
|
+
* @returns {void}
|
|
6588
|
+
*/
|
|
6589
|
+
private checkMediaConnection() {
|
|
6590
|
+
if (this.mediaProperties?.webrtcMediaConnection) {
|
|
6591
|
+
return;
|
|
6592
|
+
}
|
|
6593
|
+
throw new NoMediaEstablishedYetError();
|
|
6594
|
+
}
|
|
6595
|
+
|
|
6596
|
+
/**
|
|
6597
|
+
* Method to enable or disable the 'Music mode' effect on audio track
|
|
6598
|
+
*
|
|
6599
|
+
* @param {boolean} shouldEnableMusicMode
|
|
6600
|
+
* @returns {Promise}
|
|
6601
|
+
*/
|
|
6602
|
+
async enableMusicMode(shouldEnableMusicMode: boolean) {
|
|
6603
|
+
this.checkMediaConnection();
|
|
6604
|
+
|
|
6605
|
+
if (!this.isMultistream) {
|
|
6606
|
+
throw new Error('enableMusicMode() only supported with multistream');
|
|
6607
|
+
}
|
|
6608
|
+
|
|
6609
|
+
if (shouldEnableMusicMode) {
|
|
6610
|
+
await this.mediaProperties.webrtcMediaConnection.setCodecParameters(MediaType.AudioMain, {
|
|
6611
|
+
maxaveragebitrate: '64000',
|
|
6612
|
+
maxplaybackrate: '48000',
|
|
6613
|
+
});
|
|
6614
|
+
} else {
|
|
6615
|
+
await this.mediaProperties.webrtcMediaConnection.deleteCodecParameters(MediaType.AudioMain, [
|
|
6616
|
+
'maxaveragebitrate',
|
|
6617
|
+
'maxplaybackrate',
|
|
6618
|
+
]);
|
|
6619
|
+
}
|
|
6620
|
+
}
|
|
6621
|
+
|
|
6622
|
+
/** Updates the tracks being sent on the transcoded media connection
|
|
6623
|
+
*
|
|
6624
|
+
* @returns {Promise<void>}
|
|
6625
|
+
*/
|
|
6626
|
+
private updateTranscodedMediaConnection(): Promise<void> {
|
|
6627
|
+
const LOG_HEADER = 'Meeting:index#updateTranscodedMediaConnection -->';
|
|
6628
|
+
|
|
6629
|
+
LoggerProxy.logger.info(`${LOG_HEADER} starting`);
|
|
6630
|
+
|
|
6631
|
+
if (!this.canUpdateMedia()) {
|
|
6632
|
+
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.TRANSCODED_MEDIA_CONNECTION, {});
|
|
6633
|
+
}
|
|
6634
|
+
|
|
6635
|
+
return this.mediaProperties.webrtcMediaConnection
|
|
6636
|
+
.update({
|
|
6637
|
+
localTracks: {
|
|
6638
|
+
audio: this.mediaProperties.audioTrack?.underlyingTrack || null,
|
|
6639
|
+
video: this.mediaProperties.videoTrack?.underlyingTrack || null,
|
|
6640
|
+
screenShareVideo: this.mediaProperties.shareTrack?.underlyingTrack || null,
|
|
6641
|
+
},
|
|
6642
|
+
direction: {
|
|
6643
|
+
audio: Media.getDirection(
|
|
6644
|
+
true,
|
|
6645
|
+
this.mediaProperties.mediaDirection.receiveAudio,
|
|
6646
|
+
this.mediaProperties.mediaDirection.sendAudio
|
|
6647
|
+
),
|
|
6648
|
+
video: Media.getDirection(
|
|
6649
|
+
true,
|
|
6650
|
+
this.mediaProperties.mediaDirection.receiveVideo,
|
|
6651
|
+
this.mediaProperties.mediaDirection.sendVideo
|
|
6652
|
+
),
|
|
6653
|
+
screenShareVideo: Media.getDirection(
|
|
6654
|
+
false,
|
|
6655
|
+
this.mediaProperties.mediaDirection.receiveShare,
|
|
6656
|
+
this.mediaProperties.mediaDirection.sendShare
|
|
6657
|
+
),
|
|
6658
|
+
},
|
|
6659
|
+
remoteQualityLevel: this.mediaProperties.remoteQualityLevel,
|
|
6660
|
+
})
|
|
6661
|
+
.then(() => {
|
|
6662
|
+
LoggerProxy.logger.info(`${LOG_HEADER} done`);
|
|
6663
|
+
})
|
|
6664
|
+
.catch((error) => {
|
|
6665
|
+
LoggerProxy.logger.error(`${LOG_HEADER} Error: `, error);
|
|
6666
|
+
|
|
6667
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.UPDATE_MEDIA_FAILURE, {
|
|
6668
|
+
correlation_id: this.correlationId,
|
|
6669
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6670
|
+
reason: error.message,
|
|
6671
|
+
stack: error.stack,
|
|
6672
|
+
});
|
|
6673
|
+
|
|
6674
|
+
throw error;
|
|
6675
|
+
});
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6678
|
+
/**
|
|
6679
|
+
* Publishes a track.
|
|
6680
|
+
*
|
|
6681
|
+
* @param {LocalTrack} track to publish
|
|
6682
|
+
* @returns {Promise}
|
|
6683
|
+
*/
|
|
6684
|
+
private async publishTrack(track?: LocalTrack) {
|
|
6685
|
+
if (!track) {
|
|
6686
|
+
return;
|
|
6687
|
+
}
|
|
6688
|
+
|
|
6689
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6690
|
+
if (this.isMultistream) {
|
|
6691
|
+
await this.mediaProperties.webrtcMediaConnection.publishTrack(track);
|
|
6692
|
+
} else {
|
|
6693
|
+
track.setPublished(true); // for multistream, this call is done by WCME
|
|
6694
|
+
}
|
|
6695
|
+
}
|
|
6696
|
+
}
|
|
6697
|
+
|
|
6698
|
+
/**
|
|
6699
|
+
* Un-publishes a track.
|
|
6700
|
+
*
|
|
6701
|
+
* @param {LocalTrack} track to unpublish
|
|
6702
|
+
* @returns {Promise}
|
|
6703
|
+
*/
|
|
6704
|
+
private async unpublishTrack(track?: LocalTrack) {
|
|
6705
|
+
if (!track) {
|
|
6706
|
+
return;
|
|
6707
|
+
}
|
|
6708
|
+
|
|
6709
|
+
if (this.isMultistream && this.mediaProperties.webrtcMediaConnection) {
|
|
6710
|
+
await this.mediaProperties.webrtcMediaConnection.unpublishTrack(track);
|
|
6711
|
+
} else {
|
|
6712
|
+
track.setPublished(false); // for multistream, this call is done by WCME
|
|
6713
|
+
}
|
|
6714
|
+
}
|
|
6715
|
+
|
|
6716
|
+
/**
|
|
6717
|
+
* Publishes specified local tracks in the meeting
|
|
6718
|
+
*
|
|
6719
|
+
* @param {Object} tracks
|
|
6720
|
+
* @returns {Promise}
|
|
6721
|
+
*/
|
|
6722
|
+
async publishTracks(tracks: LocalTracks): Promise<void> {
|
|
6723
|
+
this.checkMediaConnection();
|
|
6724
|
+
|
|
6725
|
+
this.annotationInfo = tracks.annotationInfo;
|
|
6726
|
+
|
|
6727
|
+
if (
|
|
6728
|
+
!tracks.microphone &&
|
|
6729
|
+
!tracks.camera &&
|
|
6730
|
+
!tracks.screenShare?.audio &&
|
|
6731
|
+
!tracks.screenShare?.video
|
|
6732
|
+
) {
|
|
6733
|
+
// nothing to do
|
|
6734
|
+
return;
|
|
6735
|
+
}
|
|
6736
|
+
|
|
6737
|
+
let floorRequestNeeded = false;
|
|
6738
|
+
|
|
6739
|
+
if (tracks.screenShare?.video) {
|
|
6740
|
+
await this.setLocalShareTrack(tracks.screenShare?.video);
|
|
6741
|
+
|
|
6742
|
+
floorRequestNeeded = true;
|
|
6743
|
+
}
|
|
6744
|
+
|
|
6745
|
+
if (tracks.microphone) {
|
|
6746
|
+
await this.setLocalAudioTrack(tracks.microphone);
|
|
6747
|
+
}
|
|
6748
|
+
|
|
6749
|
+
if (tracks.camera) {
|
|
6750
|
+
await this.setLocalVideoTrack(tracks.camera);
|
|
6751
|
+
}
|
|
6752
|
+
|
|
6753
|
+
if (!this.isMultistream) {
|
|
6754
|
+
await this.updateTranscodedMediaConnection();
|
|
6755
|
+
}
|
|
6756
|
+
|
|
6757
|
+
if (floorRequestNeeded) {
|
|
6758
|
+
// we're sending the http request to Locus to request the screen share floor
|
|
6759
|
+
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
6760
|
+
// and also if sharing from the start, we need confluence to have been created
|
|
6761
|
+
await this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.LAMBDA, {
|
|
6762
|
+
lambda: async () => {
|
|
6763
|
+
return this.requestScreenShareFloor();
|
|
6764
|
+
},
|
|
6765
|
+
});
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
|
|
6769
|
+
/**
|
|
6770
|
+
* Un-publishes specified local tracks in the meeting
|
|
6771
|
+
*
|
|
6772
|
+
* @param {Array<MediaStreamTrack>} tracks
|
|
6773
|
+
* @returns {Promise}
|
|
6774
|
+
*/
|
|
6775
|
+
async unpublishTracks(tracks: LocalTrack[]): Promise<void> {
|
|
6776
|
+
this.checkMediaConnection();
|
|
6777
|
+
|
|
6778
|
+
const promises = [];
|
|
6779
|
+
|
|
6780
|
+
for (const track of tracks.filter((t) => !!t)) {
|
|
6781
|
+
if (track === this.mediaProperties.shareTrack) {
|
|
6782
|
+
try {
|
|
6783
|
+
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
6784
|
+
} catch (e) {
|
|
6785
|
+
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
6786
|
+
}
|
|
6787
|
+
promises.push(this.setLocalShareTrack(undefined));
|
|
6788
|
+
}
|
|
6789
|
+
|
|
6790
|
+
if (track === this.mediaProperties.audioTrack) {
|
|
6791
|
+
promises.push(this.setLocalAudioTrack(undefined));
|
|
6792
|
+
}
|
|
6793
|
+
|
|
6794
|
+
if (track === this.mediaProperties.videoTrack) {
|
|
6795
|
+
promises.push(this.setLocalVideoTrack(undefined));
|
|
6796
|
+
}
|
|
6797
|
+
}
|
|
6798
|
+
|
|
6799
|
+
if (!this.isMultistream) {
|
|
6800
|
+
promises.push(this.updateTranscodedMediaConnection());
|
|
6801
|
+
}
|
|
6802
|
+
|
|
6803
|
+
await Promise.all(promises);
|
|
6804
|
+
}
|
|
6843
6805
|
}
|