webrtc-adapter.js 53 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575
  1. /*
  2. * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree.
  7. */
  8. /* More information about these options at jshint.com/docs/options */
  9. /* jshint browser: true, camelcase: true, curly: true, devel: true,
  10. eqeqeq: true, forin: false, globalstrict: true, node: true,
  11. quotmark: single, undef: true, unused: strict */
  12. /* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
  13. mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack */
  14. /* exported trace,requestUserMedia */
  15. 'use strict';
  16. var getUserMedia = null;
  17. var attachMediaStream = null;
  18. var reattachMediaStream = null;
  19. var webrtcDetectedBrowser = null;
  20. var webrtcDetectedVersion = null;
  21. var webrtcMinimumVersion = null;
  22. var webrtcUtils = {
  23. log: function() {
  24. // suppress console.log output when being included as a module.
  25. if (typeof module !== 'undefined' ||
  26. typeof require === 'function' && typeof define === 'function') {
  27. return;
  28. }
  29. console.log.apply(console, arguments);
  30. },
  31. extractVersion: function(uastring, expr, pos) {
  32. var match = uastring.match(expr);
  33. return match && match.length >= pos && parseInt(match[pos]);
  34. }
  35. };
  36. function trace(text) {
  37. // This function is used for logging.
  38. if (text[text.length - 1] === '\n') {
  39. text = text.substring(0, text.length - 1);
  40. }
  41. if (window.performance) {
  42. var now = (window.performance.now() / 1000).toFixed(3);
  43. webrtcUtils.log(now + ': ' + text);
  44. } else {
  45. webrtcUtils.log(text);
  46. }
  47. }
  48. if (typeof window === 'object') {
  49. if (window.HTMLMediaElement &&
  50. !('srcObject' in window.HTMLMediaElement.prototype)) {
  51. // Shim the srcObject property, once, when HTMLMediaElement is found.
  52. Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
  53. get: function() {
  54. // If prefixed srcObject property exists, return it.
  55. // Otherwise use the shimmed property, _srcObject
  56. return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject;
  57. },
  58. set: function(stream) {
  59. if ('mozSrcObject' in this) {
  60. this.mozSrcObject = stream;
  61. } else {
  62. // Use _srcObject as a private property for this shim
  63. this._srcObject = stream;
  64. // TODO: revokeObjectUrl(this.src) when !stream to release resources?
  65. this.src = URL.createObjectURL(stream);
  66. }
  67. }
  68. });
  69. }
  70. // Proxy existing globals
  71. getUserMedia = window.navigator && window.navigator.getUserMedia;
  72. }
  73. // Attach a media stream to an element.
  74. attachMediaStream = function(element, stream) {
  75. element.srcObject = stream;
  76. };
  77. reattachMediaStream = function(to, from) {
  78. to.srcObject = from.srcObject;
  79. };
  80. if (typeof window === 'undefined' || !window.navigator) {
  81. webrtcUtils.log('This does not appear to be a browser');
  82. webrtcDetectedBrowser = 'not a browser';
  83. } else if (navigator.mozGetUserMedia && window.mozRTCPeerConnection) {
  84. webrtcUtils.log('This appears to be Firefox');
  85. webrtcDetectedBrowser = 'firefox';
  86. // the detected firefox version.
  87. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  88. /Firefox\/([0-9]+)\./, 1);
  89. // the minimum firefox version still supported by adapter.
  90. webrtcMinimumVersion = 31;
  91. // The RTCPeerConnection object.
  92. window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  93. if (webrtcDetectedVersion < 38) {
  94. // .urls is not supported in FF < 38.
  95. // create RTCIceServers with a single url.
  96. if (pcConfig && pcConfig.iceServers) {
  97. var newIceServers = [];
  98. for (var i = 0; i < pcConfig.iceServers.length; i++) {
  99. var server = pcConfig.iceServers[i];
  100. if (server.hasOwnProperty('urls')) {
  101. for (var j = 0; j < server.urls.length; j++) {
  102. var newServer = {
  103. url: server.urls[j]
  104. };
  105. if (server.urls[j].indexOf('turn') === 0) {
  106. newServer.username = server.username;
  107. newServer.credential = server.credential;
  108. }
  109. newIceServers.push(newServer);
  110. }
  111. } else {
  112. newIceServers.push(pcConfig.iceServers[i]);
  113. }
  114. }
  115. pcConfig.iceServers = newIceServers;
  116. }
  117. }
  118. return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  119. };
  120. // The RTCSessionDescription object.
  121. if (!window.RTCSessionDescription) {
  122. window.RTCSessionDescription = mozRTCSessionDescription;
  123. }
  124. // The RTCIceCandidate object.
  125. if (!window.RTCIceCandidate) {
  126. window.RTCIceCandidate = mozRTCIceCandidate;
  127. }
  128. // getUserMedia constraints shim.
  129. getUserMedia = function(constraints, onSuccess, onError) {
  130. var constraintsToFF37 = function(c) {
  131. if (typeof c !== 'object' || c.require) {
  132. return c;
  133. }
  134. var require = [];
  135. Object.keys(c).forEach(function(key) {
  136. if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  137. return;
  138. }
  139. var r = c[key] = (typeof c[key] === 'object') ?
  140. c[key] : {ideal: c[key]};
  141. if (r.min !== undefined ||
  142. r.max !== undefined || r.exact !== undefined) {
  143. require.push(key);
  144. }
  145. if (r.exact !== undefined) {
  146. if (typeof r.exact === 'number') {
  147. r.min = r.max = r.exact;
  148. } else {
  149. c[key] = r.exact;
  150. }
  151. delete r.exact;
  152. }
  153. if (r.ideal !== undefined) {
  154. c.advanced = c.advanced || [];
  155. var oc = {};
  156. if (typeof r.ideal === 'number') {
  157. oc[key] = {min: r.ideal, max: r.ideal};
  158. } else {
  159. oc[key] = r.ideal;
  160. }
  161. c.advanced.push(oc);
  162. delete r.ideal;
  163. if (!Object.keys(r).length) {
  164. delete c[key];
  165. }
  166. }
  167. });
  168. if (require.length) {
  169. c.require = require;
  170. }
  171. return c;
  172. };
  173. if (webrtcDetectedVersion < 38) {
  174. webrtcUtils.log('spec: ' + JSON.stringify(constraints));
  175. if (constraints.audio) {
  176. constraints.audio = constraintsToFF37(constraints.audio);
  177. }
  178. if (constraints.video) {
  179. constraints.video = constraintsToFF37(constraints.video);
  180. }
  181. webrtcUtils.log('ff37: ' + JSON.stringify(constraints));
  182. }
  183. return navigator.mozGetUserMedia(constraints, onSuccess, onError);
  184. };
  185. navigator.getUserMedia = getUserMedia;
  186. // Shim for mediaDevices on older versions.
  187. if (!navigator.mediaDevices) {
  188. navigator.mediaDevices = {getUserMedia: requestUserMedia,
  189. addEventListener: function() { },
  190. removeEventListener: function() { }
  191. };
  192. }
  193. navigator.mediaDevices.enumerateDevices =
  194. navigator.mediaDevices.enumerateDevices || function() {
  195. return new Promise(function(resolve) {
  196. var infos = [
  197. {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
  198. {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
  199. ];
  200. resolve(infos);
  201. });
  202. };
  203. if (webrtcDetectedVersion < 41) {
  204. // Work around http://bugzil.la/1169665
  205. var orgEnumerateDevices =
  206. navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
  207. navigator.mediaDevices.enumerateDevices = function() {
  208. return orgEnumerateDevices().then(undefined, function(e) {
  209. if (e.name === 'NotFoundError') {
  210. return [];
  211. }
  212. throw e;
  213. });
  214. };
  215. }
  216. } else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
  217. webrtcUtils.log('This appears to be Chrome');
  218. webrtcDetectedBrowser = 'chrome';
  219. // the detected chrome version.
  220. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  221. /Chrom(e|ium)\/([0-9]+)\./, 2);
  222. // the minimum chrome version still supported by adapter.
  223. webrtcMinimumVersion = 38;
  224. // The RTCPeerConnection object.
  225. window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  226. // Translate iceTransportPolicy to iceTransports,
  227. // see https://code.google.com/p/webrtc/issues/detail?id=4869
  228. if (pcConfig && pcConfig.iceTransportPolicy) {
  229. pcConfig.iceTransports = pcConfig.iceTransportPolicy;
  230. }
  231. var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  232. var origGetStats = pc.getStats.bind(pc);
  233. pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
  234. var self = this;
  235. var args = arguments;
  236. // If selector is a function then we are in the old style stats so just
  237. // pass back the original getStats format to avoid breaking old users.
  238. if (arguments.length > 0 && typeof selector === 'function') {
  239. return origGetStats(selector, successCallback);
  240. }
  241. var fixChromeStats = function(response) {
  242. var standardReport = {};
  243. var reports = response.result();
  244. reports.forEach(function(report) {
  245. var standardStats = {
  246. id: report.id,
  247. timestamp: report.timestamp,
  248. type: report.type
  249. };
  250. report.names().forEach(function(name) {
  251. standardStats[name] = report.stat(name);
  252. });
  253. standardReport[standardStats.id] = standardStats;
  254. });
  255. return standardReport;
  256. };
  257. if (arguments.length >= 2) {
  258. var successCallbackWrapper = function(response) {
  259. args[1](fixChromeStats(response));
  260. };
  261. return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
  262. }
  263. // promise-support
  264. return new Promise(function(resolve, reject) {
  265. if (args.length === 1 && selector === null) {
  266. origGetStats.apply(self, [
  267. function(response) {
  268. resolve.apply(null, [fixChromeStats(response)]);
  269. }, reject]);
  270. } else {
  271. origGetStats.apply(self, [resolve, reject]);
  272. }
  273. });
  274. };
  275. return pc;
  276. };
  277. // add promise support
  278. ['createOffer', 'createAnswer'].forEach(function(method) {
  279. var nativeMethod = webkitRTCPeerConnection.prototype[method];
  280. webkitRTCPeerConnection.prototype[method] = function() {
  281. var self = this;
  282. if (arguments.length < 1 || (arguments.length === 1 &&
  283. typeof(arguments[0]) === 'object')) {
  284. var opts = arguments.length === 1 ? arguments[0] : undefined;
  285. return new Promise(function(resolve, reject) {
  286. nativeMethod.apply(self, [resolve, reject, opts]);
  287. });
  288. } else {
  289. return nativeMethod.apply(this, arguments);
  290. }
  291. };
  292. });
  293. ['setLocalDescription', 'setRemoteDescription',
  294. 'addIceCandidate'].forEach(function(method) {
  295. var nativeMethod = webkitRTCPeerConnection.prototype[method];
  296. webkitRTCPeerConnection.prototype[method] = function() {
  297. var args = arguments;
  298. var self = this;
  299. return new Promise(function(resolve, reject) {
  300. nativeMethod.apply(self, [args[0],
  301. function() {
  302. resolve();
  303. if (args.length >= 2) {
  304. args[1].apply(null, []);
  305. }
  306. },
  307. function(err) {
  308. reject(err);
  309. if (args.length >= 3) {
  310. args[2].apply(null, [err]);
  311. }
  312. }]
  313. );
  314. });
  315. };
  316. });
  317. // getUserMedia constraints shim.
  318. var constraintsToChrome = function(c) {
  319. if (typeof c !== 'object' || c.mandatory || c.optional) {
  320. return c;
  321. }
  322. var cc = {};
  323. Object.keys(c).forEach(function(key) {
  324. if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  325. return;
  326. }
  327. var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
  328. if (r.exact !== undefined && typeof r.exact === 'number') {
  329. r.min = r.max = r.exact;
  330. }
  331. var oldname = function(prefix, name) {
  332. if (prefix) {
  333. return prefix + name.charAt(0).toUpperCase() + name.slice(1);
  334. }
  335. return (name === 'deviceId') ? 'sourceId' : name;
  336. };
  337. if (r.ideal !== undefined) {
  338. cc.optional = cc.optional || [];
  339. var oc = {};
  340. if (typeof r.ideal === 'number') {
  341. oc[oldname('min', key)] = r.ideal;
  342. cc.optional.push(oc);
  343. oc = {};
  344. oc[oldname('max', key)] = r.ideal;
  345. cc.optional.push(oc);
  346. } else {
  347. oc[oldname('', key)] = r.ideal;
  348. cc.optional.push(oc);
  349. }
  350. }
  351. if (r.exact !== undefined && typeof r.exact !== 'number') {
  352. cc.mandatory = cc.mandatory || {};
  353. cc.mandatory[oldname('', key)] = r.exact;
  354. } else {
  355. ['min', 'max'].forEach(function(mix) {
  356. if (r[mix] !== undefined) {
  357. cc.mandatory = cc.mandatory || {};
  358. cc.mandatory[oldname(mix, key)] = r[mix];
  359. }
  360. });
  361. }
  362. });
  363. if (c.advanced) {
  364. cc.optional = (cc.optional || []).concat(c.advanced);
  365. }
  366. return cc;
  367. };
  368. getUserMedia = function(constraints, onSuccess, onError) {
  369. if (constraints.audio) {
  370. constraints.audio = constraintsToChrome(constraints.audio);
  371. }
  372. if (constraints.video) {
  373. constraints.video = constraintsToChrome(constraints.video);
  374. }
  375. webrtcUtils.log('chrome: ' + JSON.stringify(constraints));
  376. return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
  377. };
  378. navigator.getUserMedia = getUserMedia;
  379. if (!navigator.mediaDevices) {
  380. navigator.mediaDevices = {getUserMedia: requestUserMedia,
  381. enumerateDevices: function() {
  382. return new Promise(function(resolve) {
  383. var kinds = {audio: 'audioinput', video: 'videoinput'};
  384. return MediaStreamTrack.getSources(function(devices) {
  385. resolve(devices.map(function(device) {
  386. return {label: device.label,
  387. kind: kinds[device.kind],
  388. deviceId: device.id,
  389. groupId: ''};
  390. }));
  391. });
  392. });
  393. }};
  394. }
  395. // A shim for getUserMedia method on the mediaDevices object.
  396. // TODO(KaptenJansson) remove once implemented in Chrome stable.
  397. if (!navigator.mediaDevices.getUserMedia) {
  398. navigator.mediaDevices.getUserMedia = function(constraints) {
  399. return requestUserMedia(constraints);
  400. };
  401. } else {
  402. // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
  403. // function which returns a Promise, it does not accept spec-style
  404. // constraints.
  405. var origGetUserMedia = navigator.mediaDevices.getUserMedia.
  406. bind(navigator.mediaDevices);
  407. navigator.mediaDevices.getUserMedia = function(c) {
  408. webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment
  409. c.audio = constraintsToChrome(c.audio);
  410. c.video = constraintsToChrome(c.video);
  411. webrtcUtils.log('chrome: ' + JSON.stringify(c));
  412. return origGetUserMedia(c);
  413. };
  414. }
  415. // Dummy devicechange event methods.
  416. // TODO(KaptenJansson) remove once implemented in Chrome stable.
  417. if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
  418. navigator.mediaDevices.addEventListener = function() {
  419. webrtcUtils.log('Dummy mediaDevices.addEventListener called.');
  420. };
  421. }
  422. if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
  423. navigator.mediaDevices.removeEventListener = function() {
  424. webrtcUtils.log('Dummy mediaDevices.removeEventListener called.');
  425. };
  426. }
  427. // Attach a media stream to an element.
  428. attachMediaStream = function(element, stream) {
  429. if (webrtcDetectedVersion >= 43) {
  430. element.srcObject = stream;
  431. } else if (typeof element.src !== 'undefined') {
  432. element.src = URL.createObjectURL(stream);
  433. } else {
  434. webrtcUtils.log('Error attaching stream to element.');
  435. }
  436. };
  437. reattachMediaStream = function(to, from) {
  438. if (webrtcDetectedVersion >= 43) {
  439. to.srcObject = from.srcObject;
  440. } else {
  441. to.src = from.src;
  442. }
  443. };
  444. } else if (navigator.mediaDevices && navigator.userAgent.match(
  445. /Edge\/(\d+).(\d+)$/)) {
  446. webrtcUtils.log('This appears to be Edge');
  447. webrtcDetectedBrowser = 'edge';
  448. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  449. /Edge\/(\d+).(\d+)$/, 2);
  450. // the minimum version still supported by adapter.
  451. webrtcMinimumVersion = 12;
  452. if (RTCIceGatherer) {
  453. window.RTCIceCandidate = function(args) {
  454. return args;
  455. };
  456. window.RTCSessionDescription = function(args) {
  457. return args;
  458. };
  459. window.RTCPeerConnection = function(config) {
  460. var self = this;
  461. this.onicecandidate = null;
  462. this.onaddstream = null;
  463. this.onremovestream = null;
  464. this.onsignalingstatechange = null;
  465. this.oniceconnectionstatechange = null;
  466. this.onnegotiationneeded = null;
  467. this.ondatachannel = null;
  468. this.localStreams = [];
  469. this.remoteStreams = [];
  470. this.getLocalStreams = function() { return self.localStreams; };
  471. this.getRemoteStreams = function() { return self.remoteStreams; };
  472. this.localDescription = new RTCSessionDescription({
  473. type: '',
  474. sdp: ''
  475. });
  476. this.remoteDescription = new RTCSessionDescription({
  477. type: '',
  478. sdp: ''
  479. });
  480. this.signalingState = 'stable';
  481. this.iceConnectionState = 'new';
  482. this.iceOptions = {
  483. gatherPolicy: 'all',
  484. iceServers: []
  485. };
  486. if (config && config.iceTransportPolicy) {
  487. switch (config.iceTransportPolicy) {
  488. case 'all':
  489. case 'relay':
  490. this.iceOptions.gatherPolicy = config.iceTransportPolicy;
  491. break;
  492. case 'none':
  493. // FIXME: remove once implementation and spec have added this.
  494. throw new TypeError('iceTransportPolicy "none" not supported');
  495. }
  496. }
  497. if (config && config.iceServers) {
  498. this.iceOptions.iceServers = config.iceServers;
  499. }
  500. // per-track iceGathers etc
  501. this.mLines = [];
  502. this._iceCandidates = [];
  503. this._peerConnectionId = 'PC_' + Math.floor(Math.random() * 65536);
  504. // FIXME: Should be generated according to spec (guid?)
  505. // and be the same for all PCs from the same JS
  506. this._cname = Math.random().toString(36).substr(2, 10);
  507. };
  508. window.RTCPeerConnection.prototype.addStream = function(stream) {
  509. // clone just in case we're working in a local demo
  510. // FIXME: seems to be fixed
  511. this.localStreams.push(stream.clone());
  512. // FIXME: maybe trigger negotiationneeded?
  513. };
  514. window.RTCPeerConnection.prototype.removeStream = function(stream) {
  515. var idx = this.localStreams.indexOf(stream);
  516. if (idx > -1) {
  517. this.localStreams.splice(idx, 1);
  518. }
  519. // FIXME: maybe trigger negotiationneeded?
  520. };
  521. // SDP helper from sdp-jingle-json with modifications.
  522. window.RTCPeerConnection.prototype._toCandidateJSON = function(line) {
  523. var parts;
  524. if (line.indexOf('a=candidate:') === 0) {
  525. parts = line.substring(12).split(' ');
  526. } else { // no a=candidate
  527. parts = line.substring(10).split(' ');
  528. }
  529. var candidate = {
  530. foundation: parts[0],
  531. component: parts[1],
  532. protocol: parts[2].toLowerCase(),
  533. priority: parseInt(parts[3], 10),
  534. ip: parts[4],
  535. port: parseInt(parts[5], 10),
  536. // skip parts[6] == 'typ'
  537. type: parts[7]
  538. //generation: '0'
  539. };
  540. for (var i = 8; i < parts.length; i += 2) {
  541. if (parts[i] === 'raddr') {
  542. candidate.relatedAddress = parts[i + 1]; // was: relAddr
  543. } else if (parts[i] === 'rport') {
  544. candidate.relatedPort = parseInt(parts[i + 1], 10); // was: relPort
  545. } else if (parts[i] === 'generation') {
  546. candidate.generation = parts[i + 1];
  547. } else if (parts[i] === 'tcptype') {
  548. candidate.tcpType = parts[i + 1];
  549. }
  550. }
  551. return candidate;
  552. };
  553. // SDP helper from sdp-jingle-json with modifications.
  554. window.RTCPeerConnection.prototype._toCandidateSDP = function(candidate) {
  555. var sdp = [];
  556. sdp.push(candidate.foundation);
  557. sdp.push(candidate.component);
  558. sdp.push(candidate.protocol.toUpperCase());
  559. sdp.push(candidate.priority);
  560. sdp.push(candidate.ip);
  561. sdp.push(candidate.port);
  562. var type = candidate.type;
  563. sdp.push('typ');
  564. sdp.push(type);
  565. if (type === 'srflx' || type === 'prflx' || type === 'relay') {
  566. if (candidate.relatedAddress && candidate.relatedPort) {
  567. sdp.push('raddr');
  568. sdp.push(candidate.relatedAddress); // was: relAddr
  569. sdp.push('rport');
  570. sdp.push(candidate.relatedPort); // was: relPort
  571. }
  572. }
  573. if (candidate.tcpType && candidate.protocol.toUpperCase() === 'TCP') {
  574. sdp.push('tcptype');
  575. sdp.push(candidate.tcpType);
  576. }
  577. return 'a=candidate:' + sdp.join(' ');
  578. };
  579. // SDP helper from sdp-jingle-json with modifications.
  580. window.RTCPeerConnection.prototype._parseRtpMap = function(line) {
  581. var parts = line.substr(9).split(' ');
  582. var parsed = {
  583. payloadType: parseInt(parts.shift(), 10) // was: id
  584. };
  585. parts = parts[0].split('/');
  586. parsed.name = parts[0];
  587. parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
  588. parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels
  589. return parsed;
  590. };
  591. // Parses SDP to determine capabilities.
  592. window.RTCPeerConnection.prototype._getRemoteCapabilities =
  593. function(section) {
  594. var remoteCapabilities = {
  595. codecs: [],
  596. headerExtensions: [],
  597. fecMechanisms: []
  598. };
  599. var i;
  600. var lines = section.split('\r\n');
  601. var mline = lines[0].substr(2).split(' ');
  602. var rtpmapFilter = function(line) {
  603. return line.indexOf('a=rtpmap:' + mline[i]) === 0;
  604. };
  605. var fmtpFilter = function(line) {
  606. return line.indexOf('a=fmtp:' + mline[i]) === 0;
  607. };
  608. var parseFmtp = function(line) {
  609. var parsed = {};
  610. var kv;
  611. var parts = line.substr(('a=fmtp:' + mline[i]).length + 1).split(';');
  612. for (var j = 0; j < parts.length; j++) {
  613. kv = parts[j].split('=');
  614. parsed[kv[0].trim()] = kv[1];
  615. }
  616. console.log('fmtp', mline[i], parsed);
  617. return parsed;
  618. };
  619. var rtcpFbFilter = function(line) {
  620. return line.indexOf('a=rtcp-fb:' + mline[i]) === 0;
  621. };
  622. var parseRtcpFb = function(line) {
  623. var parts = line.substr(('a=rtcp-fb:' + mline[i]).length + 1)
  624. .split(' ');
  625. return {
  626. type: parts.shift(),
  627. parameter: parts.join(' ')
  628. };
  629. };
  630. for (i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
  631. var line = lines.filter(rtpmapFilter)[0];
  632. if (line) {
  633. var codec = this._parseRtpMap(line);
  634. var fmtp = lines.filter(fmtpFilter);
  635. codec.parameters = fmtp.length ? parseFmtp(fmtp[0]) : {};
  636. codec.rtcpFeedback = lines.filter(rtcpFbFilter).map(parseRtcpFb);
  637. remoteCapabilities.codecs.push(codec);
  638. }
  639. }
  640. return remoteCapabilities;
  641. };
  642. // Serializes capabilities to SDP.
  643. window.RTCPeerConnection.prototype._capabilitiesToSDP = function(caps) {
  644. var sdp = '';
  645. caps.codecs.forEach(function(codec) {
  646. var pt = codec.payloadType;
  647. if (codec.preferredPayloadType !== undefined) {
  648. pt = codec.preferredPayloadType;
  649. }
  650. sdp += 'a=rtpmap:' + pt +
  651. ' ' + codec.name +
  652. '/' + codec.clockRate +
  653. (codec.numChannels !== 1 ? '/' + codec.numChannels : '') +
  654. '\r\n';
  655. if (codec.parameters && codec.parameters.length) {
  656. sdp += 'a=ftmp:' + pt + ' ';
  657. Object.keys(codec.parameters).forEach(function(param) {
  658. sdp += param + '=' + codec.parameters[param];
  659. });
  660. sdp += '\r\n';
  661. }
  662. if (codec.rtcpFeedback) {
  663. // FIXME: special handling for trr-int?
  664. codec.rtcpFeedback.forEach(function(fb) {
  665. sdp += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' +
  666. fb.parameter + '\r\n';
  667. });
  668. }
  669. });
  670. return sdp;
  671. };
  672. // Calculates the intersection of local and remote capabilities.
  673. window.RTCPeerConnection.prototype._getCommonCapabilities =
  674. function(localCapabilities, remoteCapabilities) {
  675. var commonCapabilities = {
  676. codecs: [],
  677. headerExtensions: [],
  678. fecMechanisms: []
  679. };
  680. localCapabilities.codecs.forEach(function(lCodec) {
  681. for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
  682. var rCodec = remoteCapabilities.codecs[i];
  683. if (lCodec.name === rCodec.name &&
  684. lCodec.clockRate === rCodec.clockRate &&
  685. lCodec.numChannels === rCodec.numChannels) {
  686. // push rCodec so we reply with offerer payload type
  687. commonCapabilities.codecs.push(rCodec);
  688. // FIXME: also need to calculate intersection between
  689. // .rtcpFeedback and .parameters
  690. break;
  691. }
  692. }
  693. });
  694. localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
  695. for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) {
  696. var rHeaderExtension = remoteCapabilities.headerExtensions[i];
  697. if (lHeaderExtension.uri === rHeaderExtension.uri) {
  698. commonCapabilities.headerExtensions.push(rHeaderExtension);
  699. break;
  700. }
  701. }
  702. });
  703. // FIXME: fecMechanisms
  704. return commonCapabilities;
  705. };
  706. // Parses DTLS parameters from SDP section or sessionpart.
  707. window.RTCPeerConnection.prototype._getDtlsParameters =
  708. function(section, session) {
  709. var lines = section.split('\r\n');
  710. lines = lines.concat(session.split('\r\n')); // Search in session part, too.
  711. var fpLine = lines.filter(function(line) {
  712. return line.indexOf('a=fingerprint:') === 0;
  713. });
  714. fpLine = fpLine[0].substr(14);
  715. var dtlsParameters = {
  716. role: 'auto',
  717. fingerprints: [{
  718. algorithm: fpLine.split(' ')[0],
  719. value: fpLine.split(' ')[1]
  720. }]
  721. };
  722. return dtlsParameters;
  723. };
  724. // Serializes DTLS parameters to SDP.
  725. window.RTCPeerConnection.prototype._dtlsParametersToSDP =
  726. function(params, setupType) {
  727. var sdp = 'a=setup:' + setupType + '\r\n';
  728. params.fingerprints.forEach(function(fp) {
  729. sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
  730. });
  731. return sdp;
  732. };
  733. // Parses ICE information from SDP section or sessionpart.
  734. window.RTCPeerConnection.prototype._getIceParameters =
  735. function(section, session) {
  736. var lines = section.split('\r\n');
  737. lines = lines.concat(session.split('\r\n')); // Search in session part, too.
  738. var iceParameters = {
  739. usernameFragment: lines.filter(function(line) {
  740. return line.indexOf('a=ice-ufrag:') === 0;
  741. })[0].substr(12),
  742. password: lines.filter(function(line) {
  743. return line.indexOf('a=ice-pwd:') === 0;
  744. })[0].substr(10),
  745. };
  746. return iceParameters;
  747. };
  748. // Serializes ICE parameters to SDP.
  749. window.RTCPeerConnection.prototype._iceParametersToSDP = function(params) {
  750. return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
  751. 'a=ice-pwd:' + params.password + '\r\n';
  752. };
  753. window.RTCPeerConnection.prototype._getEncodingParameters = function(ssrc) {
  754. return {
  755. ssrc: ssrc,
  756. codecPayloadType: 0,
  757. fec: 0,
  758. rtx: 0,
  759. priority: 1.0,
  760. maxBitrate: 2000000.0,
  761. minQuality: 0,
  762. framerateBias: 0.5,
  763. resolutionScale: 1.0,
  764. framerateScale: 1.0,
  765. active: true,
  766. dependencyEncodingId: undefined,
  767. encodingId: undefined
  768. };
  769. };
  770. // Create ICE gatherer, ICE transport and DTLS transport.
  771. window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
  772. function(mid, sdpMLineIndex) {
  773. var self = this;
  774. var iceGatherer = new RTCIceGatherer(self.iceOptions);
  775. var iceTransport = new RTCIceTransport(iceGatherer);
  776. iceGatherer.onlocalcandidate = function(evt) {
  777. var event = {};
  778. event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
  779. var cand = evt.candidate;
  780. var isEndOfCandidates = !(cand && Object.keys(cand).length > 0);
  781. if (isEndOfCandidates) {
  782. event.candidate.candidate =
  783. 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
  784. } else {
  785. // RTCIceCandidate doesn't have a component, needs to be added
  786. cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
  787. event.candidate.candidate = self._toCandidateSDP(cand);
  788. }
  789. if (self.onicecandidate !== null) {
  790. if (self.localDescription && self.localDescription.type === '') {
  791. self._iceCandidates.push(event);
  792. } else {
  793. self.onicecandidate(event);
  794. }
  795. }
  796. };
  797. iceTransport.onicestatechange = function() {
  798. /*
  799. console.log(self._peerConnectionId,
  800. 'ICE state change', iceTransport.state);
  801. */
  802. self._updateIceConnectionState(iceTransport.state);
  803. };
  804. var dtlsTransport = new RTCDtlsTransport(iceTransport);
  805. dtlsTransport.ondtlsstatechange = function() {
  806. /*
  807. console.log(self._peerConnectionId, sdpMLineIndex,
  808. 'dtls state change', dtlsTransport.state);
  809. */
  810. };
  811. dtlsTransport.onerror = function(error) {
  812. console.error('dtls error', error);
  813. };
  814. return {
  815. iceGatherer: iceGatherer,
  816. iceTransport: iceTransport,
  817. dtlsTransport: dtlsTransport
  818. };
  819. };
  820. window.RTCPeerConnection.prototype.setLocalDescription =
  821. function(description) {
  822. var self = this;
  823. if (description.type === 'offer') {
  824. if (!description.ortc) {
  825. // FIXME: throw?
  826. } else {
  827. this.mLines = description.ortc;
  828. }
  829. } else if (description.type === 'answer') {
  830. var sections = self.remoteDescription.sdp.split('\r\nm=');
  831. var sessionpart = sections.shift();
  832. sections.forEach(function(section, sdpMLineIndex) {
  833. section = 'm=' + section;
  834. var iceGatherer = self.mLines[sdpMLineIndex].iceGatherer;
  835. var iceTransport = self.mLines[sdpMLineIndex].iceTransport;
  836. var dtlsTransport = self.mLines[sdpMLineIndex].dtlsTransport;
  837. var rtpSender = self.mLines[sdpMLineIndex].rtpSender;
  838. var localCapabilities =
  839. self.mLines[sdpMLineIndex].localCapabilities;
  840. var remoteCapabilities =
  841. self.mLines[sdpMLineIndex].remoteCapabilities;
  842. var sendSSRC = self.mLines[sdpMLineIndex].sendSSRC;
  843. var recvSSRC = self.mLines[sdpMLineIndex].recvSSRC;
  844. var remoteIceParameters = self._getIceParameters(section,
  845. sessionpart);
  846. iceTransport.start(iceGatherer, remoteIceParameters, 'controlled');
  847. var remoteDtlsParameters = self._getDtlsParameters(section,
  848. sessionpart);
  849. dtlsTransport.start(remoteDtlsParameters);
  850. if (rtpSender) {
  851. // calculate intersection of capabilities
  852. var params = self._getCommonCapabilities(localCapabilities,
  853. remoteCapabilities);
  854. params.muxId = sendSSRC;
  855. params.encodings = [self._getEncodingParameters(sendSSRC)];
  856. params.rtcp = {
  857. cname: self._cname,
  858. reducedSize: false,
  859. ssrc: recvSSRC,
  860. mux: true
  861. };
  862. rtpSender.send(params);
  863. }
  864. });
  865. }
  866. this.localDescription = description;
  867. switch (description.type) {
  868. case 'offer':
  869. this._updateSignalingState('have-local-offer');
  870. break;
  871. case 'answer':
  872. this._updateSignalingState('stable');
  873. break;
  874. }
  875. // FIXME: need to _reliably_ execute after args[1] or promise
  876. window.setTimeout(function() {
  877. // FIXME: need to apply ice candidates in a way which is async but in-order
  878. self._iceCandidates.forEach(function(event) {
  879. if (self.onicecandidate !== null) {
  880. self.onicecandidate(event);
  881. }
  882. });
  883. self._iceCandidates = [];
  884. }, 50);
  885. if (arguments.length > 1 && typeof arguments[1] === 'function') {
  886. window.setTimeout(arguments[1], 0);
  887. }
  888. return new Promise(function(resolve) {
  889. resolve();
  890. });
  891. };
  892. window.RTCPeerConnection.prototype.setRemoteDescription =
  893. function(description) {
  894. // FIXME: for type=offer this creates state. which should not
  895. // happen before SLD with type=answer but... we need the stream
  896. // here for onaddstream.
  897. var self = this;
  898. var sections = description.sdp.split('\r\nm=');
  899. var sessionpart = sections.shift();
  900. var stream = new MediaStream();
  901. sections.forEach(function(section, sdpMLineIndex) {
  902. section = 'm=' + section;
  903. var lines = section.split('\r\n');
  904. var mline = lines[0].substr(2).split(' ');
  905. var kind = mline[0];
  906. var line;
  907. var iceGatherer;
  908. var iceTransport;
  909. var dtlsTransport;
  910. var rtpSender;
  911. var rtpReceiver;
  912. var sendSSRC;
  913. var recvSSRC;
  914. var mid = lines.filter(function(line) {
  915. return line.indexOf('a=mid:') === 0;
  916. })[0].substr(6);
  917. var cname;
  918. var remoteCapabilities;
  919. var params;
  920. if (description.type === 'offer') {
  921. var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  922. var localCapabilities = RTCRtpReceiver.getCapabilities(kind);
  923. // determine remote caps from SDP
  924. remoteCapabilities = self._getRemoteCapabilities(section);
  925. line = lines.filter(function(line) {
  926. return line.indexOf('a=ssrc:') === 0 &&
  927. line.split(' ')[1].indexOf('cname:') === 0;
  928. });
  929. sendSSRC = (2 * sdpMLineIndex + 2) * 1001;
  930. if (line) { // FIXME: alot of assumptions here
  931. recvSSRC = line[0].split(' ')[0].split(':')[1];
  932. cname = line[0].split(' ')[1].split(':')[1];
  933. }
  934. rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  935. // calculate intersection so no unknown caps get passed into the RTPReciver
  936. params = self._getCommonCapabilities(localCapabilities,
  937. remoteCapabilities);
  938. params.muxId = recvSSRC;
  939. params.encodings = [self._getEncodingParameters(recvSSRC)];
  940. params.rtcp = {
  941. cname: cname,
  942. reducedSize: false,
  943. ssrc: sendSSRC,
  944. mux: true
  945. };
  946. rtpReceiver.receive(params);
  947. // FIXME: not correct when there are multiple streams but that is
  948. // not currently supported.
  949. stream.addTrack(rtpReceiver.track);
  950. // FIXME: honor a=sendrecv
  951. if (self.localStreams.length > 0 &&
  952. self.localStreams[0].getTracks().length >= sdpMLineIndex) {
  953. // FIXME: actually more complicated, needs to match types etc
  954. var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex];
  955. rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport);
  956. }
  957. self.mLines[sdpMLineIndex] = {
  958. iceGatherer: transports.iceGatherer,
  959. iceTransport: transports.iceTransport,
  960. dtlsTransport: transports.dtlsTransport,
  961. localCapabilities: localCapabilities,
  962. remoteCapabilities: remoteCapabilities,
  963. rtpSender: rtpSender,
  964. rtpReceiver: rtpReceiver,
  965. kind: kind,
  966. mid: mid,
  967. sendSSRC: sendSSRC,
  968. recvSSRC: recvSSRC
  969. };
  970. } else {
  971. iceGatherer = self.mLines[sdpMLineIndex].iceGatherer;
  972. iceTransport = self.mLines[sdpMLineIndex].iceTransport;
  973. dtlsTransport = self.mLines[sdpMLineIndex].dtlsTransport;
  974. rtpSender = self.mLines[sdpMLineIndex].rtpSender;
  975. rtpReceiver = self.mLines[sdpMLineIndex].rtpReceiver;
  976. sendSSRC = self.mLines[sdpMLineIndex].sendSSRC;
  977. recvSSRC = self.mLines[sdpMLineIndex].recvSSRC;
  978. }
  979. var remoteIceParameters = self._getIceParameters(section, sessionpart);
  980. var remoteDtlsParameters = self._getDtlsParameters(section,
  981. sessionpart);
  982. // for answers we start ice and dtls here, otherwise this is done in SLD
  983. if (description.type === 'answer') {
  984. iceTransport.start(iceGatherer, remoteIceParameters, 'controlling');
  985. dtlsTransport.start(remoteDtlsParameters);
  986. // determine remote caps from SDP
  987. remoteCapabilities = self._getRemoteCapabilities(section);
  988. // FIXME: store remote caps?
  989. if (rtpSender) {
  990. params = remoteCapabilities;
  991. params.muxId = sendSSRC;
  992. params.encodings = [self._getEncodingParameters(sendSSRC)];
  993. params.rtcp = {
  994. cname: self._cname,
  995. reducedSize: false,
  996. ssrc: recvSSRC,
  997. mux: true
  998. };
  999. rtpSender.send(params);
  1000. }
  1001. // FIXME: only if a=sendrecv
  1002. var bidi = lines.filter(function(line) {
  1003. return line.indexOf('a=ssrc:') === 0;
  1004. }).length > 0;
  1005. if (rtpReceiver && bidi) {
  1006. line = lines.filter(function(line) {
  1007. return line.indexOf('a=ssrc:') === 0 &&
  1008. line.split(' ')[1].indexOf('cname:') === 0;
  1009. });
  1010. if (line) { // FIXME: alot of assumptions here
  1011. recvSSRC = line[0].split(' ')[0].split(':')[1];
  1012. cname = line[0].split(' ')[1].split(':')[1];
  1013. }
  1014. params = remoteCapabilities;
  1015. params.muxId = recvSSRC;
  1016. params.encodings = [self._getEncodingParameters(recvSSRC)];
  1017. params.rtcp = {
  1018. cname: cname,
  1019. reducedSize: false,
  1020. ssrc: sendSSRC,
  1021. mux: true
  1022. };
  1023. rtpReceiver.receive(params, kind);
  1024. stream.addTrack(rtpReceiver.track);
  1025. self.mLines[sdpMLineIndex].recvSSRC = recvSSRC;
  1026. }
  1027. }
  1028. });
  1029. this.remoteDescription = description;
  1030. switch (description.type) {
  1031. case 'offer':
  1032. this._updateSignalingState('have-remote-offer');
  1033. break;
  1034. case 'answer':
  1035. this._updateSignalingState('stable');
  1036. break;
  1037. }
  1038. window.setTimeout(function() {
  1039. if (self.onaddstream !== null && stream.getTracks().length) {
  1040. self.remoteStreams.push(stream);
  1041. window.setTimeout(function() {
  1042. self.onaddstream({stream: stream});
  1043. }, 0);
  1044. }
  1045. }, 0);
  1046. if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1047. window.setTimeout(arguments[1], 0);
  1048. }
  1049. return new Promise(function(resolve) {
  1050. resolve();
  1051. });
  1052. };
  1053. window.RTCPeerConnection.prototype.close = function() {
  1054. this.mLines.forEach(function(mLine) {
  1055. /* not yet
  1056. if (mLine.iceGatherer) {
  1057. mLine.iceGatherer.close();
  1058. }
  1059. */
  1060. if (mLine.iceTransport) {
  1061. mLine.iceTransport.stop();
  1062. }
  1063. if (mLine.dtlsTransport) {
  1064. mLine.dtlsTransport.stop();
  1065. }
  1066. if (mLine.rtpSender) {
  1067. mLine.rtpSender.stop();
  1068. }
  1069. if (mLine.rtpReceiver) {
  1070. mLine.rtpReceiver.stop();
  1071. }
  1072. });
  1073. // FIXME: clean up tracks, local streams, remote streams, etc
  1074. this._updateSignalingState('closed');
  1075. this._updateIceConnectionState('closed');
  1076. };
  1077. // Update the signaling state.
  1078. window.RTCPeerConnection.prototype._updateSignalingState =
  1079. function(newState) {
  1080. this.signalingState = newState;
  1081. if (this.onsignalingstatechange !== null) {
  1082. this.onsignalingstatechange();
  1083. }
  1084. };
  1085. // Update the ICE connection state.
  1086. // FIXME: should be called 'updateConnectionState', also be called for
  1087. // DTLS changes and implement
  1088. // https://lists.w3.org/Archives/Public/public-webrtc/2015Sep/0033.html
  1089. window.RTCPeerConnection.prototype._updateIceConnectionState =
  1090. function(newState) {
  1091. var self = this;
  1092. if (this.iceConnectionState !== newState) {
  1093. var agreement = self.mLines.every(function(mLine) {
  1094. return mLine.iceTransport.state === newState;
  1095. });
  1096. if (agreement) {
  1097. self.iceConnectionState = newState;
  1098. if (this.oniceconnectionstatechange !== null) {
  1099. this.oniceconnectionstatechange();
  1100. }
  1101. }
  1102. }
  1103. };
  1104. window.RTCPeerConnection.prototype.createOffer = function() {
  1105. var self = this;
  1106. var offerOptions;
  1107. if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1108. offerOptions = arguments[0];
  1109. } else if (arguments.length === 3) {
  1110. offerOptions = arguments[2];
  1111. }
  1112. var tracks = [];
  1113. var numAudioTracks = 0;
  1114. var numVideoTracks = 0;
  1115. // Default to sendrecv.
  1116. if (this.localStreams.length) {
  1117. numAudioTracks = this.localStreams[0].getAudioTracks().length;
  1118. numVideoTracks = this.localStreams[0].getAudioTracks().length;
  1119. }
  1120. // Determine number of audio and video tracks we need to send/recv.
  1121. if (offerOptions) {
  1122. // Deal with Chrome legacy constraints...
  1123. if (offerOptions.mandatory) {
  1124. if (offerOptions.mandatory.OfferToReceiveAudio) {
  1125. numAudioTracks = 1;
  1126. } else if (offerOptions.mandatory.OfferToReceiveAudio === false) {
  1127. numAudioTracks = 0;
  1128. }
  1129. if (offerOptions.mandatory.OfferToReceiveVideo) {
  1130. numVideoTracks = 1;
  1131. } else if (offerOptions.mandatory.OfferToReceiveVideo === false) {
  1132. numVideoTracks = 0;
  1133. }
  1134. } else {
  1135. if (offerOptions.offerToReceiveAudio !== undefined) {
  1136. numAudioTracks = offerOptions.offerToReceiveAudio;
  1137. }
  1138. if (offerOptions.offerToReceiveVideo !== undefined) {
  1139. numVideoTracks = offerOptions.offerToReceiveVideo;
  1140. }
  1141. }
  1142. }
  1143. if (this.localStreams.length) {
  1144. // Push local streams.
  1145. this.localStreams[0].getTracks().forEach(function(track) {
  1146. tracks.push({
  1147. kind: track.kind,
  1148. track: track,
  1149. wantReceive: track.kind === 'audio' ?
  1150. numAudioTracks > 0 : numVideoTracks > 0
  1151. });
  1152. if (track.kind === 'audio') {
  1153. numAudioTracks--;
  1154. } else if (track.kind === 'video') {
  1155. numVideoTracks--;
  1156. }
  1157. });
  1158. }
  1159. // Create M-lines for recvonly streams.
  1160. while (numAudioTracks > 0 || numVideoTracks > 0) {
  1161. if (numAudioTracks > 0) {
  1162. tracks.push({
  1163. kind: 'audio',
  1164. wantReceive: true
  1165. });
  1166. numAudioTracks--;
  1167. }
  1168. if (numVideoTracks > 0) {
  1169. tracks.push({
  1170. kind: 'video',
  1171. wantReceive: true
  1172. });
  1173. numVideoTracks--;
  1174. }
  1175. }
  1176. var sdp = 'v=0\r\n' +
  1177. 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
  1178. 's=-\r\n' +
  1179. 't=0 0\r\n';
  1180. var mLines = [];
  1181. tracks.forEach(function(mline, sdpMLineIndex) {
  1182. // For each track, create an ice gatherer, ice transport, dtls transport,
  1183. // potentially rtpsender and rtpreceiver.
  1184. var track = mline.track;
  1185. var kind = mline.kind;
  1186. var mid = Math.random().toString(36).substr(2, 10);
  1187. var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  1188. var localCapabilities = RTCRtpSender.getCapabilities(kind);
  1189. var rtpSender;
  1190. // generate an ssrc now, to be used later in rtpSender.send
  1191. var sendSSRC = (2 * sdpMLineIndex + 1) * 1001; //Math.floor(Math.random()*4294967295);
  1192. var recvSSRC; // don't know yet
  1193. if (track) {
  1194. rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
  1195. }
  1196. var rtpReceiver;
  1197. if (mline.wantReceive) {
  1198. rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  1199. }
  1200. mLines[sdpMLineIndex] = {
  1201. iceGatherer: transports.iceGatherer,
  1202. iceTransport: transports.iceTransport,
  1203. dtlsTransport: transports.dtlsTransport,
  1204. localCapabilities: localCapabilities,
  1205. remoteCapabilities: null,
  1206. rtpSender: rtpSender,
  1207. rtpReceiver: rtpReceiver,
  1208. kind: kind,
  1209. mid: mid,
  1210. sendSSRC: sendSSRC,
  1211. recvSSRC: recvSSRC
  1212. };
  1213. // Map things to SDP.
  1214. // Build the mline.
  1215. sdp += 'm=' + kind + ' 9 UDP/TLS/RTP/SAVPF ';
  1216. sdp += localCapabilities.codecs.map(function(codec) {
  1217. return codec.preferredPayloadType;
  1218. }).join(' ') + '\r\n';
  1219. sdp += 'c=IN IP4 0.0.0.0\r\n';
  1220. sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
  1221. // Map ICE parameters (ufrag, pwd) to SDP.
  1222. sdp += self._iceParametersToSDP(
  1223. transports.iceGatherer.getLocalParameters());
  1224. // Map DTLS parameters to SDP.
  1225. sdp += self._dtlsParametersToSDP(
  1226. transports.dtlsTransport.getLocalParameters(), 'actpass');
  1227. sdp += 'a=mid:' + mid + '\r\n';
  1228. if (rtpSender && rtpReceiver) {
  1229. sdp += 'a=sendrecv\r\n';
  1230. } else if (rtpSender) {
  1231. sdp += 'a=sendonly\r\n';
  1232. } else if (rtpReceiver) {
  1233. sdp += 'a=recvonly\r\n';
  1234. } else {
  1235. sdp += 'a=inactive\r\n';
  1236. }
  1237. sdp += 'a=rtcp-mux\r\n';
  1238. // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
  1239. sdp += self._capabilitiesToSDP(localCapabilities);
  1240. if (track) {
  1241. sdp += 'a=msid:' + self.localStreams[0].id + ' ' + track.id + '\r\n';
  1242. sdp += 'a=ssrc:' + sendSSRC + ' ' + 'msid:' +
  1243. self.localStreams[0].id + ' ' + track.id + '\r\n';
  1244. }
  1245. sdp += 'a=ssrc:' + sendSSRC + ' cname:' + self._cname + '\r\n';
  1246. });
  1247. var desc = new RTCSessionDescription({
  1248. type: 'offer',
  1249. sdp: sdp,
  1250. ortc: mLines
  1251. });
  1252. if (arguments.length && typeof arguments[0] === 'function') {
  1253. window.setTimeout(arguments[0], 0, desc);
  1254. }
  1255. return new Promise(function(resolve) {
  1256. resolve(desc);
  1257. });
  1258. };
  1259. window.RTCPeerConnection.prototype.createAnswer = function() {
  1260. var self = this;
  1261. var answerOptions;
  1262. if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1263. answerOptions = arguments[0];
  1264. } else if (arguments.length === 3) {
  1265. answerOptions = arguments[2];
  1266. }
  1267. var sdp = 'v=0\r\n' +
  1268. 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
  1269. 's=-\r\n' +
  1270. 't=0 0\r\n';
  1271. this.mLines.forEach(function(mLine/*, sdpMLineIndex*/) {
  1272. var iceGatherer = mLine.iceGatherer;
  1273. //var iceTransport = mLine.iceTransport;
  1274. var dtlsTransport = mLine.dtlsTransport;
  1275. var localCapabilities = mLine.localCapabilities;
  1276. var remoteCapabilities = mLine.remoteCapabilities;
  1277. var rtpSender = mLine.rtpSender;
  1278. var rtpReceiver = mLine.rtpReceiver;
  1279. var kind = mLine.kind;
  1280. var sendSSRC = mLine.sendSSRC;
  1281. //var recvSSRC = mLine.recvSSRC;
  1282. // Calculate intersection of capabilities.
  1283. var commonCapabilities = self._getCommonCapabilities(localCapabilities,
  1284. remoteCapabilities);
  1285. // Map things to SDP.
  1286. // Build the mline.
  1287. sdp += 'm=' + kind + ' 9 UDP/TLS/RTP/SAVPF ';
  1288. sdp += commonCapabilities.codecs.map(function(codec) {
  1289. return codec.payloadType;
  1290. }).join(' ') + '\r\n';
  1291. sdp += 'c=IN IP4 0.0.0.0\r\n';
  1292. sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
  1293. // Map ICE parameters (ufrag, pwd) to SDP.
  1294. sdp += self._iceParametersToSDP(iceGatherer.getLocalParameters());
  1295. // Map DTLS parameters to SDP.
  1296. sdp += self._dtlsParametersToSDP(dtlsTransport.getLocalParameters(),
  1297. 'active');
  1298. sdp += 'a=mid:' + mLine.mid + '\r\n';
  1299. if (rtpSender && rtpReceiver) {
  1300. sdp += 'a=sendrecv\r\n';
  1301. } else if (rtpReceiver) {
  1302. sdp += 'a=sendonly\r\n';
  1303. } else if (rtpSender) {
  1304. sdp += 'a=sendonly\r\n';
  1305. } else {
  1306. sdp += 'a=inactive\r\n';
  1307. }
  1308. sdp += 'a=rtcp-mux\r\n';
  1309. // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
  1310. sdp += self._capabilitiesToSDP(commonCapabilities);
  1311. if (rtpSender) {
  1312. // add a=ssrc lines from RTPSender
  1313. sdp += 'a=msid:' + self.localStreams[0].id + ' ' +
  1314. rtpSender.track.id + '\r\n';
  1315. sdp += 'a=ssrc:' + sendSSRC + ' ' + 'msid:' +
  1316. self.localStreams[0].id + ' ' + rtpSender.track.id + '\r\n';
  1317. }
  1318. sdp += 'a=ssrc:' + sendSSRC + ' cname:' + self._cname + '\r\n';
  1319. });
  1320. var desc = new RTCSessionDescription({
  1321. type: 'answer',
  1322. sdp: sdp
  1323. // ortc: tracks -- state is created in SRD already
  1324. });
  1325. if (arguments.length && typeof arguments[0] === 'function') {
  1326. window.setTimeout(arguments[0], 0, desc);
  1327. }
  1328. return new Promise(function(resolve) {
  1329. resolve(desc);
  1330. });
  1331. };
  1332. window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
  1333. // TODO: lookup by mid
  1334. var mLine = this.mLines[candidate.sdpMLineIndex];
  1335. if (mLine) {
  1336. var cand = Object.keys(candidate.candidate).length > 0 ?
  1337. this._toCandidateJSON(candidate.candidate) : {};
  1338. // dirty hack to make simplewebrtc work.
  1339. // FIXME: need another dirty hack to avoid adding candidates after this
  1340. if (cand.type === 'endOfCandidates') {
  1341. cand = {};
  1342. }
  1343. // dirty hack to make chrome work.
  1344. if (cand.protocol === 'tcp' && cand.port === 0) {
  1345. cand = {};
  1346. }
  1347. mLine.iceTransport.addRemoteCandidate(cand);
  1348. }
  1349. if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1350. window.setTimeout(arguments[1], 0);
  1351. }
  1352. return new Promise(function(resolve) {
  1353. resolve();
  1354. });
  1355. };
  1356. window.RTCPeerConnection.prototype.getStats = function() {
  1357. var promises = [];
  1358. this.mLines.forEach(function(mLine) {
  1359. ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
  1360. 'dtlsTransport'].forEach(function(thing) {
  1361. if (mLine[thing]) {
  1362. promises.push(mLine[thing].getStats());
  1363. }
  1364. });
  1365. });
  1366. var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
  1367. arguments[1];
  1368. return new Promise(function(resolve) {
  1369. var results = {};
  1370. Promise.all(promises).then(function(res) {
  1371. res.forEach(function(result) {
  1372. Object.keys(result).forEach(function(id) {
  1373. results[id] = result[id];
  1374. });
  1375. });
  1376. if (cb) {
  1377. window.setTimeout(cb, 0, results);
  1378. }
  1379. resolve(results);
  1380. });
  1381. });
  1382. };
  1383. }
  1384. } else {
  1385. webrtcUtils.log('Browser does not appear to be WebRTC-capable');
  1386. }
  1387. // Returns the result of getUserMedia as a Promise.
  1388. function requestUserMedia(constraints) {
  1389. return new Promise(function(resolve, reject) {
  1390. getUserMedia(constraints, resolve, reject);
  1391. });
  1392. }
  1393. var webrtcTesting = {};
  1394. try {
  1395. Object.defineProperty(webrtcTesting, 'version', {
  1396. set: function(version) {
  1397. webrtcDetectedVersion = version;
  1398. }
  1399. });
  1400. } catch (e) {}
  1401. if (typeof module !== 'undefined') {
  1402. var RTCPeerConnection;
  1403. if (typeof window !== 'undefined') {
  1404. RTCPeerConnection = window.RTCPeerConnection;
  1405. }
  1406. module.exports = {
  1407. RTCPeerConnection: RTCPeerConnection,
  1408. getUserMedia: getUserMedia,
  1409. attachMediaStream: attachMediaStream,
  1410. reattachMediaStream: reattachMediaStream,
  1411. webrtcDetectedBrowser: webrtcDetectedBrowser,
  1412. webrtcDetectedVersion: webrtcDetectedVersion,
  1413. webrtcMinimumVersion: webrtcMinimumVersion,
  1414. webrtcTesting: webrtcTesting,
  1415. webrtcUtils: webrtcUtils
  1416. //requestUserMedia: not exposed on purpose.
  1417. //trace: not exposed on purpose.
  1418. };
  1419. } else if ((typeof require === 'function') && (typeof define === 'function')) {
  1420. // Expose objects and functions when RequireJS is doing the loading.
  1421. define([], function() {
  1422. return {
  1423. RTCPeerConnection: window.RTCPeerConnection,
  1424. getUserMedia: getUserMedia,
  1425. attachMediaStream: attachMediaStream,
  1426. reattachMediaStream: reattachMediaStream,
  1427. webrtcDetectedBrowser: webrtcDetectedBrowser,
  1428. webrtcDetectedVersion: webrtcDetectedVersion,
  1429. webrtcMinimumVersion: webrtcMinimumVersion,
  1430. webrtcTesting: webrtcTesting,
  1431. webrtcUtils: webrtcUtils
  1432. //requestUserMedia: not exposed on purpose.
  1433. //trace: not exposed on purpose.
  1434. };
  1435. });
  1436. }