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