@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,2223 @@
|
|
|
1
|
+
import { getLogger } from '@jitsi/logger';
|
|
2
|
+
import { cloneDeep } from 'lodash-es';
|
|
3
|
+
import transform from 'sdp-transform';
|
|
4
|
+
import { CodecMimeType } from '../../service/RTC/CodecMimeType';
|
|
5
|
+
import { MediaDirection } from '../../service/RTC/MediaDirection';
|
|
6
|
+
import { MediaType } from '../../service/RTC/MediaType';
|
|
7
|
+
import { RTCEvents } from '../../service/RTC/RTCEvents';
|
|
8
|
+
import { SignalingEvents } from '../../service/RTC/SignalingEvents';
|
|
9
|
+
import { getSourceIndexFromSourceName } from '../../service/RTC/SignalingLayer';
|
|
10
|
+
import { SSRC_GROUP_SEMANTICS, VIDEO_QUALITY_LEVELS } from '../../service/RTC/StandardVideoQualitySettings';
|
|
11
|
+
import { VideoType } from '../../service/RTC/VideoType';
|
|
12
|
+
import { AnalyticsEvents } from '../../service/statistics/AnalyticsEvents';
|
|
13
|
+
import RTCStats from '../RTCStats/RTCStats';
|
|
14
|
+
import { RTCStatsEvents } from '../RTCStats/RTCStatsEvents';
|
|
15
|
+
import browser from '../browser';
|
|
16
|
+
import FeatureFlags from '../flags/FeatureFlags';
|
|
17
|
+
import LocalSdpMunger from '../sdp/LocalSdpMunger';
|
|
18
|
+
import RtxModifier from '../sdp/RtxModifier';
|
|
19
|
+
import SDP from '../sdp/SDP';
|
|
20
|
+
import SDPUtil from '../sdp/SDPUtil';
|
|
21
|
+
import SdpSimulcast from '../sdp/SdpSimulcast';
|
|
22
|
+
import { SdpTransformWrap } from '../sdp/SdpTransformUtil';
|
|
23
|
+
import Statistics from '../statistics/statistics';
|
|
24
|
+
import { isValidNumber } from '../util/MathUtil';
|
|
25
|
+
import JitsiRemoteTrack from './JitsiRemoteTrack';
|
|
26
|
+
import RTCUtils from './RTCUtils';
|
|
27
|
+
import { SS_DEFAULT_FRAME_RATE } from './ScreenObtainer';
|
|
28
|
+
import { TPCUtils } from './TPCUtils';
|
|
29
|
+
const logger = getLogger('rtc:TraceablePeerConnection');
|
|
30
|
+
const DEGRADATION_PREFERENCE_CAMERA = 'maintain-framerate';
|
|
31
|
+
const DEGRADATION_PREFERENCE_DESKTOP = 'maintain-resolution';
|
|
32
|
+
/* eslint-disable max-params */
|
|
33
|
+
/**
|
|
34
|
+
* Creates new instance of 'TraceablePeerConnection'.
|
|
35
|
+
*/
|
|
36
|
+
class TraceablePeerConnection {
|
|
37
|
+
/**
|
|
38
|
+
* @param {RTC} rtc the instance of <tt>RTC</tt> service
|
|
39
|
+
* @param {number} id the peer connection id assigned by the parent RTC module.
|
|
40
|
+
* @param {SignalingLayer} signalingLayer the signaling layer instance
|
|
41
|
+
* @param {object} pcConfig The {@code RTCConfiguration} to use for the WebRTC peer connection.
|
|
42
|
+
* @param {object} constraints WebRTC 'PeerConnection' constraints
|
|
43
|
+
* @param {boolean} isP2P indicates whether or not the new instance will be used in a peer to peer connection.
|
|
44
|
+
* @param {object} options <tt>TracablePeerConnection</tt> config options.
|
|
45
|
+
* @param {Object} options.audioQuality - Quality settings to applied on the outbound audio stream.
|
|
46
|
+
* @param {boolean} options.capScreenshareBitrate if set to true, lower layers will be disabled for screenshare.
|
|
47
|
+
* @param {Array<CodecMimeType>} options.codecSettings - codec settings to be applied for video streams.
|
|
48
|
+
* @param {boolean} options.disableSimulcast if set to 'true' will disable the simulcast.
|
|
49
|
+
* @param {boolean} options.disableRtx if set to 'true' will disable the RTX.
|
|
50
|
+
* @param {boolean} options.enableInsertableStreams set to true when the insertable streams constraints is to be
|
|
51
|
+
* enabled on the PeerConnection.
|
|
52
|
+
* @param {boolean} options.forceTurnRelay If set to true, the browser will generate only Relay ICE candidates.
|
|
53
|
+
* @param {boolean} options.startSilent If set to 'true' no audio will be sent or received.
|
|
54
|
+
* @param {Object} options.videoQuality - Quality settings to applied on the outbound video streams.
|
|
55
|
+
*
|
|
56
|
+
* FIXME: initially the purpose of TraceablePeerConnection was to be able to
|
|
57
|
+
* debug the peer connection. Since many other responsibilities have been added
|
|
58
|
+
* it would make sense to extract a separate class from it and come up with
|
|
59
|
+
* a more suitable name.
|
|
60
|
+
*
|
|
61
|
+
* @constructor
|
|
62
|
+
*/
|
|
63
|
+
constructor(rtc, id, signalingLayer, pcConfig, constraints, isP2P, options) {
|
|
64
|
+
/**
|
|
65
|
+
* Configures the RTCRtpEncodingParameters of the outbound rtp stream associated with the given track.
|
|
66
|
+
*
|
|
67
|
+
* @param {JitsiLocalTracj} localTrack - The local track whose outbound stream needs to be configured.
|
|
68
|
+
* @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
|
|
69
|
+
*/
|
|
70
|
+
this._configureSenderEncodings = async (localTrack) => {
|
|
71
|
+
const mediaType = localTrack.getType();
|
|
72
|
+
const transceiver = localTrack?.track && localTrack.getOriginalStream()
|
|
73
|
+
? this.peerconnection.getTransceivers().find(t => t.sender?.track?.id === localTrack.getTrackId())
|
|
74
|
+
: this.peerconnection.getTransceivers().find(t => t.receiver?.track?.kind === mediaType);
|
|
75
|
+
const parameters = transceiver?.sender?.getParameters();
|
|
76
|
+
// Resolve if the encodings are not available yet. This happens immediately after the track is added to the
|
|
77
|
+
// peerconnection on chrome in unified-plan. It is ok to ignore and not report the error here since the
|
|
78
|
+
// action that triggers 'addTrack' (like unmute) will also configure the encodings and set bitrates after that.
|
|
79
|
+
if (!parameters?.encodings?.length) {
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
parameters.encodings = this.tpcUtils.getStreamEncodings(localTrack);
|
|
83
|
+
await transceiver.sender.setParameters(parameters);
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Enables/disables the streams by changing the active field on RTCRtpEncodingParameters for a given RTCRtpSender.
|
|
87
|
+
*
|
|
88
|
+
* @param {RTCRtpSender} sender - the sender associated with a MediaStreamTrack.
|
|
89
|
+
* @param {boolean} enable - whether the streams needs to be enabled or disabled.
|
|
90
|
+
* @returns {Promise} - A promise that resolves when the operation is successful, rejected otherwise.
|
|
91
|
+
*/
|
|
92
|
+
this._enableSenderEncodings = async (sender, enable) => {
|
|
93
|
+
const parameters = sender.getParameters();
|
|
94
|
+
if (parameters?.encodings?.length) {
|
|
95
|
+
for (const encoding of parameters.encodings) {
|
|
96
|
+
encoding.active = enable;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
await sender.setParameters(parameters);
|
|
100
|
+
};
|
|
101
|
+
/**
|
|
102
|
+
* Add {@link JitsiLocalTrack} to this TPC.
|
|
103
|
+
* @param {JitsiLocalTrack} track
|
|
104
|
+
* @param {boolean} isInitiator indicates if the endpoint is the offerer.
|
|
105
|
+
* @returns {Promise<void>} - resolved when done.
|
|
106
|
+
*/
|
|
107
|
+
this.addTrack = async (track, isInitiator = false) => {
|
|
108
|
+
const rtcId = track.rtcId;
|
|
109
|
+
if (this.localTracks.has(rtcId)) {
|
|
110
|
+
throw new Error(`${track} is already in ${this}`);
|
|
111
|
+
}
|
|
112
|
+
logger.info(`${this} adding ${track}`);
|
|
113
|
+
const webrtcStream = track.getOriginalStream();
|
|
114
|
+
const mediaStreamTrack = track.getTrack();
|
|
115
|
+
let transceiver;
|
|
116
|
+
if (isInitiator) {
|
|
117
|
+
const streams = [];
|
|
118
|
+
webrtcStream && streams.push(webrtcStream);
|
|
119
|
+
// Use pc.addTransceiver() for the initiator case when local tracks are getting added
|
|
120
|
+
// to the peerconnection before a session-initiate is sent over to the peer.
|
|
121
|
+
const transceiverInit = {
|
|
122
|
+
direction: MediaDirection.SENDRECV,
|
|
123
|
+
sendEncodings: [],
|
|
124
|
+
streams
|
|
125
|
+
};
|
|
126
|
+
if (!browser.isFirefox()) {
|
|
127
|
+
transceiverInit.sendEncodings = this.tpcUtils.getStreamEncodings(track);
|
|
128
|
+
}
|
|
129
|
+
transceiver = this.peerconnection.addTransceiver(mediaStreamTrack, transceiverInit);
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
// Use pc.addTrack() for responder case so that we can re-use the m-lines that were created
|
|
133
|
+
// when setRemoteDescription was called. pc.addTrack() automatically attaches to any existing
|
|
134
|
+
// unused "recv-only" transceiver.
|
|
135
|
+
const sender = this.peerconnection.addTrack(mediaStreamTrack);
|
|
136
|
+
// Find the corresponding transceiver that the track was attached to.
|
|
137
|
+
transceiver = this.peerconnection.getTransceivers().find(t => t.sender === sender);
|
|
138
|
+
}
|
|
139
|
+
if (transceiver?.mid) {
|
|
140
|
+
this.localTrackTransceiverMids.set(track.rtcId, transceiver.mid.toString());
|
|
141
|
+
}
|
|
142
|
+
if (track) {
|
|
143
|
+
this.localTracks.set(rtcId, track);
|
|
144
|
+
if (track.isAudioTrack()) {
|
|
145
|
+
this._hasHadAudioTrack = true;
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
this._hasHadVideoTrack = true;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// On Firefox, the encodings have to be configured on the sender only after the transceiver is created.
|
|
152
|
+
if (browser.isFirefox() && webrtcStream && this.doesTrueSimulcast(track)) {
|
|
153
|
+
await this._setEncodings(track);
|
|
154
|
+
}
|
|
155
|
+
};
|
|
156
|
+
/**
|
|
157
|
+
* Indicates whether or not this peer connection instance is actively
|
|
158
|
+
* sending/receiving audio media. When set to <tt>false</tt> the SDP audio
|
|
159
|
+
* media direction will be adjusted to 'inactive' in order to suspend
|
|
160
|
+
* the transmission.
|
|
161
|
+
* @type {boolean}
|
|
162
|
+
* @internal
|
|
163
|
+
*/
|
|
164
|
+
this.audioTransferActive = !(options.startSilent === true);
|
|
165
|
+
/**
|
|
166
|
+
* The DTMF sender instance used to send DTMF tones.
|
|
167
|
+
*
|
|
168
|
+
* @type {RTCDTMFSender|undefined}
|
|
169
|
+
* @private
|
|
170
|
+
*/
|
|
171
|
+
this._dtmfSender = undefined;
|
|
172
|
+
/**
|
|
173
|
+
* @typedef {Object} TouchToneRequest
|
|
174
|
+
* @property {string} tones - The DTMF tones string as defined by
|
|
175
|
+
* {@code RTCDTMFSender.insertDTMF}, 'tones' argument.
|
|
176
|
+
* @property {number} duration - The amount of time in milliseconds that
|
|
177
|
+
* each DTMF should last.
|
|
178
|
+
* @property {string} interToneGap - The length of time in miliseconds to
|
|
179
|
+
* wait between tones.
|
|
180
|
+
*/
|
|
181
|
+
/**
|
|
182
|
+
* TouchToneRequests which are waiting to be played. This queue is filled
|
|
183
|
+
* if there are touch tones currently being played.
|
|
184
|
+
*
|
|
185
|
+
* @type {Array<TouchToneRequest>}
|
|
186
|
+
* @private
|
|
187
|
+
*/
|
|
188
|
+
this._dtmfTonesQueue = [];
|
|
189
|
+
/**
|
|
190
|
+
* Indicates whether or not this peer connection instance is actively
|
|
191
|
+
* sending/receiving video media. When set to <tt>false</tt> the SDP video
|
|
192
|
+
* media direction will be adjusted to 'inactive' in order to suspend
|
|
193
|
+
* the transmission.
|
|
194
|
+
* @type {boolean}
|
|
195
|
+
* @internal
|
|
196
|
+
*/
|
|
197
|
+
this.videoTransferActive = true;
|
|
198
|
+
/**
|
|
199
|
+
* The parent instance of RTC service which created this
|
|
200
|
+
* <tt>TracablePeerConnection</tt>.
|
|
201
|
+
* @type {RTC}
|
|
202
|
+
*/
|
|
203
|
+
this.rtc = rtc;
|
|
204
|
+
/**
|
|
205
|
+
* The peer connection identifier assigned by the RTC module.
|
|
206
|
+
* @type {number}
|
|
207
|
+
*/
|
|
208
|
+
this.id = id;
|
|
209
|
+
/**
|
|
210
|
+
* RTCStats identifier for this peerconnection.
|
|
211
|
+
*/
|
|
212
|
+
this._pcId = `PC_${this.id}`;
|
|
213
|
+
/**
|
|
214
|
+
* Indicates whether or not this instance is used in a peer to peer
|
|
215
|
+
* connection.
|
|
216
|
+
* @type {boolean}
|
|
217
|
+
*/
|
|
218
|
+
this.isP2P = isP2P;
|
|
219
|
+
/**
|
|
220
|
+
* A map that holds remote tracks signaled on the peerconnection indexed by their SSRC.
|
|
221
|
+
* @type {Map<number, JitsiRemoteTrack>}
|
|
222
|
+
*/
|
|
223
|
+
this.remoteTracksBySsrc = new Map();
|
|
224
|
+
/**
|
|
225
|
+
* The map holds remote tracks associated with this peer connection.
|
|
226
|
+
* It maps user's JID to media type and a set of
|
|
227
|
+
* remote tracks.
|
|
228
|
+
* @type {Map<string, Map<MediaType, Set<JitsiRemoteTrack>>>}
|
|
229
|
+
*/
|
|
230
|
+
this.remoteTracks = new Map();
|
|
231
|
+
/**
|
|
232
|
+
* A map which stores local tracks mapped by {@link JitsiLocalTrack.rtcId}
|
|
233
|
+
* @type {Map<number, JitsiLocalTrack>}
|
|
234
|
+
*/
|
|
235
|
+
this.localTracks = new Map();
|
|
236
|
+
/**
|
|
237
|
+
* @typedef {Object} TPCGroupInfo
|
|
238
|
+
* @property {string} semantics the SSRC groups semantics
|
|
239
|
+
* @property {Array<number>} ssrcs group's SSRCs in order where the first
|
|
240
|
+
* one is group's primary SSRC, the second one is secondary (RTX) and so
|
|
241
|
+
* on...
|
|
242
|
+
*/
|
|
243
|
+
/**
|
|
244
|
+
* @typedef {Object} TPCSSRCInfo
|
|
245
|
+
* @property {Array<number>} ssrcs an array which holds all track's SSRCs
|
|
246
|
+
* @property {Array<TPCGroupInfo>} groups an array stores all track's SSRC
|
|
247
|
+
* groups
|
|
248
|
+
*/
|
|
249
|
+
/**
|
|
250
|
+
* Holds the info about local track's SSRCs mapped per their
|
|
251
|
+
* {@link JitsiLocalTrack.rtcId}
|
|
252
|
+
* @type {Map<number, TPCSSRCInfo>}
|
|
253
|
+
*/
|
|
254
|
+
this.localSSRCs = new Map();
|
|
255
|
+
/**
|
|
256
|
+
* The set of remote SSRCs seen so far.
|
|
257
|
+
* Distinguishes new SSRCs from those that have been remapped.
|
|
258
|
+
* @type {Set<number>}
|
|
259
|
+
*/
|
|
260
|
+
this.remoteSSRCs = new Set();
|
|
261
|
+
/**
|
|
262
|
+
* Mapping of source-names and their associated SSRCs that have been signaled by the JVB.
|
|
263
|
+
* @type {Map<string, number>}
|
|
264
|
+
*/
|
|
265
|
+
this.remoteSources = new Map();
|
|
266
|
+
/**
|
|
267
|
+
* The local ICE username fragment for this session.
|
|
268
|
+
*/
|
|
269
|
+
this._localUfrag = null;
|
|
270
|
+
/**
|
|
271
|
+
* The remote ICE username fragment for this session.
|
|
272
|
+
*/
|
|
273
|
+
this._remoteUfrag = null;
|
|
274
|
+
/**
|
|
275
|
+
* The DTLS transport object for the PeerConnection.
|
|
276
|
+
* Note: this assume only one shared transport exists because we bundled
|
|
277
|
+
* all streams on the same underlying transport.
|
|
278
|
+
*/
|
|
279
|
+
this._dtlsTransport = null;
|
|
280
|
+
/**
|
|
281
|
+
* The signaling layer which operates this peer connection.
|
|
282
|
+
* @type {SignalingLayer}
|
|
283
|
+
*/
|
|
284
|
+
this._signalingLayer = signalingLayer;
|
|
285
|
+
// SignalingLayer listeners
|
|
286
|
+
this._peerVideoTypeChanged = this._peerVideoTypeChanged.bind(this);
|
|
287
|
+
this._signalingLayer.on(SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);
|
|
288
|
+
this._peerMutedChanged = this._peerMutedChanged.bind(this);
|
|
289
|
+
this._signalingLayer.on(SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);
|
|
290
|
+
this.options = options;
|
|
291
|
+
// Setup SignalingLayer listeners for source-name based events.
|
|
292
|
+
this._signalingLayer.on(SignalingEvents.SOURCE_MUTED_CHANGED, (sourceName, isMuted) => this._sourceMutedChanged(sourceName, isMuted));
|
|
293
|
+
this._signalingLayer.on(SignalingEvents.SOURCE_VIDEO_TYPE_CHANGED, (sourceName, videoType) => this._sourceVideoTypeChanged(sourceName, videoType));
|
|
294
|
+
// Make sure constraints is properly formatted in order to provide information about whether or not this
|
|
295
|
+
// connection is P2P to rtcstats.
|
|
296
|
+
const safeConstraints = constraints || {};
|
|
297
|
+
safeConstraints.optional = safeConstraints.optional || [];
|
|
298
|
+
// The `optional` parameter needs to be of type array, otherwise chrome will throw an error.
|
|
299
|
+
// Firefox and Safari just ignore it.
|
|
300
|
+
if (Array.isArray(safeConstraints.optional)) {
|
|
301
|
+
safeConstraints.optional.push({ rtcStatsSFUP2P: this.isP2P });
|
|
302
|
+
}
|
|
303
|
+
else {
|
|
304
|
+
logger.warn('Optional param is not an array, rtcstats p2p data is omitted.');
|
|
305
|
+
}
|
|
306
|
+
// RTCPeerConnection constructor only accepts RTCConfiguration
|
|
307
|
+
// @ts-ignore
|
|
308
|
+
this.peerconnection = new RTCPeerConnection(pcConfig, safeConstraints);
|
|
309
|
+
this.tpcUtils = new TPCUtils(this, {
|
|
310
|
+
audioQuality: options.audioQuality,
|
|
311
|
+
isP2P: this.isP2P,
|
|
312
|
+
videoQuality: options.videoQuality
|
|
313
|
+
});
|
|
314
|
+
this.updateLog = [];
|
|
315
|
+
this.stats = {};
|
|
316
|
+
this.statsinterval = null;
|
|
317
|
+
/**
|
|
318
|
+
* Flag used to indicate if low fps screenshare is desired.
|
|
319
|
+
*/
|
|
320
|
+
this._capScreenshareBitrate = this.options.capScreenshareBitrate;
|
|
321
|
+
/**
|
|
322
|
+
* Codec preferences set for the peerconnection through config.js.
|
|
323
|
+
*/
|
|
324
|
+
this.codecSettings = this.options.codecSettings;
|
|
325
|
+
/**
|
|
326
|
+
* Flag used to indicate if RTCRtpTransceiver#setCodecPreferences is to be used instead of SDP
|
|
327
|
+
* munging for codec selection.
|
|
328
|
+
*/
|
|
329
|
+
browser.supportsCodecPreferences()
|
|
330
|
+
&& logger.info('Using RTCRtpTransceiver#setCodecPreferences for codec selection');
|
|
331
|
+
/**
|
|
332
|
+
* Flag used to indicate if the codecs are configured using the codec selection API without having the need to
|
|
333
|
+
* trigger a renegotiation for the change to be effective.
|
|
334
|
+
*/
|
|
335
|
+
this._usesCodecSelectionAPI = this.options.usesCodecSelectionAPI;
|
|
336
|
+
/**
|
|
337
|
+
* Indicates whether an audio track has ever been added to the peer connection.
|
|
338
|
+
*/
|
|
339
|
+
this._hasHadAudioTrack = false;
|
|
340
|
+
/**
|
|
341
|
+
* Indicates whether a video track has ever been added to the peer connection.
|
|
342
|
+
*/
|
|
343
|
+
this._hasHadVideoTrack = false;
|
|
344
|
+
/**
|
|
345
|
+
* @type {number} The max number of stats to keep in this.stats. Limit to
|
|
346
|
+
* 300 values, i.e. 5 minutes; set to 0 to disable
|
|
347
|
+
*/
|
|
348
|
+
this.maxstats = options.maxstats;
|
|
349
|
+
this.simulcast = new SdpSimulcast();
|
|
350
|
+
/**
|
|
351
|
+
* Munges local SDP provided to the Jingle Session in order to prevent from
|
|
352
|
+
* sending SSRC updates on attach/detach and mute/unmute (for video).
|
|
353
|
+
* @type {LocalSdpMunger}
|
|
354
|
+
*/
|
|
355
|
+
this.localSdpMunger = new LocalSdpMunger(this, this.rtc.getLocalEndpointId());
|
|
356
|
+
/**
|
|
357
|
+
* TracablePeerConnection uses RTC's eventEmitter
|
|
358
|
+
* @type {EventEmitter}
|
|
359
|
+
*/
|
|
360
|
+
this.eventEmitter = rtc.eventEmitter;
|
|
361
|
+
this.rtxModifier = new RtxModifier();
|
|
362
|
+
/**
|
|
363
|
+
* The height constraints to be applied on the sender per local video source (source name as the key).
|
|
364
|
+
* @type {Map<string, number>}
|
|
365
|
+
*/
|
|
366
|
+
this._senderMaxHeights = new Map();
|
|
367
|
+
/**
|
|
368
|
+
* Holds the RTCRtpTransceiver mids that the local tracks are attached to, mapped per their
|
|
369
|
+
* {@link JitsiLocalTrack.rtcId}.
|
|
370
|
+
* @type {Map<string, string>}
|
|
371
|
+
*/
|
|
372
|
+
this.localTrackTransceiverMids = new Map();
|
|
373
|
+
/**
|
|
374
|
+
* Holds the SSRC map for the local tracks mapped by their source names.
|
|
375
|
+
*
|
|
376
|
+
* @type {Map<string, TPCSourceInfo>}
|
|
377
|
+
* @property {string} msid - The track's MSID.
|
|
378
|
+
* @property {Array<string>} ssrcs - The SSRCs associated with the track.
|
|
379
|
+
* @property {Array<TPCGroupInfo>} groups - The SSRC groups associated with the track.
|
|
380
|
+
*/
|
|
381
|
+
this._localSsrcMap = null;
|
|
382
|
+
/**
|
|
383
|
+
* Holds the SSRC map for the remote tracks mapped by their source names.
|
|
384
|
+
*
|
|
385
|
+
* @type {Map<string, TPCSourceInfo>}
|
|
386
|
+
* @property {string} mediaType - The media type of the track.
|
|
387
|
+
* @property {string} msid - The track's MSID.
|
|
388
|
+
* @property {Array<TPCGroupInfo>} groups - The SSRC groups associated with the track.
|
|
389
|
+
* @property {Array<string>} ssrcList - The SSRCs associated with the track.
|
|
390
|
+
* @property {VideoType} videoType - The videoType of the track (undefined for audio tracks).
|
|
391
|
+
*/
|
|
392
|
+
this._remoteSsrcMap = new Map();
|
|
393
|
+
// override as desired
|
|
394
|
+
this.trace = (what, info) => {
|
|
395
|
+
logger.trace(what, info);
|
|
396
|
+
this.updateLog.push({
|
|
397
|
+
time: new Date(),
|
|
398
|
+
type: what,
|
|
399
|
+
value: info || ''
|
|
400
|
+
});
|
|
401
|
+
};
|
|
402
|
+
this.onicecandidate = null;
|
|
403
|
+
this.peerconnection.onicecandidate = event => {
|
|
404
|
+
this.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
|
|
405
|
+
if (this.onicecandidate !== null) {
|
|
406
|
+
this.onicecandidate(event);
|
|
407
|
+
}
|
|
408
|
+
};
|
|
409
|
+
this.onTrack = evt => {
|
|
410
|
+
const stream = evt.streams[0];
|
|
411
|
+
this._remoteTrackAdded(stream, evt.track, evt.transceiver);
|
|
412
|
+
stream.addEventListener('removetrack', e => {
|
|
413
|
+
this._remoteTrackRemoved(stream, e.track);
|
|
414
|
+
});
|
|
415
|
+
};
|
|
416
|
+
this.peerconnection.addEventListener('track', this.onTrack);
|
|
417
|
+
this.onsignalingstatechange = null;
|
|
418
|
+
this.peerconnection.onsignalingstatechange = event => {
|
|
419
|
+
this.trace('onsignalingstatechange', this.signalingState);
|
|
420
|
+
if (this.onsignalingstatechange !== null) {
|
|
421
|
+
this.onsignalingstatechange(event);
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
this.oniceconnectionstatechange = null;
|
|
425
|
+
this.peerconnection.oniceconnectionstatechange = event => {
|
|
426
|
+
this.trace('oniceconnectionstatechange', this.iceConnectionState);
|
|
427
|
+
if (this.oniceconnectionstatechange !== null) {
|
|
428
|
+
this.oniceconnectionstatechange(event);
|
|
429
|
+
}
|
|
430
|
+
};
|
|
431
|
+
this.onnegotiationneeded = null;
|
|
432
|
+
this.peerconnection.onnegotiationneeded = event => {
|
|
433
|
+
this.trace('onnegotiationneeded');
|
|
434
|
+
if (this.onnegotiationneeded !== null) {
|
|
435
|
+
this.onnegotiationneeded(event);
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
this.onconnectionstatechange = null;
|
|
439
|
+
this.peerconnection.onconnectionstatechange = event => {
|
|
440
|
+
this.trace('onconnectionstatechange', this.connectionState);
|
|
441
|
+
if (this.onconnectionstatechange !== null) {
|
|
442
|
+
this.onconnectionstatechange(event);
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
this.ondatachannel = null;
|
|
446
|
+
this.peerconnection.ondatachannel = event => {
|
|
447
|
+
this.trace('ondatachannel');
|
|
448
|
+
if (this.ondatachannel !== null) {
|
|
449
|
+
this.ondatachannel(event);
|
|
450
|
+
}
|
|
451
|
+
};
|
|
452
|
+
if (this.maxstats) {
|
|
453
|
+
this.statsinterval = window.setInterval(() => {
|
|
454
|
+
this.getStats().then((stats) => {
|
|
455
|
+
if (typeof stats?.result === 'function') {
|
|
456
|
+
const results = stats.result();
|
|
457
|
+
for (let i = 0; i < results.length; ++i) {
|
|
458
|
+
const res = results[i];
|
|
459
|
+
res.names().forEach(name => {
|
|
460
|
+
this._processStat(res, name, res.stat(name));
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
stats.forEach(r => this._processStat(r, '', r));
|
|
466
|
+
}
|
|
467
|
+
});
|
|
468
|
+
}, 1000);
|
|
469
|
+
}
|
|
470
|
+
this._lastVideoSenderUpdatePromise = Promise.resolve();
|
|
471
|
+
logger.info(`Create new ${this}`);
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Handles remote track mute / unmute events.
|
|
475
|
+
* @param {string} endpointId the track owner's identifier (MUC nickname)
|
|
476
|
+
* @param {MediaType} mediaType "audio" or "video"
|
|
477
|
+
* @param {boolean} isMuted the new mute state
|
|
478
|
+
* @private
|
|
479
|
+
*/
|
|
480
|
+
_peerMutedChanged(endpointId, mediaType, isMuted) {
|
|
481
|
+
// Check if endpointId is a value to avoid doing action on all remote tracks
|
|
482
|
+
if (!endpointId) {
|
|
483
|
+
logger.error(`${this} On peerMuteChanged - no endpoint ID`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
const track = this.getRemoteTracks(endpointId, mediaType);
|
|
487
|
+
if (track.length) {
|
|
488
|
+
// NOTE 1 track per media type is assumed
|
|
489
|
+
track[0].setMute(isMuted);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Handles remote source videoType changed events.
|
|
494
|
+
*
|
|
495
|
+
* @param {string} sourceName - The name of the remote source.
|
|
496
|
+
* @param {boolean} isMuted - The new value.
|
|
497
|
+
*/
|
|
498
|
+
_sourceVideoTypeChanged(sourceName, videoType) {
|
|
499
|
+
const track = this.getRemoteTracks().slice().reverse().find(t => t.getSourceName() === sourceName);
|
|
500
|
+
if (!track) {
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
track._setVideoType(videoType);
|
|
504
|
+
}
|
|
505
|
+
/**
|
|
506
|
+
* Returns the list of RTCRtpReceivers created for the source of the given media type associated with
|
|
507
|
+
* the set of remote endpoints specified.
|
|
508
|
+
* @param {Array<string>} endpoints list of the endpoints
|
|
509
|
+
* @param {string} mediaType 'audio' or 'video'
|
|
510
|
+
* @returns {Array<RTCRtpReceiver>} list of receivers created by the peerconnection.
|
|
511
|
+
*/
|
|
512
|
+
_getReceiversByEndpointIds(endpoints, mediaType) {
|
|
513
|
+
let remoteTracks = [];
|
|
514
|
+
let receivers = [];
|
|
515
|
+
for (const endpoint of endpoints) {
|
|
516
|
+
remoteTracks = remoteTracks.concat(this.getRemoteTracks(endpoint, mediaType));
|
|
517
|
+
}
|
|
518
|
+
// Get the ids of the MediaStreamTracks associated with each of these remote tracks.
|
|
519
|
+
const remoteTrackIds = remoteTracks.map(remote => remote.track?.id);
|
|
520
|
+
receivers = this.peerconnection.getReceivers()
|
|
521
|
+
.filter(receiver => receiver.track
|
|
522
|
+
&& receiver.track.kind === mediaType
|
|
523
|
+
&& remoteTrackIds.find(trackId => trackId === receiver.track.id));
|
|
524
|
+
return receivers;
|
|
525
|
+
}
|
|
526
|
+
/**
|
|
527
|
+
* Handles {@link SignalingEvents.PEER_VIDEO_TYPE_CHANGED}
|
|
528
|
+
* @param {string} endpointId the video owner's ID (MUC nickname)
|
|
529
|
+
* @param {VideoType} videoType the new value
|
|
530
|
+
* @private
|
|
531
|
+
*/
|
|
532
|
+
_peerVideoTypeChanged(endpointId, videoType) {
|
|
533
|
+
// Check if endpointId has a value to avoid action on random track
|
|
534
|
+
if (!endpointId) {
|
|
535
|
+
logger.error(`${this} No endpointID on peerVideoTypeChanged`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
const videoTrack = this.getRemoteTracks(endpointId, MediaType.VIDEO);
|
|
539
|
+
if (videoTrack.length) {
|
|
540
|
+
// NOTE 1 track per media type is assumed
|
|
541
|
+
videoTrack[0]._setVideoType(videoType);
|
|
542
|
+
}
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Adjusts the media direction on the remote description based on availability of local and remote sources in a p2p
|
|
546
|
+
* media connection.
|
|
547
|
+
*
|
|
548
|
+
* @param {RTCSessionDescription} remoteDescription the WebRTC session description
|
|
549
|
+
* instance for the remote description.
|
|
550
|
+
* @returns the transformed remoteDescription.
|
|
551
|
+
* @private
|
|
552
|
+
*/
|
|
553
|
+
_adjustRemoteMediaDirection(remoteDescription) {
|
|
554
|
+
const transformer = new SdpTransformWrap(remoteDescription?.sdp);
|
|
555
|
+
[MediaType.AUDIO, MediaType.VIDEO].forEach(mediaType => {
|
|
556
|
+
const media = transformer.selectMedia(mediaType);
|
|
557
|
+
const localSources = this.getLocalTracks(mediaType).length;
|
|
558
|
+
const remoteSources = this.getRemoteTracks(null, mediaType).length;
|
|
559
|
+
media.forEach((mLine, idx) => {
|
|
560
|
+
if (localSources && localSources === remoteSources) {
|
|
561
|
+
mLine.direction = MediaDirection.SENDRECV;
|
|
562
|
+
}
|
|
563
|
+
else if (!localSources && !remoteSources) {
|
|
564
|
+
mLine.direction = MediaDirection.INACTIVE;
|
|
565
|
+
}
|
|
566
|
+
else if (!localSources) {
|
|
567
|
+
mLine.direction = MediaDirection.SENDONLY;
|
|
568
|
+
}
|
|
569
|
+
else if (!remoteSources) {
|
|
570
|
+
mLine.direction = MediaDirection.RECVONLY;
|
|
571
|
+
// When there are 2 local sources and 1 remote source,
|
|
572
|
+
// the first m-line should be set to 'sendrecv' while
|
|
573
|
+
// the second one needs to be set to 'recvonly'.
|
|
574
|
+
}
|
|
575
|
+
else if (localSources > remoteSources) {
|
|
576
|
+
mLine.direction = idx ? MediaDirection.RECVONLY : MediaDirection.SENDRECV;
|
|
577
|
+
// When there are 2 remote sources and 1 local source, the first m-line
|
|
578
|
+
// should be set to 'sendrecv' while
|
|
579
|
+
// the second one needs to be set to 'sendonly'.
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
mLine.direction = idx ? MediaDirection.SENDONLY : MediaDirection.SENDRECV;
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
});
|
|
586
|
+
return new RTCSessionDescription({
|
|
587
|
+
sdp: transformer.toRawSDP(),
|
|
588
|
+
type: remoteDescription.type
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
/**
|
|
592
|
+
* Returns the codec to be used for screenshare based on the supported codecs and the preferred codec requested
|
|
593
|
+
* through config.js setting.
|
|
594
|
+
*
|
|
595
|
+
* @param {CodecMimeType} defaultCodec - the preferred codec for video tracks.
|
|
596
|
+
* @returns {CodecMimeType}
|
|
597
|
+
*/
|
|
598
|
+
_getPreferredCodecForScreenshare(defaultCodec) {
|
|
599
|
+
// Use the same codec for both camera and screenshare if the client doesn't support the codec selection API.
|
|
600
|
+
if (!this.usesCodecSelectionAPI()) {
|
|
601
|
+
return defaultCodec;
|
|
602
|
+
}
|
|
603
|
+
const { screenshareCodec } = this.codecSettings;
|
|
604
|
+
if (screenshareCodec && this.codecSettings.codecList.find(c => c === screenshareCodec)) {
|
|
605
|
+
return screenshareCodec;
|
|
606
|
+
}
|
|
607
|
+
// Default to AV1 for screenshare if its supported and is not overriden through config.js.
|
|
608
|
+
if (this.codecSettings.codecList.find(c => c === CodecMimeType.AV1)) {
|
|
609
|
+
return CodecMimeType.AV1;
|
|
610
|
+
}
|
|
611
|
+
return defaultCodec;
|
|
612
|
+
}
|
|
613
|
+
/**
|
|
614
|
+
* Sets up the _dtlsTransport object and initializes callbacks for it.
|
|
615
|
+
*/
|
|
616
|
+
_initializeDtlsTransport() {
|
|
617
|
+
// We are assuming here that we only have one bundled transport here
|
|
618
|
+
if (!this.peerconnection.getSenders || this._dtlsTransport) {
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
const senders = this.peerconnection.getSenders();
|
|
622
|
+
if (senders.length !== 0 && senders[0].transport) {
|
|
623
|
+
this._dtlsTransport = senders[0].transport;
|
|
624
|
+
this._dtlsTransport.onerror = error => {
|
|
625
|
+
logger.error(`${this} DtlsTransport error: ${error}`);
|
|
626
|
+
};
|
|
627
|
+
this._dtlsTransport.onstatechange = () => {
|
|
628
|
+
this.trace('dtlsTransport.onstatechange', this._dtlsTransport.state);
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
/**
|
|
633
|
+
* Set the simulcast stream encoding properties on the RTCRtpSender.
|
|
634
|
+
*
|
|
635
|
+
* @param {JitsiLocalTrack} localTrack - the current track in use for which the encodings are to be set.
|
|
636
|
+
* @returns {Promise<void>} - resolved when done.
|
|
637
|
+
*/
|
|
638
|
+
_setEncodings(localTrack) {
|
|
639
|
+
if (localTrack.getType() === MediaType.VIDEO) {
|
|
640
|
+
return this._updateVideoSenderParameters(() => this._configureSenderEncodings(localTrack));
|
|
641
|
+
}
|
|
642
|
+
return this._configureSenderEncodings(localTrack);
|
|
643
|
+
}
|
|
644
|
+
/**
|
|
645
|
+
* Munges the provided description to update the codec order, set the max bitrates (for VP9 K-SVC), set stereo flag
|
|
646
|
+
* and update the DD Header extensions for AV1.
|
|
647
|
+
*
|
|
648
|
+
* @param {RTCSessionDescription} description - The description to be munged.
|
|
649
|
+
* @returns {RTCSessionDescription} - The munged description.
|
|
650
|
+
*/
|
|
651
|
+
_mungeDescription(description) {
|
|
652
|
+
this.trace('RTCSessionDescription::preTransform', TraceablePeerConnection.dumpSDP(description));
|
|
653
|
+
let mungedSdp = transform.parse(description?.sdp);
|
|
654
|
+
mungedSdp = this.tpcUtils.mungeOpus(mungedSdp);
|
|
655
|
+
mungedSdp = this.tpcUtils.mungeCodecOrder(mungedSdp);
|
|
656
|
+
mungedSdp = this.tpcUtils.setMaxBitrates(mungedSdp, true);
|
|
657
|
+
const mungedDescription = new RTCSessionDescription({
|
|
658
|
+
sdp: transform.write(mungedSdp),
|
|
659
|
+
type: description.type
|
|
660
|
+
});
|
|
661
|
+
this.trace('RTCSessionDescription::postTransform', TraceablePeerConnection.dumpSDP(mungedDescription));
|
|
662
|
+
return mungedDescription;
|
|
663
|
+
}
|
|
664
|
+
/**
|
|
665
|
+
* Returns a wrapped-up promise so that the setParameters() call on the RTCRtpSender
|
|
666
|
+
* for video sources are chained.
|
|
667
|
+
* This is needed on Chrome as it resets the transaction id after
|
|
668
|
+
* executing setParameters() and can affect the next on
|
|
669
|
+
* the fly updates if they are not chained.
|
|
670
|
+
* https://chromium.googlesource.com/external/webrtc/+/master/pc/rtp_sender.cc#340
|
|
671
|
+
* @param {Function} nextFunction - The function to be called when the last video sender update promise is settled.
|
|
672
|
+
* @returns {Promise}
|
|
673
|
+
*/
|
|
674
|
+
_updateVideoSenderParameters(nextFunction) {
|
|
675
|
+
const nextPromise = this._lastVideoSenderUpdatePromise
|
|
676
|
+
.finally(nextFunction);
|
|
677
|
+
this._lastVideoSenderUpdatePromise = nextPromise;
|
|
678
|
+
return nextPromise;
|
|
679
|
+
}
|
|
680
|
+
/**
|
|
681
|
+
* Configures the video stream with resolution / degradation / maximum bitrates
|
|
682
|
+
*
|
|
683
|
+
* @param {number} frameHeight - The max frame height to be imposed on the outgoing video stream.
|
|
684
|
+
* @param {JitsiLocalTrack} - The local track for which the sender constraints have to be applied.
|
|
685
|
+
* @param {preferredCodec} - The video codec that needs to be configured on
|
|
686
|
+
* the sender associated with the video source.
|
|
687
|
+
* @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.
|
|
688
|
+
*/
|
|
689
|
+
_updateVideoSenderEncodings(frameHeight, localVideoTrack, preferredCodec) {
|
|
690
|
+
const videoSender = this.findSenderForTrack(localVideoTrack.getTrack());
|
|
691
|
+
const videoType = localVideoTrack.getVideoType();
|
|
692
|
+
const isScreensharingTrack = videoType === VideoType.DESKTOP;
|
|
693
|
+
if (!videoSender) {
|
|
694
|
+
return Promise.resolve();
|
|
695
|
+
}
|
|
696
|
+
const parameters = videoSender.getParameters();
|
|
697
|
+
if (!parameters?.encodings?.length) {
|
|
698
|
+
return Promise.resolve();
|
|
699
|
+
}
|
|
700
|
+
const isSharingLowFpsScreen = isScreensharingTrack && this._capScreenshareBitrate;
|
|
701
|
+
// Set the degradation preference.
|
|
702
|
+
const preference = isSharingLowFpsScreen
|
|
703
|
+
? DEGRADATION_PREFERENCE_DESKTOP // Prefer resolution for low fps share.
|
|
704
|
+
: DEGRADATION_PREFERENCE_CAMERA; // Prefer frame-rate for high fps share and camera.
|
|
705
|
+
parameters.degradationPreference = preference;
|
|
706
|
+
// Calculate the encodings active state based on the resolution requested by the bridge.
|
|
707
|
+
const codecForCamera = preferredCodec ?? this.tpcUtils.getConfiguredVideoCodec(localVideoTrack);
|
|
708
|
+
const codec = isScreensharingTrack ? this._getPreferredCodecForScreenshare(codecForCamera) : codecForCamera;
|
|
709
|
+
const activeState = this.tpcUtils.calculateEncodingsActiveState(localVideoTrack, codec, frameHeight);
|
|
710
|
+
let bitrates = this.tpcUtils.calculateEncodingsBitrates(localVideoTrack, codec, frameHeight);
|
|
711
|
+
const scalabilityModes = this.tpcUtils.calculateEncodingsScalabilityMode(localVideoTrack, codec, frameHeight);
|
|
712
|
+
let scaleFactors = this.tpcUtils.calculateEncodingsScaleFactor(localVideoTrack, codec, frameHeight);
|
|
713
|
+
let needsUpdate = false;
|
|
714
|
+
// Do not configure 'scaleResolutionDownBy' and 'maxBitrate' for encoders running in VP9 legacy K-SVC mode since
|
|
715
|
+
// the browser sends only the lowest resolution layer when those
|
|
716
|
+
// are configured. Those fields need to be reset in
|
|
717
|
+
// case they were set when the endpoint was encoding video using the other codecs before switching over to VP9
|
|
718
|
+
// K-SVC codec.
|
|
719
|
+
if (codec === CodecMimeType.VP9
|
|
720
|
+
&& browser.supportsSVC()
|
|
721
|
+
&& this.isSpatialScalabilityOn()
|
|
722
|
+
&& !this.tpcUtils.codecSettings[codec].scalabilityModeEnabled) {
|
|
723
|
+
scaleFactors = scaleFactors.map(() => undefined);
|
|
724
|
+
bitrates = bitrates.map(() => undefined);
|
|
725
|
+
}
|
|
726
|
+
for (const idx in parameters.encodings) {
|
|
727
|
+
if (parameters.encodings.hasOwnProperty(idx)) {
|
|
728
|
+
const encoding = parameters.encodings[idx];
|
|
729
|
+
const { active = undefined, codec: currentCodec = undefined, maxBitrate = undefined, scalabilityMode = undefined, scaleResolutionDownBy = undefined } = encoding;
|
|
730
|
+
if (active !== activeState[idx]) {
|
|
731
|
+
encoding.active = activeState[idx];
|
|
732
|
+
needsUpdate = true;
|
|
733
|
+
}
|
|
734
|
+
// Firefox doesn't follow the spec and lets application specify the degradation preference on the
|
|
735
|
+
// encodings.
|
|
736
|
+
browser.isFirefox() && (encoding.degradationPreference = preference);
|
|
737
|
+
if (scaleResolutionDownBy !== scaleFactors[idx]) {
|
|
738
|
+
encoding.scaleResolutionDownBy = scaleFactors[idx];
|
|
739
|
+
needsUpdate = true;
|
|
740
|
+
}
|
|
741
|
+
if (maxBitrate !== bitrates[idx]) {
|
|
742
|
+
encoding.maxBitrate = bitrates[idx];
|
|
743
|
+
needsUpdate = true;
|
|
744
|
+
}
|
|
745
|
+
// Configure scalability mode when its supported and enabled.
|
|
746
|
+
if (scalabilityModes) {
|
|
747
|
+
if (scalabilityMode !== scalabilityModes[idx]) {
|
|
748
|
+
encoding.scalabilityMode = scalabilityModes[idx];
|
|
749
|
+
needsUpdate = true;
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
else {
|
|
753
|
+
encoding.scalabilityMode = undefined;
|
|
754
|
+
}
|
|
755
|
+
const expectedPattern = `${MediaType.VIDEO}/${codec.toUpperCase()}`;
|
|
756
|
+
// Configure the codec here if its supported.
|
|
757
|
+
if (this.usesCodecSelectionAPI() && currentCodec?.mimeType !== expectedPattern) {
|
|
758
|
+
const matchingCodec = parameters.codecs.find(pt => pt.mimeType === expectedPattern);
|
|
759
|
+
encoding.codec = matchingCodec;
|
|
760
|
+
needsUpdate = true;
|
|
761
|
+
Statistics.sendAnalytics(AnalyticsEvents.VIDEO_CODEC_CHANGED, {
|
|
762
|
+
value: codec,
|
|
763
|
+
videoType
|
|
764
|
+
});
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
if (!needsUpdate) {
|
|
769
|
+
return Promise.resolve();
|
|
770
|
+
}
|
|
771
|
+
logger.info(`${this} setting max height=${frameHeight},encodings=${JSON.stringify(parameters.encodings)}`);
|
|
772
|
+
return videoSender.setParameters(parameters).then(() => {
|
|
773
|
+
localVideoTrack.maxEnabledResolution = frameHeight;
|
|
774
|
+
this.eventEmitter.emit(RTCEvents.LOCAL_TRACK_MAX_ENABLED_RESOLUTION_CHANGED, localVideoTrack);
|
|
775
|
+
});
|
|
776
|
+
}
|
|
777
|
+
/**
|
|
778
|
+
* Callback ivoked by {@code this._dtmfSender} when it has finished playing
|
|
779
|
+
* a single tone.
|
|
780
|
+
*
|
|
781
|
+
* @param {Object} event - The tonechange event which indicates what characters
|
|
782
|
+
* are left to be played for the current tone.
|
|
783
|
+
* @private
|
|
784
|
+
* @returns {void}
|
|
785
|
+
*/
|
|
786
|
+
_onToneChange(event) {
|
|
787
|
+
// An empty event.tone indicates the current tones have finished playing.
|
|
788
|
+
// Automatically start playing any queued tones on finish.
|
|
789
|
+
if (this._dtmfSender && event.tone === '' && this._dtmfTonesQueue.length) {
|
|
790
|
+
const { tones, duration, interToneGap } = this._dtmfTonesQueue.shift();
|
|
791
|
+
this._dtmfSender.insertDTMF(tones, duration, interToneGap);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
/**
|
|
795
|
+
* Internal method to create an SDP offer or answer for the peer connection.
|
|
796
|
+
* Handles codec preferences, SDP munging for simulcast and RTX, and source information extraction.
|
|
797
|
+
* @private
|
|
798
|
+
*/
|
|
799
|
+
_createOfferOrAnswer(isOffer, constraints) {
|
|
800
|
+
const logName = isOffer ? 'Offer' : 'Answer';
|
|
801
|
+
this.trace(`create${logName}`, JSON.stringify(constraints, null, ' '));
|
|
802
|
+
const handleSuccess = (resultSdp, resolveFn, rejectFn) => {
|
|
803
|
+
try {
|
|
804
|
+
this.trace(`create${logName}OnSuccess::preTransform`, TraceablePeerConnection.dumpSDP(resultSdp));
|
|
805
|
+
// Munge local description to add 3 SSRCs for video tracks when spatial scalability is enabled.
|
|
806
|
+
if (this.isSpatialScalabilityOn() && browser.usesSdpMungingForSimulcast()) {
|
|
807
|
+
// eslint-disable-next-line no-param-reassign
|
|
808
|
+
resultSdp = this.simulcast.mungeLocalDescription(resultSdp);
|
|
809
|
+
this.trace(`create${logName} OnSuccess::postTransform (simulcast)`, TraceablePeerConnection.dumpSDP(resultSdp));
|
|
810
|
+
}
|
|
811
|
+
if (!this.options.disableRtx && browser.usesSdpMungingForSimulcast()) {
|
|
812
|
+
// eslint-disable-next-line no-param-reassign
|
|
813
|
+
resultSdp = new RTCSessionDescription({
|
|
814
|
+
sdp: this.rtxModifier.modifyRtxSsrcs(resultSdp?.sdp),
|
|
815
|
+
type: resultSdp.type
|
|
816
|
+
});
|
|
817
|
+
this.trace(`create${logName}`
|
|
818
|
+
+ 'OnSuccess::postTransform (rtx modifier)', TraceablePeerConnection.dumpSDP(resultSdp));
|
|
819
|
+
}
|
|
820
|
+
if (resultSdp?.sdp) {
|
|
821
|
+
this._processAndExtractSourceInfo(resultSdp.sdp);
|
|
822
|
+
}
|
|
823
|
+
resolveFn(resultSdp);
|
|
824
|
+
}
|
|
825
|
+
catch (e) {
|
|
826
|
+
this.trace(`create${logName}OnError`, e);
|
|
827
|
+
this.trace(`create${logName}OnError`, TraceablePeerConnection.dumpSDP(resultSdp));
|
|
828
|
+
logger.error(`${this} create${logName}OnError`, e, TraceablePeerConnection.dumpSDP(resultSdp));
|
|
829
|
+
rejectFn(e);
|
|
830
|
+
}
|
|
831
|
+
};
|
|
832
|
+
const handleFailure = (err, rejectFn) => {
|
|
833
|
+
this.trace(`create${logName}OnFailure`, err);
|
|
834
|
+
rejectFn(err);
|
|
835
|
+
};
|
|
836
|
+
// Set the codec preference before creating an offer or answer so that the generated SDP will have
|
|
837
|
+
// the correct preference order.
|
|
838
|
+
if (browser.supportsCodecPreferences() && this.codecSettings) {
|
|
839
|
+
const { codecList, mediaType } = this.codecSettings;
|
|
840
|
+
const transceivers = this.peerconnection.getTransceivers()
|
|
841
|
+
.filter(t => t.receiver && t.receiver?.track?.kind === mediaType);
|
|
842
|
+
let capabilities = RTCRtpReceiver.getCapabilities(mediaType)?.codecs;
|
|
843
|
+
if (transceivers.length && capabilities) {
|
|
844
|
+
// Rearrange the codec list as per the preference order.
|
|
845
|
+
for (const codec of codecList.slice().reverse()) {
|
|
846
|
+
// Move the desired codecs (all variations of it as well) to the beginning of the list
|
|
847
|
+
/* eslint-disable-next-line arrow-body-style */
|
|
848
|
+
capabilities.sort(caps => {
|
|
849
|
+
return caps.mimeType.toLowerCase() === `${mediaType}/${codec}` ? -1 : 1;
|
|
850
|
+
});
|
|
851
|
+
}
|
|
852
|
+
// Disable ulpfec and RED on the p2p peerconnection.
|
|
853
|
+
if (this.isP2P && mediaType === MediaType.VIDEO) {
|
|
854
|
+
capabilities = capabilities
|
|
855
|
+
.filter(caps => caps.mimeType.toLowerCase() !== `${MediaType.VIDEO}/${CodecMimeType.ULPFEC}`
|
|
856
|
+
&& caps.mimeType.toLowerCase() !== `${MediaType.VIDEO}/${CodecMimeType.RED}`);
|
|
857
|
+
}
|
|
858
|
+
// Apply codec preference to all the transceivers associated with the given media type.
|
|
859
|
+
for (const transceiver of transceivers) {
|
|
860
|
+
transceiver.setCodecPreferences(capabilities);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
return new Promise((resolve, reject) => {
|
|
865
|
+
let oaPromise;
|
|
866
|
+
if (isOffer) {
|
|
867
|
+
oaPromise = this.peerconnection.createOffer(constraints);
|
|
868
|
+
}
|
|
869
|
+
else {
|
|
870
|
+
oaPromise = this.peerconnection.createAnswer(constraints);
|
|
871
|
+
}
|
|
872
|
+
oaPromise
|
|
873
|
+
.then(sdp => handleSuccess(sdp, resolve, reject), error => handleFailure(error, reject));
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
/**
|
|
877
|
+
* Extract primary SSRC from given {@link ITPCSSRCInfo} object.
|
|
878
|
+
* @param {ITPCSSRCInfo} ssrcObj
|
|
879
|
+
* @return {Nullable<number>} the primary SSRC or <tt>null</tt>
|
|
880
|
+
*/
|
|
881
|
+
_extractPrimarySSRC(ssrcObj) {
|
|
882
|
+
if (ssrcObj?.groups?.length) {
|
|
883
|
+
return ssrcObj.groups[0].ssrcs[0];
|
|
884
|
+
}
|
|
885
|
+
else if (ssrcObj?.ssrcs?.length) {
|
|
886
|
+
return ssrcObj.ssrcs[0];
|
|
887
|
+
}
|
|
888
|
+
return null;
|
|
889
|
+
}
|
|
890
|
+
/**
|
|
891
|
+
* Sends a stats entry to the rtcstats server about the mute state change for local track.
|
|
892
|
+
*
|
|
893
|
+
* @param {JitsiLocalTrack} track - The local track.
|
|
894
|
+
* @param {boolean} muted - muted state.
|
|
895
|
+
*/
|
|
896
|
+
_sendRtcStatsEvent(track, muted) {
|
|
897
|
+
const mediaType = track.getType();
|
|
898
|
+
if (mediaType === MediaType.AUDIO) {
|
|
899
|
+
RTCStats.sendStatsEntry(RTCStatsEvents.AUDIO_MUTE_CHANGED_EVENT, this._pcId, muted);
|
|
900
|
+
}
|
|
901
|
+
else if (track.getVideoType() === VideoType.DESKTOP) {
|
|
902
|
+
RTCStats.sendStatsEntry(RTCStatsEvents.SCREENSHARE_MUTE_CHANGED_EVENT, this._pcId, muted);
|
|
903
|
+
}
|
|
904
|
+
else {
|
|
905
|
+
RTCStats.sendStatsEntry(RTCStatsEvents.VIDEO_MUTE_CHANGED_EVENT, this._pcId, muted);
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
/**
|
|
909
|
+
* Handles remote source mute and unmute changed events.
|
|
910
|
+
*
|
|
911
|
+
* @param {string} sourceName - The name of the remote source.
|
|
912
|
+
* @param {boolean} isMuted - The new mute state.
|
|
913
|
+
* @internal
|
|
914
|
+
*/
|
|
915
|
+
_sourceMutedChanged(sourceName, isMuted) {
|
|
916
|
+
const track = this.getRemoteTracks().slice().reverse().find(t => t.getSourceName() === sourceName);
|
|
917
|
+
if (!track) {
|
|
918
|
+
logger.debug(`Remote track not found for source=${sourceName}, mute update failed!`);
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
track.setMute(isMuted);
|
|
922
|
+
}
|
|
923
|
+
/* eslint-enable max-params */
|
|
924
|
+
/**
|
|
925
|
+
* Process stat and adds it to the array of stats we store.
|
|
926
|
+
* @param report the current stats report.
|
|
927
|
+
* @param name the name of the report, if available
|
|
928
|
+
* @param statValue the value to add.
|
|
929
|
+
* @private
|
|
930
|
+
*/
|
|
931
|
+
_processStat(report, name, statValue) {
|
|
932
|
+
const id = `${report.id}-${name}`;
|
|
933
|
+
let s = this.stats[id];
|
|
934
|
+
const now = new Date();
|
|
935
|
+
if (!s) {
|
|
936
|
+
this.stats[id] = s = {
|
|
937
|
+
endTime: now,
|
|
938
|
+
startTime: now,
|
|
939
|
+
times: [],
|
|
940
|
+
values: []
|
|
941
|
+
};
|
|
942
|
+
}
|
|
943
|
+
s.values.push(statValue);
|
|
944
|
+
s.times.push(now.getTime());
|
|
945
|
+
if (s.values.length > this.maxstats) {
|
|
946
|
+
s.values.shift();
|
|
947
|
+
s.times.shift();
|
|
948
|
+
}
|
|
949
|
+
s.endTime = now;
|
|
950
|
+
}
|
|
951
|
+
/**
|
|
952
|
+
* Forwards the {@link peerconnection.iceConnectionState} state except that it
|
|
953
|
+
* will convert "completed" into "connected" where both mean that the ICE has
|
|
954
|
+
* succeeded and is up and running. We never see "completed" state for
|
|
955
|
+
* the JVB connection, but it started appearing for the P2P one. This method
|
|
956
|
+
* allows to adapt old logic to this new situation.
|
|
957
|
+
* @return {RTCIceConnectionState}
|
|
958
|
+
*/
|
|
959
|
+
getConnectionState() {
|
|
960
|
+
const state = this.peerconnection.iceConnectionState;
|
|
961
|
+
if (state === 'completed') {
|
|
962
|
+
return 'connected';
|
|
963
|
+
}
|
|
964
|
+
return state;
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Obtains the media direction for given {@link MediaType} that needs to be set on a p2p peerconnection's remote SDP
|
|
968
|
+
* after a source-add or source-remove action. The method takes into account whether or not there are any
|
|
969
|
+
* local tracks for the given media type.
|
|
970
|
+
* @param {MediaType} mediaType - The media type for which the direction is to be calculated.
|
|
971
|
+
* @param {boolean} isAddOperation whether the direction is to be calculated after a source-add action.
|
|
972
|
+
* @return {string} one of the SDP direction constants ('sendrecv, 'recvonly' etc.)
|
|
973
|
+
* which should be used when setting
|
|
974
|
+
* local description on the peerconnection.
|
|
975
|
+
* @internal
|
|
976
|
+
*/
|
|
977
|
+
getDesiredMediaDirection(mediaType, isAddOperation = false) {
|
|
978
|
+
return this.tpcUtils.getDesiredMediaDirection(mediaType, isAddOperation);
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Tells whether or not this TPC instance has spatial scalability enabled.
|
|
982
|
+
* @return {boolean} <tt>true</tt> if spatial scalability is enabled and active or
|
|
983
|
+
* <tt>false</tt> if it's turned off.
|
|
984
|
+
*/
|
|
985
|
+
isSpatialScalabilityOn() {
|
|
986
|
+
const h264SimulcastEnabled = this.tpcUtils.codecSettings[CodecMimeType.H264].scalabilityModeEnabled;
|
|
987
|
+
return !this.options.disableSimulcast
|
|
988
|
+
&& (this.codecSettings.codecList[0] !== CodecMimeType.H264 || h264SimulcastEnabled);
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Obtains audio levels of the remote audio tracks by getting the source information on the RTCRtpReceivers.
|
|
992
|
+
* The information relevant to the ssrc is updated each time a RTP packet constaining the ssrc is received.
|
|
993
|
+
* @param {Array<string>} speakerList list of endpoint ids for which audio levels are to be gathered.
|
|
994
|
+
* @returns {Object} containing ssrc and audio level information as a key-value pair.
|
|
995
|
+
*/
|
|
996
|
+
getAudioLevels(speakerList = []) {
|
|
997
|
+
const audioLevels = {};
|
|
998
|
+
const audioReceivers = speakerList.length
|
|
999
|
+
? this._getReceiversByEndpointIds(speakerList, MediaType.AUDIO)
|
|
1000
|
+
: this.peerconnection.getReceivers()
|
|
1001
|
+
.filter(receiver => receiver.track
|
|
1002
|
+
&& receiver.track.kind === MediaType.AUDIO && receiver.track.enabled);
|
|
1003
|
+
audioReceivers.forEach(remote => {
|
|
1004
|
+
const ssrc = remote.getSynchronizationSources();
|
|
1005
|
+
if (ssrc?.length) {
|
|
1006
|
+
// As per spec, this audiolevel is a value between 0..1 (linear), where 1.0
|
|
1007
|
+
// represents 0 dBov, 0 represents silence, and 0.5 represents approximately
|
|
1008
|
+
// 6 dBSPL change in the sound pressure level from 0 dBov.
|
|
1009
|
+
// https://www.w3.org/TR/webrtc/#dom-rtcrtpcontributingsource-audiolevel
|
|
1010
|
+
audioLevels[ssrc[0].source] = ssrc[0].audioLevel;
|
|
1011
|
+
}
|
|
1012
|
+
});
|
|
1013
|
+
return audioLevels;
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Checks if the browser is currently doing true simulcast where in three
|
|
1017
|
+
* different media streams are being sent to the
|
|
1018
|
+
* bridge. Currently this happens always for VP8 and only if simulcast is enabled for VP9/AV1/H264.
|
|
1019
|
+
*
|
|
1020
|
+
* @param {JitsiLocalTrack} localTrack - The local video track.
|
|
1021
|
+
* @returns {boolean}
|
|
1022
|
+
*/
|
|
1023
|
+
doesTrueSimulcast(localTrack) {
|
|
1024
|
+
const currentCodec = this.tpcUtils.getConfiguredVideoCodec(localTrack);
|
|
1025
|
+
return this.isSpatialScalabilityOn() && this.tpcUtils.isRunningInSimulcastMode(currentCodec);
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Returns the SSRCs associated with a given local video track.
|
|
1029
|
+
*
|
|
1030
|
+
* @param {JitsiLocalTrack} localTrack
|
|
1031
|
+
* @returns
|
|
1032
|
+
*/
|
|
1033
|
+
getLocalVideoSSRCs(localTrack) {
|
|
1034
|
+
const ssrcs = [];
|
|
1035
|
+
if (!localTrack?.isVideoTrack()) {
|
|
1036
|
+
return ssrcs;
|
|
1037
|
+
}
|
|
1038
|
+
const ssrcGroup = this.isSpatialScalabilityOn() ? SSRC_GROUP_SEMANTICS.SIM : SSRC_GROUP_SEMANTICS.FID;
|
|
1039
|
+
return this.localSSRCs.get(localTrack.rtcId)
|
|
1040
|
+
?.groups?.find(group => group.semantics === ssrcGroup)?.ssrcs || ssrcs;
|
|
1041
|
+
}
|
|
1042
|
+
/**
|
|
1043
|
+
* Obtains local tracks for given {@link MediaType}. If the <tt>mediaType</tt>
|
|
1044
|
+
* argument is omitted the list of all local tracks will be returned.
|
|
1045
|
+
* @param {MediaType} [mediaType]
|
|
1046
|
+
* @return {Array<JitsiLocalTrack>}
|
|
1047
|
+
*/
|
|
1048
|
+
getLocalTracks(mediaType = undefined) {
|
|
1049
|
+
let tracks = Array.from(this.localTracks.values());
|
|
1050
|
+
if (mediaType !== undefined) {
|
|
1051
|
+
tracks = tracks.filter(track => track.getType() === mediaType);
|
|
1052
|
+
}
|
|
1053
|
+
return tracks;
|
|
1054
|
+
}
|
|
1055
|
+
/**
|
|
1056
|
+
* Retrieves the local video tracks.
|
|
1057
|
+
*
|
|
1058
|
+
* @returns {Array<JitsiLocalTrack>} - local video tracks.
|
|
1059
|
+
*/
|
|
1060
|
+
getLocalVideoTracks() {
|
|
1061
|
+
return this.getLocalTracks(MediaType.VIDEO);
|
|
1062
|
+
}
|
|
1063
|
+
/**
|
|
1064
|
+
* Obtains all remote tracks currently known to this PeerConnection instance.
|
|
1065
|
+
*
|
|
1066
|
+
* @param {optional<string>} [endpointId] - The track owner's identifier (MUC nickname)
|
|
1067
|
+
* @param {optional<MediaType>} [mediaType] - The remote tracks will be filtered by their media type if this argument is
|
|
1068
|
+
* specified.
|
|
1069
|
+
* @return {Array<JitsiRemoteTrack>}
|
|
1070
|
+
*/
|
|
1071
|
+
getRemoteTracks(endpointId = undefined, mediaType = undefined) {
|
|
1072
|
+
let remoteTracks = [];
|
|
1073
|
+
if (FeatureFlags.isSsrcRewritingSupported()) {
|
|
1074
|
+
for (const remoteTrack of this.remoteTracksBySsrc.values()) {
|
|
1075
|
+
const owner = remoteTrack.getParticipantId();
|
|
1076
|
+
if (owner && (!endpointId || owner === endpointId)) {
|
|
1077
|
+
if (!mediaType || remoteTrack.getType() === mediaType) {
|
|
1078
|
+
remoteTracks.push(remoteTrack);
|
|
1079
|
+
}
|
|
1080
|
+
}
|
|
1081
|
+
}
|
|
1082
|
+
return remoteTracks;
|
|
1083
|
+
}
|
|
1084
|
+
const endpoints = endpointId ? [endpointId] : this.remoteTracks.keys();
|
|
1085
|
+
for (const endpoint of endpoints) {
|
|
1086
|
+
const endpointTracksByMediaType = this.remoteTracks.get(endpoint);
|
|
1087
|
+
if (endpointTracksByMediaType) {
|
|
1088
|
+
for (const trackMediaType of endpointTracksByMediaType.keys()) {
|
|
1089
|
+
// per media type filtering
|
|
1090
|
+
if (!mediaType || mediaType === trackMediaType) {
|
|
1091
|
+
remoteTracks = remoteTracks.concat(Array.from(endpointTracksByMediaType.get(trackMediaType)));
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
}
|
|
1096
|
+
return remoteTracks;
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Returns the remote sourceInfo for a given source name.
|
|
1100
|
+
*
|
|
1101
|
+
* @param {string} sourceName - The source name.
|
|
1102
|
+
* @returns {TPCSourceInfo}
|
|
1103
|
+
*/
|
|
1104
|
+
getRemoteSourceInfoBySourceName(sourceName) {
|
|
1105
|
+
return cloneDeep(this._remoteSsrcMap.get(sourceName));
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Returns a map of source names and their associated SSRCs for the remote participant.
|
|
1109
|
+
*
|
|
1110
|
+
* @param {string} id Endpoint id of the remote participant.
|
|
1111
|
+
* @returns {Map<string, TPCSourceInfo>} The map of source names and their associated SSRCs.
|
|
1112
|
+
*/
|
|
1113
|
+
getRemoteSourceInfoByParticipant(id) {
|
|
1114
|
+
const removeSsrcInfo = new Map();
|
|
1115
|
+
const remoteTracks = this.getRemoteTracks(id);
|
|
1116
|
+
if (!remoteTracks?.length) {
|
|
1117
|
+
return removeSsrcInfo;
|
|
1118
|
+
}
|
|
1119
|
+
const primarySsrcs = remoteTracks.map(track => track.getSsrc());
|
|
1120
|
+
for (const [sourceName, sourceInfo] of this._remoteSsrcMap) {
|
|
1121
|
+
if (sourceInfo.ssrcList?.some(ssrc => primarySsrcs.includes(Number(ssrc)))) {
|
|
1122
|
+
removeSsrcInfo.set(sourceName, sourceInfo);
|
|
1123
|
+
}
|
|
1124
|
+
}
|
|
1125
|
+
return removeSsrcInfo;
|
|
1126
|
+
}
|
|
1127
|
+
/**
|
|
1128
|
+
* Returns the target bitrates configured for the local video source.
|
|
1129
|
+
*
|
|
1130
|
+
* @param {JitsiLocalTrack} - The local video track.
|
|
1131
|
+
* @returns {Object}
|
|
1132
|
+
*/
|
|
1133
|
+
getTargetVideoBitrates(localTrack) {
|
|
1134
|
+
const currentCodec = this.tpcUtils.getConfiguredVideoCodec(localTrack);
|
|
1135
|
+
return this.tpcUtils.codecSettings[currentCodec].maxBitratesVideo;
|
|
1136
|
+
}
|
|
1137
|
+
/**
|
|
1138
|
+
* Tries to find {@link JitsiTrack} for given SSRC number. It will search both local and remote tracks bound to this
|
|
1139
|
+
* instance.
|
|
1140
|
+
* @param {number} ssrc
|
|
1141
|
+
* @return {Nullable<JitsiRemoteTrack | JitsiLocalTrack>}
|
|
1142
|
+
*/
|
|
1143
|
+
getTrackBySSRC(ssrc) {
|
|
1144
|
+
if (typeof ssrc !== 'number') {
|
|
1145
|
+
throw new Error(`SSRC ${ssrc} is not a number`);
|
|
1146
|
+
}
|
|
1147
|
+
for (const localTrack of this.localTracks.values()) {
|
|
1148
|
+
const { ssrcs } = this.localSSRCs.get(localTrack.rtcId) ?? { ssrcs: [] };
|
|
1149
|
+
if (ssrcs.find(localSsrc => Number(localSsrc) === ssrc)) {
|
|
1150
|
+
return localTrack;
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
if (FeatureFlags.isSsrcRewritingSupported()) {
|
|
1154
|
+
return this.remoteTracksBySsrc.get(ssrc);
|
|
1155
|
+
}
|
|
1156
|
+
for (const remoteTrack of this.getRemoteTracks()) {
|
|
1157
|
+
if (remoteTrack.getSsrc() === ssrc) {
|
|
1158
|
+
return remoteTrack;
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
return null;
|
|
1162
|
+
}
|
|
1163
|
+
/**
|
|
1164
|
+
* Tries to find SSRC number for given {@link JitsiTrack} id. It will search
|
|
1165
|
+
* both local and remote tracks bound to this instance.
|
|
1166
|
+
* @param {string} id
|
|
1167
|
+
* @return {Nullable<number>}
|
|
1168
|
+
*/
|
|
1169
|
+
getSsrcByTrackId(id) {
|
|
1170
|
+
const findTrackById = track => track.getTrack().id === id;
|
|
1171
|
+
const localTrack = this.getLocalTracks().find(findTrackById);
|
|
1172
|
+
if (localTrack) {
|
|
1173
|
+
return this.getLocalSSRC(localTrack);
|
|
1174
|
+
}
|
|
1175
|
+
const remoteTrack = this.getRemoteTracks().find(findTrackById);
|
|
1176
|
+
if (remoteTrack) {
|
|
1177
|
+
return remoteTrack.getSsrc();
|
|
1178
|
+
}
|
|
1179
|
+
return null;
|
|
1180
|
+
}
|
|
1181
|
+
/**
|
|
1182
|
+
* Called on "track added" and "stream added" PeerConnection events (because we
|
|
1183
|
+
* handle streams on per track basis). Finds the owner and the SSRC for
|
|
1184
|
+
* the track and passes that to ChatRoom for further processing.
|
|
1185
|
+
* @param {MediaStream} stream the WebRTC MediaStream instance which is
|
|
1186
|
+
* the parent of the track
|
|
1187
|
+
* @param {MediaStreamTrack} track the WebRTC MediaStreamTrack added for remote
|
|
1188
|
+
* participant.
|
|
1189
|
+
* @param {RTCRtpTransceiver} transceiver the WebRTC transceiver that is created
|
|
1190
|
+
* for the remote participant in unified plan.
|
|
1191
|
+
*/
|
|
1192
|
+
_remoteTrackAdded(stream, track, transceiver = null) {
|
|
1193
|
+
const streamId = stream.id;
|
|
1194
|
+
const mediaType = track.kind;
|
|
1195
|
+
// Do not create remote tracks for 'mixed' JVB SSRCs (used by JVB for RTCP termination).
|
|
1196
|
+
if (!this.isP2P && !RTCUtils.isUserStreamById(streamId)) {
|
|
1197
|
+
return;
|
|
1198
|
+
}
|
|
1199
|
+
logger.info(`${this} Received track event for remote stream[id=${streamId},type=${mediaType}]`);
|
|
1200
|
+
// look up an associated JID for a stream id
|
|
1201
|
+
if (!mediaType) {
|
|
1202
|
+
logger.error(`MediaType undefined for remote track, stream id: ${streamId}, track creation failed!`);
|
|
1203
|
+
return;
|
|
1204
|
+
}
|
|
1205
|
+
const remoteSDP = new SDP(this.remoteDescription?.sdp);
|
|
1206
|
+
let mediaLine;
|
|
1207
|
+
// Find the matching mline using 'mid' or the 'msid' attr of the stream.
|
|
1208
|
+
if (transceiver?.mid) {
|
|
1209
|
+
const mid = transceiver.mid;
|
|
1210
|
+
// @ts-ignore
|
|
1211
|
+
mediaLine = remoteSDP.media.find(mls => SDPUtil.findLine(mls, `a=mid:${mid}`));
|
|
1212
|
+
}
|
|
1213
|
+
else {
|
|
1214
|
+
mediaLine = remoteSDP.media.find(mls => {
|
|
1215
|
+
// @ts-ignore
|
|
1216
|
+
const msid = SDPUtil.findLine(mls, 'a=msid:');
|
|
1217
|
+
return typeof msid === 'string' && streamId === msid.substring(7).split(' ')[0];
|
|
1218
|
+
});
|
|
1219
|
+
}
|
|
1220
|
+
if (!mediaLine) {
|
|
1221
|
+
logger.error(`Matching media line not found in remote SDP for remote stream[id=${streamId},type=${mediaType}],`
|
|
1222
|
+
+ 'track creation failed!');
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
// @ts-ignore
|
|
1226
|
+
let ssrcLines = SDPUtil.findLines(mediaLine, 'a=ssrc:');
|
|
1227
|
+
ssrcLines = ssrcLines.filter(line => line.indexOf(`msid:${streamId}`) !== -1);
|
|
1228
|
+
if (!ssrcLines.length) {
|
|
1229
|
+
logger.error(`No SSRC lines found in remote SDP for remote stream[msid=${streamId},type=${mediaType}]`
|
|
1230
|
+
+ 'track creation failed!');
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
// FIXME the length of ssrcLines[0] not verified, but it will fail
|
|
1234
|
+
// with global error handler anyway
|
|
1235
|
+
const ssrcStr = ssrcLines[0].substring(7).split(' ')[0];
|
|
1236
|
+
const trackSsrc = Number(ssrcStr);
|
|
1237
|
+
const ownerEndpointId = this._signalingLayer.getSSRCOwner(trackSsrc);
|
|
1238
|
+
if (!isValidNumber(trackSsrc) || trackSsrc < 0) {
|
|
1239
|
+
logger.error(`Invalid SSRC for remote stream[ssrc=${trackSsrc},id=${streamId},type=${mediaType}]`
|
|
1240
|
+
+ 'track creation failed!');
|
|
1241
|
+
return;
|
|
1242
|
+
}
|
|
1243
|
+
if (!ownerEndpointId) {
|
|
1244
|
+
logger.error(`No SSRC owner known for remote stream[ssrc=${trackSsrc},id=${streamId},type=${mediaType}]`
|
|
1245
|
+
+ 'track creation failed!');
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
1248
|
+
const sourceName = this._signalingLayer.getTrackSourceName(trackSsrc);
|
|
1249
|
+
const peerMediaInfo = this._signalingLayer.getPeerMediaInfo(ownerEndpointId, mediaType, sourceName);
|
|
1250
|
+
const trackDetails = {
|
|
1251
|
+
mediaType,
|
|
1252
|
+
muted: peerMediaInfo?.muted ?? true,
|
|
1253
|
+
ssrc: trackSsrc,
|
|
1254
|
+
stream,
|
|
1255
|
+
track,
|
|
1256
|
+
videoType: peerMediaInfo?.videoType
|
|
1257
|
+
};
|
|
1258
|
+
if (this._remoteSsrcMap.has(sourceName) && mediaType === MediaType.VIDEO) {
|
|
1259
|
+
trackDetails.videoType = this._remoteSsrcMap.get(sourceName).videoType;
|
|
1260
|
+
}
|
|
1261
|
+
this._createRemoteTrack(ownerEndpointId, sourceName, trackDetails);
|
|
1262
|
+
}
|
|
1263
|
+
/**
|
|
1264
|
+
* Initializes a new JitsiRemoteTrack instance with the data provided by the signaling layer and SDP.
|
|
1265
|
+
*
|
|
1266
|
+
* @param {string} ownerEndpointId - The owner's endpoint ID (MUC nickname)
|
|
1267
|
+
* @param {String} sourceName - The track's source name
|
|
1268
|
+
* @param {Object} trackDetails - The track's details.
|
|
1269
|
+
* @param {MediaType} trackDetails.mediaType - media type, 'audio' or 'video'.
|
|
1270
|
+
* @param {boolean} trackDetails.muted - The initial muted status.
|
|
1271
|
+
* @param {number} trackDetails.ssrc - The track's main SSRC number.
|
|
1272
|
+
* @param {MediaStream} trackDetails.stream - The WebRTC stream instance.
|
|
1273
|
+
* @param {MediaStreamTrack} trackDetails.track - The WebRTC track instance.
|
|
1274
|
+
* @param {VideoType} trackDetails.videoType - The track's type of the video (if applicable).
|
|
1275
|
+
*/
|
|
1276
|
+
_createRemoteTrack(ownerEndpointId, sourceName, trackDetails) {
|
|
1277
|
+
const { mediaType, muted, ssrc, stream, track, videoType } = trackDetails;
|
|
1278
|
+
logger.info(`${this} creating remote track[endpoint=${ownerEndpointId},ssrc=${ssrc},`
|
|
1279
|
+
+ `type=${mediaType},sourceName=${sourceName}]`);
|
|
1280
|
+
let remoteTracksMap;
|
|
1281
|
+
let userTracksByMediaType;
|
|
1282
|
+
if (FeatureFlags.isSsrcRewritingSupported()) {
|
|
1283
|
+
const existingTrack = this.remoteTracksBySsrc.get(ssrc);
|
|
1284
|
+
if (existingTrack) {
|
|
1285
|
+
logger.info(`${this} ignored duplicated track event for SSRC[ssrc=${ssrc},type=${mediaType}]`);
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
else {
|
|
1290
|
+
remoteTracksMap = this.remoteTracks.get(ownerEndpointId);
|
|
1291
|
+
if (!remoteTracksMap) {
|
|
1292
|
+
remoteTracksMap = new Map();
|
|
1293
|
+
remoteTracksMap.set(MediaType.AUDIO, new Set());
|
|
1294
|
+
remoteTracksMap.set(MediaType.VIDEO, new Set());
|
|
1295
|
+
this.remoteTracks.set(ownerEndpointId, remoteTracksMap);
|
|
1296
|
+
}
|
|
1297
|
+
userTracksByMediaType = remoteTracksMap.get(mediaType);
|
|
1298
|
+
if (userTracksByMediaType?.size
|
|
1299
|
+
&& Array.from(userTracksByMediaType).find((jitsiTrack) => jitsiTrack.getTrack() === track)) {
|
|
1300
|
+
// Ignore duplicated event which can originate either from 'onStreamAdded' or 'onTrackAdded'.
|
|
1301
|
+
logger.info(`${this} ignored duplicated track event for track[endpoint=${ownerEndpointId},`
|
|
1302
|
+
+ `type=${mediaType}]`);
|
|
1303
|
+
return;
|
|
1304
|
+
}
|
|
1305
|
+
}
|
|
1306
|
+
const remoteTrack = new JitsiRemoteTrack(this.rtc, this.rtc.conference, ownerEndpointId, stream, track, mediaType, videoType, ssrc, muted, this.isP2P, sourceName);
|
|
1307
|
+
if (FeatureFlags.isSsrcRewritingSupported()) {
|
|
1308
|
+
this.remoteTracksBySsrc.set(ssrc, remoteTrack);
|
|
1309
|
+
}
|
|
1310
|
+
else {
|
|
1311
|
+
userTracksByMediaType.add(remoteTrack);
|
|
1312
|
+
}
|
|
1313
|
+
this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_ADDED, remoteTrack, this);
|
|
1314
|
+
}
|
|
1315
|
+
/**
|
|
1316
|
+
* Handles remote media track removal.
|
|
1317
|
+
*
|
|
1318
|
+
* @param {MediaStream} stream - WebRTC MediaStream instance which is the parent of the track.
|
|
1319
|
+
* @param {MediaStreamTrack} track - WebRTC MediaStreamTrack which has been removed from the PeerConnection.
|
|
1320
|
+
* @returns {void}
|
|
1321
|
+
*/
|
|
1322
|
+
_remoteTrackRemoved(stream, track) {
|
|
1323
|
+
const streamId = stream.id;
|
|
1324
|
+
const trackId = track?.id;
|
|
1325
|
+
// Ignore stream removed events for JVB "mixed" sources (used for RTCP termination).
|
|
1326
|
+
if (!RTCUtils.isUserStreamById(streamId)) {
|
|
1327
|
+
return;
|
|
1328
|
+
}
|
|
1329
|
+
if (!streamId) {
|
|
1330
|
+
logger.error(`${this} remote track removal failed - no stream ID`);
|
|
1331
|
+
return;
|
|
1332
|
+
}
|
|
1333
|
+
if (!trackId) {
|
|
1334
|
+
logger.error(`${this} remote track removal failed - no track ID`);
|
|
1335
|
+
return;
|
|
1336
|
+
}
|
|
1337
|
+
const toBeRemoved = this.getRemoteTracks().find(remoteTrack => remoteTrack.getStreamId() === streamId && remoteTrack.getTrackId() === trackId);
|
|
1338
|
+
if (!toBeRemoved) {
|
|
1339
|
+
logger.error(`${this} remote track removal failed - track not found`);
|
|
1340
|
+
return;
|
|
1341
|
+
}
|
|
1342
|
+
this._removeRemoteTrack(toBeRemoved);
|
|
1343
|
+
}
|
|
1344
|
+
/**
|
|
1345
|
+
* Removes and disposes given <tt>JitsiRemoteTrack</tt> instance. Emits {@link RTCEvents.REMOTE_TRACK_REMOVED}.
|
|
1346
|
+
*
|
|
1347
|
+
* @param {JitsiRemoteTrack} toBeRemoved - The remote track to be removed.
|
|
1348
|
+
* @returns {void}
|
|
1349
|
+
*/
|
|
1350
|
+
_removeRemoteTrack(toBeRemoved) {
|
|
1351
|
+
logger.info(`${this} Removing remote track stream[id=${toBeRemoved.getStreamId()},`
|
|
1352
|
+
+ `trackId=${toBeRemoved.getTrackId()}]`);
|
|
1353
|
+
toBeRemoved.dispose();
|
|
1354
|
+
const participantId = toBeRemoved.getParticipantId();
|
|
1355
|
+
if (FeatureFlags.isSsrcRewritingSupported() && !participantId) {
|
|
1356
|
+
return;
|
|
1357
|
+
}
|
|
1358
|
+
else if (!FeatureFlags.isSsrcRewritingSupported()) {
|
|
1359
|
+
const userTracksByMediaType = this.remoteTracks.get(participantId);
|
|
1360
|
+
if (!userTracksByMediaType) {
|
|
1361
|
+
logger.error(`${this} removeRemoteTrack: no remote tracks map for endpoint=${participantId}`);
|
|
1362
|
+
}
|
|
1363
|
+
else if (!userTracksByMediaType.get(toBeRemoved.getType())?.delete(toBeRemoved)) {
|
|
1364
|
+
logger.error(`${this} Failed to remove ${toBeRemoved} - type mapping messed up ?`);
|
|
1365
|
+
}
|
|
1366
|
+
}
|
|
1367
|
+
this.eventEmitter.emit(RTCEvents.REMOTE_TRACK_REMOVED, toBeRemoved);
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Processes the local SDP and creates an SSRC map for every local track.
|
|
1371
|
+
*
|
|
1372
|
+
* @param {string} localSDP - SDP from the local description.
|
|
1373
|
+
* @returns {void}
|
|
1374
|
+
*/
|
|
1375
|
+
_processAndExtractSourceInfo(localSDP) {
|
|
1376
|
+
/**
|
|
1377
|
+
* @type {Map<string, TPCSourceInfo>} The map of source names and their associated SSRCs.
|
|
1378
|
+
*/
|
|
1379
|
+
const ssrcMap = new Map();
|
|
1380
|
+
if (!localSDP || typeof localSDP !== 'string') {
|
|
1381
|
+
throw new Error('Local SDP must be a valid string, aborting!!');
|
|
1382
|
+
}
|
|
1383
|
+
const session = transform.parse(localSDP);
|
|
1384
|
+
const media = session.media.filter(mline => mline.direction === MediaDirection.SENDONLY
|
|
1385
|
+
|| mline.direction === MediaDirection.SENDRECV);
|
|
1386
|
+
if (!media.length) {
|
|
1387
|
+
this._localSsrcMap = ssrcMap;
|
|
1388
|
+
return;
|
|
1389
|
+
}
|
|
1390
|
+
for (const localTrack of this.localTracks.values()) {
|
|
1391
|
+
const sourceName = localTrack.getSourceName();
|
|
1392
|
+
const trackIndex = getSourceIndexFromSourceName(sourceName);
|
|
1393
|
+
const mediaType = localTrack.getType();
|
|
1394
|
+
const mLines = media.filter(m => m.type === mediaType);
|
|
1395
|
+
const ssrcGroups = mLines[trackIndex].ssrcGroups;
|
|
1396
|
+
let ssrcs = mLines[trackIndex].ssrcs;
|
|
1397
|
+
if (ssrcs?.length) {
|
|
1398
|
+
// Filter the ssrcs with 'cname' attribute.
|
|
1399
|
+
ssrcs = ssrcs.filter(s => s.attribute === 'cname');
|
|
1400
|
+
const msid = `${this.rtc.getLocalEndpointId()}-${mediaType}-${trackIndex}`;
|
|
1401
|
+
const ssrcInfo = {
|
|
1402
|
+
groups: [],
|
|
1403
|
+
msid,
|
|
1404
|
+
ssrcs: []
|
|
1405
|
+
};
|
|
1406
|
+
ssrcs.forEach(ssrc => ssrcInfo.ssrcs.push(ssrc.id));
|
|
1407
|
+
if (ssrcGroups?.length) {
|
|
1408
|
+
for (const group of ssrcGroups) {
|
|
1409
|
+
const parsedGroup = {
|
|
1410
|
+
semantics: group.semantics,
|
|
1411
|
+
ssrcs: group.ssrcs.split(' ').map(ssrcStr => parseInt(ssrcStr, 10))
|
|
1412
|
+
};
|
|
1413
|
+
ssrcInfo.groups.push(parsedGroup);
|
|
1414
|
+
}
|
|
1415
|
+
const simGroup = ssrcGroups.find(group => group.semantics === SSRC_GROUP_SEMANTICS.SIM);
|
|
1416
|
+
// Add a SIM group if its missing in the description (happens on Firefox).
|
|
1417
|
+
if (this.isSpatialScalabilityOn() && !simGroup) {
|
|
1418
|
+
const groupSsrcs = ssrcGroups.map(group => group.ssrcs[0]);
|
|
1419
|
+
ssrcInfo.groups.push({
|
|
1420
|
+
semantics: SSRC_GROUP_SEMANTICS.SIM,
|
|
1421
|
+
ssrcs: groupSsrcs
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
ssrcMap.set(sourceName, ssrcInfo);
|
|
1426
|
+
const oldSsrcInfo = this.localSSRCs.get(localTrack.rtcId);
|
|
1427
|
+
const oldSsrc = this._extractPrimarySSRC(oldSsrcInfo);
|
|
1428
|
+
const newSsrc = this._extractPrimarySSRC(ssrcInfo);
|
|
1429
|
+
if (oldSsrc !== newSsrc) {
|
|
1430
|
+
oldSsrc && logger.debug(`${this} Overwriting SSRC for track=${localTrack}] with ssrc=${newSsrc}`);
|
|
1431
|
+
this.localSSRCs.set(localTrack.rtcId, ssrcInfo);
|
|
1432
|
+
localTrack.setSsrc(newSsrc);
|
|
1433
|
+
}
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
this._localSsrcMap = ssrcMap;
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* @param {JitsiLocalTrack} localTrack
|
|
1440
|
+
* @returns {Nullable<number>}
|
|
1441
|
+
*/
|
|
1442
|
+
getLocalSSRC(localTrack) {
|
|
1443
|
+
const ssrcInfo = this._getSSRC(localTrack.rtcId.toString());
|
|
1444
|
+
return ssrcInfo?.ssrcs[0];
|
|
1445
|
+
}
|
|
1446
|
+
/**
|
|
1447
|
+
* Gets the signaling state of the peer connection.
|
|
1448
|
+
*/
|
|
1449
|
+
get signalingState() {
|
|
1450
|
+
return this.peerconnection.signalingState;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Gets the ICE connection state of the peer connection.
|
|
1454
|
+
*/
|
|
1455
|
+
get iceConnectionState() {
|
|
1456
|
+
return this.peerconnection.iceConnectionState;
|
|
1457
|
+
}
|
|
1458
|
+
/**
|
|
1459
|
+
* Gets the connection state of the peer connection.
|
|
1460
|
+
*/
|
|
1461
|
+
get connectionState() {
|
|
1462
|
+
return this.peerconnection.connectionState;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Gets the local description of the peer connection, with optional transformations for
|
|
1466
|
+
* simulcast and stream identifiers.
|
|
1467
|
+
*/
|
|
1468
|
+
get localDescription() {
|
|
1469
|
+
let desc = this.peerconnection.localDescription;
|
|
1470
|
+
if (!desc) {
|
|
1471
|
+
logger.debug(`${this} getLocalDescription no localDescription found`);
|
|
1472
|
+
// @ts-ignore
|
|
1473
|
+
return {};
|
|
1474
|
+
}
|
|
1475
|
+
this.trace('getLocalDescription::preTransform', TraceablePeerConnection.dumpSDP(desc));
|
|
1476
|
+
if (!this.isP2P) {
|
|
1477
|
+
desc = this.tpcUtils.injectSsrcGroupForSimulcast(desc);
|
|
1478
|
+
this.trace('getLocalDescription::postTransform (inject ssrc group)', TraceablePeerConnection.dumpSDP(desc));
|
|
1479
|
+
}
|
|
1480
|
+
desc = this.localSdpMunger.transformStreamIdentifiers(desc, this._localSsrcMap);
|
|
1481
|
+
return desc;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Gets the remote description of the peer connection, with optional adjustments for media direction in P2P mode.
|
|
1485
|
+
*/
|
|
1486
|
+
get remoteDescription() {
|
|
1487
|
+
let desc = this.peerconnection.remoteDescription;
|
|
1488
|
+
if (!desc) {
|
|
1489
|
+
logger.debug(`${this} getRemoteDescription no remoteDescription found`);
|
|
1490
|
+
// @ts-ignore
|
|
1491
|
+
return {};
|
|
1492
|
+
}
|
|
1493
|
+
this.trace('getRemoteDescription::preTransform', TraceablePeerConnection.dumpSDP(desc));
|
|
1494
|
+
if (this.isP2P) {
|
|
1495
|
+
desc = this._adjustRemoteMediaDirection(desc);
|
|
1496
|
+
}
|
|
1497
|
+
return desc;
|
|
1498
|
+
}
|
|
1499
|
+
/**
|
|
1500
|
+
* Retrieves the SSRC (Synchronization Source) identifier associated with the given RTC ID.
|
|
1501
|
+
* @private
|
|
1502
|
+
*/
|
|
1503
|
+
_getSSRC(rtcId) {
|
|
1504
|
+
return this.localSSRCs.get(Number(rtcId));
|
|
1505
|
+
}
|
|
1506
|
+
/**
|
|
1507
|
+
* Checks if low fps screensharing is in progress.
|
|
1508
|
+
*
|
|
1509
|
+
* @private
|
|
1510
|
+
* @returns {boolean} Returns true if 5 fps screensharing is in progress, false otherwise.
|
|
1511
|
+
*/
|
|
1512
|
+
isSharingLowFpsScreen() {
|
|
1513
|
+
return this._isSharingScreen() && this._capScreenshareBitrate;
|
|
1514
|
+
}
|
|
1515
|
+
/**
|
|
1516
|
+
* Checks if screensharing is in progress.
|
|
1517
|
+
*
|
|
1518
|
+
* @returns {boolean} Returns true if a desktop track has been added to the peerconnection, false otherwise.
|
|
1519
|
+
*/
|
|
1520
|
+
_isSharingScreen() {
|
|
1521
|
+
const tracks = this.getLocalVideoTracks();
|
|
1522
|
+
return Boolean(tracks.find(track => track.videoType === VideoType.DESKTOP));
|
|
1523
|
+
}
|
|
1524
|
+
/**
|
|
1525
|
+
* Adds local track to the RTCPeerConnection.
|
|
1526
|
+
*
|
|
1527
|
+
* @param {JitsiLocalTrack} track the track to be added to the pc.
|
|
1528
|
+
* @return {Promise<boolean>} Promise that resolves to true if the underlying PeerConnection's state has changed and
|
|
1529
|
+
* renegotiation is required, false if no renegotiation is needed or Promise is rejected when something goes wrong.
|
|
1530
|
+
*/
|
|
1531
|
+
addTrackToPc(track) {
|
|
1532
|
+
logger.info(`${this} Adding track=${track} to PC`);
|
|
1533
|
+
if (!this._assertTrackBelongs('addTrackToPc', track)) {
|
|
1534
|
+
// Abort
|
|
1535
|
+
return Promise.reject('Track not found on the peerconnection');
|
|
1536
|
+
}
|
|
1537
|
+
const webRtcStream = track.getOriginalStream();
|
|
1538
|
+
if (!webRtcStream) {
|
|
1539
|
+
logger.error(`${this} Unable to add track=${track} to PC - no WebRTC stream`);
|
|
1540
|
+
return Promise.reject('Stream not found');
|
|
1541
|
+
}
|
|
1542
|
+
return this.replaceTrack(null, track, true /* isMuteOperation */).then(() => {
|
|
1543
|
+
if (track) {
|
|
1544
|
+
if (track.isAudioTrack()) {
|
|
1545
|
+
this._hasHadAudioTrack = true;
|
|
1546
|
+
}
|
|
1547
|
+
else {
|
|
1548
|
+
this._hasHadVideoTrack = true;
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
return false;
|
|
1552
|
+
});
|
|
1553
|
+
}
|
|
1554
|
+
/**
|
|
1555
|
+
* This method when called will check if given <tt>localTrack</tt> belongs to
|
|
1556
|
+
* this TPC (that it has been previously added using {@link addTrack}). If the
|
|
1557
|
+
* track does not belong an error message will be logged.
|
|
1558
|
+
* @param {string} methodName the method name that will be logged in an error
|
|
1559
|
+
* message
|
|
1560
|
+
* @param {JitsiLocalTrack} localTrack
|
|
1561
|
+
* @return {boolean} <tt>true</tt> if given local track belongs to this TPC or
|
|
1562
|
+
* <tt>false</tt> otherwise.
|
|
1563
|
+
* @private
|
|
1564
|
+
*/
|
|
1565
|
+
_assertTrackBelongs(methodName, localTrack) {
|
|
1566
|
+
const doesBelong = this.localTracks.has(localTrack?.rtcId);
|
|
1567
|
+
if (!doesBelong) {
|
|
1568
|
+
logger.error(`${this} ${methodName}: track=${localTrack} does not belong to pc`);
|
|
1569
|
+
}
|
|
1570
|
+
return doesBelong;
|
|
1571
|
+
}
|
|
1572
|
+
/**
|
|
1573
|
+
* Returns the codecs in the current order of preference as configured on the peerconnection.
|
|
1574
|
+
*
|
|
1575
|
+
* @param {RTCSessionDescription} - The local description to be used.
|
|
1576
|
+
* @returns {Array}
|
|
1577
|
+
*/
|
|
1578
|
+
getConfiguredVideoCodecs(description) {
|
|
1579
|
+
return this.tpcUtils.getConfiguredVideoCodecs(description?.sdp);
|
|
1580
|
+
}
|
|
1581
|
+
/**
|
|
1582
|
+
* Enables or disables simulcast for screenshare based on the frame rate requested for desktop track capture.
|
|
1583
|
+
*
|
|
1584
|
+
* @param {number} maxFps framerate to be used for desktop track capture.
|
|
1585
|
+
*/
|
|
1586
|
+
setDesktopSharingFrameRate(maxFps) {
|
|
1587
|
+
const lowFps = maxFps <= SS_DEFAULT_FRAME_RATE;
|
|
1588
|
+
this._capScreenshareBitrate = this.isSpatialScalabilityOn() && lowFps;
|
|
1589
|
+
}
|
|
1590
|
+
/**
|
|
1591
|
+
* Sets the codec preference on the peerconnection. The codec preference goes into effect when
|
|
1592
|
+
* the next renegotiation happens for older clients that do not support the codec selection API.
|
|
1593
|
+
*
|
|
1594
|
+
* @param {Array<CodecMimeType>} codecList - Preferred codecs for video.
|
|
1595
|
+
* @param {CodecMimeType} screenshareCodec - The preferred codec for screenshare.
|
|
1596
|
+
* @returns {boolean} - Returns true if the codec settings were updated, false otherwise.
|
|
1597
|
+
*/
|
|
1598
|
+
setVideoCodecs(codecList, screenshareCodec) {
|
|
1599
|
+
let updated = false;
|
|
1600
|
+
if (!this.codecSettings || !codecList?.length) {
|
|
1601
|
+
return updated;
|
|
1602
|
+
}
|
|
1603
|
+
this.codecSettings.codecList = codecList;
|
|
1604
|
+
if (screenshareCodec) {
|
|
1605
|
+
this.codecSettings.screenshareCodec = screenshareCodec;
|
|
1606
|
+
}
|
|
1607
|
+
if (!this.usesCodecSelectionAPI()) {
|
|
1608
|
+
return updated;
|
|
1609
|
+
}
|
|
1610
|
+
for (const track of this.getLocalVideoTracks()) {
|
|
1611
|
+
const currentCodec = this.tpcUtils.getConfiguredVideoCodec(track);
|
|
1612
|
+
if (screenshareCodec && track.getVideoType() === VideoType.DESKTOP && screenshareCodec !== currentCodec) {
|
|
1613
|
+
this.configureVideoSenderEncodings(track, screenshareCodec);
|
|
1614
|
+
updated = true;
|
|
1615
|
+
}
|
|
1616
|
+
else if (currentCodec !== codecList[0]) {
|
|
1617
|
+
this.configureVideoSenderEncodings(track);
|
|
1618
|
+
updated = true;
|
|
1619
|
+
}
|
|
1620
|
+
}
|
|
1621
|
+
updated && RTCStats.sendStatsEntry(RTCStatsEvents.CODEC_CHANGED_EVENT, this._pcId, {
|
|
1622
|
+
camera: codecList[0],
|
|
1623
|
+
screenshare: screenshareCodec
|
|
1624
|
+
});
|
|
1625
|
+
return updated;
|
|
1626
|
+
}
|
|
1627
|
+
/**
|
|
1628
|
+
* Remove local track from this TPC.
|
|
1629
|
+
* @param {JitsiLocalTrack} localTrack the track to be removed from this TPC.
|
|
1630
|
+
*
|
|
1631
|
+
* FIXME It should probably remove a boolean just like {@link removeTrackFromPc}
|
|
1632
|
+
* The same applies to addTrack.
|
|
1633
|
+
*/
|
|
1634
|
+
removeTrack(localTrack) {
|
|
1635
|
+
const webRtcStream = localTrack.getOriginalStream();
|
|
1636
|
+
this.trace('removeStream', localTrack.rtcId.toString(), webRtcStream ? webRtcStream.id : undefined);
|
|
1637
|
+
if (!this._assertTrackBelongs('removeStream', localTrack)) {
|
|
1638
|
+
// Abort - nothing to be done here
|
|
1639
|
+
return;
|
|
1640
|
+
}
|
|
1641
|
+
this.localTracks.delete(localTrack.rtcId);
|
|
1642
|
+
this.localSSRCs.delete(localTrack.rtcId);
|
|
1643
|
+
if (webRtcStream) {
|
|
1644
|
+
// @ts-ignore
|
|
1645
|
+
this.peerconnection.removeStream(webRtcStream);
|
|
1646
|
+
}
|
|
1647
|
+
}
|
|
1648
|
+
/**
|
|
1649
|
+
* Returns the receiver corresponding to the given MediaStreamTrack.
|
|
1650
|
+
*
|
|
1651
|
+
* @param {MediaSreamTrack} track - The media stream track used for the search.
|
|
1652
|
+
* @returns {Optional<RTCRtpReceiver>} - The found receiver or undefined if no receiver
|
|
1653
|
+
* was found.
|
|
1654
|
+
*/
|
|
1655
|
+
findReceiverForTrack(track) {
|
|
1656
|
+
return this.peerconnection.getReceivers().find(r => r.track === track);
|
|
1657
|
+
}
|
|
1658
|
+
/**
|
|
1659
|
+
* Returns the sender corresponding to the given MediaStreamTrack.
|
|
1660
|
+
*
|
|
1661
|
+
* @param {MediaSreamTrack} track - The media stream track used for the search.
|
|
1662
|
+
* @returns {Optional<RTCRtpSender>} - The found sender or undefined if no sender
|
|
1663
|
+
* was found.
|
|
1664
|
+
*/
|
|
1665
|
+
findSenderForTrack(track) {
|
|
1666
|
+
return this.peerconnection.getSenders().find(s => s.track === track);
|
|
1667
|
+
}
|
|
1668
|
+
/**
|
|
1669
|
+
* Processes the local description SDP and caches the mids of the mlines associated with the given tracks.
|
|
1670
|
+
*
|
|
1671
|
+
* @param {Array<JitsiLocalTrack>} localTracks - local tracks that are added to the peerconnection.
|
|
1672
|
+
* @returns {void}
|
|
1673
|
+
*/
|
|
1674
|
+
processLocalSdpForTransceiverInfo(localTracks) {
|
|
1675
|
+
const localSdp = this.localDescription?.sdp;
|
|
1676
|
+
if (!localSdp) {
|
|
1677
|
+
return;
|
|
1678
|
+
}
|
|
1679
|
+
[MediaType.AUDIO, MediaType.VIDEO].forEach(mediaType => {
|
|
1680
|
+
const tracks = localTracks.filter(t => t.getType() === mediaType);
|
|
1681
|
+
const parsedSdp = transform.parse(localSdp);
|
|
1682
|
+
const mLines = parsedSdp.media.filter(mline => mline.type === mediaType);
|
|
1683
|
+
tracks.forEach((track, idx) => {
|
|
1684
|
+
if (!this.localTrackTransceiverMids.has(track.rtcId)) {
|
|
1685
|
+
this.localTrackTransceiverMids.set(track.rtcId, mLines[idx].mid.toString());
|
|
1686
|
+
}
|
|
1687
|
+
});
|
|
1688
|
+
});
|
|
1689
|
+
}
|
|
1690
|
+
/**
|
|
1691
|
+
* Replaces <tt>oldTrack</tt> with <tt>newTrack</tt> from the peer connection.
|
|
1692
|
+
* Either <tt>oldTrack</tt> or <tt>newTrack</tt> can be null; replacing a valid
|
|
1693
|
+
* <tt>oldTrack</tt> with a null <tt>newTrack</tt> effectively just removes
|
|
1694
|
+
* <tt>oldTrack</tt>
|
|
1695
|
+
*
|
|
1696
|
+
* @param {Nullable<JitsiLocalTrack>} oldTrack - The current track in use to be replaced on the pc.
|
|
1697
|
+
* @param {Nullable<JitsiLocalTrack>} newTrack - The new track to be used.
|
|
1698
|
+
* @param {boolean} isMuteOperation - Whether the operation is a mute/unmute operation.
|
|
1699
|
+
* @returns {Promise<boolean>} - If the promise resolves with true, renegotiation will be needed.
|
|
1700
|
+
* Otherwise no renegotiation is needed.
|
|
1701
|
+
*/
|
|
1702
|
+
replaceTrack(oldTrack, newTrack, isMuteOperation = false) {
|
|
1703
|
+
if (!(oldTrack || newTrack)) {
|
|
1704
|
+
logger.info(`${this} replaceTrack called with no new track and no old track`);
|
|
1705
|
+
return Promise.resolve(false);
|
|
1706
|
+
}
|
|
1707
|
+
logger.info(`${this} TPC.replaceTrack old=${oldTrack}, new=${newTrack}`);
|
|
1708
|
+
let transceiver;
|
|
1709
|
+
const mediaType = newTrack?.getType() ?? oldTrack?.getType();
|
|
1710
|
+
const localTracks = this.getLocalTracks(mediaType);
|
|
1711
|
+
const track = newTrack?.getTrack() ?? null;
|
|
1712
|
+
const isNewLocalSource = localTracks?.length
|
|
1713
|
+
&& !oldTrack
|
|
1714
|
+
&& newTrack
|
|
1715
|
+
&& !localTracks.find(t => t === newTrack);
|
|
1716
|
+
// If old track exists, replace the track on the corresponding sender.
|
|
1717
|
+
if (oldTrack && !oldTrack.isMuted()) {
|
|
1718
|
+
transceiver = this.peerconnection.getTransceivers().find(t => t.sender.track === oldTrack.getTrack());
|
|
1719
|
+
// Find the first recvonly transceiver when more than one track of the same media type is being added to the pc.
|
|
1720
|
+
// As part of the track addition, a new m-line was added to the remote description with direction set to
|
|
1721
|
+
// recvonly.
|
|
1722
|
+
}
|
|
1723
|
+
else if (isNewLocalSource) {
|
|
1724
|
+
transceiver = this.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType
|
|
1725
|
+
&& t.direction === MediaDirection.RECVONLY
|
|
1726
|
+
// Re-use any existing recvonly transceiver (if available) for p2p case.
|
|
1727
|
+
&& ((this.isP2P && t.currentDirection === MediaDirection.RECVONLY)
|
|
1728
|
+
// @ts-ignore
|
|
1729
|
+
|| (t.currentDirection === MediaDirection.INACTIVE && !t.stopped)));
|
|
1730
|
+
// For mute/unmute operations, find the transceiver based on the track index in the source name if present,
|
|
1731
|
+
// otherwise it is assumed to be the first local track that was added to the peerconnection.
|
|
1732
|
+
}
|
|
1733
|
+
else {
|
|
1734
|
+
transceiver = this.peerconnection.getTransceivers().find(t => t.receiver.track.kind === mediaType);
|
|
1735
|
+
const sourceName = newTrack?.getSourceName() ?? oldTrack?.getSourceName();
|
|
1736
|
+
if (sourceName) {
|
|
1737
|
+
const trackIndex = getSourceIndexFromSourceName(sourceName);
|
|
1738
|
+
if (this.isP2P) {
|
|
1739
|
+
transceiver = this.peerconnection.getTransceivers()
|
|
1740
|
+
.filter(t => t.receiver.track.kind === mediaType)[trackIndex];
|
|
1741
|
+
}
|
|
1742
|
+
else if (oldTrack) {
|
|
1743
|
+
const transceiverMid = this.localTrackTransceiverMids.get(oldTrack.rtcId);
|
|
1744
|
+
transceiver = this.peerconnection.getTransceivers().find(t => t.mid === transceiverMid);
|
|
1745
|
+
}
|
|
1746
|
+
else if (trackIndex) {
|
|
1747
|
+
transceiver = this.peerconnection.getTransceivers()
|
|
1748
|
+
.filter(t => t.receiver.track.kind === mediaType
|
|
1749
|
+
&& t.direction !== MediaDirection.RECVONLY)[trackIndex];
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
if (!transceiver) {
|
|
1754
|
+
return Promise.reject(new Error(`Replace track failed - no transceiver for old: ${oldTrack}, new: ${newTrack}`));
|
|
1755
|
+
}
|
|
1756
|
+
return transceiver.sender.replaceTrack(track)
|
|
1757
|
+
.then(() => {
|
|
1758
|
+
if (isMuteOperation) {
|
|
1759
|
+
// Send RTCStats events for mute operations.
|
|
1760
|
+
this._sendRtcStatsEvent((oldTrack ?? newTrack), (oldTrack && !newTrack));
|
|
1761
|
+
return Promise.resolve();
|
|
1762
|
+
}
|
|
1763
|
+
if (oldTrack) {
|
|
1764
|
+
this.localTracks.delete(oldTrack.rtcId);
|
|
1765
|
+
this.localTrackTransceiverMids.delete(oldTrack.rtcId);
|
|
1766
|
+
}
|
|
1767
|
+
if (newTrack) {
|
|
1768
|
+
if (newTrack.isAudioTrack()) {
|
|
1769
|
+
this._hasHadAudioTrack = true;
|
|
1770
|
+
}
|
|
1771
|
+
else {
|
|
1772
|
+
this._hasHadVideoTrack = true;
|
|
1773
|
+
}
|
|
1774
|
+
this.localTrackTransceiverMids.set(newTrack.rtcId, transceiver?.mid?.toString());
|
|
1775
|
+
this.localTracks.set(newTrack.rtcId, newTrack);
|
|
1776
|
+
// Send RTCStats event when the track is added for the first time.
|
|
1777
|
+
this._sendRtcStatsEvent(newTrack, false);
|
|
1778
|
+
}
|
|
1779
|
+
// Update the local SSRC cache for the case when one track gets replaced with another and no
|
|
1780
|
+
// renegotiation is triggered as a result of this.
|
|
1781
|
+
if (oldTrack && newTrack) {
|
|
1782
|
+
const oldTrackSSRC = this.localSSRCs.get(oldTrack.rtcId);
|
|
1783
|
+
if (oldTrackSSRC) {
|
|
1784
|
+
this.localSSRCs.delete(oldTrack.rtcId);
|
|
1785
|
+
this.localSSRCs.set(newTrack.rtcId, oldTrackSSRC);
|
|
1786
|
+
const oldSsrcNum = this._extractPrimarySSRC(oldTrackSSRC);
|
|
1787
|
+
newTrack.setSsrc(oldSsrcNum);
|
|
1788
|
+
}
|
|
1789
|
+
// When a screenshare is stopped and started again, replace track will be called.
|
|
1790
|
+
if (oldTrack.getVideoType() === VideoType.DESKTOP) {
|
|
1791
|
+
RTCStats.sendStatsEntry(RTCStatsEvents.SCREENSHARE_MUTE_CHANGED_EVENT, this._pcId, false);
|
|
1792
|
+
}
|
|
1793
|
+
}
|
|
1794
|
+
// In the scenario where we remove the oldTrack (oldTrack is not null and newTrack is null) on FF
|
|
1795
|
+
// if we change the direction to RECVONLY, create answer will generate SDP with only 1 receive
|
|
1796
|
+
// only ssrc instead of keeping all 6 ssrcs that we currently have. Stopping the screen sharing
|
|
1797
|
+
// and then starting it again will trigger 2 rounds of source-remove and source-add replacing
|
|
1798
|
+
// the 6 ssrcs for the screen sharing with 1 receive only ssrc and then removing the receive
|
|
1799
|
+
// only ssrc and adding the same 6 ssrcs. On the remote participant's side the same ssrcs will
|
|
1800
|
+
// be reused on a new m-line and if the remote participant is FF due to
|
|
1801
|
+
// https://bugzilla.mozilla.org/show_bug.cgi?id=1768729 the video stream won't be rendered.
|
|
1802
|
+
// That's why we need keep the direction to SENDRECV for FF.
|
|
1803
|
+
//
|
|
1804
|
+
// NOTE: If we return to the approach of not removing the track for FF and instead using the
|
|
1805
|
+
// enabled property for muting the track, we may need to change the direction to
|
|
1806
|
+
// RECVONLY if FF still sends the media even though the enabled flag is set to false.
|
|
1807
|
+
transceiver.direction
|
|
1808
|
+
= newTrack || browser.isFirefox() ? MediaDirection.SENDRECV : MediaDirection.RECVONLY;
|
|
1809
|
+
// Configure simulcast encodings on Firefox when a track is added to the
|
|
1810
|
+
// peerconnection for the first time.
|
|
1811
|
+
const configureEncodingsPromise = browser.isFirefox() && !oldTrack && newTrack && this.doesTrueSimulcast(newTrack)
|
|
1812
|
+
? this._setEncodings(newTrack)
|
|
1813
|
+
: Promise.resolve();
|
|
1814
|
+
return configureEncodingsPromise.then(() => this.isP2P);
|
|
1815
|
+
});
|
|
1816
|
+
}
|
|
1817
|
+
/**
|
|
1818
|
+
* Removes local track from the RTCPeerConnection.
|
|
1819
|
+
*
|
|
1820
|
+
* @param {JitsiLocalTrack} localTrack the local track to be removed.
|
|
1821
|
+
* @return {Promise<boolean>} Promise that resolves to true if the underlying PeerConnection's state has changed and
|
|
1822
|
+
* renegotiation is required, false if no renegotiation is needed or Promise is rejected when something goes wrong.
|
|
1823
|
+
*/
|
|
1824
|
+
removeTrackFromPc(localTrack) {
|
|
1825
|
+
const webRtcStream = localTrack.getOriginalStream();
|
|
1826
|
+
this.trace('removeTrack', `${localTrack.rtcId} ${webRtcStream ? webRtcStream.id : 'null'}`);
|
|
1827
|
+
if (!this._assertTrackBelongs('removeTrack', localTrack)) {
|
|
1828
|
+
// Abort - nothing to be done here
|
|
1829
|
+
return Promise.reject('Track not found in the peerconnection');
|
|
1830
|
+
}
|
|
1831
|
+
return this.replaceTrack(localTrack, null, true /* isMuteOperation */).then(() => false);
|
|
1832
|
+
}
|
|
1833
|
+
/**
|
|
1834
|
+
* Updates the remote source map with the given source map for adding or removing sources.
|
|
1835
|
+
*
|
|
1836
|
+
* @param {Map<string, TPCSourceInfo>} sourceMap - The map of source names to their corresponding SSRCs.
|
|
1837
|
+
* @param {boolean} isAdd - Whether the sources are being added or removed.
|
|
1838
|
+
* @returns {void}
|
|
1839
|
+
*/
|
|
1840
|
+
updateRemoteSources(sourceMap, isAdd) {
|
|
1841
|
+
for (const [sourceName, ssrcInfo] of sourceMap) {
|
|
1842
|
+
if (isAdd) {
|
|
1843
|
+
this._remoteSsrcMap.set(sourceName, ssrcInfo);
|
|
1844
|
+
}
|
|
1845
|
+
else {
|
|
1846
|
+
this._remoteSsrcMap.delete(sourceName);
|
|
1847
|
+
}
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
/**
|
|
1851
|
+
* Returns true if the codec selection API is used for switching between codecs for the video sources.
|
|
1852
|
+
*
|
|
1853
|
+
* @returns {boolean}
|
|
1854
|
+
*/
|
|
1855
|
+
usesCodecSelectionAPI() {
|
|
1856
|
+
// Browser throws an error when H.264 is set on the encodings. Therefore, munge the SDP when H.264 needs to be
|
|
1857
|
+
// selected.
|
|
1858
|
+
// TODO: Remove this check when the above issue is fixed.
|
|
1859
|
+
return this._usesCodecSelectionAPI && this.codecSettings.codecList[0] !== CodecMimeType.H264;
|
|
1860
|
+
}
|
|
1861
|
+
/**
|
|
1862
|
+
* Creates a new data channel on the peer connection with the specified label and options.
|
|
1863
|
+
*/
|
|
1864
|
+
createDataChannel(label, opts) {
|
|
1865
|
+
this.trace('createDataChannel', label, opts);
|
|
1866
|
+
return this.peerconnection.createDataChannel(label, opts);
|
|
1867
|
+
}
|
|
1868
|
+
/**
|
|
1869
|
+
* Returns the expected send resolution for a local video track based on what encodings are currently active.
|
|
1870
|
+
*
|
|
1871
|
+
* @param {JitsiLocalTrack} localTrack - The local video track.
|
|
1872
|
+
* @returns {number}
|
|
1873
|
+
*/
|
|
1874
|
+
calculateExpectedSendResolution(localTrack) {
|
|
1875
|
+
const captureResolution = localTrack.getCaptureResolution();
|
|
1876
|
+
let result = Math.min(localTrack.maxEnabledResolution, captureResolution);
|
|
1877
|
+
if (localTrack.getVideoType() === VideoType.CAMERA) {
|
|
1878
|
+
// Find the closest matching resolution based on the current codec, simulcast config and the requested
|
|
1879
|
+
// resolution by the bridge or the peer.
|
|
1880
|
+
if (this.doesTrueSimulcast(localTrack)) {
|
|
1881
|
+
const sender = this.findSenderForTrack(localTrack.getTrack());
|
|
1882
|
+
if (!sender) {
|
|
1883
|
+
return result;
|
|
1884
|
+
}
|
|
1885
|
+
const { encodings } = sender.getParameters();
|
|
1886
|
+
result = encodings.reduce((maxValue, encoding) => {
|
|
1887
|
+
if (encoding.active) {
|
|
1888
|
+
// eslint-disable-next-line no-param-reassign
|
|
1889
|
+
maxValue = Math.max(maxValue, Math.floor(captureResolution / encoding.scaleResolutionDownBy));
|
|
1890
|
+
}
|
|
1891
|
+
return maxValue;
|
|
1892
|
+
}, 0);
|
|
1893
|
+
}
|
|
1894
|
+
}
|
|
1895
|
+
return result;
|
|
1896
|
+
}
|
|
1897
|
+
/**
|
|
1898
|
+
* Configures the stream encodings for the audio tracks that are added to the peerconnection.
|
|
1899
|
+
*
|
|
1900
|
+
* @param {Nullable<JitsiLocalTrack>} localAudioTrack - The local audio track.
|
|
1901
|
+
* @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.
|
|
1902
|
+
*/
|
|
1903
|
+
configureAudioSenderEncodings(localAudioTrack = null) {
|
|
1904
|
+
if (localAudioTrack) {
|
|
1905
|
+
return this._setEncodings(localAudioTrack);
|
|
1906
|
+
}
|
|
1907
|
+
const promises = [];
|
|
1908
|
+
for (const track of this.getLocalTracks(MediaType.AUDIO)) {
|
|
1909
|
+
promises.push(this._setEncodings(track));
|
|
1910
|
+
}
|
|
1911
|
+
return Promise.allSettled(promises);
|
|
1912
|
+
}
|
|
1913
|
+
/**
|
|
1914
|
+
* Configures the stream encodings depending on the video type,
|
|
1915
|
+
* scalability mode and the bitrate settings for the codec
|
|
1916
|
+
* that is currently selected.
|
|
1917
|
+
*
|
|
1918
|
+
* @param {Nullable<JitsiLocalTrack>} - The local track for which the sender encodings have to configured.
|
|
1919
|
+
* @param {CodecMimeType} - The preferred codec for the video track.
|
|
1920
|
+
* @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.
|
|
1921
|
+
*/
|
|
1922
|
+
configureVideoSenderEncodings(localVideoTrack = null, codec = null) {
|
|
1923
|
+
const preferredCodec = codec ?? this.codecSettings.codecList[0];
|
|
1924
|
+
if (localVideoTrack) {
|
|
1925
|
+
const height = this._senderMaxHeights.get(localVideoTrack.getSourceName())
|
|
1926
|
+
?? VIDEO_QUALITY_LEVELS[0].height;
|
|
1927
|
+
return this.setSenderVideoConstraints(height, localVideoTrack, preferredCodec);
|
|
1928
|
+
}
|
|
1929
|
+
const promises = [];
|
|
1930
|
+
for (const track of this.getLocalVideoTracks()) {
|
|
1931
|
+
const maxHeight = this._senderMaxHeights.get(track.getSourceName()) ?? VIDEO_QUALITY_LEVELS[0].height;
|
|
1932
|
+
promises.push(this.setSenderVideoConstraints(maxHeight, track, preferredCodec));
|
|
1933
|
+
}
|
|
1934
|
+
return Promise.allSettled(promises);
|
|
1935
|
+
}
|
|
1936
|
+
/**
|
|
1937
|
+
* Sets the local description on the peerconnection.
|
|
1938
|
+
*
|
|
1939
|
+
* @param {RTCSessionDescription} description - The local description to be set.
|
|
1940
|
+
* @returns {Promise<void>} - Resolved when the operation is successful and rejected with an error otherwise.
|
|
1941
|
+
*/
|
|
1942
|
+
setLocalDescription(description) {
|
|
1943
|
+
let localDescription = description;
|
|
1944
|
+
localDescription = this._mungeDescription(localDescription);
|
|
1945
|
+
return new Promise((resolve, reject) => {
|
|
1946
|
+
this.peerconnection.setLocalDescription(localDescription)
|
|
1947
|
+
.then(() => {
|
|
1948
|
+
this.trace('setLocalDescriptionOnSuccess');
|
|
1949
|
+
const localUfrag = SDPUtil.getUfrag(localDescription.sdp);
|
|
1950
|
+
if (localUfrag !== this._localUfrag) {
|
|
1951
|
+
this._localUfrag = localUfrag;
|
|
1952
|
+
this.eventEmitter.emit(RTCEvents.LOCAL_UFRAG_CHANGED, this, localUfrag);
|
|
1953
|
+
}
|
|
1954
|
+
this._initializeDtlsTransport();
|
|
1955
|
+
resolve();
|
|
1956
|
+
}, err => {
|
|
1957
|
+
this.trace('setLocalDescriptionOnFailure', err);
|
|
1958
|
+
reject(err);
|
|
1959
|
+
});
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* Sets the remote description on the peerconnection.
|
|
1964
|
+
*
|
|
1965
|
+
* @param {RTCSessionDescription} description - The remote description to be set.
|
|
1966
|
+
* @returns {Promise<void>} - Resolved when the operation is successful and rejected with an error otherwise.
|
|
1967
|
+
*/
|
|
1968
|
+
setRemoteDescription(description) {
|
|
1969
|
+
let remoteDescription = description;
|
|
1970
|
+
if (this.isSpatialScalabilityOn()) {
|
|
1971
|
+
remoteDescription = this.tpcUtils.insertUnifiedPlanSimulcastReceive(remoteDescription);
|
|
1972
|
+
this.trace('setRemoteDescription::postTransform (sim receive)', TraceablePeerConnection.dumpSDP(remoteDescription));
|
|
1973
|
+
}
|
|
1974
|
+
remoteDescription = this.tpcUtils.ensureCorrectOrderOfSsrcs(remoteDescription);
|
|
1975
|
+
this.trace('setRemoteDescription::postTransform (correct ssrc order)', TraceablePeerConnection.dumpSDP(remoteDescription));
|
|
1976
|
+
remoteDescription = this._mungeDescription(remoteDescription);
|
|
1977
|
+
return new Promise((resolve, reject) => {
|
|
1978
|
+
this.peerconnection.setRemoteDescription(remoteDescription)
|
|
1979
|
+
.then(() => {
|
|
1980
|
+
this.trace('setRemoteDescriptionOnSuccess');
|
|
1981
|
+
const remoteUfrag = SDPUtil.getUfrag(remoteDescription.sdp);
|
|
1982
|
+
if (remoteUfrag !== this._remoteUfrag) {
|
|
1983
|
+
this._remoteUfrag = remoteUfrag;
|
|
1984
|
+
this.eventEmitter.emit(RTCEvents.REMOTE_UFRAG_CHANGED, this, remoteUfrag);
|
|
1985
|
+
}
|
|
1986
|
+
this._initializeDtlsTransport();
|
|
1987
|
+
resolve();
|
|
1988
|
+
})
|
|
1989
|
+
.catch(err => {
|
|
1990
|
+
this.trace('setRemoteDescriptionOnFailure', err);
|
|
1991
|
+
reject(err);
|
|
1992
|
+
});
|
|
1993
|
+
});
|
|
1994
|
+
}
|
|
1995
|
+
/**
|
|
1996
|
+
* Changes the resolution of the video stream that is sent to the peer based on the resolution requested by the peer
|
|
1997
|
+
* and user preference, sets the degradation preference on the sender
|
|
1998
|
+
* based on the video type, configures the maximum
|
|
1999
|
+
* bitrates on the send stream.
|
|
2000
|
+
*
|
|
2001
|
+
* @param {number} frameHeight - The max frame height to be imposed on the outgoing video stream.
|
|
2002
|
+
* @param {JitsiLocalTrack} - The local track for which the sender constraints have to be applied.
|
|
2003
|
+
* @param {preferredCodec} - The video codec that needs to be configured on the
|
|
2004
|
+
* sender associated with the video source.
|
|
2005
|
+
* @returns {Promise} promise that will be resolved when the operation is successful and rejected otherwise.
|
|
2006
|
+
*/
|
|
2007
|
+
setSenderVideoConstraints(frameHeight, localVideoTrack, preferredCodec) {
|
|
2008
|
+
if (frameHeight < 0 || !isValidNumber(frameHeight)) {
|
|
2009
|
+
throw new Error(`Invalid frameHeight: ${frameHeight}`);
|
|
2010
|
+
}
|
|
2011
|
+
if (!localVideoTrack) {
|
|
2012
|
+
throw new Error('Local video track is missing');
|
|
2013
|
+
}
|
|
2014
|
+
const sourceName = localVideoTrack.getSourceName();
|
|
2015
|
+
this._senderMaxHeights.set(sourceName, frameHeight);
|
|
2016
|
+
// Ignore sender constraints if the video track is muted.
|
|
2017
|
+
if (localVideoTrack.isMuted()) {
|
|
2018
|
+
return Promise.resolve();
|
|
2019
|
+
}
|
|
2020
|
+
const codec = preferredCodec ?? this.codecSettings.codecList[0];
|
|
2021
|
+
return this._updateVideoSenderParameters(() => this._updateVideoSenderEncodings(frameHeight, localVideoTrack, codec));
|
|
2022
|
+
}
|
|
2023
|
+
/**
|
|
2024
|
+
* Resumes or suspends media on the peerconnection by setting the active state on RTCRtpEncodingParameters
|
|
2025
|
+
* associated with all the senders that have a track attached to it.
|
|
2026
|
+
*
|
|
2027
|
+
* @param {boolean} enable - whether outgoing media needs to be enabled or disabled.
|
|
2028
|
+
* @param {string} mediaType - media type, 'audio' or 'video', if neither is passed, all outgoing media will either
|
|
2029
|
+
* be enabled or disabled.
|
|
2030
|
+
* @returns {Promise} - A promise that is resolved when the change is succesful on all the senders, rejected
|
|
2031
|
+
* otherwise.
|
|
2032
|
+
*/
|
|
2033
|
+
setMediaTransferActive(enable, mediaType) {
|
|
2034
|
+
logger.info(`${this} ${enable ? 'Resuming' : 'Suspending'} media transfer.`);
|
|
2035
|
+
const senders = this.peerconnection.getSenders()
|
|
2036
|
+
.filter(s => Boolean(s.track) && (!mediaType || s.track.kind === mediaType));
|
|
2037
|
+
const promises = [];
|
|
2038
|
+
for (const sender of senders) {
|
|
2039
|
+
if (sender.track.kind === MediaType.VIDEO) {
|
|
2040
|
+
promises.push(this._updateVideoSenderParameters(() => this._enableSenderEncodings(sender, enable)));
|
|
2041
|
+
}
|
|
2042
|
+
else {
|
|
2043
|
+
promises.push(this._enableSenderEncodings(sender, enable));
|
|
2044
|
+
}
|
|
2045
|
+
}
|
|
2046
|
+
return Promise.allSettled(promises)
|
|
2047
|
+
.then(settledResult => {
|
|
2048
|
+
const errors = settledResult
|
|
2049
|
+
.filter(result => result.status === 'rejected')
|
|
2050
|
+
.map(result => result.reason);
|
|
2051
|
+
if (errors.length) {
|
|
2052
|
+
return Promise.reject(new Error('Failed to change encodings on the RTCRtpSenders'
|
|
2053
|
+
+ `${errors.join(' ')}`));
|
|
2054
|
+
}
|
|
2055
|
+
return Promise.resolve();
|
|
2056
|
+
});
|
|
2057
|
+
}
|
|
2058
|
+
/**
|
|
2059
|
+
* Enables/disables outgoing video media transmission on this peer connection. When disabled the stream encoding's
|
|
2060
|
+
* active state is enabled or disabled to send or stop the media.
|
|
2061
|
+
* @param {boolean} active <tt>true</tt> to enable video media transmission
|
|
2062
|
+
* or <tt>false</tt> to disable. If the value
|
|
2063
|
+
* is not a boolean the call will have no effect.
|
|
2064
|
+
* @return {Promise} A promise that is resolved when the change is succesful, rejected otherwise.
|
|
2065
|
+
* @public
|
|
2066
|
+
*/
|
|
2067
|
+
setVideoTransferActive(active) {
|
|
2068
|
+
logger.debug(`${this} video transfer active: ${active}`);
|
|
2069
|
+
const changed = this.videoTransferActive !== active;
|
|
2070
|
+
this.videoTransferActive = active;
|
|
2071
|
+
if (changed) {
|
|
2072
|
+
return this.setMediaTransferActive(active, MediaType.VIDEO);
|
|
2073
|
+
}
|
|
2074
|
+
return Promise.resolve();
|
|
2075
|
+
}
|
|
2076
|
+
/**
|
|
2077
|
+
* Sends DTMF tones if possible.
|
|
2078
|
+
*
|
|
2079
|
+
* @param {string} tones - The DTMF tones string as defined by {@code RTCDTMFSender.insertDTMF}, 'tones' argument.
|
|
2080
|
+
* @param {number} duration - The amount of time in milliseconds that each DTMF should last. It's 200ms by default.
|
|
2081
|
+
* @param {number} interToneGap - The length of time in miliseconds to wait between tones. It's 200ms by default.
|
|
2082
|
+
*
|
|
2083
|
+
* @returns {void}
|
|
2084
|
+
*/
|
|
2085
|
+
sendTones(tones, duration = 200, interToneGap = 200) {
|
|
2086
|
+
if (!this._dtmfSender) {
|
|
2087
|
+
if (this.peerconnection.getSenders) {
|
|
2088
|
+
const rtpSender = this.peerconnection.getSenders().find(s => s.dtmf);
|
|
2089
|
+
this._dtmfSender = rtpSender?.dtmf;
|
|
2090
|
+
this._dtmfSender && logger.info(`${this} initialized DTMFSender using getSenders`);
|
|
2091
|
+
}
|
|
2092
|
+
if (!this._dtmfSender) {
|
|
2093
|
+
const localAudioTrack = Array.from(this.localTracks.values()).find(t => t.isAudioTrack());
|
|
2094
|
+
// @ts-ignore
|
|
2095
|
+
if (this.peerconnection.createDTMFSender && localAudioTrack) {
|
|
2096
|
+
// @ts-ignore
|
|
2097
|
+
this._dtmfSender = this.peerconnection.createDTMFSender(localAudioTrack.getTrack());
|
|
2098
|
+
}
|
|
2099
|
+
this._dtmfSender && logger.info(`${this} initialized DTMFSender using deprecated createDTMFSender`);
|
|
2100
|
+
}
|
|
2101
|
+
if (this._dtmfSender) {
|
|
2102
|
+
this._dtmfSender.ontonechange = this._onToneChange.bind(this);
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
if (this._dtmfSender) {
|
|
2106
|
+
if (this._dtmfSender.toneBuffer) {
|
|
2107
|
+
this._dtmfTonesQueue.push({
|
|
2108
|
+
duration,
|
|
2109
|
+
interToneGap,
|
|
2110
|
+
tones
|
|
2111
|
+
});
|
|
2112
|
+
return;
|
|
2113
|
+
}
|
|
2114
|
+
this._dtmfSender.insertDTMF(tones, duration, interToneGap);
|
|
2115
|
+
}
|
|
2116
|
+
else {
|
|
2117
|
+
logger.warn(`${this} sendTones - failed to select DTMFSender`);
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
/**
|
|
2121
|
+
* Closes underlying WebRTC PeerConnection instance and removes all remote
|
|
2122
|
+
* tracks by emitting {@link RTCEvents.REMOTE_TRACK_REMOVED} for each one of
|
|
2123
|
+
* them.
|
|
2124
|
+
*/
|
|
2125
|
+
close() {
|
|
2126
|
+
this.trace('stop');
|
|
2127
|
+
// Off SignalingEvents
|
|
2128
|
+
this._signalingLayer.off(SignalingEvents.PEER_MUTED_CHANGED, this._peerMutedChanged);
|
|
2129
|
+
this._signalingLayer.off(SignalingEvents.PEER_VIDEO_TYPE_CHANGED, this._peerVideoTypeChanged);
|
|
2130
|
+
this.peerconnection.removeEventListener('track', this.onTrack);
|
|
2131
|
+
if (FeatureFlags.isSsrcRewritingSupported()) {
|
|
2132
|
+
for (const remoteTrack of this.remoteTracksBySsrc.values()) {
|
|
2133
|
+
this._removeRemoteTrack(remoteTrack);
|
|
2134
|
+
}
|
|
2135
|
+
this.remoteTracksBySsrc.clear();
|
|
2136
|
+
}
|
|
2137
|
+
else {
|
|
2138
|
+
for (const peerTracks of this.remoteTracks.values()) {
|
|
2139
|
+
for (const remoteTracks of peerTracks.values()) {
|
|
2140
|
+
for (const remoteTrack of remoteTracks) {
|
|
2141
|
+
this._removeRemoteTrack(remoteTrack);
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
this.remoteTracks.clear();
|
|
2146
|
+
}
|
|
2147
|
+
this._dtmfSender = null;
|
|
2148
|
+
this._dtmfTonesQueue = [];
|
|
2149
|
+
if (!this.rtc._removePeerConnection(this)) {
|
|
2150
|
+
logger.error(`${this} rtc._removePeerConnection returned false`);
|
|
2151
|
+
}
|
|
2152
|
+
if (this.statsinterval !== null) {
|
|
2153
|
+
window.clearInterval(this.statsinterval);
|
|
2154
|
+
this.statsinterval = null;
|
|
2155
|
+
}
|
|
2156
|
+
logger.info(`${this} Closing peerconnection`);
|
|
2157
|
+
this.peerconnection.close();
|
|
2158
|
+
}
|
|
2159
|
+
/**
|
|
2160
|
+
* Creates an SDP answer for the peer connection based on the provided constraints.
|
|
2161
|
+
*/
|
|
2162
|
+
createAnswer(constraints) {
|
|
2163
|
+
return this._createOfferOrAnswer(false /* answer */, constraints);
|
|
2164
|
+
}
|
|
2165
|
+
/**
|
|
2166
|
+
* Creates an SDP offer for the peer connection based on the provided constraints.
|
|
2167
|
+
*/
|
|
2168
|
+
createOffer(constraints) {
|
|
2169
|
+
return this._createOfferOrAnswer(true /* offer */, constraints);
|
|
2170
|
+
}
|
|
2171
|
+
/**
|
|
2172
|
+
* Track the SSRCs seen so far.
|
|
2173
|
+
* @param {number} ssrc - SSRC.
|
|
2174
|
+
* @return {boolean} - Whether this is a new SSRC.
|
|
2175
|
+
*/
|
|
2176
|
+
addRemoteSsrc(ssrc) {
|
|
2177
|
+
const existing = this.remoteSSRCs.has(ssrc);
|
|
2178
|
+
if (!existing) {
|
|
2179
|
+
this.remoteSSRCs.add(ssrc);
|
|
2180
|
+
}
|
|
2181
|
+
return !existing;
|
|
2182
|
+
}
|
|
2183
|
+
/**
|
|
2184
|
+
* Adds an ICE candidate to the peer connection.
|
|
2185
|
+
*/
|
|
2186
|
+
addIceCandidate(candidate) {
|
|
2187
|
+
this.trace('addIceCandidate', JSON.stringify({
|
|
2188
|
+
candidate: candidate.candidate,
|
|
2189
|
+
sdpMLineIndex: candidate.sdpMLineIndex,
|
|
2190
|
+
sdpMid: candidate.sdpMid,
|
|
2191
|
+
usernameFragment: candidate.usernameFragment
|
|
2192
|
+
}, null, ' '));
|
|
2193
|
+
return this.peerconnection.addIceCandidate(candidate);
|
|
2194
|
+
}
|
|
2195
|
+
/**
|
|
2196
|
+
* Obtains call-related stats from the peer connection.
|
|
2197
|
+
*
|
|
2198
|
+
* @returns {Promise<Object>} Promise which resolves with data providing statistics about
|
|
2199
|
+
* the peerconnection.
|
|
2200
|
+
*/
|
|
2201
|
+
getStats() {
|
|
2202
|
+
return this.peerconnection.getStats();
|
|
2203
|
+
}
|
|
2204
|
+
/**
|
|
2205
|
+
* Creates a text representation of this <tt>TraceablePeerConnection</tt>
|
|
2206
|
+
* instance.
|
|
2207
|
+
* @return {string}
|
|
2208
|
+
*/
|
|
2209
|
+
toString() {
|
|
2210
|
+
return `TPC[id=${this.id},type=${this.isP2P ? 'P2P' : 'JVB'}]`;
|
|
2211
|
+
}
|
|
2212
|
+
}
|
|
2213
|
+
/**
|
|
2214
|
+
* Returns a string representation of a SessionDescription object.
|
|
2215
|
+
*/
|
|
2216
|
+
TraceablePeerConnection.dumpSDP = function (description) {
|
|
2217
|
+
if (!description?.sdp) {
|
|
2218
|
+
return '';
|
|
2219
|
+
}
|
|
2220
|
+
return `type: ${description.type}\r\n${description.sdp}`;
|
|
2221
|
+
};
|
|
2222
|
+
export default TraceablePeerConnection;
|
|
2223
|
+
//# sourceMappingURL=TraceablePeerConnection.js.map
|