From 4019ea867fc362d1c2d32714c7bac5bb11968df3 Mon Sep 17 00:00:00 2001 From: Christoph Oberhofer Date: Sun, 27 Mar 2016 23:18:43 +0200 Subject: [PATCH] Implemented new MediaDevices API; Fallback for facingMode on Chrome-Mobile --- example/live_w_locator.js | 5 +- package.json | 3 +- src/common/typedefs.js | 4 - src/config/config.dev.js | 6 +- src/input/camera_access.js | 175 +++++++++++++++---------------------- src/quagga.js | 12 +-- 6 files changed, 85 insertions(+), 120 deletions(-) diff --git a/example/live_w_locator.js b/example/live_w_locator.js index a71f789..350a0f6 100644 --- a/example/live_w_locator.js +++ b/example/live_w_locator.js @@ -98,8 +98,7 @@ $(function() { var values = value.split('x'); return { width: parseInt(values[0]), - height: parseInt(values[1]), - facing: "environment" + height: parseInt(values[1]) } } }, @@ -118,7 +117,7 @@ $(function() { constraints: { width: 640, height: 480, - facing: "environment" // or user + facingMode: "environment" } }, locator: { diff --git a/package.json b/package.json index 8a97f8b..d970c89 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "gl-matrix": "^2.3.1", "lodash": "^3.10.1", "ndarray": "^1.0.18", - "ndarray-linear-interpolate": "^1.0.0" + "ndarray-linear-interpolate": "^1.0.0", + "webrtc-adapter": "^1.0.6" } } diff --git a/src/common/typedefs.js b/src/common/typedefs.js index d3a607e..4d138b1 100644 --- a/src/common/typedefs.js +++ b/src/common/typedefs.js @@ -14,10 +14,6 @@ if (typeof window !== 'undefined') { window.setTimeout(callback, 1000 / 60); }; })(); - - navigator.getUserMedia = navigator.getUserMedia || - navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia; - window.URL = window.URL || window.webkitURL || window.mozURL || window.msURL; } Math.imul = Math.imul || function(a, b) { var ah = (a >>> 16) & 0xffff, diff --git a/src/config/config.dev.js b/src/config/config.dev.js index 59ba61b..ea01319 100644 --- a/src/config/config.dev.js +++ b/src/config/config.dev.js @@ -5,9 +5,9 @@ module.exports = { constraints: { width: 640, height: 480, - minAspectRatio: 0, - maxAspectRatio: 100, - facing: "environment" // or user + // aspectRatio: 640/480, // optional + facingMode: "environment", // or user + // deviceId: "38745983457387598375983759834" }, area: { top: "0%", diff --git a/src/input/camera_access.js b/src/input/camera_access.js index da20d61..d5f3c3a 100644 --- a/src/input/camera_access.js +++ b/src/input/camera_access.js @@ -1,45 +1,31 @@ const merge = require('lodash/object/merge'); +const pick = require('lodash/object/pick'); +const mapKeys = require('lodash/object/mapKeys'); var streamRef, loadedDataHandler; -/** - * Wraps browser-specific getUserMedia - * @param {Object} constraints - * @param {Object} success Callback - * @param {Object} failure Callback - */ -function getUserMedia(constraints, success, failure) { - if (typeof navigator.getUserMedia !== 'undefined') { - navigator.getUserMedia(constraints, function (stream) { - streamRef = stream; - var videoSrc = (window.URL && window.URL.createObjectURL(stream)) || stream; - success.apply(null, [videoSrc]); - }, failure); - } else { - failure(new TypeError("getUserMedia not available")); - } -} - -function loadedData(video, callback) { - var attempts = 10; +function waitForVideo(video) { + return new Promise((resolve, reject) => { + let attempts = 10; - function checkVideo() { - if (attempts > 0) { - if (video.videoWidth > 0 && video.videoHeight > 0) { - if (ENV.development) { - console.log(video.videoWidth + "px x " + video.videoHeight + "px"); + function checkVideo() { + if (attempts > 0) { + if (video.videoWidth > 0 && video.videoHeight > 0) { + if (ENV.development) { + console.log(video.videoWidth + "px x " + video.videoHeight + "px"); + } + resolve(); + } else { + window.setTimeout(checkVideo, 500); } - callback(); } else { - window.setTimeout(checkVideo, 500); + reject('Unable to play video stream. Is webcam working?'); } - } else { - callback('Unable to play video stream. Is webcam working?'); + attempts--; } - attempts--; - } - checkVideo(); + checkVideo(); + }); } /** @@ -47,89 +33,72 @@ function loadedData(video, callback) { * and calls the callback function when the content is ready * @param {Object} constraints * @param {Object} video - * @param {Object} callback */ -function initCamera(constraints, video, callback) { - getUserMedia(constraints, function(src) { - video.src = src; - if (loadedDataHandler) { - video.removeEventListener("loadeddata", loadedDataHandler, false); - } - loadedDataHandler = loadedData.bind(null, video, callback); - video.addEventListener('loadeddata', loadedDataHandler, false); - video.play(); - }, function(e) { - callback(e); - }); +function initCamera(video, constraints) { + return navigator.mediaDevices.getUserMedia(constraints) + .then((stream) => { + return new Promise((resolve, reject) => { + streamRef = stream; + video.src = window.URL.createObjectURL(stream); + video.onloadedmetadata = (e) => { + video.play(); + resolve(); + }; + }); + }) + .then(waitForVideo.bind(null, video)); } -/** - * Normalizes the incoming constraints to satisfy the current browser - * @param config - * @param cb Callback which is called whenever constraints are created - * @returns {*} - */ -function normalizeConstraints(config, cb) { - var constraints = { - audio: false, - video: true - }, - videoConstraints = merge({ - width: 640, - height: 480, - minAspectRatio: 0, - maxAspectRatio: 100, - facing: "environment" - }, config); +function deprecatedConstraints(videoConstraints) { + const normalized = pick(videoConstraints, ["width", "height", "facingMode", + "aspectRatio", "deviceId"]); - if ( typeof MediaStreamTrack !== 'undefined' && typeof MediaStreamTrack.getSources !== 'undefined') { - MediaStreamTrack.getSources(function(sourceInfos) { - var videoSourceId; - for (var i = 0; i < sourceInfos.length; ++i) { - var sourceInfo = sourceInfos[i]; - if (sourceInfo.kind === "video" && sourceInfo.facing === videoConstraints.facing) { - videoSourceId = sourceInfo.id; + if (typeof videoConstraints["minAspectRatio"] !== 'undefined' && + videoConstraints["minAspectRatio"] > 0) { + normalized["aspectRatio"] = videoConstraints["minAspectRatio"]; + console.log("WARNING: Constraint 'minAspectRatio' is deprecated; Use 'aspectRatio' instead"); + } + if (typeof videoConstraints["facing"] !== 'undefined') { + normalized["facingMode"] = videoConstraints["facing"]; + console.log("WARNING: Constraint 'facing' is deprecated. Use 'facingMode' instead'"); + } + return normalized; +} + +function applyCameraFacing(facing, constraints) { + if (typeof constraints.video.deviceId !== 'undefined' || !facing){ + return Promise.resolve(constraints); + } + if ( typeof MediaStreamTrack !== 'undefined' && + typeof MediaStreamTrack.getSources !== 'undefined') { + return new Promise((resolve, reject) => { + MediaStreamTrack.getSources((sourceInfos) => { + const videoSource = sourceInfos.filter((sourceInfo) => ( + sourceInfo.kind === "video" && sourceInfo.facing === facing + ))[0]; + if (videoSource) { + return resolve(merge({}, constraints, + {video: {deviceId: videoSource.id}})); } - } - constraints.video = { - mandatory: { - minWidth: videoConstraints.width, - minHeight: videoConstraints.height, - minAspectRatio: videoConstraints.minAspectRatio, - maxAspectRatio: videoConstraints.maxAspectRatio - }, - optional: [{ - sourceId: videoSourceId - }] - }; - return cb(constraints); + return resolve(constraints); + }); }); - } else { - constraints.video = { - mediaSource: "camera", - width: { min: videoConstraints.width, max: videoConstraints.width }, - height: { min: videoConstraints.height, max: videoConstraints.height }, - require: ["width", "height"] - }; - return cb(constraints); } + return Promise.resolve(merge({}, videoConstraints, {facingMode: facing})); } -/** - * Requests the back-facing camera of the user. The callback is called - * whenever the stream is ready to be consumed, or if an error occures. - * @param {Object} video - * @param {Object} callback - */ -function request(video, videoConstraints, callback) { - normalizeConstraints(videoConstraints, function(constraints) { - initCamera(constraints, video, callback); - }); +function pickConstraints(videoConstraints) { + const constraints = { + audio: false, + video: deprecatedConstraints(videoConstraints) + }; + return applyCameraFacing(constraints.video.facingMode, constraints); } export default { - request: function(video, constraints, callback) { - request(video, constraints, callback); + request: function(video, videoConstraints) { + return pickConstraints(videoConstraints) + .then(initCamera.bind(null, video)); }, release: function() { var tracks = streamRef && streamRef.getVideoTracks(); diff --git a/src/quagga.js b/src/quagga.js index 495e5f4..47b71cc 100644 --- a/src/quagga.js +++ b/src/quagga.js @@ -1,4 +1,5 @@ import TypeDefs from './common/typedefs'; // eslint-disable-line no-unused-vars +import WebrtcAdapter from 'webrtc-adapter'; // eslint-disable-line no-unused-vars import ImageWrapper from './common/image_wrapper'; import BarcodeLocator from './locator/barcode_locator'; import BarcodeDecoder from './decoder/barcode_decoder'; @@ -56,12 +57,11 @@ function initInputStream(cb) { } } _inputStream = InputStream.createLiveStream(video); - CameraAccess.request(video, _config.inputStream.constraints, function(err) { - if (!err) { - _inputStream.trigger("canrecord"); - } else { - return cb(err); - } + CameraAccess.request(video, _config.inputStream.constraints) + .then(() => { + _inputStream.trigger("canrecord"); + }).catch((err) => { + return cb(err); }); }