Advertisement
Guest User

Untitled

a guest
Jul 14th, 2017
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 62.58 KB | None | 0 0
  1. /*
  2. * Copyright (c) 2016 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.  
  9. /* More information about these options at jshint.com/docs/options */
  10. /* jshint browser: true, camelcase: true, curly: true, devel: true,
  11. eqeqeq: true, forin: false, globalstrict: true, node: true,
  12. quotmark: single, undef: true, unused: strict */
  13. /* global mozRTCIceCandidate, mozRTCPeerConnection, Promise,
  14. mozRTCSessionDescription, webkitRTCPeerConnection, MediaStreamTrack,
  15. MediaStream, RTCIceGatherer, RTCIceTransport, RTCDtlsTransport,
  16. RTCRtpSender, RTCRtpReceiver*/
  17. /* exported trace,requestUserMedia */
  18.  
  19. 'use strict';
  20.  
  21. var getUserMedia = null;
  22. var attachMediaStream = null;
  23. var reattachMediaStream = null;
  24. var webrtcDetectedBrowser = null;
  25. var webrtcDetectedVersion = null;
  26. var webrtcMinimumVersion = null;
  27. var webrtcUtils = {
  28. log: function() {
  29. // suppress console.log output when being included as a module.
  30. if (typeof module !== 'undefined' ||
  31. typeof require === 'function' && typeof define === 'function') {
  32. return;
  33. }
  34. console.log.apply(console, arguments);
  35. },
  36. extractVersion: function(uastring, expr, pos) {
  37. var match = uastring.match(expr);
  38. return match && match.length >= pos && parseInt(match[pos], 10);
  39. }
  40. };
  41.  
  42. function trace(text) {
  43. // This function is used for logging.
  44. if (text[text.length - 1] === '\n') {
  45. text = text.substring(0, text.length - 1);
  46. }
  47. if (window.performance) {
  48. var now = (window.performance.now() / 1000).toFixed(3);
  49. webrtcUtils.log(now + ': ' + text);
  50. } else {
  51. webrtcUtils.log(text);
  52. }
  53. }
  54.  
  55. if (typeof window === 'object') {
  56. if (window.HTMLMediaElement &&
  57. !('srcObject' in window.HTMLMediaElement.prototype)) {
  58. // Shim the srcObject property, once, when HTMLMediaElement is found.
  59. Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
  60. get: function() {
  61. // If prefixed srcObject property exists, return it.
  62. // Otherwise use the shimmed property, _srcObject
  63. return 'mozSrcObject' in this ? this.mozSrcObject : this._srcObject;
  64. },
  65. set: function(stream) {
  66. if ('mozSrcObject' in this) {
  67. this.mozSrcObject = stream;
  68. } else {
  69. // Use _srcObject as a private property for this shim
  70. this._srcObject = stream;
  71. // TODO: revokeObjectUrl(this.src) when !stream to release resources?
  72. this.src = URL.createObjectURL(stream);
  73. }
  74. }
  75. });
  76. }
  77. // Proxy existing globals
  78. getUserMedia = window.navigator && window.navigator.getUserMedia;
  79. }
  80.  
  81. // Attach a media stream to an element.
  82. attachMediaStream = function(element, stream) {
  83. element.srcObject = stream;
  84. };
  85.  
  86. reattachMediaStream = function(to, from) {
  87. to.srcObject = from.srcObject;
  88. };
  89.  
  90. if (typeof window === 'undefined' || !window.navigator) {
  91. webrtcUtils.log('This does not appear to be a browser');
  92. webrtcDetectedBrowser = 'not a browser';
  93. } else if (navigator.mozGetUserMedia) {
  94. webrtcUtils.log('This appears to be Firefox');
  95.  
  96. webrtcDetectedBrowser = 'firefox';
  97.  
  98. // the detected firefox version.
  99. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  100. /Firefox\/([0-9]+)\./, 1);
  101.  
  102. // the minimum firefox version still supported by adapter.
  103. webrtcMinimumVersion = 31;
  104.  
  105. // Shim for RTCPeerConnection on older versions.
  106. if (!window.RTCPeerConnection) {
  107. window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  108. if (webrtcDetectedVersion < 38) {
  109. // .urls is not supported in FF < 38.
  110. // create RTCIceServers with a single url.
  111. if (pcConfig && pcConfig.iceServers) {
  112. var newIceServers = [];
  113. for (var i = 0; i < pcConfig.iceServers.length; i++) {
  114. var server = pcConfig.iceServers[i];
  115. if (server.hasOwnProperty('urls')) {
  116. for (var j = 0; j < server.urls.length; j++) {
  117. var newServer = {
  118. url: server.urls[j]
  119. };
  120. if (server.urls[j].indexOf('turn') === 0) {
  121. newServer.username = server.username;
  122. newServer.credential = server.credential;
  123. }
  124. newIceServers.push(newServer);
  125. }
  126. } else {
  127. newIceServers.push(pcConfig.iceServers[i]);
  128. }
  129. }
  130. pcConfig.iceServers = newIceServers;
  131. }
  132. }
  133. return new mozRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  134. };
  135. window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
  136.  
  137. // wrap static methods. Currently just generateCertificate.
  138. if (mozRTCPeerConnection.generateCertificate) {
  139. Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
  140. get: function() {
  141. if (arguments.length) {
  142. return mozRTCPeerConnection.generateCertificate.apply(null,
  143. arguments);
  144. } else {
  145. return mozRTCPeerConnection.generateCertificate;
  146. }
  147. }
  148. });
  149. }
  150.  
  151. window.RTCSessionDescription = mozRTCSessionDescription;
  152. window.RTCIceCandidate = mozRTCIceCandidate;
  153. }
  154.  
  155. // getUserMedia constraints shim.
  156. getUserMedia = function(constraints, onSuccess, onError) {
  157. var constraintsToFF37 = function(c) {
  158. if (typeof c !== 'object' || c.require) {
  159. return c;
  160. }
  161. var require = [];
  162. Object.keys(c).forEach(function(key) {
  163. if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  164. return;
  165. }
  166. var r = c[key] = (typeof c[key] === 'object') ?
  167. c[key] : {ideal: c[key]};
  168. if (r.min !== undefined ||
  169. r.max !== undefined || r.exact !== undefined) {
  170. require.push(key);
  171. }
  172. if (r.exact !== undefined) {
  173. if (typeof r.exact === 'number') {
  174. r.min = r.max = r.exact;
  175. } else {
  176. c[key] = r.exact;
  177. }
  178. delete r.exact;
  179. }
  180. if (r.ideal !== undefined) {
  181. c.advanced = c.advanced || [];
  182. var oc = {};
  183. if (typeof r.ideal === 'number') {
  184. oc[key] = {min: r.ideal, max: r.ideal};
  185. } else {
  186. oc[key] = r.ideal;
  187. }
  188. c.advanced.push(oc);
  189. delete r.ideal;
  190. if (!Object.keys(r).length) {
  191. delete c[key];
  192. }
  193. }
  194. });
  195. if (require.length) {
  196. c.require = require;
  197. }
  198. return c;
  199. };
  200. if (webrtcDetectedVersion < 38) {
  201. webrtcUtils.log('spec: ' + JSON.stringify(constraints));
  202. if (constraints.audio) {
  203. constraints.audio = constraintsToFF37(constraints.audio);
  204. }
  205. if (constraints.video) {
  206. constraints.video = constraintsToFF37(constraints.video);
  207. }
  208. webrtcUtils.log('ff37: ' + JSON.stringify(constraints));
  209. }
  210. return navigator.mozGetUserMedia(constraints, onSuccess, onError);
  211. };
  212.  
  213. navigator.getUserMedia = getUserMedia;
  214.  
  215. // Shim for mediaDevices on older versions.
  216. if (!navigator.mediaDevices) {
  217. navigator.mediaDevices = {getUserMedia: requestUserMedia,
  218. addEventListener: function() { },
  219. removeEventListener: function() { }
  220. };
  221. }
  222. navigator.mediaDevices.enumerateDevices =
  223. navigator.mediaDevices.enumerateDevices || function() {
  224. return new Promise(function(resolve) {
  225. var infos = [
  226. {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
  227. {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
  228. ];
  229. resolve(infos);
  230. });
  231. };
  232.  
  233. if (webrtcDetectedVersion < 41) {
  234. // Work around http://bugzil.la/1169665
  235. var orgEnumerateDevices =
  236. navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
  237. navigator.mediaDevices.enumerateDevices = function() {
  238. return orgEnumerateDevices().then(undefined, function(e) {
  239. if (e.name === 'NotFoundError') {
  240. return [];
  241. }
  242. throw e;
  243. });
  244. };
  245. }
  246. } else if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) {
  247. webrtcUtils.log('This appears to be Chrome');
  248.  
  249. webrtcDetectedBrowser = 'chrome';
  250.  
  251. // the detected chrome version.
  252. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  253. /Chrom(e|ium)\/([0-9]+)\./, 2);
  254.  
  255. // the minimum chrome version still supported by adapter.
  256. webrtcMinimumVersion = 38;
  257.  
  258. // The RTCPeerConnection object.
  259. window.RTCPeerConnection = function(pcConfig, pcConstraints) {
  260. // Translate iceTransportPolicy to iceTransports,
  261. // see https://code.google.com/p/webrtc/issues/detail?id=4869
  262. if (pcConfig && pcConfig.iceTransportPolicy) {
  263. pcConfig.iceTransports = pcConfig.iceTransportPolicy;
  264. }
  265.  
  266. var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors
  267. var origGetStats = pc.getStats.bind(pc);
  268. pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line
  269. var self = this;
  270. var args = arguments;
  271.  
  272. // If selector is a function then we are in the old style stats so just
  273. // pass back the original getStats format to avoid breaking old users.
  274. if (arguments.length > 0 && typeof selector === 'function') {
  275. return origGetStats(selector, successCallback);
  276. }
  277.  
  278. var fixChromeStats = function(response) {
  279. var standardReport = {};
  280. var reports = response.result();
  281. reports.forEach(function(report) {
  282. var standardStats = {
  283. id: report.id,
  284. timestamp: report.timestamp,
  285. type: report.type
  286. };
  287. report.names().forEach(function(name) {
  288. standardStats[name] = report.stat(name);
  289. });
  290. standardReport[standardStats.id] = standardStats;
  291. });
  292.  
  293. return standardReport;
  294. };
  295.  
  296. if (arguments.length >= 2) {
  297. var successCallbackWrapper = function(response) {
  298. args[1](fixChromeStats(response));
  299. };
  300.  
  301. return origGetStats.apply(this, [successCallbackWrapper, arguments[0]]);
  302. }
  303.  
  304. // promise-support
  305. return new Promise(function(resolve, reject) {
  306. if (args.length === 1 && selector === null) {
  307. origGetStats.apply(self, [
  308. function(response) {
  309. resolve.apply(null, [fixChromeStats(response)]);
  310. }, reject]);
  311. } else {
  312. origGetStats.apply(self, [resolve, reject]);
  313. }
  314. });
  315. };
  316.  
  317. return pc;
  318. };
  319. window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
  320.  
  321. // wrap static methods. Currently just generateCertificate.
  322. if (webkitRTCPeerConnection.generateCertificate) {
  323. Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
  324. get: function() {
  325. if (arguments.length) {
  326. return webkitRTCPeerConnection.generateCertificate.apply(null,
  327. arguments);
  328. } else {
  329. return webkitRTCPeerConnection.generateCertificate;
  330. }
  331. }
  332. });
  333. }
  334.  
  335. // add promise support
  336. ['createOffer', 'createAnswer'].forEach(function(method) {
  337. var nativeMethod = webkitRTCPeerConnection.prototype[method];
  338. webkitRTCPeerConnection.prototype[method] = function() {
  339. var self = this;
  340. if (arguments.length < 1 || (arguments.length === 1 &&
  341. typeof(arguments[0]) === 'object')) {
  342. var opts = arguments.length === 1 ? arguments[0] : undefined;
  343. return new Promise(function(resolve, reject) {
  344. nativeMethod.apply(self, [resolve, reject, opts]);
  345. });
  346. } else {
  347. return nativeMethod.apply(this, arguments);
  348. }
  349. };
  350. });
  351.  
  352. ['setLocalDescription', 'setRemoteDescription',
  353. 'addIceCandidate'].forEach(function(method) {
  354. var nativeMethod = webkitRTCPeerConnection.prototype[method];
  355. webkitRTCPeerConnection.prototype[method] = function() {
  356. var args = arguments;
  357. var self = this;
  358. return new Promise(function(resolve, reject) {
  359. nativeMethod.apply(self, [args[0],
  360. function() {
  361. resolve();
  362. if (args.length >= 2) {
  363. args[1].apply(null, []);
  364. }
  365. },
  366. function(err) {
  367. reject(err);
  368. if (args.length >= 3) {
  369. args[2].apply(null, [err]);
  370. }
  371. }]
  372. );
  373. });
  374. };
  375. });
  376.  
  377. // getUserMedia constraints shim.
  378. var constraintsToChrome = function(c) {
  379. if (typeof c !== 'object' || c.mandatory || c.optional) {
  380. return c;
  381. }
  382. var cc = {};
  383. Object.keys(c).forEach(function(key) {
  384. if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  385. return;
  386. }
  387. var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
  388. if (r.exact !== undefined && typeof r.exact === 'number') {
  389. r.min = r.max = r.exact;
  390. }
  391. var oldname = function(prefix, name) {
  392. if (prefix) {
  393. return prefix + name.charAt(0).toUpperCase() + name.slice(1);
  394. }
  395. return (name === 'deviceId') ? 'sourceId' : name;
  396. };
  397. if (r.ideal !== undefined) {
  398. cc.optional = cc.optional || [];
  399. var oc = {};
  400. if (typeof r.ideal === 'number') {
  401. oc[oldname('min', key)] = r.ideal;
  402. cc.optional.push(oc);
  403. oc = {};
  404. oc[oldname('max', key)] = r.ideal;
  405. cc.optional.push(oc);
  406. } else {
  407. oc[oldname('', key)] = r.ideal;
  408. cc.optional.push(oc);
  409. }
  410. }
  411. if (r.exact !== undefined && typeof r.exact !== 'number') {
  412. cc.mandatory = cc.mandatory || {};
  413. cc.mandatory[oldname('', key)] = r.exact;
  414. } else {
  415. ['min', 'max'].forEach(function(mix) {
  416. if (r[mix] !== undefined) {
  417. cc.mandatory = cc.mandatory || {};
  418. cc.mandatory[oldname(mix, key)] = r[mix];
  419. }
  420. });
  421. }
  422. });
  423. if (c.advanced) {
  424. cc.optional = (cc.optional || []).concat(c.advanced);
  425. }
  426. return cc;
  427. };
  428.  
  429. getUserMedia = function(constraints, onSuccess, onError) {
  430. if (constraints.audio) {
  431. constraints.audio = constraintsToChrome(constraints.audio);
  432. }
  433. if (constraints.video) {
  434. constraints.video = constraintsToChrome(constraints.video);
  435. }
  436. webrtcUtils.log('chrome: ' + JSON.stringify(constraints));
  437. return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
  438. };
  439. navigator.getUserMedia = getUserMedia;
  440.  
  441. if (!navigator.mediaDevices) {
  442. navigator.mediaDevices = {getUserMedia: requestUserMedia,
  443. enumerateDevices: function() {
  444. return new Promise(function(resolve) {
  445. var kinds = {audio: 'audioinput', video: 'videoinput'};
  446. return MediaStreamTrack.getSources(function(devices) {
  447. resolve(devices.map(function(device) {
  448. return {label: device.label,
  449. kind: kinds[device.kind],
  450. deviceId: device.id,
  451. groupId: ''};
  452. }));
  453. });
  454. });
  455. }};
  456. }
  457.  
  458. // A shim for getUserMedia method on the mediaDevices object.
  459. // TODO(KaptenJansson) remove once implemented in Chrome stable.
  460. if (!navigator.mediaDevices.getUserMedia) {
  461. navigator.mediaDevices.getUserMedia = function(constraints) {
  462. return requestUserMedia(constraints);
  463. };
  464. } else {
  465. // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
  466. // function which returns a Promise, it does not accept spec-style
  467. // constraints.
  468. var origGetUserMedia = navigator.mediaDevices.getUserMedia.
  469. bind(navigator.mediaDevices);
  470. navigator.mediaDevices.getUserMedia = function(c) {
  471. webrtcUtils.log('spec: ' + JSON.stringify(c)); // whitespace for alignment
  472. c.audio = constraintsToChrome(c.audio);
  473. c.video = constraintsToChrome(c.video);
  474. webrtcUtils.log('chrome: ' + JSON.stringify(c));
  475. return origGetUserMedia(c);
  476. };
  477. }
  478.  
  479. // Dummy devicechange event methods.
  480. // TODO(KaptenJansson) remove once implemented in Chrome stable.
  481. if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
  482. navigator.mediaDevices.addEventListener = function() {
  483. webrtcUtils.log('Dummy mediaDevices.addEventListener called.');
  484. };
  485. }
  486. if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
  487. navigator.mediaDevices.removeEventListener = function() {
  488. webrtcUtils.log('Dummy mediaDevices.removeEventListener called.');
  489. };
  490. }
  491.  
  492. // Attach a media stream to an element.
  493. attachMediaStream = function(element, stream) {
  494. if (webrtcDetectedVersion >= 43) {
  495. element.srcObject = stream;
  496. } else if (typeof element.src !== 'undefined') {
  497. element.src = URL.createObjectURL(stream);
  498. } else {
  499. webrtcUtils.log('Error attaching stream to element.');
  500. }
  501. };
  502. reattachMediaStream = function(to, from) {
  503. if (webrtcDetectedVersion >= 43) {
  504. to.srcObject = from.srcObject;
  505. } else {
  506. to.src = from.src;
  507. }
  508. };
  509.  
  510. } else if (navigator.mediaDevices && navigator.userAgent.match(
  511. /Edge\/(\d+).(\d+)$/)) {
  512. webrtcUtils.log('This appears to be Edge');
  513. webrtcDetectedBrowser = 'edge';
  514.  
  515. webrtcDetectedVersion = webrtcUtils.extractVersion(navigator.userAgent,
  516. /Edge\/(\d+).(\d+)$/, 2);
  517.  
  518. // The minimum version still supported by adapter.
  519. // This is the build number for Edge.
  520. webrtcMinimumVersion = 10547;
  521.  
  522. if (window.RTCIceGatherer) {
  523. // Generate an alphanumeric identifier for cname or mids.
  524. // TODO: use UUIDs instead? https://gist.github.com/jed/982883
  525. var generateIdentifier = function() {
  526. return Math.random().toString(36).substr(2, 10);
  527. };
  528.  
  529. // The RTCP CNAME used by all peerconnections from the same JS.
  530. var localCName = generateIdentifier();
  531.  
  532. // SDP helpers - to be moved into separate module.
  533. var SDPUtils = {};
  534.  
  535. // Splits SDP into lines, dealing with both CRLF and LF.
  536. SDPUtils.splitLines = function(blob) {
  537. return blob.trim().split('\n').map(function(line) {
  538. return line.trim();
  539. });
  540. };
  541.  
  542. // Splits SDP into sessionpart and mediasections. Ensures CRLF.
  543. SDPUtils.splitSections = function(blob) {
  544. var parts = blob.split('\r\nm=');
  545. return parts.map(function(part, index) {
  546. return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
  547. });
  548. };
  549.  
  550. // Returns lines that start with a certain prefix.
  551. SDPUtils.matchPrefix = function(blob, prefix) {
  552. return SDPUtils.splitLines(blob).filter(function(line) {
  553. return line.indexOf(prefix) === 0;
  554. });
  555. };
  556.  
  557. // Parses an ICE candidate line. Sample input:
  558. // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 rport 55996"
  559. SDPUtils.parseCandidate = function(line) {
  560. var parts;
  561. // Parse both variants.
  562. if (line.indexOf('a=candidate:') === 0) {
  563. parts = line.substring(12).split(' ');
  564. } else {
  565. parts = line.substring(10).split(' ');
  566. }
  567.  
  568. var candidate = {
  569. foundation: parts[0],
  570. component: parts[1],
  571. protocol: parts[2].toLowerCase(),
  572. priority: parseInt(parts[3], 10),
  573. ip: parts[4],
  574. port: parseInt(parts[5], 10),
  575. // skip parts[6] == 'typ'
  576. type: parts[7]
  577. };
  578.  
  579. for (var i = 8; i < parts.length; i += 2) {
  580. switch (parts[i]) {
  581. case 'raddr':
  582. candidate.relatedAddress = parts[i + 1];
  583. break;
  584. case 'rport':
  585. candidate.relatedPort = parseInt(parts[i + 1], 10);
  586. break;
  587. case 'tcptype':
  588. candidate.tcpType = parts[i + 1];
  589. break;
  590. default: // Unknown extensions are silently ignored.
  591. break;
  592. }
  593. }
  594. return candidate;
  595. };
  596.  
  597. // Translates a candidate object into SDP candidate attribute.
  598. SDPUtils.writeCandidate = function(candidate) {
  599. var sdp = [];
  600. sdp.push(candidate.foundation);
  601. sdp.push(candidate.component);
  602. sdp.push(candidate.protocol.toUpperCase());
  603. sdp.push(candidate.priority);
  604. sdp.push(candidate.ip);
  605. sdp.push(candidate.port);
  606.  
  607. var type = candidate.type;
  608. sdp.push('typ');
  609. sdp.push(type);
  610. if (type !== 'host' && candidate.relatedAddress &&
  611. candidate.relatedPort) {
  612. sdp.push('raddr');
  613. sdp.push(candidate.relatedAddress); // was: relAddr
  614. sdp.push('rport');
  615. sdp.push(candidate.relatedPort); // was: relPort
  616. }
  617. if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
  618. sdp.push('tcptype');
  619. sdp.push(candidate.tcpType);
  620. }
  621. return 'candidate:' + sdp.join(' ');
  622. };
  623.  
  624. // Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
  625. // a=rtpmap:111 opus/48000/2
  626. SDPUtils.parseRtpMap = function(line) {
  627. var parts = line.substr(9).split(' ');
  628. var parsed = {
  629. payloadType: parseInt(parts.shift(), 10) // was: id
  630. };
  631.  
  632. parts = parts[0].split('/');
  633.  
  634. parsed.name = parts[0];
  635. parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
  636. parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels
  637. return parsed;
  638. };
  639.  
  640. // Generate an a=rtpmap line from RTCRtpCodecCapability or RTCRtpCodecParameters.
  641. SDPUtils.writeRtpMap = function(codec) {
  642. var pt = codec.payloadType;
  643. if (codec.preferredPayloadType !== undefined) {
  644. pt = codec.preferredPayloadType;
  645. }
  646. return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
  647. (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
  648. };
  649.  
  650. // Parses an ftmp line, returns dictionary. Sample input:
  651. // a=fmtp:96 vbr=on;cng=on
  652. // Also deals with vbr=on; cng=on
  653. SDPUtils.parseFmtp = function(line) {
  654. var parsed = {};
  655. var kv;
  656. var parts = line.substr(line.indexOf(' ') + 1).split(';');
  657. for (var j = 0; j < parts.length; j++) {
  658. kv = parts[j].trim().split('=');
  659. parsed[kv[0].trim()] = kv[1];
  660. }
  661. return parsed;
  662. };
  663.  
  664. // Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
  665. SDPUtils.writeFtmp = function(codec) {
  666. var line = '';
  667. var pt = codec.payloadType;
  668. if (codec.preferredPayloadType !== undefined) {
  669. pt = codec.preferredPayloadType;
  670. }
  671. if (codec.parameters && codec.parameters.length) {
  672. var params = [];
  673. Object.keys(codec.parameters).forEach(function(param) {
  674. params.push(param + '=' + codec.parameters[param]);
  675. });
  676. line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
  677. }
  678. return line;
  679. };
  680.  
  681. // Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
  682. // a=rtcp-fb:98 nack rpsi
  683. SDPUtils.parseRtcpFb = function(line) {
  684. var parts = line.substr(line.indexOf(' ') + 1).split(' ');
  685. return {
  686. type: parts.shift(),
  687. parameter: parts.join(' ')
  688. };
  689. };
  690. // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
  691. SDPUtils.writeRtcpFb = function(codec) {
  692. var lines = '';
  693. var pt = codec.payloadType;
  694. if (codec.preferredPayloadType !== undefined) {
  695. pt = codec.preferredPayloadType;
  696. }
  697. if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
  698. // FIXME: special handling for trr-int?
  699. codec.rtcpFeedback.forEach(function(fb) {
  700. lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + fb.parameter +
  701. '\r\n';
  702. });
  703. }
  704. return lines;
  705. };
  706.  
  707. // Parses an RFC 5576 ssrc media attribute. Sample input:
  708. // a=ssrc:3735928559 cname:something
  709. SDPUtils.parseSsrcMedia = function(line) {
  710. var sp = line.indexOf(' ');
  711. var parts = {
  712. ssrc: line.substr(7, sp - 7),
  713. };
  714. var colon = line.indexOf(':', sp);
  715. if (colon > -1) {
  716. parts.attribute = line.substr(sp + 1, colon - sp - 1);
  717. parts.value = line.substr(colon + 1);
  718. } else {
  719. parts.attribute = line.substr(sp + 1);
  720. }
  721. return parts;
  722. };
  723.  
  724. // Extracts DTLS parameters from SDP media section or sessionpart.
  725. // FIXME: for consistency with other functions this should only
  726. // get the fingerprint line as input. See also getIceParameters.
  727. SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
  728. var lines = SDPUtils.splitLines(mediaSection);
  729. lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.
  730. var fpLine = lines.filter(function(line) {
  731. return line.indexOf('a=fingerprint:') === 0;
  732. })[0].substr(14);
  733. // Note: a=setup line is ignored since we use the 'auto' role.
  734. var dtlsParameters = {
  735. role: 'auto',
  736. fingerprints: [{
  737. algorithm: fpLine.split(' ')[0],
  738. value: fpLine.split(' ')[1]
  739. }]
  740. };
  741. return dtlsParameters;
  742. };
  743.  
  744. // Serializes DTLS parameters to SDP.
  745. SDPUtils.writeDtlsParameters = function(params, setupType) {
  746. var sdp = 'a=setup:' + setupType + '\r\n';
  747. params.fingerprints.forEach(function(fp) {
  748. sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
  749. });
  750. return sdp;
  751. };
  752. // Parses ICE information from SDP media section or sessionpart.
  753. // FIXME: for consistency with other functions this should only
  754. // get the ice-ufrag and ice-pwd lines as input.
  755. SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
  756. var lines = SDPUtils.splitLines(mediaSection);
  757. lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too.
  758. var iceParameters = {
  759. usernameFragment: lines.filter(function(line) {
  760. return line.indexOf('a=ice-ufrag:') === 0;
  761. })[0].substr(12),
  762. password: lines.filter(function(line) {
  763. return line.indexOf('a=ice-pwd:') === 0;
  764. })[0].substr(10)
  765. };
  766. return iceParameters;
  767. };
  768.  
  769. // Serializes ICE parameters to SDP.
  770. SDPUtils.writeIceParameters = function(params) {
  771. return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
  772. 'a=ice-pwd:' + params.password + '\r\n';
  773. };
  774.  
  775. // Parses the SDP media section and returns RTCRtpParameters.
  776. SDPUtils.parseRtpParameters = function(mediaSection) {
  777. var description = {
  778. codecs: [],
  779. headerExtensions: [],
  780. fecMechanisms: [],
  781. rtcp: []
  782. };
  783. var lines = SDPUtils.splitLines(mediaSection);
  784. var mline = lines[0].split(' ');
  785. for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
  786. var pt = mline[i];
  787. var rtpmapline = SDPUtils.matchPrefix(
  788. mediaSection, 'a=rtpmap:' + pt + ' ')[0];
  789. if (rtpmapline) {
  790. var codec = SDPUtils.parseRtpMap(rtpmapline);
  791. var fmtps = SDPUtils.matchPrefix(
  792. mediaSection, 'a=fmtp:' + pt + ' ');
  793. // Only the first a=fmtp:<pt> is considered.
  794. codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
  795. codec.rtcpFeedback = SDPUtils.matchPrefix(
  796. mediaSection, 'a=rtcp-fb:' + pt + ' ')
  797. .map(SDPUtils.parseRtcpFb);
  798. description.codecs.push(codec);
  799. }
  800. }
  801. // FIXME: parse headerExtensions, fecMechanisms and rtcp.
  802. return description;
  803. };
  804.  
  805. // Generates parts of the SDP media section describing the capabilities / parameters.
  806. SDPUtils.writeRtpDescription = function(kind, caps) {
  807. var sdp = '';
  808.  
  809. // Build the mline.
  810. sdp += 'm=' + kind + ' ';
  811. sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
  812. sdp += ' UDP/TLS/RTP/SAVPF ';
  813. sdp += caps.codecs.map(function(codec) {
  814. if (codec.preferredPayloadType !== undefined) {
  815. return codec.preferredPayloadType;
  816. }
  817. return codec.payloadType;
  818. }).join(' ') + '\r\n';
  819.  
  820. sdp += 'c=IN IP4 0.0.0.0\r\n';
  821. sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
  822.  
  823. // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
  824. caps.codecs.forEach(function(codec) {
  825. sdp += SDPUtils.writeRtpMap(codec);
  826. sdp += SDPUtils.writeFtmp(codec);
  827. sdp += SDPUtils.writeRtcpFb(codec);
  828. });
  829. // FIXME: add headerExtensions, fecMechanismş and rtcp.
  830. sdp += 'a=rtcp-mux\r\n';
  831. return sdp;
  832. };
  833.  
  834. SDPUtils.writeSessionBoilerplate = function() {
  835. // FIXME: sess-id should be an NTP timestamp.
  836. return 'v=0\r\n' +
  837. 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
  838. 's=-\r\n' +
  839. 't=0 0\r\n';
  840. };
  841.  
  842. SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
  843. var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
  844.  
  845. // Map ICE parameters (ufrag, pwd) to SDP.
  846. sdp += SDPUtils.writeIceParameters(
  847. transceiver.iceGatherer.getLocalParameters());
  848.  
  849. // Map DTLS parameters to SDP.
  850. sdp += SDPUtils.writeDtlsParameters(
  851. transceiver.dtlsTransport.getLocalParameters(),
  852. type === 'offer' ? 'actpass' : 'active');
  853.  
  854. sdp += 'a=mid:' + transceiver.mid + '\r\n';
  855.  
  856. if (transceiver.rtpSender && transceiver.rtpReceiver) {
  857. sdp += 'a=sendrecv\r\n';
  858. } else if (transceiver.rtpSender) {
  859. sdp += 'a=sendonly\r\n';
  860. } else if (transceiver.rtpReceiver) {
  861. sdp += 'a=recvonly\r\n';
  862. } else {
  863. sdp += 'a=inactive\r\n';
  864. }
  865.  
  866. // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
  867. if (transceiver.rtpSender) {
  868. var msid = 'msid:' + stream.id + ' ' +
  869. transceiver.rtpSender.track.id + '\r\n';
  870. sdp += 'a=' + msid;
  871. sdp += 'a=ssrc:' + transceiver.sendSsrc + ' ' + msid;
  872. }
  873. // FIXME: this should be written by writeRtpDescription.
  874. sdp += 'a=ssrc:' + transceiver.sendSsrc + ' cname:' +
  875. localCName + '\r\n';
  876. return sdp;
  877. };
  878.  
  879. // Gets the direction from the mediaSection or the sessionpart.
  880. SDPUtils.getDirection = function(mediaSection, sessionpart) {
  881. // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
  882. var lines = SDPUtils.splitLines(mediaSection);
  883. for (var i = 0; i < lines.length; i++) {
  884. switch (lines[i]) {
  885. case 'a=sendrecv':
  886. case 'a=sendonly':
  887. case 'a=recvonly':
  888. case 'a=inactive':
  889. return lines[i].substr(2);
  890. }
  891. }
  892. if (sessionpart) {
  893. return SDPUtils.getDirection(sessionpart);
  894. }
  895. return 'sendrecv';
  896. };
  897.  
  898. // ORTC defines an RTCIceCandidate object but no constructor.
  899. // Not implemented in Edge.
  900. if (!window.RTCIceCandidate) {
  901. window.RTCIceCandidate = function(args) {
  902. return args;
  903. };
  904. }
  905. // ORTC does not have a session description object but
  906. // other browsers (i.e. Chrome) that will support both PC and ORTC
  907. // in the future might have this defined already.
  908. if (!window.RTCSessionDescription) {
  909. window.RTCSessionDescription = function(args) {
  910. return args;
  911. };
  912. }
  913.  
  914. window.RTCPeerConnection = function(config) {
  915. var self = this;
  916.  
  917. this.onicecandidate = null;
  918. this.onaddstream = null;
  919. this.onremovestream = null;
  920. this.onsignalingstatechange = null;
  921. this.oniceconnectionstatechange = null;
  922. this.onnegotiationneeded = null;
  923. this.ondatachannel = null;
  924.  
  925. this.localStreams = [];
  926. this.remoteStreams = [];
  927. this.getLocalStreams = function() { return self.localStreams; };
  928. this.getRemoteStreams = function() { return self.remoteStreams; };
  929.  
  930. this.localDescription = new RTCSessionDescription({
  931. type: '',
  932. sdp: ''
  933. });
  934. this.remoteDescription = new RTCSessionDescription({
  935. type: '',
  936. sdp: ''
  937. });
  938. this.signalingState = 'stable';
  939. this.iceConnectionState = 'new';
  940.  
  941. this.iceOptions = {
  942. gatherPolicy: 'all',
  943. iceServers: []
  944. };
  945. if (config && config.iceTransportPolicy) {
  946. switch (config.iceTransportPolicy) {
  947. case 'all':
  948. case 'relay':
  949. this.iceOptions.gatherPolicy = config.iceTransportPolicy;
  950. break;
  951. case 'none':
  952. // FIXME: remove once implementation and spec have added this.
  953. throw new TypeError('iceTransportPolicy "none" not supported');
  954. }
  955. }
  956. if (config && config.iceServers) {
  957. // Edge does not like
  958. // 1) stun:
  959. // 2) turn: that does not have all of turn:host:port?transport=udp
  960. // 3) an array of urls
  961. config.iceServers.forEach(function(server) {
  962. if (server.urls) {
  963. var url;
  964. if (typeof(server.urls) === 'string') {
  965. url = server.urls;
  966. } else {
  967. url = server.urls[0];
  968. }
  969. if (url.indexOf('transport=udp') !== -1) {
  970. self.iceServers.push({
  971. username: server.username,
  972. credential: server.credential,
  973. urls: url
  974. });
  975. }
  976. }
  977. });
  978. }
  979.  
  980. // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
  981. // everything that is needed to describe a SDP m-line.
  982. this.transceivers = [];
  983.  
  984. // since the iceGatherer is currently created in createOffer but we
  985. // must not emit candidates until after setLocalDescription we buffer
  986. // them in this array.
  987. this._localIceCandidatesBuffer = [];
  988. };
  989.  
  990. window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
  991. var self = this;
  992. // FIXME: need to apply ice candidates in a way which is async but in-order
  993. this._localIceCandidatesBuffer.forEach(function(event) {
  994. if (self.onicecandidate !== null) {
  995. self.onicecandidate(event);
  996. }
  997. });
  998. this._localIceCandidatesBuffer = [];
  999. };
  1000.  
  1001. window.RTCPeerConnection.prototype.addStream = function(stream) {
  1002. // Clone is necessary for local demos mostly, attaching directly
  1003. // to two different senders does not work (build 10547).
  1004. this.localStreams.push(stream.clone());
  1005. this._maybeFireNegotiationNeeded();
  1006. };
  1007.  
  1008. window.RTCPeerConnection.prototype.removeStream = function(stream) {
  1009. var idx = this.localStreams.indexOf(stream);
  1010. if (idx > -1) {
  1011. this.localStreams.splice(idx, 1);
  1012. this._maybeFireNegotiationNeeded();
  1013. }
  1014. };
  1015.  
  1016. // Determines the intersection of local and remote capabilities.
  1017. window.RTCPeerConnection.prototype._getCommonCapabilities =
  1018. function(localCapabilities, remoteCapabilities) {
  1019. var commonCapabilities = {
  1020. codecs: [],
  1021. headerExtensions: [],
  1022. fecMechanisms: []
  1023. };
  1024. localCapabilities.codecs.forEach(function(lCodec) {
  1025. for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
  1026. var rCodec = remoteCapabilities.codecs[i];
  1027. if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
  1028. lCodec.clockRate === rCodec.clockRate &&
  1029. lCodec.numChannels === rCodec.numChannels) {
  1030. // push rCodec so we reply with offerer payload type
  1031. commonCapabilities.codecs.push(rCodec);
  1032.  
  1033. // FIXME: also need to determine intersection between
  1034. // .rtcpFeedback and .parameters
  1035. break;
  1036. }
  1037. }
  1038. });
  1039.  
  1040. localCapabilities.headerExtensions.forEach(function(lHeaderExtension) {
  1041. for (var i = 0; i < remoteCapabilities.headerExtensions.length; i++) {
  1042. var rHeaderExtension = remoteCapabilities.headerExtensions[i];
  1043. if (lHeaderExtension.uri === rHeaderExtension.uri) {
  1044. commonCapabilities.headerExtensions.push(rHeaderExtension);
  1045. break;
  1046. }
  1047. }
  1048. });
  1049.  
  1050. // FIXME: fecMechanisms
  1051. return commonCapabilities;
  1052. };
  1053.  
  1054. // Create ICE gatherer, ICE transport and DTLS transport.
  1055. window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
  1056. function(mid, sdpMLineIndex) {
  1057. var self = this;
  1058. var iceGatherer = new RTCIceGatherer(self.iceOptions);
  1059. var iceTransport = new RTCIceTransport(iceGatherer);
  1060. iceGatherer.onlocalcandidate = function(evt) {
  1061. var event = {};
  1062. event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
  1063.  
  1064. var cand = evt.candidate;
  1065. // Edge emits an empty object for RTCIceCandidateCompleteβ€₯
  1066. if (!cand || Object.keys(cand).length === 0) {
  1067. // polyfill since RTCIceGatherer.state is not implemented in Edge 10547 yet.
  1068. if (iceGatherer.state === undefined) {
  1069. iceGatherer.state = 'completed';
  1070. }
  1071.  
  1072. // Emit a candidate with type endOfCandidates to make the samples work.
  1073. // Edge requires addIceCandidate with this empty candidate to start checking.
  1074. // The real solution is to signal end-of-candidates to the other side when
  1075. // getting the null candidate but some apps (like the samples) don't do that.
  1076. event.candidate.candidate =
  1077. 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
  1078. } else {
  1079. // RTCIceCandidate doesn't have a component, needs to be added
  1080. cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
  1081. event.candidate.candidate = SDPUtils.writeCandidate(cand);
  1082. }
  1083.  
  1084. var complete = self.transceivers.every(function(transceiver) {
  1085. return transceiver.iceGatherer &&
  1086. transceiver.iceGatherer.state === 'completed';
  1087. });
  1088. // FIXME: update .localDescription with candidate and (potentially) end-of-candidates.
  1089. // To make this harder, the gatherer might emit candidates before localdescription
  1090. // is set. To make things worse, gather.getLocalCandidates still errors in
  1091. // Edge 10547 when no candidates have been gathered yet.
  1092.  
  1093. if (self.onicecandidate !== null) {
  1094. // Emit candidate if localDescription is set.
  1095. // Also emits null candidate when all gatherers are complete.
  1096. if (self.localDescription && self.localDescription.type === '') {
  1097. self._localIceCandidatesBuffer.push(event);
  1098. if (complete) {
  1099. self._localIceCandidatesBuffer.push({});
  1100. }
  1101. } else {
  1102. self.onicecandidate(event);
  1103. if (complete) {
  1104. self.onicecandidate({});
  1105. }
  1106. }
  1107. }
  1108. };
  1109. iceTransport.onicestatechange = function() {
  1110. self._updateConnectionState();
  1111. };
  1112.  
  1113. var dtlsTransport = new RTCDtlsTransport(iceTransport);
  1114. dtlsTransport.ondtlsstatechange = function() {
  1115. self._updateConnectionState();
  1116. };
  1117. dtlsTransport.onerror = function() {
  1118. // onerror does not set state to failed by itself.
  1119. dtlsTransport.state = 'failed';
  1120. self._updateConnectionState();
  1121. };
  1122.  
  1123. return {
  1124. iceGatherer: iceGatherer,
  1125. iceTransport: iceTransport,
  1126. dtlsTransport: dtlsTransport
  1127. };
  1128. };
  1129.  
  1130. // Start the RTP Sender and Receiver for a transceiver.
  1131. window.RTCPeerConnection.prototype._transceive = function(transceiver,
  1132. send, recv) {
  1133. var params = this._getCommonCapabilities(transceiver.localCapabilities,
  1134. transceiver.remoteCapabilities);
  1135. if (send && transceiver.rtpSender) {
  1136. params.encodings = [{
  1137. ssrc: transceiver.sendSsrc
  1138. }];
  1139. params.rtcp = {
  1140. cname: localCName,
  1141. ssrc: transceiver.recvSsrc
  1142. };
  1143. transceiver.rtpSender.send(params);
  1144. }
  1145. if (recv && transceiver.rtpReceiver) {
  1146. params.encodings = [{
  1147. ssrc: transceiver.recvSsrc
  1148. }];
  1149. params.rtcp = {
  1150. cname: transceiver.cname,
  1151. ssrc: transceiver.sendSsrc
  1152. };
  1153. transceiver.rtpReceiver.receive(params);
  1154. }
  1155. };
  1156.  
  1157. window.RTCPeerConnection.prototype.setLocalDescription =
  1158. function(description) {
  1159. var self = this;
  1160. if (description.type === 'offer') {
  1161. if (!this._pendingOffer) {
  1162. } else {
  1163. this.transceivers = this._pendingOffer;
  1164. delete this._pendingOffer;
  1165. }
  1166. } else if (description.type === 'answer') {
  1167. var sections = SDPUtils.splitSections(self.remoteDescription.sdp);
  1168. var sessionpart = sections.shift();
  1169. sections.forEach(function(mediaSection, sdpMLineIndex) {
  1170. var transceiver = self.transceivers[sdpMLineIndex];
  1171. var iceGatherer = transceiver.iceGatherer;
  1172. var iceTransport = transceiver.iceTransport;
  1173. var dtlsTransport = transceiver.dtlsTransport;
  1174. var localCapabilities = transceiver.localCapabilities;
  1175. var remoteCapabilities = transceiver.remoteCapabilities;
  1176. var rejected = mediaSection.split('\n', 1)[0]
  1177. .split(' ', 2)[1] === '0';
  1178.  
  1179. if (!rejected) {
  1180. var remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
  1181. sessionpart);
  1182. iceTransport.start(iceGatherer, remoteIceParameters, 'controlled');
  1183.  
  1184. var remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
  1185. sessionpart);
  1186. dtlsTransport.start(remoteDtlsParameters);
  1187.  
  1188. // Calculate intersection of capabilities.
  1189. var params = self._getCommonCapabilities(localCapabilities,
  1190. remoteCapabilities);
  1191.  
  1192. // Start the RTCRtpSender. The RTCRtpReceiver for this transceiver
  1193. // has already been started in setRemoteDescription.
  1194. self._transceive(transceiver,
  1195. params.codecs.length > 0,
  1196. false);
  1197. }
  1198. });
  1199. }
  1200.  
  1201. this.localDescription = description;
  1202. switch (description.type) {
  1203. case 'offer':
  1204. this._updateSignalingState('have-local-offer');
  1205. break;
  1206. case 'answer':
  1207. this._updateSignalingState('stable');
  1208. break;
  1209. default:
  1210. throw new TypeError('unsupported type "' + description.type + '"');
  1211. }
  1212.  
  1213. // If a success callback was provided, emit ICE candidates after it has been
  1214. // executed. Otherwise, emit callback after the Promise is resolved.
  1215. var hasCallback = arguments.length > 1 &&
  1216. typeof arguments[1] === 'function';
  1217. if (hasCallback) {
  1218. var cb = arguments[1];
  1219. window.setTimeout(function() {
  1220. cb();
  1221. self._emitBufferedCandidates();
  1222. }, 0);
  1223. }
  1224. var p = Promise.resolve();
  1225. p.then(function() {
  1226. if (!hasCallback) {
  1227. window.setTimeout(self._emitBufferedCandidates.bind(self), 0);
  1228. }
  1229. });
  1230. return p;
  1231. };
  1232.  
  1233. window.RTCPeerConnection.prototype.setRemoteDescription =
  1234. function(description) {
  1235. var self = this;
  1236. var stream = new MediaStream();
  1237. var sections = SDPUtils.splitSections(description.sdp);
  1238. var sessionpart = sections.shift();
  1239. sections.forEach(function(mediaSection, sdpMLineIndex) {
  1240. var lines = SDPUtils.splitLines(mediaSection);
  1241. var mline = lines[0].substr(2).split(' ');
  1242. var kind = mline[0];
  1243. var rejected = mline[1] === '0';
  1244. var direction = SDPUtils.getDirection(mediaSection, sessionpart);
  1245.  
  1246. var transceiver;
  1247. var iceGatherer;
  1248. var iceTransport;
  1249. var dtlsTransport;
  1250. var rtpSender;
  1251. var rtpReceiver;
  1252. var sendSsrc;
  1253. var recvSsrc;
  1254. var localCapabilities;
  1255.  
  1256. // FIXME: ensure the mediaSection has rtcp-mux set.
  1257. var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
  1258. var remoteIceParameters;
  1259. var remoteDtlsParameters;
  1260. if (!rejected) {
  1261. remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
  1262. sessionpart);
  1263. remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
  1264. sessionpart);
  1265. }
  1266. var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6);
  1267.  
  1268. var cname;
  1269. // Gets the first SSRC. Note that with RTX there might be multiple SSRCs.
  1270. var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
  1271. .map(function(line) {
  1272. return SDPUtils.parseSsrcMedia(line);
  1273. })
  1274. .filter(function(obj) {
  1275. return obj.attribute === 'cname';
  1276. })[0];
  1277. if (remoteSsrc) {
  1278. recvSsrc = parseInt(remoteSsrc.ssrc, 10);
  1279. cname = remoteSsrc.value;
  1280. }
  1281.  
  1282. if (description.type === 'offer') {
  1283. var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  1284.  
  1285. localCapabilities = RTCRtpReceiver.getCapabilities(kind);
  1286. sendSsrc = (2 * sdpMLineIndex + 2) * 1001;
  1287.  
  1288. rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  1289.  
  1290. // FIXME: not correct when there are multiple streams but that is
  1291. // not currently supported in this shim.
  1292. stream.addTrack(rtpReceiver.track);
  1293.  
  1294. // FIXME: look at direction.
  1295. if (self.localStreams.length > 0 &&
  1296. self.localStreams[0].getTracks().length >= sdpMLineIndex) {
  1297. // FIXME: actually more complicated, needs to match types etc
  1298. var localtrack = self.localStreams[0].getTracks()[sdpMLineIndex];
  1299. rtpSender = new RTCRtpSender(localtrack, transports.dtlsTransport);
  1300. }
  1301.  
  1302. self.transceivers[sdpMLineIndex] = {
  1303. iceGatherer: transports.iceGatherer,
  1304. iceTransport: transports.iceTransport,
  1305. dtlsTransport: transports.dtlsTransport,
  1306. localCapabilities: localCapabilities,
  1307. remoteCapabilities: remoteCapabilities,
  1308. rtpSender: rtpSender,
  1309. rtpReceiver: rtpReceiver,
  1310. kind: kind,
  1311. mid: mid,
  1312. cname: cname,
  1313. sendSsrc: sendSsrc,
  1314. recvSsrc: recvSsrc
  1315. };
  1316. // Start the RTCRtpReceiver now. The RTPSender is started in setLocalDescription.
  1317. self._transceive(self.transceivers[sdpMLineIndex],
  1318. false,
  1319. direction === 'sendrecv' || direction === 'sendonly');
  1320. } else if (description.type === 'answer' && !rejected) {
  1321. transceiver = self.transceivers[sdpMLineIndex];
  1322. iceGatherer = transceiver.iceGatherer;
  1323. iceTransport = transceiver.iceTransport;
  1324. dtlsTransport = transceiver.dtlsTransport;
  1325. rtpSender = transceiver.rtpSender;
  1326. rtpReceiver = transceiver.rtpReceiver;
  1327. sendSsrc = transceiver.sendSsrc;
  1328. //recvSsrc = transceiver.recvSsrc;
  1329. localCapabilities = transceiver.localCapabilities;
  1330.  
  1331. self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc;
  1332. self.transceivers[sdpMLineIndex].remoteCapabilities =
  1333. remoteCapabilities;
  1334. self.transceivers[sdpMLineIndex].cname = cname;
  1335.  
  1336. iceTransport.start(iceGatherer, remoteIceParameters, 'controlling');
  1337. dtlsTransport.start(remoteDtlsParameters);
  1338.  
  1339. self._transceive(transceiver,
  1340. direction === 'sendrecv' || direction === 'recvonly',
  1341. direction === 'sendrecv' || direction === 'sendonly');
  1342.  
  1343. if (rtpReceiver &&
  1344. (direction === 'sendrecv' || direction === 'sendonly')) {
  1345. stream.addTrack(rtpReceiver.track);
  1346. } else {
  1347. // FIXME: actually the receiver should be created later.
  1348. delete transceiver.rtpReceiver;
  1349. }
  1350. }
  1351. });
  1352.  
  1353. this.remoteDescription = description;
  1354. switch (description.type) {
  1355. case 'offer':
  1356. this._updateSignalingState('have-remote-offer');
  1357. break;
  1358. case 'answer':
  1359. this._updateSignalingState('stable');
  1360. break;
  1361. default:
  1362. throw new TypeError('unsupported type "' + description.type + '"');
  1363. }
  1364. window.setTimeout(function() {
  1365. if (self.onaddstream !== null && stream.getTracks().length) {
  1366. self.remoteStreams.push(stream);
  1367. window.setTimeout(function() {
  1368. self.onaddstream({stream: stream});
  1369. }, 0);
  1370. }
  1371. }, 0);
  1372. if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1373. window.setTimeout(arguments[1], 0);
  1374. }
  1375. return Promise.resolve();
  1376. };
  1377.  
  1378. window.RTCPeerConnection.prototype.close = function() {
  1379. this.transceivers.forEach(function(transceiver) {
  1380. /* not yet
  1381. if (transceiver.iceGatherer) {
  1382. transceiver.iceGatherer.close();
  1383. }
  1384. */
  1385. if (transceiver.iceTransport) {
  1386. transceiver.iceTransport.stop();
  1387. }
  1388. if (transceiver.dtlsTransport) {
  1389. transceiver.dtlsTransport.stop();
  1390. }
  1391. if (transceiver.rtpSender) {
  1392. transceiver.rtpSender.stop();
  1393. }
  1394. if (transceiver.rtpReceiver) {
  1395. transceiver.rtpReceiver.stop();
  1396. }
  1397. });
  1398. // FIXME: clean up tracks, local streams, remote streams, etc
  1399. this._updateSignalingState('closed');
  1400. };
  1401.  
  1402. // Update the signaling state.
  1403. window.RTCPeerConnection.prototype._updateSignalingState =
  1404. function(newState) {
  1405. this.signalingState = newState;
  1406. if (this.onsignalingstatechange !== null) {
  1407. this.onsignalingstatechange();
  1408. }
  1409. };
  1410.  
  1411. // Determine whether to fire the negotiationneeded event.
  1412. window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
  1413. function() {
  1414. // Fire away (for now).
  1415. if (this.onnegotiationneeded !== null) {
  1416. this.onnegotiationneeded();
  1417. }
  1418. };
  1419.  
  1420. // Update the connection state.
  1421. window.RTCPeerConnection.prototype._updateConnectionState =
  1422. function() {
  1423. var self = this;
  1424. var newState;
  1425. var states = {
  1426. 'new': 0,
  1427. closed: 0,
  1428. connecting: 0,
  1429. checking: 0,
  1430. connected: 0,
  1431. completed: 0,
  1432. failed: 0
  1433. };
  1434. this.transceivers.forEach(function(transceiver) {
  1435. states[transceiver.iceTransport.state]++;
  1436. states[transceiver.dtlsTransport.state]++;
  1437. });
  1438. // ICETransport.completed and connected are the same for this purpose.
  1439. states.connected += states.completed;
  1440.  
  1441. newState = 'new';
  1442. if (states.failed > 0) {
  1443. newState = 'failed';
  1444. } else if (states.connecting > 0 || states.checking > 0) {
  1445. newState = 'connecting';
  1446. } else if (states.disconnected > 0) {
  1447. newState = 'disconnected';
  1448. } else if (states.new > 0) {
  1449. newState = 'new';
  1450. } else if (states.connecting > 0 || states.completed > 0) {
  1451. newState = 'connected';
  1452. }
  1453.  
  1454. if (newState !== self.iceConnectionState) {
  1455. self.iceConnectionState = newState;
  1456. if (this.oniceconnectionstatechange !== null) {
  1457. this.oniceconnectionstatechange();
  1458. }
  1459. }
  1460. };
  1461.  
  1462. window.RTCPeerConnection.prototype.createOffer = function() {
  1463. var self = this;
  1464. if (this._pendingOffer) {
  1465. throw new Error('createOffer called while there is a pending offer.');
  1466. }
  1467. var offerOptions;
  1468. if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1469. offerOptions = arguments[0];
  1470. } else if (arguments.length === 3) {
  1471. offerOptions = arguments[2];
  1472. }
  1473.  
  1474. var tracks = [];
  1475. var numAudioTracks = 0;
  1476. var numVideoTracks = 0;
  1477. // Default to sendrecv.
  1478. if (this.localStreams.length) {
  1479. numAudioTracks = this.localStreams[0].getAudioTracks().length;
  1480. numVideoTracks = this.localStreams[0].getVideoTracks().length;
  1481. }
  1482. // Determine number of audio and video tracks we need to send/recv.
  1483. if (offerOptions) {
  1484. // Reject Chrome legacy constraints.
  1485. if (offerOptions.mandatory || offerOptions.optional) {
  1486. throw new TypeError(
  1487. 'Legacy mandatory/optional constraints not supported.');
  1488. }
  1489. if (offerOptions.offerToReceiveAudio !== undefined) {
  1490. numAudioTracks = offerOptions.offerToReceiveAudio;
  1491. }
  1492. if (offerOptions.offerToReceiveVideo !== undefined) {
  1493. numVideoTracks = offerOptions.offerToReceiveVideo;
  1494. }
  1495. }
  1496. if (this.localStreams.length) {
  1497. // Push local streams.
  1498. this.localStreams[0].getTracks().forEach(function(track) {
  1499. tracks.push({
  1500. kind: track.kind,
  1501. track: track,
  1502. wantReceive: track.kind === 'audio' ?
  1503. numAudioTracks > 0 : numVideoTracks > 0
  1504. });
  1505. if (track.kind === 'audio') {
  1506. numAudioTracks--;
  1507. } else if (track.kind === 'video') {
  1508. numVideoTracks--;
  1509. }
  1510. });
  1511. }
  1512. // Create M-lines for recvonly streams.
  1513. while (numAudioTracks > 0 || numVideoTracks > 0) {
  1514. if (numAudioTracks > 0) {
  1515. tracks.push({
  1516. kind: 'audio',
  1517. wantReceive: true
  1518. });
  1519. numAudioTracks--;
  1520. }
  1521. if (numVideoTracks > 0) {
  1522. tracks.push({
  1523. kind: 'video',
  1524. wantReceive: true
  1525. });
  1526. numVideoTracks--;
  1527. }
  1528. }
  1529.  
  1530. var sdp = SDPUtils.writeSessionBoilerplate();
  1531. var transceivers = [];
  1532. tracks.forEach(function(mline, sdpMLineIndex) {
  1533. // For each track, create an ice gatherer, ice transport, dtls transport,
  1534. // potentially rtpsender and rtpreceiver.
  1535. var track = mline.track;
  1536. var kind = mline.kind;
  1537. var mid = generateIdentifier();
  1538.  
  1539. var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
  1540.  
  1541. var localCapabilities = RTCRtpSender.getCapabilities(kind);
  1542. var rtpSender;
  1543. var rtpReceiver;
  1544.  
  1545. // generate an ssrc now, to be used later in rtpSender.send
  1546. var sendSsrc = (2 * sdpMLineIndex + 1) * 1001;
  1547. if (track) {
  1548. rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
  1549. }
  1550.  
  1551. if (mline.wantReceive) {
  1552. rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
  1553. }
  1554.  
  1555. transceivers[sdpMLineIndex] = {
  1556. iceGatherer: transports.iceGatherer,
  1557. iceTransport: transports.iceTransport,
  1558. dtlsTransport: transports.dtlsTransport,
  1559. localCapabilities: localCapabilities,
  1560. remoteCapabilities: null,
  1561. rtpSender: rtpSender,
  1562. rtpReceiver: rtpReceiver,
  1563. kind: kind,
  1564. mid: mid,
  1565. sendSsrc: sendSsrc,
  1566. recvSsrc: null
  1567. };
  1568. var transceiver = transceivers[sdpMLineIndex];
  1569. sdp += SDPUtils.writeMediaSection(transceiver,
  1570. transceiver.localCapabilities, 'offer', self.localStreams[0]);
  1571. });
  1572.  
  1573. this._pendingOffer = transceivers;
  1574. var desc = new RTCSessionDescription({
  1575. type: 'offer',
  1576. sdp: sdp
  1577. });
  1578. if (arguments.length && typeof arguments[0] === 'function') {
  1579. window.setTimeout(arguments[0], 0, desc);
  1580. }
  1581. return Promise.resolve(desc);
  1582. };
  1583.  
  1584. window.RTCPeerConnection.prototype.createAnswer = function() {
  1585. var self = this;
  1586. var answerOptions;
  1587. if (arguments.length === 1 && typeof arguments[0] !== 'function') {
  1588. answerOptions = arguments[0];
  1589. } else if (arguments.length === 3) {
  1590. answerOptions = arguments[2];
  1591. }
  1592.  
  1593. var sdp = SDPUtils.writeSessionBoilerplate();
  1594. this.transceivers.forEach(function(transceiver) {
  1595. // Calculate intersection of capabilities.
  1596. var commonCapabilities = self._getCommonCapabilities(
  1597. transceiver.localCapabilities,
  1598. transceiver.remoteCapabilities);
  1599.  
  1600. sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
  1601. 'answer', self.localStreams[0]);
  1602. });
  1603.  
  1604. var desc = new RTCSessionDescription({
  1605. type: 'answer',
  1606. sdp: sdp
  1607. });
  1608. if (arguments.length && typeof arguments[0] === 'function') {
  1609. window.setTimeout(arguments[0], 0, desc);
  1610. }
  1611. return Promise.resolve(desc);
  1612. };
  1613.  
  1614. window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
  1615. var mLineIndex = candidate.sdpMLineIndex;
  1616. if (candidate.sdpMid) {
  1617. for (var i = 0; i < this.transceivers.length; i++) {
  1618. if (this.transceivers[i].mid === candidate.sdpMid) {
  1619. mLineIndex = i;
  1620. break;
  1621. }
  1622. }
  1623. }
  1624. var transceiver = this.transceivers[mLineIndex];
  1625. if (transceiver) {
  1626. var cand = Object.keys(candidate.candidate).length > 0 ?
  1627. SDPUtils.parseCandidate(candidate.candidate) : {};
  1628. // Ignore Chrome's invalid candidates since Edge does not like them.
  1629. if (cand.protocol === 'tcp' && cand.port === 0) {
  1630. return;
  1631. }
  1632. // Ignore RTCP candidates, we assume RTCP-MUX.
  1633. if (cand.component !== '1') {
  1634. return;
  1635. }
  1636. // A dirty hack to make samples work.
  1637. if (cand.type === 'endOfCandidates') {
  1638. cand = {};
  1639. }
  1640. transceiver.iceTransport.addRemoteCandidate(cand);
  1641. }
  1642. if (arguments.length > 1 && typeof arguments[1] === 'function') {
  1643. window.setTimeout(arguments[1], 0);
  1644. }
  1645. return Promise.resolve();
  1646. };
  1647.  
  1648. window.RTCPeerConnection.prototype.getStats = function() {
  1649. var promises = [];
  1650. this.transceivers.forEach(function(transceiver) {
  1651. ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
  1652. 'dtlsTransport'].forEach(function(method) {
  1653. if (transceiver[method]) {
  1654. promises.push(transceiver[method].getStats());
  1655. }
  1656. });
  1657. });
  1658. var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
  1659. arguments[1];
  1660. return new Promise(function(resolve) {
  1661. var results = {};
  1662. Promise.all(promises).then(function(res) {
  1663. res.forEach(function(result) {
  1664. Object.keys(result).forEach(function(id) {
  1665. results[id] = result[id];
  1666. });
  1667. });
  1668. if (cb) {
  1669. window.setTimeout(cb, 0, results);
  1670. }
  1671. resolve(results);
  1672. });
  1673. });
  1674. };
  1675. }
  1676. } else {
  1677. webrtcUtils.log('Browser does not appear to be WebRTC-capable');
  1678. }
  1679.  
  1680. // Polyfill ontrack on browsers that don't yet have it
  1681. if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
  1682. window.RTCPeerConnection.prototype)) {
  1683. Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
  1684. get: function() { return this._ontrack; },
  1685. set: function(f) {
  1686. var self = this;
  1687. if (this._ontrack) {
  1688. this.removeEventListener('track', this._ontrack);
  1689. this.removeEventListener('addstream', this._ontrackpoly);
  1690. }
  1691. this.addEventListener('track', this._ontrack = f);
  1692. this.addEventListener('addstream', this._ontrackpoly = function(e) {
  1693. if (webrtcDetectedBrowser === 'chrome') {
  1694. // onaddstream does not fire when a track is added to an existing stream.
  1695. // but stream.onaddtrack is implemented so we use thたt
  1696. e.stream.addEventListener('addtrack', function(te) {
  1697. var event = new Event('track');
  1698. event.track = te.track;
  1699. event.receiver = {track: te.track};
  1700. event.streams = [e.stream];
  1701. self.dispatchEvent(event);
  1702. });
  1703. }
  1704. e.stream.getTracks().forEach(function(track) {
  1705. var event = new Event('track');
  1706. event.track = track;
  1707. event.receiver = {track: track};
  1708. event.streams = [e.stream];
  1709. this.dispatchEvent(event);
  1710. }.bind(this));
  1711. }.bind(this));
  1712. }
  1713. });
  1714. }
  1715.  
  1716. // Returns the result of getUserMedia as a Promise.
  1717. function requestUserMedia(constraints) {
  1718. return new Promise(function(resolve, reject) {
  1719. getUserMedia(constraints, resolve, reject);
  1720. });
  1721. }
  1722.  
  1723. var webrtcTesting = {};
  1724. try {
  1725. Object.defineProperty(webrtcTesting, 'version', {
  1726. set: function(version) {
  1727. webrtcDetectedVersion = version;
  1728. }
  1729. });
  1730. } catch (e) {}
  1731.  
  1732. if (typeof module !== 'undefined') {
  1733. var RTCPeerConnection;
  1734. var RTCIceCandidate;
  1735. var RTCSessionDescription;
  1736. if (typeof window !== 'undefined') {
  1737. RTCPeerConnection = window.RTCPeerConnection;
  1738. RTCIceCandidate = window.RTCIceCandidate;
  1739. RTCSessionDescription = window.RTCSessionDescription;
  1740. }
  1741. module.exports = {
  1742. RTCPeerConnection: RTCPeerConnection,
  1743. RTCIceCandidate: RTCIceCandidate,
  1744. RTCSessionDescription: RTCSessionDescription,
  1745. getUserMedia: getUserMedia,
  1746. attachMediaStream: attachMediaStream,
  1747. reattachMediaStream: reattachMediaStream,
  1748. webrtcDetectedBrowser: webrtcDetectedBrowser,
  1749. webrtcDetectedVersion: webrtcDetectedVersion,
  1750. webrtcMinimumVersion: webrtcMinimumVersion,
  1751. webrtcTesting: webrtcTesting,
  1752. webrtcUtils: webrtcUtils
  1753. //requestUserMedia: not exposed on purpose.
  1754. //trace: not exposed on purpose.
  1755. };
  1756. } else if ((typeof require === 'function') && (typeof define === 'function')) {
  1757. // Expose objects and functions when RequireJS is doing the loading.
  1758. define([], function() {
  1759. return {
  1760. RTCPeerConnection: window.RTCPeerConnection,
  1761. RTCIceCandidate: window.RTCIceCandidate,
  1762. RTCSessionDescription: window.RTCSessionDescription,
  1763. getUserMedia: getUserMedia,
  1764. attachMediaStream: attachMediaStream,
  1765. reattachMediaStream: reattachMediaStream,
  1766. webrtcDetectedBrowser: webrtcDetectedBrowser,
  1767. webrtcDetectedVersion: webrtcDetectedVersion,
  1768. webrtcMinimumVersion: webrtcMinimumVersion,
  1769. webrtcTesting: webrtcTesting,
  1770. webrtcUtils: webrtcUtils
  1771. //requestUserMedia: not exposed on purpose.
  1772. //trace: not exposed on purpose.
  1773. };
  1774. });
  1775. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement