Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.

LocalSdpMunger.js 7.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { isEqual } from 'lodash-es';
  2. import { MediaDirection } from '../../service/RTC/MediaDirection';
  3. import { MediaType } from '../../service/RTC/MediaType';
  4. import browser from '../browser';
  5. import { SdpTransformWrap } from './SdpTransformUtil';
  6. /**
  7. * Fakes local SDP exposed to {@link JingleSessionPC} through the local description getter. Modifies the SDP, so that
  8. * the stream identifiers are unique across all of the local PeerConnections and that the source names and video types
  9. * are injected so that Jicofo can use them to identify the sources.
  10. */
  11. export default class LocalSdpMunger {
  12. /**
  13. * Creates new <tt>LocalSdpMunger</tt> instance.
  14. *
  15. * @param {TraceablePeerConnection} tpc
  16. * @param {string} localEndpointId - The endpoint id of the local user.
  17. */
  18. constructor(tpc, localEndpointId) {
  19. this.tpc = tpc;
  20. this.localEndpointId = localEndpointId;
  21. }
  22. /**
  23. * Updates or adds a 'msid' attribute for the local sources in the SDP. Also adds 'sourceName' and 'videoType'
  24. * (if applicable) attributes. All other source attributes like 'cname', 'label' and 'mslabel' are removed since
  25. * these are not processed by Jicofo.
  26. *
  27. * @param {MLineWrap} mediaSection - The media part (audio or video) of the session description which will be
  28. * modified in place.
  29. * @returns {void}
  30. * @private
  31. */
  32. _transformMediaIdentifiers(mediaSection, ssrcMap) {
  33. const mediaType = mediaSection.mLine.type;
  34. const mediaDirection = mediaSection.mLine.direction;
  35. const sources = [ ...new Set(mediaSection.mLine.ssrcs?.map(s => s.id)) ];
  36. let trackId = mediaSection.mLine.msid?.split(' ')[1];
  37. let sourceName;
  38. if (ssrcMap.size) {
  39. const sortedSources = sources.slice().sort();
  40. for (const [ id, trackSsrcs ] of ssrcMap.entries()) {
  41. if (isEqual(sortedSources, [ ...trackSsrcs.ssrcs ].sort())) {
  42. sourceName = id;
  43. }
  44. }
  45. for (const source of sources) {
  46. if ((mediaDirection === MediaDirection.SENDONLY || mediaDirection === MediaDirection.SENDRECV)
  47. && sourceName) {
  48. const msid = mediaSection.ssrcs.find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');
  49. if (msid) {
  50. trackId = msid.value.split(' ')[1];
  51. }
  52. const generatedMsid = `${ssrcMap.get(sourceName).msid}-${this.tpc.id} ${trackId}-${this.tpc.id}`;
  53. const existingMsid = mediaSection.ssrcs
  54. .find(ssrc => ssrc.id === source && ssrc.attribute === 'msid');
  55. // Always overwrite msid since we want the msid to be in this format even if the browser generates
  56. // one. '<endpoint_id>-<mediaType>-<trackIndex>-<tpcId>' example - d8ff91-video-0-1
  57. if (existingMsid) {
  58. existingMsid.value = generatedMsid;
  59. } else {
  60. mediaSection.ssrcs.push({
  61. id: source,
  62. attribute: 'msid',
  63. value: generatedMsid
  64. });
  65. }
  66. // Inject source names as a=ssrc:3124985624 name:endpointA-v0
  67. mediaSection.ssrcs.push({
  68. id: source,
  69. attribute: 'name',
  70. value: sourceName
  71. });
  72. const videoType = this.tpc.getLocalVideoTracks()
  73. .find(track => track.getSourceName() === sourceName)
  74. ?.getVideoType();
  75. if (mediaType === MediaType.VIDEO && videoType) {
  76. // Inject videoType as a=ssrc:1234 videoType:desktop.
  77. mediaSection.ssrcs.push({
  78. id: source,
  79. attribute: 'videoType',
  80. value: videoType
  81. });
  82. }
  83. }
  84. }
  85. }
  86. // Ignore the 'label' and 'mslabel' attributes.
  87. mediaSection.ssrcs
  88. = mediaSection.ssrcs.filter(ssrc => ssrc.attribute !== 'label' && ssrc.attribute !== 'mslabel');
  89. // Remove the 'cname' attribute on Firefox as a=ssrc line with only 'cname' attribute are present in the SDP
  90. // for recvonly SSRCs generated by createAnswer. These do not have to be signaled to the peers.
  91. if (browser.isFirefox()) {
  92. mediaSection.ssrcs = mediaSection.ssrcs.filter(ssrc => ssrc.attribute !== 'cname');
  93. }
  94. // On FF when the user has started muted create answer will generate a recv only SSRC. We don't want to signal
  95. // this SSRC in order to reduce the load of the xmpp server for large calls. Therefore the SSRC needs to be
  96. // removed from the SDP.
  97. //
  98. // For all other use cases (when the user has had media but then the user has stopped it) we want to keep the
  99. // receive only SSRCs in the SDP. Otherwise source-remove will be triggered and the next time the user add a
  100. // track we will reuse the SSRCs and send source-add with the same SSRCs. This is problematic because of issues
  101. // on Chrome and FF (https://bugzilla.mozilla.org/show_bug.cgi?id=1768729) when removing and then adding the
  102. // same SSRC in the remote sdp the remote track is not rendered.
  103. if (browser.isFirefox()
  104. && (mediaDirection === MediaDirection.RECVONLY || mediaDirection === MediaDirection.INACTIVE)
  105. && (
  106. (mediaType === MediaType.VIDEO && !this.tpc._hasHadVideoTrack)
  107. || (mediaType === MediaType.AUDIO && !this.tpc._hasHadAudioTrack)
  108. )
  109. ) {
  110. mediaSection.ssrcs = undefined;
  111. mediaSection.ssrcGroups = undefined;
  112. }
  113. }
  114. /**
  115. * This transformation will make sure that stream identifiers are unique across all of the local PeerConnections
  116. * even if the same stream is used by multiple instances at the same time. It also injects 'sourceName' and
  117. * 'videoType' attribute.
  118. *
  119. * @param {RTCSessionDescription} sessionDesc - The local session description (this instance remains unchanged).
  120. * @param {Map<string, TPCSSRCInfo>} ssrcMap - The SSRC and source map for the local tracks.
  121. * @return {RTCSessionDescription} - Transformed local session description
  122. * (a modified copy of the one given as the input).
  123. */
  124. transformStreamIdentifiers(sessionDesc, ssrcMap) {
  125. if (!sessionDesc || !sessionDesc.sdp || !sessionDesc.type) {
  126. return sessionDesc;
  127. }
  128. const transformer = new SdpTransformWrap(sessionDesc.sdp);
  129. const audioMLine = transformer.selectMedia(MediaType.AUDIO)?.[0];
  130. if (audioMLine) {
  131. this._transformMediaIdentifiers(audioMLine, ssrcMap);
  132. }
  133. const videoMlines = transformer.selectMedia(MediaType.VIDEO);
  134. for (const videoMLine of videoMlines) {
  135. this._transformMediaIdentifiers(videoMLine, ssrcMap);
  136. }
  137. return {
  138. type: sessionDesc.type,
  139. sdp: transformer.toRawSDP()
  140. };
  141. }
  142. }