getusermedia.js 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189
  1. /*
  2. * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree.
  7. */
  8. /* eslint-env node */
  9. 'use strict';
  10. import * as utils from '../utils.js';
  11. const logging = utils.log;
  12. export function shimGetUserMedia(window, browserDetails) {
  13. const navigator = window && window.navigator;
  14. if (!navigator.mediaDevices) {
  15. return;
  16. }
  17. const constraintsToChrome_ = function(c) {
  18. if (typeof c !== 'object' || c.mandatory || c.optional) {
  19. return c;
  20. }
  21. const cc = {};
  22. Object.keys(c).forEach(key => {
  23. if (key === 'require' || key === 'advanced' || key === 'mediaSource') {
  24. return;
  25. }
  26. const r = (typeof c[key] === 'object') ? c[key] : {ideal: c[key]};
  27. if (r.exact !== undefined && typeof r.exact === 'number') {
  28. r.min = r.max = r.exact;
  29. }
  30. const oldname_ = function(prefix, name) {
  31. if (prefix) {
  32. return prefix + name.charAt(0).toUpperCase() + name.slice(1);
  33. }
  34. return (name === 'deviceId') ? 'sourceId' : name;
  35. };
  36. if (r.ideal !== undefined) {
  37. cc.optional = cc.optional || [];
  38. let oc = {};
  39. if (typeof r.ideal === 'number') {
  40. oc[oldname_('min', key)] = r.ideal;
  41. cc.optional.push(oc);
  42. oc = {};
  43. oc[oldname_('max', key)] = r.ideal;
  44. cc.optional.push(oc);
  45. } else {
  46. oc[oldname_('', key)] = r.ideal;
  47. cc.optional.push(oc);
  48. }
  49. }
  50. if (r.exact !== undefined && typeof r.exact !== 'number') {
  51. cc.mandatory = cc.mandatory || {};
  52. cc.mandatory[oldname_('', key)] = r.exact;
  53. } else {
  54. ['min', 'max'].forEach(mix => {
  55. if (r[mix] !== undefined) {
  56. cc.mandatory = cc.mandatory || {};
  57. cc.mandatory[oldname_(mix, key)] = r[mix];
  58. }
  59. });
  60. }
  61. });
  62. if (c.advanced) {
  63. cc.optional = (cc.optional || []).concat(c.advanced);
  64. }
  65. return cc;
  66. };
  67. const shimConstraints_ = function(constraints, func) {
  68. if (browserDetails.version >= 61) {
  69. return func(constraints);
  70. }
  71. constraints = JSON.parse(JSON.stringify(constraints));
  72. if (constraints && typeof constraints.audio === 'object') {
  73. const remap = function(obj, a, b) {
  74. if (a in obj && !(b in obj)) {
  75. obj[b] = obj[a];
  76. delete obj[a];
  77. }
  78. };
  79. constraints = JSON.parse(JSON.stringify(constraints));
  80. remap(constraints.audio, 'autoGainControl', 'googAutoGainControl');
  81. remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression');
  82. constraints.audio = constraintsToChrome_(constraints.audio);
  83. }
  84. if (constraints && typeof constraints.video === 'object') {
  85. // Shim facingMode for mobile & surface pro.
  86. let face = constraints.video.facingMode;
  87. face = face && ((typeof face === 'object') ? face : {ideal: face});
  88. const getSupportedFacingModeLies = browserDetails.version < 66;
  89. if ((face && (face.exact === 'user' || face.exact === 'environment' ||
  90. face.ideal === 'user' || face.ideal === 'environment')) &&
  91. !(navigator.mediaDevices.getSupportedConstraints &&
  92. navigator.mediaDevices.getSupportedConstraints().facingMode &&
  93. !getSupportedFacingModeLies)) {
  94. delete constraints.video.facingMode;
  95. let matches;
  96. if (face.exact === 'environment' || face.ideal === 'environment') {
  97. matches = ['back', 'rear'];
  98. } else if (face.exact === 'user' || face.ideal === 'user') {
  99. matches = ['front'];
  100. }
  101. if (matches) {
  102. // Look for matches in label, or use last cam for back (typical).
  103. return navigator.mediaDevices.enumerateDevices()
  104. .then(devices => {
  105. devices = devices.filter(d => d.kind === 'videoinput');
  106. let dev = devices.find(d => matches.some(match =>
  107. d.label.toLowerCase().includes(match)));
  108. if (!dev && devices.length && matches.includes('back')) {
  109. dev = devices[devices.length - 1]; // more likely the back cam
  110. }
  111. if (dev) {
  112. constraints.video.deviceId = face.exact
  113. ? {exact: dev.deviceId}
  114. : {ideal: dev.deviceId};
  115. }
  116. constraints.video = constraintsToChrome_(constraints.video);
  117. logging('chrome: ' + JSON.stringify(constraints));
  118. return func(constraints);
  119. });
  120. }
  121. }
  122. constraints.video = constraintsToChrome_(constraints.video);
  123. }
  124. logging('chrome: ' + JSON.stringify(constraints));
  125. return func(constraints);
  126. };
  127. const shimError_ = function(e) {
  128. if (browserDetails.version >= 64) {
  129. return e;
  130. }
  131. return {
  132. name: {
  133. PermissionDeniedError: 'NotAllowedError',
  134. PermissionDismissedError: 'NotAllowedError',
  135. InvalidStateError: 'NotAllowedError',
  136. DevicesNotFoundError: 'NotFoundError',
  137. ConstraintNotSatisfiedError: 'OverconstrainedError',
  138. TrackStartError: 'NotReadableError',
  139. MediaDeviceFailedDueToShutdown: 'NotAllowedError',
  140. MediaDeviceKillSwitchOn: 'NotAllowedError',
  141. TabCaptureError: 'AbortError',
  142. ScreenCaptureError: 'AbortError',
  143. DeviceCaptureError: 'AbortError'
  144. }[e.name] || e.name,
  145. message: e.message,
  146. constraint: e.constraint || e.constraintName,
  147. toString() {
  148. return this.name + (this.message && ': ') + this.message;
  149. }
  150. };
  151. };
  152. const getUserMedia_ = function(constraints, onSuccess, onError) {
  153. shimConstraints_(constraints, c => {
  154. navigator.webkitGetUserMedia(c, onSuccess, e => {
  155. if (onError) {
  156. onError(shimError_(e));
  157. }
  158. });
  159. });
  160. };
  161. navigator.getUserMedia = getUserMedia_.bind(navigator);
  162. // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia
  163. // function which returns a Promise, it does not accept spec-style
  164. // constraints.
  165. if (navigator.mediaDevices.getUserMedia) {
  166. const origGetUserMedia = navigator.mediaDevices.getUserMedia.
  167. bind(navigator.mediaDevices);
  168. navigator.mediaDevices.getUserMedia = function(cs) {
  169. return shimConstraints_(cs, c => origGetUserMedia(c).then(stream => {
  170. if (c.audio && !stream.getAudioTracks().length ||
  171. c.video && !stream.getVideoTracks().length) {
  172. stream.getTracks().forEach(track => {
  173. track.stop();
  174. });
  175. throw new DOMException('', 'NotFoundError');
  176. }
  177. return stream;
  178. }, e => Promise.reject(shimError_(e))));
  179. };
  180. }
  181. }