@abrar71/lib-jitsi-meet 0.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/LICENSE +202 -0
- package/README.md +26 -0
- package/dist/esm/JitsiConference.js +3692 -0
- package/dist/esm/JitsiConference.js.map +1 -0
- package/dist/esm/JitsiConferenceErrors.js +126 -0
- package/dist/esm/JitsiConferenceErrors.js.map +1 -0
- package/dist/esm/JitsiConferenceEventManager.js +424 -0
- package/dist/esm/JitsiConferenceEventManager.js.map +1 -0
- package/dist/esm/JitsiConferenceEvents.js +431 -0
- package/dist/esm/JitsiConferenceEvents.js.map +1 -0
- package/dist/esm/JitsiConnection.js +187 -0
- package/dist/esm/JitsiConnection.js.map +1 -0
- package/dist/esm/JitsiConnectionErrors.js +52 -0
- package/dist/esm/JitsiConnectionErrors.js.map +1 -0
- package/dist/esm/JitsiConnectionEvents.js +57 -0
- package/dist/esm/JitsiConnectionEvents.js.map +1 -0
- package/dist/esm/JitsiMediaDevices.js +221 -0
- package/dist/esm/JitsiMediaDevices.js.map +1 -0
- package/dist/esm/JitsiMediaDevicesEvents.js +29 -0
- package/dist/esm/JitsiMediaDevicesEvents.js.map +1 -0
- package/dist/esm/JitsiMeetJS.js +499 -0
- package/dist/esm/JitsiMeetJS.js.map +1 -0
- package/dist/esm/JitsiParticipant.js +323 -0
- package/dist/esm/JitsiParticipant.js.map +1 -0
- package/dist/esm/JitsiTrackError.js +122 -0
- package/dist/esm/JitsiTrackError.js.map +1 -0
- package/dist/esm/JitsiTrackErrors.js +91 -0
- package/dist/esm/JitsiTrackErrors.js.map +1 -0
- package/dist/esm/JitsiTrackEvents.js +60 -0
- package/dist/esm/JitsiTrackEvents.js.map +1 -0
- package/dist/esm/JitsiTranscriptionStatus.js +15 -0
- package/dist/esm/JitsiTranscriptionStatus.js.map +1 -0
- package/dist/esm/modules/RTC/BridgeChannel.js +398 -0
- package/dist/esm/modules/RTC/BridgeChannel.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiLocalTrack.js +896 -0
- package/dist/esm/modules/RTC/JitsiLocalTrack.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiRemoteTrack.js +427 -0
- package/dist/esm/modules/RTC/JitsiRemoteTrack.js.map +1 -0
- package/dist/esm/modules/RTC/JitsiTrack.js +453 -0
- package/dist/esm/modules/RTC/JitsiTrack.js.map +1 -0
- package/dist/esm/modules/RTC/MockClasses.js +388 -0
- package/dist/esm/modules/RTC/MockClasses.js.map +1 -0
- package/dist/esm/modules/RTC/RTC.js +658 -0
- package/dist/esm/modules/RTC/RTC.js.map +1 -0
- package/dist/esm/modules/RTC/RTCUtils.js +762 -0
- package/dist/esm/modules/RTC/RTCUtils.js.map +1 -0
- package/dist/esm/modules/RTC/ScreenObtainer.js +380 -0
- package/dist/esm/modules/RTC/ScreenObtainer.js.map +1 -0
- package/dist/esm/modules/RTC/TPCUtils.js +803 -0
- package/dist/esm/modules/RTC/TPCUtils.js.map +1 -0
- package/dist/esm/modules/RTC/TraceablePeerConnection.js +2223 -0
- package/dist/esm/modules/RTC/TraceablePeerConnection.js.map +1 -0
- package/dist/esm/modules/RTCStats/DefaulLogStorage.js +35 -0
- package/dist/esm/modules/RTCStats/DefaulLogStorage.js.map +1 -0
- package/dist/esm/modules/RTCStats/RTCStats.js +219 -0
- package/dist/esm/modules/RTCStats/RTCStats.js.map +1 -0
- package/dist/esm/modules/RTCStats/RTCStatsEvents.js +92 -0
- package/dist/esm/modules/RTCStats/RTCStatsEvents.js.map +1 -0
- package/dist/esm/modules/RTCStats/interfaces.js +2 -0
- package/dist/esm/modules/RTCStats/interfaces.js.map +1 -0
- package/dist/esm/modules/browser/BrowserCapabilities.js +345 -0
- package/dist/esm/modules/browser/BrowserCapabilities.js.map +1 -0
- package/dist/esm/modules/browser/index.js +3 -0
- package/dist/esm/modules/browser/index.js.map +1 -0
- package/dist/esm/modules/connectivity/ConnectionQuality.js +389 -0
- package/dist/esm/modules/connectivity/ConnectionQuality.js.map +1 -0
- package/dist/esm/modules/connectivity/IceFailedHandling.js +84 -0
- package/dist/esm/modules/connectivity/IceFailedHandling.js.map +1 -0
- package/dist/esm/modules/connectivity/NetworkInfo.js +49 -0
- package/dist/esm/modules/connectivity/NetworkInfo.js.map +1 -0
- package/dist/esm/modules/connectivity/TrackStreamingStatus.js +453 -0
- package/dist/esm/modules/connectivity/TrackStreamingStatus.js.map +1 -0
- package/dist/esm/modules/detection/ActiveDeviceDetector.js +79 -0
- package/dist/esm/modules/detection/ActiveDeviceDetector.js.map +1 -0
- package/dist/esm/modules/detection/DetectionEvents.js +58 -0
- package/dist/esm/modules/detection/DetectionEvents.js.map +1 -0
- package/dist/esm/modules/detection/NoAudioSignalDetection.js +127 -0
- package/dist/esm/modules/detection/NoAudioSignalDetection.js.map +1 -0
- package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js +47 -0
- package/dist/esm/modules/detection/P2PDominantSpeakerDetection.js.map +1 -0
- package/dist/esm/modules/detection/TrackVADEmitter.js +190 -0
- package/dist/esm/modules/detection/TrackVADEmitter.js.map +1 -0
- package/dist/esm/modules/detection/VADAudioAnalyser.js +199 -0
- package/dist/esm/modules/detection/VADAudioAnalyser.js.map +1 -0
- package/dist/esm/modules/detection/VADNoiseDetection.js +168 -0
- package/dist/esm/modules/detection/VADNoiseDetection.js.map +1 -0
- package/dist/esm/modules/detection/VADReportingService.js +203 -0
- package/dist/esm/modules/detection/VADReportingService.js.map +1 -0
- package/dist/esm/modules/detection/VADTalkMutedDetection.js +131 -0
- package/dist/esm/modules/detection/VADTalkMutedDetection.js.map +1 -0
- package/dist/esm/modules/e2ee/Context.js +274 -0
- package/dist/esm/modules/e2ee/Context.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEContext.js +158 -0
- package/dist/esm/modules/e2ee/E2EEContext.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEErrors.js +10 -0
- package/dist/esm/modules/e2ee/E2EEErrors.js.map +1 -0
- package/dist/esm/modules/e2ee/E2EEncryption.js +87 -0
- package/dist/esm/modules/e2ee/E2EEncryption.js.map +1 -0
- package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js +24 -0
- package/dist/esm/modules/e2ee/ExternallyManagedKeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/KeyHandler.js +137 -0
- package/dist/esm/modules/e2ee/KeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/ManagedKeyHandler.js +182 -0
- package/dist/esm/modules/e2ee/ManagedKeyHandler.js.map +1 -0
- package/dist/esm/modules/e2ee/OlmAdapter.js +860 -0
- package/dist/esm/modules/e2ee/OlmAdapter.js.map +1 -0
- package/dist/esm/modules/e2ee/SAS.js +128 -0
- package/dist/esm/modules/e2ee/SAS.js.map +1 -0
- package/dist/esm/modules/e2ee/Worker.js +102 -0
- package/dist/esm/modules/e2ee/Worker.js.map +1 -0
- package/dist/esm/modules/e2ee/crypto-utils.js +53 -0
- package/dist/esm/modules/e2ee/crypto-utils.js.map +1 -0
- package/dist/esm/modules/e2ee/utils.js +15 -0
- package/dist/esm/modules/e2ee/utils.js.map +1 -0
- package/dist/esm/modules/e2eping/e2eping.js +314 -0
- package/dist/esm/modules/e2eping/e2eping.js.map +1 -0
- package/dist/esm/modules/flags/FeatureFlags.js +36 -0
- package/dist/esm/modules/flags/FeatureFlags.js.map +1 -0
- package/dist/esm/modules/litemode/LiteModeContext.js +50 -0
- package/dist/esm/modules/litemode/LiteModeContext.js.map +1 -0
- package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js +98 -0
- package/dist/esm/modules/proxyconnection/CustomSignalingLayer.js.map +1 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js +348 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionPC.js.map +1 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionService.js +279 -0
- package/dist/esm/modules/proxyconnection/ProxyConnectionService.js.map +1 -0
- package/dist/esm/modules/proxyconnection/constants.js +14 -0
- package/dist/esm/modules/proxyconnection/constants.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/CodecSelection.js +222 -0
- package/dist/esm/modules/qualitycontrol/CodecSelection.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/MockClasses.js +120 -0
- package/dist/esm/modules/qualitycontrol/MockClasses.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/QualityController.js +366 -0
- package/dist/esm/modules/qualitycontrol/QualityController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js +73 -0
- package/dist/esm/modules/qualitycontrol/ReceiveAudioController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js +216 -0
- package/dist/esm/modules/qualitycontrol/ReceiveVideoController.js.map +1 -0
- package/dist/esm/modules/qualitycontrol/SendVideoController.js +133 -0
- package/dist/esm/modules/qualitycontrol/SendVideoController.js.map +1 -0
- package/dist/esm/modules/recording/JibriSession.js +279 -0
- package/dist/esm/modules/recording/JibriSession.js.map +1 -0
- package/dist/esm/modules/recording/RecordingManager.js +257 -0
- package/dist/esm/modules/recording/RecordingManager.js.map +1 -0
- package/dist/esm/modules/recording/recordingConstants.js +21 -0
- package/dist/esm/modules/recording/recordingConstants.js.map +1 -0
- package/dist/esm/modules/recording/recordingXMLUtils.js +69 -0
- package/dist/esm/modules/recording/recordingXMLUtils.js.map +1 -0
- package/dist/esm/modules/red/red.js +108 -0
- package/dist/esm/modules/red/red.js.map +1 -0
- package/dist/esm/modules/sdp/LocalSdpMunger.js +143 -0
- package/dist/esm/modules/sdp/LocalSdpMunger.js.map +1 -0
- package/dist/esm/modules/sdp/RtxModifier.js +179 -0
- package/dist/esm/modules/sdp/RtxModifier.js.map +1 -0
- package/dist/esm/modules/sdp/SDP.js +848 -0
- package/dist/esm/modules/sdp/SDP.js.map +1 -0
- package/dist/esm/modules/sdp/SDPDiffer.js +96 -0
- package/dist/esm/modules/sdp/SDPDiffer.js.map +1 -0
- package/dist/esm/modules/sdp/SDPUtil.js +798 -0
- package/dist/esm/modules/sdp/SDPUtil.js.map +1 -0
- package/dist/esm/modules/sdp/SampleSdpStrings.js +589 -0
- package/dist/esm/modules/sdp/SampleSdpStrings.js.map +1 -0
- package/dist/esm/modules/sdp/SdpSimulcast.js +196 -0
- package/dist/esm/modules/sdp/SdpSimulcast.js.map +1 -0
- package/dist/esm/modules/sdp/SdpTransformUtil.js +337 -0
- package/dist/esm/modules/sdp/SdpTransformUtil.js.map +1 -0
- package/dist/esm/modules/sdp/constants.js +2 -0
- package/dist/esm/modules/sdp/constants.js.map +1 -0
- package/dist/esm/modules/settings/Settings.js +95 -0
- package/dist/esm/modules/settings/Settings.js.map +1 -0
- package/dist/esm/modules/statistics/AnalyticsAdapter.js +277 -0
- package/dist/esm/modules/statistics/AnalyticsAdapter.js.map +1 -0
- package/dist/esm/modules/statistics/AvgRTPStatsReporter.js +817 -0
- package/dist/esm/modules/statistics/AvgRTPStatsReporter.js.map +1 -0
- package/dist/esm/modules/statistics/LocalStatsCollector.js +149 -0
- package/dist/esm/modules/statistics/LocalStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/PreCallTest.js +15 -0
- package/dist/esm/modules/statistics/PreCallTest.js.map +1 -0
- package/dist/esm/modules/statistics/RTPStatsCollector.js +601 -0
- package/dist/esm/modules/statistics/RTPStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/SpeakerStats.js +163 -0
- package/dist/esm/modules/statistics/SpeakerStats.js.map +1 -0
- package/dist/esm/modules/statistics/SpeakerStatsCollector.js +161 -0
- package/dist/esm/modules/statistics/SpeakerStatsCollector.js.map +1 -0
- package/dist/esm/modules/statistics/constants.js +7 -0
- package/dist/esm/modules/statistics/constants.js.map +1 -0
- package/dist/esm/modules/statistics/statistics.js +362 -0
- package/dist/esm/modules/statistics/statistics.js.map +1 -0
- package/dist/esm/modules/util/AsyncQueue.js +102 -0
- package/dist/esm/modules/util/AsyncQueue.js.map +1 -0
- package/dist/esm/modules/util/Deferred.js +41 -0
- package/dist/esm/modules/util/Deferred.js.map +1 -0
- package/dist/esm/modules/util/EventEmitter.js +17 -0
- package/dist/esm/modules/util/EventEmitter.js.map +1 -0
- package/dist/esm/modules/util/EventEmitterForwarder.js +52 -0
- package/dist/esm/modules/util/EventEmitterForwarder.js.map +1 -0
- package/dist/esm/modules/util/Listenable.js +106 -0
- package/dist/esm/modules/util/Listenable.js.map +1 -0
- package/dist/esm/modules/util/MathUtil.js +103 -0
- package/dist/esm/modules/util/MathUtil.js.map +1 -0
- package/dist/esm/modules/util/RandomUtil.js +66 -0
- package/dist/esm/modules/util/RandomUtil.js.map +1 -0
- package/dist/esm/modules/util/Retry.js +15 -0
- package/dist/esm/modules/util/Retry.js.map +1 -0
- package/dist/esm/modules/util/ScriptUtil.js +58 -0
- package/dist/esm/modules/util/ScriptUtil.js.map +1 -0
- package/dist/esm/modules/util/StringUtils.js +21 -0
- package/dist/esm/modules/util/StringUtils.js.map +1 -0
- package/dist/esm/modules/util/TestUtils.js +14 -0
- package/dist/esm/modules/util/TestUtils.js.map +1 -0
- package/dist/esm/modules/util/UsernameGenerator.js +436 -0
- package/dist/esm/modules/util/UsernameGenerator.js.map +1 -0
- package/dist/esm/modules/util/XMLUtils.js +135 -0
- package/dist/esm/modules/util/XMLUtils.js.map +1 -0
- package/dist/esm/modules/version/ComponentsVersions.js +52 -0
- package/dist/esm/modules/version/ComponentsVersions.js.map +1 -0
- package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js +137 -0
- package/dist/esm/modules/videosipgw/JitsiVideoSIPGWSession.js.map +1 -0
- package/dist/esm/modules/videosipgw/VideoSIPGW.js +102 -0
- package/dist/esm/modules/videosipgw/VideoSIPGW.js.map +1 -0
- package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js +65 -0
- package/dist/esm/modules/videosipgw/VideoSIPGWConstants.js.map +1 -0
- package/dist/esm/modules/watchRTC/WatchRTC.js +69 -0
- package/dist/esm/modules/watchRTC/WatchRTC.js.map +1 -0
- package/dist/esm/modules/watchRTC/functions.js +31 -0
- package/dist/esm/modules/watchRTC/functions.js.map +1 -0
- package/dist/esm/modules/watchRTC/interfaces.js +2 -0
- package/dist/esm/modules/watchRTC/interfaces.js.map +1 -0
- package/dist/esm/modules/webaudio/AudioMixer.js +74 -0
- package/dist/esm/modules/webaudio/AudioMixer.js.map +1 -0
- package/dist/esm/modules/webaudio/WebAudioUtils.js +13 -0
- package/dist/esm/modules/webaudio/WebAudioUtils.js.map +1 -0
- package/dist/esm/modules/xmpp/AVModeration.js +156 -0
- package/dist/esm/modules/xmpp/AVModeration.js.map +1 -0
- package/dist/esm/modules/xmpp/BreakoutRooms.js +230 -0
- package/dist/esm/modules/xmpp/BreakoutRooms.js.map +1 -0
- package/dist/esm/modules/xmpp/Caps.js +223 -0
- package/dist/esm/modules/xmpp/Caps.js.map +1 -0
- package/dist/esm/modules/xmpp/ChatRoom.js +1877 -0
- package/dist/esm/modules/xmpp/ChatRoom.js.map +1 -0
- package/dist/esm/modules/xmpp/ConnectionPlugin.js +37 -0
- package/dist/esm/modules/xmpp/ConnectionPlugin.js.map +1 -0
- package/dist/esm/modules/xmpp/FileSharing.js +95 -0
- package/dist/esm/modules/xmpp/FileSharing.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleHelperFunctions.js +168 -0
- package/dist/esm/modules/xmpp/JingleHelperFunctions.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSession.js +166 -0
- package/dist/esm/modules/xmpp/JingleSession.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSessionPC.js +1969 -0
- package/dist/esm/modules/xmpp/JingleSessionPC.js.map +1 -0
- package/dist/esm/modules/xmpp/JingleSessionState.js +23 -0
- package/dist/esm/modules/xmpp/JingleSessionState.js.map +1 -0
- package/dist/esm/modules/xmpp/Lobby.js +384 -0
- package/dist/esm/modules/xmpp/Lobby.js.map +1 -0
- package/dist/esm/modules/xmpp/MediaSessionEvents.js +12 -0
- package/dist/esm/modules/xmpp/MediaSessionEvents.js.map +1 -0
- package/dist/esm/modules/xmpp/MockClasses.js +77 -0
- package/dist/esm/modules/xmpp/MockClasses.js.map +1 -0
- package/dist/esm/modules/xmpp/Polls.js +87 -0
- package/dist/esm/modules/xmpp/Polls.js.map +1 -0
- package/dist/esm/modules/xmpp/ResumeTask.js +149 -0
- package/dist/esm/modules/xmpp/ResumeTask.js.map +1 -0
- package/dist/esm/modules/xmpp/RoomMetadata.js +96 -0
- package/dist/esm/modules/xmpp/RoomMetadata.js.map +1 -0
- package/dist/esm/modules/xmpp/SignalingLayerImpl.js +313 -0
- package/dist/esm/modules/xmpp/SignalingLayerImpl.js.map +1 -0
- package/dist/esm/modules/xmpp/StropheErrorHandler.js +53 -0
- package/dist/esm/modules/xmpp/StropheErrorHandler.js.map +1 -0
- package/dist/esm/modules/xmpp/StropheLastSuccess.js +52 -0
- package/dist/esm/modules/xmpp/StropheLastSuccess.js.map +1 -0
- package/dist/esm/modules/xmpp/XmppConnection.js +579 -0
- package/dist/esm/modules/xmpp/XmppConnection.js.map +1 -0
- package/dist/esm/modules/xmpp/moderator.js +524 -0
- package/dist/esm/modules/xmpp/moderator.js.map +1 -0
- package/dist/esm/modules/xmpp/sha1.js +165 -0
- package/dist/esm/modules/xmpp/sha1.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.disco.js +222 -0
- package/dist/esm/modules/xmpp/strophe.disco.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.emuc.js +206 -0
- package/dist/esm/modules/xmpp/strophe.emuc.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.jingle.js +404 -0
- package/dist/esm/modules/xmpp/strophe.jingle.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.logger.js +44 -0
- package/dist/esm/modules/xmpp/strophe.logger.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.ping.js +170 -0
- package/dist/esm/modules/xmpp/strophe.ping.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.rayo.js +117 -0
- package/dist/esm/modules/xmpp/strophe.rayo.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.stream-management.js +365 -0
- package/dist/esm/modules/xmpp/strophe.stream-management.js.map +1 -0
- package/dist/esm/modules/xmpp/strophe.util.js +116 -0
- package/dist/esm/modules/xmpp/strophe.util.js.map +1 -0
- package/dist/esm/modules/xmpp/xmpp.js +973 -0
- package/dist/esm/modules/xmpp/xmpp.js.map +1 -0
- package/dist/esm/service/RTC/BridgeVideoType.js +24 -0
- package/dist/esm/service/RTC/BridgeVideoType.js.map +1 -0
- package/dist/esm/service/RTC/CameraFacingMode.js +21 -0
- package/dist/esm/service/RTC/CameraFacingMode.js.map +1 -0
- package/dist/esm/service/RTC/CodecMimeType.js +36 -0
- package/dist/esm/service/RTC/CodecMimeType.js.map +1 -0
- package/dist/esm/service/RTC/MediaDirection.js +23 -0
- package/dist/esm/service/RTC/MediaDirection.js.map +1 -0
- package/dist/esm/service/RTC/MediaType.js +20 -0
- package/dist/esm/service/RTC/MediaType.js.map +1 -0
- package/dist/esm/service/RTC/RTCEvents.js +111 -0
- package/dist/esm/service/RTC/RTCEvents.js.map +1 -0
- package/dist/esm/service/RTC/ReceiverAudioSubscription.js +27 -0
- package/dist/esm/service/RTC/ReceiverAudioSubscription.js.map +1 -0
- package/dist/esm/service/RTC/Resolutions.js +56 -0
- package/dist/esm/service/RTC/Resolutions.js.map +1 -0
- package/dist/esm/service/RTC/SignalingEvents.js +42 -0
- package/dist/esm/service/RTC/SignalingEvents.js.map +1 -0
- package/dist/esm/service/RTC/SignalingLayer.js +153 -0
- package/dist/esm/service/RTC/SignalingLayer.js.map +1 -0
- package/dist/esm/service/RTC/StandardVideoQualitySettings.js +180 -0
- package/dist/esm/service/RTC/StandardVideoQualitySettings.js.map +1 -0
- package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js +36 -0
- package/dist/esm/service/RTC/VideoEncoderScalabilityMode.js.map +1 -0
- package/dist/esm/service/RTC/VideoType.js +19 -0
- package/dist/esm/service/RTC/VideoType.js.map +1 -0
- package/dist/esm/service/authentication/AuthenticationEvents.js +13 -0
- package/dist/esm/service/authentication/AuthenticationEvents.js.map +1 -0
- package/dist/esm/service/connectivity/ConnectionQualityEvents.js +13 -0
- package/dist/esm/service/connectivity/ConnectionQualityEvents.js.map +1 -0
- package/dist/esm/service/connectivity/Constants.js +3 -0
- package/dist/esm/service/connectivity/Constants.js.map +1 -0
- package/dist/esm/service/e2eping/E2ePingEvents.js +8 -0
- package/dist/esm/service/e2eping/E2ePingEvents.js.map +1 -0
- package/dist/esm/service/statistics/AnalyticsEvents.js +521 -0
- package/dist/esm/service/statistics/AnalyticsEvents.js.map +1 -0
- package/dist/esm/service/statistics/Events.js +36 -0
- package/dist/esm/service/statistics/Events.js.map +1 -0
- package/dist/esm/service/statistics/constants.js +2 -0
- package/dist/esm/service/statistics/constants.js.map +1 -0
- package/dist/esm/service/xmpp/XMPPEvents.js +359 -0
- package/dist/esm/service/xmpp/XMPPEvents.js.map +1 -0
- package/dist/esm/service/xmpp/XMPPExtensioProtocols.js +64 -0
- package/dist/esm/service/xmpp/XMPPExtensioProtocols.js.map +1 -0
- package/dist/esm/test-setup-polyfill.js +34 -0
- package/dist/esm/test-setup-polyfill.js.map +1 -0
- package/dist/esm/tools/gen-version.js +15 -0
- package/dist/esm/tools/gen-version.js.map +1 -0
- package/dist/esm/version.js +3 -0
- package/dist/esm/version.js.map +1 -0
- package/dist/umd/lib-jitsi-meet.e2ee-worker.js +484 -0
- package/dist/umd/lib-jitsi-meet.min.js +3 -0
- package/dist/umd/lib-jitsi-meet.min.js.LICENSE.txt +18 -0
- package/dist/umd/lib-jitsi-meet.min.map +1 -0
- package/package.json +93 -0
- package/types/index.d.ts +16180 -0
|
@@ -0,0 +1,1969 @@
|
|
|
1
|
+
import { getLogger } from '@jitsi/logger';
|
|
2
|
+
import { isEqual } from 'lodash-es';
|
|
3
|
+
import { $build, $iq, Strophe } from 'strophe.js';
|
|
4
|
+
import { JitsiConferenceEvents } from '../../JitsiConferenceEvents';
|
|
5
|
+
import { JitsiTrackEvents } from '../../JitsiTrackEvents';
|
|
6
|
+
import { CodecMimeType } from '../../service/RTC/CodecMimeType';
|
|
7
|
+
import { MediaDirection } from '../../service/RTC/MediaDirection';
|
|
8
|
+
import { MediaType } from '../../service/RTC/MediaType';
|
|
9
|
+
import { SSRC_GROUP_SEMANTICS } from '../../service/RTC/StandardVideoQualitySettings';
|
|
10
|
+
import { VideoType } from '../../service/RTC/VideoType';
|
|
11
|
+
import { AnalyticsEvents } from '../../service/statistics/AnalyticsEvents';
|
|
12
|
+
import { XMPPEvents } from '../../service/xmpp/XMPPEvents';
|
|
13
|
+
import { XEP } from '../../service/xmpp/XMPPExtensioProtocols';
|
|
14
|
+
import { SS_DEFAULT_FRAME_RATE } from '../RTC/ScreenObtainer';
|
|
15
|
+
import browser from '../browser';
|
|
16
|
+
import FeatureFlags from '../flags/FeatureFlags';
|
|
17
|
+
import SDP from '../sdp/SDP';
|
|
18
|
+
import { SDPDiffer } from '../sdp/SDPDiffer';
|
|
19
|
+
import SDPUtil from '../sdp/SDPUtil';
|
|
20
|
+
import Statistics from '../statistics/statistics';
|
|
21
|
+
import AsyncQueue, { ClearedQueueError } from '../util/AsyncQueue';
|
|
22
|
+
import { exists, findAll, findFirst, getAttribute } from '../util/XMLUtils';
|
|
23
|
+
import JingleSession from './JingleSession';
|
|
24
|
+
import { JingleSessionState } from './JingleSessionState';
|
|
25
|
+
import { MediaSessionEvents } from './MediaSessionEvents';
|
|
26
|
+
import { handleStropheError } from './StropheErrorHandler';
|
|
27
|
+
import XmppConnection from './XmppConnection';
|
|
28
|
+
const logger = getLogger('xmpp:JingleSessionPC');
|
|
29
|
+
/**
|
|
30
|
+
* Constant tells how long we're going to wait for IQ response, before timeout
|
|
31
|
+
* error is triggered.
|
|
32
|
+
* @type {number}
|
|
33
|
+
*/
|
|
34
|
+
const IQ_TIMEOUT = 10000;
|
|
35
|
+
/*
|
|
36
|
+
* The default number of samples (per stat) to keep when webrtc stats gathering
|
|
37
|
+
* is enabled in TraceablePeerConnection.
|
|
38
|
+
*/
|
|
39
|
+
const DEFAULT_MAX_STATS = 300;
|
|
40
|
+
/**
|
|
41
|
+
* The time duration for which the client keeps gathering ICE candidates to be sent out in a single IQ.
|
|
42
|
+
* @type {number} timeout in ms.
|
|
43
|
+
*/
|
|
44
|
+
const ICE_CAND_GATHERING_TIMEOUT = 150;
|
|
45
|
+
/**
|
|
46
|
+
* Reads the endpoint ID given a string which represents either the endpoint's full JID, or the endpoint ID itself.
|
|
47
|
+
* @param {String} jidOrEndpointId A string which is either the full JID of a participant, or the ID of an
|
|
48
|
+
* endpoint/participant.
|
|
49
|
+
* @returns The endpoint ID associated with 'jidOrEndpointId'.
|
|
50
|
+
*/
|
|
51
|
+
function getEndpointId(jidOrEndpointId) {
|
|
52
|
+
return Strophe.getResourceFromJid(jidOrEndpointId) || jidOrEndpointId;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Add "source" element as a child of "description" element.
|
|
56
|
+
* @param {Object} description The "description" element to add to.
|
|
57
|
+
* @param {Object} s Contains properties of the source being added.
|
|
58
|
+
* @param {Number} ssrc_ The SSRC.
|
|
59
|
+
* @param {String} msid The "msid" attribute.
|
|
60
|
+
*/
|
|
61
|
+
function _addSourceElement(description, s, ssrc_, msid) {
|
|
62
|
+
description.c('source', {
|
|
63
|
+
name: s.source,
|
|
64
|
+
ssrc: ssrc_,
|
|
65
|
+
videoType: s.videoType?.toLowerCase(),
|
|
66
|
+
xmlns: XEP.SOURCE_ATTRIBUTES
|
|
67
|
+
})
|
|
68
|
+
.c('parameter', {
|
|
69
|
+
name: 'msid',
|
|
70
|
+
value: msid
|
|
71
|
+
})
|
|
72
|
+
.up()
|
|
73
|
+
.c('ssrc-info', {
|
|
74
|
+
owner: s.owner,
|
|
75
|
+
xmlns: 'http://jitsi.org/jitmeet'
|
|
76
|
+
})
|
|
77
|
+
.up()
|
|
78
|
+
.up();
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
*
|
|
82
|
+
*/
|
|
83
|
+
export default class JingleSessionPC extends JingleSession {
|
|
84
|
+
/**
|
|
85
|
+
* Parses 'senders' attribute of the video content.
|
|
86
|
+
* @param {Element} jingleContents
|
|
87
|
+
* @return {Nullable<string>} one of the values of content "senders" attribute
|
|
88
|
+
* defined by Jingle. If there is no "senders" attribute or if the value is
|
|
89
|
+
* invalid then <tt>null</tt> will be returned.
|
|
90
|
+
* @private
|
|
91
|
+
*/
|
|
92
|
+
static parseVideoSenders(jingleContents) {
|
|
93
|
+
const videoContents = findFirst(jingleContents, ':scope>content[name="video"]');
|
|
94
|
+
const senders = getAttribute(videoContents, 'senders');
|
|
95
|
+
if (senders === 'both'
|
|
96
|
+
|| senders === 'initiator'
|
|
97
|
+
|| senders === 'responder'
|
|
98
|
+
|| senders === 'none') {
|
|
99
|
+
return senders;
|
|
100
|
+
}
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* Parses the source-name and max frame height value of the 'content-modify' IQ when source-name signaling
|
|
105
|
+
* is enabled.
|
|
106
|
+
*
|
|
107
|
+
* @param {Element} jingleContents - An element pointing to the '>jingle' element.
|
|
108
|
+
* @returns {Nullable<Object>}
|
|
109
|
+
*/
|
|
110
|
+
static parseSourceMaxFrameHeight(jingleContents) {
|
|
111
|
+
const receiverConstraints = [];
|
|
112
|
+
const sourceFrameHeightSel = findAll(jingleContents, ':scope>content[name="video"]>source-frame-height');
|
|
113
|
+
let maxHeight, sourceName;
|
|
114
|
+
if (sourceFrameHeightSel.length) {
|
|
115
|
+
sourceFrameHeightSel.forEach(source => {
|
|
116
|
+
sourceName = source.getAttribute('sourceName');
|
|
117
|
+
maxHeight = source.getAttribute('maxHeight');
|
|
118
|
+
receiverConstraints.push({
|
|
119
|
+
maxHeight,
|
|
120
|
+
sourceName
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
return receiverConstraints;
|
|
124
|
+
}
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
/* eslint-disable max-params */
|
|
128
|
+
/**
|
|
129
|
+
* Creates new <tt>JingleSessionPC</tt>
|
|
130
|
+
* @param {string} sid the Jingle Session ID - random string which identifies the session
|
|
131
|
+
* @param {string} localJid our JID
|
|
132
|
+
* @param {string} remoteJid remote peer JID
|
|
133
|
+
* @param {XmppConnection} connection - The XMPP connection instance.
|
|
134
|
+
* @param mediaConstraints the media constraints object passed to createOffer/Answer, as defined
|
|
135
|
+
* by the WebRTC standard
|
|
136
|
+
* @param pcConfig The {@code RTCConfiguration} to use for the WebRTC peer connection.
|
|
137
|
+
* @param {boolean} isP2P indicates whether this instance is meant to be used in a direct, peer to
|
|
138
|
+
* peer connection or <tt>false</tt> if it's a JVB connection.
|
|
139
|
+
* @param {boolean} isInitiator indicates if it will be the side which initiates the session.
|
|
140
|
+
* @constructor
|
|
141
|
+
*
|
|
142
|
+
* @implements {SignalingLayer}
|
|
143
|
+
*/
|
|
144
|
+
constructor(sid, localJid, remoteJid, connection, mediaConstraints, pcConfig, isP2P, isInitiator) {
|
|
145
|
+
super(sid, localJid, remoteJid, connection, mediaConstraints, pcConfig, isInitiator);
|
|
146
|
+
/**
|
|
147
|
+
* The bridge session's identifier. One Jingle session can during
|
|
148
|
+
* it's lifetime participate in multiple bridge sessions managed by
|
|
149
|
+
* Jicofo. A new bridge session is started whenever Jicofo sends
|
|
150
|
+
* 'session-initiate'.
|
|
151
|
+
*
|
|
152
|
+
* @type {?string}
|
|
153
|
+
* @private
|
|
154
|
+
*/
|
|
155
|
+
this._bridgeSessionId = null;
|
|
156
|
+
/**
|
|
157
|
+
* The oldest SDP passed to {@link notifyMySSRCUpdate} while the XMPP connection was offline that will be
|
|
158
|
+
* used to update Jicofo once the XMPP connection goes back online.
|
|
159
|
+
* @type {Optional<SDP>}
|
|
160
|
+
* @private
|
|
161
|
+
*/
|
|
162
|
+
this._cachedOldLocalSdp = undefined;
|
|
163
|
+
/**
|
|
164
|
+
* The latest SDP passed to {@link notifyMySSRCUpdate} while the XMPP connection was offline that will be
|
|
165
|
+
* used to update Jicofo once the XMPP connection goes back online.
|
|
166
|
+
* @type {Optional<SDP>}
|
|
167
|
+
* @private
|
|
168
|
+
*/
|
|
169
|
+
this._cachedNewLocalSdp = undefined;
|
|
170
|
+
/**
|
|
171
|
+
* Stores result of {@link window.performance.now()} at the time when
|
|
172
|
+
* ICE enters 'checking' state.
|
|
173
|
+
* @type {Nullable<number>}
|
|
174
|
+
* @private
|
|
175
|
+
*/
|
|
176
|
+
this._iceCheckingStartedTimestamp = null;
|
|
177
|
+
/**
|
|
178
|
+
* Stores result of {@link window.performance.now()} at the time when
|
|
179
|
+
* first ICE candidate is spawned by the peerconnection to mark when
|
|
180
|
+
* ICE gathering started. That's, because ICE gathering state changed
|
|
181
|
+
* events are not supported by most of the browsers, so we try something
|
|
182
|
+
* that will work everywhere. It may not be as accurate, but given that
|
|
183
|
+
* 'host' candidate usually comes first, the delay should be minimal.
|
|
184
|
+
* @type {Nullable<number>} null if no value has been stored yet
|
|
185
|
+
* @private
|
|
186
|
+
*/
|
|
187
|
+
this._gatheringStartedTimestamp = null;
|
|
188
|
+
/**
|
|
189
|
+
* Receiver constraints (max height) set by the application per remote source. Will be used for p2p connection.
|
|
190
|
+
*
|
|
191
|
+
* @type {Map<string, number>}
|
|
192
|
+
*/
|
|
193
|
+
this._sourceReceiverConstraints = undefined;
|
|
194
|
+
/**
|
|
195
|
+
* Indicates whether or not this session is willing to send/receive
|
|
196
|
+
* video media. When set to <tt>false</tt> the underlying peer
|
|
197
|
+
* connection will disable local video transfer and the remote peer will
|
|
198
|
+
* be will be asked to stop sending video via 'content-modify' IQ
|
|
199
|
+
* (the senders attribute of video contents will be adjusted
|
|
200
|
+
* accordingly). Note that this notification is sent only in P2P
|
|
201
|
+
* session, because Jicofo does not support it yet. Obviously when
|
|
202
|
+
* the value is changed from <tt>false</tt> to <tt>true</tt> another
|
|
203
|
+
* notification will be sent to resume video transfer on the remote
|
|
204
|
+
* side.
|
|
205
|
+
* @type {boolean}
|
|
206
|
+
* @private
|
|
207
|
+
*/
|
|
208
|
+
this._localSendReceiveVideoActive = true;
|
|
209
|
+
/**
|
|
210
|
+
* Indicates whether or not the remote peer has video transfer active.
|
|
211
|
+
* When set to <tt>true</tt> it means that remote peer is neither
|
|
212
|
+
* sending nor willing to receive video. In such case we'll ask
|
|
213
|
+
* our peerconnection to stop sending video by calling
|
|
214
|
+
* {@link TraceablePeerConnection.setVideoTransferActive} with
|
|
215
|
+
* <tt>false</tt>.
|
|
216
|
+
* @type {boolean}
|
|
217
|
+
* @private
|
|
218
|
+
*/
|
|
219
|
+
this._remoteSendReceiveVideoActive = true;
|
|
220
|
+
/**
|
|
221
|
+
* Marks that ICE gathering duration has been reported already. That
|
|
222
|
+
* prevents reporting it again.
|
|
223
|
+
* @type {boolean}
|
|
224
|
+
* @private
|
|
225
|
+
*/
|
|
226
|
+
this._gatheringReported = false;
|
|
227
|
+
this.lasticecandidate = false;
|
|
228
|
+
/**
|
|
229
|
+
* Indicates whether or not this <tt>JingleSessionPC</tt> is used in
|
|
230
|
+
* a peer to peer type of session.
|
|
231
|
+
* @type {boolean} <tt>true</tt> if it's a peer to peer
|
|
232
|
+
* session or <tt>false</tt> if it's a JVB session
|
|
233
|
+
*/
|
|
234
|
+
this.isP2P = isP2P;
|
|
235
|
+
/**
|
|
236
|
+
* Number of remote video sources, in SSRC rewriting mode.
|
|
237
|
+
* Used to generate next unique msid attribute.
|
|
238
|
+
*
|
|
239
|
+
* @type {Number}
|
|
240
|
+
*/
|
|
241
|
+
this.numRemoteVideoSources = 0;
|
|
242
|
+
/**
|
|
243
|
+
* Number of remote audio sources, in SSRC rewriting mode.
|
|
244
|
+
* Used to generate next unique msid attribute.
|
|
245
|
+
*
|
|
246
|
+
* @type {Number}
|
|
247
|
+
*/
|
|
248
|
+
this.numRemoteAudioSources = 0;
|
|
249
|
+
/**
|
|
250
|
+
* Remote preference for the receive video max frame heights when source-name signaling is enabled.
|
|
251
|
+
*
|
|
252
|
+
* @type {Optional<Map<string, number>>}
|
|
253
|
+
* @private
|
|
254
|
+
*/
|
|
255
|
+
this.remoteSourceMaxFrameHeights = undefined;
|
|
256
|
+
/**
|
|
257
|
+
* The queue used to serialize operations done on the peerconnection after the session is established.
|
|
258
|
+
* The queue is paused until the first offer/answer cycle is complete. Only track or codec related
|
|
259
|
+
* operations which necessitate a renegotiation cycle need to be pushed to the modification queue.
|
|
260
|
+
* These tasks will be executed after the session has been established.
|
|
261
|
+
*
|
|
262
|
+
* @type {AsyncQueue}
|
|
263
|
+
*/
|
|
264
|
+
this.modificationQueue = new AsyncQueue();
|
|
265
|
+
this.modificationQueue.pause();
|
|
266
|
+
/**
|
|
267
|
+
* Flag used to guarantee that the connection established event is
|
|
268
|
+
* triggered just once.
|
|
269
|
+
* @type {boolean}
|
|
270
|
+
*/
|
|
271
|
+
this.wasConnected = false;
|
|
272
|
+
/**
|
|
273
|
+
* Keeps track of how long (in ms) it took from ICE start to ICE
|
|
274
|
+
* connect.
|
|
275
|
+
*
|
|
276
|
+
* @type {number}
|
|
277
|
+
*/
|
|
278
|
+
this.establishmentDuration = undefined;
|
|
279
|
+
this._xmppListeners = [];
|
|
280
|
+
this._xmppListeners.push(connection.addCancellableListener(XmppConnection.Events.CONN_STATUS_CHANGED, this.onXmppStatusChanged.bind(this)));
|
|
281
|
+
this._removeSenderVideoConstraintsChangeListener = undefined;
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Handles either Jingle 'source-add' or 'source-remove' message for this Jingle session.
|
|
285
|
+
*
|
|
286
|
+
* @param {boolean} isAdd <tt>true</tt> for 'source-add' or <tt>false</tt> otherwise.
|
|
287
|
+
* @param {Array<Element>} elem an array of Jingle "content" elements.
|
|
288
|
+
* @returns {Promise} resolved when the operation is done or rejected with an error.
|
|
289
|
+
*/
|
|
290
|
+
_addOrRemoveRemoteStream(isAdd, elem) {
|
|
291
|
+
const logPrefix = isAdd ? 'addRemoteStream' : 'removeRemoteStream';
|
|
292
|
+
const workFunction = (finishedCallback) => {
|
|
293
|
+
if (!this.peerconnection.remoteDescription?.sdp) {
|
|
294
|
+
const errMsg = `${logPrefix} - received before remoteDescription is set, ignoring!!`;
|
|
295
|
+
logger.error(errMsg);
|
|
296
|
+
finishedCallback(errMsg);
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
logger.debug(`${this} Processing ${logPrefix}`);
|
|
300
|
+
const currentRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp, this.isP2P);
|
|
301
|
+
const sourceDescription = this._processSourceMapFromJingle(elem, isAdd);
|
|
302
|
+
if (!sourceDescription.size) {
|
|
303
|
+
logger.debug(`${this} ${logPrefix} - no sources to ${isAdd ? 'add' : 'remove'}`);
|
|
304
|
+
finishedCallback(undefined);
|
|
305
|
+
}
|
|
306
|
+
logger.debug(`${isAdd ? 'adding' : 'removing'} sources=${Array.from(sourceDescription.keys())}`);
|
|
307
|
+
// Update the remote description.
|
|
308
|
+
const modifiedMids = currentRemoteSdp.updateRemoteSources(sourceDescription, isAdd);
|
|
309
|
+
for (const mid of modifiedMids) {
|
|
310
|
+
if (this.isP2P) {
|
|
311
|
+
const { media } = SDPUtil.parseMLine(currentRemoteSdp.media[mid].split('\r\n')[0]);
|
|
312
|
+
const desiredDirection = this.peerconnection.getDesiredMediaDirection(media, isAdd);
|
|
313
|
+
const currentDirections = isAdd ? [MediaDirection.RECVONLY, MediaDirection.INACTIVE]
|
|
314
|
+
: [MediaDirection.SENDRECV, MediaDirection.SENDONLY];
|
|
315
|
+
currentDirections.forEach(direction => {
|
|
316
|
+
currentRemoteSdp.media[mid] = currentRemoteSdp.media[mid]
|
|
317
|
+
.replace(`a=${direction}`, `a=${desiredDirection}`);
|
|
318
|
+
});
|
|
319
|
+
currentRemoteSdp.raw = currentRemoteSdp.session + currentRemoteSdp.media.join('');
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
this._renegotiate(currentRemoteSdp.raw).then(() => {
|
|
323
|
+
logger.debug(`${this} ${logPrefix} - OK`);
|
|
324
|
+
finishedCallback(undefined);
|
|
325
|
+
}, error => {
|
|
326
|
+
logger.error(`${this} ${logPrefix} failed:`, error);
|
|
327
|
+
finishedCallback(error);
|
|
328
|
+
});
|
|
329
|
+
};
|
|
330
|
+
logger.debug(`${this} Queued ${logPrefix} task`);
|
|
331
|
+
// Queue and execute
|
|
332
|
+
this.modificationQueue.push(workFunction);
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* See {@link addTrackToPc} and {@link removeTrackFromPc}.
|
|
336
|
+
*
|
|
337
|
+
* @param {boolean} isRemove <tt>true</tt> for "remove" operation or <tt>false</tt> for "add" operation.
|
|
338
|
+
* @param {JitsiLocalTrack} track the track that will be added/removed.
|
|
339
|
+
* @returns {Promise} resolved when the operation is done or rejected with an error.
|
|
340
|
+
*/
|
|
341
|
+
_addRemoveTrack(isRemove, track) {
|
|
342
|
+
if (!track) {
|
|
343
|
+
return Promise.reject('invalid "track" argument value');
|
|
344
|
+
}
|
|
345
|
+
const operationName = isRemove ? 'removeTrack' : 'addTrack';
|
|
346
|
+
const workFunction = finishedCallback => {
|
|
347
|
+
const tpc = this.peerconnection;
|
|
348
|
+
if (!tpc) {
|
|
349
|
+
finishedCallback(`Error: tried ${operationName} track with no active peer connection`);
|
|
350
|
+
return;
|
|
351
|
+
}
|
|
352
|
+
const operationPromise = isRemove
|
|
353
|
+
? tpc.removeTrackFromPc(track)
|
|
354
|
+
: tpc.addTrackToPc(track);
|
|
355
|
+
operationPromise
|
|
356
|
+
.then(shouldRenegotiate => {
|
|
357
|
+
if (shouldRenegotiate) {
|
|
358
|
+
this._renegotiate().then(finishedCallback);
|
|
359
|
+
}
|
|
360
|
+
else {
|
|
361
|
+
finishedCallback();
|
|
362
|
+
}
|
|
363
|
+
}, finishedCallback /* will be called with an error */);
|
|
364
|
+
};
|
|
365
|
+
logger.debug(`${this} Queued ${operationName} task`);
|
|
366
|
+
return new Promise((resolve, reject) => {
|
|
367
|
+
this.modificationQueue.push(workFunction, error => {
|
|
368
|
+
if (error) {
|
|
369
|
+
if (error instanceof ClearedQueueError) {
|
|
370
|
+
// The session might have been terminated before the task was executed, making it obsolete.
|
|
371
|
+
logger.debug(`${this} ${operationName} aborted: session terminated`);
|
|
372
|
+
resolve();
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
logger.error(`${this} ${operationName} failed`);
|
|
376
|
+
reject(error);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
logger.debug(`${this} ${operationName} done`);
|
|
380
|
+
resolve();
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
/**
|
|
386
|
+
* Checks whether or not this session instance is still operational.
|
|
387
|
+
*
|
|
388
|
+
* @returns {boolean} {@code true} if operation or {@code false} otherwise.
|
|
389
|
+
*/
|
|
390
|
+
_assertNotEnded() {
|
|
391
|
+
return this.state !== JingleSessionState.ENDED;
|
|
392
|
+
}
|
|
393
|
+
/**
|
|
394
|
+
* Takes in a jingle offer iq, returns the new sdp offer that can be set as remote description in the
|
|
395
|
+
* peerconnection.
|
|
396
|
+
*
|
|
397
|
+
* @param {Element} offerIq the incoming offer.
|
|
398
|
+
* @returns {SDP object} the jingle offer translated to SDP.
|
|
399
|
+
*/
|
|
400
|
+
_processNewJingleOfferIq(offerIq) {
|
|
401
|
+
const remoteSdp = new SDP('', this.isP2P);
|
|
402
|
+
if (this.webrtcIceTcpDisable) {
|
|
403
|
+
remoteSdp.removeTcpCandidates = true;
|
|
404
|
+
}
|
|
405
|
+
if (this.webrtcIceUdpDisable) {
|
|
406
|
+
remoteSdp.removeUdpCandidates = true;
|
|
407
|
+
}
|
|
408
|
+
if (this.failICE) {
|
|
409
|
+
remoteSdp.failICE = true;
|
|
410
|
+
}
|
|
411
|
+
remoteSdp.fromJingle(offerIq);
|
|
412
|
+
this._processSourceMapFromJingle(findAll(offerIq, ':scope>content'));
|
|
413
|
+
return remoteSdp;
|
|
414
|
+
}
|
|
415
|
+
/**
|
|
416
|
+
* Parses the SSRC information from the source-add/source-remove element passed and updates the SSRC owners.
|
|
417
|
+
*
|
|
418
|
+
* @param {Object} sourceElement the source-add/source-remove element from jingle.
|
|
419
|
+
* @param {boolean} isAdd true if the sources are being added, false if they are to be removed.
|
|
420
|
+
* @returns {Map<string, Object>} - The map of source name to ssrcs, msid and groups.
|
|
421
|
+
*/
|
|
422
|
+
_processSourceMapFromJingle(sourceElement, isAdd = true) {
|
|
423
|
+
/**
|
|
424
|
+
* Map of source name to ssrcs, mediaType, msid and groups.
|
|
425
|
+
* @type {Map<string,
|
|
426
|
+
* {
|
|
427
|
+
* mediaType: string,
|
|
428
|
+
* msid: string,
|
|
429
|
+
* ssrcList: Array<number>,
|
|
430
|
+
* groups: ISsrcGroupInfo
|
|
431
|
+
* }>}
|
|
432
|
+
*/
|
|
433
|
+
const sourceDescription = new Map();
|
|
434
|
+
const sourceElementArray = Array.isArray(sourceElement) ? sourceElement : [sourceElement];
|
|
435
|
+
for (const content of sourceElementArray) {
|
|
436
|
+
const descriptionsWithSources = findAll(content, ':scope>description').filter((el) => exists(el, ':scope>source'));
|
|
437
|
+
for (const description of descriptionsWithSources) {
|
|
438
|
+
const mediaType = getAttribute(description, 'media');
|
|
439
|
+
if (mediaType === MediaType.AUDIO && this.options.startSilent) {
|
|
440
|
+
// eslint-disable-next-line no-continue
|
|
441
|
+
continue;
|
|
442
|
+
}
|
|
443
|
+
// Get direct source children, excluding those inside ssrc-group elements
|
|
444
|
+
const sources = findAll(description, ':scope>source');
|
|
445
|
+
const removeSsrcs = [];
|
|
446
|
+
for (const source of sources) {
|
|
447
|
+
const ssrc = getAttribute(source, 'ssrc');
|
|
448
|
+
const sourceName = getAttribute(source, 'name');
|
|
449
|
+
const msid = getAttribute(findFirst(source, ':scope>parameter[name="msid"]'), 'value');
|
|
450
|
+
let videoType = getAttribute(source, 'videoType');
|
|
451
|
+
// If the videoType is DESKTOP_HIGH_FPS for remote tracks, we should treat it as DESKTOP.
|
|
452
|
+
if (videoType === VideoType.DESKTOP_HIGH_FPS) {
|
|
453
|
+
videoType = VideoType.DESKTOP;
|
|
454
|
+
}
|
|
455
|
+
if (sourceDescription.has(sourceName)) {
|
|
456
|
+
sourceDescription.get(sourceName).ssrcList?.push(ssrc);
|
|
457
|
+
}
|
|
458
|
+
else {
|
|
459
|
+
sourceDescription.set(sourceName, {
|
|
460
|
+
groups: [],
|
|
461
|
+
mediaType,
|
|
462
|
+
msid,
|
|
463
|
+
ssrcList: [ssrc],
|
|
464
|
+
videoType
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
// Update the source owner and source name.
|
|
468
|
+
// Use *|xmlns to match xmlns attributes across any namespace (CSS Selectors Level 3)
|
|
469
|
+
const owner = getAttribute(findFirst(source, ':scope>ssrc-info[*|xmlns="http://jitsi.org/jitmeet"]'), 'owner');
|
|
470
|
+
if (owner && isAdd) {
|
|
471
|
+
// JVB source-add.
|
|
472
|
+
this._signalingLayer.setSSRCOwner(Number(ssrc), getEndpointId(owner), sourceName);
|
|
473
|
+
}
|
|
474
|
+
else if (isAdd) {
|
|
475
|
+
// P2P source-add.
|
|
476
|
+
this._signalingLayer.setSSRCOwner(Number(ssrc), Strophe.getResourceFromJid(this.remoteJid), sourceName);
|
|
477
|
+
}
|
|
478
|
+
else {
|
|
479
|
+
removeSsrcs.push(Number(ssrc));
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
// 'source-remove' from remote peer.
|
|
483
|
+
removeSsrcs.length && this._signalingLayer.removeSSRCOwners(removeSsrcs);
|
|
484
|
+
findAll(description, ':scope>ssrc-group').forEach(group => {
|
|
485
|
+
const semantics = getAttribute(group, 'semantics');
|
|
486
|
+
const groupSsrcs = [];
|
|
487
|
+
findAll(group, ':scope>source').forEach(source => {
|
|
488
|
+
groupSsrcs.push(getAttribute(source, 'ssrc'));
|
|
489
|
+
});
|
|
490
|
+
for (const [sourceName, { ssrcList }] of sourceDescription) {
|
|
491
|
+
if (isEqual(ssrcList.slice().sort(), groupSsrcs.slice().sort())) {
|
|
492
|
+
sourceDescription.get(sourceName).groups.push({
|
|
493
|
+
semantics,
|
|
494
|
+
ssrcs: groupSsrcs
|
|
495
|
+
});
|
|
496
|
+
}
|
|
497
|
+
}
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
sourceDescription.size && this.peerconnection.updateRemoteSources(sourceDescription, isAdd);
|
|
502
|
+
return sourceDescription;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Does a new offer/answer flow using the existing remote description (if not provided) and signals any new sources
|
|
506
|
+
* to Jicofo or the remote peer.
|
|
507
|
+
*
|
|
508
|
+
* @param {string} [optionalRemoteSdp] optional, raw remote sdp to use. If not provided, the remote sdp from the
|
|
509
|
+
* peerconnection will be used.
|
|
510
|
+
* @returns {Promise} promise which resolves when the o/a flow is complete with no arguments or rejects with an
|
|
511
|
+
* error {string}
|
|
512
|
+
*/
|
|
513
|
+
async _renegotiate(optionalRemoteSdp) {
|
|
514
|
+
if (this.peerconnection.signalingState === 'closed') {
|
|
515
|
+
throw new Error('Attempted to renegotiate in state closed');
|
|
516
|
+
}
|
|
517
|
+
const remoteSdp = optionalRemoteSdp || this.peerconnection.remoteDescription.sdp;
|
|
518
|
+
if (!remoteSdp) {
|
|
519
|
+
throw new Error(`Cannot renegotiate without remote description, state=${this.state}`);
|
|
520
|
+
}
|
|
521
|
+
const remoteDescription = {
|
|
522
|
+
sdp: remoteSdp,
|
|
523
|
+
type: 'offer'
|
|
524
|
+
};
|
|
525
|
+
const oldLocalSDP = this.peerconnection.localDescription.sdp;
|
|
526
|
+
logger.debug(`${this} Renegotiate: setting remote description`);
|
|
527
|
+
try {
|
|
528
|
+
await this.peerconnection.setRemoteDescription(remoteDescription);
|
|
529
|
+
logger.debug(`${this} Renegotiate: creating answer`);
|
|
530
|
+
const answer = await this.peerconnection.createAnswer(this.mediaConstraints);
|
|
531
|
+
logger.debug(`${this} Renegotiate: setting local description`);
|
|
532
|
+
await this.peerconnection.setLocalDescription(answer);
|
|
533
|
+
if (oldLocalSDP) {
|
|
534
|
+
// Send the source updates after every renegotiation cycle.
|
|
535
|
+
this.notifyMySSRCUpdate(new SDP(oldLocalSDP), new SDP(this.peerconnection.localDescription.sdp));
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
catch (error) {
|
|
539
|
+
logger.error(`${this} Renegotiate failed:`, error);
|
|
540
|
+
throw error;
|
|
541
|
+
}
|
|
542
|
+
}
|
|
543
|
+
/**
|
|
544
|
+
* Sends 'content-modify' IQ in order to ask the remote peer to either stop or resume sending video media or to
|
|
545
|
+
* adjust sender's video constraints.
|
|
546
|
+
*
|
|
547
|
+
* @returns {void}
|
|
548
|
+
*/
|
|
549
|
+
_sendContentModify() {
|
|
550
|
+
const senders = this._localSendReceiveVideoActive ? 'both' : 'none';
|
|
551
|
+
const sessionModify = $iq({
|
|
552
|
+
to: this.remoteJid,
|
|
553
|
+
type: 'set'
|
|
554
|
+
})
|
|
555
|
+
.c('jingle', {
|
|
556
|
+
action: 'content-modify',
|
|
557
|
+
initiator: this.initiatorJid,
|
|
558
|
+
sid: this.sid,
|
|
559
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
560
|
+
})
|
|
561
|
+
.c('content', {
|
|
562
|
+
name: MediaType.VIDEO,
|
|
563
|
+
senders
|
|
564
|
+
});
|
|
565
|
+
if (typeof this._sourceReceiverConstraints !== 'undefined') {
|
|
566
|
+
this._sourceReceiverConstraints.forEach((maxHeight, sourceName) => {
|
|
567
|
+
sessionModify
|
|
568
|
+
.c('source-frame-height', { xmlns: 'http://jitsi.org/jitmeet/video' })
|
|
569
|
+
.attrs({
|
|
570
|
+
maxHeight,
|
|
571
|
+
sourceName
|
|
572
|
+
});
|
|
573
|
+
sessionModify.up();
|
|
574
|
+
logger.info(`${this} sending content-modify for source-name: ${sourceName}, maxHeight: ${maxHeight}`);
|
|
575
|
+
});
|
|
576
|
+
}
|
|
577
|
+
logger.debug(sessionModify.tree());
|
|
578
|
+
this.connection.sendIQ(sessionModify, null, this.newJingleErrorHandler(), IQ_TIMEOUT);
|
|
579
|
+
}
|
|
580
|
+
/**
|
|
581
|
+
* Sends given candidate in Jingle 'transport-info' message.
|
|
582
|
+
*
|
|
583
|
+
* @param {RTCIceCandidate} candidate the WebRTC ICE candidate instance
|
|
584
|
+
* @returns {void}
|
|
585
|
+
*/
|
|
586
|
+
_sendIceCandidate(candidate) {
|
|
587
|
+
const localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
|
588
|
+
if (candidate?.candidate.length && !this.lasticecandidate) {
|
|
589
|
+
const ice = SDPUtil.iceparams(localSDP.media[candidate.sdpMLineIndex], localSDP.session);
|
|
590
|
+
const jcand = SDPUtil.candidateToJingle(candidate.candidate);
|
|
591
|
+
if (!(ice && jcand)) {
|
|
592
|
+
logger.error('failed to get ice && jcand');
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
ice.xmlns = XEP.ICE_UDP_TRANSPORT;
|
|
596
|
+
if (this.usedrip) {
|
|
597
|
+
if (this.dripContainer.length === 0) {
|
|
598
|
+
setTimeout(() => {
|
|
599
|
+
if (this.dripContainer.length === 0) {
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
this._sendIceCandidates(this.dripContainer);
|
|
603
|
+
this.dripContainer = [];
|
|
604
|
+
}, ICE_CAND_GATHERING_TIMEOUT);
|
|
605
|
+
}
|
|
606
|
+
this.dripContainer.push(candidate);
|
|
607
|
+
}
|
|
608
|
+
else {
|
|
609
|
+
this._sendIceCandidates([candidate]);
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
else {
|
|
613
|
+
logger.debug(`${this} _sendIceCandidate: last candidate`);
|
|
614
|
+
// FIXME: remember to re-think in ICE-restart
|
|
615
|
+
this.lasticecandidate = true;
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* Sends given candidates in Jingle 'transport-info' message.
|
|
620
|
+
*
|
|
621
|
+
* @param {Array<RTCIceCandidate>} candidates an array of the WebRTC ICE candidate instances.
|
|
622
|
+
* @returns {void}
|
|
623
|
+
*/
|
|
624
|
+
_sendIceCandidates(candidates) {
|
|
625
|
+
if (!this._assertNotEnded()) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
logger.debug(`${this} _sendIceCandidates count: ${candidates?.length}`);
|
|
629
|
+
const cand = $iq({ to: this.remoteJid,
|
|
630
|
+
type: 'set' })
|
|
631
|
+
.c('jingle', { action: 'transport-info',
|
|
632
|
+
initiator: this.initiatorJid,
|
|
633
|
+
sid: this.sid,
|
|
634
|
+
xmlns: 'urn:xmpp:jingle:1' });
|
|
635
|
+
const localSDP = new SDP(this.peerconnection.localDescription.sdp);
|
|
636
|
+
for (let mid = 0; mid < localSDP.media.length; mid++) {
|
|
637
|
+
const cands = candidates.filter(el => el.sdpMLineIndex === mid);
|
|
638
|
+
const mline = SDPUtil.parseMLine(localSDP.media[mid].split('\r\n')[0]);
|
|
639
|
+
if (cands.length > 0) {
|
|
640
|
+
const ice = SDPUtil.iceparams(localSDP.media[mid], localSDP.session);
|
|
641
|
+
ice.xmlns = XEP.ICE_UDP_TRANSPORT;
|
|
642
|
+
cand.c('content', {
|
|
643
|
+
creator: this.initiatorJid === this.localJid
|
|
644
|
+
? 'initiator' : 'responder',
|
|
645
|
+
name: cands[0].sdpMid ? cands[0].sdpMid : mline.media
|
|
646
|
+
}).c('transport', ice);
|
|
647
|
+
for (let i = 0; i < cands.length; i++) {
|
|
648
|
+
const candidate = SDPUtil.candidateToJingle(cands[i].candidate);
|
|
649
|
+
// Mangle ICE candidate if 'failICE' test option is enabled
|
|
650
|
+
if (this.failICE) {
|
|
651
|
+
candidate.ip = '1.1.1.1';
|
|
652
|
+
}
|
|
653
|
+
cand.c('candidate', candidate).up();
|
|
654
|
+
}
|
|
655
|
+
// add fingerprint
|
|
656
|
+
const fingerprintLine = SDPUtil.findLine(localSDP.media[mid], 'a=fingerprint:', localSDP.session);
|
|
657
|
+
if (fingerprintLine) {
|
|
658
|
+
const tmp = SDPUtil.parseFingerprint(fingerprintLine);
|
|
659
|
+
tmp.required = true;
|
|
660
|
+
cand.c('fingerprint', { xmlns: 'urn:xmpp:jingle:apps:dtls:0' })
|
|
661
|
+
.t(tmp.fingerprint);
|
|
662
|
+
delete tmp.fingerprint;
|
|
663
|
+
cand.attrs(tmp);
|
|
664
|
+
cand.up();
|
|
665
|
+
}
|
|
666
|
+
cand.up(); // transport
|
|
667
|
+
cand.up(); // content
|
|
668
|
+
}
|
|
669
|
+
}
|
|
670
|
+
// might merge last-candidate notification into this, but it is called
|
|
671
|
+
// a lot later. See webrtc issue #2340
|
|
672
|
+
// logger.debug('was this the last candidate', this.lasticecandidate);
|
|
673
|
+
this.connection.sendIQ(cand, null, this.newJingleErrorHandler(), IQ_TIMEOUT);
|
|
674
|
+
}
|
|
675
|
+
/**
|
|
676
|
+
* Sends Jingle 'session-accept' message.
|
|
677
|
+
*
|
|
678
|
+
* @param {function()} success callback called when we receive 'RESULT' packet for the 'session-accept'.
|
|
679
|
+
* @param {function(error)} failure called when we receive an error response or when the request has timed out.
|
|
680
|
+
* @returns {void}
|
|
681
|
+
*/
|
|
682
|
+
_sendSessionAccept(success, failure) {
|
|
683
|
+
// NOTE: since we're just reading from it, we don't need to be within
|
|
684
|
+
// the modification queue to access the local description
|
|
685
|
+
const localSDP = new SDP(this.peerconnection.localDescription.sdp, this.isP2P);
|
|
686
|
+
const accept = $iq({ to: this.remoteJid,
|
|
687
|
+
type: 'set' })
|
|
688
|
+
.c('jingle', {
|
|
689
|
+
action: 'session-accept',
|
|
690
|
+
initiator: this.initiatorJid,
|
|
691
|
+
responder: this.responderJid,
|
|
692
|
+
sid: this.sid,
|
|
693
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
694
|
+
});
|
|
695
|
+
if (this.webrtcIceTcpDisable) {
|
|
696
|
+
localSDP.removeTcpCandidates = true;
|
|
697
|
+
}
|
|
698
|
+
if (this.webrtcIceUdpDisable) {
|
|
699
|
+
localSDP.removeUdpCandidates = true;
|
|
700
|
+
}
|
|
701
|
+
if (this.failICE) {
|
|
702
|
+
localSDP.failICE = true;
|
|
703
|
+
}
|
|
704
|
+
if (typeof this.options.channelLastN === 'number' && this.options.channelLastN >= 0) {
|
|
705
|
+
// @ts-ignore will be fixed after merge of sdp
|
|
706
|
+
localSDP.initialLastN = this.options.channelLastN;
|
|
707
|
+
}
|
|
708
|
+
localSDP.toJingle(accept, this.initiatorJid === this.localJid ? 'initiator' : 'responder');
|
|
709
|
+
logger.info(`${this} Sending session-accept`);
|
|
710
|
+
logger.debug(accept.tree());
|
|
711
|
+
this.connection.sendIQ(accept, success, this.newJingleErrorHandler(error => {
|
|
712
|
+
failure(error);
|
|
713
|
+
// 'session-accept' is a critical timeout and we'll
|
|
714
|
+
// have to restart
|
|
715
|
+
this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT_TIMEOUT, this);
|
|
716
|
+
}), IQ_TIMEOUT);
|
|
717
|
+
// XXX Videobridge needs WebRTC's answer (ICE ufrag and pwd, DTLS
|
|
718
|
+
// fingerprint and setup) ASAP in order to start the connection
|
|
719
|
+
// establishment.
|
|
720
|
+
//
|
|
721
|
+
// FIXME Flushing the connection at this point triggers an issue with
|
|
722
|
+
// BOSH request handling in Prosody on slow connections.
|
|
723
|
+
//
|
|
724
|
+
// The problem is that this request will be quite large and it may take
|
|
725
|
+
// time before it reaches Prosody. In the meantime Strophe may decide
|
|
726
|
+
// to send the next one. And it was observed that a small request with
|
|
727
|
+
// 'transport-info' usually follows this one. It does reach Prosody
|
|
728
|
+
// before the previous one was completely received. 'rid' on the server
|
|
729
|
+
// is increased and Prosody ignores the request with 'session-accept'.
|
|
730
|
+
// It will never reach Jicofo and everything in the request table is
|
|
731
|
+
// lost. Removing the flush does not guarantee it will never happen, but
|
|
732
|
+
// makes it much less likely('transport-info' is bundled with
|
|
733
|
+
// 'session-accept' and any immediate requests).
|
|
734
|
+
//
|
|
735
|
+
// this.connection.flush();
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Sends 'session-initiate' to the remote peer.
|
|
739
|
+
*
|
|
740
|
+
* NOTE this method is synchronous and we're not waiting for the RESULT
|
|
741
|
+
* response which would delay the startup process.
|
|
742
|
+
*
|
|
743
|
+
* @param {string} offerSdp - The local session description which will be used to generate an offer.
|
|
744
|
+
* @returns {void}
|
|
745
|
+
*/
|
|
746
|
+
_sendSessionInitiate(offerSdp) {
|
|
747
|
+
let init = $iq({
|
|
748
|
+
to: this.remoteJid,
|
|
749
|
+
type: 'set'
|
|
750
|
+
}).c('jingle', {
|
|
751
|
+
action: 'session-initiate',
|
|
752
|
+
initiator: this.initiatorJid,
|
|
753
|
+
sid: this.sid,
|
|
754
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
755
|
+
});
|
|
756
|
+
new SDP(offerSdp, this.isP2P).toJingle(init, this.isInitiator ? 'initiator' : 'responder');
|
|
757
|
+
init = init.tree();
|
|
758
|
+
logger.debug(`${this} Session-initiate: `, init);
|
|
759
|
+
this.connection.sendIQ(init, () => {
|
|
760
|
+
logger.info(`${this} Got RESULT for "session-initiate"`);
|
|
761
|
+
}, error => {
|
|
762
|
+
handleStropheError(error, {
|
|
763
|
+
isP2P: this.isP2P,
|
|
764
|
+
operation: 'session-initiate',
|
|
765
|
+
remoteJid: this.remoteJid,
|
|
766
|
+
roomJid: this.room?.roomjid,
|
|
767
|
+
session: this.toString(),
|
|
768
|
+
sid: this.sid,
|
|
769
|
+
userJid: this.connection.jid
|
|
770
|
+
});
|
|
771
|
+
}, IQ_TIMEOUT);
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Method returns function(errorResponse) which is a callback to be passed to Strophe connection.sendIQ method. An
|
|
775
|
+
* 'error' structure is created that is passed as 1st argument to given <tt>failureCb</tt>. The format of this
|
|
776
|
+
* structure is as follows:
|
|
777
|
+
* {
|
|
778
|
+
* code: {XMPP error response code}
|
|
779
|
+
* reason: {the name of XMPP error reason element or 'timeout' if the
|
|
780
|
+
* request has timed out within <tt>IQ_TIMEOUT</tt> milliseconds}
|
|
781
|
+
* source: {request.tree() that provides original request}
|
|
782
|
+
* session: {this JingleSessionPC.toString()}
|
|
783
|
+
* }
|
|
784
|
+
* @param failureCb function(error) called when error response was returned or when a timeout has occurred.
|
|
785
|
+
* @returns {function(this:JingleSessionPC)}
|
|
786
|
+
*/
|
|
787
|
+
newJingleErrorHandler(failureCb) {
|
|
788
|
+
return errResponse => {
|
|
789
|
+
const error = {
|
|
790
|
+
code: undefined,
|
|
791
|
+
msg: undefined,
|
|
792
|
+
reason: undefined,
|
|
793
|
+
session: undefined
|
|
794
|
+
};
|
|
795
|
+
if (errResponse instanceof Element) {
|
|
796
|
+
// Get XMPP error code and condition(reason)
|
|
797
|
+
const errorElSel = findFirst(errResponse, 'error');
|
|
798
|
+
if (errorElSel) {
|
|
799
|
+
error.code = getAttribute(errorElSel, 'code');
|
|
800
|
+
const errorResponseChildren = errorElSel.children;
|
|
801
|
+
if (errorResponseChildren.length) {
|
|
802
|
+
error.reason = errorResponseChildren[0].tagName;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (!errResponse) {
|
|
807
|
+
error.reason = 'timeout';
|
|
808
|
+
}
|
|
809
|
+
// When remote peer decides to terminate the session, but it still has few messages on the queue for
|
|
810
|
+
// processing, it will first send us 'session-terminate' (we enter ENDED) and then follow with
|
|
811
|
+
// 'item-not-found' for the queued requests. These 'item-not-found' errors can be ignored.
|
|
812
|
+
const ignoreErrors = this.state === JingleSessionState.ENDED && error?.reason === 'item-not-found';
|
|
813
|
+
if (!ignoreErrors) {
|
|
814
|
+
failureCb?.(error);
|
|
815
|
+
// Call handleStropheError for centralized error logging and analytics.
|
|
816
|
+
handleStropheError(errResponse, {
|
|
817
|
+
isP2P: this.isP2P,
|
|
818
|
+
operation: 'Jingle IQ',
|
|
819
|
+
remoteJid: this.remoteJid,
|
|
820
|
+
roomJid: this.room?.roomjid,
|
|
821
|
+
session: this.toString(),
|
|
822
|
+
sid: this.sid,
|
|
823
|
+
state: this.state,
|
|
824
|
+
userJid: this.connection.jid
|
|
825
|
+
});
|
|
826
|
+
}
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
/**
|
|
830
|
+
* Figures out added/removed ssrcs and sends updated IQs to the remote peer or Jicofo.
|
|
831
|
+
*
|
|
832
|
+
* @param oldSDP SDP object for old description.
|
|
833
|
+
* @param newSDP SDP object for new description.
|
|
834
|
+
* @returns {void}
|
|
835
|
+
*/
|
|
836
|
+
notifyMySSRCUpdate(oldSDP, newSDP) {
|
|
837
|
+
if (this.state !== JingleSessionState.ACTIVE) {
|
|
838
|
+
logger.warn(`${this} Skipping SSRC update in '${this.state} ' state.`);
|
|
839
|
+
return;
|
|
840
|
+
}
|
|
841
|
+
if (!this.connection.connected) {
|
|
842
|
+
// The goal is to compare the oldest SDP with the latest one upon reconnect
|
|
843
|
+
if (!this._cachedOldLocalSdp) {
|
|
844
|
+
this._cachedOldLocalSdp = oldSDP;
|
|
845
|
+
}
|
|
846
|
+
this._cachedNewLocalSdp = newSDP;
|
|
847
|
+
logger.warn(`${this} Not sending SSRC update while the signaling is disconnected`);
|
|
848
|
+
return;
|
|
849
|
+
}
|
|
850
|
+
this._cachedOldLocalSdp = undefined;
|
|
851
|
+
this._cachedNewLocalSdp = undefined;
|
|
852
|
+
const getSignaledSourceInfo = sdpDiffer => {
|
|
853
|
+
const newMedia = sdpDiffer.getNewMedia();
|
|
854
|
+
let ssrcs = [];
|
|
855
|
+
let mediaType = null;
|
|
856
|
+
// It is assumed that sources are signaled one at a time.
|
|
857
|
+
Object.keys(newMedia).forEach(mediaIndex => {
|
|
858
|
+
const signaledSsrcs = Object.keys(newMedia[mediaIndex].ssrcs);
|
|
859
|
+
mediaType = newMedia[mediaIndex].mediaType;
|
|
860
|
+
if (signaledSsrcs?.length) {
|
|
861
|
+
ssrcs = ssrcs.concat(signaledSsrcs);
|
|
862
|
+
}
|
|
863
|
+
});
|
|
864
|
+
return {
|
|
865
|
+
mediaType,
|
|
866
|
+
ssrcs
|
|
867
|
+
};
|
|
868
|
+
};
|
|
869
|
+
// send source-remove IQ.
|
|
870
|
+
let sdpDiffer = new SDPDiffer(newSDP, oldSDP, this.isP2P);
|
|
871
|
+
const remove = $iq({ to: this.remoteJid,
|
|
872
|
+
type: 'set' })
|
|
873
|
+
.c('jingle', {
|
|
874
|
+
action: 'source-remove',
|
|
875
|
+
initiator: this.initiatorJid,
|
|
876
|
+
sid: this.sid,
|
|
877
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
878
|
+
});
|
|
879
|
+
sdpDiffer.toJingle(remove);
|
|
880
|
+
// context a common object for one run of ssrc update (source-add and source-remove) so we can match them if we
|
|
881
|
+
// need to
|
|
882
|
+
const ctx = {};
|
|
883
|
+
const removedSsrcInfo = getSignaledSourceInfo(sdpDiffer);
|
|
884
|
+
if (removedSsrcInfo.ssrcs.length) {
|
|
885
|
+
// Log only the SSRCs instead of the full IQ.
|
|
886
|
+
logger.info(`${this} Sending source-remove for ${removedSsrcInfo.mediaType}`
|
|
887
|
+
+ ` ssrcs=${removedSsrcInfo.ssrcs}`);
|
|
888
|
+
this.connection.sendIQ(remove, () => {
|
|
889
|
+
this.room.eventEmitter.emit(XMPPEvents.SOURCE_REMOVE, this, ctx);
|
|
890
|
+
}, this.newJingleErrorHandler(error => {
|
|
891
|
+
this.room.eventEmitter.emit(XMPPEvents.SOURCE_REMOVE_ERROR, this, error, ctx);
|
|
892
|
+
}), IQ_TIMEOUT);
|
|
893
|
+
}
|
|
894
|
+
// send source-add IQ.
|
|
895
|
+
sdpDiffer = new SDPDiffer(oldSDP, newSDP, this.isP2P);
|
|
896
|
+
const add = $iq({ to: this.remoteJid,
|
|
897
|
+
type: 'set' })
|
|
898
|
+
.c('jingle', {
|
|
899
|
+
action: 'source-add',
|
|
900
|
+
initiator: this.initiatorJid,
|
|
901
|
+
sid: this.sid,
|
|
902
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
903
|
+
});
|
|
904
|
+
sdpDiffer.toJingle(add);
|
|
905
|
+
const addedSsrcInfo = getSignaledSourceInfo(sdpDiffer);
|
|
906
|
+
if (addedSsrcInfo.ssrcs.length) {
|
|
907
|
+
// Log only the SSRCs instead of the full IQ.
|
|
908
|
+
logger.info(`${this} Sending source-add for ${addedSsrcInfo.mediaType} ssrcs=${addedSsrcInfo.ssrcs}`);
|
|
909
|
+
this.connection.sendIQ(add, () => {
|
|
910
|
+
this.room.eventEmitter.emit(XMPPEvents.SOURCE_ADD, this, ctx);
|
|
911
|
+
}, this.newJingleErrorHandler(error => {
|
|
912
|
+
this.room.eventEmitter.emit(XMPPEvents.SOURCE_ADD_ERROR, this, error, addedSsrcInfo.mediaType, ctx);
|
|
913
|
+
}), IQ_TIMEOUT);
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
/**
|
|
917
|
+
* Handles XMPP connection state changes. Resends any session updates that were cached while the XMPP connection
|
|
918
|
+
* was down.
|
|
919
|
+
*
|
|
920
|
+
* @param {Strophe.Status} status - The new status.
|
|
921
|
+
* @returns {void}
|
|
922
|
+
*/
|
|
923
|
+
onXmppStatusChanged(status) {
|
|
924
|
+
if (status === XmppConnection.Status.CONNECTED && this._cachedOldLocalSdp) {
|
|
925
|
+
logger.info(`${this} Sending SSRC update on reconnect`);
|
|
926
|
+
this.notifyMySSRCUpdate(this._cachedOldLocalSdp, this._cachedNewLocalSdp);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
/**
|
|
930
|
+
* This is a setRemoteDescription/setLocalDescription cycle which starts at converting Strophe Jingle IQ into
|
|
931
|
+
* remote offer SDP. Once converted, setRemoteDescription, createAnswer and setLocalDescription calls follow.
|
|
932
|
+
*
|
|
933
|
+
* @param jingleOfferAnswerIq element pointing to the jingle element of the offer (or answer) IQ
|
|
934
|
+
* @param success callback called when sRD/sLD cycle finishes successfully.
|
|
935
|
+
* @param failure callback called with an error object as an argument if we fail at any point during setRD,
|
|
936
|
+
* createAnswer, setLD.
|
|
937
|
+
* @param {Array<JitsiLocalTrack>} [localTracks] the optional list of the local tracks that will be added, before
|
|
938
|
+
* the offer/answer cycle executes (for the local track addition to be an atomic operation together with the
|
|
939
|
+
* offer/answer).
|
|
940
|
+
* @returns {void}
|
|
941
|
+
*/
|
|
942
|
+
setOfferAnswerCycle(jingleOfferAnswerIq, success, failure, localTracks = []) {
|
|
943
|
+
logger.debug(`${this} Executing setOfferAnswerCycle task`);
|
|
944
|
+
const addTracks = [];
|
|
945
|
+
const audioTracks = localTracks.filter(track => track.getType() === MediaType.AUDIO);
|
|
946
|
+
const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);
|
|
947
|
+
let tracks = localTracks;
|
|
948
|
+
// Add only 1 video track at a time. Adding 2 or more video tracks to the peerconnection at the same time
|
|
949
|
+
// makes the browser go into a renegotiation loop by firing 'negotiationneeded' event after every
|
|
950
|
+
// renegotiation.
|
|
951
|
+
if (videoTracks.length > 1) {
|
|
952
|
+
tracks = [...audioTracks, videoTracks[0]];
|
|
953
|
+
}
|
|
954
|
+
for (const track of tracks) {
|
|
955
|
+
addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));
|
|
956
|
+
}
|
|
957
|
+
const newRemoteSdp = this._processNewJingleOfferIq(jingleOfferAnswerIq);
|
|
958
|
+
const bridgeSession = findFirst(jingleOfferAnswerIq, ':scope>bridge-session[*|xmlns="http://jitsi.org/protocol/focus"]');
|
|
959
|
+
const bridgeSessionId = getAttribute(bridgeSession, 'id');
|
|
960
|
+
if (bridgeSessionId !== this._bridgeSessionId) {
|
|
961
|
+
this._bridgeSessionId = bridgeSessionId;
|
|
962
|
+
}
|
|
963
|
+
Promise.all(addTracks)
|
|
964
|
+
.then(() => this._renegotiate(newRemoteSdp.raw))
|
|
965
|
+
.then(() => {
|
|
966
|
+
this.peerconnection.processLocalSdpForTransceiverInfo(tracks);
|
|
967
|
+
if (this.state === JingleSessionState.PENDING) {
|
|
968
|
+
this.state = JingleSessionState.ACTIVE;
|
|
969
|
+
// #1 Sync up video transfer active/inactive only after the initial O/A cycle. We want to
|
|
970
|
+
// adjust the video media direction only in the local SDP and the Jingle contents direction
|
|
971
|
+
// included in the initial offer/answer is mapped to the remote SDP. Jingle 'content-modify'
|
|
972
|
+
// IQ is processed in a way that it will only modify local SDP when remote peer is no longer
|
|
973
|
+
// interested in receiving video content. Changing media direction in the remote SDP will mess
|
|
974
|
+
// up our SDP translation chain (simulcast, video mute, RTX etc.)
|
|
975
|
+
// #2 Sends the max frame height if it was set, before the session-initiate/accept
|
|
976
|
+
if (this.isP2P && (!this._localSendReceiveVideoActive || this._sourceReceiverConstraints)) {
|
|
977
|
+
this._sendContentModify();
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
})
|
|
981
|
+
.then(() => {
|
|
982
|
+
logger.debug(`${this} setOfferAnswerCycle task done`);
|
|
983
|
+
success();
|
|
984
|
+
})
|
|
985
|
+
.catch(error => {
|
|
986
|
+
logger.error(`${this} setOfferAnswerCycle task failed: ${error}`);
|
|
987
|
+
failure(error);
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Accepts incoming Jingle 'session-initiate' and should send 'session-accept' in result.
|
|
992
|
+
*
|
|
993
|
+
* @param jingleOffer element pointing to the jingle element of the offer IQ
|
|
994
|
+
* @param success callback called when we accept incoming session successfully and receive RESULT packet to
|
|
995
|
+
* 'session-accept' sent.
|
|
996
|
+
* @param failure function(error) called if for any reason we fail to accept the incoming offer. 'error' argument
|
|
997
|
+
* can be used to log some details about the error.
|
|
998
|
+
* @param {Array<JitsiLocalTrack>} [localTracks] the optional list of the local tracks that will be added, before
|
|
999
|
+
* the offer/answer cycle executes. We allow the localTracks to optionally be passed in so that the addition of the
|
|
1000
|
+
* local tracks and the processing of the initial offer can all be done atomically. We want to make sure that any
|
|
1001
|
+
* other operations which originate in the XMPP Jingle messages related with this session to be executed with an
|
|
1002
|
+
* assumption that the initial offer/answer cycle has been executed already.
|
|
1003
|
+
*/
|
|
1004
|
+
acceptOffer(jingleOffer, success, failure, localTracks = []) {
|
|
1005
|
+
this.setOfferAnswerCycle(jingleOffer, () => {
|
|
1006
|
+
// FIXME we may not care about RESULT packet for session-accept
|
|
1007
|
+
// then we should either call 'success' here immediately or
|
|
1008
|
+
// modify sendSessionAccept method to do that
|
|
1009
|
+
this._sendSessionAccept(() => {
|
|
1010
|
+
// Start processing tasks on the modification queue.
|
|
1011
|
+
logger.debug(`${this} Resuming the modification queue after session is established!`);
|
|
1012
|
+
this.modificationQueue.resume();
|
|
1013
|
+
success();
|
|
1014
|
+
this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT, this);
|
|
1015
|
+
// The first video track is added to the peerconnection and signaled as part of the session-accept.
|
|
1016
|
+
// Add secondary video tracks (that were already added to conference) to the peerconnection here.
|
|
1017
|
+
// This will happen when someone shares a secondary source to a two people call, the other user
|
|
1018
|
+
// leaves and joins the call again, a new peerconnection is created for p2p/jvb connection. At this
|
|
1019
|
+
// point, there are 2 video tracks which need to be signaled to the remote peer.
|
|
1020
|
+
const videoTracks = localTracks.filter(track => track.getType() === MediaType.VIDEO);
|
|
1021
|
+
videoTracks.length && videoTracks.splice(0, 1);
|
|
1022
|
+
videoTracks.length && this.addTracks(videoTracks);
|
|
1023
|
+
}, error => {
|
|
1024
|
+
failure(error);
|
|
1025
|
+
this.room.eventEmitter.emit(XMPPEvents.SESSION_ACCEPT_ERROR, this, error);
|
|
1026
|
+
});
|
|
1027
|
+
}, failure, localTracks);
|
|
1028
|
+
}
|
|
1029
|
+
/**
|
|
1030
|
+
* {@inheritDoc}
|
|
1031
|
+
*/
|
|
1032
|
+
addIceCandidates(elem) {
|
|
1033
|
+
if (this.peerconnection.signalingState === 'closed') {
|
|
1034
|
+
logger.warn(`${this} Ignored add ICE candidate when in closed state`);
|
|
1035
|
+
return;
|
|
1036
|
+
}
|
|
1037
|
+
const iceCandidates = [];
|
|
1038
|
+
findAll(elem, ':scope>content>transport>candidate')
|
|
1039
|
+
.forEach(candidate => {
|
|
1040
|
+
let line = SDPUtil.candidateFromJingle(candidate);
|
|
1041
|
+
line = line.replace('\r\n', '').replace('a=', '');
|
|
1042
|
+
// FIXME this code does not care to handle
|
|
1043
|
+
// non-bundle transport
|
|
1044
|
+
const rtcCandidate = new RTCIceCandidate({
|
|
1045
|
+
candidate: line,
|
|
1046
|
+
sdpMLineIndex: 0,
|
|
1047
|
+
// FF comes up with more complex names like audio-23423,
|
|
1048
|
+
// Given that it works on both Chrome and FF without
|
|
1049
|
+
// providing it, let's leave it like this for the time
|
|
1050
|
+
// being...
|
|
1051
|
+
// sdpMid: 'audio',
|
|
1052
|
+
sdpMid: ''
|
|
1053
|
+
});
|
|
1054
|
+
iceCandidates.push(rtcCandidate);
|
|
1055
|
+
});
|
|
1056
|
+
if (!iceCandidates.length) {
|
|
1057
|
+
logger.error(`${this} No ICE candidates to add ?`, elem[0]?.outerHTML);
|
|
1058
|
+
return;
|
|
1059
|
+
}
|
|
1060
|
+
// We want to have this task queued, so that we know it is executed,
|
|
1061
|
+
// after the initial sRD/sLD offer/answer cycle was done (based on
|
|
1062
|
+
// the assumption that candidates are spawned after the offer/answer
|
|
1063
|
+
// and XMPP preserves order).
|
|
1064
|
+
const workFunction = finishedCallback => {
|
|
1065
|
+
for (const iceCandidate of iceCandidates) {
|
|
1066
|
+
this.peerconnection.addIceCandidate(iceCandidate)
|
|
1067
|
+
.then(() => logger.debug(`${this} addIceCandidate ok!`), err => logger.error(`${this} addIceCandidate failed!`, err));
|
|
1068
|
+
}
|
|
1069
|
+
finishedCallback();
|
|
1070
|
+
logger.debug(`${this} ICE candidates task finished`);
|
|
1071
|
+
};
|
|
1072
|
+
logger.debug(`${this} Queued add (${iceCandidates.length}) ICE candidates task`);
|
|
1073
|
+
this.modificationQueue.push(workFunction);
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Handles a Jingle source-add message for this Jingle session.
|
|
1077
|
+
*
|
|
1078
|
+
* @param {Array<Element>} elem an array of Jingle "content" elements.
|
|
1079
|
+
* @returns {Promise} resolved when the operation is done or rejected with an error.
|
|
1080
|
+
*/
|
|
1081
|
+
addRemoteStream(elem) {
|
|
1082
|
+
this._addOrRemoveRemoteStream(true /* add */, elem);
|
|
1083
|
+
}
|
|
1084
|
+
/**
|
|
1085
|
+
* Adds a new track to the peerconnection. This method needs to be called only when a secondary JitsiLocalTrack is
|
|
1086
|
+
* being added to the peerconnection for the first time.
|
|
1087
|
+
*
|
|
1088
|
+
* @param {Array<JitsiLocalTrack>} localTracks - Tracks to be added to the peer connection.
|
|
1089
|
+
* @returns {Promise<void>} that resolves when the track is successfully added to the peerconnection, rejected
|
|
1090
|
+
* otherwise.
|
|
1091
|
+
*/
|
|
1092
|
+
addTracks(localTracks = null) {
|
|
1093
|
+
if (!localTracks?.length) {
|
|
1094
|
+
Promise.reject(new Error('No tracks passed'));
|
|
1095
|
+
}
|
|
1096
|
+
const replaceTracks = [];
|
|
1097
|
+
const workFunction = finishedCallback => {
|
|
1098
|
+
const remoteSdp = new SDP(this.peerconnection.remoteDescription.sdp, this.isP2P);
|
|
1099
|
+
const recvOnlyTransceiver = this.peerconnection.peerconnection.getTransceivers()
|
|
1100
|
+
.find(t => t.receiver.track.kind === MediaType.VIDEO
|
|
1101
|
+
&& t.direction === MediaDirection.RECVONLY
|
|
1102
|
+
&& t.currentDirection === MediaDirection.RECVONLY);
|
|
1103
|
+
// Add transceivers by adding a new mline in the remote description for each track. Do not create a new
|
|
1104
|
+
// m-line if a recv-only transceiver exists in the p2p case. The new track will be attached to the
|
|
1105
|
+
// existing one in that case.
|
|
1106
|
+
for (const track of localTracks) {
|
|
1107
|
+
if (!this.isP2P || !recvOnlyTransceiver) {
|
|
1108
|
+
remoteSdp.addMlineForNewSource(track.getType());
|
|
1109
|
+
}
|
|
1110
|
+
}
|
|
1111
|
+
this._renegotiate(remoteSdp.raw)
|
|
1112
|
+
.then(() => {
|
|
1113
|
+
// Replace the tracks on the newly generated transceivers.
|
|
1114
|
+
for (const track of localTracks) {
|
|
1115
|
+
replaceTracks.push(this.peerconnection.replaceTrack(null, track));
|
|
1116
|
+
}
|
|
1117
|
+
return Promise.all(replaceTracks);
|
|
1118
|
+
})
|
|
1119
|
+
// Trigger a renegotiation here since renegotiations are suppressed at TPC.replaceTrack for screenshare
|
|
1120
|
+
// tracks. This is done here so that presence for screenshare tracks is sent before signaling.
|
|
1121
|
+
.then(() => this._renegotiate())
|
|
1122
|
+
.then(() => finishedCallback(), error => finishedCallback(error));
|
|
1123
|
+
};
|
|
1124
|
+
return new Promise((resolve, reject) => {
|
|
1125
|
+
logger.debug(`${this} Queued renegotiation after addTrack`);
|
|
1126
|
+
this.modificationQueue.push(workFunction, error => {
|
|
1127
|
+
if (error) {
|
|
1128
|
+
if (error instanceof ClearedQueueError) {
|
|
1129
|
+
// The session might have been terminated before the task was executed, making it obsolete.
|
|
1130
|
+
logger.debug(`${this} renegotiation after addTrack aborted: session terminated`);
|
|
1131
|
+
resolve();
|
|
1132
|
+
return;
|
|
1133
|
+
}
|
|
1134
|
+
logger.error(`${this} renegotiation after addTrack error`, error);
|
|
1135
|
+
reject(error);
|
|
1136
|
+
}
|
|
1137
|
+
else {
|
|
1138
|
+
logger.debug(`${this} renegotiation after addTrack executed - OK`);
|
|
1139
|
+
resolve();
|
|
1140
|
+
}
|
|
1141
|
+
});
|
|
1142
|
+
});
|
|
1143
|
+
}
|
|
1144
|
+
/**
|
|
1145
|
+
* Adds local track back to the peerconnection associated with this session.
|
|
1146
|
+
*
|
|
1147
|
+
* @param {JitsiLocalTrack} track - the local track to be added back to the peerconnection.
|
|
1148
|
+
* @return {Promise} a promise that will resolve once the local track is added back to this session and
|
|
1149
|
+
* renegotiation succeeds (if its warranted). Will be rejected with a <tt>string</tt> that provides some error
|
|
1150
|
+
* details in case something goes wrong.
|
|
1151
|
+
* @returns {Promise<void>}
|
|
1152
|
+
*/
|
|
1153
|
+
addTrackToPc(track) {
|
|
1154
|
+
return this._addRemoveTrack(false /* add */, track)
|
|
1155
|
+
.then(async () => {
|
|
1156
|
+
// Configure the video encodings after the track is unmuted. If the user joins the call muted and
|
|
1157
|
+
// unmutes it the first time, all the parameters need to be configured.
|
|
1158
|
+
if (track.isVideoTrack()) {
|
|
1159
|
+
await this.peerconnection.configureVideoSenderEncodings(track);
|
|
1160
|
+
}
|
|
1161
|
+
});
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Closes the underlying peerconnection and shuts down the modification queue.
|
|
1165
|
+
*
|
|
1166
|
+
* @returns {void}
|
|
1167
|
+
*/
|
|
1168
|
+
close() {
|
|
1169
|
+
this.state = JingleSessionState.ENDED;
|
|
1170
|
+
this.establishmentDuration = undefined;
|
|
1171
|
+
if (this.peerconnection) {
|
|
1172
|
+
this.peerconnection.onicecandidate = null;
|
|
1173
|
+
this.peerconnection.oniceconnectionstatechange = null;
|
|
1174
|
+
this.peerconnection.onnegotiationneeded = null;
|
|
1175
|
+
this.peerconnection.onsignalingstatechange = null;
|
|
1176
|
+
}
|
|
1177
|
+
logger.debug(`${this} Clearing modificationQueue`);
|
|
1178
|
+
// Remove any pending tasks from the queue
|
|
1179
|
+
this.modificationQueue.clear();
|
|
1180
|
+
logger.debug(`${this} Queued PC close task`);
|
|
1181
|
+
this.modificationQueue.push(finishCallback => {
|
|
1182
|
+
// do not try to close if already closed.
|
|
1183
|
+
this.peerconnection && this.peerconnection.close();
|
|
1184
|
+
finishCallback();
|
|
1185
|
+
logger.debug(`${this} PC close task done!`);
|
|
1186
|
+
});
|
|
1187
|
+
logger.debug(`${this} Shutdown modificationQueue!`);
|
|
1188
|
+
// No more tasks can go in after the close task
|
|
1189
|
+
this.modificationQueue.shutdown();
|
|
1190
|
+
}
|
|
1191
|
+
/**
|
|
1192
|
+
* @inheritDoc
|
|
1193
|
+
* @param {JingleSessionPCOptions} options - a set of config options.
|
|
1194
|
+
* @returns {void}
|
|
1195
|
+
*/
|
|
1196
|
+
doInitialize(options) {
|
|
1197
|
+
this.failICE = Boolean(options.testing?.failICE);
|
|
1198
|
+
this.lasticecandidate = false;
|
|
1199
|
+
this.options = options;
|
|
1200
|
+
/**
|
|
1201
|
+
* {@code true} if reconnect is in progress.
|
|
1202
|
+
* @type {boolean}
|
|
1203
|
+
*/
|
|
1204
|
+
this.isReconnect = false;
|
|
1205
|
+
/**
|
|
1206
|
+
* Set to {@code true} if the connection was ever stable
|
|
1207
|
+
* @type {boolean}
|
|
1208
|
+
*/
|
|
1209
|
+
this.wasstable = false;
|
|
1210
|
+
this.webrtcIceUdpDisable = Boolean(options.webrtcIceUdpDisable);
|
|
1211
|
+
this.webrtcIceTcpDisable = Boolean(options.webrtcIceTcpDisable);
|
|
1212
|
+
const pcOptions = {
|
|
1213
|
+
audioQuality: options.audioQuality,
|
|
1214
|
+
capScreenshareBitrate: undefined,
|
|
1215
|
+
codecSettings: options.codecSettings,
|
|
1216
|
+
disableRtx: options.disableRtx,
|
|
1217
|
+
disableSimulcast: this.isP2P ? true : options.disableSimulcast,
|
|
1218
|
+
enableInsertableStreams: options.enableInsertableStreams,
|
|
1219
|
+
forceTurnRelay: options.forceTurnRelay,
|
|
1220
|
+
maxstats: undefined,
|
|
1221
|
+
startSilent: undefined,
|
|
1222
|
+
usesCodecSelectionAPI: undefined,
|
|
1223
|
+
videoQuality: undefined
|
|
1224
|
+
};
|
|
1225
|
+
if (options.gatherStats) {
|
|
1226
|
+
pcOptions.maxstats = DEFAULT_MAX_STATS;
|
|
1227
|
+
}
|
|
1228
|
+
pcOptions.usesCodecSelectionAPI = this.usesCodecSelectionAPI
|
|
1229
|
+
= browser.supportsCodecSelectionAPI()
|
|
1230
|
+
&& (options.testing?.enableCodecSelectionAPI ?? true)
|
|
1231
|
+
&& !this.isP2P;
|
|
1232
|
+
if (options.videoQuality) {
|
|
1233
|
+
const settings = Object.entries(options.videoQuality)
|
|
1234
|
+
.map(entry => {
|
|
1235
|
+
entry[0] = entry[0].toLowerCase();
|
|
1236
|
+
return entry;
|
|
1237
|
+
});
|
|
1238
|
+
pcOptions.videoQuality = Object.fromEntries(settings);
|
|
1239
|
+
}
|
|
1240
|
+
if (!this.isP2P) {
|
|
1241
|
+
// Do not send lower spatial layers for low fps screenshare and enable them only for high fps screenshare.
|
|
1242
|
+
pcOptions.capScreenshareBitrate = !(options.desktopSharingFrameRate?.max > SS_DEFAULT_FRAME_RATE);
|
|
1243
|
+
}
|
|
1244
|
+
if (options.startSilent) {
|
|
1245
|
+
pcOptions.startSilent = true;
|
|
1246
|
+
}
|
|
1247
|
+
this.peerconnection
|
|
1248
|
+
= this.rtc.createPeerConnection(this._signalingLayer, this.pcConfig, this.isP2P, pcOptions);
|
|
1249
|
+
this.peerconnection.onicecandidate = ev => {
|
|
1250
|
+
if (!ev) {
|
|
1251
|
+
// There was an incomplete check for ev before which left
|
|
1252
|
+
// the last line of the function unprotected from a potential
|
|
1253
|
+
// throw of an exception. Consequently, it may be argued that
|
|
1254
|
+
// the check is unnecessary. Anyway, I'm leaving it and making
|
|
1255
|
+
// the check complete.
|
|
1256
|
+
return;
|
|
1257
|
+
}
|
|
1258
|
+
// XXX this is broken, candidate is not parsed.
|
|
1259
|
+
const candidate = ev.candidate;
|
|
1260
|
+
const now = window.performance.now();
|
|
1261
|
+
if (candidate) {
|
|
1262
|
+
if (this._gatheringStartedTimestamp === null) {
|
|
1263
|
+
this._gatheringStartedTimestamp = now;
|
|
1264
|
+
}
|
|
1265
|
+
// Discard candidates of disabled protocols.
|
|
1266
|
+
let protocol = candidate.protocol;
|
|
1267
|
+
if (typeof protocol === 'string') {
|
|
1268
|
+
protocol = protocol.toLowerCase();
|
|
1269
|
+
if (protocol === 'tcp' || protocol === 'ssltcp') {
|
|
1270
|
+
if (this.webrtcIceTcpDisable) {
|
|
1271
|
+
return;
|
|
1272
|
+
}
|
|
1273
|
+
}
|
|
1274
|
+
else if (protocol === 'udp') {
|
|
1275
|
+
if (this.webrtcIceUdpDisable) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
}
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
else if (!this._gatheringReported) {
|
|
1282
|
+
// End of gathering
|
|
1283
|
+
Statistics.sendAnalytics(AnalyticsEvents.ICE_DURATION, {
|
|
1284
|
+
initiator: this.isInitiator,
|
|
1285
|
+
p2p: this.isP2P,
|
|
1286
|
+
phase: 'gathering',
|
|
1287
|
+
value: now - this._gatheringStartedTimestamp
|
|
1288
|
+
});
|
|
1289
|
+
this._gatheringReported = true;
|
|
1290
|
+
}
|
|
1291
|
+
if (this.isP2P) {
|
|
1292
|
+
this._sendIceCandidate(candidate);
|
|
1293
|
+
}
|
|
1294
|
+
};
|
|
1295
|
+
// Note there is a change in the spec about closed:
|
|
1296
|
+
// This value moved into the RTCPeerConnectionState enum in
|
|
1297
|
+
// the May 13, 2016 draft of the specification, as it reflects the state
|
|
1298
|
+
// of the RTCPeerConnection, not the signaling connection. You now
|
|
1299
|
+
// detect a closed connection by checking for connectionState to be
|
|
1300
|
+
// "closed" instead.
|
|
1301
|
+
// I suppose at some point this will be moved to onconnectionstatechange
|
|
1302
|
+
this.peerconnection.onsignalingstatechange = () => {
|
|
1303
|
+
if (this.peerconnection.signalingState === 'stable') {
|
|
1304
|
+
this.wasstable = true;
|
|
1305
|
+
}
|
|
1306
|
+
else if (this.peerconnection.signalingState === 'closed'
|
|
1307
|
+
|| this.peerconnection.connectionState === 'closed') {
|
|
1308
|
+
this.room.eventEmitter.emit(XMPPEvents.SUSPEND_DETECTED, this);
|
|
1309
|
+
}
|
|
1310
|
+
};
|
|
1311
|
+
/**
|
|
1312
|
+
* The oniceconnectionstatechange event handler contains the code to
|
|
1313
|
+
* execute when the iceconnectionstatechange event, of type Event,
|
|
1314
|
+
* is received by this RTCPeerConnection. Such an event is sent when
|
|
1315
|
+
* the value of RTCPeerConnection.iceConnectionState changes.
|
|
1316
|
+
*/
|
|
1317
|
+
this.peerconnection.oniceconnectionstatechange = () => {
|
|
1318
|
+
const now = window.performance.now();
|
|
1319
|
+
let isStable = false;
|
|
1320
|
+
if (!this.isP2P) {
|
|
1321
|
+
this.room.connectionTimes[`ice.state.${this.peerconnection.iceConnectionState}`]
|
|
1322
|
+
= now;
|
|
1323
|
+
}
|
|
1324
|
+
logger.info(`(TIME) ICE ${this.peerconnection.iceConnectionState} ${this.isP2P ? 'P2P' : 'JVB'}:\t`, now);
|
|
1325
|
+
Statistics.sendAnalytics(AnalyticsEvents.ICE_STATE_CHANGED, {
|
|
1326
|
+
p2p: this.isP2P,
|
|
1327
|
+
reconnect: this.isReconnect,
|
|
1328
|
+
'signaling_state': this.peerconnection.signalingState,
|
|
1329
|
+
state: this.peerconnection.iceConnectionState,
|
|
1330
|
+
value: now
|
|
1331
|
+
});
|
|
1332
|
+
this.room.eventEmitter.emit(XMPPEvents.ICE_CONNECTION_STATE_CHANGED, this, this.peerconnection.iceConnectionState);
|
|
1333
|
+
switch (this.peerconnection.iceConnectionState) {
|
|
1334
|
+
case 'checking':
|
|
1335
|
+
this._iceCheckingStartedTimestamp = now;
|
|
1336
|
+
break;
|
|
1337
|
+
case 'connected':
|
|
1338
|
+
case 'completed':
|
|
1339
|
+
// Informs interested parties that the connection has been restored. This includes the case when
|
|
1340
|
+
// media connection to the bridge has been restored after an ICE failure by using session-terminate.
|
|
1341
|
+
if (this.peerconnection.signalingState === 'stable') {
|
|
1342
|
+
isStable = true;
|
|
1343
|
+
this.room.eventEmitter.emit(XMPPEvents.CONNECTION_RESTORED, this);
|
|
1344
|
+
}
|
|
1345
|
+
// Add a workaround for an issue on chrome in Unified plan when the local endpoint is the offerer.
|
|
1346
|
+
// The 'signalingstatechange' event for 'stable' is handled after the 'iceconnectionstatechange' event
|
|
1347
|
+
// for 'completed' is handled by the client. This prevents the client from firing a
|
|
1348
|
+
// CONNECTION_ESTABLISHED event for the p2p session. As a result, the offerer continues to stay on the
|
|
1349
|
+
// jvb connection while the remote peer switches to the p2p connection breaking the media flow between
|
|
1350
|
+
// the endpoints.
|
|
1351
|
+
// TODO - file a chromium bug and add the information here.
|
|
1352
|
+
if (!this.wasConnected
|
|
1353
|
+
&& (this.wasstable
|
|
1354
|
+
|| isStable
|
|
1355
|
+
|| (this.isInitiator && (browser.isChromiumBased() || browser.isReactNative())))) {
|
|
1356
|
+
Statistics.sendAnalytics(AnalyticsEvents.ICE_DURATION, {
|
|
1357
|
+
initiator: this.isInitiator,
|
|
1358
|
+
p2p: this.isP2P,
|
|
1359
|
+
phase: 'checking',
|
|
1360
|
+
value: now - this._iceCheckingStartedTimestamp
|
|
1361
|
+
});
|
|
1362
|
+
// Switch between ICE gathering and ICE checking whichever
|
|
1363
|
+
// started first (scenarios are different for initiator
|
|
1364
|
+
// vs responder)
|
|
1365
|
+
const iceStarted = Math.min(this._iceCheckingStartedTimestamp, this._gatheringStartedTimestamp);
|
|
1366
|
+
this.establishmentDuration = now - iceStarted;
|
|
1367
|
+
Statistics.sendAnalytics(AnalyticsEvents.ICE_DURATION, {
|
|
1368
|
+
initiator: this.isInitiator,
|
|
1369
|
+
p2p: this.isP2P,
|
|
1370
|
+
phase: 'establishment',
|
|
1371
|
+
value: this.establishmentDuration
|
|
1372
|
+
});
|
|
1373
|
+
this.wasConnected = true;
|
|
1374
|
+
this.room.eventEmitter.emit(XMPPEvents.CONNECTION_ESTABLISHED, this);
|
|
1375
|
+
}
|
|
1376
|
+
this.isReconnect = false;
|
|
1377
|
+
break;
|
|
1378
|
+
case 'disconnected':
|
|
1379
|
+
this.isReconnect = true;
|
|
1380
|
+
// Informs interested parties that the connection has been
|
|
1381
|
+
// interrupted.
|
|
1382
|
+
if (this.wasstable) {
|
|
1383
|
+
this.room.eventEmitter.emit(XMPPEvents.CONNECTION_INTERRUPTED, this);
|
|
1384
|
+
}
|
|
1385
|
+
break;
|
|
1386
|
+
case 'failed':
|
|
1387
|
+
this.room.eventEmitter.emit(XMPPEvents.CONNECTION_ICE_FAILED, this);
|
|
1388
|
+
break;
|
|
1389
|
+
}
|
|
1390
|
+
};
|
|
1391
|
+
/**
|
|
1392
|
+
* The connection state event is fired whenever the aggregate of underlying
|
|
1393
|
+
* transports change their state.
|
|
1394
|
+
*/
|
|
1395
|
+
this.peerconnection.onconnectionstatechange = () => {
|
|
1396
|
+
const icestate = this.peerconnection.iceConnectionState;
|
|
1397
|
+
logger.info(`(TIME) ${this.isP2P ? 'P2P' : 'JVB'} PC state is now ${this.peerconnection.connectionState} `
|
|
1398
|
+
+ `(ICE state ${this.peerconnection.iceConnectionState}):\t`, window.performance.now());
|
|
1399
|
+
switch (this.peerconnection.connectionState) {
|
|
1400
|
+
case 'failed':
|
|
1401
|
+
// Since version 76 Chrome no longer switches ICE connection
|
|
1402
|
+
// state to failed (see
|
|
1403
|
+
// https://bugs.chromium.org/p/chromium/issues/detail?id=982793
|
|
1404
|
+
// for details) we use this workaround to recover from lost connections
|
|
1405
|
+
if (icestate === 'disconnected') {
|
|
1406
|
+
this.room.eventEmitter.emit(XMPPEvents.CONNECTION_ICE_FAILED, this);
|
|
1407
|
+
}
|
|
1408
|
+
break;
|
|
1409
|
+
}
|
|
1410
|
+
};
|
|
1411
|
+
/**
|
|
1412
|
+
* The negotiationneeded event is fired whenever we shake the media on the
|
|
1413
|
+
* RTCPeerConnection object.
|
|
1414
|
+
*/
|
|
1415
|
+
this.peerconnection.onnegotiationneeded = () => {
|
|
1416
|
+
const state = this.peerconnection.signalingState;
|
|
1417
|
+
const remoteDescription = this.peerconnection.remoteDescription;
|
|
1418
|
+
if (!this.isP2P
|
|
1419
|
+
&& state === 'stable'
|
|
1420
|
+
&& remoteDescription
|
|
1421
|
+
&& typeof remoteDescription.sdp === 'string') {
|
|
1422
|
+
logger.info(`${this} onnegotiationneeded fired on ${this.peerconnection}`);
|
|
1423
|
+
const workFunction = async (finishedCallback) => {
|
|
1424
|
+
try {
|
|
1425
|
+
await this._renegotiate();
|
|
1426
|
+
await this.peerconnection.configureAudioSenderEncodings();
|
|
1427
|
+
finishedCallback();
|
|
1428
|
+
}
|
|
1429
|
+
catch (error) {
|
|
1430
|
+
finishedCallback(error);
|
|
1431
|
+
}
|
|
1432
|
+
};
|
|
1433
|
+
this.modificationQueue.push(workFunction, error => {
|
|
1434
|
+
if (error) {
|
|
1435
|
+
logger.error(`${this} onnegotiationneeded error`, error);
|
|
1436
|
+
}
|
|
1437
|
+
else {
|
|
1438
|
+
logger.debug(`${this} onnegotiationneeded executed - OK`);
|
|
1439
|
+
}
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
};
|
|
1443
|
+
}
|
|
1444
|
+
/**
|
|
1445
|
+
* Returns the ice connection state for the peer connection.
|
|
1446
|
+
*
|
|
1447
|
+
* @returns the ice connection state for the peer connection.
|
|
1448
|
+
*/
|
|
1449
|
+
getIceConnectionState() {
|
|
1450
|
+
return this.peerconnection.getConnectionState();
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Returns the preference for max frame height for the remote video sources.
|
|
1454
|
+
*
|
|
1455
|
+
* @returns {Optional<$Map<string, number>>}
|
|
1456
|
+
*/
|
|
1457
|
+
getRemoteSourcesRecvMaxFrameHeight() {
|
|
1458
|
+
if (this.isP2P) {
|
|
1459
|
+
return this.remoteSourceMaxFrameHeights;
|
|
1460
|
+
}
|
|
1461
|
+
return undefined;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Creates an offer and sends Jingle 'session-initiate' to the remote peer.
|
|
1465
|
+
*
|
|
1466
|
+
* @param {Array<JitsiLocalTrack>} localTracks the local tracks that will be added, before the offer/answer cycle
|
|
1467
|
+
* executes (for the local track addition to be an atomic operation together with the offer/answer).
|
|
1468
|
+
* @returns {Promise<void>} that resolves when the offer is sent to the remote peer, rejected otherwise.
|
|
1469
|
+
*/
|
|
1470
|
+
async invite(localTracks = []) {
|
|
1471
|
+
if (!this.isInitiator) {
|
|
1472
|
+
throw new Error('Trying to invite from the responder session');
|
|
1473
|
+
}
|
|
1474
|
+
logger.debug(`${this} Executing invite task`);
|
|
1475
|
+
const addTracks = [];
|
|
1476
|
+
for (const track of localTracks) {
|
|
1477
|
+
addTracks.push(this.peerconnection.addTrack(track, this.isInitiator));
|
|
1478
|
+
}
|
|
1479
|
+
try {
|
|
1480
|
+
await Promise.all(addTracks);
|
|
1481
|
+
const offerSdp = await this.peerconnection.createOffer(this.mediaConstraints);
|
|
1482
|
+
await this.peerconnection.setLocalDescription(offerSdp);
|
|
1483
|
+
this.peerconnection.processLocalSdpForTransceiverInfo(localTracks);
|
|
1484
|
+
this._sendSessionInitiate(this.peerconnection.localDescription.sdp);
|
|
1485
|
+
logger.debug(`${this} invite executed - OK`);
|
|
1486
|
+
}
|
|
1487
|
+
catch (error) {
|
|
1488
|
+
logger.error(`${this} invite error`, error);
|
|
1489
|
+
throw error;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Enables/disables local video based on 'senders' attribute of the video conent in 'content-modify' IQ sent by the
|
|
1494
|
+
* remote peer. Also, checks if the sourceMaxFrameHeight (as requested by the p2p peer) or the senders attribute of
|
|
1495
|
+
* the video content has changed and modifies the local video resolution accordingly.
|
|
1496
|
+
*
|
|
1497
|
+
* @param {Element} jingleContents - The content of the 'content-modify' IQ sent by the remote peer.
|
|
1498
|
+
* @returns {void}
|
|
1499
|
+
*/
|
|
1500
|
+
modifyContents(jingleContents) {
|
|
1501
|
+
const newVideoSenders = JingleSessionPC.parseVideoSenders(jingleContents);
|
|
1502
|
+
const sourceMaxFrameHeights = JingleSessionPC.parseSourceMaxFrameHeight(jingleContents);
|
|
1503
|
+
if (sourceMaxFrameHeights) {
|
|
1504
|
+
this.remoteSourceMaxFrameHeights = sourceMaxFrameHeights;
|
|
1505
|
+
this.eventEmitter.emit(MediaSessionEvents.REMOTE_SOURCE_CONSTRAINTS_CHANGED, this, sourceMaxFrameHeights);
|
|
1506
|
+
}
|
|
1507
|
+
if (newVideoSenders === null) {
|
|
1508
|
+
logger.error(`${this} - failed to parse video "senders" attribute in "content-modify" action`);
|
|
1509
|
+
return;
|
|
1510
|
+
}
|
|
1511
|
+
if (!this._assertNotEnded()) {
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
const isRemoteVideoActive = newVideoSenders === 'both'
|
|
1515
|
+
|| (newVideoSenders === 'initiator' && this.isInitiator)
|
|
1516
|
+
|| (newVideoSenders === 'responder' && !this.isInitiator);
|
|
1517
|
+
if (isRemoteVideoActive !== this._remoteSendReceiveVideoActive) {
|
|
1518
|
+
logger.debug(`${this} new remote video active: ${isRemoteVideoActive}`);
|
|
1519
|
+
this._remoteSendReceiveVideoActive = isRemoteVideoActive;
|
|
1520
|
+
this.peerconnection
|
|
1521
|
+
.setVideoTransferActive(this._localSendReceiveVideoActive && this._remoteSendReceiveVideoActive);
|
|
1522
|
+
}
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Handles the termination of the session.
|
|
1526
|
+
*
|
|
1527
|
+
* @param {string} reasonCondition - The XMPP Jingle reason condition.
|
|
1528
|
+
* @param {string} reasonText - The XMPP Jingle reason text.
|
|
1529
|
+
* @returns {void}
|
|
1530
|
+
*/
|
|
1531
|
+
onTerminated(reasonCondition, reasonText) {
|
|
1532
|
+
// Do something with reason and reasonCondition when we start to care
|
|
1533
|
+
// this.reasonCondition = reasonCondition;
|
|
1534
|
+
// this.reasonText = reasonText;
|
|
1535
|
+
logger.info(`${this} Session terminated`, reasonCondition, reasonText);
|
|
1536
|
+
this._xmppListeners.forEach(removeListener => removeListener());
|
|
1537
|
+
this._xmppListeners = [];
|
|
1538
|
+
if (this._removeSenderVideoConstraintsChangeListener) {
|
|
1539
|
+
this._removeSenderVideoConstraintsChangeListener();
|
|
1540
|
+
}
|
|
1541
|
+
if (FeatureFlags.isSsrcRewritingSupported() && this.peerconnection) {
|
|
1542
|
+
this.peerconnection.getRemoteTracks().forEach(track => {
|
|
1543
|
+
this.room.eventEmitter.emit(JitsiConferenceEvents.TRACK_REMOVED, track);
|
|
1544
|
+
});
|
|
1545
|
+
}
|
|
1546
|
+
this.close();
|
|
1547
|
+
}
|
|
1548
|
+
/**
|
|
1549
|
+
* Processes the source map message received from the bridge and creates a new remote track for newly signaled
|
|
1550
|
+
* SSRCs or updates the source-name and owner on the remote track for an existing SSRC.
|
|
1551
|
+
*
|
|
1552
|
+
* @param {Object} message - The source map message.
|
|
1553
|
+
* @param {string} mediaType - The media type, 'audio' or 'video'.
|
|
1554
|
+
* @returns {void}
|
|
1555
|
+
*/
|
|
1556
|
+
processSourceMap(message, mediaType) {
|
|
1557
|
+
if (!FeatureFlags.isSsrcRewritingSupported()) {
|
|
1558
|
+
return;
|
|
1559
|
+
}
|
|
1560
|
+
if (mediaType === MediaType.AUDIO && this.options.startSilent) {
|
|
1561
|
+
return;
|
|
1562
|
+
}
|
|
1563
|
+
const newSsrcs = [];
|
|
1564
|
+
for (const src of message.mappedSources) {
|
|
1565
|
+
const { owner, source, ssrc } = src;
|
|
1566
|
+
const isNewSsrc = this.peerconnection.addRemoteSsrc(ssrc);
|
|
1567
|
+
if (isNewSsrc) {
|
|
1568
|
+
newSsrcs.push(src);
|
|
1569
|
+
logger.debug(`New SSRC signaled ${ssrc}: owner=${owner}, source-name=${source}`);
|
|
1570
|
+
// Check if there is an old mapping for the given source and clear the owner on the associated track.
|
|
1571
|
+
const oldSsrc = this.peerconnection.remoteSources.get(source);
|
|
1572
|
+
if (oldSsrc) {
|
|
1573
|
+
this._signalingLayer.removeSSRCOwners([oldSsrc]);
|
|
1574
|
+
const track = this.peerconnection.getTrackBySSRC(oldSsrc);
|
|
1575
|
+
if (track) {
|
|
1576
|
+
this.room.eventEmitter.emit(JitsiTrackEvents.TRACK_OWNER_SET, track);
|
|
1577
|
+
}
|
|
1578
|
+
}
|
|
1579
|
+
}
|
|
1580
|
+
else {
|
|
1581
|
+
const track = this.peerconnection.getTrackBySSRC(ssrc);
|
|
1582
|
+
if (!track || (track.getParticipantId() === owner && track.getSourceName() === source)) {
|
|
1583
|
+
!track && logger.warn(`Remote track for SSRC=${ssrc} hasn't been created yet,`
|
|
1584
|
+
+ 'not processing the source map');
|
|
1585
|
+
continue; // eslint-disable-line no-continue
|
|
1586
|
+
}
|
|
1587
|
+
logger.debug(`Existing SSRC re-mapped ${ssrc}: new owner=${owner}, source-name=${source}`);
|
|
1588
|
+
this._signalingLayer.setSSRCOwner(ssrc, owner, source);
|
|
1589
|
+
const oldSourceName = track.getSourceName();
|
|
1590
|
+
const sourceInfo = this.peerconnection.getRemoteSourceInfoBySourceName(oldSourceName);
|
|
1591
|
+
// Update the SSRC map on the peerconnection.
|
|
1592
|
+
if (sourceInfo) {
|
|
1593
|
+
this.peerconnection.updateRemoteSources(new Map([[oldSourceName, sourceInfo]]), false);
|
|
1594
|
+
this.peerconnection.updateRemoteSources(new Map([[source, sourceInfo]]), true /* isAdd */);
|
|
1595
|
+
}
|
|
1596
|
+
// Update the muted state and the video type on the track since the presence for this track could have
|
|
1597
|
+
// been received before the updated source map is received on the bridge channel.
|
|
1598
|
+
const { muted, videoType } = this._signalingLayer.getPeerMediaInfo(owner, mediaType, source);
|
|
1599
|
+
muted && this.peerconnection._sourceMutedChanged(source, muted);
|
|
1600
|
+
this.room.eventEmitter.emit(JitsiTrackEvents.TRACK_OWNER_SET, track, owner, source, videoType);
|
|
1601
|
+
}
|
|
1602
|
+
}
|
|
1603
|
+
// Add the new SSRCs to the remote description by generating a source message.
|
|
1604
|
+
if (newSsrcs.length) {
|
|
1605
|
+
let node = $build('content', {
|
|
1606
|
+
name: mediaType,
|
|
1607
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
1608
|
+
}).c('description', {
|
|
1609
|
+
media: mediaType,
|
|
1610
|
+
xmlns: XEP.RTP_MEDIA
|
|
1611
|
+
});
|
|
1612
|
+
for (const src of newSsrcs) {
|
|
1613
|
+
const { rtx, ssrc, source } = src;
|
|
1614
|
+
let msid;
|
|
1615
|
+
if (mediaType === MediaType.VIDEO) {
|
|
1616
|
+
const idx = ++this.numRemoteVideoSources;
|
|
1617
|
+
msid = `remote-video-${idx} remote-video-${idx}`;
|
|
1618
|
+
if (rtx !== '-1') {
|
|
1619
|
+
_addSourceElement(node, src, rtx, msid);
|
|
1620
|
+
node.c('ssrc-group', {
|
|
1621
|
+
semantics: SSRC_GROUP_SEMANTICS.FID,
|
|
1622
|
+
xmlns: XEP.SOURCE_ATTRIBUTES
|
|
1623
|
+
})
|
|
1624
|
+
.c('source', {
|
|
1625
|
+
ssrc,
|
|
1626
|
+
xmlns: XEP.SOURCE_ATTRIBUTES
|
|
1627
|
+
})
|
|
1628
|
+
.up()
|
|
1629
|
+
.c('source', {
|
|
1630
|
+
ssrc: rtx,
|
|
1631
|
+
xmlns: XEP.SOURCE_ATTRIBUTES
|
|
1632
|
+
})
|
|
1633
|
+
.up()
|
|
1634
|
+
.up();
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
else {
|
|
1638
|
+
const idx = ++this.numRemoteAudioSources;
|
|
1639
|
+
msid = `remote-audio-${idx} remote-audio-${idx}`;
|
|
1640
|
+
}
|
|
1641
|
+
_addSourceElement(node, src, ssrc, msid);
|
|
1642
|
+
this.peerconnection.remoteSources.set(source, ssrc);
|
|
1643
|
+
}
|
|
1644
|
+
node = node.up();
|
|
1645
|
+
this._addOrRemoveRemoteStream(true /* add */, node.node);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Handles a Jingle source-remove message for this Jingle session.
|
|
1650
|
+
*
|
|
1651
|
+
* @param {Array<Element>} contents - An array of content elements from the source-remove message.
|
|
1652
|
+
* @returns {void}
|
|
1653
|
+
*/
|
|
1654
|
+
removeRemoteStream(elem) {
|
|
1655
|
+
this._addOrRemoveRemoteStream(false /* remove */, elem);
|
|
1656
|
+
}
|
|
1657
|
+
/**
|
|
1658
|
+
* Handles the deletion of SSRCs associated with a remote user from the remote description when the user leaves.
|
|
1659
|
+
*
|
|
1660
|
+
* @param {string} id Endpoint id of the participant that has left the call.
|
|
1661
|
+
* @returns {void}
|
|
1662
|
+
*/
|
|
1663
|
+
removeRemoteStreamsOnLeave(id) {
|
|
1664
|
+
const workFunction = finishCallback => {
|
|
1665
|
+
const removeSsrcInfo = this.peerconnection.getRemoteSourceInfoByParticipant(id);
|
|
1666
|
+
if (removeSsrcInfo.size) {
|
|
1667
|
+
logger.debug(`${this} Removing SSRCs for user ${id}, sources=${Array.from(removeSsrcInfo.keys())}`);
|
|
1668
|
+
const newRemoteSdp = new SDP(this.peerconnection.remoteDescription.sdp, this.isP2P);
|
|
1669
|
+
newRemoteSdp.updateRemoteSources(removeSsrcInfo, false /* isAdd */);
|
|
1670
|
+
this.peerconnection.updateRemoteSources(removeSsrcInfo, false /* isAdd */);
|
|
1671
|
+
this._renegotiate(newRemoteSdp.raw)
|
|
1672
|
+
.then(() => finishCallback(), error => finishCallback(error));
|
|
1673
|
+
}
|
|
1674
|
+
else {
|
|
1675
|
+
finishCallback();
|
|
1676
|
+
}
|
|
1677
|
+
};
|
|
1678
|
+
logger.debug(`${this} Queued removeRemoteStreamsOnLeave task for participant ${id}`);
|
|
1679
|
+
this.modificationQueue.push(workFunction, error => {
|
|
1680
|
+
if (error) {
|
|
1681
|
+
logger.error(`${this} removeRemoteStreamsOnLeave error:`, error);
|
|
1682
|
+
}
|
|
1683
|
+
else {
|
|
1684
|
+
logger.info(`${this} removeRemoteStreamsOnLeave done!`);
|
|
1685
|
+
}
|
|
1686
|
+
});
|
|
1687
|
+
}
|
|
1688
|
+
/**
|
|
1689
|
+
* Removes local track from the peerconnection as part of the mute operation.
|
|
1690
|
+
*
|
|
1691
|
+
* @param {JitsiLocalTrack} track the local track to be removed.
|
|
1692
|
+
* @return {Promise} a promise which will be resolved once the local track is removed from this session or rejected
|
|
1693
|
+
* with a <tt>string</tt> that the describes the error if anything goes wrong.
|
|
1694
|
+
*/
|
|
1695
|
+
removeTrackFromPc(track) {
|
|
1696
|
+
return this._addRemoveTrack(true /* remove */, track);
|
|
1697
|
+
}
|
|
1698
|
+
/**
|
|
1699
|
+
* Replaces <tt>oldTrack</tt> with <tt>newTrack</tt> and performs a single offer/answer cycle (if needed) after
|
|
1700
|
+
* both operations are done.
|
|
1701
|
+
* <tt>oldTrack</tt> or <tt>newTrack</tt> can be null; replacing a valid <tt>oldTrack</tt> with a null
|
|
1702
|
+
* <tt>newTrack</tt> effectively just removes <tt>oldTrack</tt>.
|
|
1703
|
+
*
|
|
1704
|
+
* @param {Nullable<JitsiLocalTrack>} oldTrack the current track in use to be replaced.
|
|
1705
|
+
* @param {Nullable<JitsiLocalTrack>} newTrack the new track to use.
|
|
1706
|
+
* @returns {Promise} which resolves once the replacement is complete with no arguments or rejects with an error.
|
|
1707
|
+
*/
|
|
1708
|
+
replaceTrack(oldTrack, newTrack) {
|
|
1709
|
+
const workFunction = finishedCallback => {
|
|
1710
|
+
logger.debug(`${this} replaceTrack worker started. oldTrack = ${oldTrack}, newTrack = ${newTrack}`);
|
|
1711
|
+
this.peerconnection.replaceTrack(oldTrack, newTrack)
|
|
1712
|
+
.then(shouldRenegotiate => {
|
|
1713
|
+
let promise = Promise.resolve();
|
|
1714
|
+
logger.debug(`${this} TPC.replaceTrack finished. shouldRenegotiate = ${shouldRenegotiate}, JingleSessionState = ${this.state}`);
|
|
1715
|
+
if (shouldRenegotiate && (oldTrack || newTrack) && this.state === JingleSessionState.ACTIVE) {
|
|
1716
|
+
promise = this._renegotiate();
|
|
1717
|
+
}
|
|
1718
|
+
return promise.then(() => {
|
|
1719
|
+
// Set the source name of the new track.
|
|
1720
|
+
if (oldTrack && newTrack && oldTrack.isVideoTrack()) {
|
|
1721
|
+
newTrack.setSourceName(oldTrack.getSourceName());
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
})
|
|
1725
|
+
.then(() => finishedCallback(), error => finishedCallback(error));
|
|
1726
|
+
};
|
|
1727
|
+
return new Promise((resolve, reject) => {
|
|
1728
|
+
logger.debug(`${this} Queued replaceTrack task. Old track = ${oldTrack}, new track = ${newTrack}`);
|
|
1729
|
+
this.modificationQueue.push(workFunction, error => {
|
|
1730
|
+
if (error) {
|
|
1731
|
+
if (error instanceof ClearedQueueError) {
|
|
1732
|
+
// The session might have been terminated before the task was executed, making it obsolete.
|
|
1733
|
+
logger.debug('Replace track aborted: session terminated');
|
|
1734
|
+
resolve();
|
|
1735
|
+
return;
|
|
1736
|
+
}
|
|
1737
|
+
logger.error(`${this} Replace track error:`, error);
|
|
1738
|
+
reject(error);
|
|
1739
|
+
}
|
|
1740
|
+
else {
|
|
1741
|
+
logger.info(`${this} Replace track done!`);
|
|
1742
|
+
resolve();
|
|
1743
|
+
}
|
|
1744
|
+
});
|
|
1745
|
+
});
|
|
1746
|
+
}
|
|
1747
|
+
/**
|
|
1748
|
+
* Sets the answer received from the remote peer as the remote description.
|
|
1749
|
+
*
|
|
1750
|
+
* @param {Element} jingleAnswer - The jingle answer element.
|
|
1751
|
+
* @returns {Promise<void>} that resolves when the answer is set as the remote description, rejected otherwise.
|
|
1752
|
+
*/
|
|
1753
|
+
async setAnswer(jingleAnswer) {
|
|
1754
|
+
if (!this.isInitiator) {
|
|
1755
|
+
throw new Error('Trying to set an answer on the responder session');
|
|
1756
|
+
}
|
|
1757
|
+
logger.debug(`${this} Executing setAnswer task`);
|
|
1758
|
+
const newRemoteSdp = this._processNewJingleOfferIq(jingleAnswer);
|
|
1759
|
+
const oldLocalSdp = new SDP(this.peerconnection.localDescription.sdp);
|
|
1760
|
+
const remoteDescription = {
|
|
1761
|
+
sdp: newRemoteSdp.raw,
|
|
1762
|
+
type: 'answer'
|
|
1763
|
+
};
|
|
1764
|
+
try {
|
|
1765
|
+
await this.peerconnection.setRemoteDescription(remoteDescription);
|
|
1766
|
+
if (this.state === JingleSessionState.PENDING) {
|
|
1767
|
+
this.state = JingleSessionState.ACTIVE;
|
|
1768
|
+
// Start processing tasks on the modification queue.
|
|
1769
|
+
logger.debug(`${this} Resuming the modification queue after session is established!`);
|
|
1770
|
+
this.modificationQueue.resume();
|
|
1771
|
+
const newLocalSdp = new SDP(this.peerconnection.localDescription.sdp);
|
|
1772
|
+
this._sendContentModify();
|
|
1773
|
+
this.notifyMySSRCUpdate(oldLocalSdp, newLocalSdp);
|
|
1774
|
+
}
|
|
1775
|
+
logger.debug(`${this} setAnswer task done`);
|
|
1776
|
+
}
|
|
1777
|
+
catch (error) {
|
|
1778
|
+
logger.error(`${this} setAnswer task failed: ${error}`);
|
|
1779
|
+
throw error;
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
/**
|
|
1783
|
+
* Resumes or suspends media transfer over the underlying peer connection.
|
|
1784
|
+
*
|
|
1785
|
+
* @param {boolean} active - <tt>true</tt> to enable media transfer or <tt>false</tt> to suspend media transmission.
|
|
1786
|
+
* @returns {Promise<void>}
|
|
1787
|
+
*/
|
|
1788
|
+
setMediaTransferActive(active) {
|
|
1789
|
+
const changed = this.peerconnection.audioTransferActive !== active
|
|
1790
|
+
|| this.peerconnection.videoTransferActive !== active;
|
|
1791
|
+
if (!changed) {
|
|
1792
|
+
return Promise.resolve();
|
|
1793
|
+
}
|
|
1794
|
+
return this.peerconnection.setMediaTransferActive(active)
|
|
1795
|
+
.then(async () => {
|
|
1796
|
+
this.peerconnection.audioTransferActive = active;
|
|
1797
|
+
this.peerconnection.videoTransferActive = active;
|
|
1798
|
+
// Reconfigure the audio and video tracks so that only the correct encodings are active.
|
|
1799
|
+
const promises = [];
|
|
1800
|
+
promises.push(this.peerconnection.configureVideoSenderEncodings());
|
|
1801
|
+
promises.push(this.peerconnection.configureAudioSenderEncodings());
|
|
1802
|
+
await Promise.allSettled(promises);
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
/**
|
|
1806
|
+
* Resumes or suspends video media transfer over the p2p peer connection.
|
|
1807
|
+
*
|
|
1808
|
+
* @param {boolean} videoActive <tt>true</tt> to enable video media transfer or <tt>false</tt> to suspend video
|
|
1809
|
+
* media transmission.
|
|
1810
|
+
* @return {Promise} a <tt>Promise</tt> which will resolve once the operation is done. It will be rejected with
|
|
1811
|
+
* an error description as a string in case anything goes wrong.
|
|
1812
|
+
*/
|
|
1813
|
+
setP2pVideoTransferActive(videoActive) {
|
|
1814
|
+
if (!this.peerconnection) {
|
|
1815
|
+
return Promise.reject('Can not modify video transfer active state,'
|
|
1816
|
+
+ ' before "initialize" is called');
|
|
1817
|
+
}
|
|
1818
|
+
if (this._localSendReceiveVideoActive !== videoActive) {
|
|
1819
|
+
this._localSendReceiveVideoActive = videoActive;
|
|
1820
|
+
if (this.isP2P && this.state === JingleSessionState.ACTIVE) {
|
|
1821
|
+
this._sendContentModify();
|
|
1822
|
+
}
|
|
1823
|
+
return this.peerconnection
|
|
1824
|
+
.setVideoTransferActive(this._localSendReceiveVideoActive && this._remoteSendReceiveVideoActive);
|
|
1825
|
+
}
|
|
1826
|
+
return Promise.resolve();
|
|
1827
|
+
}
|
|
1828
|
+
/**
|
|
1829
|
+
* Adjust the preference for max video frame height that the local party is willing to receive. Signals
|
|
1830
|
+
* the remote p2p peer.
|
|
1831
|
+
*
|
|
1832
|
+
* @param {Map<string, number>} sourceReceiverConstraints - The receiver constraints per source.
|
|
1833
|
+
* @returns {void}
|
|
1834
|
+
*/
|
|
1835
|
+
setReceiverVideoConstraint(sourceReceiverConstraints) {
|
|
1836
|
+
logger.info(`${this} setReceiverVideoConstraint - constraints: ${JSON.stringify(sourceReceiverConstraints)}`);
|
|
1837
|
+
this._sourceReceiverConstraints = sourceReceiverConstraints;
|
|
1838
|
+
if (this.isP2P) {
|
|
1839
|
+
// Tell the remote peer about our receive constraint. If Jingle session is not yet active the state will
|
|
1840
|
+
// be synced after offer/answer.
|
|
1841
|
+
if (this.state === JingleSessionState.ACTIVE) {
|
|
1842
|
+
this._sendContentModify();
|
|
1843
|
+
}
|
|
1844
|
+
}
|
|
1845
|
+
}
|
|
1846
|
+
/**
|
|
1847
|
+
* Sets the resolution constraint on the local video tracks.
|
|
1848
|
+
*
|
|
1849
|
+
* @param {number} maxFrameHeight - The user preferred max frame height.
|
|
1850
|
+
* @param {string} sourceName - The source name of the track.
|
|
1851
|
+
* @returns {Promise} promise that will be resolved when the operation is
|
|
1852
|
+
* successful and rejected otherwise.
|
|
1853
|
+
*/
|
|
1854
|
+
setSenderVideoConstraint(maxFrameHeight, sourceName = null) {
|
|
1855
|
+
if (this._assertNotEnded()) {
|
|
1856
|
+
logger.info(`${this} setSenderVideoConstraint: ${maxFrameHeight}, sourceName: ${sourceName}`);
|
|
1857
|
+
const jitsiLocalTrack = sourceName
|
|
1858
|
+
? this.rtc.getLocalVideoTracks().find(track => track.getSourceName() === sourceName)
|
|
1859
|
+
: this.rtc.getLocalVideoTrack();
|
|
1860
|
+
return this.peerconnection.setSenderVideoConstraints(maxFrameHeight, jitsiLocalTrack);
|
|
1861
|
+
}
|
|
1862
|
+
return Promise.resolve();
|
|
1863
|
+
}
|
|
1864
|
+
/**
|
|
1865
|
+
* Updates the codecs on the peerconnection and initiates a renegotiation (if needed) for the
|
|
1866
|
+
* new codec config to take effect.
|
|
1867
|
+
*
|
|
1868
|
+
* @param {Array<CodecMimeType>} codecList - Preferred codecs for video.
|
|
1869
|
+
* @param {CodecMimeType} screenshareCodec - The preferred screenshare codec.
|
|
1870
|
+
* @returns {void}
|
|
1871
|
+
*/
|
|
1872
|
+
setVideoCodecs(codecList, screenshareCodec) {
|
|
1873
|
+
if (this._assertNotEnded()) {
|
|
1874
|
+
const updated = this.peerconnection.setVideoCodecs(codecList, screenshareCodec);
|
|
1875
|
+
if (updated) {
|
|
1876
|
+
this.eventEmitter.emit(MediaSessionEvents.VIDEO_CODEC_CHANGED);
|
|
1877
|
+
}
|
|
1878
|
+
// Browser throws an error when H.264 is set on the encodings. Therefore, munge the SDP when H.264 needs to
|
|
1879
|
+
// be selected.
|
|
1880
|
+
// TODO: Remove this check when the above issue is fixed.
|
|
1881
|
+
if (this.usesCodecSelectionAPI && codecList[0] !== CodecMimeType.H264) {
|
|
1882
|
+
return;
|
|
1883
|
+
}
|
|
1884
|
+
// Skip renegotiation when the selected codec order matches with that of the remote SDP.
|
|
1885
|
+
const currentCodecOrder = this.peerconnection.getConfiguredVideoCodecs();
|
|
1886
|
+
if (codecList.every((val, index) => val === currentCodecOrder[index])) {
|
|
1887
|
+
return;
|
|
1888
|
+
}
|
|
1889
|
+
this.eventEmitter.emit(MediaSessionEvents.VIDEO_CODEC_CHANGED);
|
|
1890
|
+
Statistics.sendAnalytics(AnalyticsEvents.VIDEO_CODEC_CHANGED, {
|
|
1891
|
+
value: codecList[0],
|
|
1892
|
+
videoType: VideoType.CAMERA
|
|
1893
|
+
});
|
|
1894
|
+
logger.info(`${this} setVideoCodecs: codecList=${codecList}, screenshareCodec=${screenshareCodec}`);
|
|
1895
|
+
// Initiate a renegotiate for the codec setting to take effect.
|
|
1896
|
+
const workFunction = async (finishedCallback) => {
|
|
1897
|
+
try {
|
|
1898
|
+
await this._renegotiate();
|
|
1899
|
+
await this.peerconnection.configureVideoSenderEncodings();
|
|
1900
|
+
logger.debug(`${this} setVideoCodecs task is done`);
|
|
1901
|
+
return finishedCallback();
|
|
1902
|
+
}
|
|
1903
|
+
catch (error) {
|
|
1904
|
+
logger.error(`${this} setVideoCodecs task failed: ${error}`);
|
|
1905
|
+
return finishedCallback(error);
|
|
1906
|
+
}
|
|
1907
|
+
};
|
|
1908
|
+
logger.debug(`${this} Queued setVideoCodecs task`);
|
|
1909
|
+
// Queue and execute
|
|
1910
|
+
this.modificationQueue.push(workFunction);
|
|
1911
|
+
}
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* @inheritDoc
|
|
1915
|
+
*/
|
|
1916
|
+
terminate(success, failure, options = {}) {
|
|
1917
|
+
if (this.state === JingleSessionState.ENDED) {
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
if (!options || Boolean(options.sendSessionTerminate)) {
|
|
1921
|
+
const sessionTerminate = $iq({
|
|
1922
|
+
to: this.remoteJid,
|
|
1923
|
+
type: 'set'
|
|
1924
|
+
})
|
|
1925
|
+
.c('jingle', {
|
|
1926
|
+
action: 'session-terminate',
|
|
1927
|
+
initiator: this.initiatorJid,
|
|
1928
|
+
sid: this.sid,
|
|
1929
|
+
xmlns: 'urn:xmpp:jingle:1'
|
|
1930
|
+
})
|
|
1931
|
+
.c('reason')
|
|
1932
|
+
.c((options?.reason) || 'success')
|
|
1933
|
+
.up();
|
|
1934
|
+
if (options?.reasonDescription) {
|
|
1935
|
+
sessionTerminate
|
|
1936
|
+
.c('text')
|
|
1937
|
+
.t(options.reasonDescription)
|
|
1938
|
+
.up()
|
|
1939
|
+
.up();
|
|
1940
|
+
}
|
|
1941
|
+
else {
|
|
1942
|
+
sessionTerminate.up();
|
|
1943
|
+
}
|
|
1944
|
+
this._bridgeSessionId
|
|
1945
|
+
&& sessionTerminate.c('bridge-session', {
|
|
1946
|
+
id: this._bridgeSessionId,
|
|
1947
|
+
restart: options && options.requestRestart === true,
|
|
1948
|
+
xmlns: 'http://jitsi.org/protocol/focus'
|
|
1949
|
+
}).up();
|
|
1950
|
+
logger.info(`${this} Sending session-terminate`);
|
|
1951
|
+
logger.debug(sessionTerminate.tree());
|
|
1952
|
+
this.connection.sendIQ(sessionTerminate, success, this.newJingleErrorHandler(failure), IQ_TIMEOUT);
|
|
1953
|
+
}
|
|
1954
|
+
else {
|
|
1955
|
+
logger.info(`${this} Skipped sending session-terminate`);
|
|
1956
|
+
}
|
|
1957
|
+
// this should result in 'onTerminated' being called by strophe.jingle.js
|
|
1958
|
+
this.connection.jingle.terminate(this.sid);
|
|
1959
|
+
}
|
|
1960
|
+
/**
|
|
1961
|
+
* Converts to string with minor summary.
|
|
1962
|
+
*
|
|
1963
|
+
* @return {string}
|
|
1964
|
+
*/
|
|
1965
|
+
toString() {
|
|
1966
|
+
return `JingleSessionPC[session=${this.isP2P ? 'P2P' : 'JVB'},initiator=${this.isInitiator},sid=${this.sid}]`;
|
|
1967
|
+
}
|
|
1968
|
+
}
|
|
1969
|
+
//# sourceMappingURL=JingleSessionPC.js.map
|