You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1980 lines
67 KiB
1980 lines
67 KiB
(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<r.length;o++)s(r[o]);return s})({1:[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'; |
|
|
|
// Shimming starts here. |
|
(function() { |
|
// Utils. |
|
var logging = require('./utils').log; |
|
var browserDetails = require('./utils').browserDetails; |
|
// Export to the adapter global object visible in the browser. |
|
module.exports.browserDetails = browserDetails; |
|
module.exports.extractVersion = require('./utils').extractVersion; |
|
module.exports.disableLog = require('./utils').disableLog; |
|
|
|
// Uncomment if you do not want any logging at all including the switch |
|
// statement below. Can also be turned off in the browser via |
|
// adapter.disableLog(true) but then logging from the switch statement below |
|
// will still appear. |
|
require('./utils').disableLog(true); |
|
|
|
// Warn if version is not supported regardless of browser. |
|
// Min version can be set per browser in utils.js |
|
if (browserDetails.version < browserDetails.minVersion) { |
|
logging('Browser: ' + browserDetails.browser + ' Version: ' + |
|
browserDetails.version + ' <' + ' minimum supported version: ' + |
|
browserDetails.minVersion + '\n some things might not work!'); |
|
} |
|
|
|
// Browser shims. |
|
var chromeShim = require('./chrome/chrome_shim') || null; |
|
var edgeShim = require('./edge/edge_shim') || null; |
|
var firefoxShim = require('./firefox/firefox_shim') || null; |
|
|
|
// Shim browser if found. |
|
switch (browserDetails.browser) { |
|
case 'chrome': |
|
if (!chromeShim) { |
|
logging('Chrome shim is not included in this adapter release.'); |
|
return; |
|
} |
|
logging('Adapter.js shimming chrome!'); |
|
// Export to the adapter global object visible in the browser. |
|
module.exports.browserShim = chromeShim; |
|
|
|
chromeShim.shimGetUserMedia(); |
|
chromeShim.shimSourceObject(); |
|
chromeShim.shimPeerConnection(); |
|
chromeShim.shimOnTrack(); |
|
break; |
|
case 'edge': |
|
if (!edgeShim) { |
|
logging('MS edge shim is not included in this adapter release.'); |
|
return; |
|
} |
|
logging('Adapter.js shimming edge!'); |
|
// Export to the adapter global object visible in the browser. |
|
module.exports.browserShim = edgeShim; |
|
|
|
edgeShim.shimPeerConnection(); |
|
break; |
|
case 'firefox': |
|
if (!firefoxShim) { |
|
logging('Firefox shim is not included in this adapter release.'); |
|
return; |
|
} |
|
logging('Adapter.js shimming firefox!'); |
|
// Export to the adapter global object visible in the browser. |
|
module.exports.browserShim = firefoxShim; |
|
|
|
firefoxShim.shimGetUserMedia(); |
|
firefoxShim.shimSourceObject(); |
|
firefoxShim.shimPeerConnection(); |
|
firefoxShim.shimOnTrack(); |
|
break; |
|
default: |
|
logging('Unsupported browser!'); |
|
} |
|
})(); |
|
|
|
},{"./chrome/chrome_shim":2,"./edge/edge_shim":4,"./firefox/firefox_shim":5,"./utils":6}],2:[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 logging = require('../utils.js').log; |
|
var browserDetails = require('../utils.js').browserDetails; |
|
|
|
var chromeShim = { |
|
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) { |
|
var self = this; |
|
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) { |
|
// onaddstream does not fire when a track is added to an existing stream. |
|
// but stream.onaddtrack is implemented so we use that |
|
e.stream.addEventListener('addtrack', function(te) { |
|
var event = new Event('track'); |
|
event.track = te.track; |
|
event.receiver = {track: te.track}; |
|
event.streams = [e.stream]; |
|
self.dispatchEvent(event); |
|
}); |
|
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() { |
|
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._srcObject; |
|
}, |
|
set: function(stream) { |
|
// Use _srcObject as a private property for this shim |
|
this._srcObject = stream; |
|
if (this.src) { |
|
URL.revokeObjectURL(this.src); |
|
} |
|
this.src = URL.createObjectURL(stream); |
|
// We need to recreate the blob url when a track is added or removed. |
|
// Doing it manually since we want to avoid a recursion. |
|
stream.addEventListener('addtrack', function() { |
|
if (self.src) { |
|
URL.revokeObjectURL(self.src); |
|
} |
|
self.src = URL.createObjectURL(stream); |
|
}); |
|
stream.addEventListener('removetrack', function() { |
|
if (self.src) { |
|
URL.revokeObjectURL(self.src); |
|
} |
|
self.src = URL.createObjectURL(stream); |
|
}); |
|
} |
|
}); |
|
} |
|
} |
|
}, |
|
|
|
shimPeerConnection: function() { |
|
// The RTCPeerConnection object. |
|
window.RTCPeerConnection = function(pcConfig, pcConstraints) { |
|
// Translate iceTransportPolicy to iceTransports, |
|
// see https://code.google.com/p/webrtc/issues/detail?id=4869 |
|
logging('PeerConnection'); |
|
if (pcConfig && pcConfig.iceTransportPolicy) { |
|
pcConfig.iceTransports = pcConfig.iceTransportPolicy; |
|
} |
|
|
|
var pc = new webkitRTCPeerConnection(pcConfig, pcConstraints); // jscs:ignore requireCapitalizedConstructors |
|
var origGetStats = pc.getStats.bind(pc); |
|
pc.getStats = function(selector, successCallback, errorCallback) { // jshint ignore: line |
|
var self = this; |
|
var args = arguments; |
|
|
|
// If selector is a function then we are in the old style stats so just |
|
// pass back the original getStats format to avoid breaking old users. |
|
if (arguments.length > 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 && selector === null) { |
|
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() { |
|
if (arguments.length) { |
|
return webkitRTCPeerConnection.generateCertificate.apply(null, |
|
arguments); |
|
} else { |
|
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]); |
|
}); |
|
} else { |
|
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; |
|
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]); |
|
} |
|
}] |
|
); |
|
}); |
|
}; |
|
}); |
|
}, |
|
|
|
shimGetUserMedia: 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) { |
|
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.'); |
|
}; |
|
} |
|
}, |
|
|
|
// 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: chromeShim.shimGetUserMedia, |
|
attachMediaStream: chromeShim.attachMediaStream, |
|
reattachMediaStream: chromeShim.reattachMediaStream |
|
}; |
|
|
|
},{"../utils.js":6}],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. |
|
*/ |
|
'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('\r\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 |
|
parsed.numChannels = parts.length === 3 ? parseInt(parts[2], 10) : 1; // was: channels |
|
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 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.writeFtmp = function(codec) { |
|
var line = ''; |
|
var pt = codec.payloadType; |
|
if (codec.preferredPayloadType !== undefined) { |
|
pt = codec.preferredPayloadType; |
|
} |
|
if (codec.parameters && 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: line.substr(7, sp - 7), |
|
}; |
|
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); |
|
lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. |
|
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); |
|
lines = lines.concat(SDPUtils.splitLines(sessionpart)); // Search in session part, too. |
|
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:<pt> 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); |
|
} |
|
} |
|
// FIXME: parse headerExtensions, fecMechanisms and 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.writeFtmp(codec); |
|
sdp += SDPUtils.writeRtcpFb(codec); |
|
}); |
|
// FIXME: add headerExtensions, fecMechanismş and rtcp. |
|
sdp += 'a=rtcp-mux\r\n'; |
|
return sdp; |
|
}; |
|
|
|
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.sendSsrc + ' ' + msid; |
|
} |
|
// FIXME: this should be written by writeRtpDescription. |
|
sdp += 'a=ssrc:' + transceiver.sendSsrc + ' 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); |
|
} |
|
} |
|
if (sessionpart) { |
|
return SDPUtils.getDirection(sessionpart); |
|
} |
|
return 'sendrecv'; |
|
}; |
|
|
|
// Expose public methods. |
|
module.exports = SDPUtils; |
|
|
|
},{}],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. |
|
*/ |
|
'use strict'; |
|
|
|
var SDPUtils = require('./edge_sdp'); |
|
var logging = require('../utils').log; |
|
var browserDetails = require('../utils').browserDetails; |
|
|
|
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; |
|
|
|
this.onicecandidate = null; |
|
this.onaddstream = 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.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'); |
|
} |
|
} |
|
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('transport=udp') !== -1; |
|
})[0]; |
|
return true; |
|
} |
|
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; |
|
// FIXME: need to apply ice candidates in a way which is async but in-order |
|
this._localIceCandidatesBuffer.forEach(function(event) { |
|
if (self.onicecandidate !== null) { |
|
self.onicecandidate(event); |
|
} |
|
}); |
|
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 = {}; |
|
event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; |
|
|
|
var cand = evt.candidate; |
|
// Edge emits an empty object for RTCIceCandidateComplete‥ |
|
if (!cand || Object.keys(cand).length === 0) { |
|
// 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'; |
|
}); |
|
// FIXME: update .localDescription with candidate and (potentially) end-of-candidates. |
|
// To make this harder, the gatherer might emit candidates before localdescription |
|
// is set. To make things worse, gather.getLocalCandidates still errors in |
|
// Edge 10547 when no candidates have been gathered yet. |
|
|
|
if (self.onicecandidate !== null) { |
|
// Emit candidate if localDescription is set. |
|
// Also emits null candidate when all gatherers are complete. |
|
if (self.localDescription && self.localDescription.type === '') { |
|
self._localIceCandidatesBuffer.push(event); |
|
if (complete) { |
|
self._localIceCandidatesBuffer.push({}); |
|
} |
|
} else { |
|
self.onicecandidate(event); |
|
if (complete) { |
|
self.onicecandidate({}); |
|
} |
|
} |
|
} |
|
}; |
|
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 = [{ |
|
ssrc: transceiver.sendSsrc |
|
}]; |
|
params.rtcp = { |
|
cname: SDPUtils.localCName, |
|
ssrc: transceiver.recvSsrc |
|
}; |
|
transceiver.rtpSender.send(params); |
|
} |
|
if (recv && transceiver.rtpReceiver) { |
|
params.encodings = [{ |
|
ssrc: transceiver.recvSsrc |
|
}]; |
|
params.rtcp = { |
|
cname: transceiver.cname, |
|
ssrc: transceiver.sendSsrc |
|
}; |
|
transceiver.rtpReceiver.receive(params); |
|
} |
|
}; |
|
|
|
window.RTCPeerConnection.prototype.setLocalDescription = |
|
function(description) { |
|
var self = this; |
|
if (description.type === 'offer') { |
|
if (!this._pendingOffer) { |
|
} else { |
|
this.transceivers = this._pendingOffer; |
|
delete this._pendingOffer; |
|
} |
|
} else if (description.type === 'answer') { |
|
var sections = SDPUtils.splitSections(self.remoteDescription.sdp); |
|
var 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 = description; |
|
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(); |
|
self._emitBufferedCandidates(); |
|
}, 0); |
|
} |
|
var p = Promise.resolve(); |
|
p.then(function() { |
|
if (!hasCallback) { |
|
window.setTimeout(self._emitBufferedCandidates.bind(self), 0); |
|
} |
|
}); |
|
return p; |
|
}; |
|
|
|
window.RTCPeerConnection.prototype.setRemoteDescription = |
|
function(description) { |
|
var self = this; |
|
var stream = new MediaStream(); |
|
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 sendSsrc; |
|
var recvSsrc; |
|
var localCapabilities; |
|
|
|
// 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); |
|
} |
|
var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0].substr(6); |
|
|
|
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) { |
|
recvSsrc = parseInt(remoteSsrc.ssrc, 10); |
|
cname = remoteSsrc.value; |
|
} |
|
|
|
if (description.type === 'offer') { |
|
var transports = self._createIceAndDtlsTransports(mid, sdpMLineIndex); |
|
|
|
localCapabilities = RTCRtpReceiver.getCapabilities(kind); |
|
sendSsrc = (2 * sdpMLineIndex + 2) * 1001; |
|
|
|
rtpReceiver = new RTCRtpReceiver(transports.dtlsTransport, kind); |
|
|
|
// FIXME: not correct when there are multiple streams but that is |
|
// not currently supported in this shim. |
|
stream.addTrack(rtpReceiver.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, |
|
sendSsrc: sendSsrc, |
|
recvSsrc: recvSsrc |
|
}; |
|
// 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; |
|
sendSsrc = transceiver.sendSsrc; |
|
//recvSsrc = transceiver.recvSsrc; |
|
localCapabilities = transceiver.localCapabilities; |
|
|
|
self.transceivers[sdpMLineIndex].recvSsrc = recvSsrc; |
|
self.transceivers[sdpMLineIndex].remoteCapabilities = |
|
remoteCapabilities; |
|
self.transceivers[sdpMLineIndex].cname = cname; |
|
|
|
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')) { |
|
stream.addTrack(rtpReceiver.track); |
|
} else { |
|
// FIXME: actually the receiver should be created later. |
|
delete transceiver.rtpReceiver; |
|
} |
|
} |
|
}); |
|
|
|
this.remoteDescription = description; |
|
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 + '"'); |
|
} |
|
window.setTimeout(function() { |
|
if (self.onaddstream !== null && stream.getTracks().length) { |
|
self.remoteStreams.push(stream); |
|
window.setTimeout(function() { |
|
self.onaddstream({stream: stream}); |
|
}, 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; |
|
if (this.onsignalingstatechange !== null) { |
|
this.onsignalingstatechange(); |
|
} |
|
}; |
|
|
|
// Determine whether to fire the negotiationneeded event. |
|
window.RTCPeerConnection.prototype._maybeFireNegotiationNeeded = |
|
function() { |
|
// Fire away (for now). |
|
if (this.onnegotiationneeded !== null) { |
|
this.onnegotiationneeded(); |
|
} |
|
}; |
|
|
|
// 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.connecting > 0 || states.completed > 0) { |
|
newState = 'connected'; |
|
} |
|
|
|
if (newState !== self.iceConnectionState) { |
|
self.iceConnectionState = newState; |
|
if (this.oniceconnectionstatechange !== null) { |
|
this.oniceconnectionstatechange(); |
|
} |
|
} |
|
}; |
|
|
|
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 sendSsrc = (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, |
|
sendSsrc: sendSsrc, |
|
recvSsrc: 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 answerOptions; |
|
if (arguments.length === 1 && typeof arguments[0] !== 'function') { |
|
answerOptions = arguments[0]; |
|
} else if (arguments.length === 3) { |
|
answerOptions = arguments[2]; |
|
} |
|
|
|
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); |
|
} |
|
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":6,"./edge_sdp":3}],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. |
|
*/ |
|
'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) { |
|
var self = this; |
|
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); // jscs:ignore requireCapitalizedConstructors |
|
}; |
|
window.RTCPeerConnection.prototype = mozRTCPeerConnection.prototype; |
|
|
|
// wrap static methods. Currently just generateCertificate. |
|
if (mozRTCPeerConnection.generateCertificate) { |
|
Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { |
|
get: function() { |
|
if (arguments.length) { |
|
return mozRTCPeerConnection.generateCertificate.apply(null, |
|
arguments); |
|
} else { |
|
return mozRTCPeerConnection.generateCertificate; |
|
} |
|
} |
|
}); |
|
} |
|
|
|
window.RTCSessionDescription = mozRTCSessionDescription; |
|
window.RTCIceCandidate = mozRTCIceCandidate; |
|
} |
|
}, |
|
|
|
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; |
|
}; |
|
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: firefoxShim.shimGetUserMedia, |
|
attachMediaStream: firefoxShim.attachMediaStream, |
|
reattachMediaStream: firefoxShim.reattachMediaStream |
|
} |
|
|
|
},{"../utils":6}],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. |
|
*/ |
|
'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; |
|
} |
|
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; |
|
|
|
// Non supported browser. |
|
if (typeof window === 'undefined' || !window.navigator) { |
|
result.browser = 'Not a supported browser.'; |
|
return result; |
|
} |
|
|
|
// Firefox. |
|
if (navigator.mozGetUserMedia) { |
|
result.browser = 'firefox'; |
|
result.version = this.extractVersion(navigator.userAgent, |
|
/Firefox\/([0-9]+)\./, 1); |
|
result.minVersion = 31; |
|
return result; |
|
} |
|
|
|
// Chrome/Chromium/Webview. |
|
if (navigator.webkitGetUserMedia && window.webkitRTCPeerConnection) { |
|
result.browser = 'chrome'; |
|
result.version = this.extractVersion(navigator.userAgent, |
|
/Chrom(e|ium)\/([0-9]+)\./, 2); |
|
result.minVersion = 38; |
|
return result; |
|
} |
|
|
|
// Edge. |
|
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; |
|
return result; |
|
} |
|
} |
|
}; |
|
|
|
// Export. |
|
module.exports = { |
|
log: utils.log, |
|
disableLog: utils.disableLog, |
|
browserDetails: utils.detectBrowser(), |
|
extractVersion: utils.extractVersion |
|
}; |
|
|
|
},{}]},{},[1])(1) |
|
}); |