| New file |
| | |
| | | var ZLMRTCClient = (function (exports) { |
| | | 'use strict'; |
| | | |
| | | const Events$1 = { |
| | | WEBRTC_NOT_SUPPORT: 'WEBRTC_NOT_SUPPORT', |
| | | WEBRTC_ICE_CANDIDATE_ERROR: 'WEBRTC_ICE_CANDIDATE_ERROR', |
| | | WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED: 'WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED', |
| | | WEBRTC_ON_REMOTE_STREAMS: 'WEBRTC_ON_REMOTE_STREAMS', |
| | | WEBRTC_ON_LOCAL_STREAM: 'WEBRTC_ON_LOCAL_STREAM', |
| | | WEBRTC_ON_CONNECTION_STATE_CHANGE: 'WEBRTC_ON_CONNECTION_STATE_CHANGE', |
| | | WEBRTC_ON_DATA_CHANNEL_OPEN: 'WEBRTC_ON_DATA_CHANNEL_OPEN', |
| | | WEBRTC_ON_DATA_CHANNEL_CLOSE: 'WEBRTC_ON_DATA_CHANNEL_CLOSE', |
| | | WEBRTC_ON_DATA_CHANNEL_ERR: 'WEBRTC_ON_DATA_CHANNEL_ERR', |
| | | WEBRTC_ON_DATA_CHANNEL_MSG: 'WEBRTC_ON_DATA_CHANNEL_MSG', |
| | | CAPTURE_STREAM_FAILED: 'CAPTURE_STREAM_FAILED' |
| | | }; |
| | | |
| | | const VERSION = '1.0.1'; |
| | | const BUILD_DATE = 'Thu Mar 24 2022 17:42:57 GMT+0800 (China Standard Time)'; |
| | | |
| | | // Copyright (C) <2018> Intel Corporation |
| | | // |
| | | // SPDX-License-Identifier: Apache-2.0 |
| | | // eslint-disable-next-line require-jsdoc |
| | | function isFirefox() { |
| | | return window.navigator.userAgent.match('Firefox') !== null; |
| | | } // eslint-disable-next-line require-jsdoc |
| | | |
| | | function isChrome() { |
| | | return window.navigator.userAgent.match('Chrome') !== null; |
| | | } // eslint-disable-next-line require-jsdoc |
| | | |
| | | function isEdge() { |
| | | return window.navigator.userAgent.match(/Edge\/(\d+).(\d+)$/) !== null; |
| | | } // eslint-disable-next-line require-jsdoc |
| | | |
| | | // Copyright (C) <2018> Intel Corporation |
| | | /** |
| | | * @class AudioSourceInfo |
| | | * @classDesc Source info about an audio track. Values: 'mic', 'screen-cast', 'file', 'mixed'. |
| | | * @memberOf Owt.Base |
| | | * @readonly |
| | | * @enum {string} |
| | | */ |
| | | |
| | | const AudioSourceInfo = { |
| | | MIC: 'mic', |
| | | SCREENCAST: 'screen-cast', |
| | | FILE: 'file', |
| | | MIXED: 'mixed' |
| | | }; |
| | | /** |
| | | * @class VideoSourceInfo |
| | | * @classDesc Source info about a video track. Values: 'camera', 'screen-cast', 'file', 'mixed'. |
| | | * @memberOf Owt.Base |
| | | * @readonly |
| | | * @enum {string} |
| | | */ |
| | | |
| | | const VideoSourceInfo = { |
| | | CAMERA: 'camera', |
| | | SCREENCAST: 'screen-cast', |
| | | FILE: 'file', |
| | | MIXED: 'mixed' |
| | | }; |
| | | /** |
| | | * @class TrackKind |
| | | * @classDesc Kind of a track. Values: 'audio' for audio track, 'video' for video track, 'av' for both audio and video tracks. |
| | | * @memberOf Owt.Base |
| | | * @readonly |
| | | * @enum {string} |
| | | */ |
| | | |
| | | const TrackKind = { |
| | | /** |
| | | * Audio tracks. |
| | | * @type string |
| | | */ |
| | | AUDIO: 'audio', |
| | | |
| | | /** |
| | | * Video tracks. |
| | | * @type string |
| | | */ |
| | | VIDEO: 'video', |
| | | |
| | | /** |
| | | * Both audio and video tracks. |
| | | * @type string |
| | | */ |
| | | AUDIO_AND_VIDEO: 'av' |
| | | }; |
| | | /** |
| | | * @class Resolution |
| | | * @memberOf Owt.Base |
| | | * @classDesc The Resolution defines the size of a rectangle. |
| | | * @constructor |
| | | * @param {number} width |
| | | * @param {number} height |
| | | */ |
| | | |
| | | class Resolution { |
| | | // eslint-disable-next-line require-jsdoc |
| | | constructor(width, height) { |
| | | /** |
| | | * @member {number} width |
| | | * @instance |
| | | * @memberof Owt.Base.Resolution |
| | | */ |
| | | this.width = width; |
| | | /** |
| | | * @member {number} height |
| | | * @instance |
| | | * @memberof Owt.Base.Resolution |
| | | */ |
| | | |
| | | this.height = height; |
| | | } |
| | | |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | let logDisabled_ = true; |
| | | let deprecationWarnings_ = true; |
| | | |
| | | /** |
| | | * 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. |
| | | */ |
| | | function extractVersion(uastring, expr, pos) { |
| | | const match = uastring.match(expr); |
| | | return match && match.length >= pos && parseInt(match[pos], 10); |
| | | } |
| | | |
| | | // Wraps the peerconnection event eventNameToWrap in a function |
| | | // which returns the modified event object (or false to prevent |
| | | // the event). |
| | | function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | const proto = window.RTCPeerConnection.prototype; |
| | | const nativeAddEventListener = proto.addEventListener; |
| | | proto.addEventListener = function(nativeEventName, cb) { |
| | | if (nativeEventName !== eventNameToWrap) { |
| | | return nativeAddEventListener.apply(this, arguments); |
| | | } |
| | | const wrappedCallback = (e) => { |
| | | const modifiedEvent = wrapper(e); |
| | | if (modifiedEvent) { |
| | | if (cb.handleEvent) { |
| | | cb.handleEvent(modifiedEvent); |
| | | } else { |
| | | cb(modifiedEvent); |
| | | } |
| | | } |
| | | }; |
| | | this._eventMap = this._eventMap || {}; |
| | | if (!this._eventMap[eventNameToWrap]) { |
| | | this._eventMap[eventNameToWrap] = new Map(); |
| | | } |
| | | this._eventMap[eventNameToWrap].set(cb, wrappedCallback); |
| | | return nativeAddEventListener.apply(this, [nativeEventName, |
| | | wrappedCallback]); |
| | | }; |
| | | |
| | | const nativeRemoveEventListener = proto.removeEventListener; |
| | | proto.removeEventListener = function(nativeEventName, cb) { |
| | | if (nativeEventName !== eventNameToWrap || !this._eventMap |
| | | || !this._eventMap[eventNameToWrap]) { |
| | | return nativeRemoveEventListener.apply(this, arguments); |
| | | } |
| | | if (!this._eventMap[eventNameToWrap].has(cb)) { |
| | | return nativeRemoveEventListener.apply(this, arguments); |
| | | } |
| | | const unwrappedCb = this._eventMap[eventNameToWrap].get(cb); |
| | | this._eventMap[eventNameToWrap].delete(cb); |
| | | if (this._eventMap[eventNameToWrap].size === 0) { |
| | | delete this._eventMap[eventNameToWrap]; |
| | | } |
| | | if (Object.keys(this._eventMap).length === 0) { |
| | | delete this._eventMap; |
| | | } |
| | | return nativeRemoveEventListener.apply(this, [nativeEventName, |
| | | unwrappedCb]); |
| | | }; |
| | | |
| | | Object.defineProperty(proto, 'on' + eventNameToWrap, { |
| | | get() { |
| | | return this['_on' + eventNameToWrap]; |
| | | }, |
| | | set(cb) { |
| | | if (this['_on' + eventNameToWrap]) { |
| | | this.removeEventListener(eventNameToWrap, |
| | | this['_on' + eventNameToWrap]); |
| | | delete this['_on' + eventNameToWrap]; |
| | | } |
| | | if (cb) { |
| | | this.addEventListener(eventNameToWrap, |
| | | this['_on' + eventNameToWrap] = cb); |
| | | } |
| | | }, |
| | | enumerable: true, |
| | | configurable: true |
| | | }); |
| | | } |
| | | |
| | | function disableLog(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'; |
| | | } |
| | | |
| | | /** |
| | | * Disable or enable deprecation warnings |
| | | * @param {!boolean} bool set to true to disable warnings. |
| | | */ |
| | | function disableWarnings(bool) { |
| | | if (typeof bool !== 'boolean') { |
| | | return new Error('Argument type: ' + typeof bool + |
| | | '. Please use a boolean.'); |
| | | } |
| | | deprecationWarnings_ = !bool; |
| | | return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); |
| | | } |
| | | |
| | | function log$1() { |
| | | if (typeof window === 'object') { |
| | | if (logDisabled_) { |
| | | return; |
| | | } |
| | | if (typeof console !== 'undefined' && typeof console.log === 'function') { |
| | | console.log.apply(console, arguments); |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Shows a deprecation warning suggesting the modern and spec-compatible API. |
| | | */ |
| | | function deprecated(oldMethod, newMethod) { |
| | | if (!deprecationWarnings_) { |
| | | return; |
| | | } |
| | | console.warn(oldMethod + ' is deprecated, please use ' + newMethod + |
| | | ' instead.'); |
| | | } |
| | | |
| | | /** |
| | | * Browser detector. |
| | | * |
| | | * @return {object} result containing browser and version |
| | | * properties. |
| | | */ |
| | | function detectBrowser(window) { |
| | | // Returned result object. |
| | | const result = {browser: null, version: null}; |
| | | |
| | | // Fail early if it's not a browser |
| | | if (typeof window === 'undefined' || !window.navigator) { |
| | | result.browser = 'Not a browser.'; |
| | | return result; |
| | | } |
| | | |
| | | const {navigator} = window; |
| | | |
| | | if (navigator.mozGetUserMedia) { // Firefox. |
| | | result.browser = 'firefox'; |
| | | result.version = extractVersion(navigator.userAgent, |
| | | /Firefox\/(\d+)\./, 1); |
| | | } else if (navigator.webkitGetUserMedia || |
| | | (window.isSecureContext === false && window.webkitRTCPeerConnection && |
| | | !window.RTCIceGatherer)) { |
| | | // Chrome, Chromium, Webview, Opera. |
| | | // Version matches Chrome/WebRTC version. |
| | | // Chrome 74 removed webkitGetUserMedia on http as well so we need the |
| | | // more complicated fallback to webkitRTCPeerConnection. |
| | | result.browser = 'chrome'; |
| | | result.version = extractVersion(navigator.userAgent, |
| | | /Chrom(e|ium)\/(\d+)\./, 2); |
| | | } else if (navigator.mediaDevices && |
| | | navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { // Edge. |
| | | result.browser = 'edge'; |
| | | result.version = extractVersion(navigator.userAgent, |
| | | /Edge\/(\d+).(\d+)$/, 2); |
| | | } else if (window.RTCPeerConnection && |
| | | navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { // Safari. |
| | | result.browser = 'safari'; |
| | | result.version = extractVersion(navigator.userAgent, |
| | | /AppleWebKit\/(\d+)\./, 1); |
| | | result.supportsUnifiedPlan = window.RTCRtpTransceiver && |
| | | 'currentDirection' in window.RTCRtpTransceiver.prototype; |
| | | } else { // Default fallthrough: not supported. |
| | | result.browser = 'Not a supported browser.'; |
| | | return result; |
| | | } |
| | | |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Checks if something is an object. |
| | | * |
| | | * @param {*} val The something you want to check. |
| | | * @return true if val is an object, false otherwise. |
| | | */ |
| | | function isObject$1(val) { |
| | | return Object.prototype.toString.call(val) === '[object Object]'; |
| | | } |
| | | |
| | | /** |
| | | * Remove all empty objects and undefined values |
| | | * from a nested object -- an enhanced and vanilla version |
| | | * of Lodash's `compact`. |
| | | */ |
| | | function compactObject(data) { |
| | | if (!isObject$1(data)) { |
| | | return data; |
| | | } |
| | | |
| | | return Object.keys(data).reduce(function(accumulator, key) { |
| | | const isObj = isObject$1(data[key]); |
| | | const value = isObj ? compactObject(data[key]) : data[key]; |
| | | const isEmptyObject = isObj && !Object.keys(value).length; |
| | | if (value === undefined || isEmptyObject) { |
| | | return accumulator; |
| | | } |
| | | return Object.assign(accumulator, {[key]: value}); |
| | | }, {}); |
| | | } |
| | | |
| | | /* iterates the stats graph recursively. */ |
| | | function walkStats(stats, base, resultSet) { |
| | | if (!base || resultSet.has(base.id)) { |
| | | return; |
| | | } |
| | | resultSet.set(base.id, base); |
| | | Object.keys(base).forEach(name => { |
| | | if (name.endsWith('Id')) { |
| | | walkStats(stats, stats.get(base[name]), resultSet); |
| | | } else if (name.endsWith('Ids')) { |
| | | base[name].forEach(id => { |
| | | walkStats(stats, stats.get(id), resultSet); |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | /* filter getStats for a sender/receiver track. */ |
| | | function filterStats(result, track, outbound) { |
| | | const streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; |
| | | const filteredResult = new Map(); |
| | | if (track === null) { |
| | | return filteredResult; |
| | | } |
| | | const trackStats = []; |
| | | result.forEach(value => { |
| | | if (value.type === 'track' && |
| | | value.trackIdentifier === track.id) { |
| | | trackStats.push(value); |
| | | } |
| | | }); |
| | | trackStats.forEach(trackStat => { |
| | | result.forEach(stats => { |
| | | if (stats.type === streamStatsType && stats.trackId === trackStat.id) { |
| | | walkStats(result, stats, filteredResult); |
| | | } |
| | | }); |
| | | }); |
| | | return filteredResult; |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | const logging = log$1; |
| | | |
| | | function shimGetUserMedia$3(window, browserDetails) { |
| | | const navigator = window && window.navigator; |
| | | |
| | | if (!navigator.mediaDevices) { |
| | | return; |
| | | } |
| | | |
| | | const constraintsToChrome_ = function(c) { |
| | | if (typeof c !== 'object' || c.mandatory || c.optional) { |
| | | return c; |
| | | } |
| | | const cc = {}; |
| | | Object.keys(c).forEach(key => { |
| | | if (key === 'require' || key === 'advanced' || key === 'mediaSource') { |
| | | return; |
| | | } |
| | | const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]}; |
| | | if (r.exact !== undefined && typeof r.exact === 'number') { |
| | | r.min = r.max = r.exact; |
| | | } |
| | | const 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 || []; |
| | | let 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(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; |
| | | }; |
| | | |
| | | const shimConstraints_ = function(constraints, func) { |
| | | if (browserDetails.version >= 61) { |
| | | return func(constraints); |
| | | } |
| | | constraints = JSON.parse(JSON.stringify(constraints)); |
| | | if (constraints && typeof constraints.audio === 'object') { |
| | | const remap = function(obj, a, b) { |
| | | if (a in obj && !(b in obj)) { |
| | | obj[b] = obj[a]; |
| | | delete obj[a]; |
| | | } |
| | | }; |
| | | constraints = JSON.parse(JSON.stringify(constraints)); |
| | | remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); |
| | | remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); |
| | | constraints.audio = constraintsToChrome_(constraints.audio); |
| | | } |
| | | if (constraints && typeof constraints.video === 'object') { |
| | | // Shim facingMode for mobile & surface pro. |
| | | let face = constraints.video.facingMode; |
| | | face = face && ((typeof face === 'object') ? face : {ideal: face}); |
| | | const getSupportedFacingModeLies = browserDetails.version < 66; |
| | | |
| | | if ((face && (face.exact === 'user' || face.exact === 'environment' || |
| | | face.ideal === 'user' || face.ideal === 'environment')) && |
| | | !(navigator.mediaDevices.getSupportedConstraints && |
| | | navigator.mediaDevices.getSupportedConstraints().facingMode && |
| | | !getSupportedFacingModeLies)) { |
| | | delete constraints.video.facingMode; |
| | | let matches; |
| | | if (face.exact === 'environment' || face.ideal === 'environment') { |
| | | matches = ['back', 'rear']; |
| | | } else if (face.exact === 'user' || face.ideal === 'user') { |
| | | matches = ['front']; |
| | | } |
| | | if (matches) { |
| | | // Look for matches in label, or use last cam for back (typical). |
| | | return navigator.mediaDevices.enumerateDevices() |
| | | .then(devices => { |
| | | devices = devices.filter(d => d.kind === 'videoinput'); |
| | | let dev = devices.find(d => matches.some(match => |
| | | d.label.toLowerCase().includes(match))); |
| | | if (!dev && devices.length && matches.includes('back')) { |
| | | dev = devices[devices.length - 1]; // more likely the back cam |
| | | } |
| | | if (dev) { |
| | | constraints.video.deviceId = face.exact ? {exact: dev.deviceId} : |
| | | {ideal: dev.deviceId}; |
| | | } |
| | | constraints.video = constraintsToChrome_(constraints.video); |
| | | logging('chrome: ' + JSON.stringify(constraints)); |
| | | return func(constraints); |
| | | }); |
| | | } |
| | | } |
| | | constraints.video = constraintsToChrome_(constraints.video); |
| | | } |
| | | logging('chrome: ' + JSON.stringify(constraints)); |
| | | return func(constraints); |
| | | }; |
| | | |
| | | const shimError_ = function(e) { |
| | | if (browserDetails.version >= 64) { |
| | | return e; |
| | | } |
| | | return { |
| | | name: { |
| | | PermissionDeniedError: 'NotAllowedError', |
| | | PermissionDismissedError: 'NotAllowedError', |
| | | InvalidStateError: 'NotAllowedError', |
| | | DevicesNotFoundError: 'NotFoundError', |
| | | ConstraintNotSatisfiedError: 'OverconstrainedError', |
| | | TrackStartError: 'NotReadableError', |
| | | MediaDeviceFailedDueToShutdown: 'NotAllowedError', |
| | | MediaDeviceKillSwitchOn: 'NotAllowedError', |
| | | TabCaptureError: 'AbortError', |
| | | ScreenCaptureError: 'AbortError', |
| | | DeviceCaptureError: 'AbortError' |
| | | }[e.name] || e.name, |
| | | message: e.message, |
| | | constraint: e.constraint || e.constraintName, |
| | | toString() { |
| | | return this.name + (this.message && ': ') + this.message; |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | const getUserMedia_ = function(constraints, onSuccess, onError) { |
| | | shimConstraints_(constraints, c => { |
| | | navigator.webkitGetUserMedia(c, onSuccess, e => { |
| | | if (onError) { |
| | | onError(shimError_(e)); |
| | | } |
| | | }); |
| | | }); |
| | | }; |
| | | navigator.getUserMedia = getUserMedia_.bind(navigator); |
| | | |
| | | // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia |
| | | // function which returns a Promise, it does not accept spec-style |
| | | // constraints. |
| | | if (navigator.mediaDevices.getUserMedia) { |
| | | const origGetUserMedia = navigator.mediaDevices.getUserMedia. |
| | | bind(navigator.mediaDevices); |
| | | navigator.mediaDevices.getUserMedia = function(cs) { |
| | | return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => { |
| | | if (c.audio && !stream.getAudioTracks().length || |
| | | c.video && !stream.getVideoTracks().length) { |
| | | stream.getTracks().forEach(track => { |
| | | track.stop(); |
| | | }); |
| | | throw new DOMException('', 'NotFoundError'); |
| | | } |
| | | return stream; |
| | | }, e => Promise.reject(shimError_(e)))); |
| | | }; |
| | | } |
| | | } |
| | | |
| | | /* |
| | | * Copyright (c) 2018 The adapter.js 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. |
| | | */ |
| | | function shimGetDisplayMedia$2(window, getSourceId) { |
| | | if (window.navigator.mediaDevices && |
| | | 'getDisplayMedia' in window.navigator.mediaDevices) { |
| | | return; |
| | | } |
| | | if (!(window.navigator.mediaDevices)) { |
| | | return; |
| | | } |
| | | // getSourceId is a function that returns a promise resolving with |
| | | // the sourceId of the screen/window/tab to be shared. |
| | | if (typeof getSourceId !== 'function') { |
| | | console.error('shimGetDisplayMedia: getSourceId argument is not ' + |
| | | 'a function'); |
| | | return; |
| | | } |
| | | window.navigator.mediaDevices.getDisplayMedia = |
| | | function getDisplayMedia(constraints) { |
| | | return getSourceId(constraints) |
| | | .then(sourceId => { |
| | | const widthSpecified = constraints.video && constraints.video.width; |
| | | const heightSpecified = constraints.video && |
| | | constraints.video.height; |
| | | const frameRateSpecified = constraints.video && |
| | | constraints.video.frameRate; |
| | | constraints.video = { |
| | | mandatory: { |
| | | chromeMediaSource: 'desktop', |
| | | chromeMediaSourceId: sourceId, |
| | | maxFrameRate: frameRateSpecified || 3 |
| | | } |
| | | }; |
| | | if (widthSpecified) { |
| | | constraints.video.mandatory.maxWidth = widthSpecified; |
| | | } |
| | | if (heightSpecified) { |
| | | constraints.video.mandatory.maxHeight = heightSpecified; |
| | | } |
| | | return window.navigator.mediaDevices.getUserMedia(constraints); |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimMediaStream(window) { |
| | | window.MediaStream = window.MediaStream || window.webkitMediaStream; |
| | | } |
| | | |
| | | function shimOnTrack$1(window) { |
| | | if (typeof window === 'object' && window.RTCPeerConnection && !('ontrack' in |
| | | window.RTCPeerConnection.prototype)) { |
| | | Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { |
| | | get() { |
| | | return this._ontrack; |
| | | }, |
| | | set(f) { |
| | | if (this._ontrack) { |
| | | this.removeEventListener('track', this._ontrack); |
| | | } |
| | | this.addEventListener('track', this._ontrack = f); |
| | | }, |
| | | enumerable: true, |
| | | configurable: true |
| | | }); |
| | | const origSetRemoteDescription = |
| | | window.RTCPeerConnection.prototype.setRemoteDescription; |
| | | window.RTCPeerConnection.prototype.setRemoteDescription = |
| | | function setRemoteDescription() { |
| | | if (!this._ontrackpoly) { |
| | | this._ontrackpoly = (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', te => { |
| | | let receiver; |
| | | if (window.RTCPeerConnection.prototype.getReceivers) { |
| | | receiver = this.getReceivers() |
| | | .find(r => r.track && r.track.id === te.track.id); |
| | | } else { |
| | | receiver = {track: te.track}; |
| | | } |
| | | |
| | | const event = new Event('track'); |
| | | event.track = te.track; |
| | | event.receiver = receiver; |
| | | event.transceiver = {receiver}; |
| | | event.streams = [e.stream]; |
| | | this.dispatchEvent(event); |
| | | }); |
| | | e.stream.getTracks().forEach(track => { |
| | | let receiver; |
| | | if (window.RTCPeerConnection.prototype.getReceivers) { |
| | | receiver = this.getReceivers() |
| | | .find(r => r.track && r.track.id === track.id); |
| | | } else { |
| | | receiver = {track}; |
| | | } |
| | | const event = new Event('track'); |
| | | event.track = track; |
| | | event.receiver = receiver; |
| | | event.transceiver = {receiver}; |
| | | event.streams = [e.stream]; |
| | | this.dispatchEvent(event); |
| | | }); |
| | | }; |
| | | this.addEventListener('addstream', this._ontrackpoly); |
| | | } |
| | | return origSetRemoteDescription.apply(this, arguments); |
| | | }; |
| | | } else { |
| | | // even if RTCRtpTransceiver is in window, it is only used and |
| | | // emitted in unified-plan. Unfortunately this means we need |
| | | // to unconditionally wrap the event. |
| | | wrapPeerConnectionEvent(window, 'track', e => { |
| | | if (!e.transceiver) { |
| | | Object.defineProperty(e, 'transceiver', |
| | | {value: {receiver: e.receiver}}); |
| | | } |
| | | return e; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function shimGetSendersWithDtmf(window) { |
| | | // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. |
| | | if (typeof window === 'object' && window.RTCPeerConnection && |
| | | !('getSenders' in window.RTCPeerConnection.prototype) && |
| | | 'createDTMFSender' in window.RTCPeerConnection.prototype) { |
| | | const shimSenderWithDtmf = function(pc, track) { |
| | | return { |
| | | track, |
| | | get dtmf() { |
| | | if (this._dtmf === undefined) { |
| | | if (track.kind === 'audio') { |
| | | this._dtmf = pc.createDTMFSender(track); |
| | | } else { |
| | | this._dtmf = null; |
| | | } |
| | | } |
| | | return this._dtmf; |
| | | }, |
| | | _pc: pc |
| | | }; |
| | | }; |
| | | |
| | | // augment addTrack when getSenders is not available. |
| | | if (!window.RTCPeerConnection.prototype.getSenders) { |
| | | window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
| | | this._senders = this._senders || []; |
| | | return this._senders.slice(); // return a copy of the internal state. |
| | | }; |
| | | const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
| | | window.RTCPeerConnection.prototype.addTrack = |
| | | function addTrack(track, stream) { |
| | | let sender = origAddTrack.apply(this, arguments); |
| | | if (!sender) { |
| | | sender = shimSenderWithDtmf(this, track); |
| | | this._senders.push(sender); |
| | | } |
| | | return sender; |
| | | }; |
| | | |
| | | const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; |
| | | window.RTCPeerConnection.prototype.removeTrack = |
| | | function removeTrack(sender) { |
| | | origRemoveTrack.apply(this, arguments); |
| | | const idx = this._senders.indexOf(sender); |
| | | if (idx !== -1) { |
| | | this._senders.splice(idx, 1); |
| | | } |
| | | }; |
| | | } |
| | | const origAddStream = window.RTCPeerConnection.prototype.addStream; |
| | | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
| | | this._senders = this._senders || []; |
| | | origAddStream.apply(this, [stream]); |
| | | stream.getTracks().forEach(track => { |
| | | this._senders.push(shimSenderWithDtmf(this, track)); |
| | | }); |
| | | }; |
| | | |
| | | const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
| | | window.RTCPeerConnection.prototype.removeStream = |
| | | function removeStream(stream) { |
| | | this._senders = this._senders || []; |
| | | origRemoveStream.apply(this, [stream]); |
| | | |
| | | stream.getTracks().forEach(track => { |
| | | const sender = this._senders.find(s => s.track === track); |
| | | if (sender) { // remove sender |
| | | this._senders.splice(this._senders.indexOf(sender), 1); |
| | | } |
| | | }); |
| | | }; |
| | | } else if (typeof window === 'object' && window.RTCPeerConnection && |
| | | 'getSenders' in window.RTCPeerConnection.prototype && |
| | | 'createDTMFSender' in window.RTCPeerConnection.prototype && |
| | | window.RTCRtpSender && |
| | | !('dtmf' in window.RTCRtpSender.prototype)) { |
| | | const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
| | | window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
| | | const senders = origGetSenders.apply(this, []); |
| | | senders.forEach(sender => sender._pc = this); |
| | | return senders; |
| | | }; |
| | | |
| | | Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { |
| | | get() { |
| | | if (this._dtmf === undefined) { |
| | | if (this.track.kind === 'audio') { |
| | | this._dtmf = this._pc.createDTMFSender(this.track); |
| | | } else { |
| | | this._dtmf = null; |
| | | } |
| | | } |
| | | return this._dtmf; |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function shimGetStats(window) { |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | |
| | | const origGetStats = window.RTCPeerConnection.prototype.getStats; |
| | | window.RTCPeerConnection.prototype.getStats = function getStats() { |
| | | const [selector, onSucc, onErr] = 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.apply(this, arguments); |
| | | } |
| | | |
| | | // When spec-style getStats is supported, return those when called with |
| | | // either no arguments or the selector argument is null. |
| | | if (origGetStats.length === 0 && (arguments.length === 0 || |
| | | typeof selector !== 'function')) { |
| | | return origGetStats.apply(this, []); |
| | | } |
| | | |
| | | const fixChromeStats_ = function(response) { |
| | | const standardReport = {}; |
| | | const reports = response.result(); |
| | | reports.forEach(report => { |
| | | const standardStats = { |
| | | id: report.id, |
| | | timestamp: report.timestamp, |
| | | type: { |
| | | localcandidate: 'local-candidate', |
| | | remotecandidate: 'remote-candidate' |
| | | }[report.type] || report.type |
| | | }; |
| | | report.names().forEach(name => { |
| | | standardStats[name] = report.stat(name); |
| | | }); |
| | | standardReport[standardStats.id] = standardStats; |
| | | }); |
| | | |
| | | return standardReport; |
| | | }; |
| | | |
| | | // shim getStats with maplike support |
| | | const makeMapStats = function(stats) { |
| | | return new Map(Object.keys(stats).map(key => [key, stats[key]])); |
| | | }; |
| | | |
| | | if (arguments.length >= 2) { |
| | | const successCallbackWrapper_ = function(response) { |
| | | onSucc(makeMapStats(fixChromeStats_(response))); |
| | | }; |
| | | |
| | | return origGetStats.apply(this, [successCallbackWrapper_, |
| | | selector]); |
| | | } |
| | | |
| | | // promise-support |
| | | return new Promise((resolve, reject) => { |
| | | origGetStats.apply(this, [ |
| | | function(response) { |
| | | resolve(makeMapStats(fixChromeStats_(response))); |
| | | }, reject]); |
| | | }).then(onSucc, onErr); |
| | | }; |
| | | } |
| | | |
| | | function shimSenderReceiverGetStats(window) { |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection && |
| | | window.RTCRtpSender && window.RTCRtpReceiver)) { |
| | | return; |
| | | } |
| | | |
| | | // shim sender stats. |
| | | if (!('getStats' in window.RTCRtpSender.prototype)) { |
| | | const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
| | | if (origGetSenders) { |
| | | window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
| | | const senders = origGetSenders.apply(this, []); |
| | | senders.forEach(sender => sender._pc = this); |
| | | return senders; |
| | | }; |
| | | } |
| | | |
| | | const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
| | | if (origAddTrack) { |
| | | window.RTCPeerConnection.prototype.addTrack = function addTrack() { |
| | | const sender = origAddTrack.apply(this, arguments); |
| | | sender._pc = this; |
| | | return sender; |
| | | }; |
| | | } |
| | | window.RTCRtpSender.prototype.getStats = function getStats() { |
| | | const sender = this; |
| | | return this._pc.getStats().then(result => |
| | | /* Note: this will include stats of all senders that |
| | | * send a track with the same id as sender.track as |
| | | * it is not possible to identify the RTCRtpSender. |
| | | */ |
| | | filterStats(result, sender.track, true)); |
| | | }; |
| | | } |
| | | |
| | | // shim receiver stats. |
| | | if (!('getStats' in window.RTCRtpReceiver.prototype)) { |
| | | const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; |
| | | if (origGetReceivers) { |
| | | window.RTCPeerConnection.prototype.getReceivers = |
| | | function getReceivers() { |
| | | const receivers = origGetReceivers.apply(this, []); |
| | | receivers.forEach(receiver => receiver._pc = this); |
| | | return receivers; |
| | | }; |
| | | } |
| | | wrapPeerConnectionEvent(window, 'track', e => { |
| | | e.receiver._pc = e.srcElement; |
| | | return e; |
| | | }); |
| | | window.RTCRtpReceiver.prototype.getStats = function getStats() { |
| | | const receiver = this; |
| | | return this._pc.getStats().then(result => |
| | | filterStats(result, receiver.track, false)); |
| | | }; |
| | | } |
| | | |
| | | if (!('getStats' in window.RTCRtpSender.prototype && |
| | | 'getStats' in window.RTCRtpReceiver.prototype)) { |
| | | return; |
| | | } |
| | | |
| | | // shim RTCPeerConnection.getStats(track). |
| | | const origGetStats = window.RTCPeerConnection.prototype.getStats; |
| | | window.RTCPeerConnection.prototype.getStats = function getStats() { |
| | | if (arguments.length > 0 && |
| | | arguments[0] instanceof window.MediaStreamTrack) { |
| | | const track = arguments[0]; |
| | | let sender; |
| | | let receiver; |
| | | let err; |
| | | this.getSenders().forEach(s => { |
| | | if (s.track === track) { |
| | | if (sender) { |
| | | err = true; |
| | | } else { |
| | | sender = s; |
| | | } |
| | | } |
| | | }); |
| | | this.getReceivers().forEach(r => { |
| | | if (r.track === track) { |
| | | if (receiver) { |
| | | err = true; |
| | | } else { |
| | | receiver = r; |
| | | } |
| | | } |
| | | return r.track === track; |
| | | }); |
| | | if (err || (sender && receiver)) { |
| | | return Promise.reject(new DOMException( |
| | | 'There are more than one sender or receiver for the track.', |
| | | 'InvalidAccessError')); |
| | | } else if (sender) { |
| | | return sender.getStats(); |
| | | } else if (receiver) { |
| | | return receiver.getStats(); |
| | | } |
| | | return Promise.reject(new DOMException( |
| | | 'There is no sender or receiver for the track.', |
| | | 'InvalidAccessError')); |
| | | } |
| | | return origGetStats.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimAddTrackRemoveTrackWithNative(window) { |
| | | // shim addTrack/removeTrack with native variants in order to make |
| | | // the interactions with legacy getLocalStreams behave as in other browsers. |
| | | // Keeps a mapping stream.id => [stream, rtpsenders...] |
| | | window.RTCPeerConnection.prototype.getLocalStreams = |
| | | function getLocalStreams() { |
| | | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
| | | return Object.keys(this._shimmedLocalStreams) |
| | | .map(streamId => this._shimmedLocalStreams[streamId][0]); |
| | | }; |
| | | |
| | | const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
| | | window.RTCPeerConnection.prototype.addTrack = |
| | | function addTrack(track, stream) { |
| | | if (!stream) { |
| | | return origAddTrack.apply(this, arguments); |
| | | } |
| | | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
| | | |
| | | const sender = origAddTrack.apply(this, arguments); |
| | | if (!this._shimmedLocalStreams[stream.id]) { |
| | | this._shimmedLocalStreams[stream.id] = [stream, sender]; |
| | | } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { |
| | | this._shimmedLocalStreams[stream.id].push(sender); |
| | | } |
| | | return sender; |
| | | }; |
| | | |
| | | const origAddStream = window.RTCPeerConnection.prototype.addStream; |
| | | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
| | | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
| | | |
| | | stream.getTracks().forEach(track => { |
| | | const alreadyExists = this.getSenders().find(s => s.track === track); |
| | | if (alreadyExists) { |
| | | throw new DOMException('Track already exists.', |
| | | 'InvalidAccessError'); |
| | | } |
| | | }); |
| | | const existingSenders = this.getSenders(); |
| | | origAddStream.apply(this, arguments); |
| | | const newSenders = this.getSenders() |
| | | .filter(newSender => existingSenders.indexOf(newSender) === -1); |
| | | this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); |
| | | }; |
| | | |
| | | const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
| | | window.RTCPeerConnection.prototype.removeStream = |
| | | function removeStream(stream) { |
| | | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
| | | delete this._shimmedLocalStreams[stream.id]; |
| | | return origRemoveStream.apply(this, arguments); |
| | | }; |
| | | |
| | | const origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; |
| | | window.RTCPeerConnection.prototype.removeTrack = |
| | | function removeTrack(sender) { |
| | | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; |
| | | if (sender) { |
| | | Object.keys(this._shimmedLocalStreams).forEach(streamId => { |
| | | const idx = this._shimmedLocalStreams[streamId].indexOf(sender); |
| | | if (idx !== -1) { |
| | | this._shimmedLocalStreams[streamId].splice(idx, 1); |
| | | } |
| | | if (this._shimmedLocalStreams[streamId].length === 1) { |
| | | delete this._shimmedLocalStreams[streamId]; |
| | | } |
| | | }); |
| | | } |
| | | return origRemoveTrack.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimAddTrackRemoveTrack(window, browserDetails) { |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | // shim addTrack and removeTrack. |
| | | if (window.RTCPeerConnection.prototype.addTrack && |
| | | browserDetails.version >= 65) { |
| | | return shimAddTrackRemoveTrackWithNative(window); |
| | | } |
| | | |
| | | // also shim pc.getLocalStreams when addTrack is shimmed |
| | | // to return the original streams. |
| | | const origGetLocalStreams = window.RTCPeerConnection.prototype |
| | | .getLocalStreams; |
| | | window.RTCPeerConnection.prototype.getLocalStreams = |
| | | function getLocalStreams() { |
| | | const nativeStreams = origGetLocalStreams.apply(this); |
| | | this._reverseStreams = this._reverseStreams || {}; |
| | | return nativeStreams.map(stream => this._reverseStreams[stream.id]); |
| | | }; |
| | | |
| | | const origAddStream = window.RTCPeerConnection.prototype.addStream; |
| | | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
| | | this._streams = this._streams || {}; |
| | | this._reverseStreams = this._reverseStreams || {}; |
| | | |
| | | stream.getTracks().forEach(track => { |
| | | const alreadyExists = this.getSenders().find(s => s.track === track); |
| | | if (alreadyExists) { |
| | | throw new DOMException('Track already exists.', |
| | | 'InvalidAccessError'); |
| | | } |
| | | }); |
| | | // Add identity mapping for consistency with addTrack. |
| | | // Unless this is being used with a stream from addTrack. |
| | | if (!this._reverseStreams[stream.id]) { |
| | | const newStream = new window.MediaStream(stream.getTracks()); |
| | | this._streams[stream.id] = newStream; |
| | | this._reverseStreams[newStream.id] = stream; |
| | | stream = newStream; |
| | | } |
| | | origAddStream.apply(this, [stream]); |
| | | }; |
| | | |
| | | const origRemoveStream = window.RTCPeerConnection.prototype.removeStream; |
| | | window.RTCPeerConnection.prototype.removeStream = |
| | | function removeStream(stream) { |
| | | this._streams = this._streams || {}; |
| | | this._reverseStreams = this._reverseStreams || {}; |
| | | |
| | | origRemoveStream.apply(this, [(this._streams[stream.id] || stream)]); |
| | | delete this._reverseStreams[(this._streams[stream.id] ? |
| | | this._streams[stream.id].id : stream.id)]; |
| | | delete this._streams[stream.id]; |
| | | }; |
| | | |
| | | window.RTCPeerConnection.prototype.addTrack = |
| | | function addTrack(track, stream) { |
| | | if (this.signalingState === 'closed') { |
| | | throw new DOMException( |
| | | 'The RTCPeerConnection\'s signalingState is \'closed\'.', |
| | | 'InvalidStateError'); |
| | | } |
| | | const streams = [].slice.call(arguments, 1); |
| | | if (streams.length !== 1 || |
| | | !streams[0].getTracks().find(t => t === track)) { |
| | | // this is not fully correct but all we can manage without |
| | | // [[associated MediaStreams]] internal slot. |
| | | throw new DOMException( |
| | | 'The adapter.js addTrack polyfill only supports a single ' + |
| | | ' stream which is associated with the specified track.', |
| | | 'NotSupportedError'); |
| | | } |
| | | |
| | | const alreadyExists = this.getSenders().find(s => s.track === track); |
| | | if (alreadyExists) { |
| | | throw new DOMException('Track already exists.', |
| | | 'InvalidAccessError'); |
| | | } |
| | | |
| | | this._streams = this._streams || {}; |
| | | this._reverseStreams = this._reverseStreams || {}; |
| | | const oldStream = this._streams[stream.id]; |
| | | if (oldStream) { |
| | | // this is using odd Chrome behaviour, use with caution: |
| | | // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 |
| | | // Note: we rely on the high-level addTrack/dtmf shim to |
| | | // create the sender with a dtmf sender. |
| | | oldStream.addTrack(track); |
| | | |
| | | // Trigger ONN async. |
| | | Promise.resolve().then(() => { |
| | | this.dispatchEvent(new Event('negotiationneeded')); |
| | | }); |
| | | } else { |
| | | const newStream = new window.MediaStream([track]); |
| | | this._streams[stream.id] = newStream; |
| | | this._reverseStreams[newStream.id] = stream; |
| | | this.addStream(newStream); |
| | | } |
| | | return this.getSenders().find(s => s.track === track); |
| | | }; |
| | | |
| | | // replace the internal stream id with the external one and |
| | | // vice versa. |
| | | function replaceInternalStreamId(pc, description) { |
| | | let sdp = description.sdp; |
| | | Object.keys(pc._reverseStreams || []).forEach(internalId => { |
| | | const externalStream = pc._reverseStreams[internalId]; |
| | | const internalStream = pc._streams[externalStream.id]; |
| | | sdp = sdp.replace(new RegExp(internalStream.id, 'g'), |
| | | externalStream.id); |
| | | }); |
| | | return new RTCSessionDescription({ |
| | | type: description.type, |
| | | sdp |
| | | }); |
| | | } |
| | | function replaceExternalStreamId(pc, description) { |
| | | let sdp = description.sdp; |
| | | Object.keys(pc._reverseStreams || []).forEach(internalId => { |
| | | const externalStream = pc._reverseStreams[internalId]; |
| | | const internalStream = pc._streams[externalStream.id]; |
| | | sdp = sdp.replace(new RegExp(externalStream.id, 'g'), |
| | | internalStream.id); |
| | | }); |
| | | return new RTCSessionDescription({ |
| | | type: description.type, |
| | | sdp |
| | | }); |
| | | } |
| | | ['createOffer', 'createAnswer'].forEach(function(method) { |
| | | const nativeMethod = window.RTCPeerConnection.prototype[method]; |
| | | const methodObj = {[method]() { |
| | | const args = arguments; |
| | | const isLegacyCall = arguments.length && |
| | | typeof arguments[0] === 'function'; |
| | | if (isLegacyCall) { |
| | | return nativeMethod.apply(this, [ |
| | | (description) => { |
| | | const desc = replaceInternalStreamId(this, description); |
| | | args[0].apply(null, [desc]); |
| | | }, |
| | | (err) => { |
| | | if (args[1]) { |
| | | args[1].apply(null, err); |
| | | } |
| | | }, arguments[2] |
| | | ]); |
| | | } |
| | | return nativeMethod.apply(this, arguments) |
| | | .then(description => replaceInternalStreamId(this, description)); |
| | | }}; |
| | | window.RTCPeerConnection.prototype[method] = methodObj[method]; |
| | | }); |
| | | |
| | | const origSetLocalDescription = |
| | | window.RTCPeerConnection.prototype.setLocalDescription; |
| | | window.RTCPeerConnection.prototype.setLocalDescription = |
| | | function setLocalDescription() { |
| | | if (!arguments.length || !arguments[0].type) { |
| | | return origSetLocalDescription.apply(this, arguments); |
| | | } |
| | | arguments[0] = replaceExternalStreamId(this, arguments[0]); |
| | | return origSetLocalDescription.apply(this, arguments); |
| | | }; |
| | | |
| | | // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier |
| | | |
| | | const origLocalDescription = Object.getOwnPropertyDescriptor( |
| | | window.RTCPeerConnection.prototype, 'localDescription'); |
| | | Object.defineProperty(window.RTCPeerConnection.prototype, |
| | | 'localDescription', { |
| | | get() { |
| | | const description = origLocalDescription.get.apply(this); |
| | | if (description.type === '') { |
| | | return description; |
| | | } |
| | | return replaceInternalStreamId(this, description); |
| | | } |
| | | }); |
| | | |
| | | window.RTCPeerConnection.prototype.removeTrack = |
| | | function removeTrack(sender) { |
| | | if (this.signalingState === 'closed') { |
| | | throw new DOMException( |
| | | 'The RTCPeerConnection\'s signalingState is \'closed\'.', |
| | | 'InvalidStateError'); |
| | | } |
| | | // We can not yet check for sender instanceof RTCRtpSender |
| | | // since we shim RTPSender. So we check if sender._pc is set. |
| | | if (!sender._pc) { |
| | | throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + |
| | | 'does not implement interface RTCRtpSender.', 'TypeError'); |
| | | } |
| | | const isLocal = sender._pc === this; |
| | | if (!isLocal) { |
| | | throw new DOMException('Sender was not created by this connection.', |
| | | 'InvalidAccessError'); |
| | | } |
| | | |
| | | // Search for the native stream the senders track belongs to. |
| | | this._streams = this._streams || {}; |
| | | let stream; |
| | | Object.keys(this._streams).forEach(streamid => { |
| | | const hasTrack = this._streams[streamid].getTracks() |
| | | .find(track => sender.track === track); |
| | | if (hasTrack) { |
| | | stream = this._streams[streamid]; |
| | | } |
| | | }); |
| | | |
| | | if (stream) { |
| | | if (stream.getTracks().length === 1) { |
| | | // if this is the last track of the stream, remove the stream. This |
| | | // takes care of any shimmed _senders. |
| | | this.removeStream(this._reverseStreams[stream.id]); |
| | | } else { |
| | | // relying on the same odd chrome behaviour as above. |
| | | stream.removeTrack(sender.track); |
| | | } |
| | | this.dispatchEvent(new Event('negotiationneeded')); |
| | | } |
| | | }; |
| | | } |
| | | |
| | | function shimPeerConnection$2(window, browserDetails) { |
| | | if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { |
| | | // very basic support for old versions. |
| | | window.RTCPeerConnection = window.webkitRTCPeerConnection; |
| | | } |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | |
| | | // shim implicit creation of RTCSessionDescription/RTCIceCandidate |
| | | if (browserDetails.version < 53) { |
| | | ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] |
| | | .forEach(function(method) { |
| | | const nativeMethod = window.RTCPeerConnection.prototype[method]; |
| | | const methodObj = {[method]() { |
| | | arguments[0] = new ((method === 'addIceCandidate') ? |
| | | window.RTCIceCandidate : |
| | | window.RTCSessionDescription)(arguments[0]); |
| | | return nativeMethod.apply(this, arguments); |
| | | }}; |
| | | window.RTCPeerConnection.prototype[method] = methodObj[method]; |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // Attempt to fix ONN in plan-b mode. |
| | | function fixNegotiationNeeded(window, browserDetails) { |
| | | wrapPeerConnectionEvent(window, 'negotiationneeded', e => { |
| | | const pc = e.target; |
| | | if (browserDetails.version < 72 || (pc.getConfiguration && |
| | | pc.getConfiguration().sdpSemantics === 'plan-b')) { |
| | | if (pc.signalingState !== 'stable') { |
| | | return; |
| | | } |
| | | } |
| | | return e; |
| | | }); |
| | | } |
| | | |
| | | var chromeShim = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | shimMediaStream: shimMediaStream, |
| | | shimOnTrack: shimOnTrack$1, |
| | | shimGetSendersWithDtmf: shimGetSendersWithDtmf, |
| | | shimGetStats: shimGetStats, |
| | | shimSenderReceiverGetStats: shimSenderReceiverGetStats, |
| | | shimAddTrackRemoveTrackWithNative: shimAddTrackRemoveTrackWithNative, |
| | | shimAddTrackRemoveTrack: shimAddTrackRemoveTrack, |
| | | shimPeerConnection: shimPeerConnection$2, |
| | | fixNegotiationNeeded: fixNegotiationNeeded, |
| | | shimGetUserMedia: shimGetUserMedia$3, |
| | | shimGetDisplayMedia: shimGetDisplayMedia$2 |
| | | }); |
| | | |
| | | /* |
| | | * Copyright (c) 2018 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. |
| | | */ |
| | | // Edge does not like |
| | | // 1) stun: filtered after 14393 unless ?transport=udp is present |
| | | // 2) turn: that does not have all of turn:host:port?transport=udp |
| | | // 3) turn: with ipv6 addresses |
| | | // 4) turn: occurring muliple times |
| | | function filterIceServers$1(iceServers, edgeVersion) { |
| | | let hasTurn = false; |
| | | iceServers = JSON.parse(JSON.stringify(iceServers)); |
| | | return iceServers.filter(server => { |
| | | if (server && (server.urls || server.url)) { |
| | | let urls = server.urls || server.url; |
| | | if (server.url && !server.urls) { |
| | | deprecated('RTCIceServer.url', 'RTCIceServer.urls'); |
| | | } |
| | | const isString = typeof urls === 'string'; |
| | | if (isString) { |
| | | urls = [urls]; |
| | | } |
| | | urls = urls.filter(url => { |
| | | // filter STUN unconditionally. |
| | | if (url.indexOf('stun:') === 0) { |
| | | return false; |
| | | } |
| | | |
| | | const validTurn = url.startsWith('turn') && |
| | | !url.startsWith('turn:[') && |
| | | url.includes('transport=udp'); |
| | | if (validTurn && !hasTurn) { |
| | | hasTurn = true; |
| | | return true; |
| | | } |
| | | return validTurn && !hasTurn; |
| | | }); |
| | | |
| | | delete server.url; |
| | | server.urls = isString ? urls[0] : urls; |
| | | return !!urls.length; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | function createCommonjsModule(fn) { |
| | | var module = { exports: {} }; |
| | | return fn(module, module.exports), module.exports; |
| | | } |
| | | |
| | | /* eslint-env node */ |
| | | |
| | | var sdp = createCommonjsModule(function (module) { |
| | | |
| | | // 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 the session description. |
| | | SDPUtils.getDescription = function(blob) { |
| | | var sections = SDPUtils.splitSections(blob); |
| | | return sections && sections[0]; |
| | | }; |
| | | |
| | | // returns the individual media sections. |
| | | SDPUtils.getMediaSections = function(blob) { |
| | | var sections = SDPUtils.splitSections(blob); |
| | | sections.shift(); |
| | | return sections; |
| | | }; |
| | | |
| | | // 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: parseInt(parts[1], 10), |
| | | protocol: parts[2].toLowerCase(), |
| | | priority: parseInt(parts[3], 10), |
| | | ip: parts[4], |
| | | address: parts[4], // address is an alias for ip. |
| | | 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; |
| | | case 'ufrag': |
| | | candidate.ufrag = parts[i + 1]; // for backward compability. |
| | | candidate.usernameFragment = parts[i + 1]; |
| | | break; |
| | | default: // extension handling, in particular ufrag |
| | | candidate[parts[i]] = parts[i + 1]; |
| | | 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.address || 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); |
| | | sdp.push('rport'); |
| | | sdp.push(candidate.relatedPort); |
| | | } |
| | | if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { |
| | | sdp.push('tcptype'); |
| | | sdp.push(candidate.tcpType); |
| | | } |
| | | if (candidate.usernameFragment || candidate.ufrag) { |
| | | sdp.push('ufrag'); |
| | | sdp.push(candidate.usernameFragment || candidate.ufrag); |
| | | } |
| | | return 'candidate:' + sdp.join(' '); |
| | | }; |
| | | |
| | | // Parses an ice-options line, returns an array of option tags. |
| | | // a=ice-options:foo bar |
| | | SDPUtils.parseIceOptions = function(line) { |
| | | return line.substr(14).split(' '); |
| | | }; |
| | | |
| | | // 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.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; |
| | | // legacy alias, got renamed back to channels in ORTC. |
| | | parsed.numChannels = parsed.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; |
| | | } |
| | | var channels = codec.channels || codec.numChannels || 1; |
| | | return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + |
| | | (channels !== 1 ? '/' + channels : '') + '\r\n'; |
| | | }; |
| | | |
| | | // Parses an a=extmap line (headerextension from RFC 5285). Sample input: |
| | | // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset |
| | | // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset |
| | | SDPUtils.parseExtmap = function(line) { |
| | | var parts = line.substr(9).split(' '); |
| | | return { |
| | | id: parseInt(parts[0], 10), |
| | | direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', |
| | | uri: parts[1] |
| | | }; |
| | | }; |
| | | |
| | | // Generates a=extmap line from RTCRtpHeaderExtensionParameters or |
| | | // RTCRtpHeaderExtension. |
| | | SDPUtils.writeExtmap = function(headerExtension) { |
| | | return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + |
| | | (headerExtension.direction && headerExtension.direction !== 'sendrecv' |
| | | ? '/' + headerExtension.direction |
| | | : '') + |
| | | ' ' + 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) { |
| | | if (codec.parameters[param]) { |
| | | params.push(param + '=' + codec.parameters[param]); |
| | | } else { |
| | | params.push(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 && fb.parameter.length ? ' ' + 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; |
| | | }; |
| | | |
| | | SDPUtils.parseSsrcGroup = function(line) { |
| | | var parts = line.substr(13).split(' '); |
| | | return { |
| | | semantics: parts.shift(), |
| | | ssrcs: parts.map(function(ssrc) { |
| | | return parseInt(ssrc, 10); |
| | | }) |
| | | }; |
| | | }; |
| | | |
| | | // Extracts the MID (RFC 5888) from a media section. |
| | | // returns the MID or undefined if no mid line was found. |
| | | SDPUtils.getMid = function(mediaSection) { |
| | | var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; |
| | | if (mid) { |
| | | return mid.substr(6); |
| | | } |
| | | }; |
| | | |
| | | SDPUtils.parseFingerprint = function(line) { |
| | | var parts = line.substr(14).split(' '); |
| | | return { |
| | | algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. |
| | | value: parts[1] |
| | | }; |
| | | }; |
| | | |
| | | // 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.matchPrefix(mediaSection + sessionpart, |
| | | 'a=fingerprint:'); |
| | | // Note: a=setup line is ignored since we use the 'auto' role. |
| | | // Note2: 'algorithm' is not case sensitive except in Edge. |
| | | return { |
| | | role: 'auto', |
| | | fingerprints: lines.map(SDPUtils.parseFingerprint) |
| | | }; |
| | | }; |
| | | |
| | | // 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 a=crypto lines into |
| | | // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members |
| | | SDPUtils.parseCryptoLine = function(line) { |
| | | var parts = line.substr(9).split(' '); |
| | | return { |
| | | tag: parseInt(parts[0], 10), |
| | | cryptoSuite: parts[1], |
| | | keyParams: parts[2], |
| | | sessionParams: parts.slice(3), |
| | | }; |
| | | }; |
| | | |
| | | SDPUtils.writeCryptoLine = function(parameters) { |
| | | return 'a=crypto:' + parameters.tag + ' ' + |
| | | parameters.cryptoSuite + ' ' + |
| | | (typeof parameters.keyParams === 'object' |
| | | ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) |
| | | : parameters.keyParams) + |
| | | (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + |
| | | '\r\n'; |
| | | }; |
| | | |
| | | // Parses the crypto key parameters into |
| | | // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* |
| | | SDPUtils.parseCryptoKeyParams = function(keyParams) { |
| | | if (keyParams.indexOf('inline:') !== 0) { |
| | | return null; |
| | | } |
| | | var parts = keyParams.substr(7).split('|'); |
| | | return { |
| | | keyMethod: 'inline', |
| | | keySalt: parts[0], |
| | | lifeTime: parts[1], |
| | | mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, |
| | | mkiLength: parts[2] ? parts[2].split(':')[1] : undefined, |
| | | }; |
| | | }; |
| | | |
| | | SDPUtils.writeCryptoKeyParams = function(keyParams) { |
| | | return keyParams.keyMethod + ':' |
| | | + keyParams.keySalt + |
| | | (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + |
| | | (keyParams.mkiValue && keyParams.mkiLength |
| | | ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength |
| | | : ''); |
| | | }; |
| | | |
| | | // Extracts all SDES paramters. |
| | | SDPUtils.getCryptoParameters = function(mediaSection, sessionpart) { |
| | | var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, |
| | | 'a=crypto:'); |
| | | return lines.map(SDPUtils.parseCryptoLine); |
| | | }; |
| | | |
| | | // 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 ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, |
| | | 'a=ice-ufrag:')[0]; |
| | | var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, |
| | | 'a=ice-pwd:')[0]; |
| | | if (!(ufrag && pwd)) { |
| | | return null; |
| | | } |
| | | return { |
| | | usernameFragment: ufrag.substr(12), |
| | | password: pwd.substr(10), |
| | | }; |
| | | }; |
| | | |
| | | // 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); |
| | | // parse FEC mechanisms from rtpmap lines. |
| | | switch (codec.name.toUpperCase()) { |
| | | case 'RED': |
| | | case 'ULPFEC': |
| | | description.fecMechanisms.push(codec.name.toUpperCase()); |
| | | 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); |
| | | }); |
| | | var maxptime = 0; |
| | | caps.codecs.forEach(function(codec) { |
| | | if (codec.maxptime > maxptime) { |
| | | maxptime = codec.maxptime; |
| | | } |
| | | }); |
| | | if (maxptime > 0) { |
| | | sdp += 'a=maxptime:' + maxptime + '\r\n'; |
| | | } |
| | | sdp += 'a=rtcp-mux\r\n'; |
| | | |
| | | if (caps.headerExtensions) { |
| | | caps.headerExtensions.forEach(function(extension) { |
| | | sdp += SDPUtils.writeExtmap(extension); |
| | | }); |
| | | } |
| | | // FIXME: write fecMechanisms. |
| | | 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.substr(17).split(' '); |
| | | 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) |
| | | }; |
| | | if (primarySsrc && secondarySsrc) { |
| | | encParam.rtx = {ssrc: secondarySsrc}; |
| | | } |
| | | encodingParameters.push(encParam); |
| | | if (hasRed) { |
| | | encParam = JSON.parse(JSON.stringify(encParam)); |
| | | encParam.fec = { |
| | | ssrc: primarySsrc, |
| | | 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) { |
| | | // use formula from JSEP to convert b=AS to TIAS value. |
| | | bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 |
| | | - (50 * 40 * 8); |
| | | } else { |
| | | bandwidth = undefined; |
| | | } |
| | | encodingParameters.forEach(function(params) { |
| | | params.maxBitrate = bandwidth; |
| | | }); |
| | | } |
| | | return encodingParameters; |
| | | }; |
| | | |
| | | // parses http://draft.ortc.org/#rtcrtcpparameters* |
| | | SDPUtils.parseRtcpParameters = function(mediaSection) { |
| | | var rtcpParameters = {}; |
| | | |
| | | // Gets the first SSRC. Note tha 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) { |
| | | rtcpParameters.cname = remoteSsrc.value; |
| | | rtcpParameters.ssrc = remoteSsrc.ssrc; |
| | | } |
| | | |
| | | // Edge uses the compound attribute instead of reducedSize |
| | | // compound is !reducedSize |
| | | var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); |
| | | rtcpParameters.reducedSize = rsize.length > 0; |
| | | rtcpParameters.compound = rsize.length === 0; |
| | | |
| | | // parses the rtcp-mux attrіbute. |
| | | // Note that Edge does not support unmuxed RTCP. |
| | | var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); |
| | | rtcpParameters.mux = mux.length > 0; |
| | | |
| | | return rtcpParameters; |
| | | }; |
| | | |
| | | // parses either a=msid: or a=ssrc:... msid lines and returns |
| | | // the id of the MediaStream and MediaStreamTrack. |
| | | SDPUtils.parseMsid = function(mediaSection) { |
| | | var parts; |
| | | var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); |
| | | if (spec.length === 1) { |
| | | parts = spec[0].substr(7).split(' '); |
| | | return {stream: parts[0], track: parts[1]}; |
| | | } |
| | | var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:') |
| | | .map(function(line) { |
| | | return SDPUtils.parseSsrcMedia(line); |
| | | }) |
| | | .filter(function(msidParts) { |
| | | return msidParts.attribute === 'msid'; |
| | | }); |
| | | if (planB.length > 0) { |
| | | parts = planB[0].value.split(' '); |
| | | return {stream: parts[0], track: parts[1]}; |
| | | } |
| | | }; |
| | | |
| | | // SCTP |
| | | // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back |
| | | // to draft-ietf-mmusic-sctp-sdp-05 |
| | | SDPUtils.parseSctpDescription = function(mediaSection) { |
| | | var mline = SDPUtils.parseMLine(mediaSection); |
| | | var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); |
| | | var maxMessageSize; |
| | | if (maxSizeLine.length > 0) { |
| | | maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); |
| | | } |
| | | if (isNaN(maxMessageSize)) { |
| | | maxMessageSize = 65536; |
| | | } |
| | | var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); |
| | | if (sctpPort.length > 0) { |
| | | return { |
| | | port: parseInt(sctpPort[0].substr(12), 10), |
| | | protocol: mline.fmt, |
| | | maxMessageSize: maxMessageSize |
| | | }; |
| | | } |
| | | var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); |
| | | if (sctpMapLines.length > 0) { |
| | | var parts = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:')[0] |
| | | .substr(10) |
| | | .split(' '); |
| | | return { |
| | | port: parseInt(parts[0], 10), |
| | | protocol: parts[1], |
| | | maxMessageSize: maxMessageSize |
| | | }; |
| | | } |
| | | }; |
| | | |
| | | // SCTP |
| | | // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers |
| | | // support by now receiving in this format, unless we originally parsed |
| | | // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line |
| | | // protocol of DTLS/SCTP -- without UDP/ or TCP/) |
| | | SDPUtils.writeSctpDescription = function(media, sctp) { |
| | | var output = []; |
| | | if (media.protocol !== 'DTLS/SCTP') { |
| | | output = [ |
| | | 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', |
| | | 'c=IN IP4 0.0.0.0\r\n', |
| | | 'a=sctp-port:' + sctp.port + '\r\n' |
| | | ]; |
| | | } else { |
| | | output = [ |
| | | 'm=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', |
| | | 'c=IN IP4 0.0.0.0\r\n', |
| | | 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n' |
| | | ]; |
| | | } |
| | | if (sctp.maxMessageSize !== undefined) { |
| | | output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); |
| | | } |
| | | return output.join(''); |
| | | }; |
| | | |
| | | // Generate a session ID for SDP. |
| | | // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 |
| | | // recommends using a cryptographically random +ve 64-bit value |
| | | // but right now this should be acceptable and within the right range |
| | | SDPUtils.generateSessionId = function() { |
| | | return Math.random().toString().substr(2, 21); |
| | | }; |
| | | |
| | | // Write boilder plate for start of SDP |
| | | // sessId argument is optional - if not supplied it will |
| | | // be generated randomly |
| | | // sessVersion is optional and defaults to 2 |
| | | // sessUser is optional and defaults to 'thisisadapterortc' |
| | | SDPUtils.writeSessionBoilerplate = function(sessId, sessVer, sessUser) { |
| | | var sessionId; |
| | | var version = sessVer !== undefined ? sessVer : 2; |
| | | if (sessId) { |
| | | sessionId = sessId; |
| | | } else { |
| | | sessionId = SDPUtils.generateSessionId(); |
| | | } |
| | | var user = sessUser || 'thisisadapterortc'; |
| | | // FIXME: sess-id should be an NTP timestamp. |
| | | return 'v=0\r\n' + |
| | | 'o=' + user + ' ' + sessionId + ' ' + version + |
| | | ' 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.direction) { |
| | | sdp += 'a=' + transceiver.direction + '\r\n'; |
| | | } else 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'; |
| | | } |
| | | |
| | | if (transceiver.rtpSender) { |
| | | // spec. |
| | | var msid = 'msid:' + stream.id + ' ' + |
| | | transceiver.rtpSender.track.id + '\r\n'; |
| | | sdp += 'a=' + msid; |
| | | |
| | | // for Chrome. |
| | | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + |
| | | ' ' + msid; |
| | | if (transceiver.sendEncodingParameters[0].rtx) { |
| | | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + |
| | | ' ' + msid; |
| | | sdp += 'a=ssrc-group:FID ' + |
| | | transceiver.sendEncodingParameters[0].ssrc + ' ' + |
| | | transceiver.sendEncodingParameters[0].rtx.ssrc + |
| | | '\r\n'; |
| | | } |
| | | } |
| | | // FIXME: this should be written by writeRtpDescription. |
| | | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + |
| | | ' cname:' + SDPUtils.localCName + '\r\n'; |
| | | if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { |
| | | sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.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); |
| | | // FIXME: What should happen here? |
| | | } |
| | | } |
| | | if (sessionpart) { |
| | | return SDPUtils.getDirection(sessionpart); |
| | | } |
| | | return 'sendrecv'; |
| | | }; |
| | | |
| | | SDPUtils.getKind = function(mediaSection) { |
| | | var lines = SDPUtils.splitLines(mediaSection); |
| | | var mline = lines[0].split(' '); |
| | | return mline[0].substr(2); |
| | | }; |
| | | |
| | | SDPUtils.isRejected = function(mediaSection) { |
| | | return mediaSection.split(' ', 2)[1] === '0'; |
| | | }; |
| | | |
| | | SDPUtils.parseMLine = function(mediaSection) { |
| | | var lines = SDPUtils.splitLines(mediaSection); |
| | | var parts = lines[0].substr(2).split(' '); |
| | | return { |
| | | kind: parts[0], |
| | | port: parseInt(parts[1], 10), |
| | | protocol: parts[2], |
| | | fmt: parts.slice(3).join(' ') |
| | | }; |
| | | }; |
| | | |
| | | SDPUtils.parseOLine = function(mediaSection) { |
| | | var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; |
| | | var parts = line.substr(2).split(' '); |
| | | return { |
| | | username: parts[0], |
| | | sessionId: parts[1], |
| | | sessionVersion: parseInt(parts[2], 10), |
| | | netType: parts[3], |
| | | addressType: parts[4], |
| | | address: parts[5] |
| | | }; |
| | | }; |
| | | |
| | | // a very naive interpretation of a valid SDP. |
| | | SDPUtils.isValidSDP = function(blob) { |
| | | if (typeof blob !== 'string' || blob.length === 0) { |
| | | return false; |
| | | } |
| | | var lines = SDPUtils.splitLines(blob); |
| | | for (var i = 0; i < lines.length; i++) { |
| | | if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { |
| | | return false; |
| | | } |
| | | // TODO: check the modifier a bit more. |
| | | } |
| | | return true; |
| | | }; |
| | | |
| | | // Expose public methods. |
| | | { |
| | | module.exports = SDPUtils; |
| | | } |
| | | }); |
| | | |
| | | /* |
| | | * Copyright (c) 2017 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. |
| | | */ |
| | | |
| | | |
| | | |
| | | function fixStatsType(stat) { |
| | | return { |
| | | inboundrtp: 'inbound-rtp', |
| | | outboundrtp: 'outbound-rtp', |
| | | candidatepair: 'candidate-pair', |
| | | localcandidate: 'local-candidate', |
| | | remotecandidate: 'remote-candidate' |
| | | }[stat.type] || stat.type; |
| | | } |
| | | |
| | | function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { |
| | | var sdp$1 = sdp.writeRtpDescription(transceiver.kind, caps); |
| | | |
| | | // Map ICE parameters (ufrag, pwd) to SDP. |
| | | sdp$1 += sdp.writeIceParameters( |
| | | transceiver.iceGatherer.getLocalParameters()); |
| | | |
| | | // Map DTLS parameters to SDP. |
| | | sdp$1 += sdp.writeDtlsParameters( |
| | | transceiver.dtlsTransport.getLocalParameters(), |
| | | type === 'offer' ? 'actpass' : dtlsRole || 'active'); |
| | | |
| | | sdp$1 += 'a=mid:' + transceiver.mid + '\r\n'; |
| | | |
| | | if (transceiver.rtpSender && transceiver.rtpReceiver) { |
| | | sdp$1 += 'a=sendrecv\r\n'; |
| | | } else if (transceiver.rtpSender) { |
| | | sdp$1 += 'a=sendonly\r\n'; |
| | | } else if (transceiver.rtpReceiver) { |
| | | sdp$1 += 'a=recvonly\r\n'; |
| | | } else { |
| | | sdp$1 += 'a=inactive\r\n'; |
| | | } |
| | | |
| | | if (transceiver.rtpSender) { |
| | | var trackId = transceiver.rtpSender._initialTrackId || |
| | | transceiver.rtpSender.track.id; |
| | | transceiver.rtpSender._initialTrackId = trackId; |
| | | // spec. |
| | | var msid = 'msid:' + (stream ? stream.id : '-') + ' ' + |
| | | trackId + '\r\n'; |
| | | sdp$1 += 'a=' + msid; |
| | | // for Chrome. Legacy should no longer be required. |
| | | sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + |
| | | ' ' + msid; |
| | | |
| | | // RTX |
| | | if (transceiver.sendEncodingParameters[0].rtx) { |
| | | sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + |
| | | ' ' + msid; |
| | | sdp$1 += 'a=ssrc-group:FID ' + |
| | | transceiver.sendEncodingParameters[0].ssrc + ' ' + |
| | | transceiver.sendEncodingParameters[0].rtx.ssrc + |
| | | '\r\n'; |
| | | } |
| | | } |
| | | // FIXME: this should be written by writeRtpDescription. |
| | | sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + |
| | | ' cname:' + sdp.localCName + '\r\n'; |
| | | if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { |
| | | sdp$1 += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + |
| | | ' cname:' + sdp.localCName + '\r\n'; |
| | | } |
| | | return sdp$1; |
| | | } |
| | | |
| | | // Edge does not like |
| | | // 1) stun: filtered after 14393 unless ?transport=udp is present |
| | | // 2) turn: that does not have all of turn:host:port?transport=udp |
| | | // 3) turn: with ipv6 addresses |
| | | // 4) turn: occurring muliple times |
| | | function filterIceServers(iceServers, edgeVersion) { |
| | | var hasTurn = false; |
| | | iceServers = JSON.parse(JSON.stringify(iceServers)); |
| | | return iceServers.filter(function(server) { |
| | | if (server && (server.urls || server.url)) { |
| | | var urls = server.urls || server.url; |
| | | if (server.url && !server.urls) { |
| | | console.warn('RTCIceServer.url is deprecated! Use urls instead.'); |
| | | } |
| | | var isString = typeof urls === 'string'; |
| | | if (isString) { |
| | | urls = [urls]; |
| | | } |
| | | urls = urls.filter(function(url) { |
| | | var validTurn = url.indexOf('turn:') === 0 && |
| | | url.indexOf('transport=udp') !== -1 && |
| | | url.indexOf('turn:[') === -1 && |
| | | !hasTurn; |
| | | |
| | | if (validTurn) { |
| | | hasTurn = true; |
| | | return true; |
| | | } |
| | | return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && |
| | | url.indexOf('?transport=udp') === -1; |
| | | }); |
| | | |
| | | delete server.url; |
| | | server.urls = isString ? urls[0] : urls; |
| | | return !!urls.length; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // Determines the intersection of local and remote capabilities. |
| | | function getCommonCapabilities(localCapabilities, remoteCapabilities) { |
| | | var commonCapabilities = { |
| | | codecs: [], |
| | | headerExtensions: [], |
| | | fecMechanisms: [] |
| | | }; |
| | | |
| | | var findCodecByPayloadType = function(pt, codecs) { |
| | | pt = parseInt(pt, 10); |
| | | for (var i = 0; i < codecs.length; i++) { |
| | | if (codecs[i].payloadType === pt || |
| | | codecs[i].preferredPayloadType === pt) { |
| | | return codecs[i]; |
| | | } |
| | | } |
| | | }; |
| | | |
| | | var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { |
| | | var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); |
| | | var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); |
| | | return lCodec && rCodec && |
| | | lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); |
| | | }; |
| | | |
| | | 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) { |
| | | if (lCodec.name.toLowerCase() === 'rtx' && |
| | | lCodec.parameters && rCodec.parameters.apt) { |
| | | // for RTX we need to find the local rtx that has a apt |
| | | // which points to the same local codec as the remote one. |
| | | if (!rtxCapabilityMatches(lCodec, rCodec, |
| | | localCapabilities.codecs, remoteCapabilities.codecs)) { |
| | | continue; |
| | | } |
| | | } |
| | | rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy |
| | | // number of channels is the highest common number of channels |
| | | rCodec.numChannels = Math.min(lCodec.numChannels, |
| | | rCodec.numChannels); |
| | | // push rCodec so we reply with offerer payload type |
| | | commonCapabilities.codecs.push(rCodec); |
| | | |
| | | // determine common feedback mechanisms |
| | | rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { |
| | | for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { |
| | | if (lCodec.rtcpFeedback[j].type === fb.type && |
| | | lCodec.rtcpFeedback[j].parameter === fb.parameter) { |
| | | return true; |
| | | } |
| | | } |
| | | return false; |
| | | }); |
| | | // FIXME: also need to determine .parameters |
| | | // see https://github.com/openpeer/ortc/issues/569 |
| | | 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; |
| | | } |
| | | |
| | | // is action=setLocalDescription with type allowed in signalingState |
| | | function isActionAllowedInSignalingState(action, type, signalingState) { |
| | | return { |
| | | offer: { |
| | | setLocalDescription: ['stable', 'have-local-offer'], |
| | | setRemoteDescription: ['stable', 'have-remote-offer'] |
| | | }, |
| | | answer: { |
| | | setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], |
| | | setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] |
| | | } |
| | | }[type][action].indexOf(signalingState) !== -1; |
| | | } |
| | | |
| | | function maybeAddCandidate(iceTransport, candidate) { |
| | | // Edge's internal representation adds some fields therefore |
| | | // not all fieldѕ are taken into account. |
| | | var alreadyAdded = iceTransport.getRemoteCandidates() |
| | | .find(function(remoteCandidate) { |
| | | return candidate.foundation === remoteCandidate.foundation && |
| | | candidate.ip === remoteCandidate.ip && |
| | | candidate.port === remoteCandidate.port && |
| | | candidate.priority === remoteCandidate.priority && |
| | | candidate.protocol === remoteCandidate.protocol && |
| | | candidate.type === remoteCandidate.type; |
| | | }); |
| | | if (!alreadyAdded) { |
| | | iceTransport.addRemoteCandidate(candidate); |
| | | } |
| | | return !alreadyAdded; |
| | | } |
| | | |
| | | |
| | | function makeError(name, description) { |
| | | var e = new Error(description); |
| | | e.name = name; |
| | | // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names |
| | | e.code = { |
| | | NotSupportedError: 9, |
| | | InvalidStateError: 11, |
| | | InvalidAccessError: 15, |
| | | TypeError: undefined, |
| | | OperationError: undefined |
| | | }[name]; |
| | | return e; |
| | | } |
| | | |
| | | var rtcpeerconnection = function(window, edgeVersion) { |
| | | // https://w3c.github.io/mediacapture-main/#mediastream |
| | | // Helper function to add the track to the stream and |
| | | // dispatch the event ourselves. |
| | | function addTrackToStreamAndFireEvent(track, stream) { |
| | | stream.addTrack(track); |
| | | stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', |
| | | {track: track})); |
| | | } |
| | | |
| | | function removeTrackFromStreamAndFireEvent(track, stream) { |
| | | stream.removeTrack(track); |
| | | stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', |
| | | {track: track})); |
| | | } |
| | | |
| | | function fireAddTrack(pc, track, receiver, streams) { |
| | | var trackEvent = new Event('track'); |
| | | trackEvent.track = track; |
| | | trackEvent.receiver = receiver; |
| | | trackEvent.transceiver = {receiver: receiver}; |
| | | trackEvent.streams = streams; |
| | | window.setTimeout(function() { |
| | | pc._dispatchEvent('track', trackEvent); |
| | | }); |
| | | } |
| | | |
| | | var RTCPeerConnection = function(config) { |
| | | var pc = this; |
| | | |
| | | var _eventTarget = document.createDocumentFragment(); |
| | | ['addEventListener', 'removeEventListener', 'dispatchEvent'] |
| | | .forEach(function(method) { |
| | | pc[method] = _eventTarget[method].bind(_eventTarget); |
| | | }); |
| | | |
| | | this.canTrickleIceCandidates = null; |
| | | |
| | | this.needNegotiation = false; |
| | | |
| | | this.localStreams = []; |
| | | this.remoteStreams = []; |
| | | |
| | | this._localDescription = null; |
| | | this._remoteDescription = null; |
| | | |
| | | this.signalingState = 'stable'; |
| | | this.iceConnectionState = 'new'; |
| | | this.connectionState = 'new'; |
| | | this.iceGatheringState = 'new'; |
| | | |
| | | config = JSON.parse(JSON.stringify(config || {})); |
| | | |
| | | this.usingBundle = config.bundlePolicy === 'max-bundle'; |
| | | if (config.rtcpMuxPolicy === 'negotiate') { |
| | | throw(makeError('NotSupportedError', |
| | | 'rtcpMuxPolicy \'negotiate\' is not supported')); |
| | | } else if (!config.rtcpMuxPolicy) { |
| | | config.rtcpMuxPolicy = 'require'; |
| | | } |
| | | |
| | | switch (config.iceTransportPolicy) { |
| | | case 'all': |
| | | case 'relay': |
| | | break; |
| | | default: |
| | | config.iceTransportPolicy = 'all'; |
| | | break; |
| | | } |
| | | |
| | | switch (config.bundlePolicy) { |
| | | case 'balanced': |
| | | case 'max-compat': |
| | | case 'max-bundle': |
| | | break; |
| | | default: |
| | | config.bundlePolicy = 'balanced'; |
| | | break; |
| | | } |
| | | |
| | | config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); |
| | | |
| | | this._iceGatherers = []; |
| | | if (config.iceCandidatePoolSize) { |
| | | for (var i = config.iceCandidatePoolSize; i > 0; i--) { |
| | | this._iceGatherers.push(new window.RTCIceGatherer({ |
| | | iceServers: config.iceServers, |
| | | gatherPolicy: config.iceTransportPolicy |
| | | })); |
| | | } |
| | | } else { |
| | | config.iceCandidatePoolSize = 0; |
| | | } |
| | | |
| | | this._config = config; |
| | | |
| | | // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... |
| | | // everything that is needed to describe a SDP m-line. |
| | | this.transceivers = []; |
| | | |
| | | this._sdpSessionId = sdp.generateSessionId(); |
| | | this._sdpSessionVersion = 0; |
| | | |
| | | this._dtlsRole = undefined; // role for a=setup to use in answers. |
| | | |
| | | this._isClosed = false; |
| | | }; |
| | | |
| | | Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { |
| | | configurable: true, |
| | | get: function() { |
| | | return this._localDescription; |
| | | } |
| | | }); |
| | | Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { |
| | | configurable: true, |
| | | get: function() { |
| | | return this._remoteDescription; |
| | | } |
| | | }); |
| | | |
| | | // set up event handlers on prototype |
| | | RTCPeerConnection.prototype.onicecandidate = null; |
| | | RTCPeerConnection.prototype.onaddstream = null; |
| | | RTCPeerConnection.prototype.ontrack = null; |
| | | RTCPeerConnection.prototype.onremovestream = null; |
| | | RTCPeerConnection.prototype.onsignalingstatechange = null; |
| | | RTCPeerConnection.prototype.oniceconnectionstatechange = null; |
| | | RTCPeerConnection.prototype.onconnectionstatechange = null; |
| | | RTCPeerConnection.prototype.onicegatheringstatechange = null; |
| | | RTCPeerConnection.prototype.onnegotiationneeded = null; |
| | | RTCPeerConnection.prototype.ondatachannel = null; |
| | | |
| | | RTCPeerConnection.prototype._dispatchEvent = function(name, event) { |
| | | if (this._isClosed) { |
| | | return; |
| | | } |
| | | this.dispatchEvent(event); |
| | | if (typeof this['on' + name] === 'function') { |
| | | this['on' + name](event); |
| | | } |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype._emitGatheringStateChange = function() { |
| | | var event = new Event('icegatheringstatechange'); |
| | | this._dispatchEvent('icegatheringstatechange', event); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getConfiguration = function() { |
| | | return this._config; |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getLocalStreams = function() { |
| | | return this.localStreams; |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getRemoteStreams = function() { |
| | | return this.remoteStreams; |
| | | }; |
| | | |
| | | // internal helper to create a transceiver object. |
| | | // (which is not yet the same as the WebRTC 1.0 transceiver) |
| | | RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { |
| | | var hasBundleTransport = this.transceivers.length > 0; |
| | | var transceiver = { |
| | | track: null, |
| | | iceGatherer: null, |
| | | iceTransport: null, |
| | | dtlsTransport: null, |
| | | localCapabilities: null, |
| | | remoteCapabilities: null, |
| | | rtpSender: null, |
| | | rtpReceiver: null, |
| | | kind: kind, |
| | | mid: null, |
| | | sendEncodingParameters: null, |
| | | recvEncodingParameters: null, |
| | | stream: null, |
| | | associatedRemoteMediaStreams: [], |
| | | wantReceive: true |
| | | }; |
| | | if (this.usingBundle && hasBundleTransport) { |
| | | transceiver.iceTransport = this.transceivers[0].iceTransport; |
| | | transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; |
| | | } else { |
| | | var transports = this._createIceAndDtlsTransports(); |
| | | transceiver.iceTransport = transports.iceTransport; |
| | | transceiver.dtlsTransport = transports.dtlsTransport; |
| | | } |
| | | if (!doNotAdd) { |
| | | this.transceivers.push(transceiver); |
| | | } |
| | | return transceiver; |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.addTrack = function(track, stream) { |
| | | if (this._isClosed) { |
| | | throw makeError('InvalidStateError', |
| | | 'Attempted to call addTrack on a closed peerconnection.'); |
| | | } |
| | | |
| | | var alreadyExists = this.transceivers.find(function(s) { |
| | | return s.track === track; |
| | | }); |
| | | |
| | | if (alreadyExists) { |
| | | throw makeError('InvalidAccessError', 'Track already exists.'); |
| | | } |
| | | |
| | | var transceiver; |
| | | for (var i = 0; i < this.transceivers.length; i++) { |
| | | if (!this.transceivers[i].track && |
| | | this.transceivers[i].kind === track.kind) { |
| | | transceiver = this.transceivers[i]; |
| | | } |
| | | } |
| | | if (!transceiver) { |
| | | transceiver = this._createTransceiver(track.kind); |
| | | } |
| | | |
| | | this._maybeFireNegotiationNeeded(); |
| | | |
| | | if (this.localStreams.indexOf(stream) === -1) { |
| | | this.localStreams.push(stream); |
| | | } |
| | | |
| | | transceiver.track = track; |
| | | transceiver.stream = stream; |
| | | transceiver.rtpSender = new window.RTCRtpSender(track, |
| | | transceiver.dtlsTransport); |
| | | return transceiver.rtpSender; |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.addStream = function(stream) { |
| | | var pc = this; |
| | | if (edgeVersion >= 15025) { |
| | | stream.getTracks().forEach(function(track) { |
| | | pc.addTrack(track, stream); |
| | | }); |
| | | } else { |
| | | // Clone is necessary for local demos mostly, attaching directly |
| | | // to two different senders does not work (build 10547). |
| | | // Fixed in 15025 (or earlier) |
| | | var clonedStream = stream.clone(); |
| | | stream.getTracks().forEach(function(track, idx) { |
| | | var clonedTrack = clonedStream.getTracks()[idx]; |
| | | track.addEventListener('enabled', function(event) { |
| | | clonedTrack.enabled = event.enabled; |
| | | }); |
| | | }); |
| | | clonedStream.getTracks().forEach(function(track) { |
| | | pc.addTrack(track, clonedStream); |
| | | }); |
| | | } |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.removeTrack = function(sender) { |
| | | if (this._isClosed) { |
| | | throw makeError('InvalidStateError', |
| | | 'Attempted to call removeTrack on a closed peerconnection.'); |
| | | } |
| | | |
| | | if (!(sender instanceof window.RTCRtpSender)) { |
| | | throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + |
| | | 'does not implement interface RTCRtpSender.'); |
| | | } |
| | | |
| | | var transceiver = this.transceivers.find(function(t) { |
| | | return t.rtpSender === sender; |
| | | }); |
| | | |
| | | if (!transceiver) { |
| | | throw makeError('InvalidAccessError', |
| | | 'Sender was not created by this connection.'); |
| | | } |
| | | var stream = transceiver.stream; |
| | | |
| | | transceiver.rtpSender.stop(); |
| | | transceiver.rtpSender = null; |
| | | transceiver.track = null; |
| | | transceiver.stream = null; |
| | | |
| | | // remove the stream from the set of local streams |
| | | var localStreams = this.transceivers.map(function(t) { |
| | | return t.stream; |
| | | }); |
| | | if (localStreams.indexOf(stream) === -1 && |
| | | this.localStreams.indexOf(stream) > -1) { |
| | | this.localStreams.splice(this.localStreams.indexOf(stream), 1); |
| | | } |
| | | |
| | | this._maybeFireNegotiationNeeded(); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.removeStream = function(stream) { |
| | | var pc = this; |
| | | stream.getTracks().forEach(function(track) { |
| | | var sender = pc.getSenders().find(function(s) { |
| | | return s.track === track; |
| | | }); |
| | | if (sender) { |
| | | pc.removeTrack(sender); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getSenders = function() { |
| | | return this.transceivers.filter(function(transceiver) { |
| | | return !!transceiver.rtpSender; |
| | | }) |
| | | .map(function(transceiver) { |
| | | return transceiver.rtpSender; |
| | | }); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getReceivers = function() { |
| | | return this.transceivers.filter(function(transceiver) { |
| | | return !!transceiver.rtpReceiver; |
| | | }) |
| | | .map(function(transceiver) { |
| | | return transceiver.rtpReceiver; |
| | | }); |
| | | }; |
| | | |
| | | |
| | | RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, |
| | | usingBundle) { |
| | | var pc = this; |
| | | if (usingBundle && sdpMLineIndex > 0) { |
| | | return this.transceivers[0].iceGatherer; |
| | | } else if (this._iceGatherers.length) { |
| | | return this._iceGatherers.shift(); |
| | | } |
| | | var iceGatherer = new window.RTCIceGatherer({ |
| | | iceServers: this._config.iceServers, |
| | | gatherPolicy: this._config.iceTransportPolicy |
| | | }); |
| | | Object.defineProperty(iceGatherer, 'state', |
| | | {value: 'new', writable: true} |
| | | ); |
| | | |
| | | this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; |
| | | this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { |
| | | var end = !event.candidate || Object.keys(event.candidate).length === 0; |
| | | // polyfill since RTCIceGatherer.state is not implemented in |
| | | // Edge 10547 yet. |
| | | iceGatherer.state = end ? 'completed' : 'gathering'; |
| | | if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { |
| | | pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); |
| | | } |
| | | }; |
| | | iceGatherer.addEventListener('localcandidate', |
| | | this.transceivers[sdpMLineIndex].bufferCandidates); |
| | | return iceGatherer; |
| | | }; |
| | | |
| | | // start gathering from an RTCIceGatherer. |
| | | RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { |
| | | var pc = this; |
| | | var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; |
| | | if (iceGatherer.onlocalcandidate) { |
| | | return; |
| | | } |
| | | var bufferedCandidateEvents = |
| | | this.transceivers[sdpMLineIndex].bufferedCandidateEvents; |
| | | this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; |
| | | iceGatherer.removeEventListener('localcandidate', |
| | | this.transceivers[sdpMLineIndex].bufferCandidates); |
| | | iceGatherer.onlocalcandidate = function(evt) { |
| | | if (pc.usingBundle && sdpMLineIndex > 0) { |
| | | // if we know that we use bundle we can drop candidates with |
| | | // ѕdpMLineIndex > 0. If we don't do this then our state gets |
| | | // confused since we dispose the extra ice gatherer. |
| | | return; |
| | | } |
| | | var event = new Event('icecandidate'); |
| | | event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; |
| | | |
| | | var cand = evt.candidate; |
| | | // Edge emits an empty object for RTCIceCandidateComplete‥ |
| | | var end = !cand || Object.keys(cand).length === 0; |
| | | if (end) { |
| | | // polyfill since RTCIceGatherer.state is not implemented in |
| | | // Edge 10547 yet. |
| | | if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { |
| | | iceGatherer.state = 'completed'; |
| | | } |
| | | } else { |
| | | if (iceGatherer.state === 'new') { |
| | | iceGatherer.state = 'gathering'; |
| | | } |
| | | // RTCIceCandidate doesn't have a component, needs to be added |
| | | cand.component = 1; |
| | | // also the usernameFragment. TODO: update SDP to take both variants. |
| | | cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; |
| | | |
| | | var serializedCandidate = sdp.writeCandidate(cand); |
| | | event.candidate = Object.assign(event.candidate, |
| | | sdp.parseCandidate(serializedCandidate)); |
| | | |
| | | event.candidate.candidate = serializedCandidate; |
| | | event.candidate.toJSON = function() { |
| | | return { |
| | | candidate: event.candidate.candidate, |
| | | sdpMid: event.candidate.sdpMid, |
| | | sdpMLineIndex: event.candidate.sdpMLineIndex, |
| | | usernameFragment: event.candidate.usernameFragment |
| | | }; |
| | | }; |
| | | } |
| | | |
| | | // update local description. |
| | | var sections = sdp.getMediaSections(pc._localDescription.sdp); |
| | | if (!end) { |
| | | sections[event.candidate.sdpMLineIndex] += |
| | | 'a=' + event.candidate.candidate + '\r\n'; |
| | | } else { |
| | | sections[event.candidate.sdpMLineIndex] += |
| | | 'a=end-of-candidates\r\n'; |
| | | } |
| | | pc._localDescription.sdp = |
| | | sdp.getDescription(pc._localDescription.sdp) + |
| | | sections.join(''); |
| | | var complete = pc.transceivers.every(function(transceiver) { |
| | | return transceiver.iceGatherer && |
| | | transceiver.iceGatherer.state === 'completed'; |
| | | }); |
| | | |
| | | if (pc.iceGatheringState !== 'gathering') { |
| | | pc.iceGatheringState = 'gathering'; |
| | | pc._emitGatheringStateChange(); |
| | | } |
| | | |
| | | // Emit candidate. Also emit null candidate when all gatherers are |
| | | // complete. |
| | | if (!end) { |
| | | pc._dispatchEvent('icecandidate', event); |
| | | } |
| | | if (complete) { |
| | | pc._dispatchEvent('icecandidate', new Event('icecandidate')); |
| | | pc.iceGatheringState = 'complete'; |
| | | pc._emitGatheringStateChange(); |
| | | } |
| | | }; |
| | | |
| | | // emit already gathered candidates. |
| | | window.setTimeout(function() { |
| | | bufferedCandidateEvents.forEach(function(e) { |
| | | iceGatherer.onlocalcandidate(e); |
| | | }); |
| | | }, 0); |
| | | }; |
| | | |
| | | // Create ICE transport and DTLS transport. |
| | | RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { |
| | | var pc = this; |
| | | var iceTransport = new window.RTCIceTransport(null); |
| | | iceTransport.onicestatechange = function() { |
| | | pc._updateIceConnectionState(); |
| | | pc._updateConnectionState(); |
| | | }; |
| | | |
| | | var dtlsTransport = new window.RTCDtlsTransport(iceTransport); |
| | | dtlsTransport.ondtlsstatechange = function() { |
| | | pc._updateConnectionState(); |
| | | }; |
| | | dtlsTransport.onerror = function() { |
| | | // onerror does not set state to failed by itself. |
| | | Object.defineProperty(dtlsTransport, 'state', |
| | | {value: 'failed', writable: true}); |
| | | pc._updateConnectionState(); |
| | | }; |
| | | |
| | | return { |
| | | iceTransport: iceTransport, |
| | | dtlsTransport: dtlsTransport |
| | | }; |
| | | }; |
| | | |
| | | // Destroy ICE gatherer, ICE transport and DTLS transport. |
| | | // Without triggering the callbacks. |
| | | RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( |
| | | sdpMLineIndex) { |
| | | var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; |
| | | if (iceGatherer) { |
| | | delete iceGatherer.onlocalcandidate; |
| | | delete this.transceivers[sdpMLineIndex].iceGatherer; |
| | | } |
| | | var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; |
| | | if (iceTransport) { |
| | | delete iceTransport.onicestatechange; |
| | | delete this.transceivers[sdpMLineIndex].iceTransport; |
| | | } |
| | | var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; |
| | | if (dtlsTransport) { |
| | | delete dtlsTransport.ondtlsstatechange; |
| | | delete dtlsTransport.onerror; |
| | | delete this.transceivers[sdpMLineIndex].dtlsTransport; |
| | | } |
| | | }; |
| | | |
| | | // Start the RTP Sender and Receiver for a transceiver. |
| | | RTCPeerConnection.prototype._transceive = function(transceiver, |
| | | send, recv) { |
| | | var params = getCommonCapabilities(transceiver.localCapabilities, |
| | | transceiver.remoteCapabilities); |
| | | if (send && transceiver.rtpSender) { |
| | | params.encodings = transceiver.sendEncodingParameters; |
| | | params.rtcp = { |
| | | cname: sdp.localCName, |
| | | compound: transceiver.rtcpParameters.compound |
| | | }; |
| | | if (transceiver.recvEncodingParameters.length) { |
| | | params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; |
| | | } |
| | | transceiver.rtpSender.send(params); |
| | | } |
| | | if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { |
| | | // remove RTX field in Edge 14942 |
| | | if (transceiver.kind === 'video' |
| | | && transceiver.recvEncodingParameters |
| | | && edgeVersion < 15019) { |
| | | transceiver.recvEncodingParameters.forEach(function(p) { |
| | | delete p.rtx; |
| | | }); |
| | | } |
| | | if (transceiver.recvEncodingParameters.length) { |
| | | params.encodings = transceiver.recvEncodingParameters; |
| | | } else { |
| | | params.encodings = [{}]; |
| | | } |
| | | params.rtcp = { |
| | | compound: transceiver.rtcpParameters.compound |
| | | }; |
| | | if (transceiver.rtcpParameters.cname) { |
| | | params.rtcp.cname = transceiver.rtcpParameters.cname; |
| | | } |
| | | if (transceiver.sendEncodingParameters.length) { |
| | | params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; |
| | | } |
| | | transceiver.rtpReceiver.receive(params); |
| | | } |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.setLocalDescription = function(description) { |
| | | var pc = this; |
| | | |
| | | // Note: pranswer is not supported. |
| | | if (['offer', 'answer'].indexOf(description.type) === -1) { |
| | | return Promise.reject(makeError('TypeError', |
| | | 'Unsupported type "' + description.type + '"')); |
| | | } |
| | | |
| | | if (!isActionAllowedInSignalingState('setLocalDescription', |
| | | description.type, pc.signalingState) || pc._isClosed) { |
| | | return Promise.reject(makeError('InvalidStateError', |
| | | 'Can not set local ' + description.type + |
| | | ' in state ' + pc.signalingState)); |
| | | } |
| | | |
| | | var sections; |
| | | var sessionpart; |
| | | if (description.type === 'offer') { |
| | | // VERY limited support for SDP munging. Limited to: |
| | | // * changing the order of codecs |
| | | sections = sdp.splitSections(description.sdp); |
| | | sessionpart = sections.shift(); |
| | | sections.forEach(function(mediaSection, sdpMLineIndex) { |
| | | var caps = sdp.parseRtpParameters(mediaSection); |
| | | pc.transceivers[sdpMLineIndex].localCapabilities = caps; |
| | | }); |
| | | |
| | | pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { |
| | | pc._gather(transceiver.mid, sdpMLineIndex); |
| | | }); |
| | | } else if (description.type === 'answer') { |
| | | sections = sdp.splitSections(pc._remoteDescription.sdp); |
| | | sessionpart = sections.shift(); |
| | | var isIceLite = sdp.matchPrefix(sessionpart, |
| | | 'a=ice-lite').length > 0; |
| | | sections.forEach(function(mediaSection, sdpMLineIndex) { |
| | | var transceiver = pc.transceivers[sdpMLineIndex]; |
| | | var iceGatherer = transceiver.iceGatherer; |
| | | var iceTransport = transceiver.iceTransport; |
| | | var dtlsTransport = transceiver.dtlsTransport; |
| | | var localCapabilities = transceiver.localCapabilities; |
| | | var remoteCapabilities = transceiver.remoteCapabilities; |
| | | |
| | | // treat bundle-only as not-rejected. |
| | | var rejected = sdp.isRejected(mediaSection) && |
| | | sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0; |
| | | |
| | | if (!rejected && !transceiver.rejected) { |
| | | var remoteIceParameters = sdp.getIceParameters( |
| | | mediaSection, sessionpart); |
| | | var remoteDtlsParameters = sdp.getDtlsParameters( |
| | | mediaSection, sessionpart); |
| | | if (isIceLite) { |
| | | remoteDtlsParameters.role = 'server'; |
| | | } |
| | | |
| | | if (!pc.usingBundle || sdpMLineIndex === 0) { |
| | | pc._gather(transceiver.mid, sdpMLineIndex); |
| | | if (iceTransport.state === 'new') { |
| | | iceTransport.start(iceGatherer, remoteIceParameters, |
| | | isIceLite ? 'controlling' : 'controlled'); |
| | | } |
| | | if (dtlsTransport.state === 'new') { |
| | | dtlsTransport.start(remoteDtlsParameters); |
| | | } |
| | | } |
| | | |
| | | // Calculate intersection of capabilities. |
| | | var params = getCommonCapabilities(localCapabilities, |
| | | remoteCapabilities); |
| | | |
| | | // Start the RTCRtpSender. The RTCRtpReceiver for this |
| | | // transceiver has already been started in setRemoteDescription. |
| | | pc._transceive(transceiver, |
| | | params.codecs.length > 0, |
| | | false); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | pc._localDescription = { |
| | | type: description.type, |
| | | sdp: description.sdp |
| | | }; |
| | | if (description.type === 'offer') { |
| | | pc._updateSignalingState('have-local-offer'); |
| | | } else { |
| | | pc._updateSignalingState('stable'); |
| | | } |
| | | |
| | | return Promise.resolve(); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.setRemoteDescription = function(description) { |
| | | var pc = this; |
| | | |
| | | // Note: pranswer is not supported. |
| | | if (['offer', 'answer'].indexOf(description.type) === -1) { |
| | | return Promise.reject(makeError('TypeError', |
| | | 'Unsupported type "' + description.type + '"')); |
| | | } |
| | | |
| | | if (!isActionAllowedInSignalingState('setRemoteDescription', |
| | | description.type, pc.signalingState) || pc._isClosed) { |
| | | return Promise.reject(makeError('InvalidStateError', |
| | | 'Can not set remote ' + description.type + |
| | | ' in state ' + pc.signalingState)); |
| | | } |
| | | |
| | | var streams = {}; |
| | | pc.remoteStreams.forEach(function(stream) { |
| | | streams[stream.id] = stream; |
| | | }); |
| | | var receiverList = []; |
| | | var sections = sdp.splitSections(description.sdp); |
| | | var sessionpart = sections.shift(); |
| | | var isIceLite = sdp.matchPrefix(sessionpart, |
| | | 'a=ice-lite').length > 0; |
| | | var usingBundle = sdp.matchPrefix(sessionpart, |
| | | 'a=group:BUNDLE ').length > 0; |
| | | pc.usingBundle = usingBundle; |
| | | var iceOptions = sdp.matchPrefix(sessionpart, |
| | | 'a=ice-options:')[0]; |
| | | if (iceOptions) { |
| | | pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') |
| | | .indexOf('trickle') >= 0; |
| | | } else { |
| | | pc.canTrickleIceCandidates = false; |
| | | } |
| | | |
| | | sections.forEach(function(mediaSection, sdpMLineIndex) { |
| | | var lines = sdp.splitLines(mediaSection); |
| | | var kind = sdp.getKind(mediaSection); |
| | | // treat bundle-only as not-rejected. |
| | | var rejected = sdp.isRejected(mediaSection) && |
| | | sdp.matchPrefix(mediaSection, 'a=bundle-only').length === 0; |
| | | var protocol = lines[0].substr(2).split(' ')[2]; |
| | | |
| | | var direction = sdp.getDirection(mediaSection, sessionpart); |
| | | var remoteMsid = sdp.parseMsid(mediaSection); |
| | | |
| | | var mid = sdp.getMid(mediaSection) || sdp.generateIdentifier(); |
| | | |
| | | // Reject datachannels which are not implemented yet. |
| | | if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || |
| | | protocol === 'UDP/DTLS/SCTP'))) { |
| | | // TODO: this is dangerous in the case where a non-rejected m-line |
| | | // becomes rejected. |
| | | pc.transceivers[sdpMLineIndex] = { |
| | | mid: mid, |
| | | kind: kind, |
| | | protocol: protocol, |
| | | rejected: true |
| | | }; |
| | | return; |
| | | } |
| | | |
| | | if (!rejected && pc.transceivers[sdpMLineIndex] && |
| | | pc.transceivers[sdpMLineIndex].rejected) { |
| | | // recycle a rejected transceiver. |
| | | pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); |
| | | } |
| | | |
| | | var transceiver; |
| | | var iceGatherer; |
| | | var iceTransport; |
| | | var dtlsTransport; |
| | | var rtpReceiver; |
| | | var sendEncodingParameters; |
| | | var recvEncodingParameters; |
| | | var localCapabilities; |
| | | |
| | | var track; |
| | | // FIXME: ensure the mediaSection has rtcp-mux set. |
| | | var remoteCapabilities = sdp.parseRtpParameters(mediaSection); |
| | | var remoteIceParameters; |
| | | var remoteDtlsParameters; |
| | | if (!rejected) { |
| | | remoteIceParameters = sdp.getIceParameters(mediaSection, |
| | | sessionpart); |
| | | remoteDtlsParameters = sdp.getDtlsParameters(mediaSection, |
| | | sessionpart); |
| | | remoteDtlsParameters.role = 'client'; |
| | | } |
| | | recvEncodingParameters = |
| | | sdp.parseRtpEncodingParameters(mediaSection); |
| | | |
| | | var rtcpParameters = sdp.parseRtcpParameters(mediaSection); |
| | | |
| | | var isComplete = sdp.matchPrefix(mediaSection, |
| | | 'a=end-of-candidates', sessionpart).length > 0; |
| | | var cands = sdp.matchPrefix(mediaSection, 'a=candidate:') |
| | | .map(function(cand) { |
| | | return sdp.parseCandidate(cand); |
| | | }) |
| | | .filter(function(cand) { |
| | | return cand.component === 1; |
| | | }); |
| | | |
| | | // Check if we can use BUNDLE and dispose transports. |
| | | if ((description.type === 'offer' || description.type === 'answer') && |
| | | !rejected && usingBundle && sdpMLineIndex > 0 && |
| | | pc.transceivers[sdpMLineIndex]) { |
| | | pc._disposeIceAndDtlsTransports(sdpMLineIndex); |
| | | pc.transceivers[sdpMLineIndex].iceGatherer = |
| | | pc.transceivers[0].iceGatherer; |
| | | pc.transceivers[sdpMLineIndex].iceTransport = |
| | | pc.transceivers[0].iceTransport; |
| | | pc.transceivers[sdpMLineIndex].dtlsTransport = |
| | | pc.transceivers[0].dtlsTransport; |
| | | if (pc.transceivers[sdpMLineIndex].rtpSender) { |
| | | pc.transceivers[sdpMLineIndex].rtpSender.setTransport( |
| | | pc.transceivers[0].dtlsTransport); |
| | | } |
| | | if (pc.transceivers[sdpMLineIndex].rtpReceiver) { |
| | | pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( |
| | | pc.transceivers[0].dtlsTransport); |
| | | } |
| | | } |
| | | if (description.type === 'offer' && !rejected) { |
| | | transceiver = pc.transceivers[sdpMLineIndex] || |
| | | pc._createTransceiver(kind); |
| | | transceiver.mid = mid; |
| | | |
| | | if (!transceiver.iceGatherer) { |
| | | transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, |
| | | usingBundle); |
| | | } |
| | | |
| | | if (cands.length && transceiver.iceTransport.state === 'new') { |
| | | if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { |
| | | transceiver.iceTransport.setRemoteCandidates(cands); |
| | | } else { |
| | | cands.forEach(function(candidate) { |
| | | maybeAddCandidate(transceiver.iceTransport, candidate); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); |
| | | |
| | | // filter RTX until additional stuff needed for RTX is implemented |
| | | // in adapter.js |
| | | if (edgeVersion < 15019) { |
| | | localCapabilities.codecs = localCapabilities.codecs.filter( |
| | | function(codec) { |
| | | return codec.name !== 'rtx'; |
| | | }); |
| | | } |
| | | |
| | | sendEncodingParameters = transceiver.sendEncodingParameters || [{ |
| | | ssrc: (2 * sdpMLineIndex + 2) * 1001 |
| | | }]; |
| | | |
| | | // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams |
| | | var isNewTrack = false; |
| | | if (direction === 'sendrecv' || direction === 'sendonly') { |
| | | isNewTrack = !transceiver.rtpReceiver; |
| | | rtpReceiver = transceiver.rtpReceiver || |
| | | new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); |
| | | |
| | | if (isNewTrack) { |
| | | var stream; |
| | | track = rtpReceiver.track; |
| | | // FIXME: does not work with Plan B. |
| | | if (remoteMsid && remoteMsid.stream === '-') ; else if (remoteMsid) { |
| | | if (!streams[remoteMsid.stream]) { |
| | | streams[remoteMsid.stream] = new window.MediaStream(); |
| | | Object.defineProperty(streams[remoteMsid.stream], 'id', { |
| | | get: function() { |
| | | return remoteMsid.stream; |
| | | } |
| | | }); |
| | | } |
| | | Object.defineProperty(track, 'id', { |
| | | get: function() { |
| | | return remoteMsid.track; |
| | | } |
| | | }); |
| | | stream = streams[remoteMsid.stream]; |
| | | } else { |
| | | if (!streams.default) { |
| | | streams.default = new window.MediaStream(); |
| | | } |
| | | stream = streams.default; |
| | | } |
| | | if (stream) { |
| | | addTrackToStreamAndFireEvent(track, stream); |
| | | transceiver.associatedRemoteMediaStreams.push(stream); |
| | | } |
| | | receiverList.push([track, rtpReceiver, stream]); |
| | | } |
| | | } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { |
| | | transceiver.associatedRemoteMediaStreams.forEach(function(s) { |
| | | var nativeTrack = s.getTracks().find(function(t) { |
| | | return t.id === transceiver.rtpReceiver.track.id; |
| | | }); |
| | | if (nativeTrack) { |
| | | removeTrackFromStreamAndFireEvent(nativeTrack, s); |
| | | } |
| | | }); |
| | | transceiver.associatedRemoteMediaStreams = []; |
| | | } |
| | | |
| | | transceiver.localCapabilities = localCapabilities; |
| | | transceiver.remoteCapabilities = remoteCapabilities; |
| | | transceiver.rtpReceiver = rtpReceiver; |
| | | transceiver.rtcpParameters = rtcpParameters; |
| | | transceiver.sendEncodingParameters = sendEncodingParameters; |
| | | transceiver.recvEncodingParameters = recvEncodingParameters; |
| | | |
| | | // Start the RTCRtpReceiver now. The RTPSender is started in |
| | | // setLocalDescription. |
| | | pc._transceive(pc.transceivers[sdpMLineIndex], |
| | | false, |
| | | isNewTrack); |
| | | } else if (description.type === 'answer' && !rejected) { |
| | | transceiver = pc.transceivers[sdpMLineIndex]; |
| | | iceGatherer = transceiver.iceGatherer; |
| | | iceTransport = transceiver.iceTransport; |
| | | dtlsTransport = transceiver.dtlsTransport; |
| | | rtpReceiver = transceiver.rtpReceiver; |
| | | sendEncodingParameters = transceiver.sendEncodingParameters; |
| | | localCapabilities = transceiver.localCapabilities; |
| | | |
| | | pc.transceivers[sdpMLineIndex].recvEncodingParameters = |
| | | recvEncodingParameters; |
| | | pc.transceivers[sdpMLineIndex].remoteCapabilities = |
| | | remoteCapabilities; |
| | | pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; |
| | | |
| | | if (cands.length && iceTransport.state === 'new') { |
| | | if ((isIceLite || isComplete) && |
| | | (!usingBundle || sdpMLineIndex === 0)) { |
| | | iceTransport.setRemoteCandidates(cands); |
| | | } else { |
| | | cands.forEach(function(candidate) { |
| | | maybeAddCandidate(transceiver.iceTransport, candidate); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | if (!usingBundle || sdpMLineIndex === 0) { |
| | | if (iceTransport.state === 'new') { |
| | | iceTransport.start(iceGatherer, remoteIceParameters, |
| | | 'controlling'); |
| | | } |
| | | if (dtlsTransport.state === 'new') { |
| | | dtlsTransport.start(remoteDtlsParameters); |
| | | } |
| | | } |
| | | |
| | | // If the offer contained RTX but the answer did not, |
| | | // remove RTX from sendEncodingParameters. |
| | | var commonCapabilities = getCommonCapabilities( |
| | | transceiver.localCapabilities, |
| | | transceiver.remoteCapabilities); |
| | | |
| | | var hasRtx = commonCapabilities.codecs.filter(function(c) { |
| | | return c.name.toLowerCase() === 'rtx'; |
| | | }).length; |
| | | if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { |
| | | delete transceiver.sendEncodingParameters[0].rtx; |
| | | } |
| | | |
| | | pc._transceive(transceiver, |
| | | direction === 'sendrecv' || direction === 'recvonly', |
| | | direction === 'sendrecv' || direction === 'sendonly'); |
| | | |
| | | // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams |
| | | if (rtpReceiver && |
| | | (direction === 'sendrecv' || direction === 'sendonly')) { |
| | | track = rtpReceiver.track; |
| | | if (remoteMsid) { |
| | | if (!streams[remoteMsid.stream]) { |
| | | streams[remoteMsid.stream] = new window.MediaStream(); |
| | | } |
| | | addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); |
| | | receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); |
| | | } else { |
| | | if (!streams.default) { |
| | | streams.default = new window.MediaStream(); |
| | | } |
| | | addTrackToStreamAndFireEvent(track, streams.default); |
| | | receiverList.push([track, rtpReceiver, streams.default]); |
| | | } |
| | | } else { |
| | | // FIXME: actually the receiver should be created later. |
| | | delete transceiver.rtpReceiver; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | if (pc._dtlsRole === undefined) { |
| | | pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; |
| | | } |
| | | |
| | | pc._remoteDescription = { |
| | | type: description.type, |
| | | sdp: description.sdp |
| | | }; |
| | | if (description.type === 'offer') { |
| | | pc._updateSignalingState('have-remote-offer'); |
| | | } else { |
| | | pc._updateSignalingState('stable'); |
| | | } |
| | | Object.keys(streams).forEach(function(sid) { |
| | | var stream = streams[sid]; |
| | | if (stream.getTracks().length) { |
| | | if (pc.remoteStreams.indexOf(stream) === -1) { |
| | | pc.remoteStreams.push(stream); |
| | | var event = new Event('addstream'); |
| | | event.stream = stream; |
| | | window.setTimeout(function() { |
| | | pc._dispatchEvent('addstream', event); |
| | | }); |
| | | } |
| | | |
| | | receiverList.forEach(function(item) { |
| | | var track = item[0]; |
| | | var receiver = item[1]; |
| | | if (stream.id !== item[2].id) { |
| | | return; |
| | | } |
| | | fireAddTrack(pc, track, receiver, [stream]); |
| | | }); |
| | | } |
| | | }); |
| | | receiverList.forEach(function(item) { |
| | | if (item[2]) { |
| | | return; |
| | | } |
| | | fireAddTrack(pc, item[0], item[1], []); |
| | | }); |
| | | |
| | | // check whether addIceCandidate({}) was called within four seconds after |
| | | // setRemoteDescription. |
| | | window.setTimeout(function() { |
| | | if (!(pc && pc.transceivers)) { |
| | | return; |
| | | } |
| | | pc.transceivers.forEach(function(transceiver) { |
| | | if (transceiver.iceTransport && |
| | | transceiver.iceTransport.state === 'new' && |
| | | transceiver.iceTransport.getRemoteCandidates().length > 0) { |
| | | console.warn('Timeout for addRemoteCandidate. Consider sending ' + |
| | | 'an end-of-candidates notification'); |
| | | transceiver.iceTransport.addRemoteCandidate({}); |
| | | } |
| | | }); |
| | | }, 4000); |
| | | |
| | | return Promise.resolve(); |
| | | }; |
| | | |
| | | 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._isClosed = true; |
| | | this._updateSignalingState('closed'); |
| | | }; |
| | | |
| | | // Update the signaling state. |
| | | RTCPeerConnection.prototype._updateSignalingState = function(newState) { |
| | | this.signalingState = newState; |
| | | var event = new Event('signalingstatechange'); |
| | | this._dispatchEvent('signalingstatechange', event); |
| | | }; |
| | | |
| | | // Determine whether to fire the negotiationneeded event. |
| | | RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { |
| | | var pc = this; |
| | | if (this.signalingState !== 'stable' || this.needNegotiation === true) { |
| | | return; |
| | | } |
| | | this.needNegotiation = true; |
| | | window.setTimeout(function() { |
| | | if (pc.needNegotiation) { |
| | | pc.needNegotiation = false; |
| | | var event = new Event('negotiationneeded'); |
| | | pc._dispatchEvent('negotiationneeded', event); |
| | | } |
| | | }, 0); |
| | | }; |
| | | |
| | | // Update the ice connection state. |
| | | RTCPeerConnection.prototype._updateIceConnectionState = function() { |
| | | var newState; |
| | | var states = { |
| | | 'new': 0, |
| | | closed: 0, |
| | | checking: 0, |
| | | connected: 0, |
| | | completed: 0, |
| | | disconnected: 0, |
| | | failed: 0 |
| | | }; |
| | | this.transceivers.forEach(function(transceiver) { |
| | | if (transceiver.iceTransport && !transceiver.rejected) { |
| | | states[transceiver.iceTransport.state]++; |
| | | } |
| | | }); |
| | | |
| | | newState = 'new'; |
| | | if (states.failed > 0) { |
| | | newState = 'failed'; |
| | | } else if (states.checking > 0) { |
| | | newState = 'checking'; |
| | | } else if (states.disconnected > 0) { |
| | | newState = 'disconnected'; |
| | | } else if (states.new > 0) { |
| | | newState = 'new'; |
| | | } else if (states.connected > 0) { |
| | | newState = 'connected'; |
| | | } else if (states.completed > 0) { |
| | | newState = 'completed'; |
| | | } |
| | | |
| | | if (newState !== this.iceConnectionState) { |
| | | this.iceConnectionState = newState; |
| | | var event = new Event('iceconnectionstatechange'); |
| | | this._dispatchEvent('iceconnectionstatechange', event); |
| | | } |
| | | }; |
| | | |
| | | // Update the connection state. |
| | | RTCPeerConnection.prototype._updateConnectionState = function() { |
| | | var newState; |
| | | var states = { |
| | | 'new': 0, |
| | | closed: 0, |
| | | connecting: 0, |
| | | connected: 0, |
| | | completed: 0, |
| | | disconnected: 0, |
| | | failed: 0 |
| | | }; |
| | | this.transceivers.forEach(function(transceiver) { |
| | | if (transceiver.iceTransport && transceiver.dtlsTransport && |
| | | !transceiver.rejected) { |
| | | 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) { |
| | | newState = 'connecting'; |
| | | } else if (states.disconnected > 0) { |
| | | newState = 'disconnected'; |
| | | } else if (states.new > 0) { |
| | | newState = 'new'; |
| | | } else if (states.connected > 0) { |
| | | newState = 'connected'; |
| | | } |
| | | |
| | | if (newState !== this.connectionState) { |
| | | this.connectionState = newState; |
| | | var event = new Event('connectionstatechange'); |
| | | this._dispatchEvent('connectionstatechange', event); |
| | | } |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.createOffer = function() { |
| | | var pc = this; |
| | | |
| | | if (pc._isClosed) { |
| | | return Promise.reject(makeError('InvalidStateError', |
| | | 'Can not call createOffer after close')); |
| | | } |
| | | |
| | | var numAudioTracks = pc.transceivers.filter(function(t) { |
| | | return t.kind === 'audio'; |
| | | }).length; |
| | | var numVideoTracks = pc.transceivers.filter(function(t) { |
| | | return t.kind === 'video'; |
| | | }).length; |
| | | |
| | | // Determine number of audio and video tracks we need to send/recv. |
| | | var offerOptions = arguments[0]; |
| | | if (offerOptions) { |
| | | // Reject Chrome legacy constraints. |
| | | if (offerOptions.mandatory || offerOptions.optional) { |
| | | throw new TypeError( |
| | | 'Legacy mandatory/optional constraints not supported.'); |
| | | } |
| | | if (offerOptions.offerToReceiveAudio !== undefined) { |
| | | if (offerOptions.offerToReceiveAudio === true) { |
| | | numAudioTracks = 1; |
| | | } else if (offerOptions.offerToReceiveAudio === false) { |
| | | numAudioTracks = 0; |
| | | } else { |
| | | numAudioTracks = offerOptions.offerToReceiveAudio; |
| | | } |
| | | } |
| | | if (offerOptions.offerToReceiveVideo !== undefined) { |
| | | if (offerOptions.offerToReceiveVideo === true) { |
| | | numVideoTracks = 1; |
| | | } else if (offerOptions.offerToReceiveVideo === false) { |
| | | numVideoTracks = 0; |
| | | } else { |
| | | numVideoTracks = offerOptions.offerToReceiveVideo; |
| | | } |
| | | } |
| | | } |
| | | |
| | | pc.transceivers.forEach(function(transceiver) { |
| | | if (transceiver.kind === 'audio') { |
| | | numAudioTracks--; |
| | | if (numAudioTracks < 0) { |
| | | transceiver.wantReceive = false; |
| | | } |
| | | } else if (transceiver.kind === 'video') { |
| | | numVideoTracks--; |
| | | if (numVideoTracks < 0) { |
| | | transceiver.wantReceive = false; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | // Create M-lines for recvonly streams. |
| | | while (numAudioTracks > 0 || numVideoTracks > 0) { |
| | | if (numAudioTracks > 0) { |
| | | pc._createTransceiver('audio'); |
| | | numAudioTracks--; |
| | | } |
| | | if (numVideoTracks > 0) { |
| | | pc._createTransceiver('video'); |
| | | numVideoTracks--; |
| | | } |
| | | } |
| | | |
| | | var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId, |
| | | pc._sdpSessionVersion++); |
| | | pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { |
| | | // For each track, create an ice gatherer, ice transport, |
| | | // dtls transport, potentially rtpsender and rtpreceiver. |
| | | var track = transceiver.track; |
| | | var kind = transceiver.kind; |
| | | var mid = transceiver.mid || sdp.generateIdentifier(); |
| | | transceiver.mid = mid; |
| | | |
| | | if (!transceiver.iceGatherer) { |
| | | transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, |
| | | pc.usingBundle); |
| | | } |
| | | |
| | | var localCapabilities = window.RTCRtpSender.getCapabilities(kind); |
| | | // filter RTX until additional stuff needed for RTX is implemented |
| | | // in adapter.js |
| | | if (edgeVersion < 15019) { |
| | | localCapabilities.codecs = localCapabilities.codecs.filter( |
| | | function(codec) { |
| | | return codec.name !== 'rtx'; |
| | | }); |
| | | } |
| | | localCapabilities.codecs.forEach(function(codec) { |
| | | // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 |
| | | // by adding level-asymmetry-allowed=1 |
| | | if (codec.name === 'H264' && |
| | | codec.parameters['level-asymmetry-allowed'] === undefined) { |
| | | codec.parameters['level-asymmetry-allowed'] = '1'; |
| | | } |
| | | |
| | | // for subsequent offers, we might have to re-use the payload |
| | | // type of the last offer. |
| | | if (transceiver.remoteCapabilities && |
| | | transceiver.remoteCapabilities.codecs) { |
| | | transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { |
| | | if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && |
| | | codec.clockRate === remoteCodec.clockRate) { |
| | | codec.preferredPayloadType = remoteCodec.payloadType; |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | localCapabilities.headerExtensions.forEach(function(hdrExt) { |
| | | var remoteExtensions = transceiver.remoteCapabilities && |
| | | transceiver.remoteCapabilities.headerExtensions || []; |
| | | remoteExtensions.forEach(function(rHdrExt) { |
| | | if (hdrExt.uri === rHdrExt.uri) { |
| | | hdrExt.id = rHdrExt.id; |
| | | } |
| | | }); |
| | | }); |
| | | |
| | | // generate an ssrc now, to be used later in rtpSender.send |
| | | var sendEncodingParameters = transceiver.sendEncodingParameters || [{ |
| | | ssrc: (2 * sdpMLineIndex + 1) * 1001 |
| | | }]; |
| | | if (track) { |
| | | // add RTX |
| | | if (edgeVersion >= 15019 && kind === 'video' && |
| | | !sendEncodingParameters[0].rtx) { |
| | | sendEncodingParameters[0].rtx = { |
| | | ssrc: sendEncodingParameters[0].ssrc + 1 |
| | | }; |
| | | } |
| | | } |
| | | |
| | | if (transceiver.wantReceive) { |
| | | transceiver.rtpReceiver = new window.RTCRtpReceiver( |
| | | transceiver.dtlsTransport, kind); |
| | | } |
| | | |
| | | transceiver.localCapabilities = localCapabilities; |
| | | transceiver.sendEncodingParameters = sendEncodingParameters; |
| | | }); |
| | | |
| | | // always offer BUNDLE and dispose on return if not supported. |
| | | if (pc._config.bundlePolicy !== 'max-compat') { |
| | | sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { |
| | | return t.mid; |
| | | }).join(' ') + '\r\n'; |
| | | } |
| | | sdp$1 += 'a=ice-options:trickle\r\n'; |
| | | |
| | | pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { |
| | | sdp$1 += writeMediaSection(transceiver, transceiver.localCapabilities, |
| | | 'offer', transceiver.stream, pc._dtlsRole); |
| | | sdp$1 += 'a=rtcp-rsize\r\n'; |
| | | |
| | | if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && |
| | | (sdpMLineIndex === 0 || !pc.usingBundle)) { |
| | | transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { |
| | | cand.component = 1; |
| | | sdp$1 += 'a=' + sdp.writeCandidate(cand) + '\r\n'; |
| | | }); |
| | | |
| | | if (transceiver.iceGatherer.state === 'completed') { |
| | | sdp$1 += 'a=end-of-candidates\r\n'; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | var desc = new window.RTCSessionDescription({ |
| | | type: 'offer', |
| | | sdp: sdp$1 |
| | | }); |
| | | return Promise.resolve(desc); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.createAnswer = function() { |
| | | var pc = this; |
| | | |
| | | if (pc._isClosed) { |
| | | return Promise.reject(makeError('InvalidStateError', |
| | | 'Can not call createAnswer after close')); |
| | | } |
| | | |
| | | if (!(pc.signalingState === 'have-remote-offer' || |
| | | pc.signalingState === 'have-local-pranswer')) { |
| | | return Promise.reject(makeError('InvalidStateError', |
| | | 'Can not call createAnswer in signalingState ' + pc.signalingState)); |
| | | } |
| | | |
| | | var sdp$1 = sdp.writeSessionBoilerplate(pc._sdpSessionId, |
| | | pc._sdpSessionVersion++); |
| | | if (pc.usingBundle) { |
| | | sdp$1 += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { |
| | | return t.mid; |
| | | }).join(' ') + '\r\n'; |
| | | } |
| | | sdp$1 += 'a=ice-options:trickle\r\n'; |
| | | |
| | | var mediaSectionsInOffer = sdp.getMediaSections( |
| | | pc._remoteDescription.sdp).length; |
| | | pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { |
| | | if (sdpMLineIndex + 1 > mediaSectionsInOffer) { |
| | | return; |
| | | } |
| | | if (transceiver.rejected) { |
| | | if (transceiver.kind === 'application') { |
| | | if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt |
| | | sdp$1 += 'm=application 0 DTLS/SCTP 5000\r\n'; |
| | | } else { |
| | | sdp$1 += 'm=application 0 ' + transceiver.protocol + |
| | | ' webrtc-datachannel\r\n'; |
| | | } |
| | | } else if (transceiver.kind === 'audio') { |
| | | sdp$1 += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + |
| | | 'a=rtpmap:0 PCMU/8000\r\n'; |
| | | } else if (transceiver.kind === 'video') { |
| | | sdp$1 += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + |
| | | 'a=rtpmap:120 VP8/90000\r\n'; |
| | | } |
| | | sdp$1 += 'c=IN IP4 0.0.0.0\r\n' + |
| | | 'a=inactive\r\n' + |
| | | 'a=mid:' + transceiver.mid + '\r\n'; |
| | | return; |
| | | } |
| | | |
| | | // FIXME: look at direction. |
| | | if (transceiver.stream) { |
| | | var localTrack; |
| | | if (transceiver.kind === 'audio') { |
| | | localTrack = transceiver.stream.getAudioTracks()[0]; |
| | | } else if (transceiver.kind === 'video') { |
| | | localTrack = transceiver.stream.getVideoTracks()[0]; |
| | | } |
| | | if (localTrack) { |
| | | // add RTX |
| | | if (edgeVersion >= 15019 && transceiver.kind === 'video' && |
| | | !transceiver.sendEncodingParameters[0].rtx) { |
| | | transceiver.sendEncodingParameters[0].rtx = { |
| | | ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Calculate intersection of capabilities. |
| | | var commonCapabilities = getCommonCapabilities( |
| | | transceiver.localCapabilities, |
| | | transceiver.remoteCapabilities); |
| | | |
| | | var hasRtx = commonCapabilities.codecs.filter(function(c) { |
| | | return c.name.toLowerCase() === 'rtx'; |
| | | }).length; |
| | | if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { |
| | | delete transceiver.sendEncodingParameters[0].rtx; |
| | | } |
| | | |
| | | sdp$1 += writeMediaSection(transceiver, commonCapabilities, |
| | | 'answer', transceiver.stream, pc._dtlsRole); |
| | | if (transceiver.rtcpParameters && |
| | | transceiver.rtcpParameters.reducedSize) { |
| | | sdp$1 += 'a=rtcp-rsize\r\n'; |
| | | } |
| | | }); |
| | | |
| | | var desc = new window.RTCSessionDescription({ |
| | | type: 'answer', |
| | | sdp: sdp$1 |
| | | }); |
| | | return Promise.resolve(desc); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.addIceCandidate = function(candidate) { |
| | | var pc = this; |
| | | var sections; |
| | | if (candidate && !(candidate.sdpMLineIndex !== undefined || |
| | | candidate.sdpMid)) { |
| | | return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); |
| | | } |
| | | |
| | | // TODO: needs to go into ops queue. |
| | | return new Promise(function(resolve, reject) { |
| | | if (!pc._remoteDescription) { |
| | | return reject(makeError('InvalidStateError', |
| | | 'Can not add ICE candidate without a remote description')); |
| | | } else if (!candidate || candidate.candidate === '') { |
| | | for (var j = 0; j < pc.transceivers.length; j++) { |
| | | if (pc.transceivers[j].rejected) { |
| | | continue; |
| | | } |
| | | pc.transceivers[j].iceTransport.addRemoteCandidate({}); |
| | | sections = sdp.getMediaSections(pc._remoteDescription.sdp); |
| | | sections[j] += 'a=end-of-candidates\r\n'; |
| | | pc._remoteDescription.sdp = |
| | | sdp.getDescription(pc._remoteDescription.sdp) + |
| | | sections.join(''); |
| | | if (pc.usingBundle) { |
| | | break; |
| | | } |
| | | } |
| | | } else { |
| | | var sdpMLineIndex = candidate.sdpMLineIndex; |
| | | if (candidate.sdpMid) { |
| | | for (var i = 0; i < pc.transceivers.length; i++) { |
| | | if (pc.transceivers[i].mid === candidate.sdpMid) { |
| | | sdpMLineIndex = i; |
| | | break; |
| | | } |
| | | } |
| | | } |
| | | var transceiver = pc.transceivers[sdpMLineIndex]; |
| | | if (transceiver) { |
| | | if (transceiver.rejected) { |
| | | return resolve(); |
| | | } |
| | | var cand = Object.keys(candidate.candidate).length > 0 ? |
| | | sdp.parseCandidate(candidate.candidate) : {}; |
| | | // Ignore Chrome's invalid candidates since Edge does not like them. |
| | | if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { |
| | | return resolve(); |
| | | } |
| | | // Ignore RTCP candidates, we assume RTCP-MUX. |
| | | if (cand.component && cand.component !== 1) { |
| | | return resolve(); |
| | | } |
| | | // when using bundle, avoid adding candidates to the wrong |
| | | // ice transport. And avoid adding candidates added in the SDP. |
| | | if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && |
| | | transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { |
| | | if (!maybeAddCandidate(transceiver.iceTransport, cand)) { |
| | | return reject(makeError('OperationError', |
| | | 'Can not add ICE candidate')); |
| | | } |
| | | } |
| | | |
| | | // update the remoteDescription. |
| | | var candidateString = candidate.candidate.trim(); |
| | | if (candidateString.indexOf('a=') === 0) { |
| | | candidateString = candidateString.substr(2); |
| | | } |
| | | sections = sdp.getMediaSections(pc._remoteDescription.sdp); |
| | | sections[sdpMLineIndex] += 'a=' + |
| | | (cand.type ? candidateString : 'end-of-candidates') |
| | | + '\r\n'; |
| | | pc._remoteDescription.sdp = |
| | | sdp.getDescription(pc._remoteDescription.sdp) + |
| | | sections.join(''); |
| | | } else { |
| | | return reject(makeError('OperationError', |
| | | 'Can not add ICE candidate')); |
| | | } |
| | | } |
| | | resolve(); |
| | | }); |
| | | }; |
| | | |
| | | RTCPeerConnection.prototype.getStats = function(selector) { |
| | | if (selector && selector instanceof window.MediaStreamTrack) { |
| | | var senderOrReceiver = null; |
| | | this.transceivers.forEach(function(transceiver) { |
| | | if (transceiver.rtpSender && |
| | | transceiver.rtpSender.track === selector) { |
| | | senderOrReceiver = transceiver.rtpSender; |
| | | } else if (transceiver.rtpReceiver && |
| | | transceiver.rtpReceiver.track === selector) { |
| | | senderOrReceiver = transceiver.rtpReceiver; |
| | | } |
| | | }); |
| | | if (!senderOrReceiver) { |
| | | throw makeError('InvalidAccessError', 'Invalid selector.'); |
| | | } |
| | | return senderOrReceiver.getStats(); |
| | | } |
| | | |
| | | var promises = []; |
| | | this.transceivers.forEach(function(transceiver) { |
| | | ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', |
| | | 'dtlsTransport'].forEach(function(method) { |
| | | if (transceiver[method]) { |
| | | promises.push(transceiver[method].getStats()); |
| | | } |
| | | }); |
| | | }); |
| | | return Promise.all(promises).then(function(allStats) { |
| | | var results = new Map(); |
| | | allStats.forEach(function(stats) { |
| | | stats.forEach(function(stat) { |
| | | results.set(stat.id, stat); |
| | | }); |
| | | }); |
| | | return results; |
| | | }); |
| | | }; |
| | | |
| | | // fix low-level stat names and return Map instead of object. |
| | | var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', |
| | | 'RTCIceTransport', 'RTCDtlsTransport']; |
| | | ortcObjects.forEach(function(ortcObjectName) { |
| | | var obj = window[ortcObjectName]; |
| | | if (obj && obj.prototype && obj.prototype.getStats) { |
| | | var nativeGetstats = obj.prototype.getStats; |
| | | obj.prototype.getStats = function() { |
| | | return nativeGetstats.apply(this) |
| | | .then(function(nativeStats) { |
| | | var mapStats = new Map(); |
| | | Object.keys(nativeStats).forEach(function(id) { |
| | | nativeStats[id].type = fixStatsType(nativeStats[id]); |
| | | mapStats.set(id, nativeStats[id]); |
| | | }); |
| | | return mapStats; |
| | | }); |
| | | }; |
| | | } |
| | | }); |
| | | |
| | | // legacy callback shims. Should be moved to adapter.js some days. |
| | | var methods = ['createOffer', 'createAnswer']; |
| | | methods.forEach(function(method) { |
| | | var nativeMethod = RTCPeerConnection.prototype[method]; |
| | | RTCPeerConnection.prototype[method] = function() { |
| | | var args = arguments; |
| | | if (typeof args[0] === 'function' || |
| | | typeof args[1] === 'function') { // legacy |
| | | return nativeMethod.apply(this, [arguments[2]]) |
| | | .then(function(description) { |
| | | if (typeof args[0] === 'function') { |
| | | args[0].apply(null, [description]); |
| | | } |
| | | }, function(error) { |
| | | if (typeof args[1] === 'function') { |
| | | args[1].apply(null, [error]); |
| | | } |
| | | }); |
| | | } |
| | | return nativeMethod.apply(this, arguments); |
| | | }; |
| | | }); |
| | | |
| | | methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; |
| | | methods.forEach(function(method) { |
| | | var nativeMethod = RTCPeerConnection.prototype[method]; |
| | | RTCPeerConnection.prototype[method] = function() { |
| | | var args = arguments; |
| | | if (typeof args[1] === 'function' || |
| | | typeof args[2] === 'function') { // legacy |
| | | return nativeMethod.apply(this, arguments) |
| | | .then(function() { |
| | | if (typeof args[1] === 'function') { |
| | | args[1].apply(null); |
| | | } |
| | | }, function(error) { |
| | | if (typeof args[2] === 'function') { |
| | | args[2].apply(null, [error]); |
| | | } |
| | | }); |
| | | } |
| | | return nativeMethod.apply(this, arguments); |
| | | }; |
| | | }); |
| | | |
| | | // getStats is special. It doesn't have a spec legacy method yet we support |
| | | // getStats(something, cb) without error callbacks. |
| | | ['getStats'].forEach(function(method) { |
| | | var nativeMethod = RTCPeerConnection.prototype[method]; |
| | | RTCPeerConnection.prototype[method] = function() { |
| | | var args = arguments; |
| | | if (typeof args[1] === 'function') { |
| | | return nativeMethod.apply(this, arguments) |
| | | .then(function() { |
| | | if (typeof args[1] === 'function') { |
| | | args[1].apply(null); |
| | | } |
| | | }); |
| | | } |
| | | return nativeMethod.apply(this, arguments); |
| | | }; |
| | | }); |
| | | |
| | | return RTCPeerConnection; |
| | | }; |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimGetUserMedia$2(window) { |
| | | const navigator = window && window.navigator; |
| | | |
| | | const shimError_ = function(e) { |
| | | return { |
| | | name: {PermissionDeniedError: 'NotAllowedError'}[e.name] || e.name, |
| | | message: e.message, |
| | | constraint: e.constraint, |
| | | toString() { |
| | | return this.name; |
| | | } |
| | | }; |
| | | }; |
| | | |
| | | // getUserMedia error shim. |
| | | const origGetUserMedia = navigator.mediaDevices.getUserMedia. |
| | | bind(navigator.mediaDevices); |
| | | navigator.mediaDevices.getUserMedia = function(c) { |
| | | return origGetUserMedia(c).catch(e => Promise.reject(shimError_(e))); |
| | | }; |
| | | } |
| | | |
| | | /* |
| | | * Copyright (c) 2018 The adapter.js 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. |
| | | */ |
| | | |
| | | function shimGetDisplayMedia$1(window) { |
| | | if (!('getDisplayMedia' in window.navigator)) { |
| | | return; |
| | | } |
| | | if (!(window.navigator.mediaDevices)) { |
| | | return; |
| | | } |
| | | if (window.navigator.mediaDevices && |
| | | 'getDisplayMedia' in window.navigator.mediaDevices) { |
| | | return; |
| | | } |
| | | window.navigator.mediaDevices.getDisplayMedia = |
| | | window.navigator.getDisplayMedia.bind(window.navigator); |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimPeerConnection$1(window, browserDetails) { |
| | | if (window.RTCIceGatherer) { |
| | | if (!window.RTCIceCandidate) { |
| | | window.RTCIceCandidate = function RTCIceCandidate(args) { |
| | | return args; |
| | | }; |
| | | } |
| | | if (!window.RTCSessionDescription) { |
| | | window.RTCSessionDescription = function RTCSessionDescription(args) { |
| | | return args; |
| | | }; |
| | | } |
| | | // this adds an additional event listener to MediaStrackTrack that signals |
| | | // when a tracks enabled property was changed. Workaround for a bug in |
| | | // addStream, see below. No longer required in 15025+ |
| | | if (browserDetails.version < 15025) { |
| | | const origMSTEnabled = Object.getOwnPropertyDescriptor( |
| | | window.MediaStreamTrack.prototype, 'enabled'); |
| | | Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { |
| | | set(value) { |
| | | origMSTEnabled.set.call(this, value); |
| | | const ev = new Event('enabled'); |
| | | ev.enabled = value; |
| | | this.dispatchEvent(ev); |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | // ORTC defines the DTMF sender a bit different. |
| | | // https://github.com/w3c/ortc/issues/714 |
| | | if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { |
| | | Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { |
| | | get() { |
| | | if (this._dtmf === undefined) { |
| | | if (this.track.kind === 'audio') { |
| | | this._dtmf = new window.RTCDtmfSender(this); |
| | | } else if (this.track.kind === 'video') { |
| | | this._dtmf = null; |
| | | } |
| | | } |
| | | return this._dtmf; |
| | | } |
| | | }); |
| | | } |
| | | // Edge currently only implements the RTCDtmfSender, not the |
| | | // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* |
| | | if (window.RTCDtmfSender && !window.RTCDTMFSender) { |
| | | window.RTCDTMFSender = window.RTCDtmfSender; |
| | | } |
| | | |
| | | const RTCPeerConnectionShim = rtcpeerconnection(window, |
| | | browserDetails.version); |
| | | window.RTCPeerConnection = function RTCPeerConnection(config) { |
| | | if (config && config.iceServers) { |
| | | config.iceServers = filterIceServers$1(config.iceServers, |
| | | browserDetails.version); |
| | | log$1('ICE servers after filtering:', config.iceServers); |
| | | } |
| | | return new RTCPeerConnectionShim(config); |
| | | }; |
| | | window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; |
| | | } |
| | | |
| | | function shimReplaceTrack(window) { |
| | | // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 |
| | | if (window.RTCRtpSender && |
| | | !('replaceTrack' in window.RTCRtpSender.prototype)) { |
| | | window.RTCRtpSender.prototype.replaceTrack = |
| | | window.RTCRtpSender.prototype.setTrack; |
| | | } |
| | | } |
| | | |
| | | var edgeShim = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | shimPeerConnection: shimPeerConnection$1, |
| | | shimReplaceTrack: shimReplaceTrack, |
| | | shimGetUserMedia: shimGetUserMedia$2, |
| | | shimGetDisplayMedia: shimGetDisplayMedia$1 |
| | | }); |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimGetUserMedia$1(window, browserDetails) { |
| | | const navigator = window && window.navigator; |
| | | const MediaStreamTrack = window && window.MediaStreamTrack; |
| | | |
| | | navigator.getUserMedia = function(constraints, onSuccess, onError) { |
| | | // Replace Firefox 44+'s deprecation warning with unprefixed version. |
| | | deprecated('navigator.getUserMedia', |
| | | 'navigator.mediaDevices.getUserMedia'); |
| | | navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); |
| | | }; |
| | | |
| | | if (!(browserDetails.version > 55 && |
| | | 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { |
| | | const remap = function(obj, a, b) { |
| | | if (a in obj && !(b in obj)) { |
| | | obj[b] = obj[a]; |
| | | delete obj[a]; |
| | | } |
| | | }; |
| | | |
| | | const nativeGetUserMedia = navigator.mediaDevices.getUserMedia. |
| | | bind(navigator.mediaDevices); |
| | | navigator.mediaDevices.getUserMedia = function(c) { |
| | | if (typeof c === 'object' && typeof c.audio === 'object') { |
| | | c = JSON.parse(JSON.stringify(c)); |
| | | remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); |
| | | remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); |
| | | } |
| | | return nativeGetUserMedia(c); |
| | | }; |
| | | |
| | | if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { |
| | | const nativeGetSettings = MediaStreamTrack.prototype.getSettings; |
| | | MediaStreamTrack.prototype.getSettings = function() { |
| | | const obj = nativeGetSettings.apply(this, arguments); |
| | | remap(obj, 'mozAutoGainControl', 'autoGainControl'); |
| | | remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); |
| | | return obj; |
| | | }; |
| | | } |
| | | |
| | | if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { |
| | | const nativeApplyConstraints = |
| | | MediaStreamTrack.prototype.applyConstraints; |
| | | MediaStreamTrack.prototype.applyConstraints = function(c) { |
| | | if (this.kind === 'audio' && typeof c === 'object') { |
| | | c = JSON.parse(JSON.stringify(c)); |
| | | remap(c, 'autoGainControl', 'mozAutoGainControl'); |
| | | remap(c, 'noiseSuppression', 'mozNoiseSuppression'); |
| | | } |
| | | return nativeApplyConstraints.apply(this, [c]); |
| | | }; |
| | | } |
| | | } |
| | | } |
| | | |
| | | /* |
| | | * Copyright (c) 2018 The adapter.js 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. |
| | | */ |
| | | |
| | | function shimGetDisplayMedia(window, preferredMediaSource) { |
| | | if (window.navigator.mediaDevices && |
| | | 'getDisplayMedia' in window.navigator.mediaDevices) { |
| | | return; |
| | | } |
| | | if (!(window.navigator.mediaDevices)) { |
| | | return; |
| | | } |
| | | window.navigator.mediaDevices.getDisplayMedia = |
| | | function getDisplayMedia(constraints) { |
| | | if (!(constraints && constraints.video)) { |
| | | const err = new DOMException('getDisplayMedia without video ' + |
| | | 'constraints is undefined'); |
| | | err.name = 'NotFoundError'; |
| | | // from https://heycam.github.io/webidl/#idl-DOMException-error-names |
| | | err.code = 8; |
| | | return Promise.reject(err); |
| | | } |
| | | if (constraints.video === true) { |
| | | constraints.video = {mediaSource: preferredMediaSource}; |
| | | } else { |
| | | constraints.video.mediaSource = preferredMediaSource; |
| | | } |
| | | return window.navigator.mediaDevices.getUserMedia(constraints); |
| | | }; |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimOnTrack(window) { |
| | | if (typeof window === 'object' && window.RTCTrackEvent && |
| | | ('receiver' in window.RTCTrackEvent.prototype) && |
| | | !('transceiver' in window.RTCTrackEvent.prototype)) { |
| | | Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { |
| | | get() { |
| | | return {receiver: this.receiver}; |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function shimPeerConnection(window, browserDetails) { |
| | | if (typeof window !== 'object' || |
| | | !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { |
| | | return; // probably media.peerconnection.enabled=false in about:config |
| | | } |
| | | if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { |
| | | // very basic support for old versions. |
| | | window.RTCPeerConnection = window.mozRTCPeerConnection; |
| | | } |
| | | |
| | | if (browserDetails.version < 53) { |
| | | // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. |
| | | ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'] |
| | | .forEach(function(method) { |
| | | const nativeMethod = window.RTCPeerConnection.prototype[method]; |
| | | const methodObj = {[method]() { |
| | | arguments[0] = new ((method === 'addIceCandidate') ? |
| | | window.RTCIceCandidate : |
| | | window.RTCSessionDescription)(arguments[0]); |
| | | return nativeMethod.apply(this, arguments); |
| | | }}; |
| | | window.RTCPeerConnection.prototype[method] = methodObj[method]; |
| | | }); |
| | | } |
| | | |
| | | const modernStatsTypes = { |
| | | inboundrtp: 'inbound-rtp', |
| | | outboundrtp: 'outbound-rtp', |
| | | candidatepair: 'candidate-pair', |
| | | localcandidate: 'local-candidate', |
| | | remotecandidate: 'remote-candidate' |
| | | }; |
| | | |
| | | const nativeGetStats = window.RTCPeerConnection.prototype.getStats; |
| | | window.RTCPeerConnection.prototype.getStats = function getStats() { |
| | | const [selector, onSucc, onErr] = arguments; |
| | | return nativeGetStats.apply(this, [selector || null]) |
| | | .then(stats => { |
| | | if (browserDetails.version < 53 && !onSucc) { |
| | | // Shim only promise getStats with spec-hyphens in type names |
| | | // Leave callback version alone; misc old uses of forEach before Map |
| | | try { |
| | | stats.forEach(stat => { |
| | | stat.type = modernStatsTypes[stat.type] || stat.type; |
| | | }); |
| | | } catch (e) { |
| | | if (e.name !== 'TypeError') { |
| | | throw e; |
| | | } |
| | | // Avoid TypeError: "type" is read-only, in old versions. 34-43ish |
| | | stats.forEach((stat, i) => { |
| | | stats.set(i, Object.assign({}, stat, { |
| | | type: modernStatsTypes[stat.type] || stat.type |
| | | })); |
| | | }); |
| | | } |
| | | } |
| | | return stats; |
| | | }) |
| | | .then(onSucc, onErr); |
| | | }; |
| | | } |
| | | |
| | | function shimSenderGetStats(window) { |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection && |
| | | window.RTCRtpSender)) { |
| | | return; |
| | | } |
| | | if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { |
| | | return; |
| | | } |
| | | const origGetSenders = window.RTCPeerConnection.prototype.getSenders; |
| | | if (origGetSenders) { |
| | | window.RTCPeerConnection.prototype.getSenders = function getSenders() { |
| | | const senders = origGetSenders.apply(this, []); |
| | | senders.forEach(sender => sender._pc = this); |
| | | return senders; |
| | | }; |
| | | } |
| | | |
| | | const origAddTrack = window.RTCPeerConnection.prototype.addTrack; |
| | | if (origAddTrack) { |
| | | window.RTCPeerConnection.prototype.addTrack = function addTrack() { |
| | | const sender = origAddTrack.apply(this, arguments); |
| | | sender._pc = this; |
| | | return sender; |
| | | }; |
| | | } |
| | | window.RTCRtpSender.prototype.getStats = function getStats() { |
| | | return this.track ? this._pc.getStats(this.track) : |
| | | Promise.resolve(new Map()); |
| | | }; |
| | | } |
| | | |
| | | function shimReceiverGetStats(window) { |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection && |
| | | window.RTCRtpSender)) { |
| | | return; |
| | | } |
| | | if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { |
| | | return; |
| | | } |
| | | const origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; |
| | | if (origGetReceivers) { |
| | | window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { |
| | | const receivers = origGetReceivers.apply(this, []); |
| | | receivers.forEach(receiver => receiver._pc = this); |
| | | return receivers; |
| | | }; |
| | | } |
| | | wrapPeerConnectionEvent(window, 'track', e => { |
| | | e.receiver._pc = e.srcElement; |
| | | return e; |
| | | }); |
| | | window.RTCRtpReceiver.prototype.getStats = function getStats() { |
| | | return this._pc.getStats(this.track); |
| | | }; |
| | | } |
| | | |
| | | function shimRemoveStream(window) { |
| | | if (!window.RTCPeerConnection || |
| | | 'removeStream' in window.RTCPeerConnection.prototype) { |
| | | return; |
| | | } |
| | | window.RTCPeerConnection.prototype.removeStream = |
| | | function removeStream(stream) { |
| | | deprecated('removeStream', 'removeTrack'); |
| | | this.getSenders().forEach(sender => { |
| | | if (sender.track && stream.getTracks().includes(sender.track)) { |
| | | this.removeTrack(sender); |
| | | } |
| | | }); |
| | | }; |
| | | } |
| | | |
| | | function shimRTCDataChannel(window) { |
| | | // rename DataChannel to RTCDataChannel (native fix in FF60): |
| | | // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 |
| | | if (window.DataChannel && !window.RTCDataChannel) { |
| | | window.RTCDataChannel = window.DataChannel; |
| | | } |
| | | } |
| | | |
| | | function shimAddTransceiver(window) { |
| | | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 |
| | | // Firefox ignores the init sendEncodings options passed to addTransceiver |
| | | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
| | | return; |
| | | } |
| | | const origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; |
| | | if (origAddTransceiver) { |
| | | window.RTCPeerConnection.prototype.addTransceiver = |
| | | function addTransceiver() { |
| | | this.setParametersPromises = []; |
| | | const initParameters = arguments[1]; |
| | | const shouldPerformCheck = initParameters && |
| | | 'sendEncodings' in initParameters; |
| | | if (shouldPerformCheck) { |
| | | // If sendEncodings params are provided, validate grammar |
| | | initParameters.sendEncodings.forEach((encodingParam) => { |
| | | if ('rid' in encodingParam) { |
| | | const ridRegex = /^[a-z0-9]{0,16}$/i; |
| | | if (!ridRegex.test(encodingParam.rid)) { |
| | | throw new TypeError('Invalid RID value provided.'); |
| | | } |
| | | } |
| | | if ('scaleResolutionDownBy' in encodingParam) { |
| | | if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { |
| | | throw new RangeError('scale_resolution_down_by must be >= 1.0'); |
| | | } |
| | | } |
| | | if ('maxFramerate' in encodingParam) { |
| | | if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { |
| | | throw new RangeError('max_framerate must be >= 0.0'); |
| | | } |
| | | } |
| | | }); |
| | | } |
| | | const transceiver = origAddTransceiver.apply(this, arguments); |
| | | if (shouldPerformCheck) { |
| | | // Check if the init options were applied. If not we do this in an |
| | | // asynchronous way and save the promise reference in a global object. |
| | | // This is an ugly hack, but at the same time is way more robust than |
| | | // checking the sender parameters before and after the createOffer |
| | | // Also note that after the createoffer we are not 100% sure that |
| | | // the params were asynchronously applied so we might miss the |
| | | // opportunity to recreate offer. |
| | | const {sender} = transceiver; |
| | | const params = sender.getParameters(); |
| | | if (!('encodings' in params) || |
| | | // Avoid being fooled by patched getParameters() below. |
| | | (params.encodings.length === 1 && |
| | | Object.keys(params.encodings[0]).length === 0)) { |
| | | params.encodings = initParameters.sendEncodings; |
| | | sender.sendEncodings = initParameters.sendEncodings; |
| | | this.setParametersPromises.push(sender.setParameters(params) |
| | | .then(() => { |
| | | delete sender.sendEncodings; |
| | | }).catch(() => { |
| | | delete sender.sendEncodings; |
| | | }) |
| | | ); |
| | | } |
| | | } |
| | | return transceiver; |
| | | }; |
| | | } |
| | | } |
| | | |
| | | function shimGetParameters(window) { |
| | | if (!(typeof window === 'object' && window.RTCRtpSender)) { |
| | | return; |
| | | } |
| | | const origGetParameters = window.RTCRtpSender.prototype.getParameters; |
| | | if (origGetParameters) { |
| | | window.RTCRtpSender.prototype.getParameters = |
| | | function getParameters() { |
| | | const params = origGetParameters.apply(this, arguments); |
| | | if (!('encodings' in params)) { |
| | | params.encodings = [].concat(this.sendEncodings || [{}]); |
| | | } |
| | | return params; |
| | | }; |
| | | } |
| | | } |
| | | |
| | | function shimCreateOffer(window) { |
| | | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 |
| | | // Firefox ignores the init sendEncodings options passed to addTransceiver |
| | | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
| | | return; |
| | | } |
| | | const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; |
| | | window.RTCPeerConnection.prototype.createOffer = function createOffer() { |
| | | if (this.setParametersPromises && this.setParametersPromises.length) { |
| | | return Promise.all(this.setParametersPromises) |
| | | .then(() => { |
| | | return origCreateOffer.apply(this, arguments); |
| | | }) |
| | | .finally(() => { |
| | | this.setParametersPromises = []; |
| | | }); |
| | | } |
| | | return origCreateOffer.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimCreateAnswer(window) { |
| | | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 |
| | | // Firefox ignores the init sendEncodings options passed to addTransceiver |
| | | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 |
| | | if (!(typeof window === 'object' && window.RTCPeerConnection)) { |
| | | return; |
| | | } |
| | | const origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; |
| | | window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { |
| | | if (this.setParametersPromises && this.setParametersPromises.length) { |
| | | return Promise.all(this.setParametersPromises) |
| | | .then(() => { |
| | | return origCreateAnswer.apply(this, arguments); |
| | | }) |
| | | .finally(() => { |
| | | this.setParametersPromises = []; |
| | | }); |
| | | } |
| | | return origCreateAnswer.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | var firefoxShim = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | shimOnTrack: shimOnTrack, |
| | | shimPeerConnection: shimPeerConnection, |
| | | shimSenderGetStats: shimSenderGetStats, |
| | | shimReceiverGetStats: shimReceiverGetStats, |
| | | shimRemoveStream: shimRemoveStream, |
| | | shimRTCDataChannel: shimRTCDataChannel, |
| | | shimAddTransceiver: shimAddTransceiver, |
| | | shimGetParameters: shimGetParameters, |
| | | shimCreateOffer: shimCreateOffer, |
| | | shimCreateAnswer: shimCreateAnswer, |
| | | shimGetUserMedia: shimGetUserMedia$1, |
| | | shimGetDisplayMedia: shimGetDisplayMedia |
| | | }); |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | function shimLocalStreamsAPI(window) { |
| | | if (typeof window !== 'object' || !window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { |
| | | window.RTCPeerConnection.prototype.getLocalStreams = |
| | | function getLocalStreams() { |
| | | if (!this._localStreams) { |
| | | this._localStreams = []; |
| | | } |
| | | return this._localStreams; |
| | | }; |
| | | } |
| | | if (!('addStream' in window.RTCPeerConnection.prototype)) { |
| | | const _addTrack = window.RTCPeerConnection.prototype.addTrack; |
| | | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { |
| | | if (!this._localStreams) { |
| | | this._localStreams = []; |
| | | } |
| | | if (!this._localStreams.includes(stream)) { |
| | | this._localStreams.push(stream); |
| | | } |
| | | // Try to emulate Chrome's behaviour of adding in audio-video order. |
| | | // Safari orders by track id. |
| | | stream.getAudioTracks().forEach(track => _addTrack.call(this, track, |
| | | stream)); |
| | | stream.getVideoTracks().forEach(track => _addTrack.call(this, track, |
| | | stream)); |
| | | }; |
| | | |
| | | window.RTCPeerConnection.prototype.addTrack = |
| | | function addTrack(track, ...streams) { |
| | | if (streams) { |
| | | streams.forEach((stream) => { |
| | | if (!this._localStreams) { |
| | | this._localStreams = [stream]; |
| | | } else if (!this._localStreams.includes(stream)) { |
| | | this._localStreams.push(stream); |
| | | } |
| | | }); |
| | | } |
| | | return _addTrack.apply(this, arguments); |
| | | }; |
| | | } |
| | | if (!('removeStream' in window.RTCPeerConnection.prototype)) { |
| | | window.RTCPeerConnection.prototype.removeStream = |
| | | function removeStream(stream) { |
| | | if (!this._localStreams) { |
| | | this._localStreams = []; |
| | | } |
| | | const index = this._localStreams.indexOf(stream); |
| | | if (index === -1) { |
| | | return; |
| | | } |
| | | this._localStreams.splice(index, 1); |
| | | const tracks = stream.getTracks(); |
| | | this.getSenders().forEach(sender => { |
| | | if (tracks.includes(sender.track)) { |
| | | this.removeTrack(sender); |
| | | } |
| | | }); |
| | | }; |
| | | } |
| | | } |
| | | |
| | | function shimRemoteStreamsAPI(window) { |
| | | if (typeof window !== 'object' || !window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { |
| | | window.RTCPeerConnection.prototype.getRemoteStreams = |
| | | function getRemoteStreams() { |
| | | return this._remoteStreams ? this._remoteStreams : []; |
| | | }; |
| | | } |
| | | if (!('onaddstream' in window.RTCPeerConnection.prototype)) { |
| | | Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { |
| | | get() { |
| | | return this._onaddstream; |
| | | }, |
| | | set(f) { |
| | | if (this._onaddstream) { |
| | | this.removeEventListener('addstream', this._onaddstream); |
| | | this.removeEventListener('track', this._onaddstreampoly); |
| | | } |
| | | this.addEventListener('addstream', this._onaddstream = f); |
| | | this.addEventListener('track', this._onaddstreampoly = (e) => { |
| | | e.streams.forEach(stream => { |
| | | if (!this._remoteStreams) { |
| | | this._remoteStreams = []; |
| | | } |
| | | if (this._remoteStreams.includes(stream)) { |
| | | return; |
| | | } |
| | | this._remoteStreams.push(stream); |
| | | const event = new Event('addstream'); |
| | | event.stream = stream; |
| | | this.dispatchEvent(event); |
| | | }); |
| | | }); |
| | | } |
| | | }); |
| | | const origSetRemoteDescription = |
| | | window.RTCPeerConnection.prototype.setRemoteDescription; |
| | | window.RTCPeerConnection.prototype.setRemoteDescription = |
| | | function setRemoteDescription() { |
| | | const pc = this; |
| | | if (!this._onaddstreampoly) { |
| | | this.addEventListener('track', this._onaddstreampoly = function(e) { |
| | | e.streams.forEach(stream => { |
| | | if (!pc._remoteStreams) { |
| | | pc._remoteStreams = []; |
| | | } |
| | | if (pc._remoteStreams.indexOf(stream) >= 0) { |
| | | return; |
| | | } |
| | | pc._remoteStreams.push(stream); |
| | | const event = new Event('addstream'); |
| | | event.stream = stream; |
| | | pc.dispatchEvent(event); |
| | | }); |
| | | }); |
| | | } |
| | | return origSetRemoteDescription.apply(pc, arguments); |
| | | }; |
| | | } |
| | | } |
| | | |
| | | function shimCallbacksAPI(window) { |
| | | if (typeof window !== 'object' || !window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | const prototype = window.RTCPeerConnection.prototype; |
| | | const origCreateOffer = prototype.createOffer; |
| | | const origCreateAnswer = prototype.createAnswer; |
| | | const setLocalDescription = prototype.setLocalDescription; |
| | | const setRemoteDescription = prototype.setRemoteDescription; |
| | | const addIceCandidate = prototype.addIceCandidate; |
| | | |
| | | prototype.createOffer = |
| | | function createOffer(successCallback, failureCallback) { |
| | | const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; |
| | | const promise = origCreateOffer.apply(this, [options]); |
| | | if (!failureCallback) { |
| | | return promise; |
| | | } |
| | | promise.then(successCallback, failureCallback); |
| | | return Promise.resolve(); |
| | | }; |
| | | |
| | | prototype.createAnswer = |
| | | function createAnswer(successCallback, failureCallback) { |
| | | const options = (arguments.length >= 2) ? arguments[2] : arguments[0]; |
| | | const promise = origCreateAnswer.apply(this, [options]); |
| | | if (!failureCallback) { |
| | | return promise; |
| | | } |
| | | promise.then(successCallback, failureCallback); |
| | | return Promise.resolve(); |
| | | }; |
| | | |
| | | let withCallback = function(description, successCallback, failureCallback) { |
| | | const promise = setLocalDescription.apply(this, [description]); |
| | | if (!failureCallback) { |
| | | return promise; |
| | | } |
| | | promise.then(successCallback, failureCallback); |
| | | return Promise.resolve(); |
| | | }; |
| | | prototype.setLocalDescription = withCallback; |
| | | |
| | | withCallback = function(description, successCallback, failureCallback) { |
| | | const promise = setRemoteDescription.apply(this, [description]); |
| | | if (!failureCallback) { |
| | | return promise; |
| | | } |
| | | promise.then(successCallback, failureCallback); |
| | | return Promise.resolve(); |
| | | }; |
| | | prototype.setRemoteDescription = withCallback; |
| | | |
| | | withCallback = function(candidate, successCallback, failureCallback) { |
| | | const promise = addIceCandidate.apply(this, [candidate]); |
| | | if (!failureCallback) { |
| | | return promise; |
| | | } |
| | | promise.then(successCallback, failureCallback); |
| | | return Promise.resolve(); |
| | | }; |
| | | prototype.addIceCandidate = withCallback; |
| | | } |
| | | |
| | | function shimGetUserMedia(window) { |
| | | const navigator = window && window.navigator; |
| | | |
| | | if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { |
| | | // shim not needed in Safari 12.1 |
| | | const mediaDevices = navigator.mediaDevices; |
| | | const _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); |
| | | navigator.mediaDevices.getUserMedia = (constraints) => { |
| | | return _getUserMedia(shimConstraints(constraints)); |
| | | }; |
| | | } |
| | | |
| | | if (!navigator.getUserMedia && navigator.mediaDevices && |
| | | navigator.mediaDevices.getUserMedia) { |
| | | navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { |
| | | navigator.mediaDevices.getUserMedia(constraints) |
| | | .then(cb, errcb); |
| | | }.bind(navigator); |
| | | } |
| | | } |
| | | |
| | | function shimConstraints(constraints) { |
| | | if (constraints && constraints.video !== undefined) { |
| | | return Object.assign({}, |
| | | constraints, |
| | | {video: compactObject(constraints.video)} |
| | | ); |
| | | } |
| | | |
| | | return constraints; |
| | | } |
| | | |
| | | function shimRTCIceServerUrls(window) { |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | // migrate from non-spec RTCIceServer.url to RTCIceServer.urls |
| | | const OrigPeerConnection = window.RTCPeerConnection; |
| | | window.RTCPeerConnection = |
| | | function RTCPeerConnection(pcConfig, pcConstraints) { |
| | | if (pcConfig && pcConfig.iceServers) { |
| | | const newIceServers = []; |
| | | for (let i = 0; i < pcConfig.iceServers.length; i++) { |
| | | let server = pcConfig.iceServers[i]; |
| | | if (!server.hasOwnProperty('urls') && |
| | | server.hasOwnProperty('url')) { |
| | | deprecated('RTCIceServer.url', 'RTCIceServer.urls'); |
| | | server = JSON.parse(JSON.stringify(server)); |
| | | server.urls = server.url; |
| | | delete server.url; |
| | | newIceServers.push(server); |
| | | } else { |
| | | newIceServers.push(pcConfig.iceServers[i]); |
| | | } |
| | | } |
| | | pcConfig.iceServers = newIceServers; |
| | | } |
| | | return new OrigPeerConnection(pcConfig, pcConstraints); |
| | | }; |
| | | window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; |
| | | // wrap static methods. Currently just generateCertificate. |
| | | if ('generateCertificate' in OrigPeerConnection) { |
| | | Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { |
| | | get() { |
| | | return OrigPeerConnection.generateCertificate; |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function shimTrackEventTransceiver(window) { |
| | | // Add event.transceiver member over deprecated event.receiver |
| | | if (typeof window === 'object' && window.RTCTrackEvent && |
| | | 'receiver' in window.RTCTrackEvent.prototype && |
| | | !('transceiver' in window.RTCTrackEvent.prototype)) { |
| | | Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { |
| | | get() { |
| | | return {receiver: this.receiver}; |
| | | } |
| | | }); |
| | | } |
| | | } |
| | | |
| | | function shimCreateOfferLegacy(window) { |
| | | const origCreateOffer = window.RTCPeerConnection.prototype.createOffer; |
| | | window.RTCPeerConnection.prototype.createOffer = |
| | | function createOffer(offerOptions) { |
| | | if (offerOptions) { |
| | | if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { |
| | | // support bit values |
| | | offerOptions.offerToReceiveAudio = |
| | | !!offerOptions.offerToReceiveAudio; |
| | | } |
| | | const audioTransceiver = this.getTransceivers().find(transceiver => |
| | | transceiver.receiver.track.kind === 'audio'); |
| | | if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { |
| | | if (audioTransceiver.direction === 'sendrecv') { |
| | | if (audioTransceiver.setDirection) { |
| | | audioTransceiver.setDirection('sendonly'); |
| | | } else { |
| | | audioTransceiver.direction = 'sendonly'; |
| | | } |
| | | } else if (audioTransceiver.direction === 'recvonly') { |
| | | if (audioTransceiver.setDirection) { |
| | | audioTransceiver.setDirection('inactive'); |
| | | } else { |
| | | audioTransceiver.direction = 'inactive'; |
| | | } |
| | | } |
| | | } else if (offerOptions.offerToReceiveAudio === true && |
| | | !audioTransceiver) { |
| | | this.addTransceiver('audio'); |
| | | } |
| | | |
| | | if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { |
| | | // support bit values |
| | | offerOptions.offerToReceiveVideo = |
| | | !!offerOptions.offerToReceiveVideo; |
| | | } |
| | | const videoTransceiver = this.getTransceivers().find(transceiver => |
| | | transceiver.receiver.track.kind === 'video'); |
| | | if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { |
| | | if (videoTransceiver.direction === 'sendrecv') { |
| | | if (videoTransceiver.setDirection) { |
| | | videoTransceiver.setDirection('sendonly'); |
| | | } else { |
| | | videoTransceiver.direction = 'sendonly'; |
| | | } |
| | | } else if (videoTransceiver.direction === 'recvonly') { |
| | | if (videoTransceiver.setDirection) { |
| | | videoTransceiver.setDirection('inactive'); |
| | | } else { |
| | | videoTransceiver.direction = 'inactive'; |
| | | } |
| | | } |
| | | } else if (offerOptions.offerToReceiveVideo === true && |
| | | !videoTransceiver) { |
| | | this.addTransceiver('video'); |
| | | } |
| | | } |
| | | return origCreateOffer.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimAudioContext(window) { |
| | | if (typeof window !== 'object' || window.AudioContext) { |
| | | return; |
| | | } |
| | | window.AudioContext = window.webkitAudioContext; |
| | | } |
| | | |
| | | var safariShim = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | shimLocalStreamsAPI: shimLocalStreamsAPI, |
| | | shimRemoteStreamsAPI: shimRemoteStreamsAPI, |
| | | shimCallbacksAPI: shimCallbacksAPI, |
| | | shimGetUserMedia: shimGetUserMedia, |
| | | shimConstraints: shimConstraints, |
| | | shimRTCIceServerUrls: shimRTCIceServerUrls, |
| | | shimTrackEventTransceiver: shimTrackEventTransceiver, |
| | | shimCreateOfferLegacy: shimCreateOfferLegacy, |
| | | shimAudioContext: shimAudioContext |
| | | }); |
| | | |
| | | /* |
| | | * Copyright (c) 2017 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. |
| | | */ |
| | | |
| | | function shimRTCIceCandidate(window) { |
| | | // foundation is arbitrarily chosen as an indicator for full support for |
| | | // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface |
| | | if (!window.RTCIceCandidate || (window.RTCIceCandidate && 'foundation' in |
| | | window.RTCIceCandidate.prototype)) { |
| | | return; |
| | | } |
| | | |
| | | const NativeRTCIceCandidate = window.RTCIceCandidate; |
| | | window.RTCIceCandidate = function RTCIceCandidate(args) { |
| | | // Remove the a= which shouldn't be part of the candidate string. |
| | | if (typeof args === 'object' && args.candidate && |
| | | args.candidate.indexOf('a=') === 0) { |
| | | args = JSON.parse(JSON.stringify(args)); |
| | | args.candidate = args.candidate.substr(2); |
| | | } |
| | | |
| | | if (args.candidate && args.candidate.length) { |
| | | // Augment the native candidate with the parsed fields. |
| | | const nativeCandidate = new NativeRTCIceCandidate(args); |
| | | const parsedCandidate = sdp.parseCandidate(args.candidate); |
| | | const augmentedCandidate = Object.assign(nativeCandidate, |
| | | parsedCandidate); |
| | | |
| | | // Add a serializer that does not serialize the extra attributes. |
| | | augmentedCandidate.toJSON = function toJSON() { |
| | | return { |
| | | candidate: augmentedCandidate.candidate, |
| | | sdpMid: augmentedCandidate.sdpMid, |
| | | sdpMLineIndex: augmentedCandidate.sdpMLineIndex, |
| | | usernameFragment: augmentedCandidate.usernameFragment, |
| | | }; |
| | | }; |
| | | return augmentedCandidate; |
| | | } |
| | | return new NativeRTCIceCandidate(args); |
| | | }; |
| | | window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; |
| | | |
| | | // Hook up the augmented candidate in onicecandidate and |
| | | // addEventListener('icecandidate', ...) |
| | | wrapPeerConnectionEvent(window, 'icecandidate', e => { |
| | | if (e.candidate) { |
| | | Object.defineProperty(e, 'candidate', { |
| | | value: new window.RTCIceCandidate(e.candidate), |
| | | writable: 'false' |
| | | }); |
| | | } |
| | | return e; |
| | | }); |
| | | } |
| | | |
| | | function shimMaxMessageSize(window, browserDetails) { |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | |
| | | if (!('sctp' in window.RTCPeerConnection.prototype)) { |
| | | Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { |
| | | get() { |
| | | return typeof this._sctp === 'undefined' ? null : this._sctp; |
| | | } |
| | | }); |
| | | } |
| | | |
| | | const sctpInDescription = function(description) { |
| | | if (!description || !description.sdp) { |
| | | return false; |
| | | } |
| | | const sections = sdp.splitSections(description.sdp); |
| | | sections.shift(); |
| | | return sections.some(mediaSection => { |
| | | const mLine = sdp.parseMLine(mediaSection); |
| | | return mLine && mLine.kind === 'application' |
| | | && mLine.protocol.indexOf('SCTP') !== -1; |
| | | }); |
| | | }; |
| | | |
| | | const getRemoteFirefoxVersion = function(description) { |
| | | // TODO: Is there a better solution for detecting Firefox? |
| | | const match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); |
| | | if (match === null || match.length < 2) { |
| | | return -1; |
| | | } |
| | | const version = parseInt(match[1], 10); |
| | | // Test for NaN (yes, this is ugly) |
| | | return version !== version ? -1 : version; |
| | | }; |
| | | |
| | | const getCanSendMaxMessageSize = function(remoteIsFirefox) { |
| | | // Every implementation we know can send at least 64 KiB. |
| | | // Note: Although Chrome is technically able to send up to 256 KiB, the |
| | | // data does not reach the other peer reliably. |
| | | // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 |
| | | let canSendMaxMessageSize = 65536; |
| | | if (browserDetails.browser === 'firefox') { |
| | | if (browserDetails.version < 57) { |
| | | if (remoteIsFirefox === -1) { |
| | | // FF < 57 will send in 16 KiB chunks using the deprecated PPID |
| | | // fragmentation. |
| | | canSendMaxMessageSize = 16384; |
| | | } else { |
| | | // However, other FF (and RAWRTC) can reassemble PPID-fragmented |
| | | // messages. Thus, supporting ~2 GiB when sending. |
| | | canSendMaxMessageSize = 2147483637; |
| | | } |
| | | } else if (browserDetails.version < 60) { |
| | | // Currently, all FF >= 57 will reset the remote maximum message size |
| | | // to the default value when a data channel is created at a later |
| | | // stage. :( |
| | | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 |
| | | canSendMaxMessageSize = |
| | | browserDetails.version === 57 ? 65535 : 65536; |
| | | } else { |
| | | // FF >= 60 supports sending ~2 GiB |
| | | canSendMaxMessageSize = 2147483637; |
| | | } |
| | | } |
| | | return canSendMaxMessageSize; |
| | | }; |
| | | |
| | | const getMaxMessageSize = function(description, remoteIsFirefox) { |
| | | // Note: 65536 bytes is the default value from the SDP spec. Also, |
| | | // every implementation we know supports receiving 65536 bytes. |
| | | let maxMessageSize = 65536; |
| | | |
| | | // FF 57 has a slightly incorrect default remote max message size, so |
| | | // we need to adjust it here to avoid a failure when sending. |
| | | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 |
| | | if (browserDetails.browser === 'firefox' |
| | | && browserDetails.version === 57) { |
| | | maxMessageSize = 65535; |
| | | } |
| | | |
| | | const match = sdp.matchPrefix(description.sdp, |
| | | 'a=max-message-size:'); |
| | | if (match.length > 0) { |
| | | maxMessageSize = parseInt(match[0].substr(19), 10); |
| | | } else if (browserDetails.browser === 'firefox' && |
| | | remoteIsFirefox !== -1) { |
| | | // If the maximum message size is not present in the remote SDP and |
| | | // both local and remote are Firefox, the remote peer can receive |
| | | // ~2 GiB. |
| | | maxMessageSize = 2147483637; |
| | | } |
| | | return maxMessageSize; |
| | | }; |
| | | |
| | | const origSetRemoteDescription = |
| | | window.RTCPeerConnection.prototype.setRemoteDescription; |
| | | window.RTCPeerConnection.prototype.setRemoteDescription = |
| | | function setRemoteDescription() { |
| | | this._sctp = null; |
| | | // Chrome decided to not expose .sctp in plan-b mode. |
| | | // As usual, adapter.js has to do an 'ugly worakaround' |
| | | // to cover up the mess. |
| | | if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { |
| | | const {sdpSemantics} = this.getConfiguration(); |
| | | if (sdpSemantics === 'plan-b') { |
| | | Object.defineProperty(this, 'sctp', { |
| | | get() { |
| | | return typeof this._sctp === 'undefined' ? null : this._sctp; |
| | | }, |
| | | enumerable: true, |
| | | configurable: true, |
| | | }); |
| | | } |
| | | } |
| | | |
| | | if (sctpInDescription(arguments[0])) { |
| | | // Check if the remote is FF. |
| | | const isFirefox = getRemoteFirefoxVersion(arguments[0]); |
| | | |
| | | // Get the maximum message size the local peer is capable of sending |
| | | const canSendMMS = getCanSendMaxMessageSize(isFirefox); |
| | | |
| | | // Get the maximum message size of the remote peer. |
| | | const remoteMMS = getMaxMessageSize(arguments[0], isFirefox); |
| | | |
| | | // Determine final maximum message size |
| | | let maxMessageSize; |
| | | if (canSendMMS === 0 && remoteMMS === 0) { |
| | | maxMessageSize = Number.POSITIVE_INFINITY; |
| | | } else if (canSendMMS === 0 || remoteMMS === 0) { |
| | | maxMessageSize = Math.max(canSendMMS, remoteMMS); |
| | | } else { |
| | | maxMessageSize = Math.min(canSendMMS, remoteMMS); |
| | | } |
| | | |
| | | // Create a dummy RTCSctpTransport object and the 'maxMessageSize' |
| | | // attribute. |
| | | const sctp = {}; |
| | | Object.defineProperty(sctp, 'maxMessageSize', { |
| | | get() { |
| | | return maxMessageSize; |
| | | } |
| | | }); |
| | | this._sctp = sctp; |
| | | } |
| | | |
| | | return origSetRemoteDescription.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimSendThrowTypeError(window) { |
| | | if (!(window.RTCPeerConnection && |
| | | 'createDataChannel' in window.RTCPeerConnection.prototype)) { |
| | | return; |
| | | } |
| | | |
| | | // Note: Although Firefox >= 57 has a native implementation, the maximum |
| | | // message size can be reset for all data channels at a later stage. |
| | | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 |
| | | |
| | | function wrapDcSend(dc, pc) { |
| | | const origDataChannelSend = dc.send; |
| | | dc.send = function send() { |
| | | const data = arguments[0]; |
| | | const length = data.length || data.size || data.byteLength; |
| | | if (dc.readyState === 'open' && |
| | | pc.sctp && length > pc.sctp.maxMessageSize) { |
| | | throw new TypeError('Message too large (can send a maximum of ' + |
| | | pc.sctp.maxMessageSize + ' bytes)'); |
| | | } |
| | | return origDataChannelSend.apply(dc, arguments); |
| | | }; |
| | | } |
| | | const origCreateDataChannel = |
| | | window.RTCPeerConnection.prototype.createDataChannel; |
| | | window.RTCPeerConnection.prototype.createDataChannel = |
| | | function createDataChannel() { |
| | | const dataChannel = origCreateDataChannel.apply(this, arguments); |
| | | wrapDcSend(dataChannel, this); |
| | | return dataChannel; |
| | | }; |
| | | wrapPeerConnectionEvent(window, 'datachannel', e => { |
| | | wrapDcSend(e.channel, e.target); |
| | | return e; |
| | | }); |
| | | } |
| | | |
| | | |
| | | /* shims RTCConnectionState by pretending it is the same as iceConnectionState. |
| | | * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 |
| | | * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect |
| | | * since DTLS failures would be hidden. See |
| | | * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 |
| | | * for the Firefox tracking bug. |
| | | */ |
| | | function shimConnectionState(window) { |
| | | if (!window.RTCPeerConnection || |
| | | 'connectionState' in window.RTCPeerConnection.prototype) { |
| | | return; |
| | | } |
| | | const proto = window.RTCPeerConnection.prototype; |
| | | Object.defineProperty(proto, 'connectionState', { |
| | | get() { |
| | | return { |
| | | completed: 'connected', |
| | | checking: 'connecting' |
| | | }[this.iceConnectionState] || this.iceConnectionState; |
| | | }, |
| | | enumerable: true, |
| | | configurable: true |
| | | }); |
| | | Object.defineProperty(proto, 'onconnectionstatechange', { |
| | | get() { |
| | | return this._onconnectionstatechange || null; |
| | | }, |
| | | set(cb) { |
| | | if (this._onconnectionstatechange) { |
| | | this.removeEventListener('connectionstatechange', |
| | | this._onconnectionstatechange); |
| | | delete this._onconnectionstatechange; |
| | | } |
| | | if (cb) { |
| | | this.addEventListener('connectionstatechange', |
| | | this._onconnectionstatechange = cb); |
| | | } |
| | | }, |
| | | enumerable: true, |
| | | configurable: true |
| | | }); |
| | | |
| | | ['setLocalDescription', 'setRemoteDescription'].forEach((method) => { |
| | | const origMethod = proto[method]; |
| | | proto[method] = function() { |
| | | if (!this._connectionstatechangepoly) { |
| | | this._connectionstatechangepoly = e => { |
| | | const pc = e.target; |
| | | if (pc._lastConnectionState !== pc.connectionState) { |
| | | pc._lastConnectionState = pc.connectionState; |
| | | const newEvent = new Event('connectionstatechange', e); |
| | | pc.dispatchEvent(newEvent); |
| | | } |
| | | return e; |
| | | }; |
| | | this.addEventListener('iceconnectionstatechange', |
| | | this._connectionstatechangepoly); |
| | | } |
| | | return origMethod.apply(this, arguments); |
| | | }; |
| | | }); |
| | | } |
| | | |
| | | function removeExtmapAllowMixed(window, browserDetails) { |
| | | /* remove a=extmap-allow-mixed for webrtc.org < M71 */ |
| | | if (!window.RTCPeerConnection) { |
| | | return; |
| | | } |
| | | if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { |
| | | return; |
| | | } |
| | | if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { |
| | | return; |
| | | } |
| | | const nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; |
| | | window.RTCPeerConnection.prototype.setRemoteDescription = |
| | | function setRemoteDescription(desc) { |
| | | if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { |
| | | const sdp = desc.sdp.split('\n').filter((line) => { |
| | | return line.trim() !== 'a=extmap-allow-mixed'; |
| | | }).join('\n'); |
| | | // Safari enforces read-only-ness of RTCSessionDescription fields. |
| | | if (window.RTCSessionDescription && |
| | | desc instanceof window.RTCSessionDescription) { |
| | | arguments[0] = new window.RTCSessionDescription({ |
| | | type: desc.type, |
| | | sdp, |
| | | }); |
| | | } else { |
| | | desc.sdp = sdp; |
| | | } |
| | | } |
| | | return nativeSRD.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | function shimAddIceCandidateNullOrEmpty(window, browserDetails) { |
| | | // Support for addIceCandidate(null or undefined) |
| | | // as well as addIceCandidate({candidate: "", ...}) |
| | | // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 |
| | | // Note: must be called before other polyfills which change the signature. |
| | | if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { |
| | | return; |
| | | } |
| | | const nativeAddIceCandidate = |
| | | window.RTCPeerConnection.prototype.addIceCandidate; |
| | | if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { |
| | | return; |
| | | } |
| | | window.RTCPeerConnection.prototype.addIceCandidate = |
| | | function addIceCandidate() { |
| | | if (!arguments[0]) { |
| | | if (arguments[1]) { |
| | | arguments[1].apply(null); |
| | | } |
| | | return Promise.resolve(); |
| | | } |
| | | // Firefox 68+ emits and processes {candidate: "", ...}, ignore |
| | | // in older versions. |
| | | // Native support for ignoring exists for Chrome M77+. |
| | | // Safari ignores as well, exact version unknown but works in the same |
| | | // version that also ignores addIceCandidate(null). |
| | | if (((browserDetails.browser === 'chrome' && browserDetails.version < 78) |
| | | || (browserDetails.browser === 'firefox' |
| | | && browserDetails.version < 68) |
| | | || (browserDetails.browser === 'safari')) |
| | | && arguments[0] && arguments[0].candidate === '') { |
| | | return Promise.resolve(); |
| | | } |
| | | return nativeAddIceCandidate.apply(this, arguments); |
| | | }; |
| | | } |
| | | |
| | | var commonShim = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | shimRTCIceCandidate: shimRTCIceCandidate, |
| | | shimMaxMessageSize: shimMaxMessageSize, |
| | | shimSendThrowTypeError: shimSendThrowTypeError, |
| | | shimConnectionState: shimConnectionState, |
| | | removeExtmapAllowMixed: removeExtmapAllowMixed, |
| | | shimAddIceCandidateNullOrEmpty: shimAddIceCandidateNullOrEmpty |
| | | }); |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | // Shimming starts here. |
| | | function adapterFactory({window} = {}, options = { |
| | | shimChrome: true, |
| | | shimFirefox: true, |
| | | shimEdge: true, |
| | | shimSafari: true, |
| | | }) { |
| | | // Utils. |
| | | const logging = log$1; |
| | | const browserDetails = detectBrowser(window); |
| | | |
| | | const adapter = { |
| | | browserDetails, |
| | | commonShim, |
| | | extractVersion: extractVersion, |
| | | disableLog: disableLog, |
| | | disableWarnings: disableWarnings |
| | | }; |
| | | |
| | | // Shim browser if found. |
| | | switch (browserDetails.browser) { |
| | | case 'chrome': |
| | | if (!chromeShim || !shimPeerConnection$2 || |
| | | !options.shimChrome) { |
| | | logging('Chrome shim is not included in this adapter release.'); |
| | | return adapter; |
| | | } |
| | | if (browserDetails.version === null) { |
| | | logging('Chrome shim can not determine version, not shimming.'); |
| | | return adapter; |
| | | } |
| | | logging('adapter.js shimming chrome.'); |
| | | // Export to the adapter global object visible in the browser. |
| | | adapter.browserShim = chromeShim; |
| | | |
| | | // Must be called before shimPeerConnection. |
| | | shimAddIceCandidateNullOrEmpty(window, browserDetails); |
| | | |
| | | shimGetUserMedia$3(window, browserDetails); |
| | | shimMediaStream(window); |
| | | shimPeerConnection$2(window, browserDetails); |
| | | shimOnTrack$1(window); |
| | | shimAddTrackRemoveTrack(window, browserDetails); |
| | | shimGetSendersWithDtmf(window); |
| | | shimGetStats(window); |
| | | shimSenderReceiverGetStats(window); |
| | | fixNegotiationNeeded(window, browserDetails); |
| | | |
| | | shimRTCIceCandidate(window); |
| | | shimConnectionState(window); |
| | | shimMaxMessageSize(window, browserDetails); |
| | | shimSendThrowTypeError(window); |
| | | removeExtmapAllowMixed(window, browserDetails); |
| | | break; |
| | | case 'firefox': |
| | | if (!firefoxShim || !shimPeerConnection || |
| | | !options.shimFirefox) { |
| | | logging('Firefox shim is not included in this adapter release.'); |
| | | return adapter; |
| | | } |
| | | logging('adapter.js shimming firefox.'); |
| | | // Export to the adapter global object visible in the browser. |
| | | adapter.browserShim = firefoxShim; |
| | | |
| | | // Must be called before shimPeerConnection. |
| | | shimAddIceCandidateNullOrEmpty(window, browserDetails); |
| | | |
| | | shimGetUserMedia$1(window, browserDetails); |
| | | shimPeerConnection(window, browserDetails); |
| | | shimOnTrack(window); |
| | | shimRemoveStream(window); |
| | | shimSenderGetStats(window); |
| | | shimReceiverGetStats(window); |
| | | shimRTCDataChannel(window); |
| | | shimAddTransceiver(window); |
| | | shimGetParameters(window); |
| | | shimCreateOffer(window); |
| | | shimCreateAnswer(window); |
| | | |
| | | shimRTCIceCandidate(window); |
| | | shimConnectionState(window); |
| | | shimMaxMessageSize(window, browserDetails); |
| | | shimSendThrowTypeError(window); |
| | | break; |
| | | case 'edge': |
| | | if (!edgeShim || !shimPeerConnection$1 || !options.shimEdge) { |
| | | logging('MS edge shim is not included in this adapter release.'); |
| | | return adapter; |
| | | } |
| | | logging('adapter.js shimming edge.'); |
| | | // Export to the adapter global object visible in the browser. |
| | | adapter.browserShim = edgeShim; |
| | | |
| | | shimGetUserMedia$2(window); |
| | | shimGetDisplayMedia$1(window); |
| | | shimPeerConnection$1(window, browserDetails); |
| | | shimReplaceTrack(window); |
| | | |
| | | // the edge shim implements the full RTCIceCandidate object. |
| | | |
| | | shimMaxMessageSize(window, browserDetails); |
| | | shimSendThrowTypeError(window); |
| | | break; |
| | | case 'safari': |
| | | if (!safariShim || !options.shimSafari) { |
| | | logging('Safari shim is not included in this adapter release.'); |
| | | return adapter; |
| | | } |
| | | logging('adapter.js shimming safari.'); |
| | | // Export to the adapter global object visible in the browser. |
| | | adapter.browserShim = safariShim; |
| | | |
| | | // Must be called before shimCallbackAPI. |
| | | shimAddIceCandidateNullOrEmpty(window, browserDetails); |
| | | |
| | | shimRTCIceServerUrls(window); |
| | | shimCreateOfferLegacy(window); |
| | | shimCallbacksAPI(window); |
| | | shimLocalStreamsAPI(window); |
| | | shimRemoteStreamsAPI(window); |
| | | shimTrackEventTransceiver(window); |
| | | shimGetUserMedia(window); |
| | | shimAudioContext(window); |
| | | |
| | | shimRTCIceCandidate(window); |
| | | shimMaxMessageSize(window, browserDetails); |
| | | shimSendThrowTypeError(window); |
| | | removeExtmapAllowMixed(window, browserDetails); |
| | | break; |
| | | default: |
| | | logging('Unsupported browser!'); |
| | | break; |
| | | } |
| | | |
| | | return adapter; |
| | | } |
| | | |
| | | /* |
| | | * 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. |
| | | */ |
| | | |
| | | adapterFactory({window: typeof window === 'undefined' ? undefined : window}); |
| | | |
| | | /** |
| | | * @class AudioTrackConstraints |
| | | * @classDesc Constraints for creating an audio MediaStreamTrack. |
| | | * @memberof Owt.Base |
| | | * @constructor |
| | | * @param {Owt.Base.AudioSourceInfo} source Source info of this audio track. |
| | | */ |
| | | |
| | | class AudioTrackConstraints { |
| | | // eslint-disable-next-line require-jsdoc |
| | | constructor(source) { |
| | | if (!Object.values(AudioSourceInfo).some(v => v === source)) { |
| | | throw new TypeError('Invalid source.'); |
| | | } |
| | | /** |
| | | * @member {string} source |
| | | * @memberof Owt.Base.AudioTrackConstraints |
| | | * @desc Values could be "mic", "screen-cast", "file" or "mixed". |
| | | * @instance |
| | | */ |
| | | |
| | | |
| | | this.source = source; |
| | | /** |
| | | * @member {string} deviceId |
| | | * @memberof Owt.Base.AudioTrackConstraints |
| | | * @desc Do not provide deviceId if source is not "mic". |
| | | * @instance |
| | | * @see https://w3c.github.io/mediacapture-main/#def-constraint-deviceId |
| | | */ |
| | | |
| | | this.deviceId = undefined; |
| | | } |
| | | |
| | | } |
| | | /** |
| | | * @class VideoTrackConstraints |
| | | * @classDesc Constraints for creating a video MediaStreamTrack. |
| | | * @memberof Owt.Base |
| | | * @constructor |
| | | * @param {Owt.Base.VideoSourceInfo} source Source info of this video track. |
| | | */ |
| | | |
| | | class VideoTrackConstraints { |
| | | // eslint-disable-next-line require-jsdoc |
| | | constructor(source) { |
| | | if (!Object.values(VideoSourceInfo).some(v => v === source)) { |
| | | throw new TypeError('Invalid source.'); |
| | | } |
| | | /** |
| | | * @member {string} source |
| | | * @memberof Owt.Base.VideoTrackConstraints |
| | | * @desc Values could be "camera", "screen-cast", "file" or "mixed". |
| | | * @instance |
| | | */ |
| | | |
| | | |
| | | this.source = source; |
| | | /** |
| | | * @member {string} deviceId |
| | | * @memberof Owt.Base.VideoTrackConstraints |
| | | * @desc Do not provide deviceId if source is not "camera". |
| | | * @instance |
| | | * @see https://w3c.github.io/mediacapture-main/#def-constraint-deviceId |
| | | */ |
| | | |
| | | this.deviceId = undefined; |
| | | /** |
| | | * @member {Owt.Base.Resolution} resolution |
| | | * @memberof Owt.Base.VideoTrackConstraints |
| | | * @instance |
| | | */ |
| | | |
| | | this.resolution = undefined; |
| | | /** |
| | | * @member {number} frameRate |
| | | * @memberof Owt.Base.VideoTrackConstraints |
| | | * @instance |
| | | */ |
| | | |
| | | this.frameRate = undefined; |
| | | } |
| | | |
| | | } |
| | | /** |
| | | * @class StreamConstraints |
| | | * @classDesc Constraints for creating a MediaStream from screen mic and camera. |
| | | * @memberof Owt.Base |
| | | * @constructor |
| | | * @param {?Owt.Base.AudioTrackConstraints} audioConstraints |
| | | * @param {?Owt.Base.VideoTrackConstraints} videoConstraints |
| | | */ |
| | | |
| | | class StreamConstraints { |
| | | // eslint-disable-next-line require-jsdoc |
| | | constructor(audioConstraints = false, videoConstraints = false) { |
| | | /** |
| | | * @member {Owt.Base.MediaStreamTrackDeviceConstraintsForAudio} audio |
| | | * @memberof Owt.Base.MediaStreamDeviceConstraints |
| | | * @instance |
| | | */ |
| | | this.audio = audioConstraints; |
| | | /** |
| | | * @member {Owt.Base.MediaStreamTrackDeviceConstraintsForVideo} Video |
| | | * @memberof Owt.Base.MediaStreamDeviceConstraints |
| | | * @instance |
| | | */ |
| | | |
| | | this.video = videoConstraints; |
| | | } |
| | | |
| | | } // eslint-disable-next-line require-jsdoc |
| | | |
| | | function isVideoConstrainsForScreenCast(constraints) { |
| | | return typeof constraints.video === 'object' && constraints.video.source === VideoSourceInfo.SCREENCAST; |
| | | } |
| | | /** |
| | | * @class MediaStreamFactory |
| | | * @classDesc A factory to create MediaStream. You can also create MediaStream by yourself. |
| | | * @memberof Owt.Base |
| | | */ |
| | | |
| | | |
| | | class MediaStreamFactory { |
| | | /** |
| | | * @function createMediaStream |
| | | * @static |
| | | * @desc Create a MediaStream with given constraints. If you want to create a MediaStream for screen cast, please make sure both audio and video's source are "screen-cast". |
| | | * @memberof Owt.Base.MediaStreamFactory |
| | | * @return {Promise<MediaStream, Error>} Return a promise that is resolved when stream is successfully created, or rejected if one of the following error happened: |
| | | * - One or more parameters cannot be satisfied. |
| | | * - Specified device is busy. |
| | | * - Cannot obtain necessary permission or operation is canceled by user. |
| | | * - Video source is screen cast, while audio source is not. |
| | | * - Audio source is screen cast, while video source is disabled. |
| | | * @param {Owt.Base.StreamConstraints} constraints |
| | | */ |
| | | static createMediaStream(constraints) { |
| | | if (typeof constraints !== 'object' || !constraints.audio && !constraints.video) { |
| | | return Promise.reject(new TypeError('Invalid constrains')); |
| | | } |
| | | |
| | | if (!isVideoConstrainsForScreenCast(constraints) && typeof constraints.audio === 'object' && constraints.audio.source === AudioSourceInfo.SCREENCAST) { |
| | | return Promise.reject(new TypeError('Cannot share screen without video.')); |
| | | } |
| | | |
| | | if (isVideoConstrainsForScreenCast(constraints) && !isChrome() && !isFirefox()) { |
| | | return Promise.reject(new TypeError('Screen sharing only supports Chrome and Firefox.')); |
| | | } |
| | | |
| | | if (isVideoConstrainsForScreenCast(constraints) && typeof constraints.audio === 'object' && constraints.audio.source !== AudioSourceInfo.SCREENCAST) { |
| | | return Promise.reject(new TypeError('Cannot capture video from screen cast while capture audio from' + ' other source.')); |
| | | } // Check and convert constraints. |
| | | |
| | | |
| | | if (!constraints.audio && !constraints.video) { |
| | | return Promise.reject(new TypeError('At least one of audio and video must be requested.')); |
| | | } |
| | | |
| | | const mediaConstraints = Object.create({}); |
| | | |
| | | if (typeof constraints.audio === 'object' && constraints.audio.source === AudioSourceInfo.MIC) { |
| | | mediaConstraints.audio = Object.create({}); |
| | | |
| | | if (isEdge()) { |
| | | mediaConstraints.audio.deviceId = constraints.audio.deviceId; |
| | | } else { |
| | | mediaConstraints.audio.deviceId = { |
| | | exact: constraints.audio.deviceId |
| | | }; |
| | | } |
| | | } else { |
| | | if (constraints.audio.source === AudioSourceInfo.SCREENCAST) { |
| | | mediaConstraints.audio = true; |
| | | } else { |
| | | mediaConstraints.audio = constraints.audio; |
| | | } |
| | | } |
| | | |
| | | if (typeof constraints.video === 'object') { |
| | | mediaConstraints.video = Object.create({}); |
| | | |
| | | if (typeof constraints.video.frameRate === 'number') { |
| | | mediaConstraints.video.frameRate = constraints.video.frameRate; |
| | | } |
| | | |
| | | if (constraints.video.resolution && constraints.video.resolution.width && constraints.video.resolution.height) { |
| | | if (constraints.video.source === VideoSourceInfo.SCREENCAST) { |
| | | mediaConstraints.video.width = constraints.video.resolution.width; |
| | | mediaConstraints.video.height = constraints.video.resolution.height; |
| | | } else { |
| | | mediaConstraints.video.width = Object.create({}); |
| | | mediaConstraints.video.width.exact = constraints.video.resolution.width; |
| | | mediaConstraints.video.height = Object.create({}); |
| | | mediaConstraints.video.height.exact = constraints.video.resolution.height; |
| | | } |
| | | } |
| | | |
| | | if (typeof constraints.video.deviceId === 'string') { |
| | | mediaConstraints.video.deviceId = { |
| | | exact: constraints.video.deviceId |
| | | }; |
| | | } |
| | | |
| | | if (isFirefox() && constraints.video.source === VideoSourceInfo.SCREENCAST) { |
| | | mediaConstraints.video.mediaSource = 'screen'; |
| | | } |
| | | } else { |
| | | mediaConstraints.video = constraints.video; |
| | | } |
| | | |
| | | if (isVideoConstrainsForScreenCast(constraints)) { |
| | | return navigator.mediaDevices.getDisplayMedia(mediaConstraints); |
| | | } else { |
| | | return navigator.mediaDevices.getUserMedia(mediaConstraints); |
| | | } |
| | | } |
| | | |
| | | } |
| | | |
| | | // Copyright (C) <2018> Intel Corporation |
| | | |
| | | var media = /*#__PURE__*/Object.freeze({ |
| | | __proto__: null, |
| | | AudioTrackConstraints: AudioTrackConstraints, |
| | | VideoTrackConstraints: VideoTrackConstraints, |
| | | StreamConstraints: StreamConstraints, |
| | | MediaStreamFactory: MediaStreamFactory, |
| | | AudioSourceInfo: AudioSourceInfo, |
| | | VideoSourceInfo: VideoSourceInfo, |
| | | TrackKind: TrackKind, |
| | | Resolution: Resolution |
| | | }); |
| | | |
| | | let logger; |
| | | let errorLogger; |
| | | function setLogger() { |
| | | /*eslint-disable */ |
| | | logger = console.log; |
| | | errorLogger = console.error; |
| | | /*eslint-enable */ |
| | | } |
| | | function log(message, ...optionalParams) { |
| | | if (logger) { |
| | | logger(message, ...optionalParams); |
| | | } |
| | | } |
| | | function error(message, ...optionalParams) { |
| | | if (errorLogger) { |
| | | errorLogger(message, ...optionalParams); |
| | | } |
| | | } |
| | | |
| | | class Event$1 { |
| | | constructor(type) { |
| | | this.listener = {}; |
| | | this.type = type | ''; |
| | | } |
| | | |
| | | on(event, fn) { |
| | | if (!this.listener[event]) { |
| | | this.listener[event] = []; |
| | | } |
| | | |
| | | this.listener[event].push(fn); |
| | | return true; |
| | | } |
| | | |
| | | off(event, fn) { |
| | | if (this.listener[event]) { |
| | | var index = this.listener[event].indexOf(fn); |
| | | |
| | | if (index > -1) { |
| | | this.listener[event].splice(index, 1); |
| | | } |
| | | |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | offAll() { |
| | | this.listener = {}; |
| | | } |
| | | |
| | | dispatch(event, data) { |
| | | if (this.listener[event]) { |
| | | this.listener[event].map(each => { |
| | | each.apply(null, [data]); |
| | | }); |
| | | return true; |
| | | } |
| | | |
| | | return false; |
| | | } |
| | | |
| | | } |
| | | |
| | | var bind = function bind(fn, thisArg) { |
| | | return function wrap() { |
| | | var args = new Array(arguments.length); |
| | | for (var i = 0; i < args.length; i++) { |
| | | args[i] = arguments[i]; |
| | | } |
| | | return fn.apply(thisArg, args); |
| | | }; |
| | | }; |
| | | |
| | | /*global toString:true*/ |
| | | |
| | | // utils is a library of generic helper functions non-specific to axios |
| | | |
| | | var toString = Object.prototype.toString; |
| | | |
| | | /** |
| | | * Determine if a value is an Array |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an Array, otherwise false |
| | | */ |
| | | function isArray(val) { |
| | | return toString.call(val) === '[object Array]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is undefined |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if the value is undefined, otherwise false |
| | | */ |
| | | function isUndefined(val) { |
| | | return typeof val === 'undefined'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Buffer |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Buffer, otherwise false |
| | | */ |
| | | function isBuffer(val) { |
| | | return val !== null && !isUndefined(val) && val.constructor !== null && !isUndefined(val.constructor) |
| | | && typeof val.constructor.isBuffer === 'function' && val.constructor.isBuffer(val); |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is an ArrayBuffer |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an ArrayBuffer, otherwise false |
| | | */ |
| | | function isArrayBuffer(val) { |
| | | return toString.call(val) === '[object ArrayBuffer]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a FormData |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an FormData, otherwise false |
| | | */ |
| | | function isFormData(val) { |
| | | return (typeof FormData !== 'undefined') && (val instanceof FormData); |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a view on an ArrayBuffer |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false |
| | | */ |
| | | function isArrayBufferView(val) { |
| | | var result; |
| | | if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { |
| | | result = ArrayBuffer.isView(val); |
| | | } else { |
| | | result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a String |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a String, otherwise false |
| | | */ |
| | | function isString(val) { |
| | | return typeof val === 'string'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Number |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Number, otherwise false |
| | | */ |
| | | function isNumber(val) { |
| | | return typeof val === 'number'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is an Object |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is an Object, otherwise false |
| | | */ |
| | | function isObject(val) { |
| | | return val !== null && typeof val === 'object'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a plain Object |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @return {boolean} True if value is a plain Object, otherwise false |
| | | */ |
| | | function isPlainObject(val) { |
| | | if (toString.call(val) !== '[object Object]') { |
| | | return false; |
| | | } |
| | | |
| | | var prototype = Object.getPrototypeOf(val); |
| | | return prototype === null || prototype === Object.prototype; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Date |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Date, otherwise false |
| | | */ |
| | | function isDate(val) { |
| | | return toString.call(val) === '[object Date]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a File |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a File, otherwise false |
| | | */ |
| | | function isFile(val) { |
| | | return toString.call(val) === '[object File]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Blob |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Blob, otherwise false |
| | | */ |
| | | function isBlob(val) { |
| | | return toString.call(val) === '[object Blob]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Function |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Function, otherwise false |
| | | */ |
| | | function isFunction(val) { |
| | | return toString.call(val) === '[object Function]'; |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a Stream |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a Stream, otherwise false |
| | | */ |
| | | function isStream(val) { |
| | | return isObject(val) && isFunction(val.pipe); |
| | | } |
| | | |
| | | /** |
| | | * Determine if a value is a URLSearchParams object |
| | | * |
| | | * @param {Object} val The value to test |
| | | * @returns {boolean} True if value is a URLSearchParams object, otherwise false |
| | | */ |
| | | function isURLSearchParams(val) { |
| | | return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; |
| | | } |
| | | |
| | | /** |
| | | * Trim excess whitespace off the beginning and end of a string |
| | | * |
| | | * @param {String} str The String to trim |
| | | * @returns {String} The String freed of excess whitespace |
| | | */ |
| | | function trim(str) { |
| | | return str.replace(/^\s*/, '').replace(/\s*$/, ''); |
| | | } |
| | | |
| | | /** |
| | | * Determine if we're running in a standard browser environment |
| | | * |
| | | * This allows axios to run in a web worker, and react-native. |
| | | * Both environments support XMLHttpRequest, but not fully standard globals. |
| | | * |
| | | * web workers: |
| | | * typeof window -> undefined |
| | | * typeof document -> undefined |
| | | * |
| | | * react-native: |
| | | * navigator.product -> 'ReactNative' |
| | | * nativescript |
| | | * navigator.product -> 'NativeScript' or 'NS' |
| | | */ |
| | | function isStandardBrowserEnv() { |
| | | if (typeof navigator !== 'undefined' && (navigator.product === 'ReactNative' || |
| | | navigator.product === 'NativeScript' || |
| | | navigator.product === 'NS')) { |
| | | return false; |
| | | } |
| | | return ( |
| | | typeof window !== 'undefined' && |
| | | typeof document !== 'undefined' |
| | | ); |
| | | } |
| | | |
| | | /** |
| | | * Iterate over an Array or an Object invoking a function for each item. |
| | | * |
| | | * If `obj` is an Array callback will be called passing |
| | | * the value, index, and complete array for each item. |
| | | * |
| | | * If 'obj' is an Object callback will be called passing |
| | | * the value, key, and complete object for each property. |
| | | * |
| | | * @param {Object|Array} obj The object to iterate |
| | | * @param {Function} fn The callback to invoke for each item |
| | | */ |
| | | function forEach(obj, fn) { |
| | | // Don't bother if no value provided |
| | | if (obj === null || typeof obj === 'undefined') { |
| | | return; |
| | | } |
| | | |
| | | // Force an array if not already something iterable |
| | | if (typeof obj !== 'object') { |
| | | /*eslint no-param-reassign:0*/ |
| | | obj = [obj]; |
| | | } |
| | | |
| | | if (isArray(obj)) { |
| | | // Iterate over array values |
| | | for (var i = 0, l = obj.length; i < l; i++) { |
| | | fn.call(null, obj[i], i, obj); |
| | | } |
| | | } else { |
| | | // Iterate over object keys |
| | | for (var key in obj) { |
| | | if (Object.prototype.hasOwnProperty.call(obj, key)) { |
| | | fn.call(null, obj[key], key, obj); |
| | | } |
| | | } |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Accepts varargs expecting each argument to be an object, then |
| | | * immutably merges the properties of each object and returns result. |
| | | * |
| | | * When multiple objects contain the same key the later object in |
| | | * the arguments list will take precedence. |
| | | * |
| | | * Example: |
| | | * |
| | | * ```js |
| | | * var result = merge({foo: 123}, {foo: 456}); |
| | | * console.log(result.foo); // outputs 456 |
| | | * ``` |
| | | * |
| | | * @param {Object} obj1 Object to merge |
| | | * @returns {Object} Result of all merge properties |
| | | */ |
| | | function merge(/* obj1, obj2, obj3, ... */) { |
| | | var result = {}; |
| | | function assignValue(val, key) { |
| | | if (isPlainObject(result[key]) && isPlainObject(val)) { |
| | | result[key] = merge(result[key], val); |
| | | } else if (isPlainObject(val)) { |
| | | result[key] = merge({}, val); |
| | | } else if (isArray(val)) { |
| | | result[key] = val.slice(); |
| | | } else { |
| | | result[key] = val; |
| | | } |
| | | } |
| | | |
| | | for (var i = 0, l = arguments.length; i < l; i++) { |
| | | forEach(arguments[i], assignValue); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | /** |
| | | * Extends object a by mutably adding to it the properties of object b. |
| | | * |
| | | * @param {Object} a The object to be extended |
| | | * @param {Object} b The object to copy properties from |
| | | * @param {Object} thisArg The object to bind function to |
| | | * @return {Object} The resulting value of object a |
| | | */ |
| | | function extend(a, b, thisArg) { |
| | | forEach(b, function assignValue(val, key) { |
| | | if (thisArg && typeof val === 'function') { |
| | | a[key] = bind(val, thisArg); |
| | | } else { |
| | | a[key] = val; |
| | | } |
| | | }); |
| | | return a; |
| | | } |
| | | |
| | | /** |
| | | * Remove byte order marker. This catches EF BB BF (the UTF-8 BOM) |
| | | * |
| | | * @param {string} content with BOM |
| | | * @return {string} content value without BOM |
| | | */ |
| | | function stripBOM(content) { |
| | | if (content.charCodeAt(0) === 0xFEFF) { |
| | | content = content.slice(1); |
| | | } |
| | | return content; |
| | | } |
| | | |
| | | var utils = { |
| | | isArray: isArray, |
| | | isArrayBuffer: isArrayBuffer, |
| | | isBuffer: isBuffer, |
| | | isFormData: isFormData, |
| | | isArrayBufferView: isArrayBufferView, |
| | | isString: isString, |
| | | isNumber: isNumber, |
| | | isObject: isObject, |
| | | isPlainObject: isPlainObject, |
| | | isUndefined: isUndefined, |
| | | isDate: isDate, |
| | | isFile: isFile, |
| | | isBlob: isBlob, |
| | | isFunction: isFunction, |
| | | isStream: isStream, |
| | | isURLSearchParams: isURLSearchParams, |
| | | isStandardBrowserEnv: isStandardBrowserEnv, |
| | | forEach: forEach, |
| | | merge: merge, |
| | | extend: extend, |
| | | trim: trim, |
| | | stripBOM: stripBOM |
| | | }; |
| | | |
| | | function encode(val) { |
| | | return encodeURIComponent(val). |
| | | replace(/%3A/gi, ':'). |
| | | replace(/%24/g, '$'). |
| | | replace(/%2C/gi, ','). |
| | | replace(/%20/g, '+'). |
| | | replace(/%5B/gi, '['). |
| | | replace(/%5D/gi, ']'); |
| | | } |
| | | |
| | | /** |
| | | * Build a URL by appending params to the end |
| | | * |
| | | * @param {string} url The base of the url (e.g., http://www.google.com) |
| | | * @param {object} [params] The params to be appended |
| | | * @returns {string} The formatted url |
| | | */ |
| | | var buildURL = function buildURL(url, params, paramsSerializer) { |
| | | /*eslint no-param-reassign:0*/ |
| | | if (!params) { |
| | | return url; |
| | | } |
| | | |
| | | var serializedParams; |
| | | if (paramsSerializer) { |
| | | serializedParams = paramsSerializer(params); |
| | | } else if (utils.isURLSearchParams(params)) { |
| | | serializedParams = params.toString(); |
| | | } else { |
| | | var parts = []; |
| | | |
| | | utils.forEach(params, function serialize(val, key) { |
| | | if (val === null || typeof val === 'undefined') { |
| | | return; |
| | | } |
| | | |
| | | if (utils.isArray(val)) { |
| | | key = key + '[]'; |
| | | } else { |
| | | val = [val]; |
| | | } |
| | | |
| | | utils.forEach(val, function parseValue(v) { |
| | | if (utils.isDate(v)) { |
| | | v = v.toISOString(); |
| | | } else if (utils.isObject(v)) { |
| | | v = JSON.stringify(v); |
| | | } |
| | | parts.push(encode(key) + '=' + encode(v)); |
| | | }); |
| | | }); |
| | | |
| | | serializedParams = parts.join('&'); |
| | | } |
| | | |
| | | if (serializedParams) { |
| | | var hashmarkIndex = url.indexOf('#'); |
| | | if (hashmarkIndex !== -1) { |
| | | url = url.slice(0, hashmarkIndex); |
| | | } |
| | | |
| | | url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; |
| | | } |
| | | |
| | | return url; |
| | | }; |
| | | |
| | | function InterceptorManager() { |
| | | this.handlers = []; |
| | | } |
| | | |
| | | /** |
| | | * Add a new interceptor to the stack |
| | | * |
| | | * @param {Function} fulfilled The function to handle `then` for a `Promise` |
| | | * @param {Function} rejected The function to handle `reject` for a `Promise` |
| | | * |
| | | * @return {Number} An ID used to remove interceptor later |
| | | */ |
| | | InterceptorManager.prototype.use = function use(fulfilled, rejected) { |
| | | this.handlers.push({ |
| | | fulfilled: fulfilled, |
| | | rejected: rejected |
| | | }); |
| | | return this.handlers.length - 1; |
| | | }; |
| | | |
| | | /** |
| | | * Remove an interceptor from the stack |
| | | * |
| | | * @param {Number} id The ID that was returned by `use` |
| | | */ |
| | | InterceptorManager.prototype.eject = function eject(id) { |
| | | if (this.handlers[id]) { |
| | | this.handlers[id] = null; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * Iterate over all the registered interceptors |
| | | * |
| | | * This method is particularly useful for skipping over any |
| | | * interceptors that may have become `null` calling `eject`. |
| | | * |
| | | * @param {Function} fn The function to call for each interceptor |
| | | */ |
| | | InterceptorManager.prototype.forEach = function forEach(fn) { |
| | | utils.forEach(this.handlers, function forEachHandler(h) { |
| | | if (h !== null) { |
| | | fn(h); |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | var InterceptorManager_1 = InterceptorManager; |
| | | |
| | | /** |
| | | * Transform the data for a request or a response |
| | | * |
| | | * @param {Object|String} data The data to be transformed |
| | | * @param {Array} headers The headers for the request or response |
| | | * @param {Array|Function} fns A single function or Array of functions |
| | | * @returns {*} The resulting transformed data |
| | | */ |
| | | var transformData = function transformData(data, headers, fns) { |
| | | /*eslint no-param-reassign:0*/ |
| | | utils.forEach(fns, function transform(fn) { |
| | | data = fn(data, headers); |
| | | }); |
| | | |
| | | return data; |
| | | }; |
| | | |
| | | var isCancel = function isCancel(value) { |
| | | return !!(value && value.__CANCEL__); |
| | | }; |
| | | |
| | | var normalizeHeaderName = function normalizeHeaderName(headers, normalizedName) { |
| | | utils.forEach(headers, function processHeader(value, name) { |
| | | if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { |
| | | headers[normalizedName] = value; |
| | | delete headers[name]; |
| | | } |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * Update an Error with the specified config, error code, and response. |
| | | * |
| | | * @param {Error} error The error to update. |
| | | * @param {Object} config The config. |
| | | * @param {string} [code] The error code (for example, 'ECONNABORTED'). |
| | | * @param {Object} [request] The request. |
| | | * @param {Object} [response] The response. |
| | | * @returns {Error} The error. |
| | | */ |
| | | var enhanceError = function enhanceError(error, config, code, request, response) { |
| | | error.config = config; |
| | | if (code) { |
| | | error.code = code; |
| | | } |
| | | |
| | | error.request = request; |
| | | error.response = response; |
| | | error.isAxiosError = true; |
| | | |
| | | error.toJSON = function toJSON() { |
| | | return { |
| | | // Standard |
| | | message: this.message, |
| | | name: this.name, |
| | | // Microsoft |
| | | description: this.description, |
| | | number: this.number, |
| | | // Mozilla |
| | | fileName: this.fileName, |
| | | lineNumber: this.lineNumber, |
| | | columnNumber: this.columnNumber, |
| | | stack: this.stack, |
| | | // Axios |
| | | config: this.config, |
| | | code: this.code |
| | | }; |
| | | }; |
| | | return error; |
| | | }; |
| | | |
| | | /** |
| | | * Create an Error with the specified message, config, error code, request and response. |
| | | * |
| | | * @param {string} message The error message. |
| | | * @param {Object} config The config. |
| | | * @param {string} [code] The error code (for example, 'ECONNABORTED'). |
| | | * @param {Object} [request] The request. |
| | | * @param {Object} [response] The response. |
| | | * @returns {Error} The created error. |
| | | */ |
| | | var createError = function createError(message, config, code, request, response) { |
| | | var error = new Error(message); |
| | | return enhanceError(error, config, code, request, response); |
| | | }; |
| | | |
| | | /** |
| | | * Resolve or reject a Promise based on response status. |
| | | * |
| | | * @param {Function} resolve A function that resolves the promise. |
| | | * @param {Function} reject A function that rejects the promise. |
| | | * @param {object} response The response. |
| | | */ |
| | | var settle = function settle(resolve, reject, response) { |
| | | var validateStatus = response.config.validateStatus; |
| | | if (!response.status || !validateStatus || validateStatus(response.status)) { |
| | | resolve(response); |
| | | } else { |
| | | reject(createError( |
| | | 'Request failed with status code ' + response.status, |
| | | response.config, |
| | | null, |
| | | response.request, |
| | | response |
| | | )); |
| | | } |
| | | }; |
| | | |
| | | var cookies = ( |
| | | utils.isStandardBrowserEnv() ? |
| | | |
| | | // Standard browser envs support document.cookie |
| | | (function standardBrowserEnv() { |
| | | return { |
| | | write: function write(name, value, expires, path, domain, secure) { |
| | | var cookie = []; |
| | | cookie.push(name + '=' + encodeURIComponent(value)); |
| | | |
| | | if (utils.isNumber(expires)) { |
| | | cookie.push('expires=' + new Date(expires).toGMTString()); |
| | | } |
| | | |
| | | if (utils.isString(path)) { |
| | | cookie.push('path=' + path); |
| | | } |
| | | |
| | | if (utils.isString(domain)) { |
| | | cookie.push('domain=' + domain); |
| | | } |
| | | |
| | | if (secure === true) { |
| | | cookie.push('secure'); |
| | | } |
| | | |
| | | document.cookie = cookie.join('; '); |
| | | }, |
| | | |
| | | read: function read(name) { |
| | | var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); |
| | | return (match ? decodeURIComponent(match[3]) : null); |
| | | }, |
| | | |
| | | remove: function remove(name) { |
| | | this.write(name, '', Date.now() - 86400000); |
| | | } |
| | | }; |
| | | })() : |
| | | |
| | | // Non standard browser env (web workers, react-native) lack needed support. |
| | | (function nonStandardBrowserEnv() { |
| | | return { |
| | | write: function write() {}, |
| | | read: function read() { return null; }, |
| | | remove: function remove() {} |
| | | }; |
| | | })() |
| | | ); |
| | | |
| | | /** |
| | | * Determines whether the specified URL is absolute |
| | | * |
| | | * @param {string} url The URL to test |
| | | * @returns {boolean} True if the specified URL is absolute, otherwise false |
| | | */ |
| | | var isAbsoluteURL = function isAbsoluteURL(url) { |
| | | // A URL is considered absolute if it begins with "<scheme>://" or "//" (protocol-relative URL). |
| | | // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed |
| | | // by any combination of letters, digits, plus, period, or hyphen. |
| | | return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); |
| | | }; |
| | | |
| | | /** |
| | | * Creates a new URL by combining the specified URLs |
| | | * |
| | | * @param {string} baseURL The base URL |
| | | * @param {string} relativeURL The relative URL |
| | | * @returns {string} The combined URL |
| | | */ |
| | | var combineURLs = function combineURLs(baseURL, relativeURL) { |
| | | return relativeURL |
| | | ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') |
| | | : baseURL; |
| | | }; |
| | | |
| | | /** |
| | | * Creates a new URL by combining the baseURL with the requestedURL, |
| | | * only when the requestedURL is not already an absolute URL. |
| | | * If the requestURL is absolute, this function returns the requestedURL untouched. |
| | | * |
| | | * @param {string} baseURL The base URL |
| | | * @param {string} requestedURL Absolute or relative URL to combine |
| | | * @returns {string} The combined full path |
| | | */ |
| | | var buildFullPath = function buildFullPath(baseURL, requestedURL) { |
| | | if (baseURL && !isAbsoluteURL(requestedURL)) { |
| | | return combineURLs(baseURL, requestedURL); |
| | | } |
| | | return requestedURL; |
| | | }; |
| | | |
| | | // Headers whose duplicates are ignored by node |
| | | // c.f. https://nodejs.org/api/http.html#http_message_headers |
| | | var ignoreDuplicateOf = [ |
| | | 'age', 'authorization', 'content-length', 'content-type', 'etag', |
| | | 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', |
| | | 'last-modified', 'location', 'max-forwards', 'proxy-authorization', |
| | | 'referer', 'retry-after', 'user-agent' |
| | | ]; |
| | | |
| | | /** |
| | | * Parse headers into an object |
| | | * |
| | | * ``` |
| | | * Date: Wed, 27 Aug 2014 08:58:49 GMT |
| | | * Content-Type: application/json |
| | | * Connection: keep-alive |
| | | * Transfer-Encoding: chunked |
| | | * ``` |
| | | * |
| | | * @param {String} headers Headers needing to be parsed |
| | | * @returns {Object} Headers parsed into an object |
| | | */ |
| | | var parseHeaders = function parseHeaders(headers) { |
| | | var parsed = {}; |
| | | var key; |
| | | var val; |
| | | var i; |
| | | |
| | | if (!headers) { return parsed; } |
| | | |
| | | utils.forEach(headers.split('\n'), function parser(line) { |
| | | i = line.indexOf(':'); |
| | | key = utils.trim(line.substr(0, i)).toLowerCase(); |
| | | val = utils.trim(line.substr(i + 1)); |
| | | |
| | | if (key) { |
| | | if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { |
| | | return; |
| | | } |
| | | if (key === 'set-cookie') { |
| | | parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); |
| | | } else { |
| | | parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; |
| | | } |
| | | } |
| | | }); |
| | | |
| | | return parsed; |
| | | }; |
| | | |
| | | var isURLSameOrigin = ( |
| | | utils.isStandardBrowserEnv() ? |
| | | |
| | | // Standard browser envs have full support of the APIs needed to test |
| | | // whether the request URL is of the same origin as current location. |
| | | (function standardBrowserEnv() { |
| | | var msie = /(msie|trident)/i.test(navigator.userAgent); |
| | | var urlParsingNode = document.createElement('a'); |
| | | var originURL; |
| | | |
| | | /** |
| | | * Parse a URL to discover it's components |
| | | * |
| | | * @param {String} url The URL to be parsed |
| | | * @returns {Object} |
| | | */ |
| | | function resolveURL(url) { |
| | | var href = url; |
| | | |
| | | if (msie) { |
| | | // IE needs attribute set twice to normalize properties |
| | | urlParsingNode.setAttribute('href', href); |
| | | href = urlParsingNode.href; |
| | | } |
| | | |
| | | urlParsingNode.setAttribute('href', href); |
| | | |
| | | // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils |
| | | return { |
| | | href: urlParsingNode.href, |
| | | protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', |
| | | host: urlParsingNode.host, |
| | | search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', |
| | | hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', |
| | | hostname: urlParsingNode.hostname, |
| | | port: urlParsingNode.port, |
| | | pathname: (urlParsingNode.pathname.charAt(0) === '/') ? |
| | | urlParsingNode.pathname : |
| | | '/' + urlParsingNode.pathname |
| | | }; |
| | | } |
| | | |
| | | originURL = resolveURL(window.location.href); |
| | | |
| | | /** |
| | | * Determine if a URL shares the same origin as the current location |
| | | * |
| | | * @param {String} requestURL The URL to test |
| | | * @returns {boolean} True if URL shares the same origin, otherwise false |
| | | */ |
| | | return function isURLSameOrigin(requestURL) { |
| | | var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; |
| | | return (parsed.protocol === originURL.protocol && |
| | | parsed.host === originURL.host); |
| | | }; |
| | | })() : |
| | | |
| | | // Non standard browser envs (web workers, react-native) lack needed support. |
| | | (function nonStandardBrowserEnv() { |
| | | return function isURLSameOrigin() { |
| | | return true; |
| | | }; |
| | | })() |
| | | ); |
| | | |
| | | var xhr = function xhrAdapter(config) { |
| | | return new Promise(function dispatchXhrRequest(resolve, reject) { |
| | | var requestData = config.data; |
| | | var requestHeaders = config.headers; |
| | | |
| | | if (utils.isFormData(requestData)) { |
| | | delete requestHeaders['Content-Type']; // Let the browser set it |
| | | } |
| | | |
| | | var request = new XMLHttpRequest(); |
| | | |
| | | // HTTP basic authentication |
| | | if (config.auth) { |
| | | var username = config.auth.username || ''; |
| | | var password = config.auth.password ? unescape(encodeURIComponent(config.auth.password)) : ''; |
| | | requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); |
| | | } |
| | | |
| | | var fullPath = buildFullPath(config.baseURL, config.url); |
| | | request.open(config.method.toUpperCase(), buildURL(fullPath, config.params, config.paramsSerializer), true); |
| | | |
| | | // Set the request timeout in MS |
| | | request.timeout = config.timeout; |
| | | |
| | | // Listen for ready state |
| | | request.onreadystatechange = function handleLoad() { |
| | | if (!request || request.readyState !== 4) { |
| | | return; |
| | | } |
| | | |
| | | // The request errored out and we didn't get a response, this will be |
| | | // handled by onerror instead |
| | | // With one exception: request that using file: protocol, most browsers |
| | | // will return status as 0 even though it's a successful request |
| | | if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { |
| | | return; |
| | | } |
| | | |
| | | // Prepare the response |
| | | var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; |
| | | var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; |
| | | var response = { |
| | | data: responseData, |
| | | status: request.status, |
| | | statusText: request.statusText, |
| | | headers: responseHeaders, |
| | | config: config, |
| | | request: request |
| | | }; |
| | | |
| | | settle(resolve, reject, response); |
| | | |
| | | // Clean up request |
| | | request = null; |
| | | }; |
| | | |
| | | // Handle browser request cancellation (as opposed to a manual cancellation) |
| | | request.onabort = function handleAbort() { |
| | | if (!request) { |
| | | return; |
| | | } |
| | | |
| | | reject(createError('Request aborted', config, 'ECONNABORTED', request)); |
| | | |
| | | // Clean up request |
| | | request = null; |
| | | }; |
| | | |
| | | // Handle low level network errors |
| | | request.onerror = function handleError() { |
| | | // Real errors are hidden from us by the browser |
| | | // onerror should only fire if it's a network error |
| | | reject(createError('Network Error', config, null, request)); |
| | | |
| | | // Clean up request |
| | | request = null; |
| | | }; |
| | | |
| | | // Handle timeout |
| | | request.ontimeout = function handleTimeout() { |
| | | var timeoutErrorMessage = 'timeout of ' + config.timeout + 'ms exceeded'; |
| | | if (config.timeoutErrorMessage) { |
| | | timeoutErrorMessage = config.timeoutErrorMessage; |
| | | } |
| | | reject(createError(timeoutErrorMessage, config, 'ECONNABORTED', |
| | | request)); |
| | | |
| | | // Clean up request |
| | | request = null; |
| | | }; |
| | | |
| | | // Add xsrf header |
| | | // This is only done if running in a standard browser environment. |
| | | // Specifically not if we're in a web worker, or react-native. |
| | | if (utils.isStandardBrowserEnv()) { |
| | | // Add xsrf header |
| | | var xsrfValue = (config.withCredentials || isURLSameOrigin(fullPath)) && config.xsrfCookieName ? |
| | | cookies.read(config.xsrfCookieName) : |
| | | undefined; |
| | | |
| | | if (xsrfValue) { |
| | | requestHeaders[config.xsrfHeaderName] = xsrfValue; |
| | | } |
| | | } |
| | | |
| | | // Add headers to the request |
| | | if ('setRequestHeader' in request) { |
| | | utils.forEach(requestHeaders, function setRequestHeader(val, key) { |
| | | if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { |
| | | // Remove Content-Type if data is undefined |
| | | delete requestHeaders[key]; |
| | | } else { |
| | | // Otherwise add header to the request |
| | | request.setRequestHeader(key, val); |
| | | } |
| | | }); |
| | | } |
| | | |
| | | // Add withCredentials to request if needed |
| | | if (!utils.isUndefined(config.withCredentials)) { |
| | | request.withCredentials = !!config.withCredentials; |
| | | } |
| | | |
| | | // Add responseType to request if needed |
| | | if (config.responseType) { |
| | | try { |
| | | request.responseType = config.responseType; |
| | | } catch (e) { |
| | | // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. |
| | | // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. |
| | | if (config.responseType !== 'json') { |
| | | throw e; |
| | | } |
| | | } |
| | | } |
| | | |
| | | // Handle progress if needed |
| | | if (typeof config.onDownloadProgress === 'function') { |
| | | request.addEventListener('progress', config.onDownloadProgress); |
| | | } |
| | | |
| | | // Not all browsers support upload events |
| | | if (typeof config.onUploadProgress === 'function' && request.upload) { |
| | | request.upload.addEventListener('progress', config.onUploadProgress); |
| | | } |
| | | |
| | | if (config.cancelToken) { |
| | | // Handle cancellation |
| | | config.cancelToken.promise.then(function onCanceled(cancel) { |
| | | if (!request) { |
| | | return; |
| | | } |
| | | |
| | | request.abort(); |
| | | reject(cancel); |
| | | // Clean up request |
| | | request = null; |
| | | }); |
| | | } |
| | | |
| | | if (!requestData) { |
| | | requestData = null; |
| | | } |
| | | |
| | | // Send the request |
| | | request.send(requestData); |
| | | }); |
| | | }; |
| | | |
| | | var DEFAULT_CONTENT_TYPE = { |
| | | 'Content-Type': 'application/x-www-form-urlencoded' |
| | | }; |
| | | |
| | | function setContentTypeIfUnset(headers, value) { |
| | | if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { |
| | | headers['Content-Type'] = value; |
| | | } |
| | | } |
| | | |
| | | function getDefaultAdapter() { |
| | | var adapter; |
| | | if (typeof XMLHttpRequest !== 'undefined') { |
| | | // For browsers use XHR adapter |
| | | adapter = xhr; |
| | | } else if (typeof process !== 'undefined' && Object.prototype.toString.call(process) === '[object process]') { |
| | | // For node use HTTP adapter |
| | | adapter = xhr; |
| | | } |
| | | return adapter; |
| | | } |
| | | |
| | | var defaults = { |
| | | adapter: getDefaultAdapter(), |
| | | |
| | | transformRequest: [function transformRequest(data, headers) { |
| | | normalizeHeaderName(headers, 'Accept'); |
| | | normalizeHeaderName(headers, 'Content-Type'); |
| | | if (utils.isFormData(data) || |
| | | utils.isArrayBuffer(data) || |
| | | utils.isBuffer(data) || |
| | | utils.isStream(data) || |
| | | utils.isFile(data) || |
| | | utils.isBlob(data) |
| | | ) { |
| | | return data; |
| | | } |
| | | if (utils.isArrayBufferView(data)) { |
| | | return data.buffer; |
| | | } |
| | | if (utils.isURLSearchParams(data)) { |
| | | setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); |
| | | return data.toString(); |
| | | } |
| | | if (utils.isObject(data)) { |
| | | setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); |
| | | return JSON.stringify(data); |
| | | } |
| | | return data; |
| | | }], |
| | | |
| | | transformResponse: [function transformResponse(data) { |
| | | /*eslint no-param-reassign:0*/ |
| | | if (typeof data === 'string') { |
| | | try { |
| | | data = JSON.parse(data); |
| | | } catch (e) { /* Ignore */ } |
| | | } |
| | | return data; |
| | | }], |
| | | |
| | | /** |
| | | * A timeout in milliseconds to abort a request. If set to 0 (default) a |
| | | * timeout is not created. |
| | | */ |
| | | timeout: 0, |
| | | |
| | | xsrfCookieName: 'XSRF-TOKEN', |
| | | xsrfHeaderName: 'X-XSRF-TOKEN', |
| | | |
| | | maxContentLength: -1, |
| | | maxBodyLength: -1, |
| | | |
| | | validateStatus: function validateStatus(status) { |
| | | return status >= 200 && status < 300; |
| | | } |
| | | }; |
| | | |
| | | defaults.headers = { |
| | | common: { |
| | | 'Accept': 'application/json, text/plain, */*' |
| | | } |
| | | }; |
| | | |
| | | utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { |
| | | defaults.headers[method] = {}; |
| | | }); |
| | | |
| | | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { |
| | | defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); |
| | | }); |
| | | |
| | | var defaults_1 = defaults; |
| | | |
| | | /** |
| | | * Throws a `Cancel` if cancellation has been requested. |
| | | */ |
| | | function throwIfCancellationRequested(config) { |
| | | if (config.cancelToken) { |
| | | config.cancelToken.throwIfRequested(); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Dispatch a request to the server using the configured adapter. |
| | | * |
| | | * @param {object} config The config that is to be used for the request |
| | | * @returns {Promise} The Promise to be fulfilled |
| | | */ |
| | | var dispatchRequest = function dispatchRequest(config) { |
| | | throwIfCancellationRequested(config); |
| | | |
| | | // Ensure headers exist |
| | | config.headers = config.headers || {}; |
| | | |
| | | // Transform request data |
| | | config.data = transformData( |
| | | config.data, |
| | | config.headers, |
| | | config.transformRequest |
| | | ); |
| | | |
| | | // Flatten headers |
| | | config.headers = utils.merge( |
| | | config.headers.common || {}, |
| | | config.headers[config.method] || {}, |
| | | config.headers |
| | | ); |
| | | |
| | | utils.forEach( |
| | | ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], |
| | | function cleanHeaderConfig(method) { |
| | | delete config.headers[method]; |
| | | } |
| | | ); |
| | | |
| | | var adapter = config.adapter || defaults_1.adapter; |
| | | |
| | | return adapter(config).then(function onAdapterResolution(response) { |
| | | throwIfCancellationRequested(config); |
| | | |
| | | // Transform response data |
| | | response.data = transformData( |
| | | response.data, |
| | | response.headers, |
| | | config.transformResponse |
| | | ); |
| | | |
| | | return response; |
| | | }, function onAdapterRejection(reason) { |
| | | if (!isCancel(reason)) { |
| | | throwIfCancellationRequested(config); |
| | | |
| | | // Transform response data |
| | | if (reason && reason.response) { |
| | | reason.response.data = transformData( |
| | | reason.response.data, |
| | | reason.response.headers, |
| | | config.transformResponse |
| | | ); |
| | | } |
| | | } |
| | | |
| | | return Promise.reject(reason); |
| | | }); |
| | | }; |
| | | |
| | | /** |
| | | * Config-specific merge-function which creates a new config-object |
| | | * by merging two configuration objects together. |
| | | * |
| | | * @param {Object} config1 |
| | | * @param {Object} config2 |
| | | * @returns {Object} New object resulting from merging config2 to config1 |
| | | */ |
| | | var mergeConfig = function mergeConfig(config1, config2) { |
| | | // eslint-disable-next-line no-param-reassign |
| | | config2 = config2 || {}; |
| | | var config = {}; |
| | | |
| | | var valueFromConfig2Keys = ['url', 'method', 'data']; |
| | | var mergeDeepPropertiesKeys = ['headers', 'auth', 'proxy', 'params']; |
| | | var defaultToConfig2Keys = [ |
| | | 'baseURL', 'transformRequest', 'transformResponse', 'paramsSerializer', |
| | | 'timeout', 'timeoutMessage', 'withCredentials', 'adapter', 'responseType', 'xsrfCookieName', |
| | | 'xsrfHeaderName', 'onUploadProgress', 'onDownloadProgress', 'decompress', |
| | | 'maxContentLength', 'maxBodyLength', 'maxRedirects', 'transport', 'httpAgent', |
| | | 'httpsAgent', 'cancelToken', 'socketPath', 'responseEncoding' |
| | | ]; |
| | | var directMergeKeys = ['validateStatus']; |
| | | |
| | | function getMergedValue(target, source) { |
| | | if (utils.isPlainObject(target) && utils.isPlainObject(source)) { |
| | | return utils.merge(target, source); |
| | | } else if (utils.isPlainObject(source)) { |
| | | return utils.merge({}, source); |
| | | } else if (utils.isArray(source)) { |
| | | return source.slice(); |
| | | } |
| | | return source; |
| | | } |
| | | |
| | | function mergeDeepProperties(prop) { |
| | | if (!utils.isUndefined(config2[prop])) { |
| | | config[prop] = getMergedValue(config1[prop], config2[prop]); |
| | | } else if (!utils.isUndefined(config1[prop])) { |
| | | config[prop] = getMergedValue(undefined, config1[prop]); |
| | | } |
| | | } |
| | | |
| | | utils.forEach(valueFromConfig2Keys, function valueFromConfig2(prop) { |
| | | if (!utils.isUndefined(config2[prop])) { |
| | | config[prop] = getMergedValue(undefined, config2[prop]); |
| | | } |
| | | }); |
| | | |
| | | utils.forEach(mergeDeepPropertiesKeys, mergeDeepProperties); |
| | | |
| | | utils.forEach(defaultToConfig2Keys, function defaultToConfig2(prop) { |
| | | if (!utils.isUndefined(config2[prop])) { |
| | | config[prop] = getMergedValue(undefined, config2[prop]); |
| | | } else if (!utils.isUndefined(config1[prop])) { |
| | | config[prop] = getMergedValue(undefined, config1[prop]); |
| | | } |
| | | }); |
| | | |
| | | utils.forEach(directMergeKeys, function merge(prop) { |
| | | if (prop in config2) { |
| | | config[prop] = getMergedValue(config1[prop], config2[prop]); |
| | | } else if (prop in config1) { |
| | | config[prop] = getMergedValue(undefined, config1[prop]); |
| | | } |
| | | }); |
| | | |
| | | var axiosKeys = valueFromConfig2Keys |
| | | .concat(mergeDeepPropertiesKeys) |
| | | .concat(defaultToConfig2Keys) |
| | | .concat(directMergeKeys); |
| | | |
| | | var otherKeys = Object |
| | | .keys(config1) |
| | | .concat(Object.keys(config2)) |
| | | .filter(function filterAxiosKeys(key) { |
| | | return axiosKeys.indexOf(key) === -1; |
| | | }); |
| | | |
| | | utils.forEach(otherKeys, mergeDeepProperties); |
| | | |
| | | return config; |
| | | }; |
| | | |
| | | /** |
| | | * Create a new instance of Axios |
| | | * |
| | | * @param {Object} instanceConfig The default config for the instance |
| | | */ |
| | | function Axios(instanceConfig) { |
| | | this.defaults = instanceConfig; |
| | | this.interceptors = { |
| | | request: new InterceptorManager_1(), |
| | | response: new InterceptorManager_1() |
| | | }; |
| | | } |
| | | |
| | | /** |
| | | * Dispatch a request |
| | | * |
| | | * @param {Object} config The config specific for this request (merged with this.defaults) |
| | | */ |
| | | Axios.prototype.request = function request(config) { |
| | | /*eslint no-param-reassign:0*/ |
| | | // Allow for axios('example/url'[, config]) a la fetch API |
| | | if (typeof config === 'string') { |
| | | config = arguments[1] || {}; |
| | | config.url = arguments[0]; |
| | | } else { |
| | | config = config || {}; |
| | | } |
| | | |
| | | config = mergeConfig(this.defaults, config); |
| | | |
| | | // Set config.method |
| | | if (config.method) { |
| | | config.method = config.method.toLowerCase(); |
| | | } else if (this.defaults.method) { |
| | | config.method = this.defaults.method.toLowerCase(); |
| | | } else { |
| | | config.method = 'get'; |
| | | } |
| | | |
| | | // Hook up interceptors middleware |
| | | var chain = [dispatchRequest, undefined]; |
| | | var promise = Promise.resolve(config); |
| | | |
| | | this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { |
| | | chain.unshift(interceptor.fulfilled, interceptor.rejected); |
| | | }); |
| | | |
| | | this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { |
| | | chain.push(interceptor.fulfilled, interceptor.rejected); |
| | | }); |
| | | |
| | | while (chain.length) { |
| | | promise = promise.then(chain.shift(), chain.shift()); |
| | | } |
| | | |
| | | return promise; |
| | | }; |
| | | |
| | | Axios.prototype.getUri = function getUri(config) { |
| | | config = mergeConfig(this.defaults, config); |
| | | return buildURL(config.url, config.params, config.paramsSerializer).replace(/^\?/, ''); |
| | | }; |
| | | |
| | | // Provide aliases for supported request methods |
| | | utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { |
| | | /*eslint func-names:0*/ |
| | | Axios.prototype[method] = function(url, config) { |
| | | return this.request(mergeConfig(config || {}, { |
| | | method: method, |
| | | url: url, |
| | | data: (config || {}).data |
| | | })); |
| | | }; |
| | | }); |
| | | |
| | | utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { |
| | | /*eslint func-names:0*/ |
| | | Axios.prototype[method] = function(url, data, config) { |
| | | return this.request(mergeConfig(config || {}, { |
| | | method: method, |
| | | url: url, |
| | | data: data |
| | | })); |
| | | }; |
| | | }); |
| | | |
| | | var Axios_1 = Axios; |
| | | |
| | | /** |
| | | * A `Cancel` is an object that is thrown when an operation is canceled. |
| | | * |
| | | * @class |
| | | * @param {string=} message The message. |
| | | */ |
| | | function Cancel(message) { |
| | | this.message = message; |
| | | } |
| | | |
| | | Cancel.prototype.toString = function toString() { |
| | | return 'Cancel' + (this.message ? ': ' + this.message : ''); |
| | | }; |
| | | |
| | | Cancel.prototype.__CANCEL__ = true; |
| | | |
| | | var Cancel_1 = Cancel; |
| | | |
| | | /** |
| | | * A `CancelToken` is an object that can be used to request cancellation of an operation. |
| | | * |
| | | * @class |
| | | * @param {Function} executor The executor function. |
| | | */ |
| | | function CancelToken(executor) { |
| | | if (typeof executor !== 'function') { |
| | | throw new TypeError('executor must be a function.'); |
| | | } |
| | | |
| | | var resolvePromise; |
| | | this.promise = new Promise(function promiseExecutor(resolve) { |
| | | resolvePromise = resolve; |
| | | }); |
| | | |
| | | var token = this; |
| | | executor(function cancel(message) { |
| | | if (token.reason) { |
| | | // Cancellation has already been requested |
| | | return; |
| | | } |
| | | |
| | | token.reason = new Cancel_1(message); |
| | | resolvePromise(token.reason); |
| | | }); |
| | | } |
| | | |
| | | /** |
| | | * Throws a `Cancel` if cancellation has been requested. |
| | | */ |
| | | CancelToken.prototype.throwIfRequested = function throwIfRequested() { |
| | | if (this.reason) { |
| | | throw this.reason; |
| | | } |
| | | }; |
| | | |
| | | /** |
| | | * Returns an object that contains a new `CancelToken` and a function that, when called, |
| | | * cancels the `CancelToken`. |
| | | */ |
| | | CancelToken.source = function source() { |
| | | var cancel; |
| | | var token = new CancelToken(function executor(c) { |
| | | cancel = c; |
| | | }); |
| | | return { |
| | | token: token, |
| | | cancel: cancel |
| | | }; |
| | | }; |
| | | |
| | | var CancelToken_1 = CancelToken; |
| | | |
| | | /** |
| | | * Syntactic sugar for invoking a function and expanding an array for arguments. |
| | | * |
| | | * Common use case would be to use `Function.prototype.apply`. |
| | | * |
| | | * ```js |
| | | * function f(x, y, z) {} |
| | | * var args = [1, 2, 3]; |
| | | * f.apply(null, args); |
| | | * ``` |
| | | * |
| | | * With `spread` this example can be re-written. |
| | | * |
| | | * ```js |
| | | * spread(function(x, y, z) {})([1, 2, 3]); |
| | | * ``` |
| | | * |
| | | * @param {Function} callback |
| | | * @returns {Function} |
| | | */ |
| | | var spread = function spread(callback) { |
| | | return function wrap(arr) { |
| | | return callback.apply(null, arr); |
| | | }; |
| | | }; |
| | | |
| | | /** |
| | | * Determines whether the payload is an error thrown by Axios |
| | | * |
| | | * @param {*} payload The value to test |
| | | * @returns {boolean} True if the payload is an error thrown by Axios, otherwise false |
| | | */ |
| | | var isAxiosError = function isAxiosError(payload) { |
| | | return (typeof payload === 'object') && (payload.isAxiosError === true); |
| | | }; |
| | | |
| | | /** |
| | | * Create an instance of Axios |
| | | * |
| | | * @param {Object} defaultConfig The default config for the instance |
| | | * @return {Axios} A new instance of Axios |
| | | */ |
| | | function createInstance(defaultConfig) { |
| | | var context = new Axios_1(defaultConfig); |
| | | var instance = bind(Axios_1.prototype.request, context); |
| | | |
| | | // Copy axios.prototype to instance |
| | | utils.extend(instance, Axios_1.prototype, context); |
| | | |
| | | // Copy context to instance |
| | | utils.extend(instance, context); |
| | | |
| | | return instance; |
| | | } |
| | | |
| | | // Create the default instance to be exported |
| | | var axios$1 = createInstance(defaults_1); |
| | | |
| | | // Expose Axios class to allow class inheritance |
| | | axios$1.Axios = Axios_1; |
| | | |
| | | // Factory for creating new instances |
| | | axios$1.create = function create(instanceConfig) { |
| | | return createInstance(mergeConfig(axios$1.defaults, instanceConfig)); |
| | | }; |
| | | |
| | | // Expose Cancel & CancelToken |
| | | axios$1.Cancel = Cancel_1; |
| | | axios$1.CancelToken = CancelToken_1; |
| | | axios$1.isCancel = isCancel; |
| | | |
| | | // Expose all/spread |
| | | axios$1.all = function all(promises) { |
| | | return Promise.all(promises); |
| | | }; |
| | | axios$1.spread = spread; |
| | | |
| | | // Expose isAxiosError |
| | | axios$1.isAxiosError = isAxiosError; |
| | | |
| | | var axios_1 = axios$1; |
| | | |
| | | // Allow use of default import syntax in TypeScript |
| | | var _default = axios$1; |
| | | axios_1.default = _default; |
| | | |
| | | var axios = axios_1; |
| | | |
| | | class RTCEndpoint extends Event$1 { |
| | | constructor(options) { |
| | | super('RTCPusherPlayer'); |
| | | this.TAG = '[RTCPusherPlayer]'; |
| | | let defaults = { |
| | | element: '', |
| | | // html video element |
| | | debug: false, |
| | | // if output debug log |
| | | zlmsdpUrl: '', |
| | | simulcast: false, |
| | | useCamera: true, |
| | | audioEnable: true, |
| | | videoEnable: true, |
| | | recvOnly: false, |
| | | resolution: { |
| | | w: 0, |
| | | h: 0 |
| | | }, |
| | | usedatachannel: false |
| | | }; |
| | | this.options = Object.assign({}, defaults, options); |
| | | |
| | | if (this.options.debug) { |
| | | setLogger(); |
| | | } |
| | | |
| | | this.e = { |
| | | onicecandidate: this._onIceCandidate.bind(this), |
| | | ontrack: this._onTrack.bind(this), |
| | | onicecandidateerror: this._onIceCandidateError.bind(this), |
| | | onconnectionstatechange: this._onconnectionstatechange.bind(this), |
| | | ondatachannelopen: this._onDataChannelOpen.bind(this), |
| | | ondatachannelmsg: this._onDataChannelMsg.bind(this), |
| | | ondatachannelerr: this._onDataChannelErr.bind(this), |
| | | ondatachannelclose: this._onDataChannelClose.bind(this) |
| | | }; |
| | | this._remoteStream = null; |
| | | this._localStream = null; |
| | | this.pc = new RTCPeerConnection(null); |
| | | this.pc.onicecandidate = this.e.onicecandidate; |
| | | this.pc.onicecandidateerror = this.e.onicecandidateerror; |
| | | this.pc.ontrack = this.e.ontrack; |
| | | this.pc.onconnectionstatechange = this.e.onconnectionstatechange; |
| | | this.datachannel = null; |
| | | |
| | | if (this.options.usedatachannel) { |
| | | this.datachannel = this.pc.createDataChannel('chat'); |
| | | this.datachannel.onclose = this.e.ondatachannelclose; |
| | | this.datachannel.onerror = this.e.ondatachannelerr; |
| | | this.datachannel.onmessage = this.e.ondatachannelmsg; |
| | | this.datachannel.onopen = this.e.ondatachannelopen; |
| | | } |
| | | |
| | | if (!this.options.recvOnly && (this.options.audioEnable || this.options.videoEnable)) this.start();else this.receive(); |
| | | } |
| | | |
| | | receive() { |
| | | |
| | | const AudioTransceiverInit = { |
| | | direction: 'recvonly', |
| | | sendEncodings: [] |
| | | }; |
| | | const VideoTransceiverInit = { |
| | | direction: 'recvonly', |
| | | sendEncodings: [] |
| | | }; |
| | | this.pc.addTransceiver('audio', AudioTransceiverInit); |
| | | this.pc.addTransceiver('video', VideoTransceiverInit); |
| | | this.pc.createOffer().then(desc => { |
| | | log(this.TAG, 'offer:', desc.sdp); |
| | | this.pc.setLocalDescription(desc).then(() => { |
| | | axios({ |
| | | method: 'post', |
| | | url: this.options.zlmsdpUrl, |
| | | responseType: 'json', |
| | | data: desc.sdp, |
| | | headers: { |
| | | 'Content-Type': 'text/plain;charset=utf-8' |
| | | } |
| | | }).then(response => { |
| | | let ret = response.data; //JSON.parse(response.data); |
| | | |
| | | if (ret.code != 0) { |
| | | // mean failed for offer/anwser exchange |
| | | this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret); |
| | | return; |
| | | } |
| | | |
| | | let anwser = {}; |
| | | anwser.sdp = ret.sdp; |
| | | anwser.type = 'answer'; |
| | | log(this.TAG, 'answer:', ret.sdp); |
| | | this.pc.setRemoteDescription(anwser).then(() => { |
| | | log(this.TAG, 'set remote sucess'); |
| | | }).catch(e => { |
| | | error(this.TAG, e); |
| | | }); |
| | | }); |
| | | }); |
| | | }).catch(e => { |
| | | error(this.TAG, e); |
| | | }); |
| | | } |
| | | |
| | | start() { |
| | | let videoConstraints = false; |
| | | let audioConstraints = false; |
| | | |
| | | if (this.options.useCamera) { |
| | | if (this.options.videoEnable) videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); |
| | | if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.MIC); |
| | | } else { |
| | | if (this.options.videoEnable) { |
| | | videoConstraints = new VideoTrackConstraints(VideoSourceInfo.SCREENCAST); |
| | | if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.SCREENCAST); |
| | | } else { |
| | | if (this.options.audioEnable) audioConstraints = new AudioTrackConstraints(AudioSourceInfo.MIC);else { |
| | | // error shared display media not only audio |
| | | error(this.TAG, 'error paramter'); |
| | | } |
| | | } |
| | | } |
| | | |
| | | if (this.options.resolution.w != 0 && this.options.resolution.h != 0 && typeof videoConstraints == 'object') { |
| | | videoConstraints.resolution = new Resolution(this.options.resolution.w, this.options.resolution.h); |
| | | } |
| | | |
| | | MediaStreamFactory.createMediaStream(new StreamConstraints(audioConstraints, videoConstraints)).then(stream => { |
| | | this._localStream = stream; |
| | | this.dispatch(Events$1.WEBRTC_ON_LOCAL_STREAM, stream); |
| | | const AudioTransceiverInit = { |
| | | direction: 'sendrecv', |
| | | sendEncodings: [] |
| | | }; |
| | | const VideoTransceiverInit = { |
| | | direction: 'sendrecv', |
| | | sendEncodings: [] |
| | | }; |
| | | |
| | | if (this.options.simulcast && stream.getVideoTracks().length > 0) { |
| | | VideoTransceiverInit.sendEncodings = [{ |
| | | rid: 'h', |
| | | active: true, |
| | | maxBitrate: 1000000 |
| | | }, { |
| | | rid: 'm', |
| | | active: true, |
| | | maxBitrate: 500000, |
| | | scaleResolutionDownBy: 2 |
| | | }, { |
| | | rid: 'l', |
| | | active: true, |
| | | maxBitrate: 200000, |
| | | scaleResolutionDownBy: 4 |
| | | }]; |
| | | } |
| | | |
| | | if (this.options.audioEnable) { |
| | | if (stream.getAudioTracks().length > 0) { |
| | | this.pc.addTransceiver(stream.getAudioTracks()[0], AudioTransceiverInit); |
| | | } else { |
| | | AudioTransceiverInit.direction = 'recvonly'; |
| | | this.pc.addTransceiver('audio', AudioTransceiverInit); |
| | | } |
| | | } |
| | | |
| | | if (this.options.videoEnable) { |
| | | if (stream.getVideoTracks().length > 0) { |
| | | this.pc.addTransceiver(stream.getVideoTracks()[0], VideoTransceiverInit); |
| | | } else { |
| | | VideoTransceiverInit.direction = 'recvonly'; |
| | | this.pc.addTransceiver('video', VideoTransceiverInit); |
| | | } |
| | | } |
| | | /* |
| | | stream.getTracks().forEach((track,idx)=>{ |
| | | debug.log(this.TAG,track); |
| | | this.pc.addTrack(track); |
| | | }); |
| | | */ |
| | | |
| | | |
| | | this.pc.createOffer().then(desc => { |
| | | log(this.TAG, 'offer:', desc.sdp); |
| | | this.pc.setLocalDescription(desc).then(() => { |
| | | axios({ |
| | | method: 'post', |
| | | url: this.options.zlmsdpUrl, |
| | | responseType: 'json', |
| | | data: desc.sdp, |
| | | headers: { |
| | | 'Content-Type': 'text/plain;charset=utf-8' |
| | | } |
| | | }).then(response => { |
| | | let ret = response.data; //JSON.parse(response.data); |
| | | |
| | | if (ret.code != 0) { |
| | | // mean failed for offer/anwser exchange |
| | | this.dispatch(Events$1.WEBRTC_OFFER_ANWSER_EXCHANGE_FAILED, ret); |
| | | return; |
| | | } |
| | | |
| | | let anwser = {}; |
| | | anwser.sdp = ret.sdp; |
| | | anwser.type = 'answer'; |
| | | log(this.TAG, 'answer:', ret.sdp); |
| | | this.pc.setRemoteDescription(anwser).then(() => { |
| | | log(this.TAG, 'set remote sucess'); |
| | | }).catch(e => { |
| | | error(this.TAG, e); |
| | | }); |
| | | }); |
| | | }); |
| | | }).catch(e => { |
| | | error(this.TAG, e); |
| | | }); |
| | | }).catch(e => { |
| | | this.dispatch(Events$1.CAPTURE_STREAM_FAILED); //debug.error(this.TAG,e); |
| | | }); //const offerOptions = {}; |
| | | |
| | | /* |
| | | if (typeof this.pc.addTransceiver === 'function') { |
| | | // |direction| seems not working on Safari. |
| | | this.pc.addTransceiver('audio', { direction: 'recvonly' }); |
| | | this.pc.addTransceiver('video', { direction: 'recvonly' }); |
| | | } else { |
| | | offerOptions.offerToReceiveAudio = true; |
| | | offerOptions.offerToReceiveVideo = true; |
| | | } |
| | | */ |
| | | } |
| | | |
| | | _onIceCandidate(event) { |
| | | if (event.candidate) { |
| | | log('Remote ICE candidate: \n ' + event.candidate.candidate); // Send the candidate to the remote peer |
| | | } |
| | | } |
| | | |
| | | _onTrack(event) { |
| | | if (this.options.element && event.streams && event.streams.length > 0) { |
| | | this.options.element.srcObject = event.streams[0]; |
| | | this._remoteStream = event.streams[0]; |
| | | this.dispatch(Events$1.WEBRTC_ON_REMOTE_STREAMS, event); |
| | | } else { |
| | | error('element pararm is failed'); |
| | | } |
| | | } |
| | | |
| | | _onIceCandidateError(event) { |
| | | this.dispatch(Events$1.WEBRTC_ICE_CANDIDATE_ERROR, event); |
| | | } |
| | | |
| | | _onconnectionstatechange(event) { |
| | | this.dispatch(Events$1.WEBRTC_ON_CONNECTION_STATE_CHANGE, this.pc.connectionState); |
| | | } |
| | | |
| | | _onDataChannelOpen(event) { |
| | | log(this.TAG, 'ondatachannel open:', event); |
| | | this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_OPEN, event); |
| | | } |
| | | |
| | | _onDataChannelMsg(event) { |
| | | log(this.TAG, 'ondatachannel msg:', event); |
| | | this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_MSG, event); |
| | | } |
| | | |
| | | _onDataChannelErr(event) { |
| | | log(this.TAG, 'ondatachannel err:', event); |
| | | this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_ERR, event); |
| | | } |
| | | |
| | | _onDataChannelClose(event) { |
| | | log(this.TAG, 'ondatachannel close:', event); |
| | | this.dispatch(Events$1.WEBRTC_ON_DATA_CHANNEL_CLOSE, event); |
| | | } |
| | | |
| | | sendMsg(data) { |
| | | if (this.datachannel != null) { |
| | | this.datachannel.send(data); |
| | | } else { |
| | | error(this.TAG, 'data channel is null'); |
| | | } |
| | | } |
| | | |
| | | closeDataChannel() { |
| | | if (this.datachannel) { |
| | | this.datachannel.close(); |
| | | this.datachannel = null; |
| | | } |
| | | } |
| | | |
| | | close() { |
| | | this.closeDataChannel(); |
| | | |
| | | if (this.pc) { |
| | | this.pc.close(); |
| | | this.pc = null; |
| | | } |
| | | |
| | | if (this.options) { |
| | | this.options = null; |
| | | } |
| | | |
| | | if (this._localStream) { |
| | | this._localStream.getTracks().forEach((track, idx) => { |
| | | track.stop(); |
| | | }); |
| | | } |
| | | |
| | | if (this._remoteStream) { |
| | | this._remoteStream.getTracks().forEach((track, idx) => { |
| | | track.stop(); |
| | | }); |
| | | } |
| | | } |
| | | |
| | | get remoteStream() { |
| | | return this._remoteStream; |
| | | } |
| | | |
| | | get localStream() { |
| | | return this._localStream; |
| | | } |
| | | |
| | | } |
| | | |
| | | const quickScan = [{ |
| | | 'label': '4K(UHD)', |
| | | 'width': 3840, |
| | | 'height': 2160 |
| | | }, { |
| | | 'label': '1080p(FHD)', |
| | | 'width': 1920, |
| | | 'height': 1080 |
| | | }, { |
| | | 'label': 'UXGA', |
| | | 'width': 1600, |
| | | 'height': 1200, |
| | | 'ratio': '4:3' |
| | | }, { |
| | | 'label': '720p(HD)', |
| | | 'width': 1280, |
| | | 'height': 720 |
| | | }, { |
| | | 'label': 'SVGA', |
| | | 'width': 800, |
| | | 'height': 600 |
| | | }, { |
| | | 'label': 'VGA', |
| | | 'width': 640, |
| | | 'height': 480 |
| | | }, { |
| | | 'label': '360p(nHD)', |
| | | 'width': 640, |
| | | 'height': 360 |
| | | }, { |
| | | 'label': 'CIF', |
| | | 'width': 352, |
| | | 'height': 288 |
| | | }, { |
| | | 'label': 'QVGA', |
| | | 'width': 320, |
| | | 'height': 240 |
| | | }, { |
| | | 'label': 'QCIF', |
| | | 'width': 176, |
| | | 'height': 144 |
| | | }, { |
| | | 'label': 'QQVGA', |
| | | 'width': 160, |
| | | 'height': 120 |
| | | }]; |
| | | function GetSupportCameraResolutions$1() { |
| | | return new Promise(function (resolve, reject) { |
| | | let resolutions = []; |
| | | let ok = 0; |
| | | let err = 0; |
| | | |
| | | for (let i = 0; i < quickScan.length; ++i) { |
| | | let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); |
| | | videoConstraints.resolution = new Resolution(quickScan[i].width, quickScan[i].height); |
| | | MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => { |
| | | resolutions.push(quickScan[i]); |
| | | ok++; |
| | | |
| | | if (ok + err == quickScan.length) { |
| | | resolve(resolutions); |
| | | } |
| | | }).catch(e => { |
| | | err++; |
| | | |
| | | if (ok + err == quickScan.length) { |
| | | resolve(resolutions); |
| | | } |
| | | }); |
| | | } |
| | | }); |
| | | } |
| | | function GetAllScanResolution$1() { |
| | | return quickScan; |
| | | } |
| | | function isSupportResolution$1(w, h) { |
| | | return new Promise(function (resolve, reject) { |
| | | let videoConstraints = new VideoTrackConstraints(VideoSourceInfo.CAMERA); |
| | | videoConstraints.resolution = new Resolution(w, h); |
| | | MediaStreamFactory.createMediaStream(new StreamConstraints(false, videoConstraints)).then(stream => { |
| | | resolve(); |
| | | }).catch(e => { |
| | | reject(e); |
| | | }); |
| | | }); |
| | | } |
| | | |
| | | console.log('build date:', BUILD_DATE); |
| | | console.log('version:', VERSION); |
| | | const Events = Events$1; |
| | | const Media = media; |
| | | const Endpoint = RTCEndpoint; |
| | | const GetSupportCameraResolutions = GetSupportCameraResolutions$1; |
| | | const GetAllScanResolution = GetAllScanResolution$1; |
| | | const isSupportResolution = isSupportResolution$1; |
| | | |
| | | exports.Endpoint = Endpoint; |
| | | exports.Events = Events; |
| | | exports.GetAllScanResolution = GetAllScanResolution; |
| | | exports.GetSupportCameraResolutions = GetSupportCameraResolutions; |
| | | exports.Media = Media; |
| | | exports.isSupportResolution = isSupportResolution; |
| | | |
| | | Object.defineProperty(exports, '__esModule', { value: true }); |
| | | |
| | | return exports; |
| | | |
| | | }({})); |
| | | //# sourceMappingURL=ZLMRTCClient.js.map |