選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

strophe.jingle.adapter.js 28KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759
  1. function TraceablePeerConnection(ice_config, constraints) {
  2. var self = this;
  3. var RTCPeerconnection = navigator.mozGetUserMedia ? mozRTCPeerConnection : webkitRTCPeerConnection;
  4. this.peerconnection = new RTCPeerconnection(ice_config, constraints);
  5. this.updateLog = [];
  6. this.stats = {};
  7. this.statsinterval = null;
  8. this.originalRemoteDescription = null;
  9. this.maxstats = 0; // limit to 300 values, i.e. 5 minutes; set to 0 to disable
  10. /**
  11. * Array of ssrcs that will be added on next modifySources call.
  12. * @type {Array}
  13. */
  14. this.addssrc = [];
  15. /**
  16. * Array of ssrcs that will be added on next modifySources call.
  17. * @type {Array}
  18. */
  19. this.removessrc = [];
  20. /**
  21. * Pending operation that will be done during modifySources call.
  22. * Currently 'mute'/'unmute' operations are supported.
  23. *
  24. * @type {String}
  25. */
  26. this.pendingop = null;
  27. /**
  28. * Flag indicates that peer connection stream have changed and modifySources should proceed.
  29. * @type {boolean}
  30. */
  31. this.switchstreams = false;
  32. // override as desired
  33. this.trace = function (what, info) {
  34. //console.warn('WTRACE', what, info);
  35. self.updateLog.push({
  36. time: new Date(),
  37. type: what,
  38. value: info || ""
  39. });
  40. };
  41. this.onicecandidate = null;
  42. this.peerconnection.onicecandidate = function (event) {
  43. self.trace('onicecandidate', JSON.stringify(event.candidate, null, ' '));
  44. if (self.onicecandidate !== null) {
  45. self.onicecandidate(event);
  46. }
  47. };
  48. this.onaddstream = null;
  49. this.peerconnection.onaddstream = function (event) {
  50. self.trace('onaddstream', event.stream.id);
  51. if (self.onaddstream !== null) {
  52. self.onaddstream(event);
  53. }
  54. };
  55. this.onremovestream = null;
  56. this.peerconnection.onremovestream = function (event) {
  57. self.trace('onremovestream', event.stream.id);
  58. if (self.onremovestream !== null) {
  59. self.onremovestream(event);
  60. }
  61. };
  62. this.onsignalingstatechange = null;
  63. this.peerconnection.onsignalingstatechange = function (event) {
  64. self.trace('onsignalingstatechange', self.signalingState);
  65. if (self.onsignalingstatechange !== null) {
  66. self.onsignalingstatechange(event);
  67. }
  68. };
  69. this.oniceconnectionstatechange = null;
  70. this.peerconnection.oniceconnectionstatechange = function (event) {
  71. self.trace('oniceconnectionstatechange', self.iceConnectionState);
  72. if (self.oniceconnectionstatechange !== null) {
  73. self.oniceconnectionstatechange(event);
  74. }
  75. };
  76. this.onnegotiationneeded = null;
  77. this.peerconnection.onnegotiationneeded = function (event) {
  78. self.trace('onnegotiationneeded');
  79. if (self.onnegotiationneeded !== null) {
  80. self.onnegotiationneeded(event);
  81. }
  82. };
  83. self.ondatachannel = null;
  84. this.peerconnection.ondatachannel = function (event) {
  85. self.trace('ondatachannel', event);
  86. if (self.ondatachannel !== null) {
  87. self.ondatachannel(event);
  88. }
  89. };
  90. if (!navigator.mozGetUserMedia && this.maxstats) {
  91. this.statsinterval = window.setInterval(function() {
  92. self.peerconnection.getStats(function(stats) {
  93. var results = stats.result();
  94. for (var i = 0; i < results.length; ++i) {
  95. //console.log(results[i].type, results[i].id, results[i].names())
  96. var now = new Date();
  97. results[i].names().forEach(function (name) {
  98. var id = results[i].id + '-' + name;
  99. if (!self.stats[id]) {
  100. self.stats[id] = {
  101. startTime: now,
  102. endTime: now,
  103. values: [],
  104. times: []
  105. };
  106. }
  107. self.stats[id].values.push(results[i].stat(name));
  108. self.stats[id].times.push(now.getTime());
  109. if (self.stats[id].values.length > self.maxstats) {
  110. self.stats[id].values.shift();
  111. self.stats[id].times.shift();
  112. }
  113. self.stats[id].endTime = now;
  114. });
  115. }
  116. });
  117. }, 1000);
  118. }
  119. };
  120. dumpSDP = function(description) {
  121. return 'type: ' + description.type + '\r\n' + description.sdp;
  122. }
  123. if (TraceablePeerConnection.prototype.__defineGetter__ !== undefined) {
  124. TraceablePeerConnection.prototype.__defineGetter__('signalingState', function() { return this.peerconnection.signalingState; });
  125. TraceablePeerConnection.prototype.__defineGetter__('iceConnectionState', function() { return this.peerconnection.iceConnectionState; });
  126. TraceablePeerConnection.prototype.__defineGetter__('localDescription', function() {
  127. var publicLocalDescription = simulcast.reverseTransformLocalDescription(this.peerconnection.localDescription);
  128. return publicLocalDescription;
  129. });
  130. TraceablePeerConnection.prototype.__defineGetter__('remoteDescription', function() {
  131. var publicRemoteDescription = simulcast.reverseTransformRemoteDescription(this.peerconnection.remoteDescription);
  132. return publicRemoteDescription;
  133. });
  134. }
  135. TraceablePeerConnection.prototype.addStream = function (stream) {
  136. this.trace('addStream', stream.id);
  137. try
  138. {
  139. this.peerconnection.addStream(stream);
  140. }
  141. catch (e)
  142. {
  143. console.error(e);
  144. return;
  145. }
  146. };
  147. TraceablePeerConnection.prototype.removeStream = function (stream) {
  148. this.trace('removeStream', stream.id);
  149. this.peerconnection.removeStream(stream);
  150. };
  151. TraceablePeerConnection.prototype.createDataChannel = function (label, opts) {
  152. this.trace('createDataChannel', label, opts);
  153. return this.peerconnection.createDataChannel(label, opts);
  154. };
  155. TraceablePeerConnection.prototype.setLocalDescription = function (description, successCallback, failureCallback) {
  156. var self = this;
  157. description = simulcast.transformLocalDescription(description);
  158. this.trace('setLocalDescription', dumpSDP(description));
  159. this.peerconnection.setLocalDescription(description,
  160. function () {
  161. self.trace('setLocalDescriptionOnSuccess');
  162. successCallback();
  163. },
  164. function (err) {
  165. self.trace('setLocalDescriptionOnFailure', err);
  166. failureCallback(err);
  167. }
  168. );
  169. /*
  170. if (this.statsinterval === null && this.maxstats > 0) {
  171. // start gathering stats
  172. }
  173. */
  174. };
  175. TraceablePeerConnection.prototype.setRemoteDescription = function (description, successCallback, failureCallback) {
  176. var self = this;
  177. description = simulcast.transformRemoteDescription(description);
  178. this.trace('setRemoteDescription', dumpSDP(description));
  179. this.originalRemoteDescription = description;
  180. this.peerconnection.setRemoteDescription(description,
  181. function () {
  182. self.trace('setRemoteDescriptionOnSuccess');
  183. successCallback();
  184. },
  185. function (err) {
  186. self.trace('setRemoteDescriptionOnFailure', err);
  187. failureCallback(err);
  188. }
  189. );
  190. /*
  191. if (this.statsinterval === null && this.maxstats > 0) {
  192. // start gathering stats
  193. }
  194. */
  195. };
  196. TraceablePeerConnection.prototype.hardMuteVideo = function (muted) {
  197. this.pendingop = muted ? 'mute' : 'unmute';
  198. };
  199. TraceablePeerConnection.prototype.enqueueAddSsrc = function(channel, ssrcLines) {
  200. if (!this.addssrc[channel]) {
  201. this.addssrc[channel] = '';
  202. }
  203. this.addssrc[channel] += ssrcLines;
  204. }
  205. TraceablePeerConnection.prototype.addSource = function (elem) {
  206. console.log('addssrc', new Date().getTime());
  207. console.log('ice', this.iceConnectionState);
  208. var sdp = new SDP(this.remoteDescription.sdp);
  209. var mySdp = new SDP(this.peerconnection.localDescription.sdp);
  210. var self = this;
  211. $(elem).each(function (idx, content) {
  212. var name = $(content).attr('name');
  213. var lines = '';
  214. tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
  215. var semantics = this.getAttribute('semantics');
  216. var ssrcs = $(this).find('>source').map(function () {
  217. return this.getAttribute('ssrc');
  218. }).get();
  219. if (ssrcs.length != 0) {
  220. lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
  221. }
  222. });
  223. tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
  224. tmp.each(function () {
  225. var ssrc = $(this).attr('ssrc');
  226. if(mySdp.containsSSRC(ssrc)){
  227. /**
  228. * This happens when multiple participants change their streams at the same time and
  229. * ColibriFocus.modifySources have to wait for stable state. In the meantime multiple
  230. * addssrc are scheduled for update IQ. See
  231. */
  232. console.warn("Got add stream request for my own ssrc: "+ssrc);
  233. return;
  234. }
  235. $(this).find('>parameter').each(function () {
  236. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  237. if ($(this).attr('value') && $(this).attr('value').length)
  238. lines += ':' + $(this).attr('value');
  239. lines += '\r\n';
  240. });
  241. });
  242. sdp.media.forEach(function(media, idx) {
  243. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  244. return;
  245. sdp.media[idx] += lines;
  246. self.enqueueAddSsrc(idx, lines);
  247. });
  248. sdp.raw = sdp.session + sdp.media.join('');
  249. });
  250. };
  251. TraceablePeerConnection.prototype.enqueueRemoveSsrc = function(channel, ssrcLines) {
  252. if (!this.removessrc[channel]){
  253. this.removessrc[channel] = '';
  254. }
  255. this.removessrc[channel] += ssrcLines;
  256. }
  257. TraceablePeerConnection.prototype.removeSource = function (elem) {
  258. console.log('removessrc', new Date().getTime());
  259. console.log('ice', this.iceConnectionState);
  260. var sdp = new SDP(this.remoteDescription.sdp);
  261. var mySdp = new SDP(this.peerconnection.localDescription.sdp);
  262. var self = this;
  263. $(elem).each(function (idx, content) {
  264. var name = $(content).attr('name');
  265. var lines = '';
  266. tmp = $(content).find('ssrc-group[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]').each(function() {
  267. var semantics = this.getAttribute('semantics');
  268. var ssrcs = $(this).find('>source').map(function () {
  269. return this.getAttribute('ssrc');
  270. }).get();
  271. if (ssrcs.length != 0) {
  272. lines += 'a=ssrc-group:' + semantics + ' ' + ssrcs.join(' ') + '\r\n';
  273. }
  274. });
  275. tmp = $(content).find('source[xmlns="urn:xmpp:jingle:apps:rtp:ssma:0"]'); // can handle both >source and >description>source
  276. tmp.each(function () {
  277. var ssrc = $(this).attr('ssrc');
  278. // This should never happen, but can be useful for bug detection
  279. if(mySdp.containsSSRC(ssrc)){
  280. console.error("Got remove stream request for my own ssrc: "+ssrc);
  281. return;
  282. }
  283. $(this).find('>parameter').each(function () {
  284. lines += 'a=ssrc:' + ssrc + ' ' + $(this).attr('name');
  285. if ($(this).attr('value') && $(this).attr('value').length)
  286. lines += ':' + $(this).attr('value');
  287. lines += '\r\n';
  288. });
  289. });
  290. sdp.media.forEach(function(media, idx) {
  291. if (!SDPUtil.find_line(media, 'a=mid:' + name))
  292. return;
  293. sdp.media[idx] += lines;
  294. self.enqueueRemoveSsrc(idx, lines);
  295. });
  296. sdp.raw = sdp.session + sdp.media.join('');
  297. });
  298. };
  299. TraceablePeerConnection.prototype.modifySources = function(successCallback) {
  300. var self = this;
  301. if (this.signalingState == 'closed') return;
  302. if (!(this.addssrc.length || this.removessrc.length || this.pendingop !== null || this.switchstreams)){
  303. // There is nothing to do since scheduled job might have been executed by another succeeding call
  304. if(successCallback){
  305. successCallback();
  306. }
  307. return;
  308. }
  309. // FIXME: this is a big hack
  310. // https://code.google.com/p/webrtc/issues/detail?id=2688
  311. if (!(this.signalingState == 'stable' && this.iceConnectionState == 'connected')) {
  312. console.warn('modifySources not yet', this.signalingState, this.iceConnectionState);
  313. this.wait = true;
  314. window.setTimeout(function() { self.modifySources(successCallback); }, 250);
  315. return;
  316. }
  317. if (this.wait) {
  318. window.setTimeout(function() { self.modifySources(successCallback); }, 2500);
  319. this.wait = false;
  320. return;
  321. }
  322. // Reset switch streams flag
  323. this.switchstreams = false;
  324. var sdp = new SDP(this.remoteDescription.sdp);
  325. // add sources
  326. this.addssrc.forEach(function(lines, idx) {
  327. sdp.media[idx] += lines;
  328. });
  329. this.addssrc = [];
  330. // remove sources
  331. this.removessrc.forEach(function(lines, idx) {
  332. lines = lines.split('\r\n');
  333. lines.pop(); // remove empty last element;
  334. lines.forEach(function(line) {
  335. sdp.media[idx] = sdp.media[idx].replace(line + '\r\n', '');
  336. });
  337. });
  338. this.removessrc = [];
  339. sdp.raw = sdp.session + sdp.media.join('');
  340. this.setRemoteDescription(new RTCSessionDescription({type: 'offer', sdp: sdp.raw}),
  341. function() {
  342. if(self.signalingState == 'closed') {
  343. console.error("createAnswer attempt on closed state");
  344. return;
  345. }
  346. self.createAnswer(
  347. function(modifiedAnswer) {
  348. // change video direction, see https://github.com/jitsi/jitmeet/issues/41
  349. if (self.pendingop !== null) {
  350. var sdp = new SDP(modifiedAnswer.sdp);
  351. if (sdp.media.length > 1) {
  352. switch(self.pendingop) {
  353. case 'mute':
  354. sdp.media[1] = sdp.media[1].replace('a=sendrecv', 'a=recvonly');
  355. break;
  356. case 'unmute':
  357. sdp.media[1] = sdp.media[1].replace('a=recvonly', 'a=sendrecv');
  358. break;
  359. }
  360. sdp.raw = sdp.session + sdp.media.join('');
  361. modifiedAnswer.sdp = sdp.raw;
  362. }
  363. self.pendingop = null;
  364. }
  365. // FIXME: pushing down an answer while ice connection state
  366. // is still checking is bad...
  367. //console.log(self.peerconnection.iceConnectionState);
  368. // trying to work around another chrome bug
  369. //modifiedAnswer.sdp = modifiedAnswer.sdp.replace(/a=setup:active/g, 'a=setup:actpass');
  370. self.setLocalDescription(modifiedAnswer,
  371. function() {
  372. //console.log('modified setLocalDescription ok');
  373. if(successCallback){
  374. successCallback();
  375. }
  376. },
  377. function(error) {
  378. console.error('modified setLocalDescription failed', error);
  379. }
  380. );
  381. },
  382. function(error) {
  383. console.error('modified answer failed', error);
  384. }
  385. );
  386. },
  387. function(error) {
  388. console.error('modify failed', error);
  389. }
  390. );
  391. };
  392. TraceablePeerConnection.prototype.close = function () {
  393. this.trace('stop');
  394. if (this.statsinterval !== null) {
  395. window.clearInterval(this.statsinterval);
  396. this.statsinterval = null;
  397. }
  398. this.peerconnection.close();
  399. };
  400. TraceablePeerConnection.prototype.createOffer = function (successCallback, failureCallback, constraints) {
  401. var self = this;
  402. this.trace('createOffer', JSON.stringify(constraints, null, ' '));
  403. this.peerconnection.createOffer(
  404. function (offer) {
  405. self.trace('createOfferOnSuccess', dumpSDP(offer));
  406. successCallback(offer);
  407. },
  408. function(err) {
  409. self.trace('createOfferOnFailure', err);
  410. failureCallback(err);
  411. },
  412. constraints
  413. );
  414. };
  415. TraceablePeerConnection.prototype.createAnswer = function (successCallback, failureCallback, constraints) {
  416. var self = this;
  417. this.trace('createAnswer', JSON.stringify(constraints, null, ' '));
  418. this.peerconnection.createAnswer(
  419. function (answer) {
  420. answer = simulcast.transformAnswer(answer);
  421. self.trace('createAnswerOnSuccess', dumpSDP(answer));
  422. successCallback(answer);
  423. },
  424. function(err) {
  425. self.trace('createAnswerOnFailure', err);
  426. failureCallback(err);
  427. },
  428. constraints
  429. );
  430. };
  431. TraceablePeerConnection.prototype.addIceCandidate = function (candidate, successCallback, failureCallback) {
  432. var self = this;
  433. this.trace('addIceCandidate', JSON.stringify(candidate, null, ' '));
  434. this.peerconnection.addIceCandidate(candidate);
  435. /* maybe later
  436. this.peerconnection.addIceCandidate(candidate,
  437. function () {
  438. self.trace('addIceCandidateOnSuccess');
  439. successCallback();
  440. },
  441. function (err) {
  442. self.trace('addIceCandidateOnFailure', err);
  443. failureCallback(err);
  444. }
  445. );
  446. */
  447. };
  448. TraceablePeerConnection.prototype.getStats = function(callback, errback) {
  449. if (navigator.mozGetUserMedia) {
  450. // ignore for now...
  451. if(!errback)
  452. errback = function () {
  453. }
  454. this.peerconnection.getStats(null,callback,errback);
  455. } else {
  456. this.peerconnection.getStats(callback);
  457. }
  458. };
  459. // mozilla chrome compat layer -- very similar to adapter.js
  460. function setupRTC() {
  461. var RTC = null;
  462. if (navigator.mozGetUserMedia) {
  463. console.log('This appears to be Firefox');
  464. var version = parseInt(navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
  465. if (version >= 22) {
  466. RTC = {
  467. peerconnection: mozRTCPeerConnection,
  468. browser: 'firefox',
  469. getUserMedia: navigator.mozGetUserMedia.bind(navigator),
  470. attachMediaStream: function (element, stream) {
  471. element[0].mozSrcObject = stream;
  472. element[0].play();
  473. },
  474. pc_constraints: {}
  475. };
  476. if (!MediaStream.prototype.getVideoTracks)
  477. MediaStream.prototype.getVideoTracks = function () { return []; };
  478. if (!MediaStream.prototype.getAudioTracks)
  479. MediaStream.prototype.getAudioTracks = function () { return []; };
  480. RTCSessionDescription = mozRTCSessionDescription;
  481. RTCIceCandidate = mozRTCIceCandidate;
  482. RTC.getLocalSSRC = function (session, callback) {
  483. session.peerconnection.getStats(function (s) {
  484. var keys = Object.keys(s);
  485. var audio = null;
  486. var video = null;
  487. for(var i = 0; i < keys.length; i++)
  488. {
  489. if(keys[i].indexOf("outbound_rtp_audio") != -1)
  490. {
  491. audio = s[keys[i]].ssrc;
  492. }
  493. if(keys[i].indexOf("outbound_rtp_video") != -1)
  494. {
  495. video = s[keys[i]].ssrc;
  496. }
  497. }
  498. session.localStreamsSSRC = {
  499. "audio": audio,//for stable 0
  500. "video": video// for stable 1
  501. };
  502. callback(session.localStreamsSSRC);
  503. },
  504. function () {
  505. callback(null);
  506. });
  507. };
  508. RTC.getStreamID = function (stream) {
  509. var tracks = stream.getVideoTracks();
  510. if(!tracks || tracks.length == 0)
  511. {
  512. tracks = stream.getAudioTracks();
  513. }
  514. return tracks[0].id.replace(/[\{,\}]/g,"");
  515. }
  516. }
  517. } else if (navigator.webkitGetUserMedia) {
  518. console.log('This appears to be Chrome');
  519. RTC = {
  520. peerconnection: webkitRTCPeerConnection,
  521. browser: 'chrome',
  522. getUserMedia: navigator.webkitGetUserMedia.bind(navigator),
  523. attachMediaStream: function (element, stream) {
  524. element.attr('src', webkitURL.createObjectURL(stream));
  525. },
  526. // DTLS should now be enabled by default but..
  527. pc_constraints: {'optional': [{'DtlsSrtpKeyAgreement': 'true'}]}
  528. };
  529. if (navigator.userAgent.indexOf('Android') != -1) {
  530. RTC.pc_constraints = {}; // disable DTLS on Android
  531. }
  532. if (!webkitMediaStream.prototype.getVideoTracks) {
  533. webkitMediaStream.prototype.getVideoTracks = function () {
  534. return this.videoTracks;
  535. };
  536. }
  537. if (!webkitMediaStream.prototype.getAudioTracks) {
  538. webkitMediaStream.prototype.getAudioTracks = function () {
  539. return this.audioTracks;
  540. };
  541. }
  542. RTC.getLocalSSRC = function (session, callback) {
  543. callback(null);
  544. }
  545. RTC.getStreamID = function (stream) {
  546. return stream.id;
  547. }
  548. }
  549. if (RTC === null) {
  550. try { console.log('Browser does not appear to be WebRTC-capable'); } catch (e) { }
  551. }
  552. return RTC;
  553. }
  554. function getUserMediaWithConstraints(um, success_callback, failure_callback, resolution, bandwidth, fps, desktopStream) {
  555. var constraints = {audio: false, video: false};
  556. if (um.indexOf('video') >= 0) {
  557. constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
  558. }
  559. if (um.indexOf('audio') >= 0) {
  560. constraints.audio = { mandatory: {}, optional: []};// same behaviour as true
  561. }
  562. if (um.indexOf('screen') >= 0) {
  563. constraints.video = {
  564. mandatory: {
  565. chromeMediaSource: "screen",
  566. googLeakyBucket: true,
  567. maxWidth: window.screen.width,
  568. maxHeight: window.screen.height,
  569. maxFrameRate: 3
  570. },
  571. optional: []
  572. };
  573. }
  574. if (um.indexOf('desktop') >= 0) {
  575. constraints.video = {
  576. mandatory: {
  577. chromeMediaSource: "desktop",
  578. chromeMediaSourceId: desktopStream,
  579. googLeakyBucket: true,
  580. maxWidth: window.screen.width,
  581. maxHeight: window.screen.height,
  582. maxFrameRate: 3
  583. },
  584. optional: []
  585. }
  586. }
  587. if (constraints.audio) {
  588. // if it is good enough for hangouts...
  589. constraints.audio.optional.push(
  590. {googEchoCancellation: true},
  591. {googAutoGainControl: true},
  592. {googNoiseSupression: true},
  593. {googHighpassFilter: true},
  594. {googNoisesuppression2: true},
  595. {googEchoCancellation2: true},
  596. {googAutoGainControl2: true}
  597. );
  598. }
  599. if (constraints.video) {
  600. constraints.video.optional.push(
  601. {googNoiseReduction: false} // chrome 37 workaround for issue 3807, reenable in M38
  602. );
  603. if (um.indexOf('video') >= 0) {
  604. constraints.video.optional.push(
  605. {googLeakyBucket: true}
  606. );
  607. }
  608. }
  609. // Check if we are running on Android device
  610. var isAndroid = navigator.userAgent.indexOf('Android') != -1;
  611. if (resolution && !constraints.video || isAndroid) {
  612. constraints.video = { mandatory: {}, optional: [] };// same behaviour as true
  613. }
  614. // see https://code.google.com/p/chromium/issues/detail?id=143631#c9 for list of supported resolutions
  615. switch (resolution) {
  616. // 16:9 first
  617. case '1080':
  618. case 'fullhd':
  619. constraints.video.mandatory.minWidth = 1920;
  620. constraints.video.mandatory.minHeight = 1080;
  621. break;
  622. case '720':
  623. case 'hd':
  624. constraints.video.mandatory.minWidth = 1280;
  625. constraints.video.mandatory.minHeight = 720;
  626. break;
  627. case '360':
  628. constraints.video.mandatory.minWidth = 640;
  629. constraints.video.mandatory.minHeight = 360;
  630. break;
  631. case '180':
  632. constraints.video.mandatory.minWidth = 320;
  633. constraints.video.mandatory.minHeight = 180;
  634. break;
  635. // 4:3
  636. case '960':
  637. constraints.video.mandatory.minWidth = 960;
  638. constraints.video.mandatory.minHeight = 720;
  639. break;
  640. case '640':
  641. case 'vga':
  642. constraints.video.mandatory.minWidth = 640;
  643. constraints.video.mandatory.minHeight = 480;
  644. break;
  645. case '320':
  646. constraints.video.mandatory.minWidth = 320;
  647. constraints.video.mandatory.minHeight = 240;
  648. break;
  649. default:
  650. if (isAndroid) {
  651. constraints.video.mandatory.minWidth = 320;
  652. constraints.video.mandatory.minHeight = 240;
  653. constraints.video.mandatory.maxFrameRate = 15;
  654. }
  655. break;
  656. }
  657. if (constraints.video.mandatory.minWidth)
  658. constraints.video.mandatory.maxWidth = constraints.video.mandatory.minWidth;
  659. if (constraints.video.mandatory.minHeight)
  660. constraints.video.mandatory.maxHeight = constraints.video.mandatory.minHeight;
  661. if (bandwidth) { // doesn't work currently, see webrtc issue 1846
  662. if (!constraints.video) constraints.video = {mandatory: {}, optional: []};//same behaviour as true
  663. constraints.video.optional.push({bandwidth: bandwidth});
  664. }
  665. if (fps) { // for some cameras it might be necessary to request 30fps
  666. // so they choose 30fps mjpg over 10fps yuy2
  667. if (!constraints.video) constraints.video = {mandatory: {}, optional: []};// same behaviour as true;
  668. constraints.video.mandatory.minFrameRate = fps;
  669. }
  670. var isFF = navigator.userAgent.toLowerCase().indexOf('firefox') > -1;
  671. try {
  672. if (config.enableSimulcast
  673. && constraints.video
  674. && constraints.video.chromeMediaSource !== 'screen'
  675. && constraints.video.chromeMediaSource !== 'desktop'
  676. && !isAndroid
  677. // We currently do not support FF, as it doesn't have multistream support.
  678. && !isFF) {
  679. simulcast.getUserMedia(constraints, function (stream) {
  680. console.log('onUserMediaSuccess');
  681. success_callback(stream);
  682. },
  683. function (error) {
  684. console.warn('Failed to get access to local media. Error ', error);
  685. if (failure_callback) {
  686. failure_callback(error);
  687. }
  688. });
  689. } else {
  690. RTC.getUserMedia(constraints,
  691. function (stream) {
  692. console.log('onUserMediaSuccess');
  693. success_callback(stream);
  694. },
  695. function (error) {
  696. console.warn('Failed to get access to local media. Error ', error);
  697. if (failure_callback) {
  698. failure_callback(error);
  699. }
  700. });
  701. }
  702. } catch (e) {
  703. console.error('GUM failed: ', e);
  704. if(failure_callback) {
  705. failure_callback(e);
  706. }
  707. }
  708. }