Removes the local SSRC replacement hack and fixes video muting/unmuting when simulcast is enabled.
This commit is contained in:
parent
8ec90ea675
commit
dc07fd733f
|
@ -66,7 +66,8 @@ var RandomUtil = {
|
|||
return ret;
|
||||
},
|
||||
randomElement: randomElement,
|
||||
randomAlphanumStr: randomAlphanumStr
|
||||
randomAlphanumStr: randomAlphanumStr,
|
||||
randomInt: randomInt
|
||||
};
|
||||
|
||||
module.exports = RandomUtil;
|
||||
|
|
|
@ -10,7 +10,6 @@ var transform = require("sdp-transform");
|
|||
var XMPPEvents = require("../../service/xmpp/XMPPEvents");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
var SSRCReplacement = require("./LocalSSRCReplacement");
|
||||
|
||||
// Jingle stuff
|
||||
function JingleSessionPC(me, sid, connection, service, eventEmitter) {
|
||||
|
@ -272,8 +271,6 @@ JingleSessionPC.prototype.accept = function () {
|
|||
//console.log('setLocalDescription success');
|
||||
self.setLocalDescription();
|
||||
|
||||
SSRCReplacement.processSessionInit(accept);
|
||||
|
||||
self.connection.sendIQ(accept,
|
||||
function () {
|
||||
var ack = {};
|
||||
|
@ -371,8 +368,6 @@ JingleSessionPC.prototype.sendIceCandidate = function (candidate) {
|
|||
self.initiator == self.me ? 'initiator' : 'responder',
|
||||
ssrc);
|
||||
|
||||
SSRCReplacement.processSessionInit(init);
|
||||
|
||||
self.connection.sendIQ(init,
|
||||
function () {
|
||||
//console.log('session initiate ack');
|
||||
|
@ -489,8 +484,6 @@ JingleSessionPC.prototype.createdOffer = function (sdp) {
|
|||
init,
|
||||
this.initiator == this.me ? 'initiator' : 'responder');
|
||||
|
||||
SSRCReplacement.processSessionInit(init);
|
||||
|
||||
self.connection.sendIQ(init,
|
||||
function () {
|
||||
var ack = {};
|
||||
|
@ -787,8 +780,6 @@ JingleSessionPC.prototype.createdAnswer = function (sdp, provisional) {
|
|||
self.initiator == self.me ? 'initiator' : 'responder',
|
||||
ssrcs);
|
||||
|
||||
SSRCReplacement.processSessionInit(accept);
|
||||
|
||||
self.connection.sendIQ(accept,
|
||||
function () {
|
||||
var ack = {};
|
||||
|
@ -1227,11 +1218,6 @@ JingleSessionPC.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
|
|||
);
|
||||
var removed = sdpDiffer.toJingle(remove);
|
||||
|
||||
// Let 'source-remove' IQ through the hack and see if we're allowed to send
|
||||
// it in the current form
|
||||
if (removed)
|
||||
remove = SSRCReplacement.processSourceRemove(remove);
|
||||
|
||||
if (removed && remove) {
|
||||
console.info("Sending source-remove", remove);
|
||||
this.connection.sendIQ(remove,
|
||||
|
@ -1258,11 +1244,6 @@ JingleSessionPC.prototype.notifyMySSRCUpdate = function (old_sdp, new_sdp) {
|
|||
);
|
||||
var added = sdpDiffer.toJingle(add);
|
||||
|
||||
// Let 'source-add' IQ through the hack and see if we're allowed to send
|
||||
// it in the current form
|
||||
if (added)
|
||||
add = SSRCReplacement.processSourceAdd(add);
|
||||
|
||||
if (added && add) {
|
||||
console.info("Sending source-add", add);
|
||||
this.connection.sendIQ(add,
|
||||
|
|
|
@ -1,271 +0,0 @@
|
|||
/* 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 =
|
||||
localVideoSSRC ?
|
||||
localVideoSSRC : 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
|
||||
var sdp = new SDP(localDescription.sdp);
|
||||
if (sdp.media.length < 2)
|
||||
return;
|
||||
|
||||
if (localVideoSSRC && sdp.media[1].indexOf("a=ssrc:") !== -1 &&
|
||||
!sdp.containsSSRC(localVideoSSRC)) {
|
||||
|
||||
console.info("Does not contain: "
|
||||
+ localVideoSSRC + "---" + sdp.media[1]);
|
||||
|
||||
// Get new video SSRC
|
||||
var map = sdp.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 =
|
||||
sdp.raw.replace(
|
||||
new RegExp('a=ssrc:' + newSSRC, 'g'),
|
||||
'a=ssrc:' + localVideoSSRC);
|
||||
}
|
||||
else if (sdp.media[1].indexOf('a=ssrc:') === -1 &&
|
||||
sdp.media[1].indexOf('a=recvonly') !== -1) {
|
||||
// Make sure we have any SSRC for recvonly video stream
|
||||
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;
|
|
@ -4,7 +4,6 @@
|
|||
var RTC = require('../RTC/RTC');
|
||||
var RTCBrowserType = require("../RTC/RTCBrowserType");
|
||||
var RTCEvents = require("../../service/RTC/RTCEvents");
|
||||
var SSRCReplacement = require("./LocalSSRCReplacement");
|
||||
|
||||
function TraceablePeerConnection(ice_config, constraints, session) {
|
||||
var self = this;
|
||||
|
@ -138,6 +137,47 @@ var dumpSDP = function(description) {
|
|||
return 'type: ' + description.type + '\r\n' + description.sdp;
|
||||
};
|
||||
|
||||
var insertRecvOnlySSRC = function (desc) {
|
||||
if (typeof desc !== 'object' || desc === null ||
|
||||
typeof desc.sdp !== 'string') {
|
||||
console.warn('An empty description was passed as an argument.');
|
||||
return desc;
|
||||
}
|
||||
|
||||
var transform = require('sdp-transform');
|
||||
var RandomUtil = require('../util/RandomUtil');
|
||||
|
||||
var session = transform.parse(desc.sdp);
|
||||
if (!Array.isArray(session.media))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var modded = false;
|
||||
session.media.forEach(function (bLine) {
|
||||
if (bLine.direction != 'recvonly')
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
modded = true;
|
||||
if (!Array.isArray(bLine.ssrcs) || bLine.ssrcs.length === 0)
|
||||
{
|
||||
var ssrc = RandomUtil.randomInt(1, 0xffffffff);
|
||||
bLine.ssrcs = [{
|
||||
id: ssrc,
|
||||
attribute: 'cname',
|
||||
value: ['recvonly-', ssrc].join(' ')
|
||||
}];
|
||||
}
|
||||
});
|
||||
|
||||
return (!modded) ? desc : new RTCSessionDescription({
|
||||
type: desc.type,
|
||||
sdp: transform.write(session),
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Takes a SessionDescription object and returns a "normalized" version.
|
||||
* Currently it only takes care of ordering the a=ssrc lines.
|
||||
|
@ -218,10 +258,6 @@ if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
|
|||
function() {
|
||||
var desc = this.peerconnection.localDescription;
|
||||
|
||||
// FIXME this should probably be after the Unified Plan -> Plan B
|
||||
// transformation.
|
||||
desc = SSRCReplacement.mungeLocalVideoSSRC(desc);
|
||||
|
||||
this.trace('getLocalDescription::preTransform', dumpSDP(desc));
|
||||
|
||||
// if we're running on FF, transform to Plan B first.
|
||||
|
@ -367,8 +403,11 @@ TraceablePeerConnection.prototype.createOffer
|
|||
self.trace('createOfferOnSuccess::postTransform (Plan B)', dumpSDP(offer));
|
||||
}
|
||||
|
||||
offer = SSRCReplacement.mungeLocalVideoSSRC(offer);
|
||||
if (RTCBrowserType.isChrome())
|
||||
{
|
||||
offer = insertRecvOnlySSRC(offer);
|
||||
self.trace('createOfferOnSuccess::mungeLocalVideoSSRC', dumpSDP(offer));
|
||||
}
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
offer = self.simulcast.mungeLocalDescription(offer);
|
||||
|
@ -398,9 +437,11 @@ TraceablePeerConnection.prototype.createAnswer
|
|||
self.trace('createAnswerOnSuccess::postTransform (Plan B)', dumpSDP(answer));
|
||||
}
|
||||
|
||||
// munge local video SSRC
|
||||
answer = SSRCReplacement.mungeLocalVideoSSRC(answer);
|
||||
if (RTCBrowserType.isChrome())
|
||||
{
|
||||
answer = insertRecvOnlySSRC(answer);
|
||||
self.trace('createAnswerOnSuccess::mungeLocalVideoSSRC', dumpSDP(answer));
|
||||
}
|
||||
|
||||
if (config.enableSimulcast && self.simulcast.isSupported()) {
|
||||
answer = self.simulcast.mungeLocalDescription(answer);
|
||||
|
|
Loading…
Reference in New Issue