您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

simulcast.bundle.js 38KB

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