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.

VideoSSRCHack.js 7.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199
  1. /* global $ */
  2. /*
  3. The purpose of this hack is to re-use SSRC of first video stream ever created
  4. for any video streams created later on. In order to do that this hack:
  5. 1. Stores the SSRC of the first video stream created by
  6. a) scanning Jingle session-accept/session-invite for existing video SSRC
  7. b) watching for 'source-add' for new video stream if it has not been
  8. created in step a)
  9. 2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
  10. the stored one. It is called by 'TracablePeerConnection' before local SDP is
  11. returned to the other parts of the application.
  12. 3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
  13. blocks those notifications. This makes Jicofo and all participants think
  14. that it exists all the time even if the video stream has been removed or
  15. replaced locally. Thanks to that there is no additional signaling activity
  16. on video mute or when switching to the desktop stream.
  17. */
  18. var SDP = require('./SDP');
  19. var RTCBrowserType = require('../RTC/RTCBrowserType');
  20. /**
  21. * The hack is enabled on all browsers except FF by default
  22. * FIXME finish the hack once removeStream method is implemented in FF
  23. * @type {boolean}
  24. */
  25. var isEnabled = !RTCBrowserType.isFirefox();
  26. /**
  27. * Stored SSRC of local video stream.
  28. */
  29. var localVideoSSRC;
  30. /**
  31. * Method removes <source> element which describes <tt>localVideoSSRC</tt>
  32. * from given Jingle IQ.
  33. * @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
  34. * @param actionName display name of the action which will be printed in log
  35. * messages.
  36. * @returns {*} modified Jingle IQ, so that it does not contain <source> element
  37. * corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
  38. * other SSRCs left to be signaled after removing it.
  39. */
  40. var filterOutSource = function (modifyIq, actionName) {
  41. var modifyIqTree = $(modifyIq.tree());
  42. if (!localVideoSSRC)
  43. return modifyIqTree[0];
  44. var videoSSRC = modifyIqTree.find(
  45. '>jingle>content[name="video"]' +
  46. '>description>source[ssrc="' + localVideoSSRC + '"]');
  47. if (!videoSSRC.length) {
  48. return modifyIqTree[0];
  49. }
  50. console.info(
  51. 'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
  52. videoSSRC.remove();
  53. // Check if any sources still left to be added/removed
  54. if (modifyIqTree.find('>jingle>content>description>source').length) {
  55. return modifyIqTree[0];
  56. } else {
  57. return null;
  58. }
  59. };
  60. /**
  61. * Scans given Jingle IQ for video SSRC and stores it.
  62. * @param jingleIq the Jingle IQ to be scanned for video SSRC.
  63. */
  64. var storeLocalVideoSSRC = function (jingleIq) {
  65. var videoSSRCs =
  66. $(jingleIq.tree())
  67. .find('>jingle>content[name="video"]>description>source');
  68. console.info('Video desc: ', videoSSRCs);
  69. if (!videoSSRCs.length)
  70. return;
  71. var ssrc = videoSSRCs.attr('ssrc');
  72. if (ssrc) {
  73. localVideoSSRC = ssrc;
  74. console.info(
  75. 'Stored local video SSRC for future re-use: ' + localVideoSSRC);
  76. } else {
  77. console.error('No "ssrc" attribute present in <source> element');
  78. }
  79. };
  80. var LocalVideoSSRCHack = {
  81. /**
  82. * Method must be called before 'session-initiate' or 'session-invite' is
  83. * sent. Scans the IQ for local video SSRC and stores it if detected.
  84. *
  85. * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
  86. * which will be scanned for local video SSRC.
  87. */
  88. processSessionInit: function (sessionInit) {
  89. if (!isEnabled)
  90. return;
  91. if (localVideoSSRC) {
  92. console.error("Local SSRC stored already: " + localVideoSSRC);
  93. return;
  94. }
  95. storeLocalVideoSSRC(sessionInit);
  96. },
  97. /**
  98. * If we have local video SSRC stored searched given
  99. * <tt>localDescription</tt> for video SSRC and makes sure it is replaced
  100. * with the stored one.
  101. * @param localDescription local description object that will have local
  102. * video SSRC replaced with the stored one
  103. * @returns modified <tt>localDescription</tt> object.
  104. */
  105. mungeLocalVideoSSRC: function (localDescription) {
  106. if (!isEnabled)
  107. return localDescription;
  108. // IF we have local video SSRC stored make sure it is replaced
  109. // with old SSRC
  110. if (localVideoSSRC) {
  111. var newSdp = new SDP(localDescription.sdp);
  112. if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
  113. !newSdp.containsSSRC(localVideoSSRC)) {
  114. // Get new video SSRC
  115. var map = newSdp.getMediaSsrcMap();
  116. var videoPart = map[1];
  117. var videoSSRCs = videoPart.ssrcs;
  118. var newSSRC = Object.keys(videoSSRCs)[0];
  119. console.info(
  120. "Replacing new video SSRC: " + newSSRC +
  121. " with " + localVideoSSRC);
  122. localDescription.sdp =
  123. newSdp.raw.replace(
  124. new RegExp('a=ssrc:' + newSSRC, 'g'),
  125. 'a=ssrc:' + localVideoSSRC);
  126. }
  127. }
  128. return localDescription;
  129. },
  130. /**
  131. * Method must be called before 'source-add' notification is sent. In case
  132. * we have local video SSRC advertised already it will be removed from the
  133. * notification. If no other SSRCs are described by given IQ null will be
  134. * returned which means that there is no point in sending the notification.
  135. * @param sourceAdd 'source-add' Jingle IQ to be processed
  136. * @returns modified 'source-add' IQ which can be sent to the focus or
  137. * <tt>null</tt> if no notification shall be sent. It is no longer
  138. * a Strophe IQ Builder instance, but DOM element tree.
  139. */
  140. processSourceAdd: function (sourceAdd) {
  141. if (!isEnabled)
  142. return sourceAdd;
  143. if (!localVideoSSRC) {
  144. // Store local SSRC if available
  145. storeLocalVideoSSRC(sourceAdd);
  146. return sourceAdd;
  147. } else {
  148. return filterOutSource(sourceAdd, 'source-add');
  149. }
  150. },
  151. /**
  152. * Method must be called before 'source-remove' notification is sent.
  153. * Removes local video SSRC from the notification. If there are no other
  154. * SSRCs described in the given IQ <tt>null</tt> will be returned which
  155. * means that there is no point in sending the notification.
  156. * @param sourceRemove 'source-remove' Jingle IQ to be processed
  157. * @returns modified 'source-remove' IQ which can be sent to the focus or
  158. * <tt>null</tt> if no notification shall be sent. It is no longer
  159. * a Strophe IQ Builder instance, but DOM element tree.
  160. */
  161. processSourceRemove: function (sourceRemove) {
  162. if (!isEnabled)
  163. return sourceRemove;
  164. return filterOutSource(sourceRemove, 'source-remove');
  165. },
  166. /**
  167. * Turns the hack on or off
  168. * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
  169. * to disable it
  170. */
  171. setEnabled: function (enabled) {
  172. isEnabled = enabled;
  173. }
  174. };
  175. module.exports = LocalVideoSSRCHack;