imagecapture.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. /**
  2. * MediaStream ImageCapture polyfill
  3. *
  4. * @license
  5. * Copyright 2018 Google Inc.
  6. *
  7. * Licensed under the Apache License, Version 2.0 (the "License");
  8. * you may not use this file except in compliance with the License.
  9. * You may obtain a copy of the License at
  10. *
  11. * http://www.apache.org/licenses/LICENSE-2.0
  12. *
  13. * Unless required by applicable law or agreed to in writing, software
  14. * distributed under the License is distributed on an "AS IS" BASIS,
  15. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  16. * See the License for the specific language governing permissions and
  17. * limitations under the License.
  18. */
  19. export let ImageCapture = window.ImageCapture;
  20. if (typeof ImageCapture === 'undefined') {
  21. ImageCapture = class {
  22. /**
  23. * TODO https://www.w3.org/TR/image-capture/#constructors
  24. *
  25. * @param {MediaStreamTrack} videoStreamTrack - A MediaStreamTrack of the 'video' kind
  26. */
  27. constructor(videoStreamTrack) {
  28. if (videoStreamTrack.kind !== 'video')
  29. throw new DOMException('NotSupportedError');
  30. this._videoStreamTrack = videoStreamTrack;
  31. if (!('readyState' in this._videoStreamTrack)) {
  32. // Polyfill for Firefox
  33. this._videoStreamTrack.readyState = 'live';
  34. }
  35. // MediaStream constructor not available until Chrome 55 - https://www.chromestatus.com/feature/5912172546752512
  36. this._previewStream = new MediaStream([videoStreamTrack]);
  37. this.videoElement = document.createElement('video');
  38. this.videoElementPlaying = new Promise(resolve => {
  39. this.videoElement.addEventListener('playing', resolve);
  40. });
  41. if (HTMLMediaElement) {
  42. this.videoElement.srcObject = this._previewStream; // Safari 11 doesn't allow use of createObjectURL for MediaStream
  43. }
  44. else {
  45. this.videoElement.src = URL.createObjectURL(this._previewStream);
  46. }
  47. this.videoElement.muted = true;
  48. this.videoElement.setAttribute('playsinline', ''); // Required by Safari on iOS 11. See https://webkit.org/blog/6784
  49. this.videoElement.play();
  50. this.canvasElement = document.createElement('canvas');
  51. // TODO Firefox has https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas
  52. this.canvas2dContext = this.canvasElement.getContext('2d');
  53. }
  54. /**
  55. * https://w3c.github.io/mediacapture-image/index.html#dom-imagecapture-videostreamtrack
  56. * @return {MediaStreamTrack} The MediaStreamTrack passed into the constructor
  57. */
  58. get videoStreamTrack() {
  59. return this._videoStreamTrack;
  60. }
  61. /**
  62. * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-getphotocapabilities
  63. * @return {Promise<PhotoCapabilities>} Fulfilled promise with
  64. * [PhotoCapabilities](https://www.w3.org/TR/image-capture/#idl-def-photocapabilities)
  65. * object on success, rejected promise on failure
  66. */
  67. getPhotoCapabilities() {
  68. return new Promise(function executorGPC(resolve, reject) {
  69. // TODO see https://github.com/w3c/mediacapture-image/issues/97
  70. const MediaSettingsRange = {
  71. current: 0, min: 0, max: 0,
  72. };
  73. resolve({
  74. exposureCompensation: MediaSettingsRange,
  75. exposureMode: 'none',
  76. fillLightMode: ['none'],
  77. focusMode: 'none',
  78. imageHeight: MediaSettingsRange,
  79. imageWidth: MediaSettingsRange,
  80. iso: MediaSettingsRange,
  81. redEyeReduction: false,
  82. whiteBalanceMode: 'none',
  83. zoom: MediaSettingsRange,
  84. });
  85. reject(new DOMException('OperationError'));
  86. });
  87. }
  88. /**
  89. * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-setoptions
  90. * @param {Object} photoSettings - Photo settings dictionary, https://www.w3.org/TR/image-capture/#idl-def-photosettings
  91. * @return {Promise<void>} Fulfilled promise on success, rejected promise on failure
  92. */
  93. setOptions(_photoSettings = {}) {
  94. return new Promise(function executorSO(_resolve, _reject) {
  95. // TODO
  96. });
  97. }
  98. /**
  99. * TODO
  100. * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-takephoto
  101. * @return {Promise<Blob>} Fulfilled promise with [Blob](https://www.w3.org/TR/FileAPI/#blob)
  102. * argument on success; rejected promise on failure
  103. */
  104. takePhoto() {
  105. const self = this;
  106. return new Promise(function executorTP(resolve, reject) {
  107. // `If the readyState of the MediaStreamTrack provided in the constructor is not live,
  108. // return a promise rejected with a new DOMException whose name is "InvalidStateError".`
  109. if (self._videoStreamTrack.readyState !== 'live') {
  110. return reject(new DOMException('InvalidStateError'));
  111. }
  112. self.videoElementPlaying.then(() => {
  113. try {
  114. self.canvasElement.width = self.videoElement.videoWidth;
  115. self.canvasElement.height = self.videoElement.videoHeight;
  116. self.canvas2dContext.drawImage(self.videoElement, 0, 0);
  117. self.canvasElement.toBlob(resolve);
  118. }
  119. catch (error) {
  120. reject(new DOMException('UnknownError'));
  121. }
  122. });
  123. });
  124. }
  125. /**
  126. * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-grabframe
  127. * @return {Promise<ImageBitmap>} Fulfilled promise with
  128. * [ImageBitmap](https://www.w3.org/TR/html51/webappapis.html#webappapis-images)
  129. * argument on success; rejected promise on failure
  130. */
  131. grabFrame() {
  132. const self = this;
  133. return new Promise(function executorGF(resolve, reject) {
  134. // `If the readyState of the MediaStreamTrack provided in the constructor is not live,
  135. // return a promise rejected with a new DOMException whose name is "InvalidStateError".`
  136. if (self._videoStreamTrack.readyState !== 'live') {
  137. return reject(new DOMException('InvalidStateError'));
  138. }
  139. self.videoElementPlaying.then(() => {
  140. try {
  141. self.canvasElement.width = self.videoElement.videoWidth;
  142. self.canvasElement.height = self.videoElement.videoHeight;
  143. self.canvas2dContext.drawImage(self.videoElement, 0, 0);
  144. // TODO polyfill https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmapFactories/createImageBitmap for IE
  145. resolve(window.createImageBitmap(self.canvasElement));
  146. }
  147. catch (error) {
  148. reject(new DOMException('UnknownError'));
  149. }
  150. });
  151. });
  152. }
  153. };
  154. }
  155. window.ImageCapture = ImageCapture;