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.

simulcast.bundle.js 37KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262
  1. !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){
  2. /**
  3. *
  4. * @constructor
  5. */
  6. function SimulcastLogger(name, lvl) {
  7. this.name = name;
  8. this.lvl = lvl;
  9. }
  10. SimulcastLogger.prototype.log = function (text) {
  11. if (this.lvl) {
  12. console.log(text);
  13. }
  14. };
  15. SimulcastLogger.prototype.info = function (text) {
  16. if (this.lvl > 1) {
  17. console.info(text);
  18. }
  19. };
  20. SimulcastLogger.prototype.fine = function (text) {
  21. if (this.lvl > 2) {
  22. console.log(text);
  23. }
  24. };
  25. SimulcastLogger.prototype.error = function (text) {
  26. console.error(text);
  27. };
  28. module.exports = SimulcastLogger;
  29. },{}],2:[function(require,module,exports){
  30. var SimulcastLogger = require("./SimulcastLogger");
  31. var SimulcastUtils = require("./SimulcastUtils");
  32. function SimulcastReceiver() {
  33. this.simulcastUtils = new SimulcastUtils();
  34. this.logger = new SimulcastLogger('SimulcastReceiver', 1);
  35. }
  36. SimulcastReceiver.prototype._remoteVideoSourceCache = '';
  37. SimulcastReceiver.prototype._remoteMaps = {
  38. msid2Quality: {},
  39. ssrc2Msid: {},
  40. msid2ssrc: {},
  41. receivingVideoStreams: {}
  42. };
  43. SimulcastReceiver.prototype._cacheRemoteVideoSources = function (lines) {
  44. this._remoteVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
  45. };
  46. SimulcastReceiver.prototype._restoreRemoteVideoSources = function (lines) {
  47. this.simulcastUtils._replaceVideoSources(lines, this._remoteVideoSourceCache);
  48. };
  49. SimulcastReceiver.prototype._ensureGoogConference = function (lines) {
  50. var sb;
  51. this.logger.info('Ensuring x-google-conference flag...')
  52. if (this.simulcastUtils._indexOfArray('a=x-google-flag:conference', lines) === this.simulcastUtils._emptyCompoundIndex) {
  53. // TODO(gp) do that for the audio as well as suggested by fippo.
  54. // Add the google conference flag
  55. sb = this.simulcastUtils._getVideoSources(lines);
  56. sb = ['a=x-google-flag:conference'].concat(sb);
  57. this.simulcastUtils._replaceVideoSources(lines, sb);
  58. }
  59. };
  60. SimulcastReceiver.prototype._restoreSimulcastGroups = function (sb) {
  61. this._restoreRemoteVideoSources(sb);
  62. };
  63. /**
  64. * Restores the simulcast groups of the remote description. In
  65. * transformRemoteDescription we remove those in order for the set remote
  66. * description to succeed. The focus needs the signal the groups to new
  67. * participants.
  68. *
  69. * @param desc
  70. * @returns {*}
  71. */
  72. SimulcastReceiver.prototype.reverseTransformRemoteDescription = function (desc) {
  73. var sb;
  74. if (!this.simulcastUtils.isValidDescription(desc)) {
  75. return desc;
  76. }
  77. if (config.enableSimulcast) {
  78. sb = desc.sdp.split('\r\n');
  79. this._restoreSimulcastGroups(sb);
  80. desc = new RTCSessionDescription({
  81. type: desc.type,
  82. sdp: sb.join('\r\n')
  83. });
  84. }
  85. return desc;
  86. };
  87. SimulcastUtils.prototype._ensureOrder = function (lines) {
  88. var videoSources, sb;
  89. videoSources = this.parseMedia(lines, ['video'])[0];
  90. sb = this._compileVideoSources(videoSources);
  91. this._replaceVideoSources(lines, sb);
  92. };
  93. SimulcastReceiver.prototype._updateRemoteMaps = function (lines) {
  94. var remoteVideoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0],
  95. videoSource, quality;
  96. // (re) initialize the remote maps.
  97. this._remoteMaps.msid2Quality = {};
  98. this._remoteMaps.ssrc2Msid = {};
  99. this._remoteMaps.msid2ssrc = {};
  100. var self = this;
  101. if (remoteVideoSources.groups && remoteVideoSources.groups.length !== 0) {
  102. remoteVideoSources.groups.forEach(function (group) {
  103. if (group.semantics === 'SIM' && group.ssrcs && group.ssrcs.length !== 0) {
  104. quality = 0;
  105. group.ssrcs.forEach(function (ssrc) {
  106. videoSource = remoteVideoSources.sources[ssrc];
  107. self._remoteMaps.msid2Quality[videoSource.msid] = quality++;
  108. self._remoteMaps.ssrc2Msid[videoSource.ssrc] = videoSource.msid;
  109. self._remoteMaps.msid2ssrc[videoSource.msid] = videoSource.ssrc;
  110. });
  111. }
  112. });
  113. }
  114. };
  115. SimulcastReceiver.prototype._setReceivingVideoStream = function (resource, ssrc) {
  116. this._remoteMaps.receivingVideoStreams[resource] = ssrc;
  117. };
  118. /**
  119. * Returns a stream with single video track, the one currently being
  120. * received by this endpoint.
  121. *
  122. * @param stream the remote simulcast stream.
  123. * @returns {webkitMediaStream}
  124. */
  125. SimulcastReceiver.prototype.getReceivingVideoStream = function (stream) {
  126. var tracks, i, electedTrack, msid, quality = 0, receivingTrackId;
  127. var self = this;
  128. if (config.enableSimulcast) {
  129. stream.getVideoTracks().some(function (track) {
  130. return Object.keys(self._remoteMaps.receivingVideoStreams).some(function (resource) {
  131. var ssrc = self._remoteMaps.receivingVideoStreams[resource];
  132. var msid = self._remoteMaps.ssrc2Msid[ssrc];
  133. if (msid == [stream.id, track.id].join(' ')) {
  134. electedTrack = track;
  135. return true;
  136. }
  137. });
  138. });
  139. if (!electedTrack) {
  140. // we don't have an elected track, choose by initial quality.
  141. tracks = stream.getVideoTracks();
  142. for (i = 0; i < tracks.length; i++) {
  143. msid = [stream.id, tracks[i].id].join(' ');
  144. if (this._remoteMaps.msid2Quality[msid] === quality) {
  145. electedTrack = tracks[i];
  146. break;
  147. }
  148. }
  149. // TODO(gp) if the initialQuality could not be satisfied, lower
  150. // the requirement and try again.
  151. }
  152. }
  153. return (electedTrack)
  154. ? new webkitMediaStream([electedTrack])
  155. : stream;
  156. };
  157. SimulcastReceiver.prototype.getReceivingSSRC = function (jid) {
  158. var resource = Strophe.getResourceFromJid(jid);
  159. var ssrc = this._remoteMaps.receivingVideoStreams[resource];
  160. // If we haven't receiving a "changed" event yet, then we must be receiving
  161. // low quality (that the sender always streams).
  162. if(!ssrc)
  163. {
  164. var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
  165. var remoteStream = remoteStreamObject.getOriginalStream();
  166. var tracks = remoteStream.getVideoTracks();
  167. if (tracks) {
  168. for (var k = 0; k < tracks.length; k++) {
  169. var track = tracks[k];
  170. var msid = [remoteStream.id, track.id].join(' ');
  171. var _ssrc = this._remoteMaps.msid2ssrc[msid];
  172. var quality = this._remoteMaps.msid2Quality[msid];
  173. if (quality == 0) {
  174. ssrc = _ssrc;
  175. }
  176. }
  177. }
  178. }
  179. return ssrc;
  180. };
  181. SimulcastReceiver.prototype.getReceivingVideoStreamBySSRC = function (ssrc)
  182. {
  183. var sid, electedStream;
  184. var i, j, k;
  185. var jid = ssrc2jid[ssrc];
  186. if(jid && RTC.remoteStreams[jid])
  187. {
  188. var remoteStreamObject = RTC.remoteStreams[jid][MediaStreamType.VIDEO_TYPE];
  189. var remoteStream = remoteStreamObject.getOriginalStream();
  190. var tracks = remoteStream.getVideoTracks();
  191. if (tracks) {
  192. for (k = 0; k < tracks.length; k++) {
  193. var track = tracks[k];
  194. var msid = [remoteStream.id, track.id].join(' ');
  195. var tmp = this._remoteMaps.msid2ssrc[msid];
  196. if (tmp == ssrc) {
  197. electedStream = new webkitMediaStream([track]);
  198. sid = remoteStreamObject.sid;
  199. // stream found, stop.
  200. break;
  201. }
  202. }
  203. }
  204. }
  205. else
  206. {
  207. console.debug(RTC.remoteStreams, jid, ssrc);
  208. }
  209. return {
  210. sid: sid,
  211. stream: electedStream
  212. };
  213. };
  214. /**
  215. * Gets the fully qualified msid (stream.id + track.id) associated to the
  216. * SSRC.
  217. *
  218. * @param ssrc
  219. * @returns {*}
  220. */
  221. SimulcastReceiver.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
  222. return this._remoteMaps.ssrc2Msid[ssrc];
  223. };
  224. /**
  225. * Removes the ssrc-group:SIM from the remote description bacause Chrome
  226. * either gets confused and thinks this is an FID group or, if an FID group
  227. * is already present, it fails to set the remote description.
  228. *
  229. * @param desc
  230. * @returns {*}
  231. */
  232. SimulcastReceiver.prototype.transformRemoteDescription = function (desc) {
  233. if (desc && desc.sdp) {
  234. var sb = desc.sdp.split('\r\n');
  235. this._updateRemoteMaps(sb);
  236. this._cacheRemoteVideoSources(sb);
  237. // NOTE(gp) this needs to be called after updateRemoteMaps because we
  238. // need the simulcast group in the _updateRemoteMaps() method.
  239. this.simulcastUtils._removeSimulcastGroup(sb);
  240. if (desc.sdp.indexOf('a=ssrc-group:SIM') !== -1) {
  241. // We don't need the goog conference flag if we're not doing
  242. // simulcast.
  243. this._ensureGoogConference(sb);
  244. }
  245. desc = new RTCSessionDescription({
  246. type: desc.type,
  247. sdp: sb.join('\r\n')
  248. });
  249. this.logger.fine(['Transformed remote description', desc.sdp].join(' '));
  250. }
  251. return desc;
  252. };
  253. module.exports = SimulcastReceiver;
  254. },{"./SimulcastLogger":1,"./SimulcastUtils":4}],3:[function(require,module,exports){
  255. var SimulcastLogger = require("./SimulcastLogger");
  256. var SimulcastUtils = require("./SimulcastUtils");
  257. function SimulcastSender() {
  258. this.simulcastUtils = new SimulcastUtils();
  259. this.logger = new SimulcastLogger('SimulcastSender', 1);
  260. }
  261. SimulcastSender.prototype.displayedLocalVideoStream = null;
  262. SimulcastSender.prototype._generateGuid = (function () {
  263. function s4() {
  264. return Math.floor((1 + Math.random()) * 0x10000)
  265. .toString(16)
  266. .substring(1);
  267. }
  268. return function () {
  269. return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
  270. s4() + '-' + s4() + s4() + s4();
  271. };
  272. }());
  273. // Returns a random integer between min (included) and max (excluded)
  274. // Using Math.round() gives a non-uniform distribution!
  275. SimulcastSender.prototype._generateRandomSSRC = function () {
  276. var min = 0, max = 0xffffffff;
  277. return Math.floor(Math.random() * (max - min)) + min;
  278. };
  279. SimulcastSender.prototype.getLocalVideoStream = function () {
  280. return (this.displayedLocalVideoStream != null)
  281. ? this.displayedLocalVideoStream
  282. // in case we have no simulcast at all, i.e. we didn't perform the GUM
  283. : RTC.localVideo.getOriginalStream();
  284. };
  285. function NativeSimulcastSender() {
  286. SimulcastSender.call(this); // call the super constructor.
  287. }
  288. NativeSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
  289. NativeSimulcastSender.prototype._localExplosionMap = {};
  290. NativeSimulcastSender.prototype._isUsingScreenStream = false;
  291. NativeSimulcastSender.prototype._localVideoSourceCache = '';
  292. NativeSimulcastSender.prototype.reset = function () {
  293. this._localExplosionMap = {};
  294. this._isUsingScreenStream = desktopsharing.isUsingScreenStream();
  295. };
  296. NativeSimulcastSender.prototype._cacheLocalVideoSources = function (lines) {
  297. this._localVideoSourceCache = this.simulcastUtils._getVideoSources(lines);
  298. };
  299. NativeSimulcastSender.prototype._restoreLocalVideoSources = function (lines) {
  300. this.simulcastUtils._replaceVideoSources(lines, this._localVideoSourceCache);
  301. };
  302. NativeSimulcastSender.prototype._appendSimulcastGroup = function (lines) {
  303. var videoSources, ssrcGroup, simSSRC, numOfSubs = 2, i, sb, msid;
  304. this.logger.info('Appending simulcast group...');
  305. // Get the primary SSRC information.
  306. videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
  307. // Start building the SIM SSRC group.
  308. ssrcGroup = ['a=ssrc-group:SIM'];
  309. // The video source buffer.
  310. sb = [];
  311. // Create the simulcast sub-streams.
  312. for (i = 0; i < numOfSubs; i++) {
  313. // TODO(gp) prevent SSRC collision.
  314. simSSRC = this._generateRandomSSRC();
  315. ssrcGroup.push(simSSRC);
  316. sb.splice.apply(sb, [sb.length, 0].concat(
  317. [["a=ssrc:", simSSRC, " cname:", videoSources.base.cname].join(''),
  318. ["a=ssrc:", simSSRC, " msid:", videoSources.base.msid].join('')]
  319. ));
  320. this.logger.info(['Generated substream ', i, ' with SSRC ', simSSRC, '.'].join(''));
  321. }
  322. // Add the group sim layers.
  323. sb.splice(0, 0, ssrcGroup.join(' '))
  324. this.simulcastUtils._replaceVideoSources(lines, sb);
  325. };
  326. // Does the actual patching.
  327. NativeSimulcastSender.prototype._ensureSimulcastGroup = function (lines) {
  328. this.logger.info('Ensuring simulcast group...');
  329. if (this.simulcastUtils._indexOfArray('a=ssrc-group:SIM', lines) === this.simulcastUtils._emptyCompoundIndex) {
  330. this._appendSimulcastGroup(lines);
  331. this._cacheLocalVideoSources(lines);
  332. } else {
  333. // verify that the ssrcs participating in the SIM group are present
  334. // in the SDP (needed for presence).
  335. this._restoreLocalVideoSources(lines);
  336. }
  337. };
  338. /**
  339. * Produces a single stream with multiple tracks for local video sources.
  340. *
  341. * @param lines
  342. * @private
  343. */
  344. NativeSimulcastSender.prototype._explodeSimulcastSenderSources = function (lines) {
  345. var sb, msid, sid, tid, videoSources, self;
  346. this.logger.info('Exploding local video sources...');
  347. videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
  348. self = this;
  349. if (videoSources.groups && videoSources.groups.length !== 0) {
  350. videoSources.groups.forEach(function (group) {
  351. if (group.semantics === 'SIM') {
  352. group.ssrcs.forEach(function (ssrc) {
  353. // Get the msid for this ssrc..
  354. if (self._localExplosionMap[ssrc]) {
  355. // .. either from the explosion map..
  356. msid = self._localExplosionMap[ssrc];
  357. } else {
  358. // .. or generate a new one (msid).
  359. sid = videoSources.sources[ssrc].msid
  360. .substring(0, videoSources.sources[ssrc].msid.indexOf(' '));
  361. tid = self._generateGuid();
  362. msid = [sid, tid].join(' ');
  363. self._localExplosionMap[ssrc] = msid;
  364. }
  365. // Assign it to the source object.
  366. videoSources.sources[ssrc].msid = msid;
  367. // TODO(gp) Change the msid of associated sources.
  368. });
  369. }
  370. });
  371. }
  372. sb = this.simulcastUtils._compileVideoSources(videoSources);
  373. this.simulcastUtils._replaceVideoSources(lines, sb);
  374. };
  375. /**
  376. * GUM for simulcast.
  377. *
  378. * @param constraints
  379. * @param success
  380. * @param err
  381. */
  382. NativeSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
  383. // There's nothing special to do for native simulcast, so just do a normal GUM.
  384. navigator.webkitGetUserMedia(constraints, function (hqStream) {
  385. success(hqStream);
  386. }, err);
  387. };
  388. /**
  389. * Prepares the local description for public usage (i.e. to be signaled
  390. * through Jingle to the focus).
  391. *
  392. * @param desc
  393. * @returns {RTCSessionDescription}
  394. */
  395. NativeSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
  396. var sb;
  397. if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
  398. return desc;
  399. }
  400. sb = desc.sdp.split('\r\n');
  401. this._explodeSimulcastSenderSources(sb);
  402. desc = new RTCSessionDescription({
  403. type: desc.type,
  404. sdp: sb.join('\r\n')
  405. });
  406. this.logger.fine(['Exploded local video sources', desc.sdp].join(' '));
  407. return desc;
  408. };
  409. /**
  410. * Ensures that the simulcast group is present in the answer, _if_ native
  411. * simulcast is enabled,
  412. *
  413. * @param desc
  414. * @returns {*}
  415. */
  416. NativeSimulcastSender.prototype.transformAnswer = function (desc) {
  417. if (!this.simulcastUtils.isValidDescription(desc) || this._isUsingScreenStream) {
  418. return desc;
  419. }
  420. var sb = desc.sdp.split('\r\n');
  421. // Even if we have enabled native simulcasting previously
  422. // (with a call to SLD with an appropriate SDP, for example),
  423. // createAnswer seems to consistently generate incomplete SDP
  424. // with missing SSRCS.
  425. //
  426. // So, subsequent calls to SLD will have missing SSRCS and presence
  427. // won't have the complete list of SRCs.
  428. this._ensureSimulcastGroup(sb);
  429. desc = new RTCSessionDescription({
  430. type: desc.type,
  431. sdp: sb.join('\r\n')
  432. });
  433. this.logger.fine(['Transformed answer', desc.sdp].join(' '));
  434. return desc;
  435. };
  436. /**
  437. *
  438. *
  439. * @param desc
  440. * @returns {*}
  441. */
  442. NativeSimulcastSender.prototype.transformLocalDescription = function (desc) {
  443. return desc;
  444. };
  445. NativeSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
  446. // Nothing to do here, native simulcast does that auto-magically.
  447. };
  448. NativeSimulcastSender.prototype.constructor = NativeSimulcastSender;
  449. function SimpleSimulcastSender() {
  450. SimulcastSender.call(this);
  451. }
  452. SimpleSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
  453. SimpleSimulcastSender.prototype.localStream = null;
  454. SimpleSimulcastSender.prototype._localMaps = {
  455. msids: [],
  456. msid2ssrc: {}
  457. };
  458. /**
  459. * Groups local video sources together in the ssrc-group:SIM group.
  460. *
  461. * @param lines
  462. * @private
  463. */
  464. SimpleSimulcastSender.prototype._groupLocalVideoSources = function (lines) {
  465. var sb, videoSources, ssrcs = [], ssrc;
  466. this.logger.info('Grouping local video sources...');
  467. videoSources = this.simulcastUtils.parseMedia(lines, ['video'])[0];
  468. for (ssrc in videoSources.sources) {
  469. // jitsi-meet destroys/creates streams at various places causing
  470. // the original local stream ids to change. The only thing that
  471. // remains unchanged is the trackid.
  472. this._localMaps.msid2ssrc[videoSources.sources[ssrc].msid.split(' ')[1]] = ssrc;
  473. }
  474. var self = this;
  475. // TODO(gp) add only "free" sources.
  476. this._localMaps.msids.forEach(function (msid) {
  477. ssrcs.push(self._localMaps.msid2ssrc[msid]);
  478. });
  479. if (!videoSources.groups) {
  480. videoSources.groups = [];
  481. }
  482. videoSources.groups.push({
  483. 'semantics': 'SIM',
  484. 'ssrcs': ssrcs
  485. });
  486. sb = this.simulcastUtils._compileVideoSources(videoSources);
  487. this.simulcastUtils._replaceVideoSources(lines, sb);
  488. };
  489. /**
  490. * GUM for simulcast.
  491. *
  492. * @param constraints
  493. * @param success
  494. * @param err
  495. */
  496. SimpleSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
  497. // TODO(gp) what if we request a resolution not supported by the hardware?
  498. // TODO(gp) make the lq stream configurable; although this wouldn't work with native simulcast
  499. var lqConstraints = {
  500. audio: false,
  501. video: {
  502. mandatory: {
  503. maxWidth: 320,
  504. maxHeight: 180,
  505. maxFrameRate: 15
  506. }
  507. }
  508. };
  509. this.logger.info('HQ constraints: ', constraints);
  510. this.logger.info('LQ constraints: ', lqConstraints);
  511. // NOTE(gp) if we request the lq stream first webkitGetUserMedia
  512. // fails randomly. Tested with Chrome 37. As fippo suggested, the
  513. // reason appears to be that Chrome only acquires the cam once and
  514. // then downscales the picture (https://code.google.com/p/chromium/issues/detail?id=346616#c11)
  515. var self = this;
  516. navigator.webkitGetUserMedia(constraints, function (hqStream) {
  517. self.localStream = hqStream;
  518. // reset local maps.
  519. self._localMaps.msids = [];
  520. self._localMaps.msid2ssrc = {};
  521. // add hq trackid to local map
  522. self._localMaps.msids.push(hqStream.getVideoTracks()[0].id);
  523. navigator.webkitGetUserMedia(lqConstraints, function (lqStream) {
  524. self.displayedLocalVideoStream = lqStream;
  525. // NOTE(gp) The specification says Array.forEach() will visit
  526. // the array elements in numeric order, and that it doesn't
  527. // visit elements that don't exist.
  528. // add lq trackid to local map
  529. self._localMaps.msids.splice(0, 0, lqStream.getVideoTracks()[0].id);
  530. self.localStream.addTrack(lqStream.getVideoTracks()[0]);
  531. success(self.localStream);
  532. }, err);
  533. }, err);
  534. };
  535. /**
  536. * Prepares the local description for public usage (i.e. to be signaled
  537. * through Jingle to the focus).
  538. *
  539. * @param desc
  540. * @returns {RTCSessionDescription}
  541. */
  542. SimpleSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
  543. var sb;
  544. if (!this.simulcastUtils.isValidDescription(desc)) {
  545. return desc;
  546. }
  547. sb = desc.sdp.split('\r\n');
  548. this._groupLocalVideoSources(sb);
  549. desc = new RTCSessionDescription({
  550. type: desc.type,
  551. sdp: sb.join('\r\n')
  552. });
  553. this.logger.fine('Grouped local video sources');
  554. this.logger.fine(desc.sdp);
  555. return desc;
  556. };
  557. /**
  558. * Ensures that the simulcast group is present in the answer, _if_ native
  559. * simulcast is enabled,
  560. *
  561. * @param desc
  562. * @returns {*}
  563. */
  564. SimpleSimulcastSender.prototype.transformAnswer = function (desc) {
  565. return desc;
  566. };
  567. /**
  568. *
  569. *
  570. * @param desc
  571. * @returns {*}
  572. */
  573. SimpleSimulcastSender.prototype.transformLocalDescription = function (desc) {
  574. var sb = desc.sdp.split('\r\n');
  575. this.simulcastUtils._removeSimulcastGroup(sb);
  576. desc = new RTCSessionDescription({
  577. type: desc.type,
  578. sdp: sb.join('\r\n')
  579. });
  580. this.logger.fine('Transformed local description');
  581. this.logger.fine(desc.sdp);
  582. return desc;
  583. };
  584. SimpleSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
  585. var trackid;
  586. var self = this;
  587. this.logger.log(['Requested to', enabled ? 'enable' : 'disable', ssrc].join(' '));
  588. if (Object.keys(this._localMaps.msid2ssrc).some(function (tid) {
  589. // Search for the track id that corresponds to the ssrc
  590. if (self._localMaps.msid2ssrc[tid] == ssrc) {
  591. trackid = tid;
  592. return true;
  593. }
  594. }) && self.localStream.getVideoTracks().some(function (track) {
  595. // Start/stop the track that corresponds to the track id
  596. if (track.id === trackid) {
  597. track.enabled = enabled;
  598. return true;
  599. }
  600. })) {
  601. this.logger.log([trackid, enabled ? 'enabled' : 'disabled'].join(' '));
  602. $(document).trigger(enabled
  603. ? 'simulcastlayerstarted'
  604. : 'simulcastlayerstopped');
  605. } else {
  606. this.logger.error("I don't have a local stream with SSRC " + ssrc);
  607. }
  608. };
  609. SimpleSimulcastSender.prototype.constructor = SimpleSimulcastSender;
  610. function NoSimulcastSender() {
  611. SimulcastSender.call(this);
  612. }
  613. NoSimulcastSender.prototype = Object.create(SimulcastSender.prototype);
  614. /**
  615. * GUM for simulcast.
  616. *
  617. * @param constraints
  618. * @param success
  619. * @param err
  620. */
  621. NoSimulcastSender.prototype.getUserMedia = function (constraints, success, err) {
  622. navigator.webkitGetUserMedia(constraints, function (hqStream) {
  623. success(hqStream);
  624. }, err);
  625. };
  626. /**
  627. * Prepares the local description for public usage (i.e. to be signaled
  628. * through Jingle to the focus).
  629. *
  630. * @param desc
  631. * @returns {RTCSessionDescription}
  632. */
  633. NoSimulcastSender.prototype.reverseTransformLocalDescription = function (desc) {
  634. return desc;
  635. };
  636. /**
  637. * Ensures that the simulcast group is present in the answer, _if_ native
  638. * simulcast is enabled,
  639. *
  640. * @param desc
  641. * @returns {*}
  642. */
  643. NoSimulcastSender.prototype.transformAnswer = function (desc) {
  644. return desc;
  645. };
  646. /**
  647. *
  648. *
  649. * @param desc
  650. * @returns {*}
  651. */
  652. NoSimulcastSender.prototype.transformLocalDescription = function (desc) {
  653. return desc;
  654. };
  655. NoSimulcastSender.prototype._setLocalVideoStreamEnabled = function (ssrc, enabled) {
  656. };
  657. NoSimulcastSender.prototype.constructor = NoSimulcastSender;
  658. module.exports = {
  659. "native": NativeSimulcastSender,
  660. "no": NoSimulcastSender
  661. }
  662. },{"./SimulcastLogger":1,"./SimulcastUtils":4}],4:[function(require,module,exports){
  663. var SimulcastLogger = require("./SimulcastLogger");
  664. /**
  665. *
  666. * @constructor
  667. */
  668. function SimulcastUtils() {
  669. this.logger = new SimulcastLogger("SimulcastUtils", 1);
  670. }
  671. /**
  672. *
  673. * @type {{}}
  674. * @private
  675. */
  676. SimulcastUtils.prototype._emptyCompoundIndex = {};
  677. /**
  678. *
  679. * @param lines
  680. * @param videoSources
  681. * @private
  682. */
  683. SimulcastUtils.prototype._replaceVideoSources = function (lines, videoSources) {
  684. var i, inVideo = false, index = -1, howMany = 0;
  685. this.logger.info('Replacing video sources...');
  686. for (i = 0; i < lines.length; i++) {
  687. if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
  688. // Out of video.
  689. break;
  690. }
  691. if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
  692. // In video.
  693. inVideo = true;
  694. }
  695. if (inVideo && (lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:'
  696. || lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:')) {
  697. if (index === -1) {
  698. index = i;
  699. }
  700. howMany++;
  701. }
  702. }
  703. // efficiency baby ;)
  704. lines.splice.apply(lines,
  705. [index, howMany].concat(videoSources));
  706. };
  707. SimulcastUtils.prototype.isValidDescription = function (desc)
  708. {
  709. return desc && desc != null
  710. && desc.type && desc.type != ''
  711. && desc.sdp && desc.sdp != '';
  712. };
  713. SimulcastUtils.prototype._getVideoSources = function (lines) {
  714. var i, inVideo = false, sb = [];
  715. this.logger.info('Getting video sources...');
  716. for (i = 0; i < lines.length; i++) {
  717. if (inVideo && lines[i].substring(0, 'm='.length) === 'm=') {
  718. // Out of video.
  719. break;
  720. }
  721. if (!inVideo && lines[i].substring(0, 'm=video '.length) === 'm=video ') {
  722. // In video.
  723. inVideo = true;
  724. }
  725. if (inVideo && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
  726. // In SSRC.
  727. sb.push(lines[i]);
  728. }
  729. if (inVideo && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
  730. sb.push(lines[i]);
  731. }
  732. }
  733. return sb;
  734. };
  735. SimulcastUtils.prototype.parseMedia = function (lines, mediatypes) {
  736. var i, res = [], type, cur_media, idx, ssrcs, cur_ssrc, ssrc,
  737. ssrc_attribute, group, semantics, skip = true;
  738. this.logger.info('Parsing media sources...');
  739. for (i = 0; i < lines.length; i++) {
  740. if (lines[i].substring(0, 'm='.length) === 'm=') {
  741. type = lines[i]
  742. .substr('m='.length, lines[i].indexOf(' ') - 'm='.length);
  743. skip = mediatypes !== undefined && mediatypes.indexOf(type) === -1;
  744. if (!skip) {
  745. cur_media = {
  746. 'type': type,
  747. 'sources': {},
  748. 'groups': []
  749. };
  750. res.push(cur_media);
  751. }
  752. } else if (!skip && lines[i].substring(0, 'a=ssrc:'.length) === 'a=ssrc:') {
  753. idx = lines[i].indexOf(' ');
  754. ssrc = lines[i].substring('a=ssrc:'.length, idx);
  755. if (cur_media.sources[ssrc] === undefined) {
  756. cur_ssrc = {'ssrc': ssrc};
  757. cur_media.sources[ssrc] = cur_ssrc;
  758. }
  759. ssrc_attribute = lines[i].substr(idx + 1).split(':', 2)[0];
  760. cur_ssrc[ssrc_attribute] = lines[i].substr(idx + 1).split(':', 2)[1];
  761. if (cur_media.base === undefined) {
  762. cur_media.base = cur_ssrc;
  763. }
  764. } else if (!skip && lines[i].substring(0, 'a=ssrc-group:'.length) === 'a=ssrc-group:') {
  765. idx = lines[i].indexOf(' ');
  766. semantics = lines[i].substr(0, idx).substr('a=ssrc-group:'.length);
  767. ssrcs = lines[i].substr(idx).trim().split(' ');
  768. group = {
  769. 'semantics': semantics,
  770. 'ssrcs': ssrcs
  771. };
  772. cur_media.groups.push(group);
  773. } else if (!skip && (lines[i].substring(0, 'a=sendrecv'.length) === 'a=sendrecv' ||
  774. lines[i].substring(0, 'a=recvonly'.length) === 'a=recvonly' ||
  775. lines[i].substring(0, 'a=sendonly'.length) === 'a=sendonly' ||
  776. lines[i].substring(0, 'a=inactive'.length) === 'a=inactive')) {
  777. cur_media.direction = lines[i].substring('a='.length);
  778. }
  779. }
  780. return res;
  781. };
  782. /**
  783. * The _indexOfArray() method returns the first a CompoundIndex at which a
  784. * given element can be found in the array, or _emptyCompoundIndex if it is
  785. * not present.
  786. *
  787. * Example:
  788. *
  789. * _indexOfArray('3', [ 'this is line 1', 'this is line 2', 'this is line 3' ])
  790. *
  791. * returns {row: 2, column: 14}
  792. *
  793. * @param needle
  794. * @param haystack
  795. * @param start
  796. * @returns {}
  797. * @private
  798. */
  799. SimulcastUtils.prototype._indexOfArray = function (needle, haystack, start) {
  800. var length = haystack.length, idx, i;
  801. if (!start) {
  802. start = 0;
  803. }
  804. for (i = start; i < length; i++) {
  805. idx = haystack[i].indexOf(needle);
  806. if (idx !== -1) {
  807. return {row: i, column: idx};
  808. }
  809. }
  810. return this._emptyCompoundIndex;
  811. };
  812. SimulcastUtils.prototype._removeSimulcastGroup = function (lines) {
  813. var i;
  814. for (i = lines.length - 1; i >= 0; i--) {
  815. if (lines[i].indexOf('a=ssrc-group:SIM') !== -1) {
  816. lines.splice(i, 1);
  817. }
  818. }
  819. };
  820. SimulcastUtils.prototype._compileVideoSources = function (videoSources) {
  821. var sb = [], ssrc, addedSSRCs = [];
  822. this.logger.info('Compiling video sources...');
  823. // Add the groups
  824. if (videoSources.groups && videoSources.groups.length !== 0) {
  825. videoSources.groups.forEach(function (group) {
  826. if (group.ssrcs && group.ssrcs.length !== 0) {
  827. sb.push([['a=ssrc-group:', group.semantics].join(''), group.ssrcs.join(' ')].join(' '));
  828. // if (group.semantics !== 'SIM') {
  829. group.ssrcs.forEach(function (ssrc) {
  830. addedSSRCs.push(ssrc);
  831. sb.splice.apply(sb, [sb.length, 0].concat([
  832. ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
  833. ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
  834. });
  835. //}
  836. }
  837. });
  838. }
  839. // Then add any free sources.
  840. if (videoSources.sources) {
  841. for (ssrc in videoSources.sources) {
  842. if (addedSSRCs.indexOf(ssrc) === -1) {
  843. sb.splice.apply(sb, [sb.length, 0].concat([
  844. ["a=ssrc:", ssrc, " cname:", videoSources.sources[ssrc].cname].join(''),
  845. ["a=ssrc:", ssrc, " msid:", videoSources.sources[ssrc].msid].join('')]));
  846. }
  847. }
  848. }
  849. return sb;
  850. };
  851. module.exports = SimulcastUtils;
  852. },{"./SimulcastLogger":1}],5:[function(require,module,exports){
  853. /*jslint plusplus: true */
  854. /*jslint nomen: true*/
  855. var SimulcastSender = require("./SimulcastSender");
  856. var NoSimulcastSender = SimulcastSender["no"];
  857. var NativeSimulcastSender = SimulcastSender["native"];
  858. var SimulcastReceiver = require("./SimulcastReceiver");
  859. var SimulcastUtils = require("./SimulcastUtils");
  860. /**
  861. *
  862. * @constructor
  863. */
  864. function SimulcastManager() {
  865. // Create the simulcast utilities.
  866. this.simulcastUtils = new SimulcastUtils();
  867. // Create remote simulcast.
  868. this.simulcastReceiver = new SimulcastReceiver();
  869. // Initialize local simulcast.
  870. // TODO(gp) move into SimulcastManager.prototype.getUserMedia and take into
  871. // account constraints.
  872. if (!config.enableSimulcast) {
  873. this.simulcastSender = new NoSimulcastSender();
  874. } else {
  875. var isChromium = window.chrome,
  876. vendorName = window.navigator.vendor;
  877. if(isChromium !== null && isChromium !== undefined
  878. /* skip opera */
  879. && vendorName === "Google Inc."
  880. /* skip Chromium as suggested by fippo */
  881. && !window.navigator.appVersion.match(/Chromium\//) ) {
  882. var ver = parseInt(window.navigator.appVersion.match(/Chrome\/(\d+)\./)[1], 10);
  883. if (ver > 37) {
  884. this.simulcastSender = new NativeSimulcastSender();
  885. } else {
  886. this.simulcastSender = new NoSimulcastSender();
  887. }
  888. } else {
  889. this.simulcastSender = new NoSimulcastSender();
  890. }
  891. }
  892. }
  893. /**
  894. * Restores the simulcast groups of the remote description. In
  895. * transformRemoteDescription we remove those in order for the set remote
  896. * description to succeed. The focus needs the signal the groups to new
  897. * participants.
  898. *
  899. * @param desc
  900. * @returns {*}
  901. */
  902. SimulcastManager.prototype.reverseTransformRemoteDescription = function (desc) {
  903. return this.simulcastReceiver.reverseTransformRemoteDescription(desc);
  904. };
  905. /**
  906. * Removes the ssrc-group:SIM from the remote description bacause Chrome
  907. * either gets confused and thinks this is an FID group or, if an FID group
  908. * is already present, it fails to set the remote description.
  909. *
  910. * @param desc
  911. * @returns {*}
  912. */
  913. SimulcastManager.prototype.transformRemoteDescription = function (desc) {
  914. return this.simulcastReceiver.transformRemoteDescription(desc);
  915. };
  916. /**
  917. * Gets the fully qualified msid (stream.id + track.id) associated to the
  918. * SSRC.
  919. *
  920. * @param ssrc
  921. * @returns {*}
  922. */
  923. SimulcastManager.prototype.getRemoteVideoStreamIdBySSRC = function (ssrc) {
  924. return this.simulcastReceiver.getRemoteVideoStreamIdBySSRC(ssrc);
  925. };
  926. /**
  927. * Returns a stream with single video track, the one currently being
  928. * received by this endpoint.
  929. *
  930. * @param stream the remote simulcast stream.
  931. * @returns {webkitMediaStream}
  932. */
  933. SimulcastManager.prototype.getReceivingVideoStream = function (stream) {
  934. return this.simulcastReceiver.getReceivingVideoStream(stream);
  935. };
  936. /**
  937. *
  938. *
  939. * @param desc
  940. * @returns {*}
  941. */
  942. SimulcastManager.prototype.transformLocalDescription = function (desc) {
  943. return this.simulcastSender.transformLocalDescription(desc);
  944. };
  945. /**
  946. *
  947. * @returns {*}
  948. */
  949. SimulcastManager.prototype.getLocalVideoStream = function() {
  950. return this.simulcastSender.getLocalVideoStream();
  951. };
  952. /**
  953. * GUM for simulcast.
  954. *
  955. * @param constraints
  956. * @param success
  957. * @param err
  958. */
  959. SimulcastManager.prototype.getUserMedia = function (constraints, success, err) {
  960. this.simulcastSender.getUserMedia(constraints, success, err);
  961. };
  962. /**
  963. * Prepares the local description for public usage (i.e. to be signaled
  964. * through Jingle to the focus).
  965. *
  966. * @param desc
  967. * @returns {RTCSessionDescription}
  968. */
  969. SimulcastManager.prototype.reverseTransformLocalDescription = function (desc) {
  970. return this.simulcastSender.reverseTransformLocalDescription(desc);
  971. };
  972. /**
  973. * Ensures that the simulcast group is present in the answer, _if_ native
  974. * simulcast is enabled,
  975. *
  976. * @param desc
  977. * @returns {*}
  978. */
  979. SimulcastManager.prototype.transformAnswer = function (desc) {
  980. return this.simulcastSender.transformAnswer(desc);
  981. };
  982. SimulcastManager.prototype.getReceivingSSRC = function (jid) {
  983. return this.simulcastReceiver.getReceivingSSRC(jid);
  984. };
  985. SimulcastManager.prototype.getReceivingVideoStreamBySSRC = function (msid) {
  986. return this.simulcastReceiver.getReceivingVideoStreamBySSRC(msid);
  987. };
  988. /**
  989. *
  990. * @param lines
  991. * @param mediatypes
  992. * @returns {*}
  993. */
  994. SimulcastManager.prototype.parseMedia = function(lines, mediatypes) {
  995. var sb = lines.sdp.split('\r\n');
  996. return this.simulcastUtils.parseMedia(sb, mediatypes);
  997. };
  998. SimulcastManager.prototype._setReceivingVideoStream = function(resource, ssrc) {
  999. this.simulcastReceiver._setReceivingVideoStream(resource, ssrc);
  1000. };
  1001. SimulcastManager.prototype._setLocalVideoStreamEnabled = function(ssrc, enabled) {
  1002. this.simulcastSender._setLocalVideoStreamEnabled(ssrc, enabled);
  1003. };
  1004. SimulcastManager.prototype.resetSender = function() {
  1005. if (typeof this.simulcastSender.reset === 'function'){
  1006. this.simulcastSender.reset();
  1007. }
  1008. };
  1009. $(document).bind('simulcastlayerschanged', function (event, endpointSimulcastLayers) {
  1010. endpointSimulcastLayers.forEach(function (esl) {
  1011. var ssrc = esl.simulcastLayer.primarySSRC;
  1012. simulcast._setReceivingVideoStream(esl.endpoint, ssrc);
  1013. });
  1014. });
  1015. $(document).bind('startsimulcastlayer', function (event, simulcastLayer) {
  1016. var ssrc = simulcastLayer.primarySSRC;
  1017. simulcast._setLocalVideoStreamEnabled(ssrc, true);
  1018. });
  1019. $(document).bind('stopsimulcastlayer', function (event, simulcastLayer) {
  1020. var ssrc = simulcastLayer.primarySSRC;
  1021. simulcast._setLocalVideoStreamEnabled(ssrc, false);
  1022. });
  1023. var simulcast = new SimulcastManager();
  1024. module.exports = simulcast;
  1025. },{"./SimulcastReceiver":2,"./SimulcastSender":3,"./SimulcastUtils":4}]},{},[5])(5)
  1026. });