@webex/plugin-meetings 3.0.0-stream-classes.5 → 3.0.0
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/.eslintrc.js +6 -0
- package/README.md +12 -0
- package/babel.config.js +3 -0
- package/dist/annotation/constants.js +12 -20
- package/dist/annotation/constants.js.map +1 -1
- package/dist/annotation/index.js +25 -10
- package/dist/annotation/index.js.map +1 -1
- package/dist/breakouts/breakout.js +2 -3
- package/dist/breakouts/breakout.js.map +1 -1
- package/dist/breakouts/collection.js +1 -2
- package/dist/breakouts/collection.js.map +1 -1
- package/dist/breakouts/edit-lock-error.js +1 -2
- package/dist/breakouts/edit-lock-error.js.map +1 -1
- package/dist/breakouts/events.js +1 -2
- package/dist/breakouts/events.js.map +1 -1
- package/dist/breakouts/index.js +13 -14
- package/dist/breakouts/index.js.map +1 -1
- package/dist/breakouts/request.js +1 -2
- package/dist/breakouts/request.js.map +1 -1
- package/dist/breakouts/utils.js +3 -6
- package/dist/breakouts/utils.js.map +1 -1
- package/dist/common/browser-detection.js +2 -3
- package/dist/common/browser-detection.js.map +1 -1
- package/dist/common/collection.js +3 -4
- package/dist/common/collection.js.map +1 -1
- package/dist/common/config.js +1 -2
- package/dist/common/config.js.map +1 -1
- package/dist/common/errors/captcha-error.js +1 -2
- package/dist/common/errors/captcha-error.js.map +1 -1
- package/dist/common/errors/intent-to-join.js +1 -2
- package/dist/common/errors/intent-to-join.js.map +1 -1
- package/dist/common/errors/join-meeting.js +1 -2
- package/dist/common/errors/join-meeting.js.map +1 -1
- package/dist/common/errors/media.js +1 -2
- package/dist/common/errors/media.js.map +1 -1
- package/dist/common/errors/no-meeting-info.d.ts +14 -0
- package/dist/common/errors/no-meeting-info.js +50 -0
- package/dist/common/errors/no-meeting-info.js.map +1 -0
- package/dist/common/errors/parameter.js +3 -4
- package/dist/common/errors/parameter.js.map +1 -1
- package/dist/common/errors/password-error.js +1 -2
- package/dist/common/errors/password-error.js.map +1 -1
- package/dist/common/errors/permission.js +1 -2
- package/dist/common/errors/permission.js.map +1 -1
- package/dist/common/errors/reclaim-host-role-errors.d.ts +60 -0
- package/dist/common/errors/reclaim-host-role-errors.js +154 -0
- package/dist/common/errors/reclaim-host-role-errors.js.map +1 -0
- package/dist/common/errors/reconnection-in-progress.js +1 -2
- package/dist/common/errors/reconnection-in-progress.js.map +1 -1
- package/dist/common/errors/reconnection.js +1 -2
- package/dist/common/errors/reconnection.js.map +1 -1
- package/dist/common/errors/stats.js +1 -2
- package/dist/common/errors/stats.js.map +1 -1
- package/dist/{types/common → common}/errors/webex-errors.d.ts +13 -1
- package/dist/common/errors/webex-errors.js +35 -16
- package/dist/common/errors/webex-errors.js.map +1 -1
- package/dist/common/errors/webex-meetings-error.js +1 -2
- package/dist/common/errors/webex-meetings-error.js.map +1 -1
- package/dist/common/events/events-scope.js +1 -2
- package/dist/common/events/events-scope.js.map +1 -1
- package/dist/common/events/events.js +1 -2
- package/dist/common/events/events.js.map +1 -1
- package/dist/common/events/trigger-proxy.js +1 -2
- package/dist/common/events/trigger-proxy.js.map +1 -1
- package/dist/common/events/util.js +1 -2
- package/dist/common/events/util.js.map +1 -1
- package/dist/common/logs/logger-config.js +1 -2
- package/dist/common/logs/logger-config.js.map +1 -1
- package/dist/common/logs/logger-proxy.js +1 -2
- package/dist/common/logs/logger-proxy.js.map +1 -1
- package/dist/{types/common → common}/logs/request.d.ts +3 -1
- package/dist/common/logs/request.js +8 -5
- package/dist/common/logs/request.js.map +1 -1
- package/dist/common/queue.js +2 -4
- package/dist/common/queue.js.map +1 -1
- package/dist/{types/config.d.ts → config.d.ts} +1 -1
- package/dist/config.js +3 -3
- package/dist/config.js.map +1 -1
- package/dist/{types/constants.d.ts → constants.d.ts} +71 -15
- package/dist/constants.js +252 -371
- package/dist/constants.js.map +1 -1
- package/dist/controls-options-manager/constants.js +3 -6
- package/dist/controls-options-manager/constants.js.map +1 -1
- package/dist/controls-options-manager/enums.js +7 -10
- package/dist/controls-options-manager/enums.js.map +1 -1
- package/dist/controls-options-manager/index.js +27 -32
- package/dist/controls-options-manager/index.js.map +1 -1
- package/dist/controls-options-manager/util.js +1 -2
- package/dist/controls-options-manager/util.js.map +1 -1
- package/dist/index.js +8 -5
- package/dist/index.js.map +1 -1
- package/dist/interceptors/index.d.ts +2 -0
- package/dist/interceptors/index.js +15 -0
- package/dist/interceptors/index.js.map +1 -0
- package/dist/interceptors/locusRetry.d.ts +27 -0
- package/dist/interceptors/locusRetry.js +94 -0
- package/dist/interceptors/locusRetry.js.map +1 -0
- package/dist/interpretation/collection.js +1 -2
- package/dist/interpretation/collection.js.map +1 -1
- package/dist/interpretation/index.js +2 -3
- package/dist/interpretation/index.js.map +1 -1
- package/dist/interpretation/siLanguage.js +2 -3
- package/dist/interpretation/siLanguage.js.map +1 -1
- package/dist/locus-info/controlsUtils.js +12 -13
- package/dist/locus-info/controlsUtils.js.map +1 -1
- package/dist/locus-info/embeddedAppsUtils.js +3 -4
- package/dist/locus-info/embeddedAppsUtils.js.map +1 -1
- package/dist/locus-info/fullState.js +1 -2
- package/dist/locus-info/fullState.js.map +1 -1
- package/dist/locus-info/hostUtils.js +1 -2
- package/dist/locus-info/hostUtils.js.map +1 -1
- package/dist/{types/locus-info → locus-info}/index.d.ts +1 -1
- package/dist/locus-info/index.js +38 -37
- package/dist/locus-info/index.js.map +1 -1
- package/dist/locus-info/infoUtils.js +3 -4
- package/dist/locus-info/infoUtils.js.map +1 -1
- package/dist/locus-info/mediaSharesUtils.js +16 -3
- package/dist/locus-info/mediaSharesUtils.js.map +1 -1
- package/dist/{types/locus-info → locus-info}/parser.d.ts +3 -2
- package/dist/locus-info/parser.js +43 -31
- package/dist/locus-info/parser.js.map +1 -1
- package/dist/locus-info/selfUtils.js +7 -6
- package/dist/locus-info/selfUtils.js.map +1 -1
- package/dist/media/index.js +15 -10
- package/dist/media/index.js.map +1 -1
- package/dist/media/properties.js +16 -7
- package/dist/media/properties.js.map +1 -1
- package/dist/media/util.js +1 -2
- package/dist/media/util.js.map +1 -1
- package/dist/mediaQualityMetrics/config.d.ts +241 -0
- package/dist/mediaQualityMetrics/config.js +135 -339
- package/dist/mediaQualityMetrics/config.js.map +1 -1
- package/dist/{types/meeting → meeting}/in-meeting-actions.d.ts +4 -0
- package/dist/meeting/in-meeting-actions.js +18 -2
- package/dist/meeting/in-meeting-actions.js.map +1 -1
- package/dist/{types/meeting → meeting}/index.d.ts +318 -45
- package/dist/meeting/index.js +2620 -1405
- package/dist/meeting/index.js.map +1 -1
- package/dist/meeting/locusMediaRequest.js +4 -5
- package/dist/meeting/locusMediaRequest.js.map +1 -1
- package/dist/meeting/muteState.js +2 -4
- package/dist/meeting/muteState.js.map +1 -1
- package/dist/{types/meeting → meeting}/request.d.ts +2 -0
- package/dist/meeting/request.js +46 -31
- package/dist/meeting/request.js.map +1 -1
- package/dist/meeting/state.js +1 -2
- package/dist/meeting/state.js.map +1 -1
- package/dist/{types/meeting → meeting}/util.d.ts +17 -0
- package/dist/meeting/util.js +83 -10
- package/dist/meeting/util.js.map +1 -1
- package/dist/meeting/voicea-meeting.d.ts +16 -0
- package/dist/meeting/voicea-meeting.js +169 -0
- package/dist/meeting/voicea-meeting.js.map +1 -0
- package/dist/meeting-info/collection.js +3 -4
- package/dist/meeting-info/collection.js.map +1 -1
- package/dist/{types/meeting-info → meeting-info}/index.d.ts +7 -0
- package/dist/meeting-info/index.js +53 -27
- package/dist/meeting-info/index.js.map +1 -1
- package/dist/{types/meeting-info → meeting-info}/meeting-info-v2.d.ts +1 -0
- package/dist/meeting-info/meeting-info-v2.js +52 -33
- package/dist/meeting-info/meeting-info-v2.js.map +1 -1
- package/dist/meeting-info/request.js +1 -2
- package/dist/meeting-info/request.js.map +1 -1
- package/dist/meeting-info/util.js +8 -8
- package/dist/meeting-info/util.js.map +1 -1
- package/dist/meeting-info/utilv2.js +12 -9
- package/dist/meeting-info/utilv2.js.map +1 -1
- package/dist/{types/meetings → meetings}/collection.d.ts +9 -0
- package/dist/meetings/collection.js +21 -5
- package/dist/meetings/collection.js.map +1 -1
- package/dist/{types/meetings → meetings}/index.d.ts +45 -16
- package/dist/meetings/index.js +166 -74
- package/dist/meetings/index.js.map +1 -1
- package/dist/meetings/request.js +2 -3
- package/dist/meetings/request.js.map +1 -1
- package/dist/meetings/util.js +3 -10
- package/dist/meetings/util.js.map +1 -1
- package/dist/{types/member → member}/index.d.ts +1 -0
- package/dist/member/index.js +10 -3
- package/dist/member/index.js.map +1 -1
- package/dist/member/member.types.d.ts +11 -0
- package/dist/member/member.types.js +17 -0
- package/dist/member/member.types.js.map +1 -0
- package/dist/member/types.js +6 -8
- package/dist/member/types.js.map +1 -1
- package/dist/member/util.js +12 -2
- package/dist/member/util.js.map +1 -1
- package/dist/members/collection.js +1 -2
- package/dist/members/collection.js.map +1 -1
- package/dist/members/index.js +25 -8
- package/dist/members/index.js.map +1 -1
- package/dist/members/request.js +2 -3
- package/dist/members/request.js.map +1 -1
- package/dist/{types/members → members}/types.d.ts +1 -0
- package/dist/members/types.js +3 -4
- package/dist/members/types.js.map +1 -1
- package/dist/{types/members → members}/util.d.ts +6 -1
- package/dist/members/util.js +18 -8
- package/dist/members/util.js.map +1 -1
- package/dist/{types/metrics → metrics}/constants.d.ts +12 -0
- package/dist/metrics/constants.js +14 -3
- package/dist/metrics/constants.js.map +1 -1
- package/dist/metrics/index.js +3 -2
- package/dist/metrics/index.js.map +1 -1
- package/dist/multistream/mediaRequestManager.js +9 -11
- package/dist/multistream/mediaRequestManager.js.map +1 -1
- package/dist/multistream/receiveSlot.js +3 -5
- package/dist/multistream/receiveSlot.js.map +1 -1
- package/dist/multistream/receiveSlotManager.js +7 -9
- package/dist/multistream/receiveSlotManager.js.map +1 -1
- package/dist/multistream/remoteMedia.js +3 -5
- package/dist/multistream/remoteMedia.js.map +1 -1
- package/dist/multistream/remoteMediaGroup.js +7 -6
- package/dist/multistream/remoteMediaGroup.js.map +1 -1
- package/dist/multistream/remoteMediaManager.js +28 -27
- package/dist/multistream/remoteMediaManager.js.map +1 -1
- package/dist/multistream/sendSlotManager.js +9 -6
- package/dist/multistream/sendSlotManager.js.map +1 -1
- package/dist/networkQualityMonitor/index.js +1 -2
- package/dist/networkQualityMonitor/index.js.map +1 -1
- package/dist/personal-meeting-room/index.js +2 -3
- package/dist/personal-meeting-room/index.js.map +1 -1
- package/dist/personal-meeting-room/request.js +2 -3
- package/dist/personal-meeting-room/request.js.map +1 -1
- package/dist/personal-meeting-room/util.js +1 -2
- package/dist/personal-meeting-room/util.js.map +1 -1
- package/dist/reachability/clusterReachability.d.ts +109 -0
- package/dist/reachability/clusterReachability.js +357 -0
- package/dist/reachability/clusterReachability.js.map +1 -0
- package/dist/reachability/index.d.ts +105 -0
- package/dist/reachability/index.js +279 -436
- package/dist/reachability/index.js.map +1 -1
- package/dist/reachability/request.js +14 -11
- package/dist/reachability/request.js.map +1 -1
- package/dist/reachability/util.d.ts +8 -0
- package/dist/reachability/util.js +29 -0
- package/dist/reachability/util.js.map +1 -0
- package/dist/reactions/constants.js +1 -2
- package/dist/reactions/constants.js.map +1 -1
- package/dist/reactions/reactions.js +2 -4
- package/dist/reactions/reactions.js.map +1 -1
- package/dist/reactions/reactions.type.js +6 -8
- package/dist/reactions/reactions.type.js.map +1 -1
- package/dist/{types/reconnection-manager → reconnection-manager}/index.d.ts +10 -0
- package/dist/reconnection-manager/index.js +129 -106
- package/dist/reconnection-manager/index.js.map +1 -1
- package/dist/recording-controller/enums.js +4 -5
- package/dist/recording-controller/enums.js.map +1 -1
- package/dist/recording-controller/index.js +43 -51
- package/dist/recording-controller/index.js.map +1 -1
- package/dist/recording-controller/util.js +1 -2
- package/dist/recording-controller/util.js.map +1 -1
- package/dist/{types/roap → roap}/index.d.ts +2 -1
- package/dist/roap/index.js +59 -28
- package/dist/roap/index.js.map +1 -1
- package/dist/roap/request.js +14 -22
- package/dist/roap/request.js.map +1 -1
- package/dist/{types/roap → roap}/turnDiscovery.d.ts +21 -4
- package/dist/roap/turnDiscovery.js +182 -89
- package/dist/roap/turnDiscovery.js.map +1 -1
- package/dist/rtcMetrics/constants.js +1 -2
- package/dist/rtcMetrics/constants.js.map +1 -1
- package/dist/{types/rtcMetrics → rtcMetrics}/index.d.ts +15 -1
- package/dist/rtcMetrics/index.js +72 -12
- package/dist/rtcMetrics/index.js.map +1 -1
- package/dist/statsAnalyzer/global.js +1 -2
- package/dist/statsAnalyzer/global.js.map +1 -1
- package/dist/{types/statsAnalyzer → statsAnalyzer}/index.d.ts +28 -11
- package/dist/statsAnalyzer/index.js +371 -318
- package/dist/statsAnalyzer/index.js.map +1 -1
- package/dist/statsAnalyzer/mqaUtil.d.ts +48 -0
- package/dist/statsAnalyzer/mqaUtil.js +295 -162
- package/dist/statsAnalyzer/mqaUtil.js.map +1 -1
- package/dist/transcription/index.js +1 -2
- package/dist/transcription/index.js.map +1 -1
- package/dist/webinar/collection.d.ts +16 -0
- package/dist/webinar/collection.js +43 -0
- package/dist/webinar/collection.js.map +1 -0
- package/dist/webinar/index.d.ts +5 -0
- package/dist/webinar/index.js +68 -0
- package/dist/webinar/index.js.map +1 -0
- package/jest.config.js +3 -0
- package/package.json +44 -24
- package/process +1 -0
- package/src/common/errors/no-meeting-info.ts +24 -0
- package/src/common/errors/reclaim-host-role-errors.ts +134 -0
- package/src/common/errors/webex-errors.ts +19 -2
- package/src/common/logs/request.ts +5 -1
- package/src/config.ts +3 -5
- package/src/constants.ts +77 -8
- package/src/index.ts +4 -0
- package/src/interceptors/index.ts +3 -0
- package/src/interceptors/locusRetry.ts +67 -0
- package/src/locus-info/index.ts +19 -14
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/locus-info/parser.ts +40 -21
- package/src/media/index.ts +8 -6
- package/src/media/properties.ts +17 -2
- package/src/mediaQualityMetrics/config.ts +103 -238
- package/src/meeting/in-meeting-actions.ts +8 -0
- package/src/meeting/index.ts +1664 -642
- package/src/meeting/request.ts +18 -0
- package/src/meeting/util.ts +102 -1
- package/src/meeting/voicea-meeting.ts +122 -0
- package/src/meeting-info/index.ts +47 -20
- package/src/meeting-info/meeting-info-v2.ts +32 -16
- package/src/meeting-info/util.ts +12 -9
- package/src/meeting-info/utilv2.ts +25 -15
- package/src/meetings/collection.ts +13 -0
- package/src/meetings/index.ts +112 -31
- package/src/meetings/util.ts +2 -8
- package/src/member/index.ts +9 -1
- package/src/member/member.types.ts +13 -0
- package/src/member/util.ts +14 -0
- package/src/members/index.ts +29 -2
- package/src/members/types.ts +1 -0
- package/src/members/util.ts +15 -1
- package/src/metrics/constants.ts +12 -0
- package/src/reachability/clusterReachability.ts +320 -0
- package/src/reachability/index.ts +221 -382
- package/src/reachability/request.ts +1 -1
- package/src/reachability/util.ts +24 -0
- package/src/reconnection-manager/index.ts +87 -83
- package/src/roap/index.ts +60 -24
- package/src/roap/request.ts +4 -17
- package/src/roap/turnDiscovery.ts +112 -39
- package/src/rtcMetrics/index.ts +71 -5
- package/src/statsAnalyzer/index.ts +430 -427
- package/src/statsAnalyzer/mqaUtil.ts +317 -168
- package/src/webinar/collection.ts +31 -0
- package/src/webinar/index.ts +62 -0
- package/test/integration/spec/journey.js +12 -12
- package/test/integration/spec/space-meeting.js +1 -1
- package/test/unit/spec/breakouts/breakout.ts +2 -1
- package/test/unit/spec/breakouts/index.ts +7 -4
- package/test/unit/spec/interceptors/locusRetry.ts +131 -0
- package/test/unit/spec/locus-info/index.js +88 -12
- package/test/unit/spec/locus-info/lib/SeqCmp.json +16 -0
- package/test/unit/spec/locus-info/mediaSharesUtils.ts +10 -0
- package/test/unit/spec/locus-info/parser.js +54 -13
- package/test/unit/spec/locus-info/selfUtils.js +1 -1
- package/test/unit/spec/media/index.ts +25 -4
- package/test/unit/spec/media/properties.ts +2 -2
- package/test/unit/spec/meeting/in-meeting-actions.ts +4 -0
- package/test/unit/spec/meeting/index.js +4388 -1382
- package/test/unit/spec/meeting/request.js +63 -12
- package/test/unit/spec/meeting/utils.js +145 -10
- package/test/unit/spec/meeting/voicea-meeting.ts +266 -0
- package/test/unit/spec/meeting-info/index.js +180 -61
- package/test/unit/spec/meeting-info/meetinginfov2.js +216 -68
- package/test/unit/spec/meetings/collection.js +12 -0
- package/test/unit/spec/meetings/index.js +674 -193
- package/test/unit/spec/meetings/utils.js +35 -12
- package/test/unit/spec/member/index.js +8 -7
- package/test/unit/spec/member/util.js +32 -0
- package/test/unit/spec/members/index.js +130 -17
- package/test/unit/spec/members/utils.js +26 -0
- package/test/unit/spec/metrics/index.js +1 -2
- package/test/unit/spec/multistream/mediaRequestManager.ts +1 -0
- package/test/unit/spec/reachability/clusterReachability.ts +279 -0
- package/test/unit/spec/reachability/index.ts +505 -135
- package/test/unit/spec/reachability/util.ts +40 -0
- package/test/unit/spec/reconnection-manager/index.js +74 -17
- package/test/unit/spec/recording-controller/index.js +0 -1
- package/test/unit/spec/roap/index.ts +181 -61
- package/test/unit/spec/roap/request.ts +27 -3
- package/test/unit/spec/roap/turnDiscovery.ts +363 -102
- package/test/unit/spec/rtcMetrics/index.ts +57 -3
- package/test/unit/spec/stats-analyzer/index.js +1225 -12
- package/test/unit/spec/webinar/collection.ts +13 -0
- package/test/unit/spec/webinar/index.ts +60 -0
- package/test/utils/webex-test-users.js +12 -4
- package/dist/types/mediaQualityMetrics/config.d.ts +0 -365
- package/dist/types/reachability/index.d.ts +0 -152
- package/dist/types/statsAnalyzer/mqaUtil.d.ts +0 -24
- /package/dist/{types/annotation → annotation}/annotation.types.d.ts +0 -0
- /package/dist/{types/annotation → annotation}/constants.d.ts +0 -0
- /package/dist/{types/annotation → annotation}/index.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/breakout.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/collection.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/edit-lock-error.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/events.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/index.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/request.d.ts +0 -0
- /package/dist/{types/breakouts → breakouts}/utils.d.ts +0 -0
- /package/dist/{types/common → common}/browser-detection.d.ts +0 -0
- /package/dist/{types/common → common}/collection.d.ts +0 -0
- /package/dist/{types/common → common}/config.d.ts +0 -0
- /package/dist/{types/common → common}/errors/captcha-error.d.ts +0 -0
- /package/dist/{types/common → common}/errors/intent-to-join.d.ts +0 -0
- /package/dist/{types/common → common}/errors/join-meeting.d.ts +0 -0
- /package/dist/{types/common → common}/errors/media.d.ts +0 -0
- /package/dist/{types/common → common}/errors/parameter.d.ts +0 -0
- /package/dist/{types/common → common}/errors/password-error.d.ts +0 -0
- /package/dist/{types/common → common}/errors/permission.d.ts +0 -0
- /package/dist/{types/common → common}/errors/reconnection-in-progress.d.ts +0 -0
- /package/dist/{types/common → common}/errors/reconnection.d.ts +0 -0
- /package/dist/{types/common → common}/errors/stats.d.ts +0 -0
- /package/dist/{types/common → common}/errors/webex-meetings-error.d.ts +0 -0
- /package/dist/{types/common → common}/events/events-scope.d.ts +0 -0
- /package/dist/{types/common → common}/events/events.d.ts +0 -0
- /package/dist/{types/common → common}/events/trigger-proxy.d.ts +0 -0
- /package/dist/{types/common → common}/events/util.d.ts +0 -0
- /package/dist/{types/common → common}/logs/logger-config.d.ts +0 -0
- /package/dist/{types/common → common}/logs/logger-proxy.d.ts +0 -0
- /package/dist/{types/common → common}/queue.d.ts +0 -0
- /package/dist/{types/controls-options-manager → controls-options-manager}/constants.d.ts +0 -0
- /package/dist/{types/controls-options-manager → controls-options-manager}/enums.d.ts +0 -0
- /package/dist/{types/controls-options-manager → controls-options-manager}/index.d.ts +0 -0
- /package/dist/{types/controls-options-manager → controls-options-manager}/types.d.ts +0 -0
- /package/dist/{types/controls-options-manager → controls-options-manager}/util.d.ts +0 -0
- /package/dist/{types/index.d.ts → index.d.ts} +0 -0
- /package/dist/{types/interpretation → interpretation}/collection.d.ts +0 -0
- /package/dist/{types/interpretation → interpretation}/index.d.ts +0 -0
- /package/dist/{types/interpretation → interpretation}/siLanguage.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/controlsUtils.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/embeddedAppsUtils.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/fullState.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/hostUtils.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/infoUtils.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/mediaSharesUtils.d.ts +0 -0
- /package/dist/{types/locus-info → locus-info}/selfUtils.d.ts +0 -0
- /package/dist/{types/media → media}/index.d.ts +0 -0
- /package/dist/{types/media → media}/properties.d.ts +0 -0
- /package/dist/{types/media → media}/util.d.ts +0 -0
- /package/dist/{types/meeting → meeting}/locusMediaRequest.d.ts +0 -0
- /package/dist/{types/meeting → meeting}/muteState.d.ts +0 -0
- /package/dist/{types/meeting → meeting}/request.type.d.ts +0 -0
- /package/dist/{types/meeting → meeting}/state.d.ts +0 -0
- /package/dist/{types/meeting-info → meeting-info}/collection.d.ts +0 -0
- /package/dist/{types/meeting-info → meeting-info}/request.d.ts +0 -0
- /package/dist/{types/meeting-info → meeting-info}/util.d.ts +0 -0
- /package/dist/{types/meeting-info → meeting-info}/utilv2.d.ts +0 -0
- /package/dist/{types/meetings → meetings}/meetings.types.d.ts +0 -0
- /package/dist/{types/meetings → meetings}/request.d.ts +0 -0
- /package/dist/{types/meetings → meetings}/util.d.ts +0 -0
- /package/dist/{types/member → member}/types.d.ts +0 -0
- /package/dist/{types/member → member}/util.d.ts +0 -0
- /package/dist/{types/members → members}/collection.d.ts +0 -0
- /package/dist/{types/members → members}/index.d.ts +0 -0
- /package/dist/{types/members → members}/request.d.ts +0 -0
- /package/dist/{types/metrics → metrics}/index.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/mediaRequestManager.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/receiveSlot.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/receiveSlotManager.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/remoteMedia.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/remoteMediaGroup.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/remoteMediaManager.d.ts +0 -0
- /package/dist/{types/multistream → multistream}/sendSlotManager.d.ts +0 -0
- /package/dist/{types/networkQualityMonitor → networkQualityMonitor}/index.d.ts +0 -0
- /package/dist/{types/personal-meeting-room → personal-meeting-room}/index.d.ts +0 -0
- /package/dist/{types/personal-meeting-room → personal-meeting-room}/request.d.ts +0 -0
- /package/dist/{types/personal-meeting-room → personal-meeting-room}/util.d.ts +0 -0
- /package/dist/{types/reachability → reachability}/request.d.ts +0 -0
- /package/dist/{types/reactions → reactions}/constants.d.ts +0 -0
- /package/dist/{types/reactions → reactions}/reactions.d.ts +0 -0
- /package/dist/{types/reactions → reactions}/reactions.type.d.ts +0 -0
- /package/dist/{types/recording-controller → recording-controller}/enums.d.ts +0 -0
- /package/dist/{types/recording-controller → recording-controller}/index.d.ts +0 -0
- /package/dist/{types/recording-controller → recording-controller}/util.d.ts +0 -0
- /package/dist/{types/roap → roap}/request.d.ts +0 -0
- /package/dist/{types/rtcMetrics → rtcMetrics}/constants.d.ts +0 -0
- /package/dist/{types/statsAnalyzer → statsAnalyzer}/global.d.ts +0 -0
- /package/dist/{types/transcription → transcription}/index.d.ts +0 -0
- /package/test/unit/spec/locus-info/{selfConstant.js → lib/selfConstant.js} +0 -0
package/src/meeting/index.ts
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import uuid from 'uuid';
|
|
2
2
|
import {cloneDeep, isEqual, isEmpty} from 'lodash';
|
|
3
|
-
import
|
|
3
|
+
import jwtDecode from 'jwt-decode';
|
|
4
4
|
// @ts-ignore - Fix this
|
|
5
5
|
import {StatelessWebexPlugin} from '@webex/webex-core';
|
|
6
|
+
// @ts-ignore - Types not available for @webex/common
|
|
7
|
+
import {Defer} from '@webex/common';
|
|
6
8
|
import {
|
|
7
9
|
ClientEvent,
|
|
8
10
|
ClientEventLeaveReason,
|
|
9
|
-
|
|
11
|
+
CallDiagnosticUtils,
|
|
10
12
|
} from '@webex/internal-plugin-metrics';
|
|
11
13
|
import {
|
|
12
14
|
ConnectionState,
|
|
@@ -16,6 +18,7 @@ import {
|
|
|
16
18
|
MediaContent,
|
|
17
19
|
MediaType,
|
|
18
20
|
RemoteTrackType,
|
|
21
|
+
RoapMessage,
|
|
19
22
|
} from '@webex/internal-media-core';
|
|
20
23
|
|
|
21
24
|
import {
|
|
@@ -30,12 +33,20 @@ import {
|
|
|
30
33
|
RemoteStream,
|
|
31
34
|
} from '@webex/media-helpers';
|
|
32
35
|
|
|
36
|
+
import {
|
|
37
|
+
EVENT_TRIGGERS as VOICEAEVENTS,
|
|
38
|
+
TURN_ON_CAPTION_STATUS,
|
|
39
|
+
} from '@webex/internal-plugin-voicea';
|
|
40
|
+
import {processNewCaptions} from './voicea-meeting';
|
|
41
|
+
|
|
33
42
|
import {
|
|
34
43
|
MeetingNotActiveError,
|
|
35
44
|
UserInLobbyError,
|
|
36
45
|
NoMediaEstablishedYetError,
|
|
37
46
|
UserNotJoinedError,
|
|
47
|
+
AddMediaFailed,
|
|
38
48
|
} from '../common/errors/webex-errors';
|
|
49
|
+
|
|
39
50
|
import {StatsAnalyzer, EVENTS as StatsAnalyzerEvents} from '../statsAnalyzer';
|
|
40
51
|
import NetworkQualityMonitor from '../networkQualityMonitor';
|
|
41
52
|
import LoggerProxy from '../common/logs/logger-proxy';
|
|
@@ -51,19 +62,20 @@ import ReconnectionManager from '../reconnection-manager';
|
|
|
51
62
|
import MeetingRequest from './request';
|
|
52
63
|
import Members from '../members/index';
|
|
53
64
|
import MeetingUtil from './util';
|
|
65
|
+
import MeetingsUtil from '../meetings/util';
|
|
54
66
|
import RecordingUtil from '../recording-controller/util';
|
|
55
67
|
import ControlsOptionsUtil from '../controls-options-manager/util';
|
|
56
68
|
import MediaUtil from '../media/util';
|
|
57
|
-
import Transcription from '../transcription';
|
|
58
69
|
import {Reactions, SkinTones} from '../reactions/reactions';
|
|
59
70
|
import PasswordError from '../common/errors/password-error';
|
|
60
71
|
import CaptchaError from '../common/errors/captcha-error';
|
|
61
72
|
import ReconnectionError from '../common/errors/reconnection';
|
|
62
73
|
import ReconnectInProgress from '../common/errors/reconnection-in-progress';
|
|
63
74
|
import {
|
|
64
|
-
|
|
75
|
+
_CONVERSATION_URL_,
|
|
65
76
|
_INCOMING_,
|
|
66
77
|
_JOIN_,
|
|
78
|
+
_MEETING_LINK_,
|
|
67
79
|
AUDIO,
|
|
68
80
|
CONTENT,
|
|
69
81
|
DISPLAY_HINTS,
|
|
@@ -91,10 +103,14 @@ import {
|
|
|
91
103
|
SHARE_STATUS,
|
|
92
104
|
SHARE_STOPPED_REASON,
|
|
93
105
|
VIDEO,
|
|
94
|
-
HTTP_VERBS,
|
|
95
106
|
SELF_ROLES,
|
|
96
107
|
INTERPRETATION,
|
|
97
108
|
SELF_POLICY,
|
|
109
|
+
MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
|
|
110
|
+
MEETING_PERMISSION_TOKEN_REFRESH_REASON,
|
|
111
|
+
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT,
|
|
112
|
+
RECONNECTION,
|
|
113
|
+
LANGUAGE_ENGLISH,
|
|
98
114
|
} from '../constants';
|
|
99
115
|
import BEHAVIORAL_METRICS from '../metrics/constants';
|
|
100
116
|
import ParameterError from '../common/errors/parameter';
|
|
@@ -122,6 +138,7 @@ import {
|
|
|
122
138
|
import Breakouts from '../breakouts';
|
|
123
139
|
import SimultaneousInterpretation from '../interpretation';
|
|
124
140
|
import Annotation from '../annotation';
|
|
141
|
+
import Webinar from '../webinar';
|
|
125
142
|
|
|
126
143
|
import InMeetingActions from './in-meeting-actions';
|
|
127
144
|
import {REACTION_RELAY_TYPES} from '../reactions/constants';
|
|
@@ -147,6 +164,36 @@ const logRequest = (request: any, {logText = ''}) => {
|
|
|
147
164
|
});
|
|
148
165
|
};
|
|
149
166
|
|
|
167
|
+
export type CaptionData = {
|
|
168
|
+
id: string;
|
|
169
|
+
isFinal: boolean;
|
|
170
|
+
translations: Array<string>;
|
|
171
|
+
text: string;
|
|
172
|
+
currentCaptionLanguage: string;
|
|
173
|
+
timestamp: string;
|
|
174
|
+
speaker: string;
|
|
175
|
+
};
|
|
176
|
+
|
|
177
|
+
export type Transcription = {
|
|
178
|
+
languageOptions: {
|
|
179
|
+
captionLanguages?: string; // list of supported caption languages from backend
|
|
180
|
+
maxLanguages?: number;
|
|
181
|
+
spokenLanguages?: Array<string>; // list of supported spoken languages from backend
|
|
182
|
+
currentCaptionLanguage?: string; // current caption language - default is english
|
|
183
|
+
requestedCaptionLanguage?: string; // requested caption language
|
|
184
|
+
currentSpokenLanguage?: string; // current spoken language - default is english
|
|
185
|
+
};
|
|
186
|
+
status: string;
|
|
187
|
+
isListening: boolean;
|
|
188
|
+
commandText: string;
|
|
189
|
+
captions: Array<CaptionData>;
|
|
190
|
+
showCaptionBox: boolean;
|
|
191
|
+
transcribingRequestStatus: string;
|
|
192
|
+
isCaptioning: boolean;
|
|
193
|
+
speakerProxy: Map<string, any>;
|
|
194
|
+
interimCaptions: Map<string, CaptionData>;
|
|
195
|
+
};
|
|
196
|
+
|
|
150
197
|
export type LocalStreams = {
|
|
151
198
|
microphone?: LocalMicrophoneStream;
|
|
152
199
|
camera?: LocalCameraStream;
|
|
@@ -167,6 +214,12 @@ export type AddMediaOptions = {
|
|
|
167
214
|
allowMediaInLobby?: boolean; // allows adding media when in the lobby
|
|
168
215
|
};
|
|
169
216
|
|
|
217
|
+
export type CallStateForMetrics = {
|
|
218
|
+
correlationId?: string;
|
|
219
|
+
joinTrigger?: string;
|
|
220
|
+
loginType?: string;
|
|
221
|
+
};
|
|
222
|
+
|
|
170
223
|
export const MEDIA_UPDATE_TYPE = {
|
|
171
224
|
TRANSCODED_MEDIA_CONNECTION: 'TRANSCODED_MEDIA_CONNECTION',
|
|
172
225
|
SHARE_FLOOR_REQUEST: 'SHARE_FLOOR_REQUEST',
|
|
@@ -179,6 +232,13 @@ export enum ScreenShareFloorStatus {
|
|
|
179
232
|
RELEASED = 'floor_released',
|
|
180
233
|
}
|
|
181
234
|
|
|
235
|
+
type FetchMeetingInfoParams = {
|
|
236
|
+
password?: string;
|
|
237
|
+
captchaCode?: string;
|
|
238
|
+
extraParams?: Record<string, any>;
|
|
239
|
+
sendCAevents?: boolean;
|
|
240
|
+
};
|
|
241
|
+
|
|
182
242
|
/**
|
|
183
243
|
* MediaDirection
|
|
184
244
|
* @typedef {Object} MediaDirection
|
|
@@ -457,8 +517,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
457
517
|
breakouts: any;
|
|
458
518
|
simultaneousInterpretation: any;
|
|
459
519
|
annotation: any;
|
|
520
|
+
webinar: any;
|
|
460
521
|
conversationUrl: string;
|
|
461
|
-
|
|
522
|
+
callStateForMetrics: CallStateForMetrics;
|
|
462
523
|
destination: string;
|
|
463
524
|
destinationType: string;
|
|
464
525
|
deviceUrl: string;
|
|
@@ -514,8 +575,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
514
575
|
|
|
515
576
|
meetingInfoFailureReason: string;
|
|
516
577
|
meetingInfoFailureCode?: number;
|
|
578
|
+
meetingInfoExtraParams?: Record<string, any>;
|
|
517
579
|
networkQualityMonitor: NetworkQualityMonitor;
|
|
518
|
-
networkStatus
|
|
580
|
+
networkStatus?: NETWORK_STATUS;
|
|
519
581
|
passwordStatus: string;
|
|
520
582
|
queuedMediaUpdates: any[];
|
|
521
583
|
recording: any;
|
|
@@ -525,6 +587,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
525
587
|
requiredCaptcha: any;
|
|
526
588
|
receiveSlotManager: ReceiveSlotManager;
|
|
527
589
|
selfUserPolicies: any;
|
|
590
|
+
enforceVBGImagesURL: string;
|
|
528
591
|
shareStatus: string;
|
|
529
592
|
screenShareFloorState: ScreenShareFloorStatus;
|
|
530
593
|
statsAnalyzer: StatsAnalyzer;
|
|
@@ -546,6 +609,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
546
609
|
meetingState: any;
|
|
547
610
|
permissionToken: string;
|
|
548
611
|
permissionTokenPayload: any;
|
|
612
|
+
permissionTokenReceivedLocalTime: number;
|
|
549
613
|
resourceId: any;
|
|
550
614
|
resourceUrl: string;
|
|
551
615
|
selfId: string;
|
|
@@ -557,7 +621,55 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
557
621
|
environment: string;
|
|
558
622
|
namespace = MEETINGS;
|
|
559
623
|
allowMediaInLobby: boolean;
|
|
624
|
+
localShareInstanceId: string;
|
|
625
|
+
remoteShareInstanceId: string;
|
|
626
|
+
turnDiscoverySkippedReason: string;
|
|
627
|
+
turnServerUsed: boolean;
|
|
628
|
+
areVoiceaEventsSetup = false;
|
|
629
|
+
voiceaListenerCallbacks: object = {
|
|
630
|
+
[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]: (payload: Transcription['languageOptions']) => {
|
|
631
|
+
this.transcription.languageOptions = payload;
|
|
632
|
+
Trigger.trigger(
|
|
633
|
+
this,
|
|
634
|
+
{
|
|
635
|
+
file: 'meeting/index',
|
|
636
|
+
function: 'setUpVoiceaListeners',
|
|
637
|
+
},
|
|
638
|
+
EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
|
|
639
|
+
payload
|
|
640
|
+
);
|
|
641
|
+
},
|
|
642
|
+
[VOICEAEVENTS.CAPTIONS_TURNED_ON]: () => {
|
|
643
|
+
this.transcription.status = TURN_ON_CAPTION_STATUS.ENABLED;
|
|
644
|
+
},
|
|
645
|
+
[VOICEAEVENTS.EVA_COMMAND]: (payload) => {
|
|
646
|
+
const {data} = payload;
|
|
647
|
+
|
|
648
|
+
this.transcription.isListening = !!data.isListening;
|
|
649
|
+
this.transcription.commandText = data.text ?? '';
|
|
650
|
+
},
|
|
651
|
+
[VOICEAEVENTS.NEW_CAPTION]: (data) => {
|
|
652
|
+
processNewCaptions({data, meeting: this});
|
|
653
|
+
Trigger.trigger(
|
|
654
|
+
this,
|
|
655
|
+
{
|
|
656
|
+
file: 'meeting/index',
|
|
657
|
+
function: 'setUpVoiceaListeners',
|
|
658
|
+
},
|
|
659
|
+
EVENT_TRIGGERS.MEETING_CAPTION_RECEIVED,
|
|
660
|
+
{
|
|
661
|
+
captions: this.transcription.captions,
|
|
662
|
+
interimCaptions: this.transcription.interimCaptions,
|
|
663
|
+
}
|
|
664
|
+
);
|
|
665
|
+
},
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
private retriedWithTurnServer: boolean;
|
|
560
669
|
private sendSlotManager: SendSlotManager = new SendSlotManager(LoggerProxy);
|
|
670
|
+
private deferSDPAnswer?: Defer; // used for waiting for a response
|
|
671
|
+
private sdpResponseTimer?: ReturnType<typeof setTimeout>;
|
|
672
|
+
private hasMediaConnectionConnectedAtLeastOnce: boolean;
|
|
561
673
|
|
|
562
674
|
/**
|
|
563
675
|
* @param {Object} attrs
|
|
@@ -592,20 +704,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
592
704
|
*/
|
|
593
705
|
this.id = uuid.v4();
|
|
594
706
|
/**
|
|
595
|
-
*
|
|
707
|
+
* Call state used for metrics
|
|
596
708
|
* @instance
|
|
597
|
-
* @type {
|
|
709
|
+
* @type {CallStateForMetrics}
|
|
598
710
|
* @readonly
|
|
599
711
|
* @public
|
|
600
712
|
* @memberof Meeting
|
|
601
713
|
*/
|
|
602
|
-
|
|
714
|
+
this.callStateForMetrics = attrs.callStateForMetrics || {};
|
|
715
|
+
const correlationId = attrs.correlationId || attrs.callStateForMetrics?.correlationId;
|
|
716
|
+
if (correlationId) {
|
|
603
717
|
LoggerProxy.logger.log(
|
|
604
|
-
`Meetings:index#constructor --> Initializing the meeting object with correlation id from app ${
|
|
718
|
+
`Meetings:index#constructor --> Initializing the meeting object with correlation id from app ${correlationId}`
|
|
605
719
|
);
|
|
606
|
-
this.correlationId =
|
|
720
|
+
this.callStateForMetrics.correlationId = correlationId;
|
|
607
721
|
} else {
|
|
608
|
-
this.correlationId = this.id;
|
|
722
|
+
this.callStateForMetrics.correlationId = this.id;
|
|
609
723
|
}
|
|
610
724
|
/**
|
|
611
725
|
* @instance
|
|
@@ -673,6 +787,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
673
787
|
*/
|
|
674
788
|
// @ts-ignore
|
|
675
789
|
this.annotation = new Annotation({parent: this.webex});
|
|
790
|
+
/**
|
|
791
|
+
* @instance
|
|
792
|
+
* @type {Webinar}
|
|
793
|
+
* @public
|
|
794
|
+
* @memberof Meeting
|
|
795
|
+
*/
|
|
796
|
+
// @ts-ignore
|
|
797
|
+
this.webinar = new Webinar({}, {parent: this.webex});
|
|
676
798
|
/**
|
|
677
799
|
* helper class for managing receive slots (for multistream media connections)
|
|
678
800
|
*/
|
|
@@ -1069,13 +1191,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1069
1191
|
*/
|
|
1070
1192
|
this.networkQualityMonitor = null;
|
|
1071
1193
|
/**
|
|
1194
|
+
* Indicates network status of the webrtc media connection
|
|
1072
1195
|
* @instance
|
|
1073
1196
|
* @type {String}
|
|
1074
1197
|
* @readonly
|
|
1075
1198
|
* @public
|
|
1076
1199
|
* @memberof Meeting
|
|
1077
1200
|
*/
|
|
1078
|
-
this.networkStatus =
|
|
1201
|
+
this.networkStatus = undefined;
|
|
1079
1202
|
/**
|
|
1080
1203
|
* Passing only info as we send basic info for meeting added event
|
|
1081
1204
|
* @instance
|
|
@@ -1140,7 +1263,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1140
1263
|
* @private
|
|
1141
1264
|
* @memberof Meeting
|
|
1142
1265
|
*/
|
|
1143
|
-
this.transcription =
|
|
1266
|
+
this.transcription = {
|
|
1267
|
+
captions: [],
|
|
1268
|
+
isListening: false,
|
|
1269
|
+
commandText: '',
|
|
1270
|
+
languageOptions: {},
|
|
1271
|
+
showCaptionBox: false,
|
|
1272
|
+
transcribingRequestStatus: 'INACTIVE',
|
|
1273
|
+
isCaptioning: false,
|
|
1274
|
+
interimCaptions: {} as Map<string, CaptionData>,
|
|
1275
|
+
speakerProxy: {} as Map<string, any>,
|
|
1276
|
+
} as Transcription;
|
|
1144
1277
|
|
|
1145
1278
|
/**
|
|
1146
1279
|
* Password status. If it's PASSWORD_STATUS.REQUIRED then verifyPassword() needs to be called
|
|
@@ -1194,6 +1327,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1194
1327
|
*/
|
|
1195
1328
|
this.keepAliveTimerId = null;
|
|
1196
1329
|
|
|
1330
|
+
/**
|
|
1331
|
+
* id for tracking Local Share instances in Call Analyzer
|
|
1332
|
+
* @instance
|
|
1333
|
+
* @type {String}
|
|
1334
|
+
* @private
|
|
1335
|
+
* @memberof Meeting
|
|
1336
|
+
*/
|
|
1337
|
+
this.localShareInstanceId = null;
|
|
1338
|
+
|
|
1339
|
+
/**
|
|
1340
|
+
* id for tracking Remote Share instances in Call Analyzer
|
|
1341
|
+
* @instance
|
|
1342
|
+
* @type {String}
|
|
1343
|
+
* @private
|
|
1344
|
+
* @memberof Meeting
|
|
1345
|
+
*/
|
|
1346
|
+
this.remoteShareInstanceId = null;
|
|
1347
|
+
|
|
1197
1348
|
/**
|
|
1198
1349
|
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1199
1350
|
* @instance
|
|
@@ -1246,6 +1397,60 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1246
1397
|
this.updateTranscodedMediaConnection();
|
|
1247
1398
|
}
|
|
1248
1399
|
};
|
|
1400
|
+
|
|
1401
|
+
/**
|
|
1402
|
+
* Promise that exists if SDP offer has been generated, and resolves once sdp answer is received.
|
|
1403
|
+
* @instance
|
|
1404
|
+
* @type {Defer}
|
|
1405
|
+
* @private
|
|
1406
|
+
* @memberof Meeting
|
|
1407
|
+
*/
|
|
1408
|
+
this.deferSDPAnswer = undefined;
|
|
1409
|
+
|
|
1410
|
+
/**
|
|
1411
|
+
* Timer for waiting for sdp answer.
|
|
1412
|
+
* @instance
|
|
1413
|
+
* @type {ReturnType<typeof setTimeout>}
|
|
1414
|
+
* @private
|
|
1415
|
+
* @memberof Meeting
|
|
1416
|
+
*/
|
|
1417
|
+
this.sdpResponseTimer = undefined;
|
|
1418
|
+
|
|
1419
|
+
/**
|
|
1420
|
+
* Reason why TURN discovery is skipped.
|
|
1421
|
+
* @instance
|
|
1422
|
+
* @type {string}
|
|
1423
|
+
* @public
|
|
1424
|
+
* @memberof Meeting
|
|
1425
|
+
*/
|
|
1426
|
+
this.turnDiscoverySkippedReason = undefined;
|
|
1427
|
+
|
|
1428
|
+
/**
|
|
1429
|
+
* Whether TURN discovery is used or not.
|
|
1430
|
+
* @instance
|
|
1431
|
+
* @type {boolean}
|
|
1432
|
+
* @public
|
|
1433
|
+
* @memberof Meeting
|
|
1434
|
+
*/
|
|
1435
|
+
this.turnServerUsed = false;
|
|
1436
|
+
|
|
1437
|
+
/**
|
|
1438
|
+
* Whether retry was done using TURN Discovery.
|
|
1439
|
+
* @instance
|
|
1440
|
+
* @type {boolean}
|
|
1441
|
+
* @private
|
|
1442
|
+
* @memberof Meeting
|
|
1443
|
+
*/
|
|
1444
|
+
this.retriedWithTurnServer = false;
|
|
1445
|
+
|
|
1446
|
+
/**
|
|
1447
|
+
* Whether or not the media connection has ever successfully connected.
|
|
1448
|
+
* @instance
|
|
1449
|
+
* @type {boolean}
|
|
1450
|
+
* @private
|
|
1451
|
+
* @memberof Meeting
|
|
1452
|
+
*/
|
|
1453
|
+
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
1249
1454
|
}
|
|
1250
1455
|
|
|
1251
1456
|
/**
|
|
@@ -1279,23 +1484,87 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1279
1484
|
}
|
|
1280
1485
|
|
|
1281
1486
|
/**
|
|
1282
|
-
*
|
|
1283
|
-
* @
|
|
1284
|
-
* @param {String} [options.password] optional
|
|
1285
|
-
* @param {String} [options.captchaCode] optional
|
|
1286
|
-
* @public
|
|
1287
|
-
* @memberof Meeting
|
|
1288
|
-
* @returns {Promise}
|
|
1487
|
+
* Getter - Returns callStateForMetrics.correlationId
|
|
1488
|
+
* @returns {string}
|
|
1289
1489
|
*/
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1490
|
+
get correlationId() {
|
|
1491
|
+
return this.callStateForMetrics.correlationId;
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
/**
|
|
1495
|
+
* Setter - sets callStateForMetrics.correlationId
|
|
1496
|
+
* @param {string} correlationId
|
|
1497
|
+
*/
|
|
1498
|
+
set correlationId(correlationId: string) {
|
|
1499
|
+
this.callStateForMetrics.correlationId = correlationId;
|
|
1500
|
+
}
|
|
1501
|
+
|
|
1502
|
+
/**
|
|
1503
|
+
* Set meeting info and trigger `MEETING_INFO_AVAILABLE` event
|
|
1504
|
+
* @param {any} info
|
|
1505
|
+
* @param {string} [meetingLookupUrl] Lookup url, defined when the meeting info fetched
|
|
1506
|
+
* @returns {void}
|
|
1507
|
+
*/
|
|
1508
|
+
private setMeetingInfo(info, meetingLookupUrl) {
|
|
1509
|
+
this.meetingInfo = info ? {...info, meetingLookupUrl} : null;
|
|
1510
|
+
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1511
|
+
|
|
1512
|
+
this.requiredCaptcha = null;
|
|
1513
|
+
if (
|
|
1514
|
+
this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
|
|
1515
|
+
this.passwordStatus === PASSWORD_STATUS.VERIFIED
|
|
1516
|
+
) {
|
|
1517
|
+
this.passwordStatus = PASSWORD_STATUS.VERIFIED;
|
|
1518
|
+
} else {
|
|
1519
|
+
this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
1520
|
+
}
|
|
1521
|
+
|
|
1522
|
+
Trigger.trigger(
|
|
1523
|
+
this,
|
|
1524
|
+
{
|
|
1525
|
+
file: 'meetings',
|
|
1526
|
+
function: 'fetchMeetingInfo',
|
|
1527
|
+
},
|
|
1528
|
+
EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
|
|
1529
|
+
);
|
|
1530
|
+
|
|
1531
|
+
this.updateMeetingActions();
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1534
|
+
/**
|
|
1535
|
+
* Add pre-fetched meeting info
|
|
1536
|
+
*
|
|
1537
|
+
* The passed meeting info should be be complete, e.g.: fetched after password or captcha provided
|
|
1538
|
+
*
|
|
1539
|
+
* @param {Object} meetingInfo - Complete meeting info
|
|
1540
|
+
* @param {FetchMeetingInfoParams} fetchParams - Fetch parameters for validation
|
|
1541
|
+
* @param {String|undefined} meetingLookupUrl - Lookup url, defined when the meeting info fetched
|
|
1542
|
+
* @returns {Promise<void>}
|
|
1543
|
+
*/
|
|
1544
|
+
public async injectMeetingInfo(
|
|
1545
|
+
meetingInfo: any,
|
|
1546
|
+
fetchParams: FetchMeetingInfoParams,
|
|
1547
|
+
meetingLookupUrl: string | undefined
|
|
1548
|
+
): Promise<void> {
|
|
1549
|
+
await this.prepForFetchMeetingInfo(fetchParams, 'injectMeetingInfo');
|
|
1550
|
+
|
|
1551
|
+
this.parseMeetingInfo(meetingInfo, this.destination);
|
|
1552
|
+
this.setMeetingInfo(meetingInfo, meetingLookupUrl);
|
|
1553
|
+
}
|
|
1554
|
+
|
|
1555
|
+
/**
|
|
1556
|
+
* Validate fetch parameters and clear the fetchMeetingInfoTimeout timeout
|
|
1557
|
+
*
|
|
1558
|
+
* @param {FetchMeetingInfoParams} fetchParams - fetch parameters for validation
|
|
1559
|
+
* @param {String} caller - Name of the caller for logging
|
|
1560
|
+
*
|
|
1561
|
+
* @returns {Promise<void>}
|
|
1562
|
+
* @private
|
|
1563
|
+
*/
|
|
1564
|
+
private prepForFetchMeetingInfo(
|
|
1565
|
+
{password = null, captchaCode = null, extraParams = {}}: FetchMeetingInfoParams,
|
|
1566
|
+
caller: string
|
|
1567
|
+
): Promise<void> {
|
|
1299
1568
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1300
1569
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
1301
1570
|
clearTimeout(this.fetchMeetingInfoTimeoutId);
|
|
@@ -1303,7 +1572,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1303
1572
|
}
|
|
1304
1573
|
if (captchaCode && !this.requiredCaptcha) {
|
|
1305
1574
|
return Promise.reject(
|
|
1306
|
-
new Error(
|
|
1575
|
+
new Error(`${caller}() called with captchaCode when captcha was not required`)
|
|
1307
1576
|
);
|
|
1308
1577
|
}
|
|
1309
1578
|
if (
|
|
@@ -1312,50 +1581,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1312
1581
|
this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
|
|
1313
1582
|
) {
|
|
1314
1583
|
return Promise.reject(
|
|
1315
|
-
new Error(
|
|
1584
|
+
new Error(`${caller}() called with password when password was not required`)
|
|
1316
1585
|
);
|
|
1317
1586
|
}
|
|
1318
1587
|
|
|
1588
|
+
this.meetingInfoExtraParams = cloneDeep(extraParams);
|
|
1589
|
+
|
|
1590
|
+
return Promise.resolve();
|
|
1591
|
+
}
|
|
1592
|
+
|
|
1593
|
+
/**
|
|
1594
|
+
* Internal method for fetching meeting info
|
|
1595
|
+
*
|
|
1596
|
+
* @returns {Promise}
|
|
1597
|
+
*/
|
|
1598
|
+
private async fetchMeetingInfoInternal({
|
|
1599
|
+
destination,
|
|
1600
|
+
destinationType,
|
|
1601
|
+
password = null,
|
|
1602
|
+
captchaCode = null,
|
|
1603
|
+
extraParams = {},
|
|
1604
|
+
sendCAevents = false,
|
|
1605
|
+
}): Promise<void> {
|
|
1319
1606
|
try {
|
|
1320
1607
|
const captchaInfo = captchaCode
|
|
1321
1608
|
? {code: captchaCode, id: this.requiredCaptcha.captchaId}
|
|
1322
1609
|
: null;
|
|
1323
1610
|
|
|
1324
1611
|
const info = await this.attrs.meetingInfoProvider.fetchMeetingInfo(
|
|
1325
|
-
|
|
1326
|
-
|
|
1612
|
+
destination,
|
|
1613
|
+
destinationType,
|
|
1327
1614
|
password,
|
|
1328
1615
|
captchaInfo,
|
|
1329
1616
|
// @ts-ignore - config coming from registerPlugin
|
|
1330
1617
|
this.config.installedOrgID,
|
|
1331
1618
|
this.locusId,
|
|
1332
1619
|
extraParams,
|
|
1333
|
-
{meetingId: this.id}
|
|
1334
|
-
);
|
|
1335
|
-
|
|
1336
|
-
this.parseMeetingInfo(info, this.destination);
|
|
1337
|
-
this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
|
|
1338
|
-
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1339
|
-
this.requiredCaptcha = null;
|
|
1340
|
-
if (
|
|
1341
|
-
this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
|
|
1342
|
-
this.passwordStatus === PASSWORD_STATUS.VERIFIED
|
|
1343
|
-
) {
|
|
1344
|
-
this.passwordStatus = PASSWORD_STATUS.VERIFIED;
|
|
1345
|
-
} else {
|
|
1346
|
-
this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
1347
|
-
}
|
|
1348
|
-
|
|
1349
|
-
Trigger.trigger(
|
|
1350
|
-
this,
|
|
1351
|
-
{
|
|
1352
|
-
file: 'meetings',
|
|
1353
|
-
function: 'fetchMeetingInfo',
|
|
1354
|
-
},
|
|
1355
|
-
EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
|
|
1620
|
+
{meetingId: this.id, sendCAevents}
|
|
1356
1621
|
);
|
|
1357
1622
|
|
|
1358
|
-
this.
|
|
1623
|
+
this.parseMeetingInfo(info?.body, this.destination, info?.errors);
|
|
1624
|
+
this.setMeetingInfo(info?.body, info?.url);
|
|
1359
1625
|
|
|
1360
1626
|
return Promise.resolve();
|
|
1361
1627
|
} catch (err) {
|
|
@@ -1417,19 +1683,113 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1417
1683
|
}
|
|
1418
1684
|
}
|
|
1419
1685
|
|
|
1686
|
+
/**
|
|
1687
|
+
* Refreshes the meeting info permission token (it's required for joining meetings)
|
|
1688
|
+
*
|
|
1689
|
+
* @param {string} [reason] used for metrics and logging purposes (optional)
|
|
1690
|
+
* @returns {Promise}
|
|
1691
|
+
*/
|
|
1692
|
+
public async refreshPermissionToken(reason?: string): Promise<void> {
|
|
1693
|
+
if (!this.meetingInfo?.permissionToken) {
|
|
1694
|
+
LoggerProxy.logger.info(
|
|
1695
|
+
`Meeting:index#refreshPermissionToken --> cannot refresh the permission token, because we don't have it (reason=${reason})`
|
|
1696
|
+
);
|
|
1697
|
+
|
|
1698
|
+
return;
|
|
1699
|
+
}
|
|
1700
|
+
|
|
1701
|
+
const isStartingSpaceInstantV2Meeting =
|
|
1702
|
+
this.destinationType === _CONVERSATION_URL_ &&
|
|
1703
|
+
// @ts-ignore - config coming from registerPlugin
|
|
1704
|
+
this.config.experimental.enableAdhocMeetings &&
|
|
1705
|
+
// @ts-ignore
|
|
1706
|
+
this.webex.meetings.preferredWebexSite;
|
|
1707
|
+
|
|
1708
|
+
const destination = isStartingSpaceInstantV2Meeting
|
|
1709
|
+
? this.meetingInfo.meetingJoinUrl
|
|
1710
|
+
: this.destination;
|
|
1711
|
+
const destinationType = isStartingSpaceInstantV2Meeting ? _MEETING_LINK_ : this.destinationType;
|
|
1712
|
+
|
|
1713
|
+
const permissionTokenExpiryInfo = this.getPermissionTokenExpiryInfo();
|
|
1714
|
+
|
|
1715
|
+
const timeLeft = permissionTokenExpiryInfo?.timeLeft;
|
|
1716
|
+
const expiryTime = permissionTokenExpiryInfo?.expiryTime;
|
|
1717
|
+
const currentTime = permissionTokenExpiryInfo?.currentTime;
|
|
1718
|
+
|
|
1719
|
+
LoggerProxy.logger.info(
|
|
1720
|
+
`Meeting:index#refreshPermissionToken --> refreshing permission token, destinationType=${destinationType}, timeLeft=${timeLeft}, permissionTokenExpiry=${expiryTime}, currentTimestamp=${currentTime},reason=${reason}`
|
|
1721
|
+
);
|
|
1722
|
+
|
|
1723
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH, {
|
|
1724
|
+
correlationId: this.correlationId,
|
|
1725
|
+
timeLeft,
|
|
1726
|
+
expiryTime,
|
|
1727
|
+
currentTime,
|
|
1728
|
+
reason,
|
|
1729
|
+
destinationType,
|
|
1730
|
+
});
|
|
1731
|
+
|
|
1732
|
+
try {
|
|
1733
|
+
await this.fetchMeetingInfoInternal({
|
|
1734
|
+
destination,
|
|
1735
|
+
destinationType,
|
|
1736
|
+
extraParams: {
|
|
1737
|
+
...this.meetingInfoExtraParams,
|
|
1738
|
+
permissionToken: this.meetingInfo.permissionToken,
|
|
1739
|
+
},
|
|
1740
|
+
sendCAevents: true, // because if we're refreshing the permissionToken, it means that user is intending to join that meeting, so we want CA events
|
|
1741
|
+
});
|
|
1742
|
+
} catch (error) {
|
|
1743
|
+
LoggerProxy.logger.info(
|
|
1744
|
+
'Meeting:index#refreshPermissionToken --> failed to refresh the permission token:',
|
|
1745
|
+
error
|
|
1746
|
+
);
|
|
1747
|
+
|
|
1748
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.PERMISSION_TOKEN_REFRESH_ERROR, {
|
|
1749
|
+
correlationId: this.correlationId,
|
|
1750
|
+
reason: error.message,
|
|
1751
|
+
stack: error.stack,
|
|
1752
|
+
});
|
|
1753
|
+
|
|
1754
|
+
throw error;
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
|
|
1758
|
+
/**
|
|
1759
|
+
* Fetches meeting information.
|
|
1760
|
+
* @param {Object} options
|
|
1761
|
+
* @param {String} [options.password] optional
|
|
1762
|
+
* @param {String} [options.captchaCode] optional
|
|
1763
|
+
* @param {Boolean} [options.sendCAevents] optional - Whether to submit Call Analyzer events or not. Default: false.
|
|
1764
|
+
* @public
|
|
1765
|
+
* @memberof Meeting
|
|
1766
|
+
* @returns {Promise}
|
|
1767
|
+
*/
|
|
1768
|
+
public async fetchMeetingInfo(options: FetchMeetingInfoParams) {
|
|
1769
|
+
await this.prepForFetchMeetingInfo(options, 'fetchMeetingInfo');
|
|
1770
|
+
|
|
1771
|
+
return this.fetchMeetingInfoInternal({
|
|
1772
|
+
destination: this.destination,
|
|
1773
|
+
destinationType: this.destinationType,
|
|
1774
|
+
...options,
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1420
1778
|
/**
|
|
1421
1779
|
* Checks if the supplied password/host key is correct. It returns a promise with information whether the
|
|
1422
1780
|
* password and captcha code were correct or not.
|
|
1423
1781
|
* @param {String} password - this can be either a password or a host key, can be undefined if only captcha was required
|
|
1424
1782
|
* @param {String} captchaCode - can be undefined if captcha was not required by the server
|
|
1783
|
+
* @param {Boolean} sendCAevents - whether Call Analyzer events should be sent when fetching meeting information
|
|
1425
1784
|
* @public
|
|
1426
1785
|
* @memberof Meeting
|
|
1427
1786
|
* @returns {Promise<{isPasswordValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
|
|
1428
1787
|
*/
|
|
1429
|
-
public verifyPassword(password: string, captchaCode: string) {
|
|
1788
|
+
public verifyPassword(password: string, captchaCode: string, sendCAevents = false) {
|
|
1430
1789
|
return this.fetchMeetingInfo({
|
|
1431
1790
|
password,
|
|
1432
1791
|
captchaCode,
|
|
1792
|
+
sendCAevents,
|
|
1433
1793
|
})
|
|
1434
1794
|
.then(() => {
|
|
1435
1795
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS);
|
|
@@ -1629,6 +1989,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1629
1989
|
* @memberof Meeting
|
|
1630
1990
|
*/
|
|
1631
1991
|
private setUpInterpretationListener() {
|
|
1992
|
+
// TODO: check if its getting used or not
|
|
1632
1993
|
this.simultaneousInterpretation.on(INTERPRETATION.EVENTS.SUPPORT_LANGUAGES_UPDATE, () => {
|
|
1633
1994
|
Trigger.trigger(
|
|
1634
1995
|
this,
|
|
@@ -1639,7 +2000,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1639
2000
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE
|
|
1640
2001
|
);
|
|
1641
2002
|
});
|
|
1642
|
-
|
|
2003
|
+
// TODO: check if its getting used or not
|
|
1643
2004
|
this.simultaneousInterpretation.on(
|
|
1644
2005
|
INTERPRETATION.EVENTS.HANDOFF_REQUESTS_ARRIVED,
|
|
1645
2006
|
(payload) => {
|
|
@@ -1656,6 +2017,43 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1656
2017
|
);
|
|
1657
2018
|
}
|
|
1658
2019
|
|
|
2020
|
+
/**
|
|
2021
|
+
* Set up the listeners for captions
|
|
2022
|
+
* @returns {undefined}
|
|
2023
|
+
* @private
|
|
2024
|
+
* @memberof Meeting
|
|
2025
|
+
*/
|
|
2026
|
+
private setUpVoiceaListeners() {
|
|
2027
|
+
// @ts-ignore
|
|
2028
|
+
this.webex.internal.voicea.listenToEvents();
|
|
2029
|
+
|
|
2030
|
+
// @ts-ignore
|
|
2031
|
+
this.webex.internal.voicea.on(
|
|
2032
|
+
VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
|
|
2033
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
|
|
2034
|
+
);
|
|
2035
|
+
|
|
2036
|
+
// @ts-ignore
|
|
2037
|
+
this.webex.internal.voicea.on(
|
|
2038
|
+
VOICEAEVENTS.CAPTIONS_TURNED_ON,
|
|
2039
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
|
|
2040
|
+
);
|
|
2041
|
+
|
|
2042
|
+
// @ts-ignore
|
|
2043
|
+
this.webex.internal.voicea.on(
|
|
2044
|
+
VOICEAEVENTS.EVA_COMMAND,
|
|
2045
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
|
|
2046
|
+
);
|
|
2047
|
+
|
|
2048
|
+
// @ts-ignore
|
|
2049
|
+
this.webex.internal.voicea.on(
|
|
2050
|
+
VOICEAEVENTS.NEW_CAPTION,
|
|
2051
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
|
|
2052
|
+
);
|
|
2053
|
+
|
|
2054
|
+
this.areVoiceaEventsSetup = true;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
1659
2057
|
/**
|
|
1660
2058
|
* Set up the locus info listener for meetings disconnected due to inactivity
|
|
1661
2059
|
* @returns {undefined}
|
|
@@ -1760,12 +2158,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1760
2158
|
|
|
1761
2159
|
/**
|
|
1762
2160
|
* sets the network status on meeting object
|
|
1763
|
-
* @param {
|
|
2161
|
+
* @param {NETWORK_STATUS} networkStatus
|
|
1764
2162
|
* @private
|
|
1765
2163
|
* @returns {undefined}
|
|
1766
2164
|
* @memberof Meeting
|
|
1767
2165
|
*/
|
|
1768
|
-
private setNetworkStatus(networkStatus
|
|
2166
|
+
private setNetworkStatus(networkStatus?: NETWORK_STATUS) {
|
|
1769
2167
|
if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
|
|
1770
2168
|
Trigger.trigger(
|
|
1771
2169
|
this,
|
|
@@ -1945,7 +2343,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1945
2343
|
modifiedBy,
|
|
1946
2344
|
lastModified,
|
|
1947
2345
|
};
|
|
1948
|
-
|
|
1949
2346
|
Trigger.trigger(
|
|
1950
2347
|
this,
|
|
1951
2348
|
{
|
|
@@ -1976,19 +2373,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1976
2373
|
this.locusInfo.on(
|
|
1977
2374
|
LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIBE_UPDATED,
|
|
1978
2375
|
({caption, transcribing}) => {
|
|
1979
|
-
//
|
|
1980
|
-
if (
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
2376
|
+
// user need to be joined to start the llm and receive transcription
|
|
2377
|
+
if (this.isJoined()) {
|
|
2378
|
+
// @ts-ignore - config coming from registerPlugin
|
|
2379
|
+
if (transcribing && !this.transcription) {
|
|
2380
|
+
this.startTranscription();
|
|
2381
|
+
} else if (!transcribing && this.transcription) {
|
|
2382
|
+
Trigger.trigger(
|
|
2383
|
+
this,
|
|
2384
|
+
{
|
|
2385
|
+
file: 'meeting/index',
|
|
2386
|
+
function: 'setupLocusControlsListener',
|
|
2387
|
+
},
|
|
2388
|
+
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION,
|
|
2389
|
+
{caption, transcribing}
|
|
2390
|
+
);
|
|
2391
|
+
}
|
|
1992
2392
|
}
|
|
1993
2393
|
}
|
|
1994
2394
|
);
|
|
@@ -2020,16 +2420,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2020
2420
|
}
|
|
2021
2421
|
);
|
|
2022
2422
|
|
|
2023
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
|
|
2024
|
-
this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
|
|
2025
|
-
// clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
|
|
2026
|
-
// which means main session is not active for the attendee
|
|
2027
|
-
if (error?.statusCode === 403) {
|
|
2028
|
-
this.locusInfo.clearMainSessionLocusCache();
|
|
2029
|
-
}
|
|
2030
|
-
});
|
|
2031
|
-
});
|
|
2032
|
-
|
|
2033
2423
|
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
|
|
2034
2424
|
Trigger.trigger(
|
|
2035
2425
|
this,
|
|
@@ -2153,6 +2543,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2153
2543
|
if (
|
|
2154
2544
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
2155
2545
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
2546
|
+
contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
|
|
2156
2547
|
whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
|
|
2157
2548
|
whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
|
|
2158
2549
|
whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
|
|
@@ -2175,11 +2566,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2175
2566
|
// LOCAL - check if we started sharing content
|
|
2176
2567
|
else if (
|
|
2177
2568
|
this.selfId === contentShare.beneficiaryId &&
|
|
2178
|
-
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
2569
|
+
contentShare.disposition === FLOOR_ACTION.GRANTED &&
|
|
2570
|
+
contentShare.deviceUrlSharing === this.deviceUrl
|
|
2179
2571
|
) {
|
|
2180
2572
|
// CONTENT - sharing content local
|
|
2181
2573
|
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
2182
2574
|
}
|
|
2575
|
+
// SAME USER REMOTE - check if same user started sharing content from another client
|
|
2576
|
+
else if (
|
|
2577
|
+
this.selfId === contentShare.beneficiaryId &&
|
|
2578
|
+
contentShare.disposition === FLOOR_ACTION.GRANTED &&
|
|
2579
|
+
contentShare.deviceUrlSharing !== this.deviceUrl
|
|
2580
|
+
) {
|
|
2581
|
+
// CONTENT - same user sharing content remote
|
|
2582
|
+
newShareStatus = SHARE_STATUS.REMOTE_SHARE_ACTIVE;
|
|
2583
|
+
}
|
|
2183
2584
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
2184
2585
|
// There is no concept of local/remote share for whiteboard
|
|
2185
2586
|
// It does not matter who requested to share the whiteboard, everyone gets the same view
|
|
@@ -2253,6 +2654,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2253
2654
|
switch (newShareStatus) {
|
|
2254
2655
|
case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
|
|
2255
2656
|
const sendStartedSharingRemote = () => {
|
|
2657
|
+
this.remoteShareInstanceId = contentShare.shareInstanceId;
|
|
2658
|
+
|
|
2256
2659
|
Trigger.trigger(
|
|
2257
2660
|
this,
|
|
2258
2661
|
{
|
|
@@ -2263,7 +2666,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2263
2666
|
{
|
|
2264
2667
|
memberId: contentShare.beneficiaryId,
|
|
2265
2668
|
url: contentShare.url,
|
|
2266
|
-
shareInstanceId:
|
|
2669
|
+
shareInstanceId: this.remoteShareInstanceId,
|
|
2267
2670
|
annotationInfo: contentShare.annotation,
|
|
2268
2671
|
}
|
|
2269
2672
|
);
|
|
@@ -2300,6 +2703,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2300
2703
|
name: 'client.share.floor-granted.local',
|
|
2301
2704
|
payload: {
|
|
2302
2705
|
mediaType: 'share',
|
|
2706
|
+
shareInstanceId: this.localShareInstanceId,
|
|
2303
2707
|
},
|
|
2304
2708
|
options: {meetingId: this.id},
|
|
2305
2709
|
});
|
|
@@ -2342,6 +2746,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2342
2746
|
} else if (newShareStatus === SHARE_STATUS.REMOTE_SHARE_ACTIVE) {
|
|
2343
2747
|
// if we got here, then some remote participant has stolen
|
|
2344
2748
|
// the presentation from another remote participant
|
|
2749
|
+
this.remoteShareInstanceId = contentShare.shareInstanceId;
|
|
2750
|
+
|
|
2345
2751
|
Trigger.trigger(
|
|
2346
2752
|
this,
|
|
2347
2753
|
{
|
|
@@ -2352,7 +2758,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2352
2758
|
{
|
|
2353
2759
|
memberId: contentShare.beneficiaryId,
|
|
2354
2760
|
url: contentShare.url,
|
|
2355
|
-
shareInstanceId:
|
|
2761
|
+
shareInstanceId: this.remoteShareInstanceId,
|
|
2356
2762
|
annotationInfo: contentShare.annotation,
|
|
2357
2763
|
}
|
|
2358
2764
|
);
|
|
@@ -2404,6 +2810,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2404
2810
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2405
2811
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2406
2812
|
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2813
|
+
this.webinar.locusUrlUpdate(payload);
|
|
2407
2814
|
|
|
2408
2815
|
Trigger.trigger(
|
|
2409
2816
|
this,
|
|
@@ -2433,6 +2840,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2433
2840
|
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2434
2841
|
this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2435
2842
|
this.simultaneousInterpretation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2843
|
+
this.webinar.webcastUrlUpdate(payload?.services?.webcast?.url);
|
|
2844
|
+
this.webinar.webinarAttendeesSearchingUrlUpdate(
|
|
2845
|
+
payload?.services?.webinarAttendeesSearching?.url
|
|
2846
|
+
);
|
|
2436
2847
|
});
|
|
2437
2848
|
}
|
|
2438
2849
|
|
|
@@ -2473,12 +2884,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2473
2884
|
);
|
|
2474
2885
|
}
|
|
2475
2886
|
});
|
|
2476
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_INFO_UPDATED, () => {
|
|
2887
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_INFO_UPDATED, ({isInitializing}) => {
|
|
2477
2888
|
this.updateMeetingActions();
|
|
2478
2889
|
this.recordingController.setDisplayHints(this.userDisplayHints);
|
|
2479
2890
|
this.recordingController.setUserPolicy(this.selfUserPolicies);
|
|
2480
2891
|
this.controlsOptionsManager.setDisplayHints(this.userDisplayHints);
|
|
2481
2892
|
this.handleDataChannelUrlChange(this.datachannelUrl);
|
|
2893
|
+
|
|
2894
|
+
if (!isInitializing) {
|
|
2895
|
+
// send updated trigger only if locus is not initializing the meeting
|
|
2896
|
+
Trigger.trigger(
|
|
2897
|
+
this,
|
|
2898
|
+
{
|
|
2899
|
+
file: 'meetings',
|
|
2900
|
+
function: 'setUpLocusInfoMeetingInfoListener',
|
|
2901
|
+
},
|
|
2902
|
+
EVENT_TRIGGERS.MEETING_INFO_UPDATED
|
|
2903
|
+
);
|
|
2904
|
+
}
|
|
2482
2905
|
});
|
|
2483
2906
|
}
|
|
2484
2907
|
|
|
@@ -2624,7 +3047,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2624
3047
|
});
|
|
2625
3048
|
}
|
|
2626
3049
|
});
|
|
2627
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3050
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
|
|
2628
3051
|
this.stopKeepAlive();
|
|
2629
3052
|
|
|
2630
3053
|
if (payload) {
|
|
@@ -2723,6 +3146,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2723
3146
|
this.simultaneousInterpretation.updateCanManageInterpreters(
|
|
2724
3147
|
payload.newRoles?.includes(SELF_ROLES.MODERATOR)
|
|
2725
3148
|
);
|
|
3149
|
+
this.webinar.updateCanManageWebcast(payload.newRoles?.includes(SELF_ROLES.MODERATOR));
|
|
2726
3150
|
Trigger.trigger(
|
|
2727
3151
|
this,
|
|
2728
3152
|
{
|
|
@@ -2964,30 +3388,40 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2964
3388
|
/**
|
|
2965
3389
|
* Sets the meeting info on the class instance
|
|
2966
3390
|
* @param {Object} meetingInfo
|
|
2967
|
-
* @param {
|
|
2968
|
-
* @param {String} meetingInfo.
|
|
2969
|
-
* @param {String} meetingInfo.
|
|
2970
|
-
* @param {String} meetingInfo.
|
|
2971
|
-
* @param {
|
|
3391
|
+
* @param {String} meetingInfo.conversationUrl
|
|
3392
|
+
* @param {String} meetingInfo.locusUrl
|
|
3393
|
+
* @param {String} meetingInfo.sipUri
|
|
3394
|
+
* @param {String} [meetingInfo.sipUrl]
|
|
3395
|
+
* @param {String} [meetingInfo.sipMeetingUri]
|
|
3396
|
+
* @param {String} [meetingInfo.meetingNumber]
|
|
3397
|
+
* @param {String} [meetingInfo.meetingJoinUrl]
|
|
3398
|
+
* @param {String} [meetingInfo.hostId]
|
|
3399
|
+
* @param {String} [meetingInfo.permissionToken]
|
|
3400
|
+
* @param {String} [meetingInfo.channel]
|
|
3401
|
+
* @param {Object} meetingInfo.owner
|
|
2972
3402
|
* @param {Object | String} destination locus object with meeting data or destination string (sip url, meeting link, etc)
|
|
3403
|
+
* @param {Object | String} errors Meeting info request error
|
|
2973
3404
|
* @returns {undefined}
|
|
2974
3405
|
* @private
|
|
2975
3406
|
* @memberof Meeting
|
|
2976
3407
|
*/
|
|
2977
3408
|
parseMeetingInfo(
|
|
2978
|
-
meetingInfo:
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
3409
|
+
meetingInfo: {
|
|
3410
|
+
conversationUrl: string;
|
|
3411
|
+
locusUrl: string;
|
|
3412
|
+
sipUri: string;
|
|
3413
|
+
owner: object;
|
|
3414
|
+
sipUrl?: string;
|
|
3415
|
+
sipMeetingUri?: string;
|
|
3416
|
+
meetingNumber?: string;
|
|
3417
|
+
meetingJoinUrl?: string;
|
|
3418
|
+
hostId?: string;
|
|
3419
|
+
permissionToken?: string;
|
|
3420
|
+
channel?: string;
|
|
3421
|
+
},
|
|
3422
|
+
destination: object | string | null = null,
|
|
3423
|
+
errors: any = undefined
|
|
2989
3424
|
) {
|
|
2990
|
-
const webexMeetingInfo = meetingInfo?.body;
|
|
2991
3425
|
// We try to use as much info from Locus meeting object, stored in destination
|
|
2992
3426
|
|
|
2993
3427
|
let locusMeetingObject;
|
|
@@ -2997,40 +3431,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2997
3431
|
}
|
|
2998
3432
|
|
|
2999
3433
|
// MeetingInfo will be undefined for 1:1 calls
|
|
3000
|
-
if (
|
|
3001
|
-
locusMeetingObject ||
|
|
3002
|
-
(webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))
|
|
3003
|
-
) {
|
|
3434
|
+
if (locusMeetingObject || (meetingInfo && !(errors?.length > 0))) {
|
|
3004
3435
|
this.conversationUrl =
|
|
3005
|
-
locusMeetingObject?.conversationUrl ||
|
|
3006
|
-
|
|
3007
|
-
this.conversationUrl;
|
|
3008
|
-
this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
|
|
3436
|
+
locusMeetingObject?.conversationUrl || meetingInfo?.conversationUrl || this.conversationUrl;
|
|
3437
|
+
this.locusUrl = locusMeetingObject?.url || meetingInfo?.locusUrl || this.locusUrl;
|
|
3009
3438
|
// @ts-ignore - config coming from registerPlugin
|
|
3010
3439
|
this.setSipUri(
|
|
3011
3440
|
// @ts-ignore
|
|
3012
3441
|
this.config.experimental.enableUnifiedMeetings
|
|
3013
|
-
? locusMeetingObject?.info.sipUri ||
|
|
3014
|
-
: locusMeetingObject?.info.sipUri ||
|
|
3442
|
+
? locusMeetingObject?.info.sipUri || meetingInfo?.sipUrl
|
|
3443
|
+
: locusMeetingObject?.info.sipUri || meetingInfo?.sipMeetingUri || this.sipUri
|
|
3015
3444
|
);
|
|
3016
3445
|
// @ts-ignore - config coming from registerPlugin
|
|
3017
3446
|
if (this.config.experimental.enableUnifiedMeetings) {
|
|
3018
|
-
this.meetingNumber =
|
|
3019
|
-
|
|
3020
|
-
this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
|
|
3447
|
+
this.meetingNumber = locusMeetingObject?.info.webExMeetingId || meetingInfo?.meetingNumber;
|
|
3448
|
+
this.meetingJoinUrl = meetingInfo?.meetingJoinUrl;
|
|
3021
3449
|
}
|
|
3022
3450
|
this.owner =
|
|
3023
|
-
locusMeetingObject?.info.owner ||
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
this.owner;
|
|
3027
|
-
this.permissionToken = webexMeetingInfo?.permissionToken;
|
|
3028
|
-
this.setPermissionTokenPayload(webexMeetingInfo?.permissionToken);
|
|
3451
|
+
locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
|
|
3452
|
+
this.permissionToken = meetingInfo?.permissionToken;
|
|
3453
|
+
this.setPermissionTokenPayload(meetingInfo?.permissionToken);
|
|
3029
3454
|
this.setSelfUserPolicies();
|
|
3030
3455
|
// Need to populate environment when sending CA event
|
|
3031
|
-
this.environment = locusMeetingObject?.info.channel ||
|
|
3456
|
+
this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
|
|
3032
3457
|
}
|
|
3033
|
-
MeetingUtil.parseInterpretationInfo(this,
|
|
3458
|
+
MeetingUtil.parseInterpretationInfo(this, meetingInfo);
|
|
3034
3459
|
}
|
|
3035
3460
|
|
|
3036
3461
|
/**
|
|
@@ -3082,6 +3507,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3082
3507
|
}) &&
|
|
3083
3508
|
this.meetingInfo?.video?.supportHDV) ||
|
|
3084
3509
|
!this.arePolicyRestrictionsSupported(),
|
|
3510
|
+
enforceVirtualBackground:
|
|
3511
|
+
ControlsOptionsUtil.hasPolicies({
|
|
3512
|
+
requiredPolicies: [SELF_POLICY.ENFORCE_VIRTUAL_BACKGROUND],
|
|
3513
|
+
policies: this.selfUserPolicies,
|
|
3514
|
+
}) && this.arePolicyRestrictionsSupported(),
|
|
3085
3515
|
supportHQV:
|
|
3086
3516
|
(ControlsOptionsUtil.hasPolicies({
|
|
3087
3517
|
requiredPolicies: [SELF_POLICY.SUPPORT_HQV],
|
|
@@ -3235,6 +3665,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3235
3665
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
|
3236
3666
|
policies: this.selfUserPolicies,
|
|
3237
3667
|
}),
|
|
3668
|
+
canChat: ControlsOptionsUtil.hasPolicies({
|
|
3669
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
|
|
3670
|
+
policies: this.selfUserPolicies,
|
|
3671
|
+
}),
|
|
3238
3672
|
canShareApplication:
|
|
3239
3673
|
(ControlsOptionsUtil.hasHints({
|
|
3240
3674
|
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
@@ -3294,6 +3728,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3294
3728
|
*/
|
|
3295
3729
|
setSelfUserPolicies() {
|
|
3296
3730
|
this.selfUserPolicies = this.permissionTokenPayload?.permission?.userPolicies;
|
|
3731
|
+
this.enforceVBGImagesURL = this.permissionTokenPayload?.permission?.enforceVBGImagesURL;
|
|
3297
3732
|
}
|
|
3298
3733
|
|
|
3299
3734
|
/**
|
|
@@ -3303,7 +3738,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3303
3738
|
* @returns {void}
|
|
3304
3739
|
*/
|
|
3305
3740
|
public setPermissionTokenPayload(permissionToken: string) {
|
|
3306
|
-
this.permissionTokenPayload =
|
|
3741
|
+
this.permissionTokenPayload = jwtDecode(permissionToken);
|
|
3742
|
+
this.permissionTokenReceivedLocalTime = new Date().getTime();
|
|
3307
3743
|
}
|
|
3308
3744
|
|
|
3309
3745
|
/**
|
|
@@ -3397,8 +3833,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3397
3833
|
* @memberof Meeting
|
|
3398
3834
|
*/
|
|
3399
3835
|
closeRemoteStreams() {
|
|
3400
|
-
const {remoteAudioStream, remoteVideoStream, remoteShareStream
|
|
3401
|
-
this.mediaProperties;
|
|
3836
|
+
const {remoteAudioStream, remoteVideoStream, remoteShareStream} = this.mediaProperties;
|
|
3402
3837
|
|
|
3403
3838
|
/**
|
|
3404
3839
|
* Triggers an event to the developer
|
|
@@ -3439,7 +3874,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3439
3874
|
stopStream(remoteAudioStream, EVENT_TYPES.REMOTE_AUDIO),
|
|
3440
3875
|
stopStream(remoteVideoStream, EVENT_TYPES.REMOTE_VIDEO),
|
|
3441
3876
|
stopStream(remoteShareStream, EVENT_TYPES.REMOTE_SHARE),
|
|
3442
|
-
stopStream(shareAudioStream, EVENT_TYPES.REMOTE_SHARE_AUDIO),
|
|
3443
3877
|
]);
|
|
3444
3878
|
}
|
|
3445
3879
|
|
|
@@ -3510,11 +3944,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3510
3944
|
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3511
3945
|
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3512
3946
|
|
|
3947
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
|
|
3513
3948
|
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3514
3949
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3515
3950
|
|
|
3516
3951
|
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3517
3952
|
|
|
3953
|
+
localDisplayStream?.on(
|
|
3954
|
+
StreamEventNames.MuteStateChange,
|
|
3955
|
+
this.handleShareVideoStreamMuteStateChange
|
|
3956
|
+
);
|
|
3518
3957
|
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3519
3958
|
localDisplayStream?.on(
|
|
3520
3959
|
LocalStreamEventNames.OutputTrackChange,
|
|
@@ -3570,7 +4009,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3570
4009
|
functionName: string;
|
|
3571
4010
|
isPublished: boolean;
|
|
3572
4011
|
mediaType: MediaType;
|
|
3573
|
-
stream:
|
|
4012
|
+
stream: LocalStream;
|
|
3574
4013
|
}) {
|
|
3575
4014
|
const {functionName, isPublished, mediaType, stream} = options;
|
|
3576
4015
|
Trigger.trigger(
|
|
@@ -3604,12 +4043,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3604
4043
|
videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3605
4044
|
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3606
4045
|
|
|
3607
|
-
shareAudioStream?.off(StreamEventNames.
|
|
4046
|
+
shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3608
4047
|
shareAudioStream?.off(
|
|
3609
4048
|
LocalStreamEventNames.OutputTrackChange,
|
|
3610
4049
|
this.localOutputTrackChangeHandler
|
|
3611
4050
|
);
|
|
3612
|
-
shareVideoStream?.off(
|
|
4051
|
+
shareVideoStream?.off(
|
|
4052
|
+
StreamEventNames.MuteStateChange,
|
|
4053
|
+
this.handleShareVideoStreamMuteStateChange
|
|
4054
|
+
);
|
|
4055
|
+
shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3613
4056
|
shareVideoStream?.off(
|
|
3614
4057
|
LocalStreamEventNames.OutputTrackChange,
|
|
3615
4058
|
this.localOutputTrackChangeHandler
|
|
@@ -3723,6 +4166,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3723
4166
|
this.receiveSlotManager.reset();
|
|
3724
4167
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3725
4168
|
this.sendSlotManager.reset();
|
|
4169
|
+
this.setNetworkStatus(undefined);
|
|
3726
4170
|
}
|
|
3727
4171
|
|
|
3728
4172
|
this.audio = null;
|
|
@@ -3744,18 +4188,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3744
4188
|
if (this.config.reconnection.detection) {
|
|
3745
4189
|
// @ts-ignore
|
|
3746
4190
|
this.webex.internal.mercury.off(ONLINE);
|
|
4191
|
+
// @ts-ignore
|
|
4192
|
+
this.webex.internal.mercury.off(OFFLINE);
|
|
3747
4193
|
}
|
|
3748
4194
|
}
|
|
3749
4195
|
|
|
3750
4196
|
/**
|
|
3751
|
-
* Convenience method to set the correlation id for the
|
|
3752
|
-
* @param {String} id correlation id to set on the
|
|
4197
|
+
* Convenience method to set the correlation id for the callStateForMetrics
|
|
4198
|
+
* @param {String} id correlation id to set on the callStateForMetrics
|
|
3753
4199
|
* @returns {undefined}
|
|
3754
|
-
* @
|
|
4200
|
+
* @public
|
|
4201
|
+
* @memberof Meeting
|
|
4202
|
+
*/
|
|
4203
|
+
public setCorrelationId(id: string) {
|
|
4204
|
+
this.callStateForMetrics.correlationId = id;
|
|
4205
|
+
}
|
|
4206
|
+
|
|
4207
|
+
/**
|
|
4208
|
+
* Update the callStateForMetrics
|
|
4209
|
+
* @param {CallStateForMetrics} callStateForMetrics updated values for callStateForMetrics
|
|
4210
|
+
* @returns {undefined}
|
|
4211
|
+
* @public
|
|
3755
4212
|
* @memberof Meeting
|
|
3756
4213
|
*/
|
|
3757
|
-
|
|
3758
|
-
this.
|
|
4214
|
+
public updateCallStateForMetrics(callStateForMetrics: CallStateForMetrics) {
|
|
4215
|
+
this.callStateForMetrics = {...this.callStateForMetrics, ...callStateForMetrics};
|
|
3759
4216
|
}
|
|
3760
4217
|
|
|
3761
4218
|
/**
|
|
@@ -3992,6 +4449,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3992
4449
|
) {
|
|
3993
4450
|
const {mediaOptions, joinOptions} = options;
|
|
3994
4451
|
|
|
4452
|
+
if (!mediaOptions?.allowMediaInLobby) {
|
|
4453
|
+
return Promise.reject(
|
|
4454
|
+
new ParameterError('joinWithMedia() can only be used with allowMediaInLobby set to true')
|
|
4455
|
+
);
|
|
4456
|
+
}
|
|
4457
|
+
|
|
4458
|
+
LoggerProxy.logger.info('Meeting:index#joinWithMedia called');
|
|
4459
|
+
|
|
3995
4460
|
return this.join(joinOptions)
|
|
3996
4461
|
.then((joinResponse) =>
|
|
3997
4462
|
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
@@ -4073,6 +4538,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4073
4538
|
|
|
4074
4539
|
return this.reconnectionManager
|
|
4075
4540
|
.reconnect(options)
|
|
4541
|
+
.then(() => this.waitForRemoteSDPAnswer())
|
|
4542
|
+
.then(() => this.waitForMediaConnectionConnected())
|
|
4076
4543
|
.then(() => {
|
|
4077
4544
|
Trigger.trigger(
|
|
4078
4545
|
this,
|
|
@@ -4083,6 +4550,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4083
4550
|
EVENT_TRIGGERS.MEETING_RECONNECTION_SUCCESS
|
|
4084
4551
|
);
|
|
4085
4552
|
LoggerProxy.logger.log('Meeting:index#reconnect --> Meeting reconnect success');
|
|
4553
|
+
|
|
4554
|
+
// @ts-ignore
|
|
4555
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
4556
|
+
name: 'client.media.recovered',
|
|
4557
|
+
payload: {
|
|
4558
|
+
recoveredBy: 'new',
|
|
4559
|
+
},
|
|
4560
|
+
options: {
|
|
4561
|
+
meetingId: this.id,
|
|
4562
|
+
},
|
|
4563
|
+
});
|
|
4564
|
+
this.reconnectionManager.setStatus(RECONNECTION.STATE.COMPLETE);
|
|
4086
4565
|
})
|
|
4087
4566
|
.catch((error) => {
|
|
4088
4567
|
Trigger.trigger(
|
|
@@ -4129,7 +4608,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4129
4608
|
}
|
|
4130
4609
|
|
|
4131
4610
|
LoggerProxy.logger.error(
|
|
4132
|
-
'Meeting:index#isTranscriptionSupported --> Webex Assistant is not supported'
|
|
4611
|
+
'Meeting:index#isTranscriptionSupported --> Webex Assistant is not enabled/supported'
|
|
4133
4612
|
);
|
|
4134
4613
|
|
|
4135
4614
|
return false;
|
|
@@ -4150,109 +4629,139 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4150
4629
|
}
|
|
4151
4630
|
|
|
4152
4631
|
/**
|
|
4153
|
-
*
|
|
4154
|
-
* @
|
|
4155
|
-
* @returns {
|
|
4632
|
+
* sets Caption language for the meeting
|
|
4633
|
+
* @param {string} language
|
|
4634
|
+
* @returns {Promise}
|
|
4156
4635
|
*/
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
${event}`
|
|
4164
|
-
);
|
|
4636
|
+
public setCaptionLanguage(language: string) {
|
|
4637
|
+
return new Promise((resolve, reject) => {
|
|
4638
|
+
if (!this.isTranscriptionSupported()) {
|
|
4639
|
+
LoggerProxy.logger.error(
|
|
4640
|
+
'Meeting:index#setCaptionLanguage --> Webex Assistant is not enabled/supported'
|
|
4641
|
+
);
|
|
4165
4642
|
|
|
4166
|
-
|
|
4167
|
-
|
|
4643
|
+
reject(new Error('Webex Assistant is not enabled/supported'));
|
|
4644
|
+
}
|
|
4168
4645
|
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4646
|
+
try {
|
|
4647
|
+
const voiceaListenerCaptionUpdate = (payload) => {
|
|
4648
|
+
// @ts-ignore
|
|
4649
|
+
this.webex.internal.voicea.off(
|
|
4650
|
+
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
|
|
4651
|
+
voiceaListenerCaptionUpdate
|
|
4652
|
+
);
|
|
4653
|
+
const {statusCode} = payload;
|
|
4176
4654
|
|
|
4177
|
-
|
|
4655
|
+
if (statusCode === 200) {
|
|
4656
|
+
this.transcription.languageOptions = {
|
|
4657
|
+
...this.transcription.languageOptions,
|
|
4658
|
+
currentCaptionLanguage: language,
|
|
4659
|
+
};
|
|
4660
|
+
resolve(language);
|
|
4661
|
+
} else {
|
|
4662
|
+
reject(payload);
|
|
4663
|
+
}
|
|
4664
|
+
};
|
|
4665
|
+
// @ts-ignore
|
|
4666
|
+
this.webex.internal.voicea.on(
|
|
4667
|
+
VOICEAEVENTS.CAPTION_LANGUAGE_UPDATE,
|
|
4668
|
+
voiceaListenerCaptionUpdate
|
|
4669
|
+
);
|
|
4670
|
+
// @ts-ignore
|
|
4671
|
+
this.webex.internal.voicea.requestLanguage(language);
|
|
4672
|
+
} catch (error) {
|
|
4673
|
+
LoggerProxy.logger.error(`Meeting:index#setCaptionLanguage --> ${error}`);
|
|
4178
4674
|
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
reason: 'unexpected error: transcription LLM web socket connection error had occured.',
|
|
4182
|
-
event,
|
|
4183
|
-
});
|
|
4675
|
+
reject(error);
|
|
4676
|
+
}
|
|
4184
4677
|
});
|
|
4185
4678
|
}
|
|
4186
4679
|
|
|
4187
4680
|
/**
|
|
4188
|
-
*
|
|
4189
|
-
* @
|
|
4190
|
-
* @returns {Promise
|
|
4681
|
+
* sets Spoken language for the meeting
|
|
4682
|
+
* @param {string} language
|
|
4683
|
+
* @returns {Promise}
|
|
4191
4684
|
*/
|
|
4192
|
-
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4685
|
+
public setSpokenLanguage(language: string) {
|
|
4686
|
+
return new Promise((resolve, reject) => {
|
|
4687
|
+
if (!this.isTranscriptionSupported()) {
|
|
4688
|
+
LoggerProxy.logger.error(
|
|
4689
|
+
'Meeting:index#setCaptionLanguage --> Webex Assistant is not enabled/supported'
|
|
4690
|
+
);
|
|
4691
|
+
|
|
4692
|
+
reject(new Error('Webex Assistant is not enabled/supported'));
|
|
4693
|
+
}
|
|
4694
|
+
|
|
4695
|
+
try {
|
|
4696
|
+
const voiceaListenerLanguageUpdate = (payload) => {
|
|
4697
|
+
// @ts-ignore
|
|
4698
|
+
this.webex.internal.voicea.off(
|
|
4699
|
+
VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
|
|
4700
|
+
voiceaListenerLanguageUpdate
|
|
4701
|
+
);
|
|
4702
|
+
const {languageCode} = payload;
|
|
4703
|
+
|
|
4704
|
+
if (languageCode) {
|
|
4705
|
+
this.transcription.languageOptions = {
|
|
4706
|
+
...this.transcription.languageOptions,
|
|
4707
|
+
currentSpokenLanguage: languageCode,
|
|
4708
|
+
};
|
|
4709
|
+
resolve(languageCode);
|
|
4710
|
+
} else {
|
|
4711
|
+
reject(payload);
|
|
4712
|
+
}
|
|
4713
|
+
};
|
|
4197
4714
|
|
|
4198
|
-
try {
|
|
4199
|
-
const {datachannelUrl} = this.locusInfo.info;
|
|
4200
|
-
// @ts-ignore - fix type
|
|
4201
|
-
const {
|
|
4202
|
-
body: {webSocketUrl},
|
|
4203
4715
|
// @ts-ignore
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
});
|
|
4716
|
+
this.webex.internal.voicea.on(
|
|
4717
|
+
VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
|
|
4718
|
+
voiceaListenerLanguageUpdate
|
|
4719
|
+
);
|
|
4209
4720
|
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4721
|
+
// @ts-ignore
|
|
4722
|
+
this.webex.internal.voicea.setSpokenLanguage(language);
|
|
4723
|
+
} catch (error) {
|
|
4724
|
+
LoggerProxy.logger.error(`Meeting:index#setSpokenLanguage --> ${error}`);
|
|
4214
4725
|
|
|
4215
|
-
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
this.members
|
|
4220
|
-
);
|
|
4726
|
+
reject(error);
|
|
4727
|
+
}
|
|
4728
|
+
});
|
|
4729
|
+
}
|
|
4221
4730
|
|
|
4731
|
+
/**
|
|
4732
|
+
* This method will enable the transcription for the current meeting if the meeting has enabled/supports Webex Assistant
|
|
4733
|
+
* @param {Object} options object with spokenlanguage setting
|
|
4734
|
+
* @public
|
|
4735
|
+
* @returns {Promise<void>} a promise to open the WebSocket connection
|
|
4736
|
+
*/
|
|
4737
|
+
public async startTranscription(options?: {spokenLanguage?: string}) {
|
|
4738
|
+
if (this.isJoined()) {
|
|
4222
4739
|
LoggerProxy.logger.info(
|
|
4223
|
-
|
|
4224
|
-
opened LLM web socket connection successfully.`
|
|
4740
|
+
'Meeting:index#startTranscription --> Attempting to enable transcription!'
|
|
4225
4741
|
);
|
|
4226
4742
|
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
}
|
|
4232
|
-
|
|
4233
|
-
// retrieve and pass the payload
|
|
4234
|
-
this.transcription.subscribe((payload) => {
|
|
4235
|
-
Trigger.trigger(
|
|
4236
|
-
this,
|
|
4237
|
-
{
|
|
4238
|
-
file: 'meeting/index',
|
|
4239
|
-
function: 'join',
|
|
4240
|
-
},
|
|
4241
|
-
EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
|
|
4242
|
-
payload
|
|
4243
|
-
);
|
|
4244
|
-
});
|
|
4743
|
+
try {
|
|
4744
|
+
if (!this.areVoiceaEventsSetup) {
|
|
4745
|
+
this.setUpVoiceaListeners();
|
|
4746
|
+
}
|
|
4245
4747
|
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
|
|
4253
|
-
|
|
4254
|
-
|
|
4255
|
-
|
|
4748
|
+
if (this.getCurUserType() === 'host') {
|
|
4749
|
+
// @ts-ignore
|
|
4750
|
+
await this.webex.internal.voicea.toggleTranscribing(true, options?.spokenLanguage);
|
|
4751
|
+
}
|
|
4752
|
+
} catch (error) {
|
|
4753
|
+
LoggerProxy.logger.error(`Meeting:index#startTranscription --> ${error}`);
|
|
4754
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.RECEIVE_TRANSCRIPTION_FAILURE, {
|
|
4755
|
+
correlation_id: this.correlationId,
|
|
4756
|
+
reason: error.message,
|
|
4757
|
+
stack: error.stack,
|
|
4758
|
+
});
|
|
4759
|
+
}
|
|
4760
|
+
} else {
|
|
4761
|
+
LoggerProxy.logger.error(
|
|
4762
|
+
`Meeting:index#startTranscription --> meeting joined : ${this.isJoined()}`
|
|
4763
|
+
);
|
|
4764
|
+
throw new Error('Meeting is not joined');
|
|
4256
4765
|
}
|
|
4257
4766
|
}
|
|
4258
4767
|
|
|
@@ -4295,13 +4804,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4295
4804
|
};
|
|
4296
4805
|
|
|
4297
4806
|
/**
|
|
4298
|
-
*
|
|
4299
|
-
* the web socket connection properly
|
|
4807
|
+
* This method stops receiving transcription for the current meeting
|
|
4300
4808
|
* @returns {void}
|
|
4301
4809
|
*/
|
|
4302
|
-
|
|
4810
|
+
stopTranscription() {
|
|
4303
4811
|
if (this.transcription) {
|
|
4304
|
-
|
|
4812
|
+
// @ts-ignore
|
|
4813
|
+
this.webex.internal.voicea.off(
|
|
4814
|
+
VOICEAEVENTS.VOICEA_ANNOUNCEMENT,
|
|
4815
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.VOICEA_ANNOUNCEMENT]
|
|
4816
|
+
);
|
|
4817
|
+
|
|
4818
|
+
// @ts-ignore
|
|
4819
|
+
this.webex.internal.voicea.off(
|
|
4820
|
+
VOICEAEVENTS.CAPTIONS_TURNED_ON,
|
|
4821
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.CAPTIONS_TURNED_ON]
|
|
4822
|
+
);
|
|
4823
|
+
|
|
4824
|
+
// @ts-ignore
|
|
4825
|
+
this.webex.internal.voicea.off(
|
|
4826
|
+
VOICEAEVENTS.EVA_COMMAND,
|
|
4827
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.EVA_COMMAND]
|
|
4828
|
+
);
|
|
4829
|
+
|
|
4830
|
+
// @ts-ignore
|
|
4831
|
+
this.webex.internal.voicea.off(
|
|
4832
|
+
VOICEAEVENTS.NEW_CAPTION,
|
|
4833
|
+
this.voiceaListenerCallbacks[VOICEAEVENTS.NEW_CAPTION]
|
|
4834
|
+
);
|
|
4835
|
+
|
|
4836
|
+
this.areVoiceaEventsSetup = false;
|
|
4837
|
+
this.triggerStopReceivingTranscriptionEvent();
|
|
4305
4838
|
}
|
|
4306
4839
|
}
|
|
4307
4840
|
|
|
@@ -4314,12 +4847,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4314
4847
|
private triggerStopReceivingTranscriptionEvent() {
|
|
4315
4848
|
LoggerProxy.logger.info(`
|
|
4316
4849
|
Meeting:index#stopReceivingTranscription -->
|
|
4317
|
-
closed
|
|
4850
|
+
closed voicea event listeners successfully.`);
|
|
4318
4851
|
|
|
4319
4852
|
Trigger.trigger(
|
|
4320
4853
|
this,
|
|
4321
4854
|
{
|
|
4322
|
-
file: 'meeting',
|
|
4855
|
+
file: 'meeting/index',
|
|
4323
4856
|
function: 'triggerStopReceivingTranscriptionEvent',
|
|
4324
4857
|
},
|
|
4325
4858
|
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
@@ -4338,7 +4871,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4338
4871
|
* if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
|
|
4339
4872
|
* Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
|
|
4340
4873
|
*/
|
|
4341
|
-
public join(options: any = {}) {
|
|
4874
|
+
public async join(options: any = {}) {
|
|
4342
4875
|
// @ts-ignore - fix type
|
|
4343
4876
|
if (!this.webex.meetings.registered) {
|
|
4344
4877
|
const errorMessage = 'Meeting:index#join --> Device not registered';
|
|
@@ -4392,27 +4925,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4392
4925
|
// @ts-ignore
|
|
4393
4926
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
4394
4927
|
name: 'client.call.initiated',
|
|
4395
|
-
payload: {
|
|
4928
|
+
payload: {
|
|
4929
|
+
trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
|
|
4930
|
+
isRoapCallEnabled: true,
|
|
4931
|
+
pstnAudioType: options?.pstnAudioType,
|
|
4932
|
+
},
|
|
4396
4933
|
options: {meetingId: this.id},
|
|
4397
4934
|
});
|
|
4398
4935
|
|
|
4399
|
-
if (!isEmpty(this.meetingInfo)) {
|
|
4400
|
-
// @ts-ignore
|
|
4401
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4402
|
-
name: 'client.meetinginfo.request',
|
|
4403
|
-
options: {meetingId: this.id},
|
|
4404
|
-
});
|
|
4405
|
-
|
|
4406
|
-
// @ts-ignore
|
|
4407
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4408
|
-
name: 'client.meetinginfo.response',
|
|
4409
|
-
payload: {
|
|
4410
|
-
identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
|
|
4411
|
-
},
|
|
4412
|
-
options: {meetingId: this.id},
|
|
4413
|
-
});
|
|
4414
|
-
}
|
|
4415
|
-
|
|
4416
4936
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
4417
4937
|
|
|
4418
4938
|
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
@@ -4466,44 +4986,55 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4466
4986
|
|
|
4467
4987
|
this.isMultistream = !!options.enableMultistream;
|
|
4468
4988
|
|
|
4989
|
+
try {
|
|
4990
|
+
// refresh the permission token if its about to expire in 10sec
|
|
4991
|
+
await this.checkAndRefreshPermissionToken(
|
|
4992
|
+
MEETING_PERMISSION_TOKEN_REFRESH_THRESHOLD_IN_SEC,
|
|
4993
|
+
MEETING_PERMISSION_TOKEN_REFRESH_REASON
|
|
4994
|
+
);
|
|
4995
|
+
} catch (error) {
|
|
4996
|
+
LoggerProxy.logger.error('Meeting:index#join --> Failed to refresh permission token:', error);
|
|
4997
|
+
|
|
4998
|
+
if (
|
|
4999
|
+
error instanceof CaptchaError ||
|
|
5000
|
+
error instanceof PasswordError ||
|
|
5001
|
+
error instanceof PermissionError
|
|
5002
|
+
) {
|
|
5003
|
+
this.meetingFiniteStateMachine.fail(error);
|
|
5004
|
+
|
|
5005
|
+
// Upload logs on refreshpermissionToken refresh Failure
|
|
5006
|
+
Trigger.trigger(
|
|
5007
|
+
this,
|
|
5008
|
+
{
|
|
5009
|
+
file: 'meeting/index',
|
|
5010
|
+
function: 'join',
|
|
5011
|
+
},
|
|
5012
|
+
EVENTS.REQUEST_UPLOAD_LOGS,
|
|
5013
|
+
this
|
|
5014
|
+
);
|
|
5015
|
+
|
|
5016
|
+
joinFailed(error);
|
|
5017
|
+
|
|
5018
|
+
this.deferJoin = undefined;
|
|
5019
|
+
|
|
5020
|
+
// if refresh permission token requires captcha, password or permission, we are throwing the errors
|
|
5021
|
+
// and bubble it up to client
|
|
5022
|
+
return Promise.reject(error);
|
|
5023
|
+
}
|
|
5024
|
+
}
|
|
5025
|
+
|
|
4469
5026
|
return MeetingUtil.joinMeetingOptions(this, options)
|
|
4470
5027
|
.then((join) => {
|
|
4471
5028
|
this.meetingFiniteStateMachine.join();
|
|
4472
5029
|
LoggerProxy.logger.log('Meeting:index#join --> Success');
|
|
4473
5030
|
|
|
4474
|
-
return join;
|
|
4475
|
-
})
|
|
4476
|
-
.then((join) => {
|
|
4477
|
-
joinSuccess(join);
|
|
4478
|
-
this.deferJoin = undefined;
|
|
4479
5031
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
|
|
4480
5032
|
correlation_id: this.correlationId,
|
|
4481
5033
|
});
|
|
4482
5034
|
|
|
4483
|
-
|
|
4484
|
-
})
|
|
4485
|
-
.then(async (join) => {
|
|
4486
|
-
// @ts-ignore - config coming from registerPlugin
|
|
4487
|
-
if (this.config.enableAutomaticLLM) {
|
|
4488
|
-
await this.updateLLMConnection();
|
|
4489
|
-
}
|
|
5035
|
+
joinSuccess(join);
|
|
4490
5036
|
|
|
4491
|
-
|
|
4492
|
-
})
|
|
4493
|
-
.then(async (join) => {
|
|
4494
|
-
if (isBrowser) {
|
|
4495
|
-
// @ts-ignore - config coming from registerPlugin
|
|
4496
|
-
if (this.config.receiveTranscription || options.receiveTranscription) {
|
|
4497
|
-
if (this.isTranscriptionSupported()) {
|
|
4498
|
-
await this.receiveTranscription();
|
|
4499
|
-
LoggerProxy.logger.info('Meeting:index#join --> enabled to recieve transcription!');
|
|
4500
|
-
}
|
|
4501
|
-
}
|
|
4502
|
-
} else {
|
|
4503
|
-
LoggerProxy.logger.error(
|
|
4504
|
-
'Meeting:index#join --> Receving transcription is not supported on this platform'
|
|
4505
|
-
);
|
|
4506
|
-
}
|
|
5037
|
+
this.deferJoin = undefined;
|
|
4507
5038
|
|
|
4508
5039
|
return join;
|
|
4509
5040
|
})
|
|
@@ -4539,9 +5070,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4539
5070
|
);
|
|
4540
5071
|
|
|
4541
5072
|
joinFailed(error);
|
|
5073
|
+
|
|
4542
5074
|
this.deferJoin = undefined;
|
|
4543
5075
|
|
|
4544
5076
|
return Promise.reject(error);
|
|
5077
|
+
})
|
|
5078
|
+
.then((join) => {
|
|
5079
|
+
// @ts-ignore - config coming from registerPlugin
|
|
5080
|
+
if (this.config.enableAutomaticLLM) {
|
|
5081
|
+
this.updateLLMConnection()
|
|
5082
|
+
.catch((error) => {
|
|
5083
|
+
LoggerProxy.logger.error(
|
|
5084
|
+
'Meeting:index#join --> Transcription Socket Connection Failed',
|
|
5085
|
+
error
|
|
5086
|
+
);
|
|
5087
|
+
|
|
5088
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.LLM_CONNECTION_AFTER_JOIN_FAILURE, {
|
|
5089
|
+
correlation_id: this.correlationId,
|
|
5090
|
+
reason: error?.message,
|
|
5091
|
+
stack: error.stack,
|
|
5092
|
+
});
|
|
5093
|
+
})
|
|
5094
|
+
.then(() => {
|
|
5095
|
+
LoggerProxy.logger.info(
|
|
5096
|
+
'Meeting:index#join --> Transcription Socket Connection Success'
|
|
5097
|
+
);
|
|
5098
|
+
Trigger.trigger(
|
|
5099
|
+
this,
|
|
5100
|
+
{
|
|
5101
|
+
file: 'meeting/index',
|
|
5102
|
+
function: 'join',
|
|
5103
|
+
},
|
|
5104
|
+
EVENT_TRIGGERS.MEETING_TRANSCRIPTION_CONNECTED,
|
|
5105
|
+
undefined
|
|
5106
|
+
);
|
|
5107
|
+
});
|
|
5108
|
+
}
|
|
5109
|
+
|
|
5110
|
+
return join;
|
|
4545
5111
|
});
|
|
4546
5112
|
}
|
|
4547
5113
|
|
|
@@ -4921,30 +5487,91 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4921
5487
|
}
|
|
4922
5488
|
};
|
|
4923
5489
|
|
|
4924
|
-
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
5490
|
+
/**
|
|
5491
|
+
* Handles an incoming Roap message
|
|
5492
|
+
* @internal
|
|
5493
|
+
* @param {RoapMessage} roapMessage roap message
|
|
5494
|
+
* @returns {undefined}
|
|
5495
|
+
*/
|
|
5496
|
+
public roapMessageReceived = (roapMessage: RoapMessage) => {
|
|
5497
|
+
const mediaServer = MeetingsUtil.getMediaServer(roapMessage.sdp);
|
|
4928
5498
|
|
|
4929
|
-
this.mediaProperties.webrtcMediaConnection.
|
|
4930
|
-
this.mediaNegotiatedEvent();
|
|
4931
|
-
this.isRoapInProgress = false;
|
|
4932
|
-
this.processNextQueuedMediaUpdate();
|
|
4933
|
-
});
|
|
5499
|
+
this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
|
|
4934
5500
|
|
|
4935
|
-
|
|
5501
|
+
if (mediaServer) {
|
|
5502
|
+
this.mediaProperties.webrtcMediaConnection.mediaServer = mediaServer;
|
|
5503
|
+
}
|
|
5504
|
+
};
|
|
5505
|
+
|
|
5506
|
+
/**
|
|
5507
|
+
* This function makes sure we send the right metrics when local and remote SDPs are processed/generated
|
|
5508
|
+
*
|
|
5509
|
+
* @returns {undefined}
|
|
5510
|
+
*/
|
|
5511
|
+
setupSdpListeners = () => {
|
|
5512
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.REMOTE_SDP_ANSWER_PROCESSED, () => {
|
|
5513
|
+
// @ts-ignore
|
|
5514
|
+
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
5515
|
+
|
|
5516
|
+
// @ts-ignore
|
|
5517
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5518
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
5519
|
+
options: {meetingId: this.id},
|
|
5520
|
+
});
|
|
5521
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ROAP_OFFER_TO_ANSWER_LATENCY, {
|
|
5522
|
+
correlation_id: this.correlationId,
|
|
5523
|
+
latency: cdl.getLocalSDPGenRemoteSDPRecv(),
|
|
5524
|
+
meetingId: this.id,
|
|
5525
|
+
});
|
|
5526
|
+
|
|
5527
|
+
if (this.deferSDPAnswer) {
|
|
5528
|
+
this.deferSDPAnswer.resolve();
|
|
5529
|
+
clearTimeout(this.sdpResponseTimer);
|
|
5530
|
+
this.sdpResponseTimer = undefined;
|
|
5531
|
+
}
|
|
5532
|
+
});
|
|
5533
|
+
|
|
5534
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.LOCAL_SDP_OFFER_GENERATED, () => {
|
|
5535
|
+
// @ts-ignore
|
|
5536
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5537
|
+
name: 'client.media-engine.local-sdp-generated',
|
|
5538
|
+
options: {meetingId: this.id},
|
|
5539
|
+
});
|
|
5540
|
+
|
|
5541
|
+
// Instantiate Defer so that the SDP offer/answer exchange timeout can start, see waitForRemoteSDPAnswer()
|
|
5542
|
+
this.deferSDPAnswer = new Defer();
|
|
5543
|
+
});
|
|
5544
|
+
|
|
5545
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.LOCAL_SDP_ANSWER_GENERATED, () => {
|
|
5546
|
+
// we are sending "remote-sdp-received" only after we've generated the answer - this indicates that we've fully processed that incoming offer
|
|
5547
|
+
// @ts-ignore
|
|
5548
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5549
|
+
name: 'client.media-engine.remote-sdp-received',
|
|
5550
|
+
options: {meetingId: this.id},
|
|
5551
|
+
});
|
|
5552
|
+
});
|
|
5553
|
+
};
|
|
5554
|
+
|
|
5555
|
+
setupMediaConnectionListeners = () => {
|
|
5556
|
+
this.setupSdpListeners();
|
|
5557
|
+
|
|
5558
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_STARTED, () => {
|
|
5559
|
+
this.isRoapInProgress = true;
|
|
5560
|
+
});
|
|
5561
|
+
|
|
5562
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
|
|
5563
|
+
this.mediaNegotiatedEvent();
|
|
5564
|
+
this.isRoapInProgress = false;
|
|
5565
|
+
this.processNextQueuedMediaUpdate();
|
|
5566
|
+
});
|
|
5567
|
+
|
|
5568
|
+
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_FAILURE, this.handleRoapFailure);
|
|
4936
5569
|
|
|
4937
5570
|
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_MESSAGE_TO_SEND, (event) => {
|
|
4938
5571
|
const LOG_HEADER = `Meeting:index#setupMediaConnectionListeners.ROAP_MESSAGE_TO_SEND --> correlationId=${this.correlationId}`;
|
|
4939
5572
|
|
|
4940
5573
|
switch (event.roapMessage.messageType) {
|
|
4941
5574
|
case 'OK':
|
|
4942
|
-
// @ts-ignore
|
|
4943
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4944
|
-
name: 'client.media-engine.remote-sdp-received',
|
|
4945
|
-
options: {meetingId: this.id},
|
|
4946
|
-
});
|
|
4947
|
-
|
|
4948
5575
|
logRequest(
|
|
4949
5576
|
this.roap.sendRoapOK({
|
|
4950
5577
|
seq: event.roapMessage.seq,
|
|
@@ -4958,33 +5585,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4958
5585
|
break;
|
|
4959
5586
|
|
|
4960
5587
|
case 'OFFER':
|
|
4961
|
-
// @ts-ignore
|
|
4962
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4963
|
-
name: 'client.media-engine.local-sdp-generated',
|
|
4964
|
-
options: {meetingId: this.id},
|
|
4965
|
-
});
|
|
4966
|
-
|
|
4967
5588
|
logRequest(
|
|
4968
|
-
this.roap
|
|
4969
|
-
|
|
4970
|
-
|
|
4971
|
-
|
|
4972
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
5589
|
+
this.roap
|
|
5590
|
+
.sendRoapMediaRequest({
|
|
5591
|
+
sdp: event.roapMessage.sdp,
|
|
5592
|
+
seq: event.roapMessage.seq,
|
|
5593
|
+
tieBreaker: event.roapMessage.tieBreaker,
|
|
5594
|
+
meeting: this, // or can pass meeting ID
|
|
5595
|
+
})
|
|
5596
|
+
.then(({roapAnswer}) => {
|
|
5597
|
+
if (roapAnswer) {
|
|
5598
|
+
LoggerProxy.logger.log(`${LOG_HEADER} received Roap ANSWER in http response`);
|
|
5599
|
+
|
|
5600
|
+
this.roapMessageReceived(roapAnswer);
|
|
5601
|
+
}
|
|
5602
|
+
}),
|
|
4975
5603
|
{
|
|
4976
5604
|
logText: `${LOG_HEADER} Roap Offer`,
|
|
4977
5605
|
}
|
|
4978
|
-
)
|
|
5606
|
+
).catch(() => {
|
|
5607
|
+
this.deferSDPAnswer.reject();
|
|
5608
|
+
clearTimeout(this.sdpResponseTimer);
|
|
5609
|
+
this.sdpResponseTimer = undefined;
|
|
5610
|
+
});
|
|
4979
5611
|
break;
|
|
4980
5612
|
|
|
4981
5613
|
case 'ANSWER':
|
|
4982
|
-
// @ts-ignore
|
|
4983
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4984
|
-
name: 'client.media-engine.remote-sdp-received',
|
|
4985
|
-
options: {meetingId: this.id},
|
|
4986
|
-
});
|
|
4987
|
-
|
|
4988
5614
|
logRequest(
|
|
4989
5615
|
this.roap.sendRoapAnswer({
|
|
4990
5616
|
sdp: event.roapMessage.sdp,
|
|
@@ -5097,70 +5723,71 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5097
5723
|
|
|
5098
5724
|
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
5099
5725
|
const connectionFailed = () => {
|
|
5100
|
-
// we know the media connection failed and browser will not attempt to recover it any more
|
|
5101
|
-
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
5102
|
-
this.reconnectionManager.resetReconnectionTimer();
|
|
5103
|
-
|
|
5104
|
-
this.reconnect({networkDisconnect: true});
|
|
5105
|
-
// @ts-ignore
|
|
5106
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5107
|
-
name: 'client.ice.end',
|
|
5108
|
-
payload: {
|
|
5109
|
-
canProceed: false,
|
|
5110
|
-
icePhase: 'IN_MEETING',
|
|
5111
|
-
errors: [
|
|
5112
|
-
// @ts-ignore
|
|
5113
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5114
|
-
{
|
|
5115
|
-
clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
|
|
5116
|
-
}
|
|
5117
|
-
),
|
|
5118
|
-
],
|
|
5119
|
-
},
|
|
5120
|
-
options: {
|
|
5121
|
-
meetingId: this.id,
|
|
5122
|
-
},
|
|
5123
|
-
});
|
|
5124
|
-
|
|
5125
|
-
this.uploadLogs({
|
|
5126
|
-
file: 'peer-connection-manager/index',
|
|
5127
|
-
function: 'connectionFailed',
|
|
5128
|
-
});
|
|
5129
|
-
|
|
5130
5726
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_FAILURE, {
|
|
5131
5727
|
correlation_id: this.correlationId,
|
|
5132
5728
|
locus_id: this.locusId,
|
|
5729
|
+
networkStatus: this.networkStatus,
|
|
5730
|
+
hasMediaConnectionConnectedAtLeastOnce: this.hasMediaConnectionConnectedAtLeastOnce,
|
|
5133
5731
|
});
|
|
5732
|
+
|
|
5733
|
+
if (this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5734
|
+
// we know the media connection failed and browser will not attempt to recover it any more
|
|
5735
|
+
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
5736
|
+
this.reconnectionManager.resetReconnectionTimer();
|
|
5737
|
+
|
|
5738
|
+
this.reconnect({networkDisconnect: true});
|
|
5739
|
+
|
|
5740
|
+
this.uploadLogs({
|
|
5741
|
+
file: 'peer-connection-manager/index',
|
|
5742
|
+
function: 'connectionFailed',
|
|
5743
|
+
});
|
|
5744
|
+
}
|
|
5134
5745
|
};
|
|
5135
5746
|
|
|
5136
5747
|
LoggerProxy.logger.info(
|
|
5137
5748
|
`Meeting:index#setupMediaConnectionListeners --> correlationId=${this.correlationId} connection state changed to ${event.state}`
|
|
5138
5749
|
);
|
|
5750
|
+
|
|
5751
|
+
// @ts-ignore
|
|
5752
|
+
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
5753
|
+
|
|
5139
5754
|
switch (event.state) {
|
|
5140
5755
|
case ConnectionState.Connecting:
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5756
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5757
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
5758
|
+
// @ts-ignore
|
|
5759
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5760
|
+
name: 'client.ice.start',
|
|
5761
|
+
options: {
|
|
5762
|
+
meetingId: this.id,
|
|
5763
|
+
},
|
|
5764
|
+
});
|
|
5765
|
+
}
|
|
5148
5766
|
break;
|
|
5149
5767
|
case ConnectionState.Connected:
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5768
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
5769
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
5770
|
+
// @ts-ignore
|
|
5771
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
5772
|
+
name: 'client.ice.end',
|
|
5773
|
+
payload: {
|
|
5774
|
+
canProceed: true,
|
|
5775
|
+
icePhase: 'JOIN_MEETING_FINAL',
|
|
5776
|
+
},
|
|
5777
|
+
options: {
|
|
5778
|
+
meetingId: this.id,
|
|
5779
|
+
},
|
|
5780
|
+
});
|
|
5781
|
+
}
|
|
5157
5782
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
5158
5783
|
correlation_id: this.correlationId,
|
|
5159
5784
|
locus_id: this.locusId,
|
|
5785
|
+
latency: cdl.getICESetupTime(),
|
|
5160
5786
|
});
|
|
5161
5787
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
5162
5788
|
this.reconnectionManager.iceReconnected();
|
|
5163
5789
|
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
5790
|
+
this.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
5164
5791
|
break;
|
|
5165
5792
|
case ConnectionState.Disconnected:
|
|
5166
5793
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
@@ -5281,7 +5908,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5281
5908
|
// @ts-ignore
|
|
5282
5909
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5283
5910
|
name: 'client.media.tx.start',
|
|
5284
|
-
payload: {
|
|
5911
|
+
payload: {
|
|
5912
|
+
mediaType: data.type,
|
|
5913
|
+
shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
|
|
5914
|
+
},
|
|
5285
5915
|
options: {
|
|
5286
5916
|
meetingId: this.id,
|
|
5287
5917
|
},
|
|
@@ -5291,7 +5921,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5291
5921
|
// @ts-ignore
|
|
5292
5922
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5293
5923
|
name: 'client.media.tx.stop',
|
|
5294
|
-
payload: {
|
|
5924
|
+
payload: {
|
|
5925
|
+
mediaType: data.type,
|
|
5926
|
+
shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
|
|
5927
|
+
},
|
|
5295
5928
|
options: {
|
|
5296
5929
|
meetingId: this.id,
|
|
5297
5930
|
},
|
|
@@ -5310,7 +5943,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5310
5943
|
// @ts-ignore
|
|
5311
5944
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5312
5945
|
name: 'client.media.rx.start',
|
|
5313
|
-
payload: {
|
|
5946
|
+
payload: {
|
|
5947
|
+
mediaType: data.type,
|
|
5948
|
+
shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
|
|
5949
|
+
},
|
|
5314
5950
|
options: {
|
|
5315
5951
|
meetingId: this.id,
|
|
5316
5952
|
},
|
|
@@ -5320,7 +5956,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5320
5956
|
// @ts-ignore
|
|
5321
5957
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5322
5958
|
name: 'client.media.rx.stop',
|
|
5323
|
-
payload: {
|
|
5959
|
+
payload: {
|
|
5960
|
+
mediaType: data.type,
|
|
5961
|
+
shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
|
|
5962
|
+
},
|
|
5324
5963
|
options: {
|
|
5325
5964
|
meetingId: this.id,
|
|
5326
5965
|
},
|
|
@@ -5373,8 +6012,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5373
6012
|
this.mediaProperties.mediaDirection.receiveShare,
|
|
5374
6013
|
];
|
|
5375
6014
|
|
|
5376
|
-
this.sendSlotManager.createSlot(mc, MediaType.VideoMain,
|
|
5377
|
-
this.sendSlotManager.createSlot(mc, MediaType.AudioMain,
|
|
6015
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoMain, videoEnabled);
|
|
6016
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioMain, audioEnabled);
|
|
5378
6017
|
this.sendSlotManager.createSlot(mc, MediaType.VideoSlides, shareEnabled);
|
|
5379
6018
|
this.sendSlotManager.createSlot(mc, MediaType.AudioSlides, shareEnabled);
|
|
5380
6019
|
}
|
|
@@ -5393,54 +6032,468 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5393
6032
|
await this.publishStream(MediaType.AudioSlides, this.mediaProperties.shareAudioStream);
|
|
5394
6033
|
}
|
|
5395
6034
|
|
|
5396
|
-
return mc;
|
|
6035
|
+
return mc;
|
|
6036
|
+
}
|
|
6037
|
+
|
|
6038
|
+
/**
|
|
6039
|
+
* Listens for an event emitted by eventEmitter and emits it from the meeting object
|
|
6040
|
+
*
|
|
6041
|
+
* @private
|
|
6042
|
+
* @param {*} eventEmitter object from which to forward the event
|
|
6043
|
+
* @param {*} eventTypeToForward which event type to listen on and to forward
|
|
6044
|
+
* @param {string} meetingEventType event type to be used in the event emitted from the meeting object
|
|
6045
|
+
* @returns {void}
|
|
6046
|
+
*/
|
|
6047
|
+
forwardEvent(eventEmitter, eventTypeToForward, meetingEventType) {
|
|
6048
|
+
eventEmitter.on(eventTypeToForward, (data) =>
|
|
6049
|
+
Trigger.trigger(
|
|
6050
|
+
this,
|
|
6051
|
+
{
|
|
6052
|
+
file: 'meetings',
|
|
6053
|
+
function: 'addMedia',
|
|
6054
|
+
},
|
|
6055
|
+
meetingEventType,
|
|
6056
|
+
data
|
|
6057
|
+
)
|
|
6058
|
+
);
|
|
6059
|
+
}
|
|
6060
|
+
|
|
6061
|
+
/**
|
|
6062
|
+
* Sets up all the references to local streams in this.mediaProperties before creating media connection
|
|
6063
|
+
* and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages.
|
|
6064
|
+
*
|
|
6065
|
+
* @private
|
|
6066
|
+
* @param {LocalStreams} localStreams
|
|
6067
|
+
* @returns {Promise<void>}
|
|
6068
|
+
*/
|
|
6069
|
+
private async setUpLocalStreamReferences(localStreams: LocalStreams) {
|
|
6070
|
+
const setUpStreamPromises = [];
|
|
6071
|
+
|
|
6072
|
+
if (localStreams?.microphone) {
|
|
6073
|
+
setUpStreamPromises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
6074
|
+
}
|
|
6075
|
+
if (localStreams?.camera) {
|
|
6076
|
+
setUpStreamPromises.push(this.setLocalVideoStream(localStreams.camera));
|
|
6077
|
+
}
|
|
6078
|
+
if (localStreams?.screenShare?.video) {
|
|
6079
|
+
setUpStreamPromises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
6080
|
+
}
|
|
6081
|
+
if (localStreams?.screenShare?.audio) {
|
|
6082
|
+
setUpStreamPromises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
6083
|
+
}
|
|
6084
|
+
|
|
6085
|
+
try {
|
|
6086
|
+
await Promise.all(setUpStreamPromises);
|
|
6087
|
+
} catch (error) {
|
|
6088
|
+
LoggerProxy.logger.error(
|
|
6089
|
+
`Meeting:index#addMedia():setUpLocalStreamReferences --> Error , `,
|
|
6090
|
+
error
|
|
6091
|
+
);
|
|
6092
|
+
|
|
6093
|
+
throw error;
|
|
6094
|
+
}
|
|
6095
|
+
}
|
|
6096
|
+
|
|
6097
|
+
/**
|
|
6098
|
+
* Calls mediaProperties.waitForMediaConnectionConnected() and sends CA client.ice.end metric on failure
|
|
6099
|
+
*
|
|
6100
|
+
* @private
|
|
6101
|
+
* @returns {Promise<void>}
|
|
6102
|
+
*/
|
|
6103
|
+
private async waitForMediaConnectionConnected(): Promise<void> {
|
|
6104
|
+
try {
|
|
6105
|
+
await this.mediaProperties.waitForMediaConnectionConnected();
|
|
6106
|
+
} catch (error) {
|
|
6107
|
+
if (!this.hasMediaConnectionConnectedAtLeastOnce) {
|
|
6108
|
+
// Only send CA event for join flow if we haven't successfully connected media yet
|
|
6109
|
+
// @ts-ignore
|
|
6110
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
6111
|
+
name: 'client.ice.end',
|
|
6112
|
+
payload: {
|
|
6113
|
+
canProceed: !this.turnServerUsed, // If we haven't done turn tls retry yet we will proceed with join attempt
|
|
6114
|
+
icePhase: this.turnServerUsed ? 'JOIN_MEETING_FINAL' : 'JOIN_MEETING_RETRY',
|
|
6115
|
+
errors: [
|
|
6116
|
+
// @ts-ignore
|
|
6117
|
+
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
6118
|
+
{
|
|
6119
|
+
clientErrorCode: CallDiagnosticUtils.generateClientErrorCodeForIceFailure({
|
|
6120
|
+
signalingState:
|
|
6121
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6122
|
+
?.signalingState ||
|
|
6123
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
6124
|
+
?.signalingState ||
|
|
6125
|
+
'unknown',
|
|
6126
|
+
iceConnectionState:
|
|
6127
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6128
|
+
?.iceConnectionState ||
|
|
6129
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc
|
|
6130
|
+
?.iceConnectionState ||
|
|
6131
|
+
'unknown',
|
|
6132
|
+
turnServerUsed: this.turnServerUsed,
|
|
6133
|
+
}),
|
|
6134
|
+
}
|
|
6135
|
+
),
|
|
6136
|
+
],
|
|
6137
|
+
},
|
|
6138
|
+
options: {
|
|
6139
|
+
meetingId: this.id,
|
|
6140
|
+
},
|
|
6141
|
+
});
|
|
6142
|
+
}
|
|
6143
|
+
throw new Error(
|
|
6144
|
+
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
6145
|
+
);
|
|
6146
|
+
}
|
|
6147
|
+
}
|
|
6148
|
+
|
|
6149
|
+
/**
|
|
6150
|
+
* Enables statsAnalyser if config allows it
|
|
6151
|
+
*
|
|
6152
|
+
* @private
|
|
6153
|
+
* @returns {void}
|
|
6154
|
+
*/
|
|
6155
|
+
private createStatsAnalyzer() {
|
|
6156
|
+
// @ts-ignore - config coming from registerPlugin
|
|
6157
|
+
if (this.config.stats.enableStatsAnalyzer) {
|
|
6158
|
+
// @ts-ignore - config coming from registerPlugin
|
|
6159
|
+
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
6160
|
+
this.statsAnalyzer = new StatsAnalyzer(
|
|
6161
|
+
// @ts-ignore - config coming from registerPlugin
|
|
6162
|
+
this.config.stats,
|
|
6163
|
+
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
6164
|
+
this.networkQualityMonitor
|
|
6165
|
+
);
|
|
6166
|
+
this.setupStatsAnalyzerEventHandlers();
|
|
6167
|
+
this.networkQualityMonitor.on(
|
|
6168
|
+
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
6169
|
+
this.sendNetworkQualityEvent.bind(this)
|
|
6170
|
+
);
|
|
6171
|
+
}
|
|
6172
|
+
}
|
|
6173
|
+
|
|
6174
|
+
/**
|
|
6175
|
+
* Handles device logging
|
|
6176
|
+
*
|
|
6177
|
+
* @private
|
|
6178
|
+
* @static
|
|
6179
|
+
* @returns {Promise<void>}
|
|
6180
|
+
*/
|
|
6181
|
+
private static async handleDeviceLogging(): Promise<void> {
|
|
6182
|
+
try {
|
|
6183
|
+
const devices = await getDevices();
|
|
6184
|
+
|
|
6185
|
+
MeetingUtil.handleDeviceLogging(devices);
|
|
6186
|
+
} catch {
|
|
6187
|
+
// getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
6188
|
+
}
|
|
6189
|
+
}
|
|
6190
|
+
|
|
6191
|
+
/**
|
|
6192
|
+
* Returns a promise. This promise is created once the local sdp offer has been successfully created and is resolved
|
|
6193
|
+
* once the remote sdp answer has been received.
|
|
6194
|
+
*
|
|
6195
|
+
* @private
|
|
6196
|
+
* @returns {Promise<void>}
|
|
6197
|
+
*/
|
|
6198
|
+
private async waitForRemoteSDPAnswer(): Promise<void> {
|
|
6199
|
+
const LOG_HEADER = 'Meeting:index#addMedia():waitForRemoteSDPAnswer -->';
|
|
6200
|
+
|
|
6201
|
+
if (!this.deferSDPAnswer) {
|
|
6202
|
+
LoggerProxy.logger.warn(`${LOG_HEADER} offer not created yet`);
|
|
6203
|
+
|
|
6204
|
+
return Promise.reject(
|
|
6205
|
+
new Error('waitForRemoteSDPAnswer() called before local sdp offer created')
|
|
6206
|
+
);
|
|
6207
|
+
}
|
|
6208
|
+
|
|
6209
|
+
const {deferSDPAnswer} = this;
|
|
6210
|
+
|
|
6211
|
+
this.sdpResponseTimer = setTimeout(() => {
|
|
6212
|
+
LoggerProxy.logger.warn(
|
|
6213
|
+
`${LOG_HEADER} timeout! no REMOTE SDP ANSWER received within ${
|
|
6214
|
+
ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT / 1000
|
|
6215
|
+
} seconds`
|
|
6216
|
+
);
|
|
6217
|
+
deferSDPAnswer.reject(new Error('Timed out waiting for REMOTE SDP ANSWER'));
|
|
6218
|
+
}, ROAP_OFFER_ANSWER_EXCHANGE_TIMEOUT);
|
|
6219
|
+
|
|
6220
|
+
LoggerProxy.logger.info(`${LOG_HEADER} waiting for REMOTE SDP ANSWER...`);
|
|
6221
|
+
|
|
6222
|
+
return deferSDPAnswer.promise;
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
/**
|
|
6226
|
+
* Calls establishMediaConnection with isForced = true to force turn discovery to happen
|
|
6227
|
+
*
|
|
6228
|
+
* @private
|
|
6229
|
+
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
|
|
6230
|
+
* @param {BundlePolicy} [bundlePolicy]
|
|
6231
|
+
* @returns {Promise<void>}
|
|
6232
|
+
*/
|
|
6233
|
+
private async retryEstablishMediaConnectionWithForcedTurnDiscovery(
|
|
6234
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
|
|
6235
|
+
bundlePolicy?: BundlePolicy
|
|
6236
|
+
): Promise<void> {
|
|
6237
|
+
const LOG_HEADER =
|
|
6238
|
+
'Meeting:index#addMedia():retryEstablishMediaConnectionWithForcedTurnDiscovery -->';
|
|
6239
|
+
|
|
6240
|
+
try {
|
|
6241
|
+
await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, true);
|
|
6242
|
+
} catch (err) {
|
|
6243
|
+
LoggerProxy.logger.error(
|
|
6244
|
+
`${LOG_HEADER} retry with TURN-TLS failed, media connection unable to connect, `,
|
|
6245
|
+
err
|
|
6246
|
+
);
|
|
6247
|
+
|
|
6248
|
+
throw err;
|
|
6249
|
+
}
|
|
6250
|
+
}
|
|
6251
|
+
|
|
6252
|
+
/**
|
|
6253
|
+
* Does relevant clean up before retrying to establish media connection
|
|
6254
|
+
* and performs the retry with forced turn discovery
|
|
6255
|
+
*
|
|
6256
|
+
* @private
|
|
6257
|
+
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
|
|
6258
|
+
* @param {BundlePolicy} [bundlePolicy]
|
|
6259
|
+
* @returns {Promise<void>}
|
|
6260
|
+
*/
|
|
6261
|
+
private async retryWithForcedTurnDiscovery(
|
|
6262
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
|
|
6263
|
+
bundlePolicy?: BundlePolicy
|
|
6264
|
+
): Promise<void> {
|
|
6265
|
+
this.retriedWithTurnServer = true;
|
|
6266
|
+
const LOG_HEADER = 'Meeting:index#addMedia():retryWithForcedTurnDiscovery -->';
|
|
6267
|
+
|
|
6268
|
+
await this.cleanUpBeforeRetryWithTurnServer();
|
|
6269
|
+
|
|
6270
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_RETRY, {
|
|
6271
|
+
correlation_id: this.correlationId,
|
|
6272
|
+
state: this.state,
|
|
6273
|
+
meetingState: this.meetingState,
|
|
6274
|
+
reason: 'forcingTurnTls',
|
|
6275
|
+
});
|
|
6276
|
+
|
|
6277
|
+
if (this.state === MEETING_STATE.STATES.LEFT) {
|
|
6278
|
+
LoggerProxy.logger.info(
|
|
6279
|
+
`${LOG_HEADER} meeting state was LEFT after first attempt to establish media connection. Attempting to rejoin. `
|
|
6280
|
+
);
|
|
6281
|
+
await this.join({rejoin: true});
|
|
6282
|
+
}
|
|
6283
|
+
|
|
6284
|
+
await this.retryEstablishMediaConnectionWithForcedTurnDiscovery(
|
|
6285
|
+
remoteMediaManagerConfig,
|
|
6286
|
+
bundlePolicy
|
|
6287
|
+
);
|
|
6288
|
+
}
|
|
6289
|
+
|
|
6290
|
+
/**
|
|
6291
|
+
* If waitForMediaConnectionConnected() fails when we haven't done turn discovery then we
|
|
6292
|
+
* attempt to establish a media connection again, but this time using turn discovery. If we
|
|
6293
|
+
* used turn discovery on the first pass we do not attempt connection again.
|
|
6294
|
+
*
|
|
6295
|
+
* @private
|
|
6296
|
+
* @param {Error} error
|
|
6297
|
+
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
|
|
6298
|
+
* @param {BundlePolicy} [bundlePolicy]
|
|
6299
|
+
* @returns {Promise<void>}
|
|
6300
|
+
*/
|
|
6301
|
+
private async handleWaitForMediaConnectionConnectedError(
|
|
6302
|
+
error: Error,
|
|
6303
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
|
|
6304
|
+
bundlePolicy?: BundlePolicy
|
|
6305
|
+
): Promise<void> {
|
|
6306
|
+
const LOG_HEADER = 'Meeting:index#addMedia():handleWaitForMediaConnectionConnectedError -->';
|
|
6307
|
+
|
|
6308
|
+
// @ts-ignore - config coming from registerPlugin
|
|
6309
|
+
if (!this.turnServerUsed) {
|
|
6310
|
+
LoggerProxy.logger.info(
|
|
6311
|
+
`${LOG_HEADER} error waiting for media to connect on UDP, TCP, retrying using TURN-TLS, `,
|
|
6312
|
+
error
|
|
6313
|
+
);
|
|
6314
|
+
|
|
6315
|
+
await this.retryWithForcedTurnDiscovery(remoteMediaManagerConfig, bundlePolicy);
|
|
6316
|
+
} else {
|
|
6317
|
+
LoggerProxy.logger.error(
|
|
6318
|
+
`${LOG_HEADER} error waiting for media to connect using UDP, TCP and TURN-TLS`,
|
|
6319
|
+
error
|
|
6320
|
+
);
|
|
6321
|
+
|
|
6322
|
+
throw new AddMediaFailed();
|
|
6323
|
+
}
|
|
6324
|
+
}
|
|
6325
|
+
|
|
6326
|
+
/**
|
|
6327
|
+
* Does TURN discovery, SDP offer/answer exhange, establishes ICE connection and DTLS handshake.
|
|
6328
|
+
*
|
|
6329
|
+
* @private
|
|
6330
|
+
* @param {RemoteMediaManagerConfiguration} [remoteMediaManagerConfig]
|
|
6331
|
+
* @param {BundlePolicy} [bundlePolicy]
|
|
6332
|
+
* @param {boolean} [isForced] - let isForced be true to do turn discovery regardless of reachability results
|
|
6333
|
+
* @returns {Promise<void>}
|
|
6334
|
+
*/
|
|
6335
|
+
private async establishMediaConnection(
|
|
6336
|
+
remoteMediaManagerConfig?: RemoteMediaManagerConfiguration,
|
|
6337
|
+
bundlePolicy?: BundlePolicy,
|
|
6338
|
+
isForced?: boolean
|
|
6339
|
+
): Promise<void> {
|
|
6340
|
+
const LOG_HEADER = 'Meeting:index#addMedia():establishMediaConnection -->';
|
|
6341
|
+
// @ts-ignore
|
|
6342
|
+
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
6343
|
+
const isRetry = this.retriedWithTurnServer;
|
|
6344
|
+
|
|
6345
|
+
try {
|
|
6346
|
+
// @ts-ignore
|
|
6347
|
+
this.webex.internal.newMetrics.submitInternalEvent({
|
|
6348
|
+
name: 'internal.client.add-media.turn-discovery.start',
|
|
6349
|
+
});
|
|
6350
|
+
|
|
6351
|
+
const turnDiscoveryObject = await this.roap.doTurnDiscovery(this, isRetry, isForced);
|
|
6352
|
+
|
|
6353
|
+
this.turnDiscoverySkippedReason = turnDiscoveryObject?.turnDiscoverySkippedReason;
|
|
6354
|
+
this.turnServerUsed = !this.turnDiscoverySkippedReason;
|
|
6355
|
+
|
|
6356
|
+
// @ts-ignore
|
|
6357
|
+
this.webex.internal.newMetrics.submitInternalEvent({
|
|
6358
|
+
name: 'internal.client.add-media.turn-discovery.end',
|
|
6359
|
+
});
|
|
6360
|
+
|
|
6361
|
+
const {turnServerInfo} = turnDiscoveryObject;
|
|
6362
|
+
|
|
6363
|
+
if (this.turnServerUsed && turnServerInfo) {
|
|
6364
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.TURN_DISCOVERY_LATENCY, {
|
|
6365
|
+
correlation_id: this.correlationId,
|
|
6366
|
+
latency: cdl.getTurnDiscoveryTime(),
|
|
6367
|
+
turnServerUsed: this.turnServerUsed,
|
|
6368
|
+
retriedWithTurnServer: this.retriedWithTurnServer,
|
|
6369
|
+
});
|
|
6370
|
+
}
|
|
6371
|
+
|
|
6372
|
+
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
6373
|
+
|
|
6374
|
+
LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
|
|
6375
|
+
|
|
6376
|
+
if (this.isMultistream) {
|
|
6377
|
+
this.remoteMediaManager = new RemoteMediaManager(
|
|
6378
|
+
this.receiveSlotManager,
|
|
6379
|
+
this.mediaRequestManagers,
|
|
6380
|
+
remoteMediaManagerConfig
|
|
6381
|
+
);
|
|
6382
|
+
|
|
6383
|
+
this.forwardEvent(
|
|
6384
|
+
this.remoteMediaManager,
|
|
6385
|
+
RemoteMediaManagerEvent.AudioCreated,
|
|
6386
|
+
EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED
|
|
6387
|
+
);
|
|
6388
|
+
this.forwardEvent(
|
|
6389
|
+
this.remoteMediaManager,
|
|
6390
|
+
RemoteMediaManagerEvent.ScreenShareAudioCreated,
|
|
6391
|
+
EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED
|
|
6392
|
+
);
|
|
6393
|
+
this.forwardEvent(
|
|
6394
|
+
this.remoteMediaManager,
|
|
6395
|
+
RemoteMediaManagerEvent.VideoLayoutChanged,
|
|
6396
|
+
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
6397
|
+
);
|
|
6398
|
+
|
|
6399
|
+
await this.remoteMediaManager.start();
|
|
6400
|
+
}
|
|
6401
|
+
|
|
6402
|
+
await mc.initiateOffer();
|
|
6403
|
+
|
|
6404
|
+
await this.waitForRemoteSDPAnswer();
|
|
6405
|
+
|
|
6406
|
+
this.handleMediaLogging(this.mediaProperties);
|
|
6407
|
+
} catch (error) {
|
|
6408
|
+
LoggerProxy.logger.error(`${LOG_HEADER} error establishing media connection, `, error);
|
|
6409
|
+
|
|
6410
|
+
throw error;
|
|
6411
|
+
}
|
|
6412
|
+
|
|
6413
|
+
try {
|
|
6414
|
+
await this.waitForMediaConnectionConnected();
|
|
6415
|
+
} catch (error) {
|
|
6416
|
+
await this.handleWaitForMediaConnectionConnectedError(
|
|
6417
|
+
error,
|
|
6418
|
+
remoteMediaManagerConfig,
|
|
6419
|
+
bundlePolicy
|
|
6420
|
+
);
|
|
6421
|
+
}
|
|
6422
|
+
}
|
|
6423
|
+
|
|
6424
|
+
/**
|
|
6425
|
+
* Cleans up stats analyzer, peer connection, and turns off listeners
|
|
6426
|
+
*
|
|
6427
|
+
* @private
|
|
6428
|
+
* @returns {Promise<void>}
|
|
6429
|
+
*/
|
|
6430
|
+
private async cleanUpOnAddMediaFailure(): Promise<void> {
|
|
6431
|
+
if (this.statsAnalyzer) {
|
|
6432
|
+
await this.statsAnalyzer.stopAnalyzer();
|
|
6433
|
+
}
|
|
6434
|
+
|
|
6435
|
+
this.statsAnalyzer = null;
|
|
6436
|
+
|
|
6437
|
+
// when media fails, we want to upload a webrtc dump to see whats going on
|
|
6438
|
+
// this function is async, but returns once the stats have been gathered
|
|
6439
|
+
await this.forceSendStatsReport({callFrom: 'addMedia'});
|
|
6440
|
+
|
|
6441
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6442
|
+
this.closePeerConnections();
|
|
6443
|
+
this.unsetPeerConnections();
|
|
6444
|
+
}
|
|
5397
6445
|
}
|
|
5398
6446
|
|
|
5399
6447
|
/**
|
|
5400
|
-
*
|
|
6448
|
+
* Sends stats report, closes peer connection and cleans up any media connection
|
|
6449
|
+
* related things before trying to establish media connection again with turn server
|
|
5401
6450
|
*
|
|
5402
6451
|
* @private
|
|
5403
|
-
* @
|
|
5404
|
-
* @param {*} eventTypeToForward which event type to listen on and to forward
|
|
5405
|
-
* @param {string} meetingEventType event type to be used in the event emitted from the meeting object
|
|
5406
|
-
* @returns {void}
|
|
6452
|
+
* @returns {Promise<void>}
|
|
5407
6453
|
*/
|
|
5408
|
-
|
|
5409
|
-
|
|
5410
|
-
|
|
5411
|
-
|
|
5412
|
-
|
|
5413
|
-
|
|
5414
|
-
|
|
5415
|
-
|
|
5416
|
-
|
|
5417
|
-
|
|
5418
|
-
|
|
5419
|
-
|
|
6454
|
+
private async cleanUpBeforeRetryWithTurnServer(): Promise<void> {
|
|
6455
|
+
// when media fails, we want to upload a webrtc dump to see whats going on
|
|
6456
|
+
// this function is async, but returns once the stats have been gathered
|
|
6457
|
+
await this.forceSendStatsReport({callFrom: 'cleanUpBeforeRetryWithTurnServer'});
|
|
6458
|
+
|
|
6459
|
+
if (this.mediaProperties.webrtcMediaConnection) {
|
|
6460
|
+
if (this.remoteMediaManager) {
|
|
6461
|
+
this.remoteMediaManager.stop();
|
|
6462
|
+
this.remoteMediaManager = null;
|
|
6463
|
+
}
|
|
6464
|
+
|
|
6465
|
+
Object.values(this.mediaRequestManagers).forEach((mediaRequestManager) =>
|
|
6466
|
+
mediaRequestManager.reset()
|
|
6467
|
+
);
|
|
6468
|
+
|
|
6469
|
+
this.receiveSlotManager.reset();
|
|
6470
|
+
this.mediaProperties.webrtcMediaConnection.close();
|
|
6471
|
+
this.sendSlotManager.reset();
|
|
6472
|
+
|
|
6473
|
+
this.mediaProperties.unsetPeerConnection();
|
|
6474
|
+
}
|
|
5420
6475
|
}
|
|
5421
6476
|
|
|
5422
6477
|
/**
|
|
5423
6478
|
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
5424
6479
|
*
|
|
5425
6480
|
* @param {AddMediaOptions} options
|
|
5426
|
-
* @returns {Promise}
|
|
6481
|
+
* @returns {Promise<void>}
|
|
5427
6482
|
* @public
|
|
5428
6483
|
* @memberof Meeting
|
|
5429
6484
|
*/
|
|
5430
|
-
addMedia(options: AddMediaOptions = {}) {
|
|
6485
|
+
async addMedia(options: AddMediaOptions = {}): Promise<void> {
|
|
6486
|
+
this.retriedWithTurnServer = false;
|
|
6487
|
+
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
5431
6488
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
5432
|
-
|
|
5433
|
-
let turnDiscoverySkippedReason;
|
|
5434
|
-
let turnServerUsed = false;
|
|
5435
|
-
|
|
5436
6489
|
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
5437
6490
|
|
|
5438
|
-
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
5439
|
-
|
|
6491
|
+
if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
|
|
6492
|
+
throw new MeetingNotActiveError();
|
|
5440
6493
|
}
|
|
5441
6494
|
|
|
5442
6495
|
if (MeetingUtil.isUserInLeftState(this.locusInfo)) {
|
|
5443
|
-
|
|
6496
|
+
throw new UserNotJoinedError();
|
|
5444
6497
|
}
|
|
5445
6498
|
|
|
5446
6499
|
const {
|
|
@@ -5459,7 +6512,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5459
6512
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
|
5460
6513
|
// @ts-ignore - isUserUnadmitted coming from SelfUtil
|
|
5461
6514
|
if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
|
|
5462
|
-
|
|
6515
|
+
throw new UserInLobbyError();
|
|
5463
6516
|
}
|
|
5464
6517
|
|
|
5465
6518
|
// @ts-ignore
|
|
@@ -5519,240 +6572,100 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5519
6572
|
|
|
5520
6573
|
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5521
6574
|
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5522
|
-
const promises = [];
|
|
5523
|
-
|
|
5524
|
-
// setup all the references to local streams in this.mediaProperties before creating media connection
|
|
5525
|
-
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5526
|
-
if (localStreams?.microphone) {
|
|
5527
|
-
promises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
5528
|
-
}
|
|
5529
|
-
if (localStreams?.camera) {
|
|
5530
|
-
promises.push(this.setLocalVideoStream(localStreams.camera));
|
|
5531
|
-
}
|
|
5532
|
-
if (localStreams?.screenShare?.video) {
|
|
5533
|
-
promises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
5534
|
-
}
|
|
5535
|
-
if (localStreams?.screenShare?.audio) {
|
|
5536
|
-
promises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
5537
|
-
}
|
|
5538
|
-
|
|
5539
|
-
return Promise.all(promises)
|
|
5540
|
-
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
5541
|
-
.then(async (turnDiscoveryObject) => {
|
|
5542
|
-
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
5543
|
-
turnServerUsed = !turnDiscoverySkippedReason;
|
|
5544
6575
|
|
|
5545
|
-
|
|
5546
|
-
|
|
5547
|
-
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
5548
|
-
|
|
5549
|
-
if (this.isMultistream) {
|
|
5550
|
-
this.remoteMediaManager = new RemoteMediaManager(
|
|
5551
|
-
this.receiveSlotManager,
|
|
5552
|
-
this.mediaRequestManagers,
|
|
5553
|
-
remoteMediaManagerConfig
|
|
5554
|
-
);
|
|
5555
|
-
|
|
5556
|
-
this.forwardEvent(
|
|
5557
|
-
this.remoteMediaManager,
|
|
5558
|
-
RemoteMediaManagerEvent.AudioCreated,
|
|
5559
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED
|
|
5560
|
-
);
|
|
5561
|
-
this.forwardEvent(
|
|
5562
|
-
this.remoteMediaManager,
|
|
5563
|
-
RemoteMediaManagerEvent.ScreenShareAudioCreated,
|
|
5564
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED
|
|
5565
|
-
);
|
|
5566
|
-
this.forwardEvent(
|
|
5567
|
-
this.remoteMediaManager,
|
|
5568
|
-
RemoteMediaManagerEvent.VideoLayoutChanged,
|
|
5569
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
5570
|
-
);
|
|
6576
|
+
try {
|
|
6577
|
+
await this.setUpLocalStreamReferences(localStreams);
|
|
5571
6578
|
|
|
5572
|
-
|
|
5573
|
-
}
|
|
6579
|
+
this.setMercuryListener();
|
|
5574
6580
|
|
|
5575
|
-
|
|
5576
|
-
})
|
|
5577
|
-
.then(() => {
|
|
5578
|
-
this.setMercuryListener();
|
|
5579
|
-
})
|
|
5580
|
-
.then(
|
|
5581
|
-
() =>
|
|
5582
|
-
getDevices()
|
|
5583
|
-
.then((devices) => {
|
|
5584
|
-
MeetingUtil.handleDeviceLogging(devices);
|
|
5585
|
-
})
|
|
5586
|
-
.catch(() => {}) // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
5587
|
-
)
|
|
5588
|
-
.then(() => {
|
|
5589
|
-
this.handleMediaLogging(this.mediaProperties);
|
|
5590
|
-
LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
|
|
6581
|
+
this.createStatsAnalyzer();
|
|
5591
6582
|
|
|
5592
|
-
|
|
5593
|
-
if (this.config.stats.enableStatsAnalyzer) {
|
|
5594
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5595
|
-
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
5596
|
-
this.statsAnalyzer = new StatsAnalyzer(
|
|
5597
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5598
|
-
this.config.stats,
|
|
5599
|
-
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5600
|
-
this.networkQualityMonitor
|
|
5601
|
-
);
|
|
5602
|
-
this.setupStatsAnalyzerEventHandlers();
|
|
5603
|
-
this.networkQualityMonitor.on(
|
|
5604
|
-
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
5605
|
-
this.sendNetworkQualityEvent.bind(this)
|
|
5606
|
-
);
|
|
5607
|
-
}
|
|
5608
|
-
})
|
|
5609
|
-
.catch((error) => {
|
|
5610
|
-
LoggerProxy.logger.error(
|
|
5611
|
-
`${LOG_HEADER} Error adding media , setting up peerconnection, `,
|
|
5612
|
-
error
|
|
5613
|
-
);
|
|
6583
|
+
await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
|
|
5614
6584
|
|
|
5615
|
-
|
|
5616
|
-
})
|
|
5617
|
-
.then(
|
|
5618
|
-
() =>
|
|
5619
|
-
new Promise<void>((resolve, reject) => {
|
|
5620
|
-
let timerCount = 0;
|
|
5621
|
-
|
|
5622
|
-
// eslint-disable-next-line func-names
|
|
5623
|
-
// eslint-disable-next-line prefer-arrow-callback
|
|
5624
|
-
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
5625
|
-
resolve();
|
|
5626
|
-
}
|
|
5627
|
-
const joiningTimer = setInterval(() => {
|
|
5628
|
-
timerCount += 1;
|
|
5629
|
-
if (this.meetingState === FULL_STATE.ACTIVE) {
|
|
5630
|
-
clearInterval(joiningTimer);
|
|
5631
|
-
resolve();
|
|
5632
|
-
}
|
|
6585
|
+
await Meeting.handleDeviceLogging();
|
|
5633
6586
|
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
}
|
|
5638
|
-
}, 1000);
|
|
5639
|
-
})
|
|
5640
|
-
)
|
|
5641
|
-
.then(() =>
|
|
5642
|
-
this.mediaProperties.waitForMediaConnectionConnected().catch(() => {
|
|
5643
|
-
// @ts-ignore
|
|
5644
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5645
|
-
name: 'client.ice.end',
|
|
5646
|
-
payload: {
|
|
5647
|
-
canProceed: false,
|
|
5648
|
-
icePhase: 'JOIN_MEETING_FINAL',
|
|
5649
|
-
errors: [
|
|
5650
|
-
// @ts-ignore
|
|
5651
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5652
|
-
{
|
|
5653
|
-
clientErrorCode: CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE,
|
|
5654
|
-
}
|
|
5655
|
-
),
|
|
5656
|
-
],
|
|
5657
|
-
},
|
|
5658
|
-
options: {
|
|
5659
|
-
meetingId: this.id,
|
|
5660
|
-
},
|
|
5661
|
-
});
|
|
5662
|
-
throw new Error(
|
|
5663
|
-
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5664
|
-
);
|
|
5665
|
-
})
|
|
5666
|
-
)
|
|
5667
|
-
.then(() => {
|
|
5668
|
-
if (this.mediaProperties.hasLocalShareStream()) {
|
|
5669
|
-
return this.enqueueScreenShareFloorRequest();
|
|
5670
|
-
}
|
|
6587
|
+
if (this.mediaProperties.hasLocalShareStream()) {
|
|
6588
|
+
await this.enqueueScreenShareFloorRequest();
|
|
6589
|
+
}
|
|
5671
6590
|
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
.then((connectionType) => {
|
|
5676
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
|
|
5677
|
-
correlation_id: this.correlationId,
|
|
5678
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5679
|
-
connectionType,
|
|
5680
|
-
isMultistream: this.isMultistream,
|
|
5681
|
-
});
|
|
5682
|
-
// @ts-ignore
|
|
5683
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5684
|
-
name: 'client.media-engine.ready',
|
|
5685
|
-
options: {
|
|
5686
|
-
meetingId: this.id,
|
|
5687
|
-
},
|
|
5688
|
-
});
|
|
5689
|
-
LoggerProxy.logger.info(
|
|
5690
|
-
`${LOG_HEADER} successfully established media connection, type=${connectionType}`
|
|
5691
|
-
);
|
|
6591
|
+
const connectionType = await this.mediaProperties.getCurrentConnectionType();
|
|
6592
|
+
// @ts-ignore
|
|
6593
|
+
const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
|
|
5692
6594
|
|
|
5693
|
-
|
|
5694
|
-
this.
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
6595
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_SUCCESS, {
|
|
6596
|
+
correlation_id: this.correlationId,
|
|
6597
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6598
|
+
connectionType,
|
|
6599
|
+
isMultistream: this.isMultistream,
|
|
6600
|
+
retriedWithTurnServer: this.retriedWithTurnServer,
|
|
6601
|
+
...reachabilityStats,
|
|
6602
|
+
});
|
|
6603
|
+
// @ts-ignore
|
|
6604
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
6605
|
+
name: 'client.media-engine.ready',
|
|
6606
|
+
options: {
|
|
6607
|
+
meetingId: this.id,
|
|
6608
|
+
},
|
|
6609
|
+
});
|
|
6610
|
+
LoggerProxy.logger.info(
|
|
6611
|
+
`${LOG_HEADER} successfully established media connection, type=${connectionType}`
|
|
6612
|
+
);
|
|
5698
6613
|
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
stack: error.stack,
|
|
5704
|
-
code: error.code,
|
|
5705
|
-
turnDiscoverySkippedReason,
|
|
5706
|
-
turnServerUsed,
|
|
5707
|
-
isMultistream: this.isMultistream,
|
|
5708
|
-
signalingState:
|
|
5709
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5710
|
-
?.signalingState ||
|
|
5711
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
5712
|
-
'unknown',
|
|
5713
|
-
connectionState:
|
|
5714
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5715
|
-
?.connectionState ||
|
|
5716
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
5717
|
-
'unknown',
|
|
5718
|
-
iceConnectionState:
|
|
5719
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5720
|
-
?.iceConnectionState ||
|
|
5721
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
5722
|
-
'unknown',
|
|
5723
|
-
});
|
|
6614
|
+
// We can log ReceiveSlot SSRCs only after the SDP exchange, so doing it here:
|
|
6615
|
+
this.remoteMediaManager?.logAllReceiveSlots();
|
|
6616
|
+
} catch (error) {
|
|
6617
|
+
LoggerProxy.logger.error(`${LOG_HEADER} failed to establish media connection: `, error);
|
|
5724
6618
|
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
? this.statsAnalyzer.stopAnalyzer()
|
|
5728
|
-
: Promise.resolve();
|
|
6619
|
+
// @ts-ignore
|
|
6620
|
+
const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
|
|
5729
6621
|
|
|
5730
|
-
|
|
5731
|
-
|
|
6622
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
6623
|
+
correlation_id: this.correlationId,
|
|
6624
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
6625
|
+
reason: error.message,
|
|
6626
|
+
stack: error.stack,
|
|
6627
|
+
code: error.code,
|
|
6628
|
+
turnDiscoverySkippedReason: this.turnDiscoverySkippedReason,
|
|
6629
|
+
turnServerUsed: this.turnServerUsed,
|
|
6630
|
+
retriedWithTurnServer: this.retriedWithTurnServer,
|
|
6631
|
+
isMultistream: this.isMultistream,
|
|
6632
|
+
signalingState:
|
|
6633
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6634
|
+
?.signalingState ||
|
|
6635
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
6636
|
+
'unknown',
|
|
6637
|
+
connectionState:
|
|
6638
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6639
|
+
?.connectionState ||
|
|
6640
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
6641
|
+
'unknown',
|
|
6642
|
+
iceConnectionState:
|
|
6643
|
+
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
6644
|
+
?.iceConnectionState ||
|
|
6645
|
+
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
6646
|
+
'unknown',
|
|
6647
|
+
...reachabilityMetrics,
|
|
6648
|
+
});
|
|
5732
6649
|
|
|
5733
|
-
|
|
5734
|
-
this.closePeerConnections();
|
|
5735
|
-
this.unsetPeerConnections();
|
|
5736
|
-
}
|
|
6650
|
+
await this.cleanUpOnAddMediaFailure();
|
|
5737
6651
|
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
6652
|
+
// Upload logs on error while adding media
|
|
6653
|
+
Trigger.trigger(
|
|
6654
|
+
this,
|
|
6655
|
+
{
|
|
6656
|
+
file: 'meeting/index',
|
|
6657
|
+
function: 'addMedia',
|
|
6658
|
+
},
|
|
6659
|
+
EVENTS.REQUEST_UPLOAD_LOGS,
|
|
6660
|
+
this
|
|
6661
|
+
);
|
|
5748
6662
|
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
6663
|
+
if (error instanceof Errors.SdpError) {
|
|
6664
|
+
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
6665
|
+
}
|
|
5752
6666
|
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
});
|
|
6667
|
+
throw error;
|
|
6668
|
+
}
|
|
5756
6669
|
}
|
|
5757
6670
|
|
|
5758
6671
|
/**
|
|
@@ -5766,6 +6679,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5766
6679
|
return !this.isRoapInProgress;
|
|
5767
6680
|
}
|
|
5768
6681
|
|
|
6682
|
+
/**
|
|
6683
|
+
* media failed, so collect a stats report from webrtc using the wcme connection to grab the rtc stats report
|
|
6684
|
+
* send a webrtc telemetry dump to the configured server using the internal media core check metrics configured callback
|
|
6685
|
+
* @param {String} callFrom - the function calling this function, optional.
|
|
6686
|
+
* @returns {Promise<void>}
|
|
6687
|
+
*/
|
|
6688
|
+
private forceSendStatsReport = async ({callFrom}: {callFrom?: string}) => {
|
|
6689
|
+
const LOG_HEADER = `Meeting:index#forceSendStatsReport --> called from ${callFrom} : `;
|
|
6690
|
+
try {
|
|
6691
|
+
await this.mediaProperties?.webrtcMediaConnection?.forceRtcMetricsSend();
|
|
6692
|
+
LoggerProxy.logger.info(
|
|
6693
|
+
`${LOG_HEADER} successfully uploaded available webrtc telemetry statistics`
|
|
6694
|
+
);
|
|
6695
|
+
} catch (e) {
|
|
6696
|
+
LoggerProxy.logger.error(`${LOG_HEADER} failed to upload webrtc telemetry statistics: `, e);
|
|
6697
|
+
}
|
|
6698
|
+
};
|
|
6699
|
+
|
|
5769
6700
|
/**
|
|
5770
6701
|
* Enqueues a media update operation.
|
|
5771
6702
|
* @param {String} mediaUpdateType one of MEDIA_UPDATE_TYPE values
|
|
@@ -6231,17 +7162,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6231
7162
|
.catch((error) => {
|
|
6232
7163
|
LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
|
|
6233
7164
|
|
|
6234
|
-
Metrics.sendBehavioralMetric(
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
|
|
6239
|
-
|
|
6240
|
-
|
|
6241
|
-
stack: error.stack,
|
|
6242
|
-
board: {channelUrl},
|
|
6243
|
-
}
|
|
6244
|
-
);
|
|
7165
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_STOP_WHITEBOARD_SHARE_FAILURE, {
|
|
7166
|
+
correlation_id: this.correlationId,
|
|
7167
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
7168
|
+
reason: error.message,
|
|
7169
|
+
stack: error.stack,
|
|
7170
|
+
board: {channelUrl},
|
|
7171
|
+
});
|
|
6245
7172
|
|
|
6246
7173
|
return Promise.reject(error);
|
|
6247
7174
|
})
|
|
@@ -6278,11 +7205,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6278
7205
|
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
6279
7206
|
// @ts-ignore
|
|
6280
7207
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
6281
|
-
name: 'client.share.
|
|
7208
|
+
name: 'client.share.floor-grant.request',
|
|
6282
7209
|
payload: {
|
|
6283
7210
|
mediaType: 'share',
|
|
7211
|
+
shareInstanceId: this.localShareInstanceId,
|
|
7212
|
+
},
|
|
7213
|
+
options: {
|
|
7214
|
+
meetingId: this.id,
|
|
6284
7215
|
},
|
|
6285
|
-
options: {meetingId: this.id},
|
|
6286
7216
|
});
|
|
6287
7217
|
|
|
6288
7218
|
return this.meetingRequest
|
|
@@ -6292,10 +7222,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6292
7222
|
deviceUrl: this.deviceUrl,
|
|
6293
7223
|
uri: content.url,
|
|
6294
7224
|
resourceUrl: this.resourceUrl,
|
|
7225
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6295
7226
|
})
|
|
6296
7227
|
.then(() => {
|
|
6297
7228
|
this.screenShareFloorState = ScreenShareFloorStatus.GRANTED;
|
|
6298
7229
|
|
|
7230
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_SUCCESS, {
|
|
7231
|
+
correlation_id: this.correlationId,
|
|
7232
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
7233
|
+
});
|
|
7234
|
+
|
|
6299
7235
|
return Promise.resolve();
|
|
6300
7236
|
})
|
|
6301
7237
|
.catch((error) => {
|
|
@@ -6308,6 +7244,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6308
7244
|
stack: error.stack,
|
|
6309
7245
|
});
|
|
6310
7246
|
|
|
7247
|
+
// @ts-ignore
|
|
7248
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
7249
|
+
name: 'client.share.floor-granted.local',
|
|
7250
|
+
payload: {
|
|
7251
|
+
mediaType: 'share',
|
|
7252
|
+
errors: MeetingUtil.getChangeMeetingFloorErrorPayload(error.message),
|
|
7253
|
+
shareInstanceId: this.localShareInstanceId,
|
|
7254
|
+
},
|
|
7255
|
+
options: {
|
|
7256
|
+
meetingId: this.id,
|
|
7257
|
+
},
|
|
7258
|
+
});
|
|
7259
|
+
|
|
6311
7260
|
this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
|
|
6312
7261
|
|
|
6313
7262
|
return Promise.reject(error);
|
|
@@ -6359,6 +7308,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6359
7308
|
name: 'client.share.stopped',
|
|
6360
7309
|
payload: {
|
|
6361
7310
|
mediaType: 'share',
|
|
7311
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6362
7312
|
},
|
|
6363
7313
|
options: {meetingId: this.id},
|
|
6364
7314
|
});
|
|
@@ -6375,6 +7325,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6375
7325
|
deviceUrl: this.deviceUrl,
|
|
6376
7326
|
uri: content.url,
|
|
6377
7327
|
resourceUrl: this.resourceUrl,
|
|
7328
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6378
7329
|
})
|
|
6379
7330
|
.catch((error) => {
|
|
6380
7331
|
LoggerProxy.logger.error('Meeting:index#releaseScreenShareFloor --> Error ', error);
|
|
@@ -6578,8 +7529,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6578
7529
|
|
|
6579
7530
|
if (layoutType) {
|
|
6580
7531
|
if (!LAYOUT_TYPES.includes(layoutType)) {
|
|
6581
|
-
this.rejectWithErrorLog(
|
|
6582
|
-
'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType
|
|
7532
|
+
return this.rejectWithErrorLog(
|
|
7533
|
+
'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType received.'
|
|
6583
7534
|
);
|
|
6584
7535
|
}
|
|
6585
7536
|
|
|
@@ -6717,6 +7668,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6717
7668
|
}
|
|
6718
7669
|
};
|
|
6719
7670
|
|
|
7671
|
+
/**
|
|
7672
|
+
* Functionality for when a share video is muted or unmuted.
|
|
7673
|
+
* @private
|
|
7674
|
+
* @memberof Meeting
|
|
7675
|
+
* @param {boolean} muted
|
|
7676
|
+
* @returns {undefined}
|
|
7677
|
+
*/
|
|
7678
|
+
private handleShareVideoStreamMuteStateChange = (muted: boolean) => {
|
|
7679
|
+
LoggerProxy.logger.log(
|
|
7680
|
+
`Meeting:index#handleShareVideoStreamMuteStateChange --> Share video stream mute state changed to muted ${muted}`
|
|
7681
|
+
);
|
|
7682
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_VIDEO_MUTE_STATE_CHANGE, {
|
|
7683
|
+
correlationId: this.correlationId,
|
|
7684
|
+
muted,
|
|
7685
|
+
});
|
|
7686
|
+
};
|
|
7687
|
+
|
|
6720
7688
|
/**
|
|
6721
7689
|
* Functionality for when a share video is ended.
|
|
6722
7690
|
* @private
|
|
@@ -6817,6 +7785,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6817
7785
|
if (roles.includes(SELF_ROLES.COHOST)) {
|
|
6818
7786
|
return 'cohost';
|
|
6819
7787
|
}
|
|
7788
|
+
if (roles.includes(SELF_ROLES.PRESENTER)) {
|
|
7789
|
+
return 'presenter';
|
|
7790
|
+
}
|
|
6820
7791
|
if (roles.includes(SELF_ROLES.ATTENDEE)) {
|
|
6821
7792
|
return 'attendee';
|
|
6822
7793
|
}
|
|
@@ -6907,8 +7878,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6907
7878
|
this.queuedMediaUpdates = [];
|
|
6908
7879
|
|
|
6909
7880
|
if (this.transcription) {
|
|
6910
|
-
this.
|
|
6911
|
-
this.triggerStopReceivingTranscriptionEvent();
|
|
7881
|
+
this.stopTranscription();
|
|
6912
7882
|
this.transcription = undefined;
|
|
6913
7883
|
}
|
|
6914
7884
|
};
|
|
@@ -7083,10 +8053,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7083
8053
|
.update({
|
|
7084
8054
|
// TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now
|
|
7085
8055
|
localTracks: {
|
|
7086
|
-
audio: this.mediaProperties.audioStream?.
|
|
7087
|
-
video: this.mediaProperties.videoStream?.
|
|
7088
|
-
screenShareVideo:
|
|
7089
|
-
|
|
8056
|
+
audio: this.mediaProperties.audioStream?.outputStream?.getTracks()[0] || null,
|
|
8057
|
+
video: this.mediaProperties.videoStream?.outputStream?.getTracks()[0] || null,
|
|
8058
|
+
screenShareVideo:
|
|
8059
|
+
this.mediaProperties.shareVideoStream?.outputStream?.getTracks()[0] || null,
|
|
8060
|
+
screenShareAudio:
|
|
8061
|
+
this.mediaProperties.shareAudioStream?.outputStream?.getTracks()[0] || null,
|
|
7090
8062
|
},
|
|
7091
8063
|
direction: {
|
|
7092
8064
|
audio: Media.getDirection(
|
|
@@ -7220,6 +8192,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7220
8192
|
}
|
|
7221
8193
|
|
|
7222
8194
|
if (floorRequestNeeded) {
|
|
8195
|
+
this.localShareInstanceId = uuid.v4();
|
|
8196
|
+
|
|
8197
|
+
// @ts-ignore
|
|
8198
|
+
this.webex.internal.newMetrics.submitClientEvent({
|
|
8199
|
+
name: 'client.share.initiated',
|
|
8200
|
+
payload: {
|
|
8201
|
+
mediaType: 'share',
|
|
8202
|
+
shareInstanceId: this.localShareInstanceId,
|
|
8203
|
+
},
|
|
8204
|
+
options: {meetingId: this.id},
|
|
8205
|
+
});
|
|
8206
|
+
|
|
8207
|
+
this.statsAnalyzer.updateMediaStatus({
|
|
8208
|
+
expected: {
|
|
8209
|
+
sendShare: true,
|
|
8210
|
+
},
|
|
8211
|
+
});
|
|
7223
8212
|
// we're sending the http request to Locus to request the screen share floor
|
|
7224
8213
|
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
7225
8214
|
// and also if sharing from the start, we need confluence to have been created
|
|
@@ -7268,6 +8257,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7268
8257
|
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
7269
8258
|
try {
|
|
7270
8259
|
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
8260
|
+
|
|
8261
|
+
this.statsAnalyzer.updateMediaStatus({
|
|
8262
|
+
expected: {
|
|
8263
|
+
sendShare: false,
|
|
8264
|
+
},
|
|
8265
|
+
});
|
|
7271
8266
|
} catch (e) {
|
|
7272
8267
|
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
7273
8268
|
}
|
|
@@ -7275,24 +8270,51 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7275
8270
|
}
|
|
7276
8271
|
|
|
7277
8272
|
/**
|
|
7278
|
-
* Gets
|
|
8273
|
+
* Gets permission token expiry information including timeLeft, expiryTime, currentTime
|
|
7279
8274
|
* (from the time the function has been fired)
|
|
7280
8275
|
*
|
|
7281
|
-
* @returns {
|
|
8276
|
+
* @returns {object} permissionTokenExpiryInfo
|
|
8277
|
+
* @returns {number} permissionTokenExpiryInfo.timeLeft The time left for token to expire
|
|
8278
|
+
* @returns {number} permissionTokenExpiryInfo.expiryTime The expiry time of permission token from the server
|
|
8279
|
+
* @returns {number} permissionTokenExpiryInfo.currentTime The current time of the local machine
|
|
7282
8280
|
*/
|
|
7283
|
-
public
|
|
8281
|
+
public getPermissionTokenExpiryInfo() {
|
|
7284
8282
|
if (!this.permissionTokenPayload) {
|
|
7285
8283
|
return undefined;
|
|
7286
8284
|
}
|
|
7287
8285
|
|
|
7288
|
-
const
|
|
8286
|
+
const permissionTokenExpiryFromServer = Number(this.permissionTokenPayload.exp);
|
|
8287
|
+
const permissionTokenIssuedTimeFromServer = Number(this.permissionTokenPayload.iat);
|
|
8288
|
+
|
|
8289
|
+
const shiftInTime = this.permissionTokenReceivedLocalTime - permissionTokenIssuedTimeFromServer;
|
|
7289
8290
|
|
|
7290
8291
|
// using new Date instead of Date.now() to allow for accurate unit testing
|
|
7291
8292
|
// https://github.com/sinonjs/fake-timers/issues/321
|
|
7292
|
-
const
|
|
8293
|
+
const currentTime = new Date().getTime();
|
|
8294
|
+
|
|
8295
|
+
// adjusted time is calculated in case your machine time is wrong
|
|
8296
|
+
const adjustedCurrentTime = currentTime - shiftInTime;
|
|
8297
|
+
|
|
8298
|
+
const timeLeft = (permissionTokenExpiryFromServer - adjustedCurrentTime) / 1000;
|
|
8299
|
+
|
|
8300
|
+
return {timeLeft, expiryTime: permissionTokenExpiryFromServer, currentTime};
|
|
8301
|
+
}
|
|
7293
8302
|
|
|
7294
|
-
|
|
7295
|
-
|
|
7296
|
-
|
|
8303
|
+
/**
|
|
8304
|
+
* Check if there is enough time left till the permission token expires
|
|
8305
|
+
* If not - refresh the permission token
|
|
8306
|
+
*
|
|
8307
|
+
* @param {number} threshold - time in seconds
|
|
8308
|
+
* @param {string} reason - reason for refreshing the permission token
|
|
8309
|
+
* @returns {Promise<void>}
|
|
8310
|
+
*/
|
|
8311
|
+
public checkAndRefreshPermissionToken(threshold: number, reason: string): Promise<void> {
|
|
8312
|
+
const timeLeft = this.getPermissionTokenExpiryInfo()?.timeLeft;
|
|
8313
|
+
|
|
8314
|
+
if (timeLeft !== undefined && timeLeft <= threshold) {
|
|
8315
|
+
return this.refreshPermissionToken(reason);
|
|
8316
|
+
}
|
|
8317
|
+
|
|
8318
|
+
return Promise.resolve();
|
|
7297
8319
|
}
|
|
7298
8320
|
}
|