modified lib-jitsi-meet dev repo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

LocalSdpMunger.js 10KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276
  1. /* global __filename */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. import * as MediaType from '../../service/RTC/MediaType';
  4. import { SdpTransformWrap } from './SdpTransformUtil';
  5. const logger = getLogger(__filename);
  6. /**
  7. * Fakes local SDP exposed to {@link JingleSessionPC} through the local
  8. * description getter. Modifies the SDP, so that it will contain muted local
  9. * video tracks description, even though their underlying {MediaStreamTrack}s
  10. * are no longer in the WebRTC peerconnection. That prevents from SSRC updates
  11. * being sent to Jicofo/remote peer and prevents sRD/sLD cycle on the remote
  12. * side.
  13. */
  14. export default class LocalSdpMunger {
  15. /**
  16. * Creates new <tt>LocalSdpMunger</tt> instance.
  17. *
  18. * @param {TraceablePeerConnection} tpc
  19. * @param {string} localEndpointId - The endpoint id of the local user.
  20. */
  21. constructor(tpc, localEndpointId) {
  22. this.tpc = tpc;
  23. this.localEndpointId = localEndpointId;
  24. }
  25. /**
  26. * Makes sure that muted local video tracks associated with the parent
  27. * {@link TraceablePeerConnection} are described in the local SDP. It's done
  28. * in order to prevent from sending 'source-remove'/'source-add' Jingle
  29. * notifications when local video track is muted (<tt>MediaStream</tt> is
  30. * removed from the peerconnection).
  31. *
  32. * NOTE 1 video track is assumed
  33. *
  34. * @param {SdpTransformWrap} transformer the transformer instance which will
  35. * be used to process the SDP.
  36. * @return {boolean} <tt>true</tt> if there were any modifications to
  37. * the SDP wrapped by <tt>transformer</tt>.
  38. * @private
  39. */
  40. _addMutedLocalVideoTracksToSDP(transformer) {
  41. // Go over each video tracks and check if the SDP has to be changed
  42. const localVideos = this.tpc.getLocalTracks(MediaType.VIDEO);
  43. if (!localVideos.length) {
  44. return false;
  45. } else if (localVideos.length !== 1) {
  46. logger.error(
  47. `${this.tpc} there is more than 1 video track ! `
  48. + 'Strange things may happen !', localVideos);
  49. }
  50. const videoMLine = transformer.selectMedia('video');
  51. if (!videoMLine) {
  52. logger.debug(
  53. `${this.tpc} unable to hack local video track SDP`
  54. + '- no "video" media');
  55. return false;
  56. }
  57. let modified = false;
  58. for (const videoTrack of localVideos) {
  59. const muted = videoTrack.isMuted();
  60. const mediaStream = videoTrack.getOriginalStream();
  61. // During the mute/unmute operation there are periods of time when
  62. // the track's underlying MediaStream is not added yet to
  63. // the PeerConnection. The SDP needs to be munged in such case.
  64. const isInPeerConnection
  65. = mediaStream && this.tpc.isMediaStreamInPc(mediaStream);
  66. const shouldFakeSdp = muted || !isInPeerConnection;
  67. if (!shouldFakeSdp) {
  68. continue; // eslint-disable-line no-continue
  69. }
  70. // Inject removed SSRCs
  71. const requiredSSRCs
  72. = this.tpc.isSimulcastOn()
  73. ? this.tpc.simulcast.ssrcCache
  74. : [ this.tpc.sdpConsistency.cachedPrimarySsrc ];
  75. if (!requiredSSRCs.length) {
  76. logger.error(`No SSRCs stored for: ${videoTrack} in ${this.tpc}`);
  77. continue; // eslint-disable-line no-continue
  78. }
  79. modified = true;
  80. // We need to fake sendrecv.
  81. // NOTE the SDP produced here goes only to Jicofo and is never set
  82. // as localDescription. That's why
  83. // TraceablePeerConnection.mediaTransferActive is ignored here.
  84. videoMLine.direction = 'sendrecv';
  85. // Check if the recvonly has MSID
  86. const primarySSRC = requiredSSRCs[0];
  87. // FIXME The cname could come from the stream, but may turn out to
  88. // be too complex. It is fine to come up with any value, as long as
  89. // we only care about the actual SSRC values when deciding whether
  90. // or not an update should be sent.
  91. const primaryCname = `injected-${primarySSRC}`;
  92. for (const ssrcNum of requiredSSRCs) {
  93. // Remove old attributes
  94. videoMLine.removeSSRC(ssrcNum);
  95. // Inject
  96. videoMLine.addSSRCAttribute({
  97. id: ssrcNum,
  98. attribute: 'cname',
  99. value: primaryCname
  100. });
  101. videoMLine.addSSRCAttribute({
  102. id: ssrcNum,
  103. attribute: 'msid',
  104. value: videoTrack.storedMSID
  105. });
  106. }
  107. if (requiredSSRCs.length > 1) {
  108. const group = {
  109. ssrcs: requiredSSRCs.join(' '),
  110. semantics: 'SIM'
  111. };
  112. if (!videoMLine.findGroup(group.semantics, group.ssrcs)) {
  113. // Inject the group
  114. videoMLine.addSSRCGroup(group);
  115. }
  116. }
  117. // Insert RTX
  118. // FIXME in P2P RTX is used by Chrome regardless of config option
  119. // status. Because of that 'source-remove'/'source-add'
  120. // notifications are still sent to remove/add RTX SSRC and FID group
  121. if (!this.tpc.options.disableRtx) {
  122. this.tpc.rtxModifier.modifyRtxSsrcs2(videoMLine);
  123. }
  124. }
  125. return modified;
  126. }
  127. /**
  128. * Modifies 'cname', 'msid', 'label' and 'mslabel' by appending
  129. * the id of {@link LocalSdpMunger#tpc} at the end, preceding by a dash
  130. * sign.
  131. *
  132. * @param {MLineWrap} mediaSection - The media part (audio or video) of the
  133. * session description which will be modified in place.
  134. * @returns {void}
  135. * @private
  136. */
  137. _transformMediaIdentifiers(mediaSection) {
  138. const pcId = this.tpc.id;
  139. for (const ssrcLine of mediaSection.ssrcs) {
  140. switch (ssrcLine.attribute) {
  141. case 'cname':
  142. case 'label':
  143. case 'mslabel':
  144. ssrcLine.value = ssrcLine.value && `${ssrcLine.value}-${pcId}`;
  145. break;
  146. case 'msid': {
  147. if (ssrcLine.value) {
  148. const streamAndTrackIDs = ssrcLine.value.split(' ');
  149. if (streamAndTrackIDs.length === 2) {
  150. let streamId = streamAndTrackIDs[0];
  151. const trackId = streamAndTrackIDs[1];
  152. // Handle a case on Firefox when the browser doesn't produce a 'a:ssrc' line with the 'msid'
  153. // attribute. Jicofo needs an unique identifier to be associated with a ssrc and uses the msid
  154. // for that. Generate the identifier using the local endpoint id.
  155. // eslint-disable-next-line max-depth
  156. if (streamId === '-') {
  157. streamId = `${this.localEndpointId}-${mediaSection.mLine?.type}`;
  158. }
  159. ssrcLine.value = `${streamId}-${pcId} ${trackId}-${pcId}`;
  160. } else {
  161. logger.warn(`Unable to munge local MSID - weird format detected: ${ssrcLine.value}`);
  162. }
  163. }
  164. break;
  165. }
  166. }
  167. }
  168. // If the msid attribute is missing, then remove the ssrc from the transformed description so that a
  169. // source-remove is signaled to Jicofo. This happens when the direction of the transceiver (or m-line)
  170. // is set to 'inactive' or 'recvonly' on Firefox, Chrome (unified) and Safari.
  171. const msid = mediaSection.ssrcs.find(s => s.attribute === 'msid');
  172. if (!this.tpc.isP2P
  173. && (!msid || mediaSection.direction === 'recvonly' || mediaSection.direction === 'inactive')) {
  174. mediaSection.ssrcs = undefined;
  175. mediaSection.ssrcGroups = undefined;
  176. }
  177. }
  178. /**
  179. * Maybe modifies local description to fake local video tracks SDP when
  180. * those are muted.
  181. *
  182. * @param {object} desc the WebRTC SDP object instance for the local
  183. * description.
  184. * @returns {RTCSessionDescription}
  185. */
  186. maybeAddMutedLocalVideoTracksToSDP(desc) {
  187. if (!desc) {
  188. throw new Error('No local description passed in.');
  189. }
  190. const transformer = new SdpTransformWrap(desc.sdp);
  191. if (this._addMutedLocalVideoTracksToSDP(transformer)) {
  192. return new RTCSessionDescription({
  193. type: desc.type,
  194. sdp: transformer.toRawSDP()
  195. });
  196. }
  197. return desc;
  198. }
  199. /**
  200. * This transformation will make sure that stream identifiers are unique
  201. * across all of the local PeerConnections even if the same stream is used
  202. * by multiple instances at the same time.
  203. * Each PeerConnection assigns different SSRCs to the same local
  204. * MediaStream, but the MSID remains the same as it's used to identify
  205. * the stream by the WebRTC backend. The transformation will append
  206. * {@link TraceablePeerConnection#id} at the end of each stream's identifier
  207. * ("cname", "msid", "label" and "mslabel").
  208. *
  209. * @param {RTCSessionDescription} sessionDesc - The local session
  210. * description (this instance remains unchanged).
  211. * @return {RTCSessionDescription} - Transformed local session description
  212. * (a modified copy of the one given as the input).
  213. */
  214. transformStreamIdentifiers(sessionDesc) {
  215. // FIXME similar check is probably duplicated in all other transformers
  216. if (!sessionDesc || !sessionDesc.sdp || !sessionDesc.type) {
  217. return sessionDesc;
  218. }
  219. const transformer = new SdpTransformWrap(sessionDesc.sdp);
  220. const audioMLine = transformer.selectMedia('audio');
  221. if (audioMLine) {
  222. this._transformMediaIdentifiers(audioMLine);
  223. }
  224. const videoMLine = transformer.selectMedia('video');
  225. if (videoMLine) {
  226. this._transformMediaIdentifiers(videoMLine);
  227. }
  228. return new RTCSessionDescription({
  229. type: sessionDesc.type,
  230. sdp: transformer.toRawSDP()
  231. });
  232. }
  233. }