diff --git a/www/webrtc/index.html b/www/webrtc/index.html
new file mode 100644
index 00000000..a9603957
--- /dev/null
+++ b/www/webrtc/index.html
@@ -0,0 +1,34 @@
+
+
+
+
+ PeerConnection PRANSWER Demo
+
+
+
+
+
+
+ ip_address
+
+
+
+
+
+
+
+
+
+
diff --git a/www/webrtc/js/adapter.js b/www/webrtc/js/adapter.js
new file mode 100644
index 00000000..2562bfd7
--- /dev/null
+++ b/www/webrtc/js/adapter.js
@@ -0,0 +1,2475 @@
+(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o 0 && typeof selector === 'function') {
+ return origGetStats(selector, successCallback);
+ }
+
+ var fixChromeStats_ = function(response) {
+ var standardReport = {};
+ var reports = response.result();
+ reports.forEach(function(report) {
+ var standardStats = {
+ id: report.id,
+ timestamp: report.timestamp,
+ type: report.type
+ };
+ report.names().forEach(function(name) {
+ standardStats[name] = report.stat(name);
+ });
+ standardReport[standardStats.id] = standardStats;
+ });
+
+ return standardReport;
+ };
+
+ if (arguments.length >= 2) {
+ var successCallbackWrapper_ = function(response) {
+ args[1](fixChromeStats_(response));
+ };
+
+ return origGetStats.apply(this, [successCallbackWrapper_,
+ arguments[0]]);
+ }
+
+ // promise-support
+ return new Promise(function(resolve, reject) {
+ if (args.length === 1 && typeof selector === 'object') {
+ origGetStats.apply(self,
+ [function(response) {
+ resolve.apply(null, [fixChromeStats_(response)]);
+ }, reject]);
+ } else {
+ origGetStats.apply(self, [resolve, reject]);
+ }
+ });
+ };
+
+ return pc;
+ };
+ window.RTCPeerConnection.prototype = webkitRTCPeerConnection.prototype;
+
+ // wrap static methods. Currently just generateCertificate.
+ if (webkitRTCPeerConnection.generateCertificate) {
+ Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+ get: function() {
+ return webkitRTCPeerConnection.generateCertificate;
+ }
+ });
+ }
+
+ // add promise support
+ ['createOffer', 'createAnswer'].forEach(function(method) {
+ var nativeMethod = webkitRTCPeerConnection.prototype[method];
+ webkitRTCPeerConnection.prototype[method] = function() {
+ var self = this;
+ if (arguments.length < 1 || (arguments.length === 1 &&
+ typeof(arguments[0]) === 'object')) {
+ var opts = arguments.length === 1 ? arguments[0] : undefined;
+ return new Promise(function(resolve, reject) {
+ nativeMethod.apply(self, [resolve, reject, opts]);
+ });
+ }
+ return nativeMethod.apply(this, arguments);
+ };
+ });
+
+ ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
+ .forEach(function(method) {
+ var nativeMethod = webkitRTCPeerConnection.prototype[method];
+ webkitRTCPeerConnection.prototype[method] = function() {
+ var args = arguments;
+ var self = this;
+ args[0] = new ((method === 'addIceCandidate')?
+ RTCIceCandidate : RTCSessionDescription)(args[0]);
+ return new Promise(function(resolve, reject) {
+ nativeMethod.apply(self, [args[0],
+ function() {
+ resolve();
+ if (args.length >= 2) {
+ args[1].apply(null, []);
+ }
+ },
+ function(err) {
+ reject(err);
+ if (args.length >= 3) {
+ args[2].apply(null, [err]);
+ }
+ }]
+ );
+ });
+ };
+ });
+ },
+
+ // Attach a media stream to an element.
+ attachMediaStream: function(element, stream) {
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
+ if (browserDetails.version >= 43) {
+ element.srcObject = stream;
+ } else if (typeof element.src !== 'undefined') {
+ element.src = URL.createObjectURL(stream);
+ } else {
+ logging('Error attaching stream to element.');
+ }
+ },
+
+ reattachMediaStream: function(to, from) {
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
+ if (browserDetails.version >= 43) {
+ to.srcObject = from.srcObject;
+ } else {
+ to.src = from.src;
+ }
+ }
+};
+
+
+// Expose public methods.
+module.exports = {
+ shimOnTrack: chromeShim.shimOnTrack,
+ shimSourceObject: chromeShim.shimSourceObject,
+ shimPeerConnection: chromeShim.shimPeerConnection,
+ shimGetUserMedia: require('./getusermedia'),
+ attachMediaStream: chromeShim.attachMediaStream,
+ reattachMediaStream: chromeShim.reattachMediaStream
+};
+
+},{"../utils.js":9,"./getusermedia":3}],3:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+var logging = require('../utils.js').log;
+
+// Expose public methods.
+module.exports = function() {
+ var constraintsToChrome_ = function(c) {
+ if (typeof c !== 'object' || c.mandatory || c.optional) {
+ return c;
+ }
+ var cc = {};
+ Object.keys(c).forEach(function(key) {
+ if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
+ return;
+ }
+ var r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
+ if (r.exact !== undefined && typeof r.exact === 'number') {
+ r.min = r.max = r.exact;
+ }
+ var oldname_ = function(prefix, name) {
+ if (prefix) {
+ return prefix + name.charAt(0).toUpperCase() + name.slice(1);
+ }
+ return (name === 'deviceId') ? 'sourceId' : name;
+ };
+ if (r.ideal !== undefined) {
+ cc.optional = cc.optional || [];
+ var oc = {};
+ if (typeof r.ideal === 'number') {
+ oc[oldname_('min', key)] = r.ideal;
+ cc.optional.push(oc);
+ oc = {};
+ oc[oldname_('max', key)] = r.ideal;
+ cc.optional.push(oc);
+ } else {
+ oc[oldname_('', key)] = r.ideal;
+ cc.optional.push(oc);
+ }
+ }
+ if (r.exact !== undefined && typeof r.exact !== 'number') {
+ cc.mandatory = cc.mandatory || {};
+ cc.mandatory[oldname_('', key)] = r.exact;
+ } else {
+ ['min', 'max'].forEach(function(mix) {
+ if (r[mix] !== undefined) {
+ cc.mandatory = cc.mandatory || {};
+ cc.mandatory[oldname_(mix, key)] = r[mix];
+ }
+ });
+ }
+ });
+ if (c.advanced) {
+ cc.optional = (cc.optional || []).concat(c.advanced);
+ }
+ return cc;
+ };
+
+ var getUserMedia_ = function(constraints, onSuccess, onError) {
+ constraints = JSON.parse(JSON.stringify(constraints));
+ if (constraints.audio) {
+ constraints.audio = constraintsToChrome_(constraints.audio);
+ }
+ if (constraints.video) {
+ constraints.video = constraintsToChrome_(constraints.video);
+ }
+ logging('chrome: ' + JSON.stringify(constraints));
+ return navigator.webkitGetUserMedia(constraints, onSuccess, onError);
+ };
+ navigator.getUserMedia = getUserMedia_;
+
+ // Returns the result of getUserMedia as a Promise.
+ var getUserMediaPromise_ = function(constraints) {
+ return new Promise(function(resolve, reject) {
+ navigator.getUserMedia(constraints, resolve, reject);
+ });
+ };
+
+ if (!navigator.mediaDevices) {
+ navigator.mediaDevices = {
+ getUserMedia: getUserMediaPromise_,
+ enumerateDevices: function() {
+ return new Promise(function(resolve) {
+ var kinds = {audio: 'audioinput', video: 'videoinput'};
+ return MediaStreamTrack.getSources(function(devices) {
+ resolve(devices.map(function(device) {
+ return {label: device.label,
+ kind: kinds[device.kind],
+ deviceId: device.id,
+ groupId: ''};
+ }));
+ });
+ });
+ }
+ };
+ }
+
+ // A shim for getUserMedia method on the mediaDevices object.
+ // TODO(KaptenJansson) remove once implemented in Chrome stable.
+ if (!navigator.mediaDevices.getUserMedia) {
+ navigator.mediaDevices.getUserMedia = function(constraints) {
+ return getUserMediaPromise_(constraints);
+ };
+ } else {
+ // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
+ // function which returns a Promise, it does not accept spec-style
+ // constraints.
+ var origGetUserMedia = navigator.mediaDevices.getUserMedia.
+ bind(navigator.mediaDevices);
+ navigator.mediaDevices.getUserMedia = function(c) {
+ if (c) {
+ logging('spec: ' + JSON.stringify(c)); // whitespace for alignment
+ c.audio = constraintsToChrome_(c.audio);
+ c.video = constraintsToChrome_(c.video);
+ logging('chrome: ' + JSON.stringify(c));
+ }
+ return origGetUserMedia(c);
+ }.bind(this);
+ }
+
+ // Dummy devicechange event methods.
+ // TODO(KaptenJansson) remove once implemented in Chrome stable.
+ if (typeof navigator.mediaDevices.addEventListener === 'undefined') {
+ navigator.mediaDevices.addEventListener = function() {
+ logging('Dummy mediaDevices.addEventListener called.');
+ };
+ }
+ if (typeof navigator.mediaDevices.removeEventListener === 'undefined') {
+ navigator.mediaDevices.removeEventListener = function() {
+ logging('Dummy mediaDevices.removeEventListener called.');
+ };
+ }
+};
+
+},{"../utils.js":9}],4:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+// SDP helpers.
+var SDPUtils = {};
+
+// Generate an alphanumeric identifier for cname or mids.
+// TODO: use UUIDs instead? https://gist.github.com/jed/982883
+SDPUtils.generateIdentifier = function() {
+ return Math.random().toString(36).substr(2, 10);
+};
+
+// The RTCP CNAME used by all peerconnections from the same JS.
+SDPUtils.localCName = SDPUtils.generateIdentifier();
+
+// Splits SDP into lines, dealing with both CRLF and LF.
+SDPUtils.splitLines = function(blob) {
+ return blob.trim().split('\n').map(function(line) {
+ return line.trim();
+ });
+};
+// Splits SDP into sessionpart and mediasections. Ensures CRLF.
+SDPUtils.splitSections = function(blob) {
+ var parts = blob.split('\nm=');
+ return parts.map(function(part, index) {
+ return (index > 0 ? 'm=' + part : part).trim() + '\r\n';
+ });
+};
+
+// Returns lines that start with a certain prefix.
+SDPUtils.matchPrefix = function(blob, prefix) {
+ return SDPUtils.splitLines(blob).filter(function(line) {
+ return line.indexOf(prefix) === 0;
+ });
+};
+
+// Parses an ICE candidate line. Sample input:
+// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8
+// rport 55996"
+SDPUtils.parseCandidate = function(line) {
+ var parts;
+ // Parse both variants.
+ if (line.indexOf('a=candidate:') === 0) {
+ parts = line.substring(12).split(' ');
+ } else {
+ parts = line.substring(10).split(' ');
+ }
+
+ var candidate = {
+ foundation: parts[0],
+ component: parts[1],
+ protocol: parts[2].toLowerCase(),
+ priority: parseInt(parts[3], 10),
+ ip: parts[4],
+ port: parseInt(parts[5], 10),
+ // skip parts[6] == 'typ'
+ type: parts[7]
+ };
+
+ for (var i = 8; i < parts.length; i += 2) {
+ switch (parts[i]) {
+ case 'raddr':
+ candidate.relatedAddress = parts[i + 1];
+ break;
+ case 'rport':
+ candidate.relatedPort = parseInt(parts[i + 1], 10);
+ break;
+ case 'tcptype':
+ candidate.tcpType = parts[i + 1];
+ break;
+ default: // Unknown extensions are silently ignored.
+ break;
+ }
+ }
+ return candidate;
+};
+
+// Translates a candidate object into SDP candidate attribute.
+SDPUtils.writeCandidate = function(candidate) {
+ var sdp = [];
+ sdp.push(candidate.foundation);
+ sdp.push(candidate.component);
+ sdp.push(candidate.protocol.toUpperCase());
+ sdp.push(candidate.priority);
+ sdp.push(candidate.ip);
+ sdp.push(candidate.port);
+
+ var type = candidate.type;
+ sdp.push('typ');
+ sdp.push(type);
+ if (type !== 'host' && candidate.relatedAddress &&
+ candidate.relatedPort) {
+ sdp.push('raddr');
+ sdp.push(candidate.relatedAddress); // was: relAddr
+ sdp.push('rport');
+ sdp.push(candidate.relatedPort); // was: relPort
+ }
+ if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') {
+ sdp.push('tcptype');
+ sdp.push(candidate.tcpType);
+ }
+ return 'candidate:' + sdp.join(' ');
+};
+
+// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input:
+// a=rtpmap:111 opus/48000/2
+SDPUtils.parseRtpMap = function(line) {
+ var parts = line.substr(9).split(' ');
+ var parsed = {
+ payloadType: parseInt(parts.shift(), 10) // was: id
+ };
+
+ parts = parts[0].split('/');
+
+ parsed.name = parts[0];
+ parsed.clockRate = parseInt(parts[1], 10); // was: clockrate
+ // was: channels
+ parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1;
+ return parsed;
+};
+
+// Generate an a=rtpmap line from RTCRtpCodecCapability or
+// RTCRtpCodecParameters.
+SDPUtils.writeRtpMap = function(codec) {
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate +
+ (codec.numChannels !== 1 ? '/' + codec.numChannels : '') + '\r\n';
+};
+
+// Parses an a=extmap line (headerextension from RFC 5285). Sample input:
+// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset
+SDPUtils.parseExtmap = function(line) {
+ var parts = line.substr(9).split(' ');
+ return {
+ id: parseInt(parts[0], 10),
+ uri: parts[1]
+ };
+};
+
+// Generates a=extmap line from RTCRtpHeaderExtensionParameters or
+// RTCRtpHeaderExtension.
+SDPUtils.writeExtmap = function(headerExtension) {
+ return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) +
+ ' ' + headerExtension.uri + '\r\n';
+};
+
+// Parses an ftmp line, returns dictionary. Sample input:
+// a=fmtp:96 vbr=on;cng=on
+// Also deals with vbr=on; cng=on
+SDPUtils.parseFmtp = function(line) {
+ var parsed = {};
+ var kv;
+ var parts = line.substr(line.indexOf(' ') + 1).split(';');
+ for (var j = 0; j < parts.length; j++) {
+ kv = parts[j].trim().split('=');
+ parsed[kv[0].trim()] = kv[1];
+ }
+ return parsed;
+};
+
+// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeFmtp = function(codec) {
+ var line = '';
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ if (codec.parameters && Object.keys(codec.parameters).length) {
+ var params = [];
+ Object.keys(codec.parameters).forEach(function(param) {
+ params.push(param + '=' + codec.parameters[param]);
+ });
+ line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n';
+ }
+ return line;
+};
+
+// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input:
+// a=rtcp-fb:98 nack rpsi
+SDPUtils.parseRtcpFb = function(line) {
+ var parts = line.substr(line.indexOf(' ') + 1).split(' ');
+ return {
+ type: parts.shift(),
+ parameter: parts.join(' ')
+ };
+};
+// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters.
+SDPUtils.writeRtcpFb = function(codec) {
+ var lines = '';
+ var pt = codec.payloadType;
+ if (codec.preferredPayloadType !== undefined) {
+ pt = codec.preferredPayloadType;
+ }
+ if (codec.rtcpFeedback && codec.rtcpFeedback.length) {
+ // FIXME: special handling for trr-int?
+ codec.rtcpFeedback.forEach(function(fb) {
+ lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + ' ' + fb.parameter +
+ '\r\n';
+ });
+ }
+ return lines;
+};
+
+// Parses an RFC 5576 ssrc media attribute. Sample input:
+// a=ssrc:3735928559 cname:something
+SDPUtils.parseSsrcMedia = function(line) {
+ var sp = line.indexOf(' ');
+ var parts = {
+ ssrc: parseInt(line.substr(7, sp - 7), 10)
+ };
+ var colon = line.indexOf(':', sp);
+ if (colon > -1) {
+ parts.attribute = line.substr(sp + 1, colon - sp - 1);
+ parts.value = line.substr(colon + 1);
+ } else {
+ parts.attribute = line.substr(sp + 1);
+ }
+ return parts;
+};
+
+// Extracts DTLS parameters from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+// get the fingerprint line as input. See also getIceParameters.
+SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) {
+ var lines = SDPUtils.splitLines(mediaSection);
+ // Search in session part, too.
+ lines = lines.concat(SDPUtils.splitLines(sessionpart));
+ var fpLine = lines.filter(function(line) {
+ return line.indexOf('a=fingerprint:') === 0;
+ })[0].substr(14);
+ // Note: a=setup line is ignored since we use the 'auto' role.
+ var dtlsParameters = {
+ role: 'auto',
+ fingerprints: [{
+ algorithm: fpLine.split(' ')[0],
+ value: fpLine.split(' ')[1]
+ }]
+ };
+ return dtlsParameters;
+};
+
+// Serializes DTLS parameters to SDP.
+SDPUtils.writeDtlsParameters = function(params, setupType) {
+ var sdp = 'a=setup:' + setupType + '\r\n';
+ params.fingerprints.forEach(function(fp) {
+ sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n';
+ });
+ return sdp;
+};
+// Parses ICE information from SDP media section or sessionpart.
+// FIXME: for consistency with other functions this should only
+// get the ice-ufrag and ice-pwd lines as input.
+SDPUtils.getIceParameters = function(mediaSection, sessionpart) {
+ var lines = SDPUtils.splitLines(mediaSection);
+ // Search in session part, too.
+ lines = lines.concat(SDPUtils.splitLines(sessionpart));
+ var iceParameters = {
+ usernameFragment: lines.filter(function(line) {
+ return line.indexOf('a=ice-ufrag:') === 0;
+ })[0].substr(12),
+ password: lines.filter(function(line) {
+ return line.indexOf('a=ice-pwd:') === 0;
+ })[0].substr(10)
+ };
+ return iceParameters;
+};
+
+// Serializes ICE parameters to SDP.
+SDPUtils.writeIceParameters = function(params) {
+ return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' +
+ 'a=ice-pwd:' + params.password + '\r\n';
+};
+
+// Parses the SDP media section and returns RTCRtpParameters.
+SDPUtils.parseRtpParameters = function(mediaSection) {
+ var description = {
+ codecs: [],
+ headerExtensions: [],
+ fecMechanisms: [],
+ rtcp: []
+ };
+ var lines = SDPUtils.splitLines(mediaSection);
+ var mline = lines[0].split(' ');
+ for (var i = 3; i < mline.length; i++) { // find all codecs from mline[3..]
+ var pt = mline[i];
+ var rtpmapline = SDPUtils.matchPrefix(
+ mediaSection, 'a=rtpmap:' + pt + ' ')[0];
+ if (rtpmapline) {
+ var codec = SDPUtils.parseRtpMap(rtpmapline);
+ var fmtps = SDPUtils.matchPrefix(
+ mediaSection, 'a=fmtp:' + pt + ' ');
+ // Only the first a=fmtp: is considered.
+ codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {};
+ codec.rtcpFeedback = SDPUtils.matchPrefix(
+ mediaSection, 'a=rtcp-fb:' + pt + ' ')
+ .map(SDPUtils.parseRtcpFb);
+ description.codecs.push(codec);
+ // parse FEC mechanisms from rtpmap lines.
+ switch (codec.name.toUpperCase()) {
+ case 'RED':
+ case 'ULPFEC':
+ description.fecMechanisms.push(codec.name.toUpperCase());
+ break;
+ default: // only RED and ULPFEC are recognized as FEC mechanisms.
+ break;
+ }
+ }
+ }
+ SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) {
+ description.headerExtensions.push(SDPUtils.parseExtmap(line));
+ });
+ // FIXME: parse rtcp.
+ return description;
+};
+
+// Generates parts of the SDP media section describing the capabilities /
+// parameters.
+SDPUtils.writeRtpDescription = function(kind, caps) {
+ var sdp = '';
+
+ // Build the mline.
+ sdp += 'm=' + kind + ' ';
+ sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs.
+ sdp += ' UDP/TLS/RTP/SAVPF ';
+ sdp += caps.codecs.map(function(codec) {
+ if (codec.preferredPayloadType !== undefined) {
+ return codec.preferredPayloadType;
+ }
+ return codec.payloadType;
+ }).join(' ') + '\r\n';
+
+ sdp += 'c=IN IP4 0.0.0.0\r\n';
+ sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n';
+
+ // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb.
+ caps.codecs.forEach(function(codec) {
+ sdp += SDPUtils.writeRtpMap(codec);
+ sdp += SDPUtils.writeFmtp(codec);
+ sdp += SDPUtils.writeRtcpFb(codec);
+ });
+ // FIXME: add headerExtensions, fecMechanismş and rtcp.
+ sdp += 'a=rtcp-mux\r\n';
+ return sdp;
+};
+
+// Parses the SDP media section and returns an array of
+// RTCRtpEncodingParameters.
+SDPUtils.parseRtpEncodingParameters = function(mediaSection) {
+ var encodingParameters = [];
+ var description = SDPUtils.parseRtpParameters(mediaSection);
+ var hasRed = description.fecMechanisms.indexOf('RED') !== -1;
+ var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1;
+
+ // filter a=ssrc:... cname:, ignore PlanB-msid
+ var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+ .map(function(line) {
+ return SDPUtils.parseSsrcMedia(line);
+ })
+ .filter(function(parts) {
+ return parts.attribute === 'cname';
+ });
+ var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc;
+ var secondarySsrc;
+
+ var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID')
+ .map(function(line) {
+ var parts = line.split(' ');
+ parts.shift();
+ return parts.map(function(part) {
+ return parseInt(part, 10);
+ });
+ });
+ if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) {
+ secondarySsrc = flows[0][1];
+ }
+
+ description.codecs.forEach(function(codec) {
+ if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) {
+ var encParam = {
+ ssrc: primarySsrc,
+ codecPayloadType: parseInt(codec.parameters.apt, 10),
+ rtx: {
+ ssrc: secondarySsrc
+ }
+ };
+ encodingParameters.push(encParam);
+ if (hasRed) {
+ encParam = JSON.parse(JSON.stringify(encParam));
+ encParam.fec = {
+ ssrc: secondarySsrc,
+ mechanism: hasUlpfec ? 'red+ulpfec' : 'red'
+ };
+ encodingParameters.push(encParam);
+ }
+ }
+ });
+ if (encodingParameters.length === 0 && primarySsrc) {
+ encodingParameters.push({
+ ssrc: primarySsrc
+ });
+ }
+
+ // we support both b=AS and b=TIAS but interpret AS as TIAS.
+ var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b=');
+ if (bandwidth.length) {
+ if (bandwidth[0].indexOf('b=TIAS:') === 0) {
+ bandwidth = parseInt(bandwidth[0].substr(7), 10);
+ } else if (bandwidth[0].indexOf('b=AS:') === 0) {
+ bandwidth = parseInt(bandwidth[0].substr(5), 10);
+ }
+ encodingParameters.forEach(function(params) {
+ params.maxBitrate = bandwidth;
+ });
+ }
+ return encodingParameters;
+};
+
+SDPUtils.writeSessionBoilerplate = function() {
+ // FIXME: sess-id should be an NTP timestamp.
+ return 'v=0\r\n' +
+ 'o=thisisadapterortc 8169639915646943137 2 IN IP4 127.0.0.1\r\n' +
+ 's=-\r\n' +
+ 't=0 0\r\n';
+};
+
+SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) {
+ var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps);
+
+ // Map ICE parameters (ufrag, pwd) to SDP.
+ sdp += SDPUtils.writeIceParameters(
+ transceiver.iceGatherer.getLocalParameters());
+
+ // Map DTLS parameters to SDP.
+ sdp += SDPUtils.writeDtlsParameters(
+ transceiver.dtlsTransport.getLocalParameters(),
+ type === 'offer' ? 'actpass' : 'active');
+
+ sdp += 'a=mid:' + transceiver.mid + '\r\n';
+
+ if (transceiver.rtpSender && transceiver.rtpReceiver) {
+ sdp += 'a=sendrecv\r\n';
+ } else if (transceiver.rtpSender) {
+ sdp += 'a=sendonly\r\n';
+ } else if (transceiver.rtpReceiver) {
+ sdp += 'a=recvonly\r\n';
+ } else {
+ sdp += 'a=inactive\r\n';
+ }
+
+ // FIXME: for RTX there might be multiple SSRCs. Not implemented in Edge yet.
+ if (transceiver.rtpSender) {
+ var msid = 'msid:' + stream.id + ' ' +
+ transceiver.rtpSender.track.id + '\r\n';
+ sdp += 'a=' + msid;
+ sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+ ' ' + msid;
+ }
+ // FIXME: this should be written by writeRtpDescription.
+ sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc +
+ ' cname:' + SDPUtils.localCName + '\r\n';
+ return sdp;
+};
+
+// Gets the direction from the mediaSection or the sessionpart.
+SDPUtils.getDirection = function(mediaSection, sessionpart) {
+ // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv.
+ var lines = SDPUtils.splitLines(mediaSection);
+ for (var i = 0; i < lines.length; i++) {
+ switch (lines[i]) {
+ case 'a=sendrecv':
+ case 'a=sendonly':
+ case 'a=recvonly':
+ case 'a=inactive':
+ return lines[i].substr(2);
+ default:
+ // FIXME: What should happen here?
+ }
+ }
+ if (sessionpart) {
+ return SDPUtils.getDirection(sessionpart);
+ }
+ return 'sendrecv';
+};
+
+// Expose public methods.
+module.exports = SDPUtils;
+
+},{}],5:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var SDPUtils = require('./edge_sdp');
+var logging = require('../utils').log;
+
+var edgeShim = {
+ shimPeerConnection: function() {
+ if (window.RTCIceGatherer) {
+ // ORTC defines an RTCIceCandidate object but no constructor.
+ // Not implemented in Edge.
+ if (!window.RTCIceCandidate) {
+ window.RTCIceCandidate = function(args) {
+ return args;
+ };
+ }
+ // ORTC does not have a session description object but
+ // other browsers (i.e. Chrome) that will support both PC and ORTC
+ // in the future might have this defined already.
+ if (!window.RTCSessionDescription) {
+ window.RTCSessionDescription = function(args) {
+ return args;
+ };
+ }
+ }
+
+ window.RTCPeerConnection = function(config) {
+ var self = this;
+
+ var _eventTarget = document.createDocumentFragment();
+ ['addEventListener', 'removeEventListener', 'dispatchEvent']
+ .forEach(function(method) {
+ self[method] = _eventTarget[method].bind(_eventTarget);
+ });
+
+ this.onicecandidate = null;
+ this.onaddstream = null;
+ this.ontrack = null;
+ this.onremovestream = null;
+ this.onsignalingstatechange = null;
+ this.oniceconnectionstatechange = null;
+ this.onnegotiationneeded = null;
+ this.ondatachannel = null;
+
+ this.localStreams = [];
+ this.remoteStreams = [];
+ this.getLocalStreams = function() {
+ return self.localStreams;
+ };
+ this.getRemoteStreams = function() {
+ return self.remoteStreams;
+ };
+
+ this.localDescription = new RTCSessionDescription({
+ type: '',
+ sdp: ''
+ });
+ this.remoteDescription = new RTCSessionDescription({
+ type: '',
+ sdp: ''
+ });
+ this.signalingState = 'stable';
+ this.iceConnectionState = 'new';
+ this.iceGatheringState = 'new';
+
+ this.iceOptions = {
+ gatherPolicy: 'all',
+ iceServers: []
+ };
+ if (config && config.iceTransportPolicy) {
+ switch (config.iceTransportPolicy) {
+ case 'all':
+ case 'relay':
+ this.iceOptions.gatherPolicy = config.iceTransportPolicy;
+ break;
+ case 'none':
+ // FIXME: remove once implementation and spec have added this.
+ throw new TypeError('iceTransportPolicy "none" not supported');
+ default:
+ // don't set iceTransportPolicy.
+ break;
+ }
+ }
+ if (config && config.iceServers) {
+ // Edge does not like
+ // 1) stun:
+ // 2) turn: that does not have all of turn:host:port?transport=udp
+ this.iceOptions.iceServers = config.iceServers.filter(function(server) {
+ if (server && server.urls) {
+ server.urls = server.urls.filter(function(url) {
+ return url.indexOf('turn:') === 0 &&
+ url.indexOf('transport=udp') !== -1;
+ })[0];
+ return !!server.urls;
+ }
+ return false;
+ });
+ }
+
+ // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ...
+ // everything that is needed to describe a SDP m-line.
+ this.transceivers = [];
+
+ // since the iceGatherer is currently created in createOffer but we
+ // must not emit candidates until after setLocalDescription we buffer
+ // them in this array.
+ this._localIceCandidatesBuffer = [];
+ };
+
+ window.RTCPeerConnection.prototype._emitBufferedCandidates = function() {
+ var self = this;
+ var sections = SDPUtils.splitSections(self.localDescription.sdp);
+ // FIXME: need to apply ice candidates in a way which is async but
+ // in-order
+ this._localIceCandidatesBuffer.forEach(function(event) {
+ var end = !event.candidate || Object.keys(event.candidate).length === 0;
+ if (end) {
+ for (var j = 1; j < sections.length; j++) {
+ if (sections[j].indexOf('\r\na=end-of-candidates\r\n') === -1) {
+ sections[j] += 'a=end-of-candidates\r\n';
+ }
+ }
+ } else if (event.candidate.candidate.indexOf('typ endOfCandidates')
+ === -1) {
+ sections[event.candidate.sdpMLineIndex + 1] +=
+ 'a=' + event.candidate.candidate + '\r\n';
+ }
+ self.localDescription.sdp = sections.join('');
+ self.dispatchEvent(event);
+ if (self.onicecandidate !== null) {
+ self.onicecandidate(event);
+ }
+ if (!event.candidate && self.iceGatheringState !== 'complete') {
+ var complete = self.transceivers.every(function(transceiver) {
+ return transceiver.iceGatherer &&
+ transceiver.iceGatherer.state === 'completed';
+ });
+ if (complete) {
+ self.iceGatheringState = 'complete';
+ }
+ }
+ });
+ this._localIceCandidatesBuffer = [];
+ };
+
+ window.RTCPeerConnection.prototype.addStream = function(stream) {
+ // Clone is necessary for local demos mostly, attaching directly
+ // to two different senders does not work (build 10547).
+ this.localStreams.push(stream.clone());
+ this._maybeFireNegotiationNeeded();
+ };
+
+ window.RTCPeerConnection.prototype.removeStream = function(stream) {
+ var idx = this.localStreams.indexOf(stream);
+ if (idx > -1) {
+ this.localStreams.splice(idx, 1);
+ this._maybeFireNegotiationNeeded();
+ }
+ };
+
+ // Determines the intersection of local and remote capabilities.
+ window.RTCPeerConnection.prototype._getCommonCapabilities =
+ function(localCapabilities, remoteCapabilities) {
+ var commonCapabilities = {
+ codecs: [],
+ headerExtensions: [],
+ fecMechanisms: []
+ };
+ localCapabilities.codecs.forEach(function(lCodec) {
+ for (var i = 0; i < remoteCapabilities.codecs.length; i++) {
+ var rCodec = remoteCapabilities.codecs[i];
+ if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() &&
+ lCodec.clockRate === rCodec.clockRate &&
+ lCodec.numChannels === rCodec.numChannels) {
+ // push rCodec so we reply with offerer payload type
+ commonCapabilities.codecs.push(rCodec);
+
+ // FIXME: also need to determine intersection between
+ // .rtcpFeedback and .parameters
+ break;
+ }
+ }
+ });
+
+ localCapabilities.headerExtensions
+ .forEach(function(lHeaderExtension) {
+ for (var i = 0; i < remoteCapabilities.headerExtensions.length;
+ i++) {
+ var rHeaderExtension = remoteCapabilities.headerExtensions[i];
+ if (lHeaderExtension.uri === rHeaderExtension.uri) {
+ commonCapabilities.headerExtensions.push(rHeaderExtension);
+ break;
+ }
+ }
+ });
+
+ // FIXME: fecMechanisms
+ return commonCapabilities;
+ };
+
+ // Create ICE gatherer, ICE transport and DTLS transport.
+ window.RTCPeerConnection.prototype._createIceAndDtlsTransports =
+ function(mid, sdpMLineIndex) {
+ var self = this;
+ var iceGatherer = new RTCIceGatherer(self.iceOptions);
+ var iceTransport = new RTCIceTransport(iceGatherer);
+ iceGatherer.onlocalcandidate = function(evt) {
+ var event = new Event('icecandidate');
+ event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex};
+
+ var cand = evt.candidate;
+ var end = !cand || Object.keys(cand).length === 0;
+ // Edge emits an empty object for RTCIceCandidateComplete‥
+ if (end) {
+ // polyfill since RTCIceGatherer.state is not implemented in
+ // Edge 10547 yet.
+ if (iceGatherer.state === undefined) {
+ iceGatherer.state = 'completed';
+ }
+
+ // Emit a candidate with type endOfCandidates to make the samples
+ // work. Edge requires addIceCandidate with this empty candidate
+ // to start checking. The real solution is to signal
+ // end-of-candidates to the other side when getting the null
+ // candidate but some apps (like the samples) don't do that.
+ event.candidate.candidate =
+ 'candidate:1 1 udp 1 0.0.0.0 9 typ endOfCandidates';
+ } else {
+ // RTCIceCandidate doesn't have a component, needs to be added
+ cand.component = iceTransport.component === 'RTCP' ? 2 : 1;
+ event.candidate.candidate = SDPUtils.writeCandidate(cand);
+ }
+
+ var complete = self.transceivers.every(function(transceiver) {
+ return transceiver.iceGatherer &&
+ transceiver.iceGatherer.state === 'completed';
+ });
+
+ // Emit candidate if localDescription is set.
+ // Also emits null candidate when all gatherers are complete.
+ switch (self.iceGatheringState) {
+ case 'new':
+ self._localIceCandidatesBuffer.push(event);
+ if (end && complete) {
+ self._localIceCandidatesBuffer.push(
+ new Event('icecandidate'));
+ }
+ break;
+ case 'gathering':
+ self._emitBufferedCandidates();
+ self.dispatchEvent(event);
+ if (self.onicecandidate !== null) {
+ self.onicecandidate(event);
+ }
+ if (complete) {
+ self.dispatchEvent(new Event('icecandidate'));
+ if (self.onicecandidate !== null) {
+ self.onicecandidate(new Event('icecandidate'));
+ }
+ self.iceGatheringState = 'complete';
+ }
+ break;
+ case 'complete':
+ // should not happen... currently!
+ break;
+ default: // no-op.
+ break;
+ }
+ };
+ iceTransport.onicestatechange = function() {
+ self._updateConnectionState();
+ };
+
+ var dtlsTransport = new RTCDtlsTransport(iceTransport);
+ dtlsTransport.ondtlsstatechange = function() {
+ self._updateConnectionState();
+ };
+ dtlsTransport.onerror = function() {
+ // onerror does not set state to failed by itself.
+ dtlsTransport.state = 'failed';
+ self._updateConnectionState();
+ };
+
+ return {
+ iceGatherer: iceGatherer,
+ iceTransport: iceTransport,
+ dtlsTransport: dtlsTransport
+ };
+ };
+
+ // Start the RTP Sender and Receiver for a transceiver.
+ window.RTCPeerConnection.prototype._transceive = function(transceiver,
+ send, recv) {
+ var params = this._getCommonCapabilities(transceiver.localCapabilities,
+ transceiver.remoteCapabilities);
+ if (send && transceiver.rtpSender) {
+ params.encodings = transceiver.sendEncodingParameters;
+ params.rtcp = {
+ cname: SDPUtils.localCName
+ };
+ if (transceiver.recvEncodingParameters.length) {
+ params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc;
+ }
+ transceiver.rtpSender.send(params);
+ }
+ if (recv && transceiver.rtpReceiver) {
+ params.encodings = transceiver.recvEncodingParameters;
+ params.rtcp = {
+ cname: transceiver.cname
+ };
+ if (transceiver.sendEncodingParameters.length) {
+ params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc;
+ }
+ transceiver.rtpReceiver.receive(params);
+ }
+ };
+
+ window.RTCPeerConnection.prototype.setLocalDescription =
+ function(description) {
+ var self = this;
+ var sections;
+ var sessionpart;
+ if (description.type === 'offer') {
+ // FIXME: What was the purpose of this empty if statement?
+ // if (!this._pendingOffer) {
+ // } else {
+ if (this._pendingOffer) {
+ // VERY limited support for SDP munging. Limited to:
+ // * changing the order of codecs
+ sections = SDPUtils.splitSections(description.sdp);
+ sessionpart = sections.shift();
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
+ var caps = SDPUtils.parseRtpParameters(mediaSection);
+ self._pendingOffer[sdpMLineIndex].localCapabilities = caps;
+ });
+ this.transceivers = this._pendingOffer;
+ delete this._pendingOffer;
+ }
+ } else if (description.type === 'answer') {
+ sections = SDPUtils.splitSections(self.remoteDescription.sdp);
+ sessionpart = sections.shift();
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
+ var transceiver = self.transceivers[sdpMLineIndex];
+ var iceGatherer = transceiver.iceGatherer;
+ var iceTransport = transceiver.iceTransport;
+ var dtlsTransport = transceiver.dtlsTransport;
+ var localCapabilities = transceiver.localCapabilities;
+ var remoteCapabilities = transceiver.remoteCapabilities;
+ var rejected = mediaSection.split('\n', 1)[0]
+ .split(' ', 2)[1] === '0';
+
+ if (!rejected) {
+ var remoteIceParameters = SDPUtils.getIceParameters(
+ mediaSection, sessionpart);
+ iceTransport.start(iceGatherer, remoteIceParameters,
+ 'controlled');
+
+ var remoteDtlsParameters = SDPUtils.getDtlsParameters(
+ mediaSection, sessionpart);
+ dtlsTransport.start(remoteDtlsParameters);
+
+ // Calculate intersection of capabilities.
+ var params = self._getCommonCapabilities(localCapabilities,
+ remoteCapabilities);
+
+ // Start the RTCRtpSender. The RTCRtpReceiver for this
+ // transceiver has already been started in setRemoteDescription.
+ self._transceive(transceiver,
+ params.codecs.length > 0,
+ false);
+ }
+ });
+ }
+
+ this.localDescription = {
+ type: description.type,
+ sdp: description.sdp
+ };
+ switch (description.type) {
+ case 'offer':
+ this._updateSignalingState('have-local-offer');
+ break;
+ case 'answer':
+ this._updateSignalingState('stable');
+ break;
+ default:
+ throw new TypeError('unsupported type "' + description.type +
+ '"');
+ }
+
+ // If a success callback was provided, emit ICE candidates after it
+ // has been executed. Otherwise, emit callback after the Promise is
+ // resolved.
+ var hasCallback = arguments.length > 1 &&
+ typeof arguments[1] === 'function';
+ if (hasCallback) {
+ var cb = arguments[1];
+ window.setTimeout(function() {
+ cb();
+ if (self.iceGatheringState === 'new') {
+ self.iceGatheringState = 'gathering';
+ }
+ self._emitBufferedCandidates();
+ }, 0);
+ }
+ var p = Promise.resolve();
+ p.then(function() {
+ if (!hasCallback) {
+ if (self.iceGatheringState === 'new') {
+ self.iceGatheringState = 'gathering';
+ }
+ // Usually candidates will be emitted earlier.
+ window.setTimeout(self._emitBufferedCandidates.bind(self), 500);
+ }
+ });
+ return p;
+ };
+
+ window.RTCPeerConnection.prototype.setRemoteDescription =
+ function(description) {
+ var self = this;
+ var stream = new MediaStream();
+ var receiverList = [];
+ var sections = SDPUtils.splitSections(description.sdp);
+ var sessionpart = sections.shift();
+ sections.forEach(function(mediaSection, sdpMLineIndex) {
+ var lines = SDPUtils.splitLines(mediaSection);
+ var mline = lines[0].substr(2).split(' ');
+ var kind = mline[0];
+ var rejected = mline[1] === '0';
+ var direction = SDPUtils.getDirection(mediaSection, sessionpart);
+
+ var transceiver;
+ var iceGatherer;
+ var iceTransport;
+ var dtlsTransport;
+ var rtpSender;
+ var rtpReceiver;
+ var sendEncodingParameters;
+ var recvEncodingParameters;
+ var localCapabilities;
+
+ var track;
+ // FIXME: ensure the mediaSection has rtcp-mux set.
+ var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection);
+ var remoteIceParameters;
+ var remoteDtlsParameters;
+ if (!rejected) {
+ remoteIceParameters = SDPUtils.getIceParameters(mediaSection,
+ sessionpart);
+ remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection,
+ sessionpart);
+ }
+ recvEncodingParameters =
+ SDPUtils.parseRtpEncodingParameters(mediaSection);
+
+ var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:');
+ if (mid.length) {
+ mid = mid[0].substr(6);
+ } else {
+ mid = SDPUtils.generateIdentifier();
+ }
+
+ var cname;
+ // Gets the first SSRC. Note that with RTX there might be multiple
+ // SSRCs.
+ var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:')
+ .map(function(line) {
+ return SDPUtils.parseSsrcMedia(line);
+ })
+ .filter(function(obj) {
+ return obj.attribute === 'cname';
+ })[0];
+ if (remoteSsrc) {
+ cname = remoteSsrc.value;
+ }
+
+ var isComplete = SDPUtils.matchPrefix(mediaSection,
+ 'a=end-of-candidates').length > 0;
+ var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:')
+ .map(function(cand) {
+ return SDPUtils.parseCandidate(cand);
+ })
+ .filter(function(cand) {
+ return cand.component === '1';
+ });
+ if (description.type === 'offer' && !rejected) {
+ var transports = self._createIceAndDtlsTransports(mid,
+ sdpMLineIndex);
+ if (isComplete) {
+ transports.iceTransport.setRemoteCandidates(cands);
+ }
+
+ localCapabilities = RTCRtpReceiver.getCapabilities(kind);
+ sendEncodingParameters = [{
+ ssrc: (2 * sdpMLineIndex + 2) * 1001
+ }];
+
+ rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
+
+ track = rtpReceiver.track;
+ receiverList.push([track, rtpReceiver]);
+ // FIXME: not correct when there are multiple streams but that is
+ // not currently supported in this shim.
+ stream.addTrack(track);
+
+ // FIXME: look at direction.
+ if (self.localStreams.length > 0 &&
+ self.localStreams[0].getTracks().length >= sdpMLineIndex) {
+ // FIXME: actually more complicated, needs to match types etc
+ var localtrack = self.localStreams[0]
+ .getTracks()[sdpMLineIndex];
+ rtpSender = new RTCRtpSender(localtrack,
+ transports.dtlsTransport);
+ }
+
+ self.transceivers[sdpMLineIndex] = {
+ iceGatherer: transports.iceGatherer,
+ iceTransport: transports.iceTransport,
+ dtlsTransport: transports.dtlsTransport,
+ localCapabilities: localCapabilities,
+ remoteCapabilities: remoteCapabilities,
+ rtpSender: rtpSender,
+ rtpReceiver: rtpReceiver,
+ kind: kind,
+ mid: mid,
+ cname: cname,
+ sendEncodingParameters: sendEncodingParameters,
+ recvEncodingParameters: recvEncodingParameters
+ };
+ // Start the RTCRtpReceiver now. The RTPSender is started in
+ // setLocalDescription.
+ self._transceive(self.transceivers[sdpMLineIndex],
+ false,
+ direction === 'sendrecv' || direction === 'sendonly');
+ } else if (description.type === 'answer' && !rejected) {
+ transceiver = self.transceivers[sdpMLineIndex];
+ iceGatherer = transceiver.iceGatherer;
+ iceTransport = transceiver.iceTransport;
+ dtlsTransport = transceiver.dtlsTransport;
+ rtpSender = transceiver.rtpSender;
+ rtpReceiver = transceiver.rtpReceiver;
+ sendEncodingParameters = transceiver.sendEncodingParameters;
+ localCapabilities = transceiver.localCapabilities;
+
+ self.transceivers[sdpMLineIndex].recvEncodingParameters =
+ recvEncodingParameters;
+ self.transceivers[sdpMLineIndex].remoteCapabilities =
+ remoteCapabilities;
+ self.transceivers[sdpMLineIndex].cname = cname;
+
+ if (isComplete) {
+ iceTransport.setRemoteCandidates(cands);
+ }
+ iceTransport.start(iceGatherer, remoteIceParameters,
+ 'controlling');
+ dtlsTransport.start(remoteDtlsParameters);
+
+ self._transceive(transceiver,
+ direction === 'sendrecv' || direction === 'recvonly',
+ direction === 'sendrecv' || direction === 'sendonly');
+
+ if (rtpReceiver &&
+ (direction === 'sendrecv' || direction === 'sendonly')) {
+ track = rtpReceiver.track;
+ receiverList.push([track, rtpReceiver]);
+ stream.addTrack(track);
+ } else {
+ // FIXME: actually the receiver should be created later.
+ delete transceiver.rtpReceiver;
+ }
+ }
+ });
+
+ this.remoteDescription = {
+ type: description.type,
+ sdp: description.sdp
+ };
+ switch (description.type) {
+ case 'offer':
+ this._updateSignalingState('have-remote-offer');
+ break;
+ case 'answer':
+ this._updateSignalingState('stable');
+ break;
+ default:
+ throw new TypeError('unsupported type "' + description.type +
+ '"');
+ }
+ if (stream.getTracks().length) {
+ self.remoteStreams.push(stream);
+ window.setTimeout(function() {
+ var event = new Event('addstream');
+ event.stream = stream;
+ self.dispatchEvent(event);
+ if (self.onaddstream !== null) {
+ window.setTimeout(function() {
+ self.onaddstream(event);
+ }, 0);
+ }
+
+ receiverList.forEach(function(item) {
+ var track = item[0];
+ var receiver = item[1];
+ var trackEvent = new Event('track');
+ trackEvent.track = track;
+ trackEvent.receiver = receiver;
+ trackEvent.streams = [stream];
+ self.dispatchEvent(event);
+ if (self.ontrack !== null) {
+ window.setTimeout(function() {
+ self.ontrack(trackEvent);
+ }, 0);
+ }
+ });
+ }, 0);
+ }
+ if (arguments.length > 1 && typeof arguments[1] === 'function') {
+ window.setTimeout(arguments[1], 0);
+ }
+ return Promise.resolve();
+ };
+
+ window.RTCPeerConnection.prototype.close = function() {
+ this.transceivers.forEach(function(transceiver) {
+ /* not yet
+ if (transceiver.iceGatherer) {
+ transceiver.iceGatherer.close();
+ }
+ */
+ if (transceiver.iceTransport) {
+ transceiver.iceTransport.stop();
+ }
+ if (transceiver.dtlsTransport) {
+ transceiver.dtlsTransport.stop();
+ }
+ if (transceiver.rtpSender) {
+ transceiver.rtpSender.stop();
+ }
+ if (transceiver.rtpReceiver) {
+ transceiver.rtpReceiver.stop();
+ }
+ });
+ // FIXME: clean up tracks, local streams, remote streams, etc
+ this._updateSignalingState('closed');
+ };
+
+ // Update the signaling state.
+ window.RTCPeerConnection.prototype._updateSignalingState =
+ function(newState) {
+ this.signalingState = newState;
+ var event = new Event('signalingstatechange');
+ this.dispatchEvent(event);
+ if (this.onsignalingstatechange !== null) {
+ this.onsignalingstatechange(event);
+ }
+ };
+
+ // Determine whether to fire the negotiationneeded event.
+ window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded =
+ function() {
+ // Fire away (for now).
+ var event = new Event('negotiationneeded');
+ this.dispatchEvent(event);
+ if (this.onnegotiationneeded !== null) {
+ this.onnegotiationneeded(event);
+ }
+ };
+
+ // Update the connection state.
+ window.RTCPeerConnection.prototype._updateConnectionState = function() {
+ var self = this;
+ var newState;
+ var states = {
+ 'new': 0,
+ closed: 0,
+ connecting: 0,
+ checking: 0,
+ connected: 0,
+ completed: 0,
+ failed: 0
+ };
+ this.transceivers.forEach(function(transceiver) {
+ states[transceiver.iceTransport.state]++;
+ states[transceiver.dtlsTransport.state]++;
+ });
+ // ICETransport.completed and connected are the same for this purpose.
+ states.connected += states.completed;
+
+ newState = 'new';
+ if (states.failed > 0) {
+ newState = 'failed';
+ } else if (states.connecting > 0 || states.checking > 0) {
+ newState = 'connecting';
+ } else if (states.disconnected > 0) {
+ newState = 'disconnected';
+ } else if (states.new > 0) {
+ newState = 'new';
+ } else if (states.connected > 0 || states.completed > 0) {
+ newState = 'connected';
+ }
+
+ if (newState !== self.iceConnectionState) {
+ self.iceConnectionState = newState;
+ var event = new Event('iceconnectionstatechange');
+ this.dispatchEvent(event);
+ if (this.oniceconnectionstatechange !== null) {
+ this.oniceconnectionstatechange(event);
+ }
+ }
+ };
+
+ window.RTCPeerConnection.prototype.createOffer = function() {
+ var self = this;
+ if (this._pendingOffer) {
+ throw new Error('createOffer called while there is a pending offer.');
+ }
+ var offerOptions;
+ if (arguments.length === 1 && typeof arguments[0] !== 'function') {
+ offerOptions = arguments[0];
+ } else if (arguments.length === 3) {
+ offerOptions = arguments[2];
+ }
+
+ var tracks = [];
+ var numAudioTracks = 0;
+ var numVideoTracks = 0;
+ // Default to sendrecv.
+ if (this.localStreams.length) {
+ numAudioTracks = this.localStreams[0].getAudioTracks().length;
+ numVideoTracks = this.localStreams[0].getVideoTracks().length;
+ }
+ // Determine number of audio and video tracks we need to send/recv.
+ if (offerOptions) {
+ // Reject Chrome legacy constraints.
+ if (offerOptions.mandatory || offerOptions.optional) {
+ throw new TypeError(
+ 'Legacy mandatory/optional constraints not supported.');
+ }
+ if (offerOptions.offerToReceiveAudio !== undefined) {
+ numAudioTracks = offerOptions.offerToReceiveAudio;
+ }
+ if (offerOptions.offerToReceiveVideo !== undefined) {
+ numVideoTracks = offerOptions.offerToReceiveVideo;
+ }
+ }
+ if (this.localStreams.length) {
+ // Push local streams.
+ this.localStreams[0].getTracks().forEach(function(track) {
+ tracks.push({
+ kind: track.kind,
+ track: track,
+ wantReceive: track.kind === 'audio' ?
+ numAudioTracks > 0 : numVideoTracks > 0
+ });
+ if (track.kind === 'audio') {
+ numAudioTracks--;
+ } else if (track.kind === 'video') {
+ numVideoTracks--;
+ }
+ });
+ }
+ // Create M-lines for recvonly streams.
+ while (numAudioTracks > 0 || numVideoTracks > 0) {
+ if (numAudioTracks > 0) {
+ tracks.push({
+ kind: 'audio',
+ wantReceive: true
+ });
+ numAudioTracks--;
+ }
+ if (numVideoTracks > 0) {
+ tracks.push({
+ kind: 'video',
+ wantReceive: true
+ });
+ numVideoTracks--;
+ }
+ }
+
+ var sdp = SDPUtils.writeSessionBoilerplate();
+ var transceivers = [];
+ tracks.forEach(function(mline, sdpMLineIndex) {
+ // For each track, create an ice gatherer, ice transport,
+ // dtls transport, potentially rtpsender and rtpreceiver.
+ var track = mline.track;
+ var kind = mline.kind;
+ var mid = SDPUtils.generateIdentifier();
+
+ var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex);
+
+ var localCapabilities = RTCRtpSender.getCapabilities(kind);
+ var rtpSender;
+ var rtpReceiver;
+
+ // generate an ssrc now, to be used later in rtpSender.send
+ var sendEncodingParameters = [{
+ ssrc: (2 * sdpMLineIndex + 1) * 1001
+ }];
+ if (track) {
+ rtpSender = new RTCRtpSender(track, transports.dtlsTransport);
+ }
+
+ if (mline.wantReceive) {
+ rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind);
+ }
+
+ transceivers[sdpMLineIndex] = {
+ iceGatherer: transports.iceGatherer,
+ iceTransport: transports.iceTransport,
+ dtlsTransport: transports.dtlsTransport,
+ localCapabilities: localCapabilities,
+ remoteCapabilities: null,
+ rtpSender: rtpSender,
+ rtpReceiver: rtpReceiver,
+ kind: kind,
+ mid: mid,
+ sendEncodingParameters: sendEncodingParameters,
+ recvEncodingParameters: null
+ };
+ var transceiver = transceivers[sdpMLineIndex];
+ sdp += SDPUtils.writeMediaSection(transceiver,
+ transceiver.localCapabilities, 'offer', self.localStreams[0]);
+ });
+
+ this._pendingOffer = transceivers;
+ var desc = new RTCSessionDescription({
+ type: 'offer',
+ sdp: sdp
+ });
+ if (arguments.length && typeof arguments[0] === 'function') {
+ window.setTimeout(arguments[0], 0, desc);
+ }
+ return Promise.resolve(desc);
+ };
+
+ window.RTCPeerConnection.prototype.createAnswer = function() {
+ var self = this;
+
+ var sdp = SDPUtils.writeSessionBoilerplate();
+ this.transceivers.forEach(function(transceiver) {
+ // Calculate intersection of capabilities.
+ var commonCapabilities = self._getCommonCapabilities(
+ transceiver.localCapabilities,
+ transceiver.remoteCapabilities);
+
+ sdp += SDPUtils.writeMediaSection(transceiver, commonCapabilities,
+ 'answer', self.localStreams[0]);
+ });
+
+ var desc = new RTCSessionDescription({
+ type: 'answer',
+ sdp: sdp
+ });
+ if (arguments.length && typeof arguments[0] === 'function') {
+ window.setTimeout(arguments[0], 0, desc);
+ }
+ return Promise.resolve(desc);
+ };
+
+ window.RTCPeerConnection.prototype.addIceCandidate = function(candidate) {
+ var mLineIndex = candidate.sdpMLineIndex;
+ if (candidate.sdpMid) {
+ for (var i = 0; i < this.transceivers.length; i++) {
+ if (this.transceivers[i].mid === candidate.sdpMid) {
+ mLineIndex = i;
+ break;
+ }
+ }
+ }
+ var transceiver = this.transceivers[mLineIndex];
+ if (transceiver) {
+ var cand = Object.keys(candidate.candidate).length > 0 ?
+ SDPUtils.parseCandidate(candidate.candidate) : {};
+ // Ignore Chrome's invalid candidates since Edge does not like them.
+ if (cand.protocol === 'tcp' && cand.port === 0) {
+ return;
+ }
+ // Ignore RTCP candidates, we assume RTCP-MUX.
+ if (cand.component !== '1') {
+ return;
+ }
+ // A dirty hack to make samples work.
+ if (cand.type === 'endOfCandidates') {
+ cand = {};
+ }
+ transceiver.iceTransport.addRemoteCandidate(cand);
+
+ // update the remoteDescription.
+ var sections = SDPUtils.splitSections(this.remoteDescription.sdp);
+ sections[mLineIndex + 1] += (cand.type ? candidate.candidate.trim()
+ : 'a=end-of-candidates') + '\r\n';
+ this.remoteDescription.sdp = sections.join('');
+ }
+ if (arguments.length > 1 && typeof arguments[1] === 'function') {
+ window.setTimeout(arguments[1], 0);
+ }
+ return Promise.resolve();
+ };
+
+ window.RTCPeerConnection.prototype.getStats = function() {
+ var promises = [];
+ this.transceivers.forEach(function(transceiver) {
+ ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport',
+ 'dtlsTransport'].forEach(function(method) {
+ if (transceiver[method]) {
+ promises.push(transceiver[method].getStats());
+ }
+ });
+ });
+ var cb = arguments.length > 1 && typeof arguments[1] === 'function' &&
+ arguments[1];
+ return new Promise(function(resolve) {
+ var results = {};
+ Promise.all(promises).then(function(res) {
+ res.forEach(function(result) {
+ Object.keys(result).forEach(function(id) {
+ results[id] = result[id];
+ });
+ });
+ if (cb) {
+ window.setTimeout(cb, 0, results);
+ }
+ resolve(results);
+ });
+ });
+ };
+ },
+
+ // Attach a media stream to an element.
+ attachMediaStream: function(element, stream) {
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
+ element.srcObject = stream;
+ },
+
+ reattachMediaStream: function(to, from) {
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
+ to.srcObject = from.srcObject;
+ }
+};
+
+// Expose public methods.
+module.exports = {
+ shimPeerConnection: edgeShim.shimPeerConnection,
+ attachMediaStream: edgeShim.attachMediaStream,
+ reattachMediaStream: edgeShim.reattachMediaStream
+};
+
+},{"../utils":9,"./edge_sdp":4}],6:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var logging = require('../utils').log;
+var browserDetails = require('../utils').browserDetails;
+
+var firefoxShim = {
+ shimOnTrack: function() {
+ if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in
+ window.RTCPeerConnection.prototype)) {
+ Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', {
+ get: function() {
+ return this._ontrack;
+ },
+ set: function(f) {
+ if (this._ontrack) {
+ this.removeEventListener('track', this._ontrack);
+ this.removeEventListener('addstream', this._ontrackpoly);
+ }
+ this.addEventListener('track', this._ontrack = f);
+ this.addEventListener('addstream', this._ontrackpoly = function(e) {
+ e.stream.getTracks().forEach(function(track) {
+ var event = new Event('track');
+ event.track = track;
+ event.receiver = {track: track};
+ event.streams = [e.stream];
+ this.dispatchEvent(event);
+ }.bind(this));
+ }.bind(this));
+ }
+ });
+ }
+ },
+
+ shimSourceObject: function() {
+ // Firefox has supported mozSrcObject since FF22, unprefixed in 42.
+ if (typeof window === 'object') {
+ if (window.HTMLMediaElement &&
+ !('srcObject' in window.HTMLMediaElement.prototype)) {
+ // Shim the srcObject property, once, when HTMLMediaElement is found.
+ Object.defineProperty(window.HTMLMediaElement.prototype, 'srcObject', {
+ get: function() {
+ return this.mozSrcObject;
+ },
+ set: function(stream) {
+ this.mozSrcObject = stream;
+ }
+ });
+ }
+ }
+ },
+
+ shimPeerConnection: function() {
+ // The RTCPeerConnection object.
+ if (!window.RTCPeerConnection) {
+ window.RTCPeerConnection = function(pcConfig, pcConstraints) {
+ if (browserDetails.version < 38) {
+ // .urls is not supported in FF < 38.
+ // create RTCIceServers with a single url.
+ if (pcConfig && pcConfig.iceServers) {
+ var newIceServers = [];
+ for (var i = 0; i < pcConfig.iceServers.length; i++) {
+ var server = pcConfig.iceServers[i];
+ if (server.hasOwnProperty('urls')) {
+ for (var j = 0; j < server.urls.length; j++) {
+ var newServer = {
+ url: server.urls[j]
+ };
+ if (server.urls[j].indexOf('turn') === 0) {
+ newServer.username = server.username;
+ newServer.credential = server.credential;
+ }
+ newIceServers.push(newServer);
+ }
+ } else {
+ newIceServers.push(pcConfig.iceServers[i]);
+ }
+ }
+ pcConfig.iceServers = newIceServers;
+ }
+ }
+ return new mozRTCPeerConnection(pcConfig, pcConstraints);
+ };
+ window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype;
+
+ // wrap static methods. Currently just generateCertificate.
+ if (mozRTCPeerConnection.generateCertificate) {
+ Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', {
+ get: function() {
+ return mozRTCPeerConnection.generateCertificate;
+ }
+ });
+ }
+
+ window.RTCSessionDescription = mozRTCSessionDescription;
+ window.RTCIceCandidate = mozRTCIceCandidate;
+ }
+
+ // shim away need for obsolete RTCIceCandidate/RTCSessionDescription.
+ ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']
+ .forEach(function(method) {
+ var nativeMethod = RTCPeerConnection.prototype[method];
+ RTCPeerConnection.prototype[method] = function() {
+ arguments[0] = new ((method === 'addIceCandidate')?
+ RTCIceCandidate : RTCSessionDescription)(arguments[0]);
+ return nativeMethod.apply(this, arguments);
+ };
+ });
+ },
+
+ shimGetUserMedia: function() {
+ // getUserMedia constraints shim.
+ var getUserMedia_ = function(constraints, onSuccess, onError) {
+ var constraintsToFF37_ = function(c) {
+ if (typeof c !== 'object' || c.require) {
+ return c;
+ }
+ var require = [];
+ Object.keys(c).forEach(function(key) {
+ if (key === 'require' || key === 'advanced' ||
+ key === 'mediaSource') {
+ return;
+ }
+ var r = c[key] = (typeof c[key] === 'object') ?
+ c[key] : {ideal: c[key]};
+ if (r.min !== undefined ||
+ r.max !== undefined || r.exact !== undefined) {
+ require.push(key);
+ }
+ if (r.exact !== undefined) {
+ if (typeof r.exact === 'number') {
+ r. min = r.max = r.exact;
+ } else {
+ c[key] = r.exact;
+ }
+ delete r.exact;
+ }
+ if (r.ideal !== undefined) {
+ c.advanced = c.advanced || [];
+ var oc = {};
+ if (typeof r.ideal === 'number') {
+ oc[key] = {min: r.ideal, max: r.ideal};
+ } else {
+ oc[key] = r.ideal;
+ }
+ c.advanced.push(oc);
+ delete r.ideal;
+ if (!Object.keys(r).length) {
+ delete c[key];
+ }
+ }
+ });
+ if (require.length) {
+ c.require = require;
+ }
+ return c;
+ };
+ constraints = JSON.parse(JSON.stringify(constraints));
+ if (browserDetails.version < 38) {
+ logging('spec: ' + JSON.stringify(constraints));
+ if (constraints.audio) {
+ constraints.audio = constraintsToFF37_(constraints.audio);
+ }
+ if (constraints.video) {
+ constraints.video = constraintsToFF37_(constraints.video);
+ }
+ logging('ff37: ' + JSON.stringify(constraints));
+ }
+ return navigator.mozGetUserMedia(constraints, onSuccess, onError);
+ };
+
+ navigator.getUserMedia = getUserMedia_;
+
+ // Returns the result of getUserMedia as a Promise.
+ var getUserMediaPromise_ = function(constraints) {
+ return new Promise(function(resolve, reject) {
+ navigator.getUserMedia(constraints, resolve, reject);
+ });
+ };
+
+ // Shim for mediaDevices on older versions.
+ if (!navigator.mediaDevices) {
+ navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
+ addEventListener: function() { },
+ removeEventListener: function() { }
+ };
+ }
+ navigator.mediaDevices.enumerateDevices =
+ navigator.mediaDevices.enumerateDevices || function() {
+ return new Promise(function(resolve) {
+ var infos = [
+ {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
+ {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
+ ];
+ resolve(infos);
+ });
+ };
+
+ if (browserDetails.version < 41) {
+ // Work around http://bugzil.la/1169665
+ var orgEnumerateDevices =
+ navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
+ navigator.mediaDevices.enumerateDevices = function() {
+ return orgEnumerateDevices().then(undefined, function(e) {
+ if (e.name === 'NotFoundError') {
+ return [];
+ }
+ throw e;
+ });
+ };
+ }
+ },
+
+ // Attach a media stream to an element.
+ attachMediaStream: function(element, stream) {
+ logging('DEPRECATED, attachMediaStream will soon be removed.');
+ element.srcObject = stream;
+ },
+
+ reattachMediaStream: function(to, from) {
+ logging('DEPRECATED, reattachMediaStream will soon be removed.');
+ to.srcObject = from.srcObject;
+ }
+};
+
+// Expose public methods.
+module.exports = {
+ shimOnTrack: firefoxShim.shimOnTrack,
+ shimSourceObject: firefoxShim.shimSourceObject,
+ shimPeerConnection: firefoxShim.shimPeerConnection,
+ shimGetUserMedia: require('./getusermedia'),
+ attachMediaStream: firefoxShim.attachMediaStream,
+ reattachMediaStream: firefoxShim.reattachMediaStream
+};
+
+},{"../utils":9,"./getusermedia":7}],7:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var logging = require('../utils').log;
+var browserDetails = require('../utils').browserDetails;
+
+// Expose public methods.
+module.exports = function() {
+ // getUserMedia constraints shim.
+ var getUserMedia_ = function(constraints, onSuccess, onError) {
+ var constraintsToFF37_ = function(c) {
+ if (typeof c !== 'object' || c.require) {
+ return c;
+ }
+ var require = [];
+ Object.keys(c).forEach(function(key) {
+ if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
+ return;
+ }
+ var r = c[key] = (typeof c[key] === 'object') ?
+ c[key] : {ideal: c[key]};
+ if (r.min !== undefined ||
+ r.max !== undefined || r.exact !== undefined) {
+ require.push(key);
+ }
+ if (r.exact !== undefined) {
+ if (typeof r.exact === 'number') {
+ r. min = r.max = r.exact;
+ } else {
+ c[key] = r.exact;
+ }
+ delete r.exact;
+ }
+ if (r.ideal !== undefined) {
+ c.advanced = c.advanced || [];
+ var oc = {};
+ if (typeof r.ideal === 'number') {
+ oc[key] = {min: r.ideal, max: r.ideal};
+ } else {
+ oc[key] = r.ideal;
+ }
+ c.advanced.push(oc);
+ delete r.ideal;
+ if (!Object.keys(r).length) {
+ delete c[key];
+ }
+ }
+ });
+ if (require.length) {
+ c.require = require;
+ }
+ return c;
+ };
+ constraints = JSON.parse(JSON.stringify(constraints));
+ if (browserDetails.version < 38) {
+ logging('spec: ' + JSON.stringify(constraints));
+ if (constraints.audio) {
+ constraints.audio = constraintsToFF37_(constraints.audio);
+ }
+ if (constraints.video) {
+ constraints.video = constraintsToFF37_(constraints.video);
+ }
+ logging('ff37: ' + JSON.stringify(constraints));
+ }
+ return navigator.mozGetUserMedia(constraints, onSuccess, onError);
+ };
+
+ navigator.getUserMedia = getUserMedia_;
+
+ // Returns the result of getUserMedia as a Promise.
+ var getUserMediaPromise_ = function(constraints) {
+ return new Promise(function(resolve, reject) {
+ navigator.getUserMedia(constraints, resolve, reject);
+ });
+ };
+
+ // Shim for mediaDevices on older versions.
+ if (!navigator.mediaDevices) {
+ navigator.mediaDevices = {getUserMedia: getUserMediaPromise_,
+ addEventListener: function() { },
+ removeEventListener: function() { }
+ };
+ }
+ navigator.mediaDevices.enumerateDevices =
+ navigator.mediaDevices.enumerateDevices || function() {
+ return new Promise(function(resolve) {
+ var infos = [
+ {kind: 'audioinput', deviceId: 'default', label: '', groupId: ''},
+ {kind: 'videoinput', deviceId: 'default', label: '', groupId: ''}
+ ];
+ resolve(infos);
+ });
+ };
+
+ if (browserDetails.version < 41) {
+ // Work around http://bugzil.la/1169665
+ var orgEnumerateDevices =
+ navigator.mediaDevices.enumerateDevices.bind(navigator.mediaDevices);
+ navigator.mediaDevices.enumerateDevices = function() {
+ return orgEnumerateDevices().then(undefined, function(e) {
+ if (e.name === 'NotFoundError') {
+ return [];
+ }
+ throw e;
+ });
+ };
+ }
+};
+
+},{"../utils":9}],8:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+'use strict';
+var safariShim = {
+ // TODO: DrAlex, should be here, double check against LayoutTests
+ // shimOnTrack: function() { },
+
+ // TODO: DrAlex
+ // attachMediaStream: function(element, stream) { },
+ // reattachMediaStream: function(to, from) { },
+
+ // TODO: once the back-end for the mac port is done, add.
+ // TODO: check for webkitGTK+
+ // shimPeerConnection: function() { },
+
+ shimGetUserMedia: function() {
+ navigator.getUserMedia = navigator.webkitGetUserMedia;
+ }
+};
+
+// Expose public methods.
+module.exports = {
+ shimGetUserMedia: safariShim.shimGetUserMedia
+ // TODO
+ // shimOnTrack: safariShim.shimOnTrack,
+ // shimPeerConnection: safariShim.shimPeerConnection,
+ // attachMediaStream: safariShim.attachMediaStream,
+ // reattachMediaStream: safariShim.reattachMediaStream
+};
+
+},{}],9:[function(require,module,exports){
+/*
+ * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+ /* eslint-env node */
+'use strict';
+
+var logDisabled_ = false;
+
+// Utility methods.
+var utils = {
+ disableLog: function(bool) {
+ if (typeof bool !== 'boolean') {
+ return new Error('Argument type: ' + typeof bool +
+ '. Please use a boolean.');
+ }
+ logDisabled_ = bool;
+ return (bool) ? 'adapter.js logging disabled' :
+ 'adapter.js logging enabled';
+ },
+
+ log: function() {
+ if (typeof window === 'object') {
+ if (logDisabled_) {
+ return;
+ }
+ if (typeof console !== 'undefined' && typeof console.log === 'function') {
+ console.log.apply(console, arguments);
+ }
+ }
+ },
+
+ /**
+ * Extract browser version out of the provided user agent string.
+ *
+ * @param {!string} uastring userAgent string.
+ * @param {!string} expr Regular expression used as match criteria.
+ * @param {!number} pos position in the version string to be returned.
+ * @return {!number} browser version.
+ */
+ extractVersion: function(uastring, expr, pos) {
+ var match = uastring.match(expr);
+ return match && match.length >= pos && parseInt(match[pos], 10);
+ },
+
+ /**
+ * Browser detector.
+ *
+ * @return {object} result containing browser, version and minVersion
+ * properties.
+ */
+ detectBrowser: function() {
+ // Returned result object.
+ var result = {};
+ result.browser = null;
+ result.version = null;
+ result.minVersion = null;
+
+ // Fail early if it's not a browser
+ if (typeof window === 'undefined' || !window.navigator) {
+ result.browser = 'Not a browser.';
+ return result;
+ }
+
+ // Firefox.
+ if (navigator.mozGetUserMedia) {
+ result.browser = 'firefox';
+ result.version = this.extractVersion(navigator.userAgent,
+ /Firefox\/([0-9]+)\./, 1);
+ result.minVersion = 31;
+
+ // all webkit-based browsers
+ } else if (navigator.webkitGetUserMedia) {
+ // Chrome, Chromium, Webview, Opera, all use the chrome shim for now
+ if (window.webkitRTCPeerConnection) {
+ result.browser = 'chrome';
+ result.version = this.extractVersion(navigator.userAgent,
+ /Chrom(e|ium)\/([0-9]+)\./, 2);
+ result.minVersion = 38;
+
+ // Safari or unknown webkit-based
+ // for the time being Safari has support for MediaStreams but not webRTC
+ } else {
+ // Safari UA substrings of interest for reference:
+ // - webkit version: AppleWebKit/602.1.25 (also used in Op,Cr)
+ // - safari UI version: Version/9.0.3 (unique to Safari)
+ // - safari UI webkit version: Safari/601.4.4 (also used in Op,Cr)
+ //
+ // if the webkit version and safari UI webkit versions are equals,
+ // ... this is a stable version.
+ //
+ // only the internal webkit version is important today to know if
+ // media streams are supported
+ //
+ if (navigator.userAgent.match(/Version\/(\d+).(\d+)/)) {
+ result.browser = 'safari';
+ result.version = this.extractVersion(navigator.userAgent,
+ /AppleWebKit\/([0-9]+)\./, 1);
+ result.minVersion = 602;
+
+ // unknown webkit-based browser
+ } else {
+ result.browser = 'Unsupported webkit-based browser ' +
+ 'with GUM support but no WebRTC support.';
+ return result;
+ }
+ }
+
+ // Edge.
+ } else if (navigator.mediaDevices &&
+ navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) {
+ result.browser = 'edge';
+ result.version = this.extractVersion(navigator.userAgent,
+ /Edge\/(\d+).(\d+)$/, 2);
+ result.minVersion = 10547;
+
+ // Default fallthrough: not supported.
+ } else {
+ result.browser = 'Not a supported browser.';
+ return result;
+ }
+
+ // Warn if version is less than minVersion.
+ if (result.version < result.minVersion) {
+ utils.log('Browser: ' + result.browser + ' Version: ' + result.version +
+ ' < minimum supported version: ' + result.minVersion +
+ '\n some things might not work!');
+ }
+
+ return result;
+ }
+};
+
+// Export.
+module.exports = {
+ log: utils.log,
+ disableLog: utils.disableLog,
+ browserDetails: utils.detectBrowser(),
+ extractVersion: utils.extractVersion
+};
+
+},{}]},{},[1])(1)
+});
\ No newline at end of file
diff --git a/www/webrtc/js/common.js b/www/webrtc/js/common.js
new file mode 100644
index 00000000..d3de5988
--- /dev/null
+++ b/www/webrtc/js/common.js
@@ -0,0 +1,12 @@
+function trace(text) {
+ // This function is used for logging.
+ if (text[text.length - 1] === '\n') {
+ text = text.substring(0, text.length - 1);
+ }
+ if (window.performance) {
+ var now = (window.performance.now() / 1000).toFixed(3);
+ console.log(now + ': ' + text);
+ } else {
+ console.log(text);
+ }
+}
diff --git a/www/webrtc/js/main.js b/www/webrtc/js/main.js
new file mode 100644
index 00000000..20c6b2da
--- /dev/null
+++ b/www/webrtc/js/main.js
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2015 The WebRTC project authors. All Rights Reserved.
+ *
+ * Use of this source code is governed by a BSD-style license
+ * that can be found in the LICENSE file in the root of the source
+ * tree.
+ */
+
+'use strict';
+
+
+var vid2 = document.getElementById('vid2');
+var btn1 = document.getElementById('btn1');
+var btn3 = document.getElementById('btn3');
+var input1 = document.getElementById('input1');
+
+btn1.addEventListener('click', start);
+
+btn3.addEventListener('click', stop);
+
+btn1.disabled = false;
+btn3.disabled = true;
+
+
+var pc2 = null;
+var xmlhttp = null;
+
+function start() {
+ btn1.disabled = true;
+ btn3.disabled = false;
+ trace('Starting Call');
+
+ var servers = null;
+ var addr = input1.value;
+ pc2 = new RTCPeerConnection(servers);
+ trace('Created remote peer connection object pc2');
+ pc2.onicecandidate = iceCallback;
+ pc2.onaddstream = gotRemoteStream;
+
+ if (window.XMLHttpRequest)
+ {
+ // IE7+, Firefox, Chrome, Opera, Safari �����ִ�д���
+ xmlhttp=new XMLHttpRequest();
+ }
+ else
+ {
+ // IE6, IE5 �����ִ�д���
+ xmlhttp=new ActiveXObject("Microsoft.XMLHTTP");
+ }
+
+ xmlhttp.onreadystatechange=function()
+ {
+ if (xmlhttp.readyState==4 && xmlhttp.status==200)
+ {
+ var res = xmlhttp.responseText;
+ gotoffer(res);
+ }
+ }
+ xmlhttp.open("GET",addr,true);
+ xmlhttp.send();
+
+}
+
+function onCreateSessionDescriptionError(error) {
+ trace('Failed to create session description: ' + error.toString());
+ stop();
+}
+
+function onCreateAnswerError(error) {
+ trace('Failed to set createAnswer: ' + error.toString());
+ stop();
+}
+
+function onSetLocalDescriptionError(error) {
+ trace('Failed to set setLocalDescription: ' + error.toString());
+ stop();
+}
+
+function onSetLocalDescriptionSuccess() {
+ trace('localDescription success.');
+}
+
+function gotoffer(offer) {
+
+ trace('Offer from server \n' + offer);
+ //??????offer sdp????????RTCSessionDescription????
+ var desc = new RTCSessionDescription();
+ desc.sdp = offer;
+ desc.type = 'offer';
+ pc2.setRemoteDescription(desc);
+ // Since the 'remote' side has no media stream we need
+ // to pass in the right constraints in order for it to
+ // accept the incoming offer of audio and video.
+ pc2.createAnswer().then(
+ gotDescription2,
+ onCreateSessionDescriptionError
+ );
+}
+
+function gotDescription2(desc) {
+ // Provisional answer, set a=inactive & set sdp type to pranswer.
+ /*desc.sdp = desc.sdp.replace(/a=recvonly/g, 'a=inactive');
+ desc.type = 'pranswer';*/
+
+ pc2.setLocalDescription(desc).then(
+ onSetLocalDescriptionSuccess,
+ onSetLocalDescriptionError
+ );
+ trace('Pranswer from pc2 \n' + desc.sdp);
+
+ //conn.send(JSON.stringify(desc));
+ // send desc.sdp to server
+}
+
+function stop() {
+ trace('Ending Call' + '\n\n');
+ pc2.close();
+ pc2 = null;
+}
+
+function gotRemoteStream(e) {
+ vid2.srcObject = e.stream;
+ trace('Received remote stream');
+}
+
+function iceCallback(event) {
+ if (event.candidate) {
+ trace('Remote ICE candidate: \n ' + event.candidate.candidate);
+ //conn.send(JSON.stringify(event.candidate));
+ }
+ else {
+ // All ICE candidates have been sent
+ }
+}