Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

LocalSSRCReplacement.js 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314
  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, msid, mslabel, label 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, localRecvOnlyMSID, localRecvOnlyMSLabel, localRecvOnlyLabel;
  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 random hex number within the range [min, max]
  108. * @param max the maximum value for the generated number
  109. * @param min the minimum value for the generated number
  110. * @returns random hex number
  111. */
  112. function rangeRandomHex(min, max)
  113. {
  114. return Math.floor(Math.random() * (max - min) + min).toString(16);
  115. }
  116. /**
  117. * Generates hex number with length 4
  118. */
  119. var random4digitsHex = rangeRandomHex.bind(null, 4096, 65535);
  120. /**
  121. * Generates hex number with length 8
  122. */
  123. var random8digitsHex = rangeRandomHex.bind(null, 268435456, 4294967295);
  124. /**
  125. * Generates hex number with length 12
  126. */
  127. var random12digitsHex = rangeRandomHex.bind(null, 17592186044416, 281474976710655);
  128. /**
  129. * Generates new label/mslabel attribute
  130. * @returns {string} label/mslabel attribute
  131. */
  132. function generateLabel() {
  133. return random8digitsHex() + "-" + random4digitsHex() + "-" + random4digitsHex() + "-" + random4digitsHex() + "-" + random12digitsHex();
  134. }
  135. /**
  136. * Generates new SSRC, CNAME, mslabel, label and msid for local video recvonly stream.
  137. * FIXME what about eventual SSRC collision ?
  138. */
  139. function generateRecvonlySSRC() {
  140. localRecvOnlySSRC =
  141. Math.random().toString(10).substring(2, 11);
  142. localRecvOnlyCName =
  143. Math.random().toString(36).substring(2);
  144. localRecvOnlyMSLabel = generateLabel();
  145. localRecvOnlyLabel = generateLabel();
  146. localRecvOnlyMSID = localRecvOnlyMSLabel + " " + localRecvOnlyLabel;
  147. console.info(
  148. "Generated local recvonly SSRC: " + localRecvOnlySSRC +
  149. ", cname: " + localRecvOnlyCName);
  150. }
  151. var LocalSSRCReplacement = {
  152. /**
  153. * Method must be called before 'session-initiate' or 'session-invite' is
  154. * sent. Scans the IQ for local video SSRC and stores it if detected.
  155. *
  156. * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
  157. * which will be scanned for local video SSRC.
  158. */
  159. processSessionInit: function (sessionInit) {
  160. if (!isEnabled)
  161. return;
  162. if (localVideoSSRC) {
  163. console.error("Local SSRC stored already: " + localVideoSSRC);
  164. return;
  165. }
  166. storeLocalVideoSSRC(sessionInit);
  167. },
  168. /**
  169. * If we have local video SSRC stored searched given
  170. * <tt>localDescription</tt> for video SSRC and makes sure it is replaced
  171. * with the stored one.
  172. * @param localDescription local description object that will have local
  173. * video SSRC replaced with the stored one
  174. * @returns modified <tt>localDescription</tt> object.
  175. */
  176. mungeLocalVideoSSRC: function (localDescription) {
  177. if (!isEnabled)
  178. return localDescription;
  179. if (!localDescription) {
  180. console.warn("localDescription is null or undefined");
  181. return localDescription;
  182. }
  183. // IF we have local video SSRC stored make sure it is replaced
  184. // with old SSRC
  185. if (localVideoSSRC) {
  186. var newSdp = new SDP(localDescription.sdp);
  187. if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
  188. !newSdp.containsSSRC(localVideoSSRC)) {
  189. // Get new video SSRC
  190. var map = newSdp.getMediaSsrcMap();
  191. var videoPart = map[1];
  192. var videoSSRCs = videoPart.ssrcs;
  193. var newSSRC = Object.keys(videoSSRCs)[0];
  194. console.info(
  195. "Replacing new video SSRC: " + newSSRC +
  196. " with " + localVideoSSRC);
  197. localDescription.sdp =
  198. newSdp.raw.replace(
  199. new RegExp('a=ssrc:' + newSSRC, 'g'),
  200. 'a=ssrc:' + localVideoSSRC);
  201. }
  202. } else {
  203. // Make sure we have any SSRC for recvonly video stream
  204. var sdp = new SDP(localDescription.sdp);
  205. if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
  206. sdp.media[1].indexOf('a=recvonly') !== -1) {
  207. if (!localRecvOnlySSRC) {
  208. generateRecvonlySSRC();
  209. }
  210. localVideoSSRC = localRecvOnlySSRC;
  211. console.info('No SSRC in video recvonly stream' +
  212. ' - adding SSRC: ' + localRecvOnlySSRC);
  213. sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
  214. ' cname:' + localRecvOnlyCName + '\r\n' +
  215. 'a=ssrc:' + localRecvOnlySSRC +
  216. ' msid:' + localRecvOnlyMSID + '\r\n' +
  217. 'a=ssrc:' + localRecvOnlySSRC +
  218. ' mslabel:' + localRecvOnlyMSLabel + '\r\n' +
  219. 'a=ssrc:' + localRecvOnlySSRC +
  220. ' label:' + localRecvOnlyLabel + '\r\n';
  221. localDescription.sdp = sdp.session + sdp.media.join('');
  222. }
  223. }
  224. return localDescription;
  225. },
  226. /**
  227. * Method must be called before 'source-add' notification is sent. In case
  228. * we have local video SSRC advertised already it will be removed from the
  229. * notification. If no other SSRCs are described by given IQ null will be
  230. * returned which means that there is no point in sending the notification.
  231. * @param sourceAdd 'source-add' Jingle IQ to be processed
  232. * @returns modified 'source-add' IQ which can be sent to the focus or
  233. * <tt>null</tt> if no notification shall be sent. It is no longer
  234. * a Strophe IQ Builder instance, but DOM element tree.
  235. */
  236. processSourceAdd: function (sourceAdd) {
  237. if (!isEnabled)
  238. return sourceAdd;
  239. if (!localVideoSSRC) {
  240. // Store local SSRC if available
  241. storeLocalVideoSSRC(sourceAdd);
  242. return sourceAdd;
  243. } else {
  244. return filterOutSource(sourceAdd, 'source-add');
  245. }
  246. },
  247. /**
  248. * Method must be called before 'source-remove' notification is sent.
  249. * Removes local video SSRC from the notification. If there are no other
  250. * SSRCs described in the given IQ <tt>null</tt> will be returned which
  251. * means that there is no point in sending the notification.
  252. * @param sourceRemove 'source-remove' Jingle IQ to be processed
  253. * @returns modified 'source-remove' IQ which can be sent to the focus or
  254. * <tt>null</tt> if no notification shall be sent. It is no longer
  255. * a Strophe IQ Builder instance, but DOM element tree.
  256. */
  257. processSourceRemove: function (sourceRemove) {
  258. if (!isEnabled)
  259. return sourceRemove;
  260. return filterOutSource(sourceRemove, 'source-remove');
  261. },
  262. /**
  263. * Turns the hack on or off
  264. * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
  265. * to disable it
  266. */
  267. setEnabled: function (enabled) {
  268. isEnabled = enabled;
  269. }
  270. };
  271. module.exports = LocalSSRCReplacement;