diff --git a/index.html b/index.html
index 757b193d3..73e83afa7 100644
--- a/index.html
+++ b/index.html
@@ -10,7 +10,8 @@
-
+
+
@@ -27,7 +28,6 @@
-
diff --git a/simulcast.js b/simulcast.js
index 58638412b..6ee8e754f 100644
--- a/simulcast.js
+++ b/simulcast.js
@@ -7,12 +7,6 @@
function Simulcast() {
"use strict";
- // TODO(gp) split the Simulcast class in two classes : NativeSimulcast and ClassicSimulcast.
-
- // Once we properly support native simulcast, enable it automatically in the
- // supported browsers (Chrome).
- this.useNativeSimulcast = false;
-
// TODO(gp) we need a logging framework for javascript à la log4j or the
// java logging framework that allows for selective log display
this.debugLvl = 0;
@@ -336,7 +330,7 @@ Simulcast.prototype = {
},
_appendSimulcastGroup: function (lines) {
- var videoSources, ssrcGroup, simSSRC, numOfSubs = 3, i, sb, msid;
+ var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
if (this.debugLvl) {
console.info('Appending simulcast group...');
@@ -442,40 +436,6 @@ Simulcast.prototype = {
return sb;
},
- /**
- * Ensures that the simulcast group is present in the answer, _if_ native
- * simulcast is enabled,
- *
- * @param desc
- * @returns {*}
- */
- transformAnswer: function (desc) {
- if (config.enableSimulcast && this.useNativeSimulcast) {
-
- var sb = desc.sdp.split('\r\n');
-
- // Even if we have enabled native simulcasting previously
- // (with a call to SLD with an appropriate SDP, for example),
- // createAnswer seems to consistently generate incomplete SDP
- // with missing SSRCS.
- //
- // So, subsequent calls to SLD will have missing SSRCS and presence
- // won't have the complete list of SRCs.
- this._ensureSimulcastGroup(sb);
-
- desc = new RTCSessionDescription({
- type: desc.type,
- sdp: sb.join('\r\n')
- });
-
- if (this.debugLvl && this.debugLvl > 1) {
- console.info('Transformed answer');
- console.info(desc.sdp);
- }
- }
-
- return desc;
- },
_restoreSimulcastGroups: function (sb) {
this._restoreRemoteVideoSources(sb);
@@ -511,55 +471,6 @@ Simulcast.prototype = {
return desc;
},
- /**
- * Prepares the local description for public usage (i.e. to be signaled
- * through Jingle to the focus).
- *
- * @param desc
- * @returns {RTCSessionDescription}
- */
- reverseTransformLocalDescription: function (desc) {
- var sb;
-
- if (!desc || desc == null) {
- return desc;
- }
-
- if (config.enableSimulcast) {
-
- if (this.useNativeSimulcast) {
- sb = desc.sdp.split('\r\n');
-
- this._explodeLocalSimulcastSources(sb);
-
- desc = new RTCSessionDescription({
- type: desc.type,
- sdp: sb.join('\r\n')
- });
-
- if (this.debugLvl && this.debugLvl > 1) {
- console.info('Exploded local video sources');
- console.info(desc.sdp);
- }
- } else {
- sb = desc.sdp.split('\r\n');
-
- this._groupLocalVideoSources(sb);
-
- desc = new RTCSessionDescription({
- type: desc.type,
- sdp: sb.join('\r\n')
- });
-
- if (this.debugLvl && this.debugLvl > 1) {
- console.info('Grouped local video sources');
- console.info(desc.sdp);
- }
- }
- }
-
- return desc;
- },
_ensureOrder: function (lines) {
var videoSources, sb;
@@ -593,70 +504,6 @@ Simulcast.prototype = {
}
},
- /**
- *
- *
- * @param desc
- * @returns {*}
- */
- transformLocalDescription: function (desc) {
- if (config.enableSimulcast && !this.useNativeSimulcast) {
-
- var sb = desc.sdp.split('\r\n');
-
- this._removeSimulcastGroup(sb);
-
- desc = new RTCSessionDescription({
- type: desc.type,
- sdp: sb.join('\r\n')
- });
-
- if (this.debugLvl && this.debugLvl > 1) {
- console.info('Transformed local description');
- console.info(desc.sdp);
- }
- }
-
- return desc;
- },
-
- /**
- * Removes the ssrc-group:SIM from the remote description bacause Chrome
- * either gets confused and thinks this is an FID group or, if an FID group
- * is already present, it fails to set the remote description.
- *
- * @param desc
- * @returns {*}
- */
- transformRemoteDescription: function (desc) {
- if (config.enableSimulcast) {
-
- var sb = desc.sdp.split('\r\n');
-
- this._updateRemoteMaps(sb);
- this._cacheRemoteVideoSources(sb);
- this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
-
- if (this.useNativeSimulcast) {
- // We don't need the goog conference flag if we're not doing
- // native simulcast.
- this._ensureGoogConference(sb);
- }
-
- desc = new RTCSessionDescription({
- type: desc.type,
- sdp: sb.join('\r\n')
- });
-
- if (this.debugLvl && this.debugLvl > 1) {
- console.info('Transformed remote description');
- console.info(desc.sdp);
- }
- }
-
- return desc;
- },
-
_setReceivingVideoStream: function (endpoint, ssrc) {
this.remoteMaps.receivingVideoStreams[endpoint] = ssrc;
},
@@ -708,83 +555,6 @@ Simulcast.prototype = {
localStream: null, displayedLocalVideoStream: null,
- /**
- * GUM for simulcast.
- *
- * @param constraints
- * @param success
- * @param err
- */
- getUserMedia: function (constraints, success, err) {
-
- // TODO(gp) what if we request a resolution not supported by the hardware?
- // TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
- var lqConstraints = {
- audio: false,
- video: {
- mandatory: {
- maxWidth: 320,
- maxHeight: 180,
- maxFrameRate: 15
- }
- }
- };
-
- console.log('HQ constraints: ', constraints);
- console.log('LQ constraints: ', lqConstraints);
-
- if (config.enableSimulcast && !this.useNativeSimulcast) {
-
- // NOTE(gp) if we request the lq stream first webkitGetUserMedia
- // fails randomly. Tested with Chrome 37. As fippo suggested, the
- // reason appears to be that Chrome only acquires the cam once and
- // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
-
- var self = this;
- navigator.webkitGetUserMedia(constraints, function (hqStream) {
-
- self.localStream = hqStream;
-
- // reset local maps.
- self.localMaps.msids = [];
- self.localMaps.msid2ssrc = {};
-
- // add hq trackid to local map
- self.localMaps.msids.push(hqStream.getVideoTracks()[0].id);
-
- navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
-
- self.displayedLocalVideoStream = lqStream;
-
- // NOTE(gp) The specification says Array.forEach() will visit
- // the array elements in numeric order, and that it doesn't
- // visit elements that don't exist.
-
- // add lq trackid to local map
- self.localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
-
- self.localStream.addTrack(lqStream.getVideoTracks()[0]);
- success(self.localStream);
- }, err);
- }, err);
- } else {
-
- // There's nothing special to do for native simulcast, so just do a normal GUM.
-
- navigator.webkitGetUserMedia(constraints, function (hqStream) {
-
- // reset local maps.
- self.localMaps.msids = [];
- self.localMaps.msid2ssrc = {};
-
- // add hq stream to local map
- self.localMaps.msids.push(hqStream.getVideoTracks()[0].id);
- self.displayedLocalVideoStream = this.localStream = hqStream;
- success(self.localStream);
- }, err);
- }
- },
-
/**
* Gets the fully qualified msid (stream.id + track.id) associated to the
* SSRC.
@@ -801,40 +571,467 @@ Simulcast.prototype = {
return this._parseMedia(lines, mediatypes);
},
- _setLocalVideoStreamEnabled: function (ssrc, enabled) {
- var trackid;
-
- var self = this;
- console.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
- if (Object.keys(this.localMaps.msid2ssrc).some(function (tid) {
- // Search for the track id that corresponds to the ssrc
- if (self.localMaps.msid2ssrc[tid] == ssrc) {
- trackid = tid;
- return true;
- }
- }) && self.localStream.getVideoTracks().some(function (track) {
- // Start/stop the track that corresponds to the track id
- if (track.id === trackid) {
- track.enabled = enabled;
- return true;
- }
- })) {
- console.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
- $(document).trigger(enabled
- ? 'simulcastlayerstarted'
- : 'simulcastlayerstopped');
- } else {
- console.error("I don't have a local stream with SSRC " + ssrc);
- }
- },
-
getLocalVideoStream: function () {
return (this.displayedLocalVideoStream != null)
? this.displayedLocalVideoStream
// in case we have no simulcast at all, i.e. we didn't perform the GUM
: connection.jingle.localVideo;
}
+};
+
+
+function NativeSimulcast() {
+ Simulcast.call(this); // call the super constructor.
}
+
+NativeSimulcast.prototype = Object.create(Simulcast.prototype);
+
+/**
+ * GUM for simulcast.
+ *
+ * @param constraints
+ * @param success
+ * @param err
+ */
+NativeSimulcast.prototype.getUserMedia = function (constraints, success, err) {
+
+ // There's nothing special to do for native simulcast, so just do a normal GUM.
+
+ var self = this;
+ navigator.webkitGetUserMedia(constraints, function (hqStream) {
+
+ // reset local maps.
+ self.localMaps.msids = [];
+ self.localMaps.msid2ssrc = {};
+
+ // add hq stream to local map
+ self.localMaps.msids.push(hqStream.getVideoTracks()[0].id);
+ self.displayedLocalVideoStream = self.localStream = hqStream;
+ success(self.localStream);
+ }, err);
+};
+
+/**
+ * Prepares the local description for public usage (i.e. to be signaled
+ * through Jingle to the focus).
+ *
+ * @param desc
+ * @returns {RTCSessionDescription}
+ */
+NativeSimulcast.prototype.reverseTransformLocalDescription = function (desc) {
+ var sb;
+
+ if (!desc || desc == null) {
+ return desc;
+ }
+
+ if (config.enableSimulcast) {
+ sb = desc.sdp.split('\r\n');
+
+ this._explodeLocalSimulcastSources(sb);
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Exploded local video sources');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+/**
+ * Ensures that the simulcast group is present in the answer, _if_ native
+ * simulcast is enabled,
+ *
+ * @param desc
+ * @returns {*}
+ */
+NativeSimulcast.prototype.transformAnswer = function (desc) {
+ if (config.enableSimulcast) {
+
+ var sb = desc.sdp.split('\r\n');
+
+ // Even if we have enabled native simulcasting previously
+ // (with a call to SLD with an appropriate SDP, for example),
+ // createAnswer seems to consistently generate incomplete SDP
+ // with missing SSRCS.
+ //
+ // So, subsequent calls to SLD will have missing SSRCS and presence
+ // won't have the complete list of SRCs.
+ this._ensureSimulcastGroup(sb);
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Transformed answer');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+
+/**
+ *
+ *
+ * @param desc
+ * @returns {*}
+ */
+NativeSimulcast.prototype.transformLocalDescription = function (desc) {
+ return desc;
+};
+
+/**
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
+ * either gets confused and thinks this is an FID group or, if an FID group
+ * is already present, it fails to set the remote description.
+ *
+ * @param desc
+ * @returns {*}
+ */
+NativeSimulcast.prototype.transformRemoteDescription = function (desc) {
+ if (config.enableSimulcast) {
+
+ var sb = desc.sdp.split('\r\n');
+
+ this._updateRemoteMaps(sb);
+ this._cacheRemoteVideoSources(sb);
+ this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
+ // We don't need the goog conference flag if we're not doing
+ // native simulcast.
+ this._ensureGoogConference(sb);
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Transformed remote description');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+NativeSimulcast.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
+ // Nothing to do here, native simulcast does that auto-magically.
+};
+
+NativeSimulcast.prototype.constructor = NativeSimulcast;
+
+function GrumpySimulcast() {
+ Simulcast.call(this);
+}
+
+GrumpySimulcast.prototype = Object.create(Simulcast.prototype);
+
+/**
+ * GUM for simulcast.
+ *
+ * @param constraints
+ * @param success
+ * @param err
+ */
+GrumpySimulcast.prototype.getUserMedia = function (constraints, success, err) {
+
+ // TODO(gp) what if we request a resolution not supported by the hardware?
+ // TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
+ var lqConstraints = {
+ audio: false,
+ video: {
+ mandatory: {
+ maxWidth: 320,
+ maxHeight: 180,
+ maxFrameRate: 15
+ }
+ }
+ };
+
+ console.log('HQ constraints: ', constraints);
+ console.log('LQ constraints: ', lqConstraints);
+
+ if (config.enableSimulcast) {
+
+ // NOTE(gp) if we request the lq stream first webkitGetUserMedia
+ // fails randomly. Tested with Chrome 37. As fippo suggested, the
+ // reason appears to be that Chrome only acquires the cam once and
+ // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
+
+ var self = this;
+ navigator.webkitGetUserMedia(constraints, function (hqStream) {
+
+ self.localStream = hqStream;
+
+ // reset local maps.
+ self.localMaps.msids = [];
+ self.localMaps.msid2ssrc = {};
+
+ // add hq trackid to local map
+ self.localMaps.msids.push(hqStream.getVideoTracks()[0].id);
+
+ navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
+
+ self.displayedLocalVideoStream = lqStream;
+
+ // NOTE(gp) The specification says Array.forEach() will visit
+ // the array elements in numeric order, and that it doesn't
+ // visit elements that don't exist.
+
+ // add lq trackid to local map
+ self.localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
+
+ self.localStream.addTrack(lqStream.getVideoTracks()[0]);
+ success(self.localStream);
+ }, err);
+ }, err);
+ }
+};
+
+/**
+ * Prepares the local description for public usage (i.e. to be signaled
+ * through Jingle to the focus).
+ *
+ * @param desc
+ * @returns {RTCSessionDescription}
+ */
+GrumpySimulcast.prototype.reverseTransformLocalDescription = function (desc) {
+ var sb;
+
+ if (!desc || desc == null) {
+ return desc;
+ }
+
+ if (config.enableSimulcast) {
+
+
+ sb = desc.sdp.split('\r\n');
+
+ this._groupLocalVideoSources(sb);
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Grouped local video sources');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+/**
+ * Ensures that the simulcast group is present in the answer, _if_ native
+ * simulcast is enabled,
+ *
+ * @param desc
+ * @returns {*}
+ */
+GrumpySimulcast.prototype.transformAnswer = function (desc) {
+ return desc;
+};
+
+
+/**
+ *
+ *
+ * @param desc
+ * @returns {*}
+ */
+GrumpySimulcast.prototype.transformLocalDescription = function (desc) {
+ if (config.enableSimulcast) {
+
+ var sb = desc.sdp.split('\r\n');
+
+ this._removeSimulcastGroup(sb);
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Transformed local description');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+/**
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
+ * either gets confused and thinks this is an FID group or, if an FID group
+ * is already present, it fails to set the remote description.
+ *
+ * @param desc
+ * @returns {*}
+ */
+GrumpySimulcast.prototype.transformRemoteDescription = function (desc) {
+ if (config.enableSimulcast) {
+
+ var sb = desc.sdp.split('\r\n');
+
+ this._updateRemoteMaps(sb);
+ this._cacheRemoteVideoSources(sb);
+ this._removeSimulcastGroup(sb); // NOTE(gp) this needs to be called after updateRemoteMaps because we need the simulcast group in the _updateRemoteMaps() method.
+
+ desc = new RTCSessionDescription({
+ type: desc.type,
+ sdp: sb.join('\r\n')
+ });
+
+ if (this.debugLvl && this.debugLvl > 1) {
+ console.info('Transformed remote description');
+ console.info(desc.sdp);
+ }
+ }
+
+ return desc;
+};
+
+GrumpySimulcast.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
+ var trackid;
+
+ var self = this;
+ console.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
+ if (Object.keys(this.localMaps.msid2ssrc).some(function (tid) {
+ // Search for the track id that corresponds to the ssrc
+ if (self.localMaps.msid2ssrc[tid] == ssrc) {
+ trackid = tid;
+ return true;
+ }
+ }) && self.localStream.getVideoTracks().some(function (track) {
+ // Start/stop the track that corresponds to the track id
+ if (track.id === trackid) {
+ track.enabled = enabled;
+ return true;
+ }
+ })) {
+ console.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
+ $(document).trigger(enabled
+ ? 'simulcastlayerstarted'
+ : 'simulcastlayerstopped');
+ } else {
+ console.error("I don't have a local stream with SSRC " + ssrc);
+ }
+};
+
+GrumpySimulcast.prototype.constructor = GrumpySimulcast;
+
+function NoSimulcast() {
+ Simulcast.call(this);
+}
+
+NoSimulcast.prototype = Object.create(Simulcast.prototype);
+
+/**
+ * GUM for simulcast.
+ *
+ * @param constraints
+ * @param success
+ * @param err
+ */
+NoSimulcast.prototype.getUserMedia = function (constraints, success, err) {
+ var self = this;
+ navigator.webkitGetUserMedia(constraints, function (hqStream) {
+
+ // reset local maps.
+ self.localMaps.msids = [];
+ self.localMaps.msid2ssrc = {};
+
+ // add hq stream to local map
+ self.localMaps.msids.push(hqStream.getVideoTracks()[0].id);
+ self.displayedLocalVideoStream = self.localStream = hqStream;
+ success(self.localStream);
+ }, err);
+};
+
+/**
+ * Prepares the local description for public usage (i.e. to be signaled
+ * through Jingle to the focus).
+ *
+ * @param desc
+ * @returns {RTCSessionDescription}
+ */
+NoSimulcast.prototype.reverseTransformLocalDescription = function (desc) {
+ return desc;
+};
+
+/**
+ * Ensures that the simulcast group is present in the answer, _if_ native
+ * simulcast is enabled,
+ *
+ * @param desc
+ * @returns {*}
+ */
+NoSimulcast.prototype.transformAnswer = function (desc) {
+ return desc;
+};
+
+
+/**
+ *
+ *
+ * @param desc
+ * @returns {*}
+ */
+NoSimulcast.prototype.transformLocalDescription = function (desc) {
+ return desc;
+};
+
+/**
+ * Removes the ssrc-group:SIM from the remote description bacause Chrome
+ * either gets confused and thinks this is an FID group or, if an FID group
+ * is already present, it fails to set the remote description.
+ *
+ * @param desc
+ * @returns {*}
+ */
+NoSimulcast.prototype.transformRemoteDescription = function (desc) {
+ return desc;
+};
+
+NoSimulcast.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
+
+};
+
+NoSimulcast.prototype.constructor = NoSimulcast;
+
+// Initialize simulcast.
+var simulcast;
+if (!config.enableSimulcast) {
+ simulcast = new NoSimulcast();
+} else {
+
+ var isChromium = window.chrome,
+ vendorName = window.navigator.vendor;
+ if(isChromium !== null && isChromium !== undefined && vendorName === "Google Inc.") {
+ var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
+ if (ver > 37) {
+ simulcast = new NativeSimulcast();
+ } else {
+ simulcast = new NoSimulcast();
+ }
+ } else {
+ simulcast = new NoSimulcast();
+ }
+
+}
+
$(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
endpointSimulcastLayers.forEach(function (esl) {
var ssrc = esl.simulcastLayer.primarySSRC;
@@ -850,6 +1047,4 @@ $(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
$(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
var ssrc = simulcastLayer.primarySSRC;
simulcast._setLocalVideoStreamEnabled(ssrc, false);
-});
-
-var simulcast = new Simulcast();
\ No newline at end of file
+});
\ No newline at end of file