| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262 | 
							- !function(e){if("object"==typeof exports&&"undefined"!=typeof module)module.exports=e();else if("function"==typeof define&&define.amd)define([],e);else{var f;"undefined"!=typeof window?f=window:"undefined"!=typeof global?f=global:"undefined"!=typeof self&&(f=self),f.simulcast=e()}}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
 - /**
 -  *
 -  * @constructor
 -  */
 - function SimulcastLogger(name, lvl) {
 -     this.name = name;
 -     this.lvl = lvl;
 - }
 - 
 - SimulcastLogger.prototype.log = function (text) {
 -     if (this.lvl) {
 -         console.log(text);
 -     }
 - };
 - 
 - SimulcastLogger.prototype.info = function (text) {
 -     if (this.lvl > 1) {
 -         console.info(text);
 -     }
 - };
 - 
 - SimulcastLogger.prototype.fine = function (text) {
 -     if (this.lvl > 2) {
 -         console.log(text);
 -     }
 - };
 - 
 - SimulcastLogger.prototype.error = function (text) {
 -     console.error(text);
 - };
 - 
 - module.exports = SimulcastLogger;
 - },{}],2:[function(require,module,exports){
 - var SimulcastLogger = require("./SimulcastLogger");
 - var SimulcastUtils = require("./SimulcastUtils");
 - 
 - function SimulcastReceiver() {
 -     this.simulcastUtils = new SimulcastUtils();
 -     this.logger = new SimulcastLogger('SimulcastReceiver', 1);
 - }
 - 
 - SimulcastReceiver.prototype._remoteVideoSourceCache = '';
 - SimulcastReceiver.prototype._remoteMaps = {
 -     msid2Quality: {},
 -     ssrc2Msid: {},
 -     msid2ssrc: {},
 -     receivingVideoStreams: {}
 - };
 - 
 - SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
 -     this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
 - };
 - 
 - SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
 -     this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
 - };
 - 
 - SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
 -     var sb;
 - 
 -     this.logger.info('Ensuring x-google-conference flag...')
 - 
 -     if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
 -         // TODO(gp) do that for the audio as well as suggested by fippo.
 -         // Add the google conference flag
 -         sb = this.simulcastUtils._getVideoSources(lines);
 -         sb = ['a=x-google-flag:conference'].concat(sb);
 -         this.simulcastUtils._replaceVideoSources(lines, sb);
 -     }
 - };
 - 
 - SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
 -     this._restoreRemoteVideoSources(sb);
 - };
 - 
 - /**
 -  * Restores the simulcast groups of the remote description. In
 -  * transformRemoteDescription we remove those in order for the set remote
 -  * description to succeed. The focus needs the signal the groups to new
 -  * participants.
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
 -     var sb;
 - 
 -     if (!this.simulcastUtils.isValidDescription(desc)) {
 -         return desc;
 -     }
 - 
 -     if (config.enableSimulcast) {
 -         sb = desc.sdp.split('\r\n');
 - 
 -         this._restoreSimulcastGroups(sb);
 - 
 -         desc = new RTCSessionDescription({
 -             type: desc.type,
 -             sdp: sb.join('\r\n')
 -         });
 -     }
 - 
 -     return desc;
 - };
 - 
 - SimulcastUtils.prototype._ensureOrder = function (lines) {
 -     var videoSources, sb;
 - 
 -     videoSources = this.parseMedia(lines, ['video'])[0];
 -     sb = this._compileVideoSources(videoSources);
 - 
 -     this._replaceVideoSources(lines, sb);
 - };
 - 
 - SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
 -     var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
 -         videoSource, quality;
 - 
 -     // (re) initialize the remote maps.
 -     this._remoteMaps.msid2Quality = {};
 -     this._remoteMaps.ssrc2Msid = {};
 -     this._remoteMaps.msid2ssrc = {};
 - 
 -     var self = this;
 -     if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
 -         remoteVideoSources.groups.forEach(function (group) {
 -             if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
 -                 quality = 0;
 -                 group.ssrcs.forEach(function (ssrc) {
 -                     videoSource = remoteVideoSources.sources[ssrc];
 -                     self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
 -                     self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
 -                     self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
 -                 });
 -             }
 -         });
 -     }
 - };
 - 
 - SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
 -     this._remoteMaps.receivingVideoStreams[resource] = ssrc;
 - };
 - 
 - /**
 -  * Returns a stream with single video track, the one currently being
 -  * received by this endpoint.
 -  *
 -  * @param stream the remote simulcast stream.
 -  * @returns {webkitMediaStream}
 -  */
 - SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
 -     var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
 - 
 -     var self = this;
 -     if (config.enableSimulcast) {
 - 
 -         stream.getVideoTracks().some(function (track) {
 -             return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
 -                 var ssrc = self._remoteMaps.receivingVideoStreams[resource];
 -                 var msid = self._remoteMaps.ssrc2Msid[ssrc];
 -                 if (msid == [stream.id, track.id].join(' ')) {
 -                     electedTrack = track;
 -                     return true;
 -                 }
 -             });
 -         });
 - 
 -         if (!electedTrack) {
 -             // we don't have an elected track, choose by initial quality.
 -             tracks = stream.getVideoTracks();
 -             for (i = 0; i < tracks.length; i++) {
 -                 msid = [stream.id, tracks[i].id].join(' ');
 -                 if (this._remoteMaps.msid2Quality[msid] === quality) {
 -                     electedTrack = tracks[i];
 -                     break;
 -                 }
 -             }
 - 
 -             // TODO(gp) if the initialQuality could not be satisfied, lower
 -             // the requirement and try again.
 -         }
 -     }
 - 
 -     return (electedTrack)
 -         ? new webkitMediaStream([electedTrack])
 -         : stream;
 - };
 - 
 - SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
 -     var resource = Strophe.getResourceFromJid(jid);
 -     var ssrc = this._remoteMaps.receivingVideoStreams[resource];
 - 
 -     // If we haven't receiving a "changed" event yet, then we must be receiving
 -     // low quality (that the sender always streams).
 -     if(!ssrc)
 -     {
 -         var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
 -         var remoteStream = remoteStreamObject.getOriginalStream();
 -         var tracks = remoteStream.getVideoTracks();
 -         if (tracks) {
 -             for (var k = 0; k < tracks.length; k++) {
 -                 var track = tracks[k];
 -                 var msid = [remoteStream.id, track.id].join(' ');
 -                 var _ssrc = this._remoteMaps.msid2ssrc[msid];
 -                 var quality = this._remoteMaps.msid2Quality[msid];
 -                 if (quality == 0) {
 -                     ssrc = _ssrc;
 -                 }
 -             }
 -         }
 -     }
 - 
 -     return ssrc;
 - };
 - 
 - SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
 - {
 -     var sid, electedStream;
 -     var i, j, k;
 -     var jid = ssrc2jid[ssrc];
 -     if(jid && RTC.remoteStreams[jid])
 -     {
 -         var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
 -         var remoteStream = remoteStreamObject.getOriginalStream();
 -         var tracks = remoteStream.getVideoTracks();
 -         if (tracks) {
 -             for (k = 0; k < tracks.length; k++) {
 -                 var track = tracks[k];
 -                 var msid = [remoteStream.id, track.id].join(' ');
 -                 var tmp = this._remoteMaps.msid2ssrc[msid];
 -                 if (tmp == ssrc) {
 -                     electedStream = new webkitMediaStream([track]);
 -                     sid = remoteStreamObject.sid;
 -                     // stream found, stop.
 -                     break;
 -                 }
 -             }
 -         }
 - 
 -     }
 -     else
 -     {
 -         console.debug(RTC.remoteStreams, jid, ssrc);
 -     }
 - 
 -     return {
 -         sid: sid,
 -         stream: electedStream
 -     };
 - };
 - 
 - /**
 -  * Gets the fully qualified msid (stream.id + track.id) associated to the
 -  * SSRC.
 -  *
 -  * @param ssrc
 -  * @returns {*}
 -  */
 - SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
 -     return this._remoteMaps.ssrc2Msid[ssrc];
 - };
 - 
 - /**
 -  * 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 {*}
 -  */
 - SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
 - 
 -     if (desc && desc.sdp) {
 -         var sb = desc.sdp.split('\r\n');
 - 
 -         this._updateRemoteMaps(sb);
 -         this._cacheRemoteVideoSources(sb);
 - 
 -         // NOTE(gp) this needs to be called after updateRemoteMaps because we
 -         // need the simulcast group in the _updateRemoteMaps() method.
 -         this.simulcastUtils._removeSimulcastGroup(sb);
 - 
 -         if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
 -             // We don't need the goog conference flag if we're not doing
 -             // simulcast.
 -             this._ensureGoogConference(sb);
 -         }
 - 
 -         desc = new RTCSessionDescription({
 -             type: desc.type,
 -             sdp: sb.join('\r\n')
 -         });
 - 
 -         this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
 -     }
 - 
 -     return desc;
 - };
 - 
 - module.exports = SimulcastReceiver;
 - },{"./SimulcastLogger":1,"./SimulcastUtils":4}],3:[function(require,module,exports){
 - var SimulcastLogger = require("./SimulcastLogger");
 - var SimulcastUtils = require("./SimulcastUtils");
 - 
 - function SimulcastSender() {
 -     this.simulcastUtils = new SimulcastUtils();
 -     this.logger = new SimulcastLogger('SimulcastSender', 1);
 - }
 - 
 - SimulcastSender.prototype.displayedLocalVideoStream = null;
 - 
 - SimulcastSender.prototype._generateGuid = (function () {
 -     function s4() {
 -         return Math.floor((1 + Math.random()) * 0x10000)
 -             .toString(16)
 -             .substring(1);
 -     }
 - 
 -     return function () {
 -         return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
 -             s4() + '-' + s4() + s4() + s4();
 -     };
 - }());
 - 
 - // Returns a random integer between min (included) and max (excluded)
 - // Using Math.round() gives a non-uniform distribution!
 - SimulcastSender.prototype._generateRandomSSRC = function () {
 -     var min = 0, max = 0xffffffff;
 -     return Math.floor(Math.random() * (max - min)) + min;
 - };
 - 
 - SimulcastSender.prototype.getLocalVideoStream = function () {
 -     return (this.displayedLocalVideoStream != null)
 -         ? this.displayedLocalVideoStream
 -         // in case we have no simulcast at all, i.e. we didn't perform the GUM
 -         : RTC.localVideo.getOriginalStream();
 - };
 - 
 - function NativeSimulcastSender() {
 -     SimulcastSender.call(this); // call the super constructor.
 - }
 - 
 - NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
 - 
 - NativeSimulcastSender.prototype._localExplosionMap = {};
 - NativeSimulcastSender.prototype._isUsingScreenStream = false;
 - NativeSimulcastSender.prototype._localVideoSourceCache = '';
 - 
 - NativeSimulcastSender.prototype.reset = function () {
 -     this._localExplosionMap = {};
 -     this._isUsingScreenStream = desktopsharing.isUsingScreenStream();
 - };
 - 
 - NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
 -     this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
 - };
 - 
 - NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
 -     this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
 - };
 - 
 - NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
 -     var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
 - 
 -     this.logger.info('Appending simulcast group...');
 - 
 -     // Get the primary SSRC information.
 -     videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
 - 
 -     // Start building the SIM SSRC group.
 -     ssrcGroup = ['a=ssrc-group:SIM'];
 - 
 -     // The video source buffer.
 -     sb = [];
 - 
 -     // Create the simulcast sub-streams.
 -     for (i = 0; i < numOfSubs; i++) {
 -         // TODO(gp) prevent SSRC collision.
 -         simSSRC = this._generateRandomSSRC();
 -         ssrcGroup.push(simSSRC);
 - 
 -         sb.splice.apply(sb, [sb.length, 0].concat(
 -             [["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
 -                 ["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
 -         ));
 - 
 -         this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
 - 
 -     }
 - 
 -     // Add the group sim layers.
 -     sb.splice(0, 0, ssrcGroup.join(' '))
 - 
 -     this.simulcastUtils._replaceVideoSources(lines, sb);
 - };
 - 
 - // Does the actual patching.
 - NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
 - 
 -     this.logger.info('Ensuring simulcast group...');
 - 
 -     if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
 -         this._appendSimulcastGroup(lines);
 -         this._cacheLocalVideoSources(lines);
 -     } else {
 -         // verify that the ssrcs participating in the SIM group are present
 -         // in the SDP (needed for presence).
 -         this._restoreLocalVideoSources(lines);
 -     }
 - };
 - 
 - /**
 -  * Produces a single stream with multiple tracks for local video sources.
 -  *
 -  * @param lines
 -  * @private
 -  */
 - NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
 -     var sb, msid, sid, tid, videoSources, self;
 - 
 -     this.logger.info('Exploding local video sources...');
 - 
 -     videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
 - 
 -     self = this;
 -     if (videoSources.groups && videoSources.groups.length !== 0) {
 -         videoSources.groups.forEach(function (group) {
 -             if (group.semantics === 'SIM') {
 -                 group.ssrcs.forEach(function (ssrc) {
 - 
 -                     // Get the msid for this ssrc..
 -                     if (self._localExplosionMap[ssrc]) {
 -                         // .. either from the explosion map..
 -                         msid = self._localExplosionMap[ssrc];
 -                     } else {
 -                         // .. or generate a new one (msid).
 -                         sid = videoSources.sources[ssrc].msid
 -                             .substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
 - 
 -                         tid = self._generateGuid();
 -                         msid = [sid, tid].join(' ');
 -                         self._localExplosionMap[ssrc] = msid;
 -                     }
 - 
 -                     // Assign it to the source object.
 -                     videoSources.sources[ssrc].msid = msid;
 - 
 -                     // TODO(gp) Change the msid of associated sources.
 -                 });
 -             }
 -         });
 -     }
 - 
 -     sb = this.simulcastUtils._compileVideoSources(videoSources);
 - 
 -     this.simulcastUtils._replaceVideoSources(lines, sb);
 - };
 - 
 - /**
 -  * GUM for simulcast.
 -  *
 -  * @param constraints
 -  * @param success
 -  * @param err
 -  */
 - NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
 - 
 -     // There's nothing special to do for native simulcast, so just do a normal GUM.
 -     navigator.webkitGetUserMedia(constraints, function (hqStream) {
 -         success(hqStream);
 -     }, err);
 - };
 - 
 - /**
 -  * Prepares the local description for public usage (i.e. to be signaled
 -  * through Jingle to the focus).
 -  *
 -  * @param desc
 -  * @returns {RTCSessionDescription}
 -  */
 - NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
 -     var sb;
 - 
 -     if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
 -         return desc;
 -     }
 - 
 - 
 -     sb = desc.sdp.split('\r\n');
 - 
 -     this._explodeSimulcastSenderSources(sb);
 - 
 -     desc = new RTCSessionDescription({
 -         type: desc.type,
 -         sdp: sb.join('\r\n')
 -     });
 - 
 -     this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
 - 
 -     return desc;
 - };
 - 
 - /**
 -  * Ensures that the simulcast group is present in the answer, _if_ native
 -  * simulcast is enabled,
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - NativeSimulcastSender.prototype.transformAnswer = function (desc) {
 - 
 -     if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
 -         return desc;
 -     }
 - 
 -     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')
 -     });
 - 
 -     this.logger.fine(['Transformed answer', desc.sdp].join(' '));
 - 
 -     return desc;
 - };
 - 
 - 
 - /**
 -  *
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
 -     return desc;
 - };
 - 
 - NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
 -     // Nothing to do here, native simulcast does that auto-magically.
 - };
 - 
 - NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
 - 
 - function SimpleSimulcastSender() {
 -     SimulcastSender.call(this);
 - }
 - 
 - SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
 - 
 - SimpleSimulcastSender.prototype.localStream = null;
 - SimpleSimulcastSender.prototype._localMaps = {
 -     msids: [],
 -     msid2ssrc: {}
 - };
 - 
 - /**
 -  * Groups local video sources together in the ssrc-group:SIM group.
 -  *
 -  * @param lines
 -  * @private
 -  */
 - SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
 -     var sb, videoSources, ssrcs = [], ssrc;
 - 
 -     this.logger.info('Grouping local video sources...');
 - 
 -     videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
 - 
 -     for (ssrc in videoSources.sources) {
 -         // jitsi-meet destroys/creates streams at various places causing
 -         // the original local stream ids to change. The only thing that
 -         // remains unchanged is the trackid.
 -         this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
 -     }
 - 
 -     var self = this;
 -     // TODO(gp) add only "free" sources.
 -     this._localMaps.msids.forEach(function (msid) {
 -         ssrcs.push(self._localMaps.msid2ssrc[msid]);
 -     });
 - 
 -     if (!videoSources.groups) {
 -         videoSources.groups = [];
 -     }
 - 
 -     videoSources.groups.push({
 -         'semantics': 'SIM',
 -         'ssrcs': ssrcs
 -     });
 - 
 -     sb = this.simulcastUtils._compileVideoSources(videoSources);
 - 
 -     this.simulcastUtils._replaceVideoSources(lines, sb);
 - };
 - 
 - /**
 -  * GUM for simulcast.
 -  *
 -  * @param constraints
 -  * @param success
 -  * @param err
 -  */
 - SimpleSimulcastSender.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
 -             }
 -         }
 -     };
 - 
 -     this.logger.info('HQ constraints: ', constraints);
 -     this.logger.info('LQ constraints: ', lqConstraints);
 - 
 - 
 -     // 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}
 -  */
 - SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
 -     var sb;
 - 
 -     if (!this.simulcastUtils.isValidDescription(desc)) {
 -         return desc;
 -     }
 - 
 -     sb = desc.sdp.split('\r\n');
 - 
 -     this._groupLocalVideoSources(sb);
 - 
 -     desc = new RTCSessionDescription({
 -         type: desc.type,
 -         sdp: sb.join('\r\n')
 -     });
 - 
 -     this.logger.fine('Grouped local video sources');
 -     this.logger.fine(desc.sdp);
 - 
 -     return desc;
 - };
 - 
 - /**
 -  * Ensures that the simulcast group is present in the answer, _if_ native
 -  * simulcast is enabled,
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
 -     return desc;
 - };
 - 
 - 
 - /**
 -  *
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
 - 
 -     var sb = desc.sdp.split('\r\n');
 - 
 -     this.simulcastUtils._removeSimulcastGroup(sb);
 - 
 -     desc = new RTCSessionDescription({
 -         type: desc.type,
 -         sdp: sb.join('\r\n')
 -     });
 - 
 -     this.logger.fine('Transformed local description');
 -     this.logger.fine(desc.sdp);
 - 
 -     return desc;
 - };
 - 
 - SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
 -     var trackid;
 - 
 -     var self = this;
 -     this.logger.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;
 -         }
 -     })) {
 -         this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
 -         $(document).trigger(enabled
 -             ? 'simulcastlayerstarted'
 -             : 'simulcastlayerstopped');
 -     } else {
 -         this.logger.error("I don't have a local stream with SSRC " + ssrc);
 -     }
 - };
 - 
 - SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
 - 
 - function NoSimulcastSender() {
 -     SimulcastSender.call(this);
 - }
 - 
 - NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
 - 
 - /**
 -  * GUM for simulcast.
 -  *
 -  * @param constraints
 -  * @param success
 -  * @param err
 -  */
 - NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
 -     navigator.webkitGetUserMedia(constraints, function (hqStream) {
 -         success(hqStream);
 -     }, err);
 - };
 - 
 - /**
 -  * Prepares the local description for public usage (i.e. to be signaled
 -  * through Jingle to the focus).
 -  *
 -  * @param desc
 -  * @returns {RTCSessionDescription}
 -  */
 - NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
 -     return desc;
 - };
 - 
 - /**
 -  * Ensures that the simulcast group is present in the answer, _if_ native
 -  * simulcast is enabled,
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - NoSimulcastSender.prototype.transformAnswer = function (desc) {
 -     return desc;
 - };
 - 
 - 
 - /**
 -  *
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
 -     return desc;
 - };
 - 
 - NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
 - 
 - };
 - 
 - NoSimulcastSender.prototype.constructor = NoSimulcastSender;
 - 
 - module.exports = {
 -     "native": NativeSimulcastSender,
 -     "no": NoSimulcastSender
 - }
 - 
 - },{"./SimulcastLogger":1,"./SimulcastUtils":4}],4:[function(require,module,exports){
 - var SimulcastLogger = require("./SimulcastLogger");
 - 
 - /**
 -  *
 -  * @constructor
 -  */
 - function SimulcastUtils() {
 -     this.logger = new SimulcastLogger("SimulcastUtils", 1);
 - }
 - 
 - /**
 -  *
 -  * @type {{}}
 -  * @private
 -  */
 - SimulcastUtils.prototype._emptyCompoundIndex = {};
 - 
 - /**
 -  *
 -  * @param lines
 -  * @param videoSources
 -  * @private
 -  */
 - SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
 -     var i, inVideo = false, index = -1, howMany = 0;
 - 
 -     this.logger.info('Replacing video sources...');
 - 
 -     for (i = 0; i < lines.length; i++) {
 -         if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
 -             // Out of video.
 -             break;
 -         }
 - 
 -         if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
 -             // In video.
 -             inVideo = true;
 -         }
 - 
 -         if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
 -             || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
 - 
 -             if (index === -1) {
 -                 index = i;
 -             }
 - 
 -             howMany++;
 -         }
 -     }
 - 
 -     //  efficiency baby ;)
 -     lines.splice.apply(lines,
 -         [index, howMany].concat(videoSources));
 - 
 - };
 - 
 - SimulcastUtils.prototype.isValidDescription = function (desc)
 - {
 -     return desc && desc != null
 -         && desc.type && desc.type != ''
 -         && desc.sdp && desc.sdp != '';
 - };
 - 
 - SimulcastUtils.prototype._getVideoSources = function (lines) {
 -     var i, inVideo = false, sb = [];
 - 
 -     this.logger.info('Getting video sources...');
 - 
 -     for (i = 0; i < lines.length; i++) {
 -         if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
 -             // Out of video.
 -             break;
 -         }
 - 
 -         if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
 -             // In video.
 -             inVideo = true;
 -         }
 - 
 -         if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
 -             // In SSRC.
 -             sb.push(lines[i]);
 -         }
 - 
 -         if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
 -             sb.push(lines[i]);
 -         }
 -     }
 - 
 -     return sb;
 - };
 - 
 - SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
 -     var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
 -         ssrc_attribute, group, semantics, skip = true;
 - 
 -     this.logger.info('Parsing media sources...');
 - 
 -     for (i = 0; i < lines.length; i++) {
 -         if (lines[i].substring(0, 'm='.length) === 'm=') {
 - 
 -             type = lines[i]
 -                 .substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
 -             skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
 - 
 -             if (!skip) {
 -                 cur_media = {
 -                     'type': type,
 -                     'sources': {},
 -                     'groups': []
 -                 };
 - 
 -                 res.push(cur_media);
 -             }
 - 
 -         } else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
 - 
 -             idx = lines[i].indexOf(' ');
 -             ssrc = lines[i].substring('a=ssrc:'.length, idx);
 -             if (cur_media.sources[ssrc] === undefined) {
 -                 cur_ssrc = {'ssrc': ssrc};
 -                 cur_media.sources[ssrc] = cur_ssrc;
 -             }
 - 
 -             ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
 -             cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
 - 
 -             if (cur_media.base === undefined) {
 -                 cur_media.base = cur_ssrc;
 -             }
 - 
 -         } else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
 -             idx = lines[i].indexOf(' ');
 -             semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
 -             ssrcs = lines[i].substr(idx).trim().split(' ');
 -             group = {
 -                 'semantics': semantics,
 -                 'ssrcs': ssrcs
 -             };
 -             cur_media.groups.push(group);
 -         } else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
 -             lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
 -             lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
 -             lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
 - 
 -             cur_media.direction = lines[i].substring('a='.length);
 -         }
 -     }
 - 
 -     return res;
 - };
 - 
 - /**
 -  * The _indexOfArray() method returns the first a CompoundIndex at which a
 -  * given element can be found in the array, or _emptyCompoundIndex if it is
 -  * not present.
 -  *
 -  * Example:
 -  *
 -  * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
 -  *
 -  * returns {row: 2, column: 14}
 -  *
 -  * @param needle
 -  * @param haystack
 -  * @param start
 -  * @returns {}
 -  * @private
 -  */
 - SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
 -     var length = haystack.length, idx, i;
 - 
 -     if (!start) {
 -         start = 0;
 -     }
 - 
 -     for (i = start; i < length; i++) {
 -         idx = haystack[i].indexOf(needle);
 -         if (idx !== -1) {
 -             return {row: i, column: idx};
 -         }
 -     }
 -     return this._emptyCompoundIndex;
 - };
 - 
 - SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
 -     var i;
 - 
 -     for (i = lines.length - 1; i >= 0; i--) {
 -         if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
 -             lines.splice(i, 1);
 -         }
 -     }
 - };
 - 
 - SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
 -     var sb = [], ssrc, addedSSRCs = [];
 - 
 -     this.logger.info('Compiling video sources...');
 - 
 -     // Add the groups
 -     if (videoSources.groups && videoSources.groups.length !== 0) {
 -         videoSources.groups.forEach(function (group) {
 -             if (group.ssrcs && group.ssrcs.length !== 0) {
 -                 sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
 - 
 -                 // if (group.semantics !== 'SIM') {
 -                 group.ssrcs.forEach(function (ssrc) {
 -                     addedSSRCs.push(ssrc);
 -                     sb.splice.apply(sb, [sb.length, 0].concat([
 -                         ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
 -                         ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
 -                 });
 -                 //}
 -             }
 -         });
 -     }
 - 
 -     // Then add any free sources.
 -     if (videoSources.sources) {
 -         for (ssrc in videoSources.sources) {
 -             if (addedSSRCs.indexOf(ssrc) === -1) {
 -                 sb.splice.apply(sb, [sb.length, 0].concat([
 -                     ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
 -                     ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
 -             }
 -         }
 -     }
 - 
 -     return sb;
 - };
 - 
 - module.exports = SimulcastUtils;
 - },{"./SimulcastLogger":1}],5:[function(require,module,exports){
 - /*jslint plusplus: true */
 - /*jslint nomen: true*/
 - 
 - var SimulcastSender = require("./SimulcastSender");
 - var NoSimulcastSender = SimulcastSender["no"];
 - var NativeSimulcastSender = SimulcastSender["native"];
 - var SimulcastReceiver = require("./SimulcastReceiver");
 - var SimulcastUtils = require("./SimulcastUtils");
 - 
 - 
 - /**
 -  *
 -  * @constructor
 -  */
 - function SimulcastManager() {
 - 
 -     // Create the simulcast utilities.
 -     this.simulcastUtils = new SimulcastUtils();
 - 
 -     // Create remote simulcast.
 -     this.simulcastReceiver = new SimulcastReceiver();
 - 
 -     // Initialize local simulcast.
 - 
 -     // TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
 -     // account constraints.
 -     if (!config.enableSimulcast) {
 -         this.simulcastSender = new NoSimulcastSender();
 -     } else {
 - 
 -         var isChromium = window.chrome,
 -             vendorName = window.navigator.vendor;
 -         if(isChromium !== null && isChromium !== undefined
 -             /* skip opera */
 -             && vendorName === "Google Inc."
 -             /* skip Chromium as suggested by fippo */
 -             && !window.navigator.appVersion.match(/Chromium\//) ) {
 -             var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
 -             if (ver > 37) {
 -                 this.simulcastSender = new NativeSimulcastSender();
 -             } else {
 -                 this.simulcastSender = new NoSimulcastSender();
 -             }
 -         } else {
 -             this.simulcastSender = new NoSimulcastSender();
 -         }
 - 
 -     }
 - }
 - 
 - /**
 -  * Restores the simulcast groups of the remote description. In
 -  * transformRemoteDescription we remove those in order for the set remote
 -  * description to succeed. The focus needs the signal the groups to new
 -  * participants.
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
 -     return this.simulcastReceiver.reverseTransformRemoteDescription(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 {*}
 -  */
 - SimulcastManager.prototype.transformRemoteDescription = function (desc) {
 -     return this.simulcastReceiver.transformRemoteDescription(desc);
 - };
 - 
 - /**
 -  * Gets the fully qualified msid (stream.id + track.id) associated to the
 -  * SSRC.
 -  *
 -  * @param ssrc
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
 -     return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
 - };
 - 
 - /**
 -  * Returns a stream with single video track, the one currently being
 -  * received by this endpoint.
 -  *
 -  * @param stream the remote simulcast stream.
 -  * @returns {webkitMediaStream}
 -  */
 - SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
 -     return this.simulcastReceiver.getReceivingVideoStream(stream);
 - };
 - 
 - /**
 -  *
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.transformLocalDescription = function (desc) {
 -     return this.simulcastSender.transformLocalDescription(desc);
 - };
 - 
 - /**
 -  *
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.getLocalVideoStream = function() {
 -     return this.simulcastSender.getLocalVideoStream();
 - };
 - 
 - /**
 -  * GUM for simulcast.
 -  *
 -  * @param constraints
 -  * @param success
 -  * @param err
 -  */
 - SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {
 - 
 -     this.simulcastSender.getUserMedia(constraints, success, err);
 - };
 - 
 - /**
 -  * Prepares the local description for public usage (i.e. to be signaled
 -  * through Jingle to the focus).
 -  *
 -  * @param desc
 -  * @returns {RTCSessionDescription}
 -  */
 - SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
 -     return this.simulcastSender.reverseTransformLocalDescription(desc);
 - };
 - 
 - /**
 -  * Ensures that the simulcast group is present in the answer, _if_ native
 -  * simulcast is enabled,
 -  *
 -  * @param desc
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.transformAnswer = function (desc) {
 -     return this.simulcastSender.transformAnswer(desc);
 - };
 - 
 - SimulcastManager.prototype.getReceivingSSRC = function (jid) {
 -     return this.simulcastReceiver.getReceivingSSRC(jid);
 - };
 - 
 - SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
 -     return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
 - };
 - 
 - /**
 -  *
 -  * @param lines
 -  * @param mediatypes
 -  * @returns {*}
 -  */
 - SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
 -     var sb = lines.sdp.split('\r\n');
 -     return this.simulcastUtils.parseMedia(sb, mediatypes);
 - };
 - 
 - SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
 -     this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
 - };
 - 
 - SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
 -     this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
 - };
 - 
 - SimulcastManager.prototype.resetSender = function() {
 -     if (typeof this.simulcastSender.reset === 'function'){
 -         this.simulcastSender.reset();
 -     }
 - };
 - 
 - $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
 -     endpointSimulcastLayers.forEach(function (esl) {
 -         var ssrc = esl.simulcastLayer.primarySSRC;
 -         simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
 -     });
 - });
 - 
 - $(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
 -     var ssrc = simulcastLayer.primarySSRC;
 -     simulcast._setLocalVideoStreamEnabled(ssrc, true);
 - });
 - 
 - $(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
 -     var ssrc = simulcastLayer.primarySSRC;
 -     simulcast._setLocalVideoStreamEnabled(ssrc, false);
 - });
 - 
 - 
 - var simulcast = new SimulcastManager();
 - 
 - module.exports = simulcast;
 - },{"./SimulcastReceiver":2,"./SimulcastSender":3,"./SimulcastUtils":4}]},{},[5])(5)
 - });
 
 
  |