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