123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268 |
- /* global $ */
-
- /*
- Here we do modifications of local video SSRCs. There are 2 situations we have
- to handle:
-
- 1. We generate SSRC for local recvonly video stream. This is the case when we
- have no local camera and it is not generated automatically, but SSRC=1 is
- used implicitly. If that happens RTCP packets will be dropped by the JVB
- and we won't be able to request video key frames correctly.
-
- 2. A hack to re-use SSRC of the first video stream for any new stream created
- in future. It turned out that Chrome may keep on using the SSRC of removed
- video stream in RTCP even though a new one has been created. So we just
- want to avoid that by re-using it. Jingle 'source-remove'/'source-add'
- notifications are blocked once first video SSRC is advertised to the focus.
-
- What this hack does:
-
- 1. Stores the SSRC of the first video stream created by
- a) scanning Jingle session-accept/session-invite for existing video SSRC
- b) watching for 'source-add' for new video stream if it has not been
- created in step a)
- 2. Exposes method 'mungeLocalVideoSSRC' which replaces any new video SSRC with
- the stored one. It is called by 'TracablePeerConnection' before local SDP is
- returned to the other parts of the application.
- 3. Scans 'source-remove'/'source-add' notifications for stored video SSRC and
- blocks those notifications. This makes Jicofo and all participants think
- that it exists all the time even if the video stream has been removed or
- replaced locally. Thanks to that there is no additional signaling activity
- on video mute or when switching to the desktop stream.
- */
-
- var SDP = require('./SDP');
- var RTCBrowserType = require('../RTC/RTCBrowserType');
-
- /**
- * The hack is enabled on all browsers except FF by default
- * FIXME finish the hack once removeStream method is implemented in FF
- * @type {boolean}
- */
- var isEnabled = !RTCBrowserType.isFirefox();
-
- /**
- * Stored SSRC of local video stream.
- */
- var localVideoSSRC;
-
- /**
- * SSRC used for recvonly video stream when we have no local camera.
- * This is in order to tell Chrome what SSRC should be used in RTCP requests
- * instead of 1.
- */
- var localRecvOnlySSRC;
-
- /**
- * cname for <tt>localRecvOnlySSRC</tt>
- */
- var localRecvOnlyCName;
-
- /**
- * Method removes <source> element which describes <tt>localVideoSSRC</tt>
- * from given Jingle IQ.
- * @param modifyIq 'source-add' or 'source-remove' Jingle IQ.
- * @param actionName display name of the action which will be printed in log
- * messages.
- * @returns {*} modified Jingle IQ, so that it does not contain <source> element
- * corresponding to <tt>localVideoSSRC</tt> or <tt>null</tt> if no
- * other SSRCs left to be signaled after removing it.
- */
- var filterOutSource = function (modifyIq, actionName) {
- var modifyIqTree = $(modifyIq.tree());
-
- if (!localVideoSSRC)
- return modifyIqTree[0];
-
- var videoSSRC = modifyIqTree.find(
- '>jingle>content[name="video"]' +
- '>description>source[ssrc="' + localVideoSSRC + '"]');
-
- if (!videoSSRC.length) {
- return modifyIqTree[0];
- }
-
- console.info(
- 'Blocking ' + actionName + ' for local video SSRC: ' + localVideoSSRC);
-
- videoSSRC.remove();
-
- // Check if any sources still left to be added/removed
- if (modifyIqTree.find('>jingle>content>description>source').length) {
- return modifyIqTree[0];
- } else {
- return null;
- }
- };
-
- /**
- * Scans given Jingle IQ for video SSRC and stores it.
- * @param jingleIq the Jingle IQ to be scanned for video SSRC.
- */
- var storeLocalVideoSSRC = function (jingleIq) {
- var videoSSRCs =
- $(jingleIq.tree())
- .find('>jingle>content[name="video"]>description>source');
-
- videoSSRCs.each(function (idx, ssrcElem) {
- if (localVideoSSRC)
- return;
- // We consider SSRC real only if it has msid attribute
- // recvonly streams in FF do not have it as well as local SSRCs
- // we generate for recvonly streams in Chrome
- var ssrSel = $(ssrcElem);
- var msid = ssrSel.find('>parameter[name="msid"]');
- if (msid.length) {
- var ssrcVal = ssrSel.attr('ssrc');
- if (ssrcVal) {
- localVideoSSRC = ssrcVal;
- console.info('Stored local video SSRC' +
- ' for future re-use: ' + localVideoSSRC);
- }
- }
- });
- };
-
- /**
- * Generates new SSRC for local video recvonly stream.
- * FIXME what about eventual SSRC collision ?
- */
- function generateRecvonlySSRC() {
- //
- localRecvOnlySSRC =
- Math.random().toString(10).substring(2, 11);
- localRecvOnlyCName =
- Math.random().toString(36).substring(2);
- console.info(
- "Generated local recvonly SSRC: " + localRecvOnlySSRC +
- ", cname: " + localRecvOnlyCName);
- }
-
- var LocalSSRCReplacement = {
- /**
- * Method must be called before 'session-initiate' or 'session-invite' is
- * sent. Scans the IQ for local video SSRC and stores it if detected.
- *
- * @param sessionInit our 'session-initiate' or 'session-accept' Jingle IQ
- * which will be scanned for local video SSRC.
- */
- processSessionInit: function (sessionInit) {
- if (!isEnabled)
- return;
-
- if (localVideoSSRC) {
- console.error("Local SSRC stored already: " + localVideoSSRC);
- return;
- }
- storeLocalVideoSSRC(sessionInit);
- },
- /**
- * If we have local video SSRC stored searched given
- * <tt>localDescription</tt> for video SSRC and makes sure it is replaced
- * with the stored one.
- * @param localDescription local description object that will have local
- * video SSRC replaced with the stored one
- * @returns modified <tt>localDescription</tt> object.
- */
- mungeLocalVideoSSRC: function (localDescription) {
- if (!isEnabled)
- return localDescription;
-
- if (!localDescription) {
- console.warn("localDescription is null or undefined");
- return localDescription;
- }
-
- // IF we have local video SSRC stored make sure it is replaced
- // with old SSRC
- if (localVideoSSRC) {
- var newSdp = new SDP(localDescription.sdp);
- if (newSdp.media[1].indexOf("a=ssrc:") !== -1 &&
- !newSdp.containsSSRC(localVideoSSRC)) {
- // Get new video SSRC
- var map = newSdp.getMediaSsrcMap();
- var videoPart = map[1];
- var videoSSRCs = videoPart.ssrcs;
- var newSSRC = Object.keys(videoSSRCs)[0];
-
- console.info(
- "Replacing new video SSRC: " + newSSRC +
- " with " + localVideoSSRC);
-
- localDescription.sdp =
- newSdp.raw.replace(
- new RegExp('a=ssrc:' + newSSRC, 'g'),
- 'a=ssrc:' + localVideoSSRC);
- }
- } else {
- // Make sure we have any SSRC for recvonly video stream
- var sdp = new SDP(localDescription.sdp);
-
- if (sdp.media[1] && sdp.media[1].indexOf('a=ssrc:') === -1 &&
- sdp.media[1].indexOf('a=recvonly') !== -1) {
-
- if (!localRecvOnlySSRC) {
- generateRecvonlySSRC();
- }
-
- console.info('No SSRC in video recvonly stream' +
- ' - adding SSRC: ' + localRecvOnlySSRC);
-
- sdp.media[1] += 'a=ssrc:' + localRecvOnlySSRC +
- ' cname:' + localRecvOnlyCName + '\r\n';
-
- localDescription.sdp = sdp.session + sdp.media.join('');
- }
- }
- return localDescription;
- },
- /**
- * Method must be called before 'source-add' notification is sent. In case
- * we have local video SSRC advertised already it will be removed from the
- * notification. If no other SSRCs are described by given IQ null will be
- * returned which means that there is no point in sending the notification.
- * @param sourceAdd 'source-add' Jingle IQ to be processed
- * @returns modified 'source-add' IQ which can be sent to the focus or
- * <tt>null</tt> if no notification shall be sent. It is no longer
- * a Strophe IQ Builder instance, but DOM element tree.
- */
- processSourceAdd: function (sourceAdd) {
- if (!isEnabled)
- return sourceAdd;
-
- if (!localVideoSSRC) {
- // Store local SSRC if available
- storeLocalVideoSSRC(sourceAdd);
- return sourceAdd;
- } else {
- return filterOutSource(sourceAdd, 'source-add');
- }
- },
- /**
- * Method must be called before 'source-remove' notification is sent.
- * Removes local video SSRC from the notification. If there are no other
- * SSRCs described in the given IQ <tt>null</tt> will be returned which
- * means that there is no point in sending the notification.
- * @param sourceRemove 'source-remove' Jingle IQ to be processed
- * @returns modified 'source-remove' IQ which can be sent to the focus or
- * <tt>null</tt> if no notification shall be sent. It is no longer
- * a Strophe IQ Builder instance, but DOM element tree.
- */
- processSourceRemove: function (sourceRemove) {
- if (!isEnabled)
- return sourceRemove;
-
- return filterOutSource(sourceRemove, 'source-remove');
- },
-
- /**
- * Turns the hack on or off
- * @param enabled <tt>true</tt> to enable the hack or <tt>false</tt>
- * to disable it
- */
- setEnabled: function (enabled) {
- isEnabled = enabled;
- }
- };
-
- module.exports = LocalSSRCReplacement;
|