@webex/plugin-meetings 3.0.0-stream-classes.4 → 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} +72 -15
- package/dist/constants.js +254 -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 +63 -38
- 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 +48 -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 +331 -44
- package/dist/meeting/index.js +2639 -1367
- package/dist/meeting/index.js.map +1 -1
- package/dist/{types/meeting → meeting}/locusMediaRequest.d.ts +1 -2
- 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 +4 -1
- package/dist/meeting/request.js +47 -32
- 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 +26 -1
- 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 +15 -0
- package/dist/metrics/constants.js +16 -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/{types/multistream → multistream}/remoteMediaManager.d.ts +9 -1
- package/dist/multistream/remoteMediaManager.js +74 -36
- 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/{types/reachability → reachability}/request.d.ts +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/{types/roap → roap}/request.d.ts +2 -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 +78 -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 +52 -16
- package/src/locus-info/mediaSharesUtils.ts +16 -0
- package/src/locus-info/parser.ts +47 -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 +1692 -627
- package/src/meeting/request.ts +19 -1
- 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 +14 -0
- package/src/multistream/remoteMediaManager.ts +41 -4
- 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/converged-space-meetings.js +7 -7
- package/test/integration/spec/journey.js +88 -106
- package/test/integration/spec/space-meeting.js +10 -10
- 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 +206 -13
- 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 +4354 -1285
- 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 +676 -195
- 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/multistream/remoteMediaManager.ts +10 -2
- 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/integrationTestUtils.js +4 -4
- 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 -158
- 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}/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}/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/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/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;
|
|
@@ -544,7 +607,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
544
607
|
meetingJoinUrl: any;
|
|
545
608
|
meetingNumber: any;
|
|
546
609
|
meetingState: any;
|
|
547
|
-
permissionToken:
|
|
610
|
+
permissionToken: string;
|
|
611
|
+
permissionTokenPayload: any;
|
|
612
|
+
permissionTokenReceivedLocalTime: number;
|
|
548
613
|
resourceId: any;
|
|
549
614
|
resourceUrl: string;
|
|
550
615
|
selfId: string;
|
|
@@ -556,7 +621,55 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
556
621
|
environment: string;
|
|
557
622
|
namespace = MEETINGS;
|
|
558
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;
|
|
559
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;
|
|
560
673
|
|
|
561
674
|
/**
|
|
562
675
|
* @param {Object} attrs
|
|
@@ -591,20 +704,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
591
704
|
*/
|
|
592
705
|
this.id = uuid.v4();
|
|
593
706
|
/**
|
|
594
|
-
*
|
|
707
|
+
* Call state used for metrics
|
|
595
708
|
* @instance
|
|
596
|
-
* @type {
|
|
709
|
+
* @type {CallStateForMetrics}
|
|
597
710
|
* @readonly
|
|
598
711
|
* @public
|
|
599
712
|
* @memberof Meeting
|
|
600
713
|
*/
|
|
601
|
-
|
|
714
|
+
this.callStateForMetrics = attrs.callStateForMetrics || {};
|
|
715
|
+
const correlationId = attrs.correlationId || attrs.callStateForMetrics?.correlationId;
|
|
716
|
+
if (correlationId) {
|
|
602
717
|
LoggerProxy.logger.log(
|
|
603
|
-
`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}`
|
|
604
719
|
);
|
|
605
|
-
this.correlationId =
|
|
720
|
+
this.callStateForMetrics.correlationId = correlationId;
|
|
606
721
|
} else {
|
|
607
|
-
this.correlationId = this.id;
|
|
722
|
+
this.callStateForMetrics.correlationId = this.id;
|
|
608
723
|
}
|
|
609
724
|
/**
|
|
610
725
|
* @instance
|
|
@@ -672,6 +787,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
672
787
|
*/
|
|
673
788
|
// @ts-ignore
|
|
674
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});
|
|
675
798
|
/**
|
|
676
799
|
* helper class for managing receive slots (for multistream media connections)
|
|
677
800
|
*/
|
|
@@ -1068,13 +1191,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1068
1191
|
*/
|
|
1069
1192
|
this.networkQualityMonitor = null;
|
|
1070
1193
|
/**
|
|
1194
|
+
* Indicates network status of the webrtc media connection
|
|
1071
1195
|
* @instance
|
|
1072
1196
|
* @type {String}
|
|
1073
1197
|
* @readonly
|
|
1074
1198
|
* @public
|
|
1075
1199
|
* @memberof Meeting
|
|
1076
1200
|
*/
|
|
1077
|
-
this.networkStatus =
|
|
1201
|
+
this.networkStatus = undefined;
|
|
1078
1202
|
/**
|
|
1079
1203
|
* Passing only info as we send basic info for meeting added event
|
|
1080
1204
|
* @instance
|
|
@@ -1139,7 +1263,17 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1139
1263
|
* @private
|
|
1140
1264
|
* @memberof Meeting
|
|
1141
1265
|
*/
|
|
1142
|
-
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;
|
|
1143
1277
|
|
|
1144
1278
|
/**
|
|
1145
1279
|
* Password status. If it's PASSWORD_STATUS.REQUIRED then verifyPassword() needs to be called
|
|
@@ -1193,6 +1327,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1193
1327
|
*/
|
|
1194
1328
|
this.keepAliveTimerId = null;
|
|
1195
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
|
+
|
|
1196
1348
|
/**
|
|
1197
1349
|
* The class that helps to control recording functions: start, stop, pause, resume, etc
|
|
1198
1350
|
* @instance
|
|
@@ -1245,6 +1397,60 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1245
1397
|
this.updateTranscodedMediaConnection();
|
|
1246
1398
|
}
|
|
1247
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;
|
|
1248
1454
|
}
|
|
1249
1455
|
|
|
1250
1456
|
/**
|
|
@@ -1278,23 +1484,87 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1278
1484
|
}
|
|
1279
1485
|
|
|
1280
1486
|
/**
|
|
1281
|
-
*
|
|
1282
|
-
* @
|
|
1283
|
-
* @param {String} [options.password] optional
|
|
1284
|
-
* @param {String} [options.captchaCode] optional
|
|
1285
|
-
* @public
|
|
1286
|
-
* @memberof Meeting
|
|
1287
|
-
* @returns {Promise}
|
|
1487
|
+
* Getter - Returns callStateForMetrics.correlationId
|
|
1488
|
+
* @returns {string}
|
|
1288
1489
|
*/
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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> {
|
|
1298
1568
|
// when fetch meeting info is called directly by the client, we want to clear out the random timer for sdk to do it
|
|
1299
1569
|
if (this.fetchMeetingInfoTimeoutId) {
|
|
1300
1570
|
clearTimeout(this.fetchMeetingInfoTimeoutId);
|
|
@@ -1302,7 +1572,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1302
1572
|
}
|
|
1303
1573
|
if (captchaCode && !this.requiredCaptcha) {
|
|
1304
1574
|
return Promise.reject(
|
|
1305
|
-
new Error(
|
|
1575
|
+
new Error(`${caller}() called with captchaCode when captcha was not required`)
|
|
1306
1576
|
);
|
|
1307
1577
|
}
|
|
1308
1578
|
if (
|
|
@@ -1311,50 +1581,47 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1311
1581
|
this.passwordStatus !== PASSWORD_STATUS.UNKNOWN
|
|
1312
1582
|
) {
|
|
1313
1583
|
return Promise.reject(
|
|
1314
|
-
new Error(
|
|
1584
|
+
new Error(`${caller}() called with password when password was not required`)
|
|
1315
1585
|
);
|
|
1316
1586
|
}
|
|
1317
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> {
|
|
1318
1606
|
try {
|
|
1319
1607
|
const captchaInfo = captchaCode
|
|
1320
1608
|
? {code: captchaCode, id: this.requiredCaptcha.captchaId}
|
|
1321
1609
|
: null;
|
|
1322
1610
|
|
|
1323
1611
|
const info = await this.attrs.meetingInfoProvider.fetchMeetingInfo(
|
|
1324
|
-
|
|
1325
|
-
|
|
1612
|
+
destination,
|
|
1613
|
+
destinationType,
|
|
1326
1614
|
password,
|
|
1327
1615
|
captchaInfo,
|
|
1328
1616
|
// @ts-ignore - config coming from registerPlugin
|
|
1329
1617
|
this.config.installedOrgID,
|
|
1330
1618
|
this.locusId,
|
|
1331
1619
|
extraParams,
|
|
1332
|
-
{meetingId: this.id}
|
|
1333
|
-
);
|
|
1334
|
-
|
|
1335
|
-
this.parseMeetingInfo(info, this.destination);
|
|
1336
|
-
this.meetingInfo = info ? {...info.body, meetingLookupUrl: info?.url} : null;
|
|
1337
|
-
this.meetingInfoFailureReason = MEETING_INFO_FAILURE_REASON.NONE;
|
|
1338
|
-
this.requiredCaptcha = null;
|
|
1339
|
-
if (
|
|
1340
|
-
this.passwordStatus === PASSWORD_STATUS.REQUIRED ||
|
|
1341
|
-
this.passwordStatus === PASSWORD_STATUS.VERIFIED
|
|
1342
|
-
) {
|
|
1343
|
-
this.passwordStatus = PASSWORD_STATUS.VERIFIED;
|
|
1344
|
-
} else {
|
|
1345
|
-
this.passwordStatus = PASSWORD_STATUS.NOT_REQUIRED;
|
|
1346
|
-
}
|
|
1347
|
-
|
|
1348
|
-
Trigger.trigger(
|
|
1349
|
-
this,
|
|
1350
|
-
{
|
|
1351
|
-
file: 'meetings',
|
|
1352
|
-
function: 'fetchMeetingInfo',
|
|
1353
|
-
},
|
|
1354
|
-
EVENT_TRIGGERS.MEETING_INFO_AVAILABLE
|
|
1620
|
+
{meetingId: this.id, sendCAevents}
|
|
1355
1621
|
);
|
|
1356
1622
|
|
|
1357
|
-
this.
|
|
1623
|
+
this.parseMeetingInfo(info?.body, this.destination, info?.errors);
|
|
1624
|
+
this.setMeetingInfo(info?.body, info?.url);
|
|
1358
1625
|
|
|
1359
1626
|
return Promise.resolve();
|
|
1360
1627
|
} catch (err) {
|
|
@@ -1416,19 +1683,113 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1416
1683
|
}
|
|
1417
1684
|
}
|
|
1418
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
|
+
|
|
1419
1778
|
/**
|
|
1420
1779
|
* Checks if the supplied password/host key is correct. It returns a promise with information whether the
|
|
1421
1780
|
* password and captcha code were correct or not.
|
|
1422
1781
|
* @param {String} password - this can be either a password or a host key, can be undefined if only captcha was required
|
|
1423
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
|
|
1424
1784
|
* @public
|
|
1425
1785
|
* @memberof Meeting
|
|
1426
1786
|
* @returns {Promise<{isPasswordValid: boolean, requiredCaptcha: boolean, failureReason: MEETING_INFO_FAILURE_REASON}>}
|
|
1427
1787
|
*/
|
|
1428
|
-
public verifyPassword(password: string, captchaCode: string) {
|
|
1788
|
+
public verifyPassword(password: string, captchaCode: string, sendCAevents = false) {
|
|
1429
1789
|
return this.fetchMeetingInfo({
|
|
1430
1790
|
password,
|
|
1431
1791
|
captchaCode,
|
|
1792
|
+
sendCAevents,
|
|
1432
1793
|
})
|
|
1433
1794
|
.then(() => {
|
|
1434
1795
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.VERIFY_PASSWORD_SUCCESS);
|
|
@@ -1628,6 +1989,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1628
1989
|
* @memberof Meeting
|
|
1629
1990
|
*/
|
|
1630
1991
|
private setUpInterpretationListener() {
|
|
1992
|
+
// TODO: check if its getting used or not
|
|
1631
1993
|
this.simultaneousInterpretation.on(INTERPRETATION.EVENTS.SUPPORT_LANGUAGES_UPDATE, () => {
|
|
1632
1994
|
Trigger.trigger(
|
|
1633
1995
|
this,
|
|
@@ -1638,7 +2000,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1638
2000
|
EVENT_TRIGGERS.MEETING_INTERPRETATION_SUPPORT_LANGUAGES_UPDATE
|
|
1639
2001
|
);
|
|
1640
2002
|
});
|
|
1641
|
-
|
|
2003
|
+
// TODO: check if its getting used or not
|
|
1642
2004
|
this.simultaneousInterpretation.on(
|
|
1643
2005
|
INTERPRETATION.EVENTS.HANDOFF_REQUESTS_ARRIVED,
|
|
1644
2006
|
(payload) => {
|
|
@@ -1655,6 +2017,43 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1655
2017
|
);
|
|
1656
2018
|
}
|
|
1657
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
|
+
|
|
1658
2057
|
/**
|
|
1659
2058
|
* Set up the locus info listener for meetings disconnected due to inactivity
|
|
1660
2059
|
* @returns {undefined}
|
|
@@ -1759,12 +2158,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1759
2158
|
|
|
1760
2159
|
/**
|
|
1761
2160
|
* sets the network status on meeting object
|
|
1762
|
-
* @param {
|
|
2161
|
+
* @param {NETWORK_STATUS} networkStatus
|
|
1763
2162
|
* @private
|
|
1764
2163
|
* @returns {undefined}
|
|
1765
2164
|
* @memberof Meeting
|
|
1766
2165
|
*/
|
|
1767
|
-
private setNetworkStatus(networkStatus
|
|
2166
|
+
private setNetworkStatus(networkStatus?: NETWORK_STATUS) {
|
|
1768
2167
|
if (networkStatus === NETWORK_STATUS.DISCONNECTED) {
|
|
1769
2168
|
Trigger.trigger(
|
|
1770
2169
|
this,
|
|
@@ -1944,7 +2343,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1944
2343
|
modifiedBy,
|
|
1945
2344
|
lastModified,
|
|
1946
2345
|
};
|
|
1947
|
-
|
|
1948
2346
|
Trigger.trigger(
|
|
1949
2347
|
this,
|
|
1950
2348
|
{
|
|
@@ -1975,19 +2373,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
1975
2373
|
this.locusInfo.on(
|
|
1976
2374
|
LOCUSINFO.EVENTS.CONTROLS_MEETING_TRANSCRIBE_UPDATED,
|
|
1977
2375
|
({caption, transcribing}) => {
|
|
1978
|
-
//
|
|
1979
|
-
if (
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
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
|
+
}
|
|
1991
2392
|
}
|
|
1992
2393
|
}
|
|
1993
2394
|
);
|
|
@@ -2019,16 +2420,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2019
2420
|
}
|
|
2020
2421
|
);
|
|
2021
2422
|
|
|
2022
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_JOIN_BREAKOUT_FROM_MAIN, ({mainLocusUrl}) => {
|
|
2023
|
-
this.meetingRequest.getLocusStatusByUrl(mainLocusUrl).catch((error) => {
|
|
2024
|
-
// clear main session cache when attendee join into breakout and forbidden to get locus from main locus url,
|
|
2025
|
-
// which means main session is not active for the attendee
|
|
2026
|
-
if (error?.statusCode === 403) {
|
|
2027
|
-
this.locusInfo.clearMainSessionLocusCache();
|
|
2028
|
-
}
|
|
2029
|
-
});
|
|
2030
|
-
});
|
|
2031
|
-
|
|
2032
2423
|
this.locusInfo.on(LOCUSINFO.EVENTS.CONTROLS_ENTRY_EXIT_TONE_UPDATED, ({entryExitTone}) => {
|
|
2033
2424
|
Trigger.trigger(
|
|
2034
2425
|
this,
|
|
@@ -2152,6 +2543,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2152
2543
|
if (
|
|
2153
2544
|
contentShare.beneficiaryId === previousContentShare?.beneficiaryId &&
|
|
2154
2545
|
contentShare.disposition === previousContentShare?.disposition &&
|
|
2546
|
+
contentShare.deviceUrlSharing === previousContentShare.deviceUrlSharing &&
|
|
2155
2547
|
whiteboardShare.beneficiaryId === previousWhiteboardShare?.beneficiaryId &&
|
|
2156
2548
|
whiteboardShare.disposition === previousWhiteboardShare?.disposition &&
|
|
2157
2549
|
whiteboardShare.resourceUrl === previousWhiteboardShare?.resourceUrl
|
|
@@ -2174,11 +2566,21 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2174
2566
|
// LOCAL - check if we started sharing content
|
|
2175
2567
|
else if (
|
|
2176
2568
|
this.selfId === contentShare.beneficiaryId &&
|
|
2177
|
-
contentShare.disposition === FLOOR_ACTION.GRANTED
|
|
2569
|
+
contentShare.disposition === FLOOR_ACTION.GRANTED &&
|
|
2570
|
+
contentShare.deviceUrlSharing === this.deviceUrl
|
|
2178
2571
|
) {
|
|
2179
2572
|
// CONTENT - sharing content local
|
|
2180
2573
|
newShareStatus = SHARE_STATUS.LOCAL_SHARE_ACTIVE;
|
|
2181
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
|
+
}
|
|
2182
2584
|
// If we did not hit the cases above, no one is sharng content, so we check if we are sharing whiteboard
|
|
2183
2585
|
// There is no concept of local/remote share for whiteboard
|
|
2184
2586
|
// It does not matter who requested to share the whiteboard, everyone gets the same view
|
|
@@ -2252,6 +2654,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2252
2654
|
switch (newShareStatus) {
|
|
2253
2655
|
case SHARE_STATUS.REMOTE_SHARE_ACTIVE: {
|
|
2254
2656
|
const sendStartedSharingRemote = () => {
|
|
2657
|
+
this.remoteShareInstanceId = contentShare.shareInstanceId;
|
|
2658
|
+
|
|
2255
2659
|
Trigger.trigger(
|
|
2256
2660
|
this,
|
|
2257
2661
|
{
|
|
@@ -2262,7 +2666,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2262
2666
|
{
|
|
2263
2667
|
memberId: contentShare.beneficiaryId,
|
|
2264
2668
|
url: contentShare.url,
|
|
2265
|
-
shareInstanceId:
|
|
2669
|
+
shareInstanceId: this.remoteShareInstanceId,
|
|
2266
2670
|
annotationInfo: contentShare.annotation,
|
|
2267
2671
|
}
|
|
2268
2672
|
);
|
|
@@ -2299,6 +2703,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2299
2703
|
name: 'client.share.floor-granted.local',
|
|
2300
2704
|
payload: {
|
|
2301
2705
|
mediaType: 'share',
|
|
2706
|
+
shareInstanceId: this.localShareInstanceId,
|
|
2302
2707
|
},
|
|
2303
2708
|
options: {meetingId: this.id},
|
|
2304
2709
|
});
|
|
@@ -2341,6 +2746,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2341
2746
|
} else if (newShareStatus === SHARE_STATUS.REMOTE_SHARE_ACTIVE) {
|
|
2342
2747
|
// if we got here, then some remote participant has stolen
|
|
2343
2748
|
// the presentation from another remote participant
|
|
2749
|
+
this.remoteShareInstanceId = contentShare.shareInstanceId;
|
|
2750
|
+
|
|
2344
2751
|
Trigger.trigger(
|
|
2345
2752
|
this,
|
|
2346
2753
|
{
|
|
@@ -2351,7 +2758,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2351
2758
|
{
|
|
2352
2759
|
memberId: contentShare.beneficiaryId,
|
|
2353
2760
|
url: contentShare.url,
|
|
2354
|
-
shareInstanceId:
|
|
2761
|
+
shareInstanceId: this.remoteShareInstanceId,
|
|
2355
2762
|
annotationInfo: contentShare.annotation,
|
|
2356
2763
|
}
|
|
2357
2764
|
);
|
|
@@ -2403,6 +2810,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2403
2810
|
this.locusId = this.locusUrl?.split('/').pop();
|
|
2404
2811
|
this.recordingController.setLocusUrl(this.locusUrl);
|
|
2405
2812
|
this.controlsOptionsManager.setLocusUrl(this.locusUrl);
|
|
2813
|
+
this.webinar.locusUrlUpdate(payload);
|
|
2406
2814
|
|
|
2407
2815
|
Trigger.trigger(
|
|
2408
2816
|
this,
|
|
@@ -2432,6 +2840,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2432
2840
|
this.breakouts.breakoutServiceUrlUpdate(payload?.services?.breakout?.url);
|
|
2433
2841
|
this.annotation.approvalUrlUpdate(payload?.services?.approval?.url);
|
|
2434
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
|
+
);
|
|
2435
2847
|
});
|
|
2436
2848
|
}
|
|
2437
2849
|
|
|
@@ -2472,12 +2884,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2472
2884
|
);
|
|
2473
2885
|
}
|
|
2474
2886
|
});
|
|
2475
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_INFO_UPDATED, () => {
|
|
2887
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.MEETING_INFO_UPDATED, ({isInitializing}) => {
|
|
2476
2888
|
this.updateMeetingActions();
|
|
2477
2889
|
this.recordingController.setDisplayHints(this.userDisplayHints);
|
|
2478
2890
|
this.recordingController.setUserPolicy(this.selfUserPolicies);
|
|
2479
2891
|
this.controlsOptionsManager.setDisplayHints(this.userDisplayHints);
|
|
2480
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
|
+
}
|
|
2481
2905
|
});
|
|
2482
2906
|
}
|
|
2483
2907
|
|
|
@@ -2623,7 +3047,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2623
3047
|
});
|
|
2624
3048
|
}
|
|
2625
3049
|
});
|
|
2626
|
-
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, (payload) => {
|
|
3050
|
+
this.locusInfo.on(LOCUSINFO.EVENTS.SELF_ADMITTED_GUEST, async (payload) => {
|
|
2627
3051
|
this.stopKeepAlive();
|
|
2628
3052
|
|
|
2629
3053
|
if (payload) {
|
|
@@ -2722,6 +3146,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2722
3146
|
this.simultaneousInterpretation.updateCanManageInterpreters(
|
|
2723
3147
|
payload.newRoles?.includes(SELF_ROLES.MODERATOR)
|
|
2724
3148
|
);
|
|
3149
|
+
this.webinar.updateCanManageWebcast(payload.newRoles?.includes(SELF_ROLES.MODERATOR));
|
|
2725
3150
|
Trigger.trigger(
|
|
2726
3151
|
this,
|
|
2727
3152
|
{
|
|
@@ -2963,30 +3388,40 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2963
3388
|
/**
|
|
2964
3389
|
* Sets the meeting info on the class instance
|
|
2965
3390
|
* @param {Object} meetingInfo
|
|
2966
|
-
* @param {
|
|
2967
|
-
* @param {String} meetingInfo.
|
|
2968
|
-
* @param {String} meetingInfo.
|
|
2969
|
-
* @param {String} meetingInfo.
|
|
2970
|
-
* @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
|
|
2971
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
|
|
2972
3404
|
* @returns {undefined}
|
|
2973
3405
|
* @private
|
|
2974
3406
|
* @memberof Meeting
|
|
2975
3407
|
*/
|
|
2976
3408
|
parseMeetingInfo(
|
|
2977
|
-
meetingInfo:
|
|
2978
|
-
|
|
2979
|
-
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
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
|
|
2988
3424
|
) {
|
|
2989
|
-
const webexMeetingInfo = meetingInfo?.body;
|
|
2990
3425
|
// We try to use as much info from Locus meeting object, stored in destination
|
|
2991
3426
|
|
|
2992
3427
|
let locusMeetingObject;
|
|
@@ -2996,39 +3431,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
2996
3431
|
}
|
|
2997
3432
|
|
|
2998
3433
|
// MeetingInfo will be undefined for 1:1 calls
|
|
2999
|
-
if (
|
|
3000
|
-
locusMeetingObject ||
|
|
3001
|
-
(webexMeetingInfo && !(meetingInfo?.errors && meetingInfo?.errors.length > 0))
|
|
3002
|
-
) {
|
|
3434
|
+
if (locusMeetingObject || (meetingInfo && !(errors?.length > 0))) {
|
|
3003
3435
|
this.conversationUrl =
|
|
3004
|
-
locusMeetingObject?.conversationUrl ||
|
|
3005
|
-
|
|
3006
|
-
this.conversationUrl;
|
|
3007
|
-
this.locusUrl = locusMeetingObject?.url || webexMeetingInfo?.locusUrl || this.locusUrl;
|
|
3436
|
+
locusMeetingObject?.conversationUrl || meetingInfo?.conversationUrl || this.conversationUrl;
|
|
3437
|
+
this.locusUrl = locusMeetingObject?.url || meetingInfo?.locusUrl || this.locusUrl;
|
|
3008
3438
|
// @ts-ignore - config coming from registerPlugin
|
|
3009
3439
|
this.setSipUri(
|
|
3010
3440
|
// @ts-ignore
|
|
3011
3441
|
this.config.experimental.enableUnifiedMeetings
|
|
3012
|
-
? locusMeetingObject?.info.sipUri ||
|
|
3013
|
-
: locusMeetingObject?.info.sipUri ||
|
|
3442
|
+
? locusMeetingObject?.info.sipUri || meetingInfo?.sipUrl
|
|
3443
|
+
: locusMeetingObject?.info.sipUri || meetingInfo?.sipMeetingUri || this.sipUri
|
|
3014
3444
|
);
|
|
3015
3445
|
// @ts-ignore - config coming from registerPlugin
|
|
3016
3446
|
if (this.config.experimental.enableUnifiedMeetings) {
|
|
3017
|
-
this.meetingNumber =
|
|
3018
|
-
|
|
3019
|
-
this.meetingJoinUrl = webexMeetingInfo?.meetingJoinUrl;
|
|
3447
|
+
this.meetingNumber = locusMeetingObject?.info.webExMeetingId || meetingInfo?.meetingNumber;
|
|
3448
|
+
this.meetingJoinUrl = meetingInfo?.meetingJoinUrl;
|
|
3020
3449
|
}
|
|
3021
3450
|
this.owner =
|
|
3022
|
-
locusMeetingObject?.info.owner ||
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
this.permissionToken = webexMeetingInfo?.permissionToken;
|
|
3027
|
-
this.setSelfUserPolicies(this.permissionToken);
|
|
3451
|
+
locusMeetingObject?.info.owner || meetingInfo?.owner || meetingInfo?.hostId || this.owner;
|
|
3452
|
+
this.permissionToken = meetingInfo?.permissionToken;
|
|
3453
|
+
this.setPermissionTokenPayload(meetingInfo?.permissionToken);
|
|
3454
|
+
this.setSelfUserPolicies();
|
|
3028
3455
|
// Need to populate environment when sending CA event
|
|
3029
|
-
this.environment = locusMeetingObject?.info.channel ||
|
|
3456
|
+
this.environment = locusMeetingObject?.info.channel || meetingInfo?.channel;
|
|
3030
3457
|
}
|
|
3031
|
-
MeetingUtil.parseInterpretationInfo(this,
|
|
3458
|
+
MeetingUtil.parseInterpretationInfo(this, meetingInfo);
|
|
3032
3459
|
}
|
|
3033
3460
|
|
|
3034
3461
|
/**
|
|
@@ -3080,6 +3507,11 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3080
3507
|
}) &&
|
|
3081
3508
|
this.meetingInfo?.video?.supportHDV) ||
|
|
3082
3509
|
!this.arePolicyRestrictionsSupported(),
|
|
3510
|
+
enforceVirtualBackground:
|
|
3511
|
+
ControlsOptionsUtil.hasPolicies({
|
|
3512
|
+
requiredPolicies: [SELF_POLICY.ENFORCE_VIRTUAL_BACKGROUND],
|
|
3513
|
+
policies: this.selfUserPolicies,
|
|
3514
|
+
}) && this.arePolicyRestrictionsSupported(),
|
|
3083
3515
|
supportHQV:
|
|
3084
3516
|
(ControlsOptionsUtil.hasPolicies({
|
|
3085
3517
|
requiredPolicies: [SELF_POLICY.SUPPORT_HQV],
|
|
@@ -3233,6 +3665,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3233
3665
|
requiredPolicies: [SELF_POLICY.SUPPORT_FILE_TRANSFER],
|
|
3234
3666
|
policies: this.selfUserPolicies,
|
|
3235
3667
|
}),
|
|
3668
|
+
canChat: ControlsOptionsUtil.hasPolicies({
|
|
3669
|
+
requiredPolicies: [SELF_POLICY.SUPPORT_CHAT],
|
|
3670
|
+
policies: this.selfUserPolicies,
|
|
3671
|
+
}),
|
|
3236
3672
|
canShareApplication:
|
|
3237
3673
|
(ControlsOptionsUtil.hasHints({
|
|
3238
3674
|
requiredHints: [DISPLAY_HINTS.SHARE_APPLICATION],
|
|
@@ -3288,11 +3724,22 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3288
3724
|
|
|
3289
3725
|
/**
|
|
3290
3726
|
* Sets the self user policies based on the contents of the permission token
|
|
3727
|
+
* @returns {void}
|
|
3728
|
+
*/
|
|
3729
|
+
setSelfUserPolicies() {
|
|
3730
|
+
this.selfUserPolicies = this.permissionTokenPayload?.permission?.userPolicies;
|
|
3731
|
+
this.enforceVBGImagesURL = this.permissionTokenPayload?.permission?.enforceVBGImagesURL;
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3734
|
+
/**
|
|
3735
|
+
* Sets the permission token payload on the class instance
|
|
3736
|
+
*
|
|
3291
3737
|
* @param {String} permissionToken
|
|
3292
3738
|
* @returns {void}
|
|
3293
3739
|
*/
|
|
3294
|
-
|
|
3295
|
-
this.
|
|
3740
|
+
public setPermissionTokenPayload(permissionToken: string) {
|
|
3741
|
+
this.permissionTokenPayload = jwtDecode(permissionToken);
|
|
3742
|
+
this.permissionTokenReceivedLocalTime = new Date().getTime();
|
|
3296
3743
|
}
|
|
3297
3744
|
|
|
3298
3745
|
/**
|
|
@@ -3386,8 +3833,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3386
3833
|
* @memberof Meeting
|
|
3387
3834
|
*/
|
|
3388
3835
|
closeRemoteStreams() {
|
|
3389
|
-
const {remoteAudioStream, remoteVideoStream, remoteShareStream
|
|
3390
|
-
this.mediaProperties;
|
|
3836
|
+
const {remoteAudioStream, remoteVideoStream, remoteShareStream} = this.mediaProperties;
|
|
3391
3837
|
|
|
3392
3838
|
/**
|
|
3393
3839
|
* Triggers an event to the developer
|
|
@@ -3428,7 +3874,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3428
3874
|
stopStream(remoteAudioStream, EVENT_TYPES.REMOTE_AUDIO),
|
|
3429
3875
|
stopStream(remoteVideoStream, EVENT_TYPES.REMOTE_VIDEO),
|
|
3430
3876
|
stopStream(remoteShareStream, EVENT_TYPES.REMOTE_SHARE),
|
|
3431
|
-
stopStream(shareAudioStream, EVENT_TYPES.REMOTE_SHARE_AUDIO),
|
|
3432
3877
|
]);
|
|
3433
3878
|
}
|
|
3434
3879
|
|
|
@@ -3499,11 +3944,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3499
3944
|
private async setLocalShareVideoStream(localDisplayStream?: LocalDisplayStream) {
|
|
3500
3945
|
const oldStream = this.mediaProperties.shareVideoStream;
|
|
3501
3946
|
|
|
3947
|
+
oldStream?.off(StreamEventNames.MuteStateChange, this.handleShareVideoStreamMuteStateChange);
|
|
3502
3948
|
oldStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3503
3949
|
oldStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3504
3950
|
|
|
3505
3951
|
this.mediaProperties.setLocalShareVideoStream(localDisplayStream);
|
|
3506
3952
|
|
|
3953
|
+
localDisplayStream?.on(
|
|
3954
|
+
StreamEventNames.MuteStateChange,
|
|
3955
|
+
this.handleShareVideoStreamMuteStateChange
|
|
3956
|
+
);
|
|
3507
3957
|
localDisplayStream?.on(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3508
3958
|
localDisplayStream?.on(
|
|
3509
3959
|
LocalStreamEventNames.OutputTrackChange,
|
|
@@ -3559,7 +4009,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3559
4009
|
functionName: string;
|
|
3560
4010
|
isPublished: boolean;
|
|
3561
4011
|
mediaType: MediaType;
|
|
3562
|
-
stream:
|
|
4012
|
+
stream: LocalStream;
|
|
3563
4013
|
}) {
|
|
3564
4014
|
const {functionName, isPublished, mediaType, stream} = options;
|
|
3565
4015
|
Trigger.trigger(
|
|
@@ -3593,12 +4043,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3593
4043
|
videoStream?.off(StreamEventNames.MuteStateChange, this.localVideoStreamMuteStateHandler);
|
|
3594
4044
|
videoStream?.off(LocalStreamEventNames.OutputTrackChange, this.localOutputTrackChangeHandler);
|
|
3595
4045
|
|
|
3596
|
-
shareAudioStream?.off(StreamEventNames.
|
|
4046
|
+
shareAudioStream?.off(StreamEventNames.Ended, this.handleShareAudioStreamEnded);
|
|
3597
4047
|
shareAudioStream?.off(
|
|
3598
4048
|
LocalStreamEventNames.OutputTrackChange,
|
|
3599
4049
|
this.localOutputTrackChangeHandler
|
|
3600
4050
|
);
|
|
3601
|
-
shareVideoStream?.off(
|
|
4051
|
+
shareVideoStream?.off(
|
|
4052
|
+
StreamEventNames.MuteStateChange,
|
|
4053
|
+
this.handleShareVideoStreamMuteStateChange
|
|
4054
|
+
);
|
|
4055
|
+
shareVideoStream?.off(StreamEventNames.Ended, this.handleShareVideoStreamEnded);
|
|
3602
4056
|
shareVideoStream?.off(
|
|
3603
4057
|
LocalStreamEventNames.OutputTrackChange,
|
|
3604
4058
|
this.localOutputTrackChangeHandler
|
|
@@ -3712,6 +4166,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3712
4166
|
this.receiveSlotManager.reset();
|
|
3713
4167
|
this.mediaProperties.webrtcMediaConnection.close();
|
|
3714
4168
|
this.sendSlotManager.reset();
|
|
4169
|
+
this.setNetworkStatus(undefined);
|
|
3715
4170
|
}
|
|
3716
4171
|
|
|
3717
4172
|
this.audio = null;
|
|
@@ -3733,18 +4188,31 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3733
4188
|
if (this.config.reconnection.detection) {
|
|
3734
4189
|
// @ts-ignore
|
|
3735
4190
|
this.webex.internal.mercury.off(ONLINE);
|
|
4191
|
+
// @ts-ignore
|
|
4192
|
+
this.webex.internal.mercury.off(OFFLINE);
|
|
3736
4193
|
}
|
|
3737
4194
|
}
|
|
3738
4195
|
|
|
3739
4196
|
/**
|
|
3740
|
-
* Convenience method to set the correlation id for the
|
|
3741
|
-
* @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
|
|
3742
4199
|
* @returns {undefined}
|
|
3743
|
-
* @
|
|
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
|
|
3744
4212
|
* @memberof Meeting
|
|
3745
4213
|
*/
|
|
3746
|
-
|
|
3747
|
-
this.
|
|
4214
|
+
public updateCallStateForMetrics(callStateForMetrics: CallStateForMetrics) {
|
|
4215
|
+
this.callStateForMetrics = {...this.callStateForMetrics, ...callStateForMetrics};
|
|
3748
4216
|
}
|
|
3749
4217
|
|
|
3750
4218
|
/**
|
|
@@ -3981,6 +4449,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
3981
4449
|
) {
|
|
3982
4450
|
const {mediaOptions, joinOptions} = options;
|
|
3983
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
|
+
|
|
3984
4460
|
return this.join(joinOptions)
|
|
3985
4461
|
.then((joinResponse) =>
|
|
3986
4462
|
this.addMedia(mediaOptions).then((mediaResponse) => ({
|
|
@@ -4062,6 +4538,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4062
4538
|
|
|
4063
4539
|
return this.reconnectionManager
|
|
4064
4540
|
.reconnect(options)
|
|
4541
|
+
.then(() => this.waitForRemoteSDPAnswer())
|
|
4542
|
+
.then(() => this.waitForMediaConnectionConnected())
|
|
4065
4543
|
.then(() => {
|
|
4066
4544
|
Trigger.trigger(
|
|
4067
4545
|
this,
|
|
@@ -4072,6 +4550,18 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4072
4550
|
EVENT_TRIGGERS.MEETING_RECONNECTION_SUCCESS
|
|
4073
4551
|
);
|
|
4074
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);
|
|
4075
4565
|
})
|
|
4076
4566
|
.catch((error) => {
|
|
4077
4567
|
Trigger.trigger(
|
|
@@ -4118,7 +4608,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4118
4608
|
}
|
|
4119
4609
|
|
|
4120
4610
|
LoggerProxy.logger.error(
|
|
4121
|
-
'Meeting:index#isTranscriptionSupported --> Webex Assistant is not supported'
|
|
4611
|
+
'Meeting:index#isTranscriptionSupported --> Webex Assistant is not enabled/supported'
|
|
4122
4612
|
);
|
|
4123
4613
|
|
|
4124
4614
|
return false;
|
|
@@ -4139,109 +4629,139 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4139
4629
|
}
|
|
4140
4630
|
|
|
4141
4631
|
/**
|
|
4142
|
-
*
|
|
4143
|
-
* @
|
|
4144
|
-
* @returns {
|
|
4632
|
+
* sets Caption language for the meeting
|
|
4633
|
+
* @param {string} language
|
|
4634
|
+
* @returns {Promise}
|
|
4145
4635
|
*/
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
${event}`
|
|
4153
|
-
);
|
|
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
|
+
);
|
|
4154
4642
|
|
|
4155
|
-
|
|
4156
|
-
|
|
4643
|
+
reject(new Error('Webex Assistant is not enabled/supported'));
|
|
4644
|
+
}
|
|
4157
4645
|
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
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;
|
|
4165
4654
|
|
|
4166
|
-
|
|
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}`);
|
|
4167
4674
|
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
reason: 'unexpected error: transcription LLM web socket connection error had occured.',
|
|
4171
|
-
event,
|
|
4172
|
-
});
|
|
4675
|
+
reject(error);
|
|
4676
|
+
}
|
|
4173
4677
|
});
|
|
4174
4678
|
}
|
|
4175
4679
|
|
|
4176
4680
|
/**
|
|
4177
|
-
*
|
|
4178
|
-
* @
|
|
4179
|
-
* @returns {Promise
|
|
4681
|
+
* sets Spoken language for the meeting
|
|
4682
|
+
* @param {string} language
|
|
4683
|
+
* @returns {Promise}
|
|
4180
4684
|
*/
|
|
4181
|
-
|
|
4182
|
-
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
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
|
+
};
|
|
4186
4714
|
|
|
4187
|
-
try {
|
|
4188
|
-
const {datachannelUrl} = this.locusInfo.info;
|
|
4189
|
-
// @ts-ignore - fix type
|
|
4190
|
-
const {
|
|
4191
|
-
body: {webSocketUrl},
|
|
4192
4715
|
// @ts-ignore
|
|
4193
|
-
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
});
|
|
4716
|
+
this.webex.internal.voicea.on(
|
|
4717
|
+
VOICEAEVENTS.SPOKEN_LANGUAGE_UPDATE,
|
|
4718
|
+
voiceaListenerLanguageUpdate
|
|
4719
|
+
);
|
|
4198
4720
|
|
|
4199
|
-
|
|
4200
|
-
|
|
4201
|
-
|
|
4202
|
-
|
|
4721
|
+
// @ts-ignore
|
|
4722
|
+
this.webex.internal.voicea.setSpokenLanguage(language);
|
|
4723
|
+
} catch (error) {
|
|
4724
|
+
LoggerProxy.logger.error(`Meeting:index#setSpokenLanguage --> ${error}`);
|
|
4203
4725
|
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
this.members
|
|
4209
|
-
);
|
|
4726
|
+
reject(error);
|
|
4727
|
+
}
|
|
4728
|
+
});
|
|
4729
|
+
}
|
|
4210
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()) {
|
|
4211
4739
|
LoggerProxy.logger.info(
|
|
4212
|
-
|
|
4213
|
-
opened LLM web socket connection successfully.`
|
|
4740
|
+
'Meeting:index#startTranscription --> Attempting to enable transcription!'
|
|
4214
4741
|
);
|
|
4215
4742
|
|
|
4216
|
-
|
|
4217
|
-
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
}
|
|
4221
|
-
|
|
4222
|
-
// retrieve and pass the payload
|
|
4223
|
-
this.transcription.subscribe((payload) => {
|
|
4224
|
-
Trigger.trigger(
|
|
4225
|
-
this,
|
|
4226
|
-
{
|
|
4227
|
-
file: 'meeting/index',
|
|
4228
|
-
function: 'join',
|
|
4229
|
-
},
|
|
4230
|
-
EVENT_TRIGGERS.MEETING_STARTED_RECEIVING_TRANSCRIPTION,
|
|
4231
|
-
payload
|
|
4232
|
-
);
|
|
4233
|
-
});
|
|
4743
|
+
try {
|
|
4744
|
+
if (!this.areVoiceaEventsSetup) {
|
|
4745
|
+
this.setUpVoiceaListeners();
|
|
4746
|
+
}
|
|
4234
4747
|
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
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');
|
|
4245
4765
|
}
|
|
4246
4766
|
}
|
|
4247
4767
|
|
|
@@ -4284,13 +4804,37 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4284
4804
|
};
|
|
4285
4805
|
|
|
4286
4806
|
/**
|
|
4287
|
-
*
|
|
4288
|
-
* the web socket connection properly
|
|
4807
|
+
* This method stops receiving transcription for the current meeting
|
|
4289
4808
|
* @returns {void}
|
|
4290
4809
|
*/
|
|
4291
|
-
|
|
4810
|
+
stopTranscription() {
|
|
4292
4811
|
if (this.transcription) {
|
|
4293
|
-
|
|
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();
|
|
4294
4838
|
}
|
|
4295
4839
|
}
|
|
4296
4840
|
|
|
@@ -4303,12 +4847,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4303
4847
|
private triggerStopReceivingTranscriptionEvent() {
|
|
4304
4848
|
LoggerProxy.logger.info(`
|
|
4305
4849
|
Meeting:index#stopReceivingTranscription -->
|
|
4306
|
-
closed
|
|
4850
|
+
closed voicea event listeners successfully.`);
|
|
4307
4851
|
|
|
4308
4852
|
Trigger.trigger(
|
|
4309
4853
|
this,
|
|
4310
4854
|
{
|
|
4311
|
-
file: 'meeting',
|
|
4855
|
+
file: 'meeting/index',
|
|
4312
4856
|
function: 'triggerStopReceivingTranscriptionEvent',
|
|
4313
4857
|
},
|
|
4314
4858
|
EVENT_TRIGGERS.MEETING_STOPPED_RECEIVING_TRANSCRIPTION
|
|
@@ -4327,7 +4871,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4327
4871
|
* if joining as host on second loop, pass pin and pass moderator if joining as guest on second loop
|
|
4328
4872
|
* Scenario D: Joining any other way (sip, pstn, conversationUrl, link just need to specify resourceId)
|
|
4329
4873
|
*/
|
|
4330
|
-
public join(options: any = {}) {
|
|
4874
|
+
public async join(options: any = {}) {
|
|
4331
4875
|
// @ts-ignore - fix type
|
|
4332
4876
|
if (!this.webex.meetings.registered) {
|
|
4333
4877
|
const errorMessage = 'Meeting:index#join --> Device not registered';
|
|
@@ -4381,27 +4925,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4381
4925
|
// @ts-ignore
|
|
4382
4926
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
4383
4927
|
name: 'client.call.initiated',
|
|
4384
|
-
payload: {
|
|
4928
|
+
payload: {
|
|
4929
|
+
trigger: this.callStateForMetrics.joinTrigger || 'user-interaction',
|
|
4930
|
+
isRoapCallEnabled: true,
|
|
4931
|
+
pstnAudioType: options?.pstnAudioType,
|
|
4932
|
+
},
|
|
4385
4933
|
options: {meetingId: this.id},
|
|
4386
4934
|
});
|
|
4387
4935
|
|
|
4388
|
-
if (!isEmpty(this.meetingInfo)) {
|
|
4389
|
-
// @ts-ignore
|
|
4390
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4391
|
-
name: 'client.meetinginfo.request',
|
|
4392
|
-
options: {meetingId: this.id},
|
|
4393
|
-
});
|
|
4394
|
-
|
|
4395
|
-
// @ts-ignore
|
|
4396
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4397
|
-
name: 'client.meetinginfo.response',
|
|
4398
|
-
payload: {
|
|
4399
|
-
identifiers: {meetingLookupUrl: this.meetingInfo?.meetingLookupUrl},
|
|
4400
|
-
},
|
|
4401
|
-
options: {meetingId: this.id},
|
|
4402
|
-
});
|
|
4403
|
-
}
|
|
4404
|
-
|
|
4405
4936
|
LoggerProxy.logger.log('Meeting:index#join --> Joining a meeting');
|
|
4406
4937
|
|
|
4407
4938
|
if (this.meetingFiniteStateMachine.state === MEETING_STATE_MACHINE.STATES.ENDED) {
|
|
@@ -4455,44 +4986,55 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4455
4986
|
|
|
4456
4987
|
this.isMultistream = !!options.enableMultistream;
|
|
4457
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
|
+
|
|
4458
5026
|
return MeetingUtil.joinMeetingOptions(this, options)
|
|
4459
5027
|
.then((join) => {
|
|
4460
5028
|
this.meetingFiniteStateMachine.join();
|
|
4461
5029
|
LoggerProxy.logger.log('Meeting:index#join --> Success');
|
|
4462
5030
|
|
|
4463
|
-
return join;
|
|
4464
|
-
})
|
|
4465
|
-
.then((join) => {
|
|
4466
|
-
joinSuccess(join);
|
|
4467
|
-
this.deferJoin = undefined;
|
|
4468
5031
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.JOIN_SUCCESS, {
|
|
4469
5032
|
correlation_id: this.correlationId,
|
|
4470
5033
|
});
|
|
4471
5034
|
|
|
4472
|
-
|
|
4473
|
-
})
|
|
4474
|
-
.then(async (join) => {
|
|
4475
|
-
// @ts-ignore - config coming from registerPlugin
|
|
4476
|
-
if (this.config.enableAutomaticLLM) {
|
|
4477
|
-
await this.updateLLMConnection();
|
|
4478
|
-
}
|
|
5035
|
+
joinSuccess(join);
|
|
4479
5036
|
|
|
4480
|
-
|
|
4481
|
-
})
|
|
4482
|
-
.then(async (join) => {
|
|
4483
|
-
if (isBrowser) {
|
|
4484
|
-
// @ts-ignore - config coming from registerPlugin
|
|
4485
|
-
if (this.config.receiveTranscription || options.receiveTranscription) {
|
|
4486
|
-
if (this.isTranscriptionSupported()) {
|
|
4487
|
-
await this.receiveTranscription();
|
|
4488
|
-
LoggerProxy.logger.info('Meeting:index#join --> enabled to recieve transcription!');
|
|
4489
|
-
}
|
|
4490
|
-
}
|
|
4491
|
-
} else {
|
|
4492
|
-
LoggerProxy.logger.error(
|
|
4493
|
-
'Meeting:index#join --> Receving transcription is not supported on this platform'
|
|
4494
|
-
);
|
|
4495
|
-
}
|
|
5037
|
+
this.deferJoin = undefined;
|
|
4496
5038
|
|
|
4497
5039
|
return join;
|
|
4498
5040
|
})
|
|
@@ -4528,9 +5070,44 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4528
5070
|
);
|
|
4529
5071
|
|
|
4530
5072
|
joinFailed(error);
|
|
5073
|
+
|
|
4531
5074
|
this.deferJoin = undefined;
|
|
4532
5075
|
|
|
4533
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;
|
|
4534
5111
|
});
|
|
4535
5112
|
}
|
|
4536
5113
|
|
|
@@ -4910,10 +5487,77 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4910
5487
|
}
|
|
4911
5488
|
};
|
|
4912
5489
|
|
|
4913
|
-
|
|
4914
|
-
|
|
4915
|
-
|
|
4916
|
-
|
|
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);
|
|
5498
|
+
|
|
5499
|
+
this.mediaProperties.webrtcMediaConnection.roapMessageReceived(roapMessage);
|
|
5500
|
+
|
|
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
|
+
});
|
|
4917
5561
|
|
|
4918
5562
|
this.mediaProperties.webrtcMediaConnection.on(Event.ROAP_DONE, () => {
|
|
4919
5563
|
this.mediaNegotiatedEvent();
|
|
@@ -4928,12 +5572,6 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4928
5572
|
|
|
4929
5573
|
switch (event.roapMessage.messageType) {
|
|
4930
5574
|
case 'OK':
|
|
4931
|
-
// @ts-ignore
|
|
4932
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4933
|
-
name: 'client.media-engine.remote-sdp-received',
|
|
4934
|
-
options: {meetingId: this.id},
|
|
4935
|
-
});
|
|
4936
|
-
|
|
4937
5575
|
logRequest(
|
|
4938
5576
|
this.roap.sendRoapOK({
|
|
4939
5577
|
seq: event.roapMessage.seq,
|
|
@@ -4947,33 +5585,32 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
4947
5585
|
break;
|
|
4948
5586
|
|
|
4949
5587
|
case 'OFFER':
|
|
4950
|
-
// @ts-ignore
|
|
4951
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4952
|
-
name: 'client.media-engine.local-sdp-generated',
|
|
4953
|
-
options: {meetingId: this.id},
|
|
4954
|
-
});
|
|
4955
|
-
|
|
4956
5588
|
logRequest(
|
|
4957
|
-
this.roap
|
|
4958
|
-
|
|
4959
|
-
|
|
4960
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
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
|
+
}),
|
|
4964
5603
|
{
|
|
4965
5604
|
logText: `${LOG_HEADER} Roap Offer`,
|
|
4966
5605
|
}
|
|
4967
|
-
)
|
|
5606
|
+
).catch(() => {
|
|
5607
|
+
this.deferSDPAnswer.reject();
|
|
5608
|
+
clearTimeout(this.sdpResponseTimer);
|
|
5609
|
+
this.sdpResponseTimer = undefined;
|
|
5610
|
+
});
|
|
4968
5611
|
break;
|
|
4969
5612
|
|
|
4970
5613
|
case 'ANSWER':
|
|
4971
|
-
// @ts-ignore
|
|
4972
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
4973
|
-
name: 'client.media-engine.remote-sdp-received',
|
|
4974
|
-
options: {meetingId: this.id},
|
|
4975
|
-
});
|
|
4976
|
-
|
|
4977
5614
|
logRequest(
|
|
4978
5615
|
this.roap.sendRoapAnswer({
|
|
4979
5616
|
sdp: event.roapMessage.sdp,
|
|
@@ -5086,68 +5723,71 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5086
5723
|
|
|
5087
5724
|
this.mediaProperties.webrtcMediaConnection.on(Event.CONNECTION_STATE_CHANGED, (event) => {
|
|
5088
5725
|
const connectionFailed = () => {
|
|
5089
|
-
// we know the media connection failed and browser will not attempt to recover it any more
|
|
5090
|
-
// so reset the timer as it's not needed anymore, we want to reconnect immediately
|
|
5091
|
-
this.reconnectionManager.resetReconnectionTimer();
|
|
5092
|
-
|
|
5093
|
-
this.reconnect({networkDisconnect: true});
|
|
5094
|
-
// @ts-ignore
|
|
5095
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5096
|
-
name: 'client.ice.end',
|
|
5097
|
-
payload: {
|
|
5098
|
-
canProceed: false,
|
|
5099
|
-
icePhase: 'IN_MEETING',
|
|
5100
|
-
errors: [
|
|
5101
|
-
// @ts-ignore
|
|
5102
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5103
|
-
CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE
|
|
5104
|
-
),
|
|
5105
|
-
],
|
|
5106
|
-
},
|
|
5107
|
-
options: {
|
|
5108
|
-
meetingId: this.id,
|
|
5109
|
-
},
|
|
5110
|
-
});
|
|
5111
|
-
|
|
5112
|
-
this.uploadLogs({
|
|
5113
|
-
file: 'peer-connection-manager/index',
|
|
5114
|
-
function: 'connectionFailed',
|
|
5115
|
-
});
|
|
5116
|
-
|
|
5117
5726
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_FAILURE, {
|
|
5118
5727
|
correlation_id: this.correlationId,
|
|
5119
5728
|
locus_id: this.locusId,
|
|
5729
|
+
networkStatus: this.networkStatus,
|
|
5730
|
+
hasMediaConnectionConnectedAtLeastOnce: this.hasMediaConnectionConnectedAtLeastOnce,
|
|
5120
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
|
+
}
|
|
5121
5745
|
};
|
|
5122
5746
|
|
|
5123
5747
|
LoggerProxy.logger.info(
|
|
5124
5748
|
`Meeting:index#setupMediaConnectionListeners --> correlationId=${this.correlationId} connection state changed to ${event.state}`
|
|
5125
5749
|
);
|
|
5750
|
+
|
|
5751
|
+
// @ts-ignore
|
|
5752
|
+
const cdl = this.webex.internal.newMetrics.callDiagnosticLatencies;
|
|
5753
|
+
|
|
5126
5754
|
switch (event.state) {
|
|
5127
5755
|
case ConnectionState.Connecting:
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
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
|
+
}
|
|
5135
5766
|
break;
|
|
5136
5767
|
case ConnectionState.Connected:
|
|
5137
|
-
|
|
5138
|
-
|
|
5139
|
-
|
|
5140
|
-
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
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
|
+
}
|
|
5144
5782
|
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.CONNECTION_SUCCESS, {
|
|
5145
5783
|
correlation_id: this.correlationId,
|
|
5146
5784
|
locus_id: this.locusId,
|
|
5785
|
+
latency: cdl.getICESetupTime(),
|
|
5147
5786
|
});
|
|
5148
5787
|
this.setNetworkStatus(NETWORK_STATUS.CONNECTED);
|
|
5149
5788
|
this.reconnectionManager.iceReconnected();
|
|
5150
5789
|
this.statsAnalyzer.startAnalyzer(this.mediaProperties.webrtcMediaConnection);
|
|
5790
|
+
this.hasMediaConnectionConnectedAtLeastOnce = true;
|
|
5151
5791
|
break;
|
|
5152
5792
|
case ConnectionState.Disconnected:
|
|
5153
5793
|
this.setNetworkStatus(NETWORK_STATUS.DISCONNECTED);
|
|
@@ -5268,7 +5908,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5268
5908
|
// @ts-ignore
|
|
5269
5909
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5270
5910
|
name: 'client.media.tx.start',
|
|
5271
|
-
payload: {
|
|
5911
|
+
payload: {
|
|
5912
|
+
mediaType: data.type,
|
|
5913
|
+
shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
|
|
5914
|
+
},
|
|
5272
5915
|
options: {
|
|
5273
5916
|
meetingId: this.id,
|
|
5274
5917
|
},
|
|
@@ -5278,7 +5921,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5278
5921
|
// @ts-ignore
|
|
5279
5922
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5280
5923
|
name: 'client.media.tx.stop',
|
|
5281
|
-
payload: {
|
|
5924
|
+
payload: {
|
|
5925
|
+
mediaType: data.type,
|
|
5926
|
+
shareInstanceId: data.type === 'share' ? this.localShareInstanceId : undefined,
|
|
5927
|
+
},
|
|
5282
5928
|
options: {
|
|
5283
5929
|
meetingId: this.id,
|
|
5284
5930
|
},
|
|
@@ -5297,7 +5943,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5297
5943
|
// @ts-ignore
|
|
5298
5944
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5299
5945
|
name: 'client.media.rx.start',
|
|
5300
|
-
payload: {
|
|
5946
|
+
payload: {
|
|
5947
|
+
mediaType: data.type,
|
|
5948
|
+
shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
|
|
5949
|
+
},
|
|
5301
5950
|
options: {
|
|
5302
5951
|
meetingId: this.id,
|
|
5303
5952
|
},
|
|
@@ -5307,7 +5956,10 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5307
5956
|
// @ts-ignore
|
|
5308
5957
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
5309
5958
|
name: 'client.media.rx.stop',
|
|
5310
|
-
payload: {
|
|
5959
|
+
payload: {
|
|
5960
|
+
mediaType: data.type,
|
|
5961
|
+
shareInstanceId: data.type === 'share' ? this.remoteShareInstanceId : undefined,
|
|
5962
|
+
},
|
|
5311
5963
|
options: {
|
|
5312
5964
|
meetingId: this.id,
|
|
5313
5965
|
},
|
|
@@ -5360,8 +6012,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5360
6012
|
this.mediaProperties.mediaDirection.receiveShare,
|
|
5361
6013
|
];
|
|
5362
6014
|
|
|
5363
|
-
this.sendSlotManager.createSlot(mc, MediaType.VideoMain,
|
|
5364
|
-
this.sendSlotManager.createSlot(mc, MediaType.AudioMain,
|
|
6015
|
+
this.sendSlotManager.createSlot(mc, MediaType.VideoMain, videoEnabled);
|
|
6016
|
+
this.sendSlotManager.createSlot(mc, MediaType.AudioMain, audioEnabled);
|
|
5365
6017
|
this.sendSlotManager.createSlot(mc, MediaType.VideoSlides, shareEnabled);
|
|
5366
6018
|
this.sendSlotManager.createSlot(mc, MediaType.AudioSlides, shareEnabled);
|
|
5367
6019
|
}
|
|
@@ -5384,50 +6036,464 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5384
6036
|
}
|
|
5385
6037
|
|
|
5386
6038
|
/**
|
|
5387
|
-
* Listens for an event emitted by eventEmitter and emits it from the meeting object
|
|
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
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
|
|
6447
|
+
/**
|
|
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
|
|
5388
6450
|
*
|
|
5389
6451
|
* @private
|
|
5390
|
-
* @
|
|
5391
|
-
* @param {*} eventTypeToForward which event type to listen on and to forward
|
|
5392
|
-
* @param {string} meetingEventType event type to be used in the event emitted from the meeting object
|
|
5393
|
-
* @returns {void}
|
|
6452
|
+
* @returns {Promise<void>}
|
|
5394
6453
|
*/
|
|
5395
|
-
|
|
5396
|
-
|
|
5397
|
-
|
|
5398
|
-
|
|
5399
|
-
|
|
5400
|
-
|
|
5401
|
-
|
|
5402
|
-
|
|
5403
|
-
|
|
5404
|
-
|
|
5405
|
-
|
|
5406
|
-
|
|
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
|
+
}
|
|
5407
6475
|
}
|
|
5408
6476
|
|
|
5409
6477
|
/**
|
|
5410
6478
|
* Creates a media connection to the server. Media connection is required for sending or receiving any audio/video.
|
|
5411
6479
|
*
|
|
5412
6480
|
* @param {AddMediaOptions} options
|
|
5413
|
-
* @returns {Promise}
|
|
6481
|
+
* @returns {Promise<void>}
|
|
5414
6482
|
* @public
|
|
5415
6483
|
* @memberof Meeting
|
|
5416
6484
|
*/
|
|
5417
|
-
addMedia(options: AddMediaOptions = {}) {
|
|
6485
|
+
async addMedia(options: AddMediaOptions = {}): Promise<void> {
|
|
6486
|
+
this.retriedWithTurnServer = false;
|
|
6487
|
+
this.hasMediaConnectionConnectedAtLeastOnce = false;
|
|
5418
6488
|
const LOG_HEADER = 'Meeting:index#addMedia -->';
|
|
5419
|
-
|
|
5420
|
-
let turnDiscoverySkippedReason;
|
|
5421
|
-
let turnServerUsed = false;
|
|
5422
|
-
|
|
5423
6489
|
LoggerProxy.logger.info(`${LOG_HEADER} called with: ${JSON.stringify(options)}`);
|
|
5424
6490
|
|
|
5425
|
-
if (this.meetingState !== FULL_STATE.ACTIVE) {
|
|
5426
|
-
|
|
6491
|
+
if (options.allowMediaInLobby !== true && this.meetingState !== FULL_STATE.ACTIVE) {
|
|
6492
|
+
throw new MeetingNotActiveError();
|
|
5427
6493
|
}
|
|
5428
6494
|
|
|
5429
6495
|
if (MeetingUtil.isUserInLeftState(this.locusInfo)) {
|
|
5430
|
-
|
|
6496
|
+
throw new UserNotJoinedError();
|
|
5431
6497
|
}
|
|
5432
6498
|
|
|
5433
6499
|
const {
|
|
@@ -5446,7 +6512,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5446
6512
|
// If the user is unjoined or guest waiting in lobby dont allow the user to addMedia
|
|
5447
6513
|
// @ts-ignore - isUserUnadmitted coming from SelfUtil
|
|
5448
6514
|
if (this.isUserUnadmitted && !this.wirelessShare && !allowMediaInLobby) {
|
|
5449
|
-
|
|
6515
|
+
throw new UserInLobbyError();
|
|
5450
6516
|
}
|
|
5451
6517
|
|
|
5452
6518
|
// @ts-ignore
|
|
@@ -5506,235 +6572,100 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5506
6572
|
|
|
5507
6573
|
this.audio = createMuteState(AUDIO, this, audioEnabled);
|
|
5508
6574
|
this.video = createMuteState(VIDEO, this, videoEnabled);
|
|
5509
|
-
const promises = [];
|
|
5510
|
-
|
|
5511
|
-
// setup all the references to local streams in this.mediaProperties before creating media connection
|
|
5512
|
-
// and before TURN discovery, so that the correct mute state is sent with TURN discovery roap messages
|
|
5513
|
-
if (localStreams?.microphone) {
|
|
5514
|
-
promises.push(this.setLocalAudioStream(localStreams.microphone));
|
|
5515
|
-
}
|
|
5516
|
-
if (localStreams?.camera) {
|
|
5517
|
-
promises.push(this.setLocalVideoStream(localStreams.camera));
|
|
5518
|
-
}
|
|
5519
|
-
if (localStreams?.screenShare?.video) {
|
|
5520
|
-
promises.push(this.setLocalShareVideoStream(localStreams.screenShare.video));
|
|
5521
|
-
}
|
|
5522
|
-
if (localStreams?.screenShare?.audio) {
|
|
5523
|
-
promises.push(this.setLocalShareAudioStream(localStreams.screenShare.audio));
|
|
5524
|
-
}
|
|
5525
|
-
|
|
5526
|
-
return Promise.all(promises)
|
|
5527
|
-
.then(() => this.roap.doTurnDiscovery(this, false))
|
|
5528
|
-
.then(async (turnDiscoveryObject) => {
|
|
5529
|
-
({turnDiscoverySkippedReason} = turnDiscoveryObject);
|
|
5530
|
-
turnServerUsed = !turnDiscoverySkippedReason;
|
|
5531
|
-
|
|
5532
|
-
const {turnServerInfo} = turnDiscoveryObject;
|
|
5533
|
-
|
|
5534
|
-
const mc = await this.createMediaConnection(turnServerInfo, bundlePolicy);
|
|
5535
6575
|
|
|
5536
|
-
|
|
5537
|
-
|
|
5538
|
-
this.receiveSlotManager,
|
|
5539
|
-
this.mediaRequestManagers,
|
|
5540
|
-
remoteMediaManagerConfig
|
|
5541
|
-
);
|
|
6576
|
+
try {
|
|
6577
|
+
await this.setUpLocalStreamReferences(localStreams);
|
|
5542
6578
|
|
|
5543
|
-
|
|
5544
|
-
this.remoteMediaManager,
|
|
5545
|
-
RemoteMediaManagerEvent.AudioCreated,
|
|
5546
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_AUDIO_CREATED
|
|
5547
|
-
);
|
|
5548
|
-
this.forwardEvent(
|
|
5549
|
-
this.remoteMediaManager,
|
|
5550
|
-
RemoteMediaManagerEvent.ScreenShareAudioCreated,
|
|
5551
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_SCREEN_SHARE_AUDIO_CREATED
|
|
5552
|
-
);
|
|
5553
|
-
this.forwardEvent(
|
|
5554
|
-
this.remoteMediaManager,
|
|
5555
|
-
RemoteMediaManagerEvent.VideoLayoutChanged,
|
|
5556
|
-
EVENT_TRIGGERS.REMOTE_MEDIA_VIDEO_LAYOUT_CHANGED
|
|
5557
|
-
);
|
|
6579
|
+
this.setMercuryListener();
|
|
5558
6580
|
|
|
5559
|
-
|
|
5560
|
-
}
|
|
6581
|
+
this.createStatsAnalyzer();
|
|
5561
6582
|
|
|
5562
|
-
|
|
5563
|
-
})
|
|
5564
|
-
.then(() => {
|
|
5565
|
-
this.setMercuryListener();
|
|
5566
|
-
})
|
|
5567
|
-
.then(
|
|
5568
|
-
() =>
|
|
5569
|
-
getDevices()
|
|
5570
|
-
.then((devices) => {
|
|
5571
|
-
MeetingUtil.handleDeviceLogging(devices);
|
|
5572
|
-
})
|
|
5573
|
-
.catch(() => {}) // getDevices may fail if we don't have browser permissions, that's ok, we still can have a media connection
|
|
5574
|
-
)
|
|
5575
|
-
.then(() => {
|
|
5576
|
-
this.handleMediaLogging(this.mediaProperties);
|
|
5577
|
-
LoggerProxy.logger.info(`${LOG_HEADER} media connection created`);
|
|
6583
|
+
await this.establishMediaConnection(remoteMediaManagerConfig, bundlePolicy, false);
|
|
5578
6584
|
|
|
5579
|
-
|
|
5580
|
-
if (this.config.stats.enableStatsAnalyzer) {
|
|
5581
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5582
|
-
this.networkQualityMonitor = new NetworkQualityMonitor(this.config.stats);
|
|
5583
|
-
this.statsAnalyzer = new StatsAnalyzer(
|
|
5584
|
-
// @ts-ignore - config coming from registerPlugin
|
|
5585
|
-
this.config.stats,
|
|
5586
|
-
(ssrc: number) => this.receiveSlotManager.findReceiveSlotBySsrc(ssrc),
|
|
5587
|
-
this.networkQualityMonitor
|
|
5588
|
-
);
|
|
5589
|
-
this.setupStatsAnalyzerEventHandlers();
|
|
5590
|
-
this.networkQualityMonitor.on(
|
|
5591
|
-
EVENT_TRIGGERS.NETWORK_QUALITY,
|
|
5592
|
-
this.sendNetworkQualityEvent.bind(this)
|
|
5593
|
-
);
|
|
5594
|
-
}
|
|
5595
|
-
})
|
|
5596
|
-
.catch((error) => {
|
|
5597
|
-
LoggerProxy.logger.error(
|
|
5598
|
-
`${LOG_HEADER} Error adding media , setting up peerconnection, `,
|
|
5599
|
-
error
|
|
5600
|
-
);
|
|
6585
|
+
await Meeting.handleDeviceLogging();
|
|
5601
6586
|
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
() =>
|
|
5606
|
-
new Promise<void>((resolve, reject) => {
|
|
5607
|
-
let timerCount = 0;
|
|
5608
|
-
|
|
5609
|
-
// eslint-disable-next-line func-names
|
|
5610
|
-
// eslint-disable-next-line prefer-arrow-callback
|
|
5611
|
-
if (this.type === _CALL_ || this.meetingState === FULL_STATE.ACTIVE) {
|
|
5612
|
-
resolve();
|
|
5613
|
-
}
|
|
5614
|
-
const joiningTimer = setInterval(() => {
|
|
5615
|
-
timerCount += 1;
|
|
5616
|
-
if (this.meetingState === FULL_STATE.ACTIVE) {
|
|
5617
|
-
clearInterval(joiningTimer);
|
|
5618
|
-
resolve();
|
|
5619
|
-
}
|
|
6587
|
+
if (this.mediaProperties.hasLocalShareStream()) {
|
|
6588
|
+
await this.enqueueScreenShareFloorRequest();
|
|
6589
|
+
}
|
|
5620
6590
|
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
}
|
|
5625
|
-
}, 1000);
|
|
5626
|
-
})
|
|
5627
|
-
)
|
|
5628
|
-
.then(() =>
|
|
5629
|
-
this.mediaProperties.waitForMediaConnectionConnected().catch(() => {
|
|
5630
|
-
// @ts-ignore
|
|
5631
|
-
this.webex.internal.newMetrics.submitClientEvent({
|
|
5632
|
-
name: 'client.ice.end',
|
|
5633
|
-
payload: {
|
|
5634
|
-
canProceed: false,
|
|
5635
|
-
icePhase: 'JOIN_MEETING_FINAL',
|
|
5636
|
-
errors: [
|
|
5637
|
-
// @ts-ignore
|
|
5638
|
-
this.webex.internal.newMetrics.callDiagnosticMetrics.getErrorPayloadForClientErrorCode(
|
|
5639
|
-
CALL_DIAGNOSTIC_CONFIG.ICE_FAILURE_CLIENT_CODE
|
|
5640
|
-
),
|
|
5641
|
-
],
|
|
5642
|
-
},
|
|
5643
|
-
options: {
|
|
5644
|
-
meetingId: this.id,
|
|
5645
|
-
},
|
|
5646
|
-
});
|
|
5647
|
-
throw new Error(
|
|
5648
|
-
`Timed out waiting for media connection to be connected, correlationId=${this.correlationId}`
|
|
5649
|
-
);
|
|
5650
|
-
})
|
|
5651
|
-
)
|
|
5652
|
-
.then(() => {
|
|
5653
|
-
if (this.mediaProperties.hasLocalShareStream()) {
|
|
5654
|
-
return this.enqueueScreenShareFloorRequest();
|
|
5655
|
-
}
|
|
6591
|
+
const connectionType = await this.mediaProperties.getCurrentConnectionType();
|
|
6592
|
+
// @ts-ignore
|
|
6593
|
+
const reachabilityStats = await this.webex.meetings.reachability.getReachabilityMetrics();
|
|
5656
6594
|
|
|
5657
|
-
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
|
|
5669
|
-
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
}
|
|
5674
|
-
|
|
5675
|
-
.catch((error) => {
|
|
5676
|
-
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.ADD_MEDIA_FAILURE, {
|
|
5677
|
-
correlation_id: this.correlationId,
|
|
5678
|
-
locus_id: this.locusUrl.split('/').pop(),
|
|
5679
|
-
reason: error.message,
|
|
5680
|
-
stack: error.stack,
|
|
5681
|
-
code: error.code,
|
|
5682
|
-
turnDiscoverySkippedReason,
|
|
5683
|
-
turnServerUsed,
|
|
5684
|
-
isMultistream: this.isMultistream,
|
|
5685
|
-
signalingState:
|
|
5686
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5687
|
-
?.signalingState ||
|
|
5688
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.signalingState ||
|
|
5689
|
-
'unknown',
|
|
5690
|
-
connectionState:
|
|
5691
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5692
|
-
?.connectionState ||
|
|
5693
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.connectionState ||
|
|
5694
|
-
'unknown',
|
|
5695
|
-
iceConnectionState:
|
|
5696
|
-
this.mediaProperties.webrtcMediaConnection?.multistreamConnection?.pc?.pc
|
|
5697
|
-
?.iceConnectionState ||
|
|
5698
|
-
this.mediaProperties.webrtcMediaConnection?.mediaConnection?.pc?.iceConnectionState ||
|
|
5699
|
-
'unknown',
|
|
5700
|
-
});
|
|
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
|
+
);
|
|
5701
6613
|
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
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);
|
|
5706
6618
|
|
|
5707
|
-
|
|
5708
|
-
|
|
6619
|
+
// @ts-ignore
|
|
6620
|
+
const reachabilityMetrics = await this.webex.meetings.reachability.getReachabilityMetrics();
|
|
5709
6621
|
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
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
|
+
});
|
|
5714
6649
|
|
|
5715
|
-
|
|
5716
|
-
`${LOG_HEADER} Error adding media failed to initiate PC and send request, `,
|
|
5717
|
-
error
|
|
5718
|
-
);
|
|
6650
|
+
await this.cleanUpOnAddMediaFailure();
|
|
5719
6651
|
|
|
5720
|
-
|
|
5721
|
-
|
|
5722
|
-
|
|
5723
|
-
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
|
|
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
|
+
);
|
|
5730
6662
|
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
6663
|
+
if (error instanceof Errors.SdpError) {
|
|
6664
|
+
this.leave({reason: MEETING_REMOVED_REASON.MEETING_CONNECTION_FAILED});
|
|
6665
|
+
}
|
|
5734
6666
|
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
});
|
|
6667
|
+
throw error;
|
|
6668
|
+
}
|
|
5738
6669
|
}
|
|
5739
6670
|
|
|
5740
6671
|
/**
|
|
@@ -5748,6 +6679,24 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5748
6679
|
return !this.isRoapInProgress;
|
|
5749
6680
|
}
|
|
5750
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
|
+
|
|
5751
6700
|
/**
|
|
5752
6701
|
* Enqueues a media update operation.
|
|
5753
6702
|
* @param {String} mediaUpdateType one of MEDIA_UPDATE_TYPE values
|
|
@@ -5872,12 +6821,15 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
5872
6821
|
return this.enqueueMediaUpdate(MEDIA_UPDATE_TYPE.UPDATE_MEDIA, options);
|
|
5873
6822
|
}
|
|
5874
6823
|
|
|
5875
|
-
if (
|
|
5876
|
-
|
|
5877
|
-
|
|
5878
|
-
|
|
6824
|
+
if (this.isMultistream) {
|
|
6825
|
+
if (shareAudioEnabled !== undefined || shareVideoEnabled !== undefined) {
|
|
6826
|
+
throw new Error(
|
|
6827
|
+
'toggling shareAudioEnabled or shareVideoEnabled in a multistream meeting is not supported, to control receiving screen share call meeting.remoteMediaManager.setLayout() with appropriate layout'
|
|
6828
|
+
);
|
|
6829
|
+
}
|
|
6830
|
+
} else if (shareAudioEnabled !== undefined) {
|
|
5879
6831
|
throw new Error(
|
|
5880
|
-
'toggling shareAudioEnabled
|
|
6832
|
+
'toggling shareAudioEnabled in a transcoded meeting is not supported as of now'
|
|
5881
6833
|
);
|
|
5882
6834
|
}
|
|
5883
6835
|
|
|
@@ -6210,17 +7162,13 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6210
7162
|
.catch((error) => {
|
|
6211
7163
|
LoggerProxy.logger.error('Meeting:index#stopWhiteboardShare --> Error ', error);
|
|
6212
7164
|
|
|
6213
|
-
Metrics.sendBehavioralMetric(
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
stack: error.stack,
|
|
6221
|
-
board: {channelUrl},
|
|
6222
|
-
}
|
|
6223
|
-
);
|
|
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
|
+
});
|
|
6224
7172
|
|
|
6225
7173
|
return Promise.reject(error);
|
|
6226
7174
|
})
|
|
@@ -6257,11 +7205,14 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6257
7205
|
if (content && this.shareStatus !== SHARE_STATUS.LOCAL_SHARE_ACTIVE) {
|
|
6258
7206
|
// @ts-ignore
|
|
6259
7207
|
this.webex.internal.newMetrics.submitClientEvent({
|
|
6260
|
-
name: 'client.share.
|
|
7208
|
+
name: 'client.share.floor-grant.request',
|
|
6261
7209
|
payload: {
|
|
6262
7210
|
mediaType: 'share',
|
|
7211
|
+
shareInstanceId: this.localShareInstanceId,
|
|
7212
|
+
},
|
|
7213
|
+
options: {
|
|
7214
|
+
meetingId: this.id,
|
|
6263
7215
|
},
|
|
6264
|
-
options: {meetingId: this.id},
|
|
6265
7216
|
});
|
|
6266
7217
|
|
|
6267
7218
|
return this.meetingRequest
|
|
@@ -6271,10 +7222,16 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6271
7222
|
deviceUrl: this.deviceUrl,
|
|
6272
7223
|
uri: content.url,
|
|
6273
7224
|
resourceUrl: this.resourceUrl,
|
|
7225
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6274
7226
|
})
|
|
6275
7227
|
.then(() => {
|
|
6276
7228
|
this.screenShareFloorState = ScreenShareFloorStatus.GRANTED;
|
|
6277
7229
|
|
|
7230
|
+
Metrics.sendBehavioralMetric(BEHAVIORAL_METRICS.MEETING_SHARE_SUCCESS, {
|
|
7231
|
+
correlation_id: this.correlationId,
|
|
7232
|
+
locus_id: this.locusUrl.split('/').pop(),
|
|
7233
|
+
});
|
|
7234
|
+
|
|
6278
7235
|
return Promise.resolve();
|
|
6279
7236
|
})
|
|
6280
7237
|
.catch((error) => {
|
|
@@ -6287,6 +7244,19 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6287
7244
|
stack: error.stack,
|
|
6288
7245
|
});
|
|
6289
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
|
+
|
|
6290
7260
|
this.screenShareFloorState = ScreenShareFloorStatus.RELEASED;
|
|
6291
7261
|
|
|
6292
7262
|
return Promise.reject(error);
|
|
@@ -6338,6 +7308,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6338
7308
|
name: 'client.share.stopped',
|
|
6339
7309
|
payload: {
|
|
6340
7310
|
mediaType: 'share',
|
|
7311
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6341
7312
|
},
|
|
6342
7313
|
options: {meetingId: this.id},
|
|
6343
7314
|
});
|
|
@@ -6354,6 +7325,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6354
7325
|
deviceUrl: this.deviceUrl,
|
|
6355
7326
|
uri: content.url,
|
|
6356
7327
|
resourceUrl: this.resourceUrl,
|
|
7328
|
+
shareInstanceId: this.localShareInstanceId,
|
|
6357
7329
|
})
|
|
6358
7330
|
.catch((error) => {
|
|
6359
7331
|
LoggerProxy.logger.error('Meeting:index#releaseScreenShareFloor --> Error ', error);
|
|
@@ -6557,8 +7529,8 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6557
7529
|
|
|
6558
7530
|
if (layoutType) {
|
|
6559
7531
|
if (!LAYOUT_TYPES.includes(layoutType)) {
|
|
6560
|
-
this.rejectWithErrorLog(
|
|
6561
|
-
'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType
|
|
7532
|
+
return this.rejectWithErrorLog(
|
|
7533
|
+
'Meeting:index#changeVideoLayout --> cannot change video layout, invalid layoutType received.'
|
|
6562
7534
|
);
|
|
6563
7535
|
}
|
|
6564
7536
|
|
|
@@ -6696,6 +7668,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6696
7668
|
}
|
|
6697
7669
|
};
|
|
6698
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
|
+
|
|
6699
7688
|
/**
|
|
6700
7689
|
* Functionality for when a share video is ended.
|
|
6701
7690
|
* @private
|
|
@@ -6796,6 +7785,9 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6796
7785
|
if (roles.includes(SELF_ROLES.COHOST)) {
|
|
6797
7786
|
return 'cohost';
|
|
6798
7787
|
}
|
|
7788
|
+
if (roles.includes(SELF_ROLES.PRESENTER)) {
|
|
7789
|
+
return 'presenter';
|
|
7790
|
+
}
|
|
6799
7791
|
if (roles.includes(SELF_ROLES.ATTENDEE)) {
|
|
6800
7792
|
return 'attendee';
|
|
6801
7793
|
}
|
|
@@ -6886,8 +7878,7 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
6886
7878
|
this.queuedMediaUpdates = [];
|
|
6887
7879
|
|
|
6888
7880
|
if (this.transcription) {
|
|
6889
|
-
this.
|
|
6890
|
-
this.triggerStopReceivingTranscriptionEvent();
|
|
7881
|
+
this.stopTranscription();
|
|
6891
7882
|
this.transcription = undefined;
|
|
6892
7883
|
}
|
|
6893
7884
|
};
|
|
@@ -7062,10 +8053,12 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7062
8053
|
.update({
|
|
7063
8054
|
// TODO: RoapMediaConnection is not ready to use stream classes yet, so we pass the raw MediaStreamTrack for now
|
|
7064
8055
|
localTracks: {
|
|
7065
|
-
audio: this.mediaProperties.audioStream?.
|
|
7066
|
-
video: this.mediaProperties.videoStream?.
|
|
7067
|
-
screenShareVideo:
|
|
7068
|
-
|
|
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,
|
|
7069
8062
|
},
|
|
7070
8063
|
direction: {
|
|
7071
8064
|
audio: Media.getDirection(
|
|
@@ -7199,6 +8192,23 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7199
8192
|
}
|
|
7200
8193
|
|
|
7201
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
|
+
});
|
|
7202
8212
|
// we're sending the http request to Locus to request the screen share floor
|
|
7203
8213
|
// only after the SDP update, because that's how it's always been done for transcoded meetings
|
|
7204
8214
|
// and also if sharing from the start, we need confluence to have been created
|
|
@@ -7247,9 +8257,64 @@ export default class Meeting extends StatelessWebexPlugin {
|
|
|
7247
8257
|
if (!this.mediaProperties.hasLocalShareStream()) {
|
|
7248
8258
|
try {
|
|
7249
8259
|
this.releaseScreenShareFloor(); // we ignore the returned promise here on purpose
|
|
8260
|
+
|
|
8261
|
+
this.statsAnalyzer.updateMediaStatus({
|
|
8262
|
+
expected: {
|
|
8263
|
+
sendShare: false,
|
|
8264
|
+
},
|
|
8265
|
+
});
|
|
7250
8266
|
} catch (e) {
|
|
7251
8267
|
// nothing to do here, error is logged already inside releaseScreenShareFloor()
|
|
7252
8268
|
}
|
|
7253
8269
|
}
|
|
7254
8270
|
}
|
|
8271
|
+
|
|
8272
|
+
/**
|
|
8273
|
+
* Gets permission token expiry information including timeLeft, expiryTime, currentTime
|
|
8274
|
+
* (from the time the function has been fired)
|
|
8275
|
+
*
|
|
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
|
|
8280
|
+
*/
|
|
8281
|
+
public getPermissionTokenExpiryInfo() {
|
|
8282
|
+
if (!this.permissionTokenPayload) {
|
|
8283
|
+
return undefined;
|
|
8284
|
+
}
|
|
8285
|
+
|
|
8286
|
+
const permissionTokenExpiryFromServer = Number(this.permissionTokenPayload.exp);
|
|
8287
|
+
const permissionTokenIssuedTimeFromServer = Number(this.permissionTokenPayload.iat);
|
|
8288
|
+
|
|
8289
|
+
const shiftInTime = this.permissionTokenReceivedLocalTime - permissionTokenIssuedTimeFromServer;
|
|
8290
|
+
|
|
8291
|
+
// using new Date instead of Date.now() to allow for accurate unit testing
|
|
8292
|
+
// https://github.com/sinonjs/fake-timers/issues/321
|
|
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
|
+
}
|
|
8302
|
+
|
|
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();
|
|
8319
|
+
}
|
|
7255
8320
|
}
|