Você não pode selecionar mais de 25 tópicos Os tópicos devem começar com uma letra ou um número, podem incluir traços ('-') e podem ter até 35 caracteres.

simulcast.js 36KB

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