You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.6 KiB
188 lines
6.6 KiB
/*
|
|
* Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
|
|
*
|
|
* Use of this source code is governed by a BSD-style license
|
|
* that can be found in the LICENSE file in the root of the source
|
|
* tree.
|
|
*/
|
|
/* eslint-env node */
|
|
'use strict';
|
|
import * as utils from '../utils.js';
|
|
const logging = utils.log;
|
|
|
|
export function shimGetUserMedia(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))));
|
|
};
|
|
}
|
|
}
|
|
|