Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.

LocalSSRCReplacement.js 9.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. /* global $ */
  2. /*
  3. Here we do modifications of local video SSRCs. There are 2 situations we have
  4. to handle:
  5. 1. We generate SSRC for local recvonly video stream. This is the case when we
  6. have no local camera and it is not generated automatically, but SSRC=1 is
  7. used implicitly. If that happens RTCP packets will be dropped by the JVB
  8. and we won't be able to request video key frames correctly.
  9. 2. A hack to re-use SSRC of the first video stream for any new stream created
  10. in future. It turned out that Chrome may keep on using the SSRC of removed
  11. video stream in RTCP even though a new one has been created. So we just
  12. want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
  13. notifications are blocked once first video SSRC is advertised to the focus.
  14. What this hack does:
  15. 1. Stores the SSRC of the first video stream created by
  16. a) scanning Jingle session-accept/session-invite for existing video SSRC
  17. b) watching for 'source-add' for new video stream if it has not been
  18. created in step a)
  19. 2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
  20. the stored one. It is called by 'TracablePeerConnection' before local SDP is
  21. returned to the other parts of the application.
  22. 3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
  23. blocks those notifications. This makes Jicofo and all participants think
  24. that it exists all the time even if the video stream has been removed or
  25. replaced locally. Thanks to that there is no additional signaling activity
  26. on video mute or when switching to the desktop stream.
  27. */
  28. var SDP = require('./SDP');
  29. var RTCBrowserType = require('../RTC/RTCBrowserType');
  30. /**
  31. * The hack is enabled on all browsers except FF by default
  32. * FIXME finish the hack once removeStream method is implemented in FF
  33. * @type {boolean}
  34. */
  35. var isEnabled = !RTCBrowserType.isFirefox();
  36. /**
  37. * Stored SSRC of local video stream.
  38. */
  39. var localVideoSSRC;
  40. /**
  41. * SSRC used for recvonly video stream when we have no local camera.
  42. * This is in order to tell Chrome what SSRC should be used in RTCP requests
  43. * instead of 1.
  44. */
  45. var localRecvOnlySSRC;
  46. /**
  47. * cname for <tt>localRecvOnlySSRC</tt>
  48. */
  49. var localRecvOnlyCName;
  50. /**
  51. * Method removes <source> element which describes <tt>localVideoSSRC</tt>
  52. * from given Jingle IQ.
  53. * @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
  54. * @param actionName display name of the action which will be printed in log
  55. * messages.
  56. * @returns {*} modified Jingle IQ, so that it does not contain <source> element
  57. * corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
  58. * other SSRCs left to be signaled after removing it.
  59. */
  60. var filterOutSource = function (modifyIq, actionName) {
  61. var modifyIqTree = $(modifyIq.tree());
  62. if (!localVideoSSRC)
  63. return modifyIqTree[0];
  64. var videoSSRC = modifyIqTree.find(
  65. '>jingle>content[name="video"]' +
  66. '>description>source[ssrc="' + localVideoSSRC + '"]');
  67. if (!videoSSRC.length) {
  68. return modifyIqTree[0];
  69. }
  70. console.info(
  71. 'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
  72. videoSSRC.remove();
  73. // Check if any sources still left to be added/removed
  74. if (modifyIqTree.find('>jingle>content>description>source').length) {
  75. return modifyIqTree[0];
  76. } else {
  77. return null;
  78. }
  79. };
  80. /**
  81. * Scans given Jingle IQ for video SSRC and stores it.
  82. * @param jingleIq the Jingle IQ to be scanned for video SSRC.
  83. */
  84. var storeLocalVideoSSRC = function (jingleIq) {
  85. var videoSSRCs =
  86. $(jingleIq.tree())
  87. .find('>jingle>content[name="video"]>description>source');
  88. videoSSRCs.each(function (idx, ssrcElem) {
  89. if (localVideoSSRC)
  90. return;
  91. // We consider SSRC real only if it has msid attribute
  92. // recvonly streams in FF do not have it as well as local SSRCs
  93. // we generate for recvonly streams in Chrome
  94. var ssrSel = $(ssrcElem);
  95. var msid = ssrSel.find('>parameter[name="msid"]');
  96. if (msid.length) {
  97. var ssrcVal = ssrSel.attr('ssrc');
  98. if (ssrcVal) {
  99. localVideoSSRC = ssrcVal;
  100. console.info('Stored local video SSRC' +
  101. ' for future re-use: ' + localVideoSSRC);
  102. }
  103. }
  104. });
  105. };
  106. /**
  107. * Generates new SSRC for local video recvonly stream.
  108. * FIXME what about eventual SSRC collision ?
  109. */
  110. function generateRecvonlySSRC() {
  111. //
  112. localRecvOnlySSRC =
  113. Math.random().toString(10).substring(2, 11);
  114. localRecvOnlyCName =
  115. Math.random().toString(36).substring(2);
  116. console.info(
  117. "Generated local recvonly SSRC: " + localRecvOnlySSRC +
  118. ", cname: " + localRecvOnlyCName);
  119. }
  120. var LocalSSRCReplacement = {
  121. /**
  122. * Method must be called before 'session-initiate' or 'session-invite' is
  123. * sent. Scans the IQ for local video SSRC and stores it if detected.
  124. *
  125. * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
  126. * which will be scanned for local video SSRC.
  127. */
  128. processSessionInit: function (sessionInit) {
  129. if (!isEnabled)
  130. return;
  131. if (localVideoSSRC) {
  132. console.error("Local SSRC stored already: " + localVideoSSRC);
  133. return;
  134. }
  135. storeLocalVideoSSRC(sessionInit);
  136. },
  137. /**
  138. * If we have local video SSRC stored searched given
  139. * <tt>localDescription</tt> for video SSRC and makes sure it is replaced
  140. * with the stored one.
  141. * @param localDescription local description object that will have local
  142. * video SSRC replaced with the stored one
  143. * @returns modified <tt>localDescription</tt> object.
  144. */
  145. mungeLocalVideoSSRC: function (localDescription) {
  146. if (!isEnabled)
  147. return localDescription;
  148. if (!localDescription) {
  149. console.warn("localDescription is null or undefined");
  150. return localDescription;
  151. }
  152. // IF we have local video SSRC stored make sure it is replaced
  153. // with old SSRC
  154. if (localVideoSSRC) {
  155. var newSdp = new SDP(localDescription.sdp);
  156. if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
  157. !newSdp.containsSSRC(localVideoSSRC)) {
  158. // Get new video SSRC
  159. var map = newSdp.getMediaSsrcMap();
  160. var videoPart = map[1];
  161. var videoSSRCs = videoPart.ssrcs;
  162. var newSSRC = Object.keys(videoSSRCs)[0];
  163. console.info(
  164. "Replacing new video SSRC: " + newSSRC +
  165. " with " + localVideoSSRC);
  166. localDescription.sdp =
  167. newSdp.raw.replace(
  168. new RegExp('a=ssrc:' + newSSRC, 'g'),
  169. 'a=ssrc:' + localVideoSSRC);
  170. }
  171. } else {
  172. // Make sure we have any SSRC for recvonly video stream
  173. var sdp = new SDP(localDescription.sdp);
  174. if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
  175. sdp.media[1].indexOf('a=recvonly') !== -1) {
  176. if (!localRecvOnlySSRC) {
  177. generateRecvonlySSRC();
  178. }
  179. console.info('No SSRC in video recvonly stream' +
  180. ' - adding SSRC: ' + localRecvOnlySSRC);
  181. sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
  182. ' cname:' + localRecvOnlyCName + '\r\n';
  183. localDescription.sdp = sdp.session + sdp.media.join('');
  184. }
  185. }
  186. return localDescription;
  187. },
  188. /**
  189. * Method must be called before 'source-add' notification is sent. In case
  190. * we have local video SSRC advertised already it will be removed from the
  191. * notification. If no other SSRCs are described by given IQ null will be
  192. * returned which means that there is no point in sending the notification.
  193. * @param sourceAdd 'source-add' Jingle IQ to be processed
  194. * @returns modified 'source-add' IQ which can be sent to the focus or
  195. * <tt>null</tt> if no notification shall be sent. It is no longer
  196. * a Strophe IQ Builder instance, but DOM element tree.
  197. */
  198. processSourceAdd: function (sourceAdd) {
  199. if (!isEnabled)
  200. return sourceAdd;
  201. if (!localVideoSSRC) {
  202. // Store local SSRC if available
  203. storeLocalVideoSSRC(sourceAdd);
  204. return sourceAdd;
  205. } else {
  206. return filterOutSource(sourceAdd, 'source-add');
  207. }
  208. },
  209. /**
  210. * Method must be called before 'source-remove' notification is sent.
  211. * Removes local video SSRC from the notification. If there are no other
  212. * SSRCs described in the given IQ <tt>null</tt> will be returned which
  213. * means that there is no point in sending the notification.
  214. * @param sourceRemove 'source-remove' Jingle IQ to be processed
  215. * @returns modified 'source-remove' IQ which can be sent to the focus or
  216. * <tt>null</tt> if no notification shall be sent. It is no longer
  217. * a Strophe IQ Builder instance, but DOM element tree.
  218. */
  219. processSourceRemove: function (sourceRemove) {
  220. if (!isEnabled)
  221. return sourceRemove;
  222. return filterOutSource(sourceRemove, 'source-remove');
  223. },
  224. /**
  225. * Turns the hack on or off
  226. * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
  227. * to disable it
  228. */
  229. setEnabled: function (enabled) {
  230. isEnabled = enabled;
  231. }
  232. };
  233. module.exports = LocalSSRCReplacement;