From 18e7db21ac97d350c9c51f7502d2d0822d8bf2ae Mon Sep 17 00:00:00 2001 From: Dan Dascalescu Date: Wed, 10 Jan 2018 15:15:33 -0800 Subject: [PATCH] 2018 update. Firefox and Safari 11 on iOS compatibility. All PRs in. --- .eslintignore | 1 + .eslintrc.js | 30 ++++---- .travis.yml | 2 + README.md | 4 +- docs/bundle.js | 168 +++++++++++++++++++++++--------------------- docs/index.css | 2 +- docs/index.js | 14 ++-- package.json | 20 +++--- src/imagecapture.js | 14 ++-- webpack.config.js | 6 +- 10 files changed, 138 insertions(+), 123 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..e4261de --- /dev/null +++ b/.eslintignore @@ -0,0 +1 @@ +docs/bundle.js diff --git a/.eslintrc.js b/.eslintrc.js index 4791bcc..482d5c8 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -1,13 +1,13 @@ module.exports = { - 'env': { - 'browser': true, - 'es6': true + env: { + browser: true, + es6: true }, - 'parserOptions': { - 'sourceType': 'module', + parserOptions: { + sourceType: 'module', }, - 'extends': 'google', - 'rules': { + extends: 'google', + rules: { 'max-len': [ 'warn', { code: 130 } // 130 on GitHub, 80 on npmjs.org for README.md code blocks ], @@ -15,17 +15,17 @@ module.exports = { 'space-before-function-paren': [ 'error', { - 'anonymous': 'always', - 'named': 'never' + anonymous: 'always', + named: 'never' } ], 'no-negated-condition': 'warn', - 'spaced-comment': ['error', 'always', { 'exceptions': ['/'] }] + 'spaced-comment': ['error', 'always', { exceptions: ['/'] }] }, - 'globals': { - 'DOMException': false, - 'MediaStream': false, - 'Polymer': false, - 'URL': false, + globals: { + DOMException: false, + MediaStream: false, + Polymer: false, + URL: false, } }; diff --git a/.travis.yml b/.travis.yml index 2c4c96e..68c2f52 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,7 @@ language: node_js node_js: + - '9' + - '8' - '7' - '6' - '5' diff --git a/README.md b/README.md index 35bea82..017807f 100644 --- a/README.md +++ b/README.md @@ -151,11 +151,13 @@ yarn yarn run dev ``` -## npm (slower) +## npm ```sh npm install npm run dev ``` + +To [make your server accessible outside of `localhost`](https://www.npmjs.com/package/localtunnel), run npm/yarn `run lt`. Before committing, make sure you pass yarn/npm `run lint` without errors, and run yarn/npm `run docs` to generate the demo. diff --git a/docs/bundle.js b/docs/bundle.js index f20a4e1..4c0d8c1 100644 --- a/docs/bundle.js +++ b/docs/bundle.js @@ -1,50 +1,52 @@ /******/ (function(modules) { // webpackBootstrap /******/ // The module cache /******/ var installedModules = {}; - +/******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { - +/******/ /******/ // Check if module is in cache -/******/ if(installedModules[moduleId]) +/******/ if(installedModules[moduleId]) { /******/ return installedModules[moduleId].exports; - +/******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = installedModules[moduleId] = { /******/ i: moduleId, /******/ l: false, /******/ exports: {} /******/ }; - +/******/ /******/ // Execute the module function /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); - +/******/ /******/ // Flag the module as loaded /******/ module.l = true; - +/******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } - - +/******/ +/******/ /******/ // expose the modules object (__webpack_modules__) /******/ __webpack_require__.m = modules; - +/******/ /******/ // expose the module cache /******/ __webpack_require__.c = installedModules; - -/******/ // identity function for calling harmory imports with the correct context +/******/ +/******/ // identity function for calling harmony imports with the correct context /******/ __webpack_require__.i = function(value) { return value; }; - -/******/ // define getter function for harmory exports +/******/ +/******/ // define getter function for harmony exports /******/ __webpack_require__.d = function(exports, name, getter) { -/******/ Object.defineProperty(exports, name, { -/******/ configurable: false, -/******/ enumerable: true, -/******/ get: getter -/******/ }); +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } /******/ }; - +/******/ /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = function(module) { /******/ var getter = module && module.__esModule ? @@ -53,28 +55,28 @@ /******/ __webpack_require__.d(getter, 'a', getter); /******/ return getter; /******/ }; - +/******/ /******/ // Object.prototype.hasOwnProperty.call /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; - +/******/ /******/ // __webpack_public_path__ /******/ __webpack_require__.p = ""; - +/******/ /******/ // Load entry module and return exports /******/ return __webpack_require__(__webpack_require__.s = 1); /******/ }) /************************************************************************/ /******/ ([ /* 0 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; -/* harmony export (binding) */ __webpack_require__.d(exports, "a", function() { return ImageCapture; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "a", function() { return ImageCapture; }); /** * MediaStream ImageCapture polyfill * * @license - * Copyright 2017 Google Inc. + * Copyright 2018 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,9 +114,17 @@ if (typeof ImageCapture === 'undefined') { // MediaStream constructor not available until Chrome 55 - https://www.chromestatus.com/feature/5912172546752512 this._previewStream = new MediaStream([videoStreamTrack]); this.videoElement = document.createElement('video'); - this.videoElement.src = URL.createObjectURL(this._previewStream); + this.videoElementPlaying = new Promise(resolve => { + this.videoElement.addEventListener('playing', resolve); + }); + if (HTMLMediaElement) { + this.videoElement.srcObject = this._previewStream; // Safari 11 doesn't allow use of createObjectURL for MediaStream + } else { + this.videoElement.src = URL.createObjectURL(this._previewStream); + } this.videoElement.muted = true; - this.videoElement.play(); // required by Firefox + this.videoElement.setAttribute('playsinline', ''); // Required by Safari on iOS 11. See https://webkit.org/blog/6784 + this.videoElement.play(); this.canvasElement = document.createElement('canvas'); // TODO Firefox has https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvas @@ -131,12 +141,14 @@ if (typeof ImageCapture === 'undefined') { /** * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-getphotocapabilities - * @return {Promise} Fulfilled promise with [PhotoCapabilities](https://www.w3.org/TR/image-capture/#idl-def-photocapabilities) object on success, rejected promise on failure + * @return {Promise} Fulfilled promise with + * [PhotoCapabilities](https://www.w3.org/TR/image-capture/#idl-def-photocapabilities) + * object on success, rejected promise on failure */ getPhotoCapabilities() { return new Promise(function executorGPC(resolve, reject) { // TODO see https://github.com/w3c/mediacapture-image/issues/97 - let MediaSettingsRange = { + const MediaSettingsRange = { current: 0, min: 0, max: 0, }; resolve({ @@ -169,75 +181,71 @@ if (typeof ImageCapture === 'undefined') { /** * TODO * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-takephoto - * @return {Promise} Fulfilled promise with [Blob](https://www.w3.org/TR/FileAPI/#blob) argument on success; rejected promise on failure + * @return {Promise} Fulfilled promise with [Blob](https://www.w3.org/TR/FileAPI/#blob) + * argument on success; rejected promise on failure */ takePhoto() { - let self = this; + const self = this; return new Promise(function executorTP(resolve, reject) { // `If the readyState of the MediaStreamTrack provided in the constructor is not live, // return a promise rejected with a new DOMException whose name is "InvalidStateError".` - if (self._videoStreamTrack.readyState === 'live') { - // -- however, checking for `live` alone doesn't guarantee the video is ready - if (self.videoElement.videoWidth) { - try { - self.canvasElement.width = self.videoElement.videoWidth; - self.canvasElement.height = self.videoElement.videoHeight; - self.canvas2dContext.drawImage(self.videoElement, 0, 0); - self.canvasElement.toBlob(blob => { - resolve(blob); - }); - } catch (error) { - reject(new DOMException('UnknownError')); - } - } else { + if (self._videoStreamTrack.readyState !== 'live') { + return reject(new DOMException('InvalidStateError')); + } + self.videoElementPlaying.then(() => { + try { + self.canvasElement.width = self.videoElement.videoWidth; + self.canvasElement.height = self.videoElement.videoHeight; + self.canvas2dContext.drawImage(self.videoElement, 0, 0); + self.canvasElement.toBlob(resolve); + } catch (error) { reject(new DOMException('UnknownError')); } - } else { - reject(new DOMException('InvalidStateError')); - } + }); }); } /** * Implements https://www.w3.org/TR/image-capture/#dom-imagecapture-grabframe - * @return {Promise} Fulfilled promise with [ImageBitmap](https://www.w3.org/TR/html51/webappapis.html#webappapis-images) argument on success; rejected promise on failure + * @return {Promise} Fulfilled promise with + * [ImageBitmap](https://www.w3.org/TR/html51/webappapis.html#webappapis-images) + * argument on success; rejected promise on failure */ grabFrame() { - let self = this; + const self = this; return new Promise(function executorGF(resolve, reject) { - if (self._videoStreamTrack.readyState === 'live') { - if (self.videoElement.videoWidth) { - try { - // videoWidth is available after videoElement.onloadedmetadata fires - self.canvasElement.width = self.videoElement.videoWidth; - self.canvasElement.height = self.videoElement.videoHeight; - // The video has an image after videoElement.oncanplay triggers - self.canvas2dContext.drawImage(self.videoElement, 0, 0); - // TODO polyfill https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmapFactories/createImageBitmap for IE - resolve(window.createImageBitmap(self.canvasElement)); - } catch (error) { - reject(new DOMException('UnknownError')); - } - } else { + // `If the readyState of the MediaStreamTrack provided in the constructor is not live, + // return a promise rejected with a new DOMException whose name is "InvalidStateError".` + if (self._videoStreamTrack.readyState !== 'live') { + return reject(new DOMException('InvalidStateError')); + } + self.videoElementPlaying.then(() => { + try { + self.canvasElement.width = self.videoElement.videoWidth; + self.canvasElement.height = self.videoElement.videoHeight; + self.canvas2dContext.drawImage(self.videoElement, 0, 0); + // TODO polyfill https://developer.mozilla.org/en-US/docs/Web/API/ImageBitmapFactories/createImageBitmap for IE + resolve(window.createImageBitmap(self.canvasElement)); + } catch (error) { reject(new DOMException('UnknownError')); } - } else { - reject(new DOMException('InvalidStateError')); - } + }); }); } - }; } +window.ImageCapture = ImageCapture; + -/***/ }, +/***/ }), /* 1 */ -/***/ function(module, exports, __webpack_require__) { +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; +Object.defineProperty(__webpack_exports__, "__esModule", { value: true }); /* harmony import */ var __WEBPACK_IMPORTED_MODULE_0__src_imagecapture__ = __webpack_require__(0); -// Copyright 2016 Google Inc. +// Copyright 2017 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -255,9 +263,9 @@ if (typeof ImageCapture === 'undefined') { // Demo for the MediaStream ImageCapture polyfill -'use strict'; -let logElement = document.querySelector('#log'); + +const logElement = document.querySelector('#log'); /** * Log messages to the #log element and the console @@ -265,7 +273,7 @@ let logElement = document.querySelector('#log'); */ function log(...messages) { console.log(...messages); - let p = document.createElement('p'); + const p = document.createElement('p'); p.innerText = messages.join(' '); logElement.appendChild(p); } @@ -276,7 +284,7 @@ function log(...messages) { */ function err(...messages) { console.error(...messages); - let p = document.createElement('p'); + const p = document.createElement('p'); p.innerText = messages.join(' '); p.style = 'color: red'; logElement.appendChild(p); @@ -285,9 +293,9 @@ function err(...messages) { let interval; -let canvas = document.getElementById('frame'); +const canvas = document.getElementById('frame'); -let photo = document.getElementById('photo'); +const photo = document.getElementById('photo'); photo.addEventListener('load', function () { // After the image loads, discard the image object to release the memory window.URL.revokeObjectURL(photo.src); @@ -310,7 +318,7 @@ function gotMedia(mediaStream) { videoDevice = mediaStream.getVideoTracks()[0]; log('Using camera', videoDevice.label); - let captureDevice = new __WEBPACK_IMPORTED_MODULE_0__src_imagecapture__["a" /* ImageCapture */](videoDevice, mediaStream); + const captureDevice = new __WEBPACK_IMPORTED_MODULE_0__src_imagecapture__["a" /* ImageCapture */](videoDevice, mediaStream); interval = setInterval(function () { captureDevice.grabFrame().then(processFrame).catch(error => { err((new Date()).toISOString(), 'Error while grabbing frame:', error); @@ -358,5 +366,5 @@ function failedToGetMedia(error) { } -/***/ } +/***/ }) /******/ ]); \ No newline at end of file diff --git a/docs/index.css b/docs/index.css index e104305..5e0a60a 100644 --- a/docs/index.css +++ b/docs/index.css @@ -1,5 +1,5 @@ /* - * Copyright 2016 Google Inc. + * Copyright 2018 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/docs/index.js b/docs/index.js index 76d8f0a..c2ff5db 100644 --- a/docs/index.js +++ b/docs/index.js @@ -1,4 +1,4 @@ -// Copyright 2016 Google Inc. +// Copyright 2018 Google Inc. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ 'use strict'; -let logElement = document.querySelector('#log'); +const logElement = document.querySelector('#log'); /** * Log messages to the #log element and the console @@ -26,7 +26,7 @@ let logElement = document.querySelector('#log'); */ function log(...messages) { console.log(...messages); - let p = document.createElement('p'); + const p = document.createElement('p'); p.innerText = messages.join(' '); logElement.appendChild(p); } @@ -37,7 +37,7 @@ function log(...messages) { */ function err(...messages) { console.error(...messages); - let p = document.createElement('p'); + const p = document.createElement('p'); p.innerText = messages.join(' '); p.style = 'color: red'; logElement.appendChild(p); @@ -46,9 +46,9 @@ function err(...messages) { import {ImageCapture} from '../src/imagecapture'; let interval; -let canvas = document.getElementById('frame'); +const canvas = document.getElementById('frame'); -let photo = document.getElementById('photo'); +const photo = document.getElementById('photo'); photo.addEventListener('load', function () { // After the image loads, discard the image object to release the memory window.URL.revokeObjectURL(photo.src); @@ -71,7 +71,7 @@ function gotMedia(mediaStream) { videoDevice = mediaStream.getVideoTracks()[0]; log('Using camera', videoDevice.label); - let captureDevice = new ImageCapture(videoDevice, mediaStream); + const captureDevice = new ImageCapture(videoDevice, mediaStream); interval = setInterval(function () { captureDevice.grabFrame().then(processFrame).catch(error => { err((new Date()).toISOString(), 'Error while grabbing frame:', error); diff --git a/package.json b/package.json index b7fb898..7a00aff 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "image-capture", - "version": "0.3.5", + "version": "0.4.0", "description": "MediaStream ImageCapture polyfill: takePhoto(), grabFrame() and more", "main": "lib/imagecapture.js", "module": "src/imagecapture.js", @@ -35,15 +35,15 @@ "homepage": "https://github.com/GoogleChrome/imagecapture-polyfill#readme", "dependencies": {}, "devDependencies": { - "babel-cli": "^6.14.0", - "babel-plugin-transform-es2015-modules-commonjs": "^6.14.0", - "babel-preset-es2015": "^6.14.0", - "eslint": "^3.6.1", - "eslint-config-google": "^0.7.0", - "localtunnel": "^1.8.1", - "uglify-js": "^3.0.24", - "webpack": "^2.1.0-beta.25", - "webpack-dev-server": "^2.1.0-beta.8" + "babel-cli": "^6.26.0", + "babel-plugin-transform-es2015-modules-commonjs": "^6.26.0", + "babel-preset-es2015": "^6.24.1", + "eslint": "^3.19.0", + "eslint-config-google": "^0.7.1", + "localtunnel": "^1.8.3", + "uglify-js": "^3.3.5", + "webpack": "^2.7.0", + "webpack-dev-server": "^2.10.1" }, "babel": { "presets": [ diff --git a/src/imagecapture.js b/src/imagecapture.js index de87948..dcce0fc 100644 --- a/src/imagecapture.js +++ b/src/imagecapture.js @@ -2,7 +2,7 @@ * MediaStream ImageCapture polyfill * * @license - * Copyright 2017 Google Inc. + * Copyright 2018 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,9 +44,9 @@ if (typeof ImageCapture === 'undefined') { this.videoElement.addEventListener('playing', resolve); }); if (HTMLMediaElement) { - this.videoElement.srcObject = this._previewStream; // Safari 11 doesn't allow use of createObjectURL for MediaStream + this.videoElement.srcObject = this._previewStream; // Safari 11 doesn't allow use of createObjectURL for MediaStream } else { - this.videoElement.src = URL.createObjectURL(this._previewStream); + this.videoElement.src = URL.createObjectURL(this._previewStream); } this.videoElement.muted = true; this.videoElement.setAttribute('playsinline', ''); // Required by Safari on iOS 11. See https://webkit.org/blog/6784 @@ -74,7 +74,7 @@ if (typeof ImageCapture === 'undefined') { getPhotoCapabilities() { return new Promise(function executorGPC(resolve, reject) { // TODO see https://github.com/w3c/mediacapture-image/issues/97 - let MediaSettingsRange = { + const MediaSettingsRange = { current: 0, min: 0, max: 0, }; resolve({ @@ -111,7 +111,7 @@ if (typeof ImageCapture === 'undefined') { * argument on success; rejected promise on failure */ takePhoto() { - let self = this; + const self = this; return new Promise(function executorTP(resolve, reject) { // `If the readyState of the MediaStreamTrack provided in the constructor is not live, // return a promise rejected with a new DOMException whose name is "InvalidStateError".` @@ -138,7 +138,7 @@ if (typeof ImageCapture === 'undefined') { * argument on success; rejected promise on failure */ grabFrame() { - let self = this; + const self = this; return new Promise(function executorGF(resolve, reject) { // `If the readyState of the MediaStreamTrack provided in the constructor is not live, // return a promise rejected with a new DOMException whose name is "InvalidStateError".` @@ -160,3 +160,5 @@ if (typeof ImageCapture === 'undefined') { } }; } + +window.ImageCapture = ImageCapture; diff --git a/webpack.config.js b/webpack.config.js index b57d8d0..7716963 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -6,11 +6,11 @@ module.exports = { devServer: { contentBase: './docs', - inline: true + inline: true, }, output: { path: path.join(__dirname, 'docs'), - filename: './bundle.js' - } + filename: './bundle.js', + }, };