Commit 92f71e41 authored by Wei Lee's avatar Wei Lee Committed by Commit Bot

Refactor imagecapture.js for readability and extendibility

Since imagecapture.js starts to contain many things that has nothing to
do with ImageCapture, this CL refactor it as following:

1. Splits imagecapture.js into MojoConnector, DeviceOperator and
   ImageCapture to make it clearer and more extensible.
2. Avoids using singleton pattern for mojo connection.
3. For camera Hal v1, simplifies the check and remove try/catch
   mechanism.

Bug: 979104
Test: tast run DUT camera.CCA*
Test: Running above test for v1/v3 devices

Change-Id: I8b93e05a566ae81731c1e8f71cd00baa82d641f5
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1739035
Commit-Queue: Wei Lee <wtlee@chromium.org>
Reviewed-by: default avatarShik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#690181}
parent 1e7ad256
......@@ -169,7 +169,9 @@ copy("chrome_camera_app_js_models") {
copy("chrome_camera_app_js_mojo") {
sources = [
"src/js/mojo/imagecapture.js",
"src/js/mojo/device_operator.js",
"src/js/mojo/image_capture.js",
"src/js/mojo/mojo_connector.js",
]
outputs = [
......
......@@ -130,7 +130,9 @@ RESOURCES = \
src/js/models/gallery.js \
src/js/models/result_saver.js \
src/js/models/video_saver.js \
src/js/mojo/imagecapture.js \
src/js/mojo/device_operator.js \
src/js/mojo/image_capture.js \
src/js/mojo/mojo_connector.js \
src/js/nav.js \
src/js/resolution_event_broker.js \
src/js/scrollbar.js \
......
......@@ -18,6 +18,7 @@
<structure name="IDR_CAMERA_CAMERA3_DEVICE_INFO_JS" file="src/js/device/camera3_device_info.js" type="chrome_html" />
<structure name="IDR_CAMERA_CAMERA_JS" file="src/js/views/camera.js" type="chrome_html" />
<structure name="IDR_CAMERA_CONSTRAINTS_PREFERRER_JS" file="src/js/device/constraints_preferrer.js" type="chrome_html" />
<structure name="IDR_CAMERA_DEVICE_OPERATOR_JS" file="src/js/mojo/device_operator.js" type="chrome_html" />
<structure name="IDR_CAMERA_DEVICE_INFO_UPDATER_JS" file="src/js/device/device_info_updater.js" type="chrome_html" />
<structure name="IDR_CAMERA_DIALOG_JS" file="src/js/views/dialog.js" type="chrome_html" />
<structure name="IDR_CAMERA_FILENAMER_JS" file="src/js/models/filenamer.js" type="chrome_html" />
......@@ -25,7 +26,7 @@
<structure name="IDR_CAMERA_GALLERY_BASE_JS" file="src/js/views/gallery_base.js" type="chrome_html" />
<structure name="IDR_CAMERA_GALLERY_JS" file="src/js/models/gallery.js" type="chrome_html" />
<structure name="IDR_CAMERA_GALLERYBUTTON_JS" file="src/js/gallerybutton.js" type="chrome_html" />
<structure name="IDR_CAMERA_IMAGECAPTURE_JS" file="src/js/mojo/imagecapture.js" type="chrome_html" />
<structure name="IDR_CAMERA_IMAGECAPTURE_JS" file="src/js/mojo/image_capture.js" type="chrome_html" />
<structure name="IDR_CAMERA_LAYOUT_JS" file="src/js/views/camera/layout.js" type="chrome_html" />
<structure name="IDR_CAMERA_MAIN_CSS" file="src/css/main.css" type="chrome_html" />
<structure name="IDR_CAMERA_MAIN_HTML" file="src/views/main.html" type="chrome_html" />
......@@ -33,6 +34,7 @@
<structure name="IDR_CAMERA_MANIFEST" file="manifest.json" type="chrome_html" />
<structure name="IDR_CAMERA_METRICS_JS" file="src/js/metrics.js" type="chrome_html" />
<structure name="IDR_CAMERA_MODES_JS" file="src/js/views/camera/modes.js" type="chrome_html" />
<structure name="IDR_CAMERA_MOJO_CONNECTOR_JS" file="src/js/mojo/mojo_connector.js" type="chrome_html" />
<structure name="IDR_CAMERA_NAV_JS" file="src/js/nav.js" type="chrome_html" />
<structure name="IDR_CAMERA_OPTIONS_JS" file="src/js/views/camera/options.js" type="chrome_html" />
<structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.js" type="chrome_html" />
......
......@@ -14,24 +14,24 @@ js_type_check("closure_compile") {
js_library("camera3_device_info") {
deps = [
"../mojo:imagecapture",
"../mojo:image_capture",
]
}
js_library("constraints_preferrer") {
deps = [
":camera3_device_info",
"..:resolution_event_broker",
"..:state",
":camera3_device_info",
"../browser_proxy:browser_proxy",
]
}
js_library("device_info_updater") {
deps = [
"..:state",
"../mojo:imagecapture",
":camera3_device_info",
":constraints_preferrer",
"..:state",
"../mojo:mojo_connector",
]
}
......@@ -96,4 +96,24 @@ cca.device.Camera3DeviceInfo = class {
this.videoMaxFps[[w, h]] = fps;
});
}
/**
* Create a Camera3DeviceInfo by given device info and the mojo device
* operator.
* @param {!MediaDeviceInfo} deviceInfo
* @param {!cca.mojo.DeviceOperator} deviceOperator
* @return {Promise<!cca.device.Camera3DeviceInfo>}
*/
static async create(deviceInfo, deviceOperator) {
const deviceId = deviceInfo.deviceId;
const facing = await deviceOperator.getCameraFacing(deviceId);
const photoResolution = await deviceOperator.getPhotoResolutions(deviceId);
const videoConfigs = await deviceOperator.getVideoConfigs(deviceId);
const supportedFpsRanges =
await deviceOperator.getSupportedFpsRanges(deviceId);
return new cca.device.Camera3DeviceInfo(
deviceInfo, facing, photoResolution, videoConfigs, supportedFpsRanges);
}
};
......@@ -22,9 +22,10 @@ cca.device.DeviceInfoUpdater = class {
/**
* @param {cca.device.PhotoResolPreferrer} photoPreferrer
* @param {cca.device.VideoConstraintsPreferrer} videoPreferrer
* @param {cca.mojo.MojoConnector} mojoConnector
* @public
* */
constructor(photoPreferrer, videoPreferrer) {
constructor(photoPreferrer, videoPreferrer, mojoConnector) {
/**
* @type {cca.device.PhotoResolPreferrer}
* @private
......@@ -37,6 +38,12 @@ cca.device.DeviceInfoUpdater = class {
*/
this.videoPreferrer_ = videoPreferrer;
/**
* @type {cca.mojo.MojoConnector}
* @private
*/
this.mojoConnector_ = mojoConnector;
/**
* Listeners to be called after new camera information is available.
* @type {!Array<function(!cca.device.DeviceInfoUpdater): Promise>}
......@@ -142,7 +149,7 @@ cca.device.DeviceInfoUpdater = class {
async enumerateDevices_() {
const devices = (await navigator.mediaDevices.enumerateDevices())
.filter((device) => device.kind == 'videoinput');
if (devices.length == 0) {
if (devices.length === 0) {
throw new Error('Device list empty.');
}
return devices;
......@@ -158,22 +165,16 @@ cca.device.DeviceInfoUpdater = class {
* @private
*/
async queryMojoDevicesInfo_() {
const devices = await this.devicesInfo_;
let /** ?Array<Object> */ privateInfos;
try {
privateInfos = await Promise.all(devices.map((d) => Promise.all([
d,
cca.mojo.getCameraFacing(d.deviceId),
cca.mojo.getPhotoResolutions(d.deviceId),
cca.mojo.getVideoConfigs(d.deviceId),
cca.mojo.getSupportedFpsRanges(d.deviceId),
])));
} catch (e) {
privateInfos = null;
const deviceOperator = this.mojoConnector_.getDeviceOperator();
if (!deviceOperator) {
return null;
}
// Non-null version for the Closure Compiler.
const nonNullDeviceOperator = deviceOperator;
return privateInfos &&
privateInfos.map((info) => new cca.device.Camera3DeviceInfo(...info));
const deviceInfos = await this.devicesInfo_;
return await Promise.all(deviceInfos.map(
(d) => cca.device.Camera3DeviceInfo.create(d, nonNullDeviceOperator)));
}
/**
......
......@@ -11,9 +11,17 @@ var cca = cca || {};
/**
* Creates the Camera App main object.
* @param {!cca.mojo.MojoConnector} mojoConnector The mojo connector which could
* be used to communicate with Chrome and video capture devices.
* @constructor
*/
cca.App = function() {
cca.App = function(mojoConnector) {
/**
* @type {cca.mojo.MojoConnector}
* @private
*/
this.mojoConnector_ = mojoConnector;
/**
* @type {cca.models.Gallery}
* @private
......@@ -45,7 +53,7 @@ cca.App = function() {
* @private
*/
this.infoUpdater_ = new cca.device.DeviceInfoUpdater(
this.photoPreferrer_, this.videoPreferrer_);
this.photoPreferrer_, this.videoPreferrer_, this.mojoConnector_);
/**
* @type {cca.GalleryButton}
......@@ -64,8 +72,8 @@ cca.App = function() {
* @private
*/
this.cameraView_ = new cca.views.Camera(
this.gallery_, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_);
this.mojoConnector_, this.gallery_, this.infoUpdater_,
this.photoPreferrer_, this.videoPreferrer_);
// End of properties. Seal the object.
Object.seal(this);
......@@ -200,9 +208,16 @@ cca.App.instance_ = null;
/**
* Creates the App object and starts camera stream.
*/
document.addEventListener('DOMContentLoaded', () => {
document.addEventListener('DOMContentLoaded', async () => {
const mojoConnector = new cca.mojo.MojoConnector();
try {
await mojoConnector.initDeviceOperator();
} catch (e) {
console.error(e);
}
if (!cca.App.instance_) {
cca.App.instance_ = new cca.App();
cca.App.instance_ = new cca.App(mojoConnector);
}
cca.App.instance_.start();
chrome.app.window.current().show();
......
......@@ -6,14 +6,30 @@ import("//third_party/closure_compiler/compile_js.gni")
js_type_check("closure_compile") {
deps = [
":imagecapture",
":device_operator",
":image_capture",
":mojo_connector",
]
}
js_library("imagecapture") {
js_library("device_operator") {
deps = [
"//media/capture/mojom:image_capture_js_library_for_compile",
"//media/capture/video/chromeos/mojom:cros_camera_js_library_for_compile",
]
externs_list = [ "$externs_path/pending.js" ]
}
js_library("image_capture") {
deps = [
":device_operator",
"..:util",
"//media/capture/mojom:image_capture_js_library_for_compile",
]
externs_list = [ "$externs_path/pending.js" ]
}
js_library("mojo_connector") {
deps = [
"//media/capture/video/chromeos/mojom:cros_camera_js_library_for_compile",
]
}
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for mojo.
*/
cca.mojo = cca.mojo || {};
/**
* Operates video capture device through CrOS Camera App Mojo interface.
*/
cca.mojo.DeviceOperator = class {
/**
* @public
* @param {!Map<string, !cros.mojom.CameraAppDeviceRemote>} devices Map of
* device remotes.
*/
constructor(devices) {
/**
* @type {!Map<string, !cros.mojom.CameraAppDeviceRemote>} A map
* that stores the device remotes that could be used to communicate
* with camera device through mojo interface.
*/
this.devices_ = devices;
}
/**
* Gets corresponding device remote by given id.
* @param {string} deviceId The id for target camera device.
* @return {!cros.mojom.CameraAppDeviceRemote} Corresponding device remote.
* @throws {Error} Thrown when the given deviceId does not found in the map.
*/
getDevice(deviceId) {
const device = this.devices_.get(deviceId);
if (device === undefined) {
throw new Error('Invalid deviceId: ', deviceId);
}
return device;
}
/**
* Gets supported photo resolutions for specific camera.
* @param {string} deviceId The renderer-facing device id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {!Promise<!Array<!Array<number>>>} Promise of supported
* resolutions. Each photo resolution is represented as [width, height].
* @throws {Error} Thrown when fail to parse the metadata.
*/
async getPhotoResolutions(deviceId) {
const formatBlob = 33;
const typeOutputStream = 0;
const numElementPerEntry = 4;
const device = this.getDevice(deviceId);
const {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const streamConfigs = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
// The data of |streamConfigs| looks like:
// streamConfigs: [FORMAT_1, WIDTH_1, HEIGHT_1, TYPE_1,
// FORMAT_2, WIDTH_2, HEIGHT_2, TYPE_2, ...]
if (streamConfigs.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of stream configurations');
}
const supportedResolutions = [];
for (let i = 0; i < streamConfigs.length; i += numElementPerEntry) {
const [format, width, height, type] =
streamConfigs.slice(i, i + numElementPerEntry);
if (format === formatBlob && type === typeOutputStream) {
supportedResolutions.push([width, height]);
}
}
return supportedResolutions;
}
/**
* Gets supported video configurations for specific camera.
* @param {string} deviceId The renderer-facing device id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {!Promise<!Array<!Array<number>>>} Promise of supported video
* configurations. Each configuration is represented as [width, height,
* maxFps].
* @throws {Error} Thrown when fail to parse the metadata.
*/
async getVideoConfigs(deviceId) {
// Currently we use YUV format for both recording and previewing on Chrome.
const formatYuv = 35;
const oneSecondInNs = 1e9;
const numElementPerEntry = 4;
const device = this.getDevice(deviceId);
const {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const minFrameDurationConfigs = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
// The data of |minFrameDurationConfigs| looks like:
// minFrameDurationCOnfigs: [FORMAT_1, WIDTH_1, HEIGHT_1, DURATION_1,
// FORMAT_2, WIDTH_2, HEIGHT_2, DURATION_2,
// ...]
if (minFrameDurationConfigs.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of frame durations configs');
}
const supportedConfigs = [];
for (let i = 0; i < minFrameDurationConfigs.length;
i += numElementPerEntry) {
const [format, width, height, minDuration] =
minFrameDurationConfigs.slice(i, i + numElementPerEntry);
if (format === formatYuv) {
const maxFps = Math.round(oneSecondInNs / minDuration);
supportedConfigs.push([width, height, maxFps]);
}
}
return supportedConfigs;
}
/**
* Gets camera facing for given device.
* @param {string} deviceId The renderer-facing device id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {!Promise<!cros.mojom.CameraFacing>} Promise of device facing.
*/
async getCameraFacing(deviceId) {
const device = this.getDevice(deviceId);
const {cameraInfo} = await device.getCameraInfo();
return cameraInfo.facing;
}
/**
* Gets supported fps ranges for specific camera.
* @param {string} deviceId The renderer-facing device id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {!Promise<!Array<Array<number>>>} Promise of supported fps ranges.
* Each range is represented as [min, max].
* @throws {Error} Thrown when fail to parse the metadata.
*/
async getSupportedFpsRanges(deviceId) {
const numElementPerEntry = 2;
const device = this.getDevice(deviceId);
const {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const availableFpsRanges = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
// The data of |availableFpsRanges| looks like:
// availableFpsRanges: [RANGE_1_MIN, RANGE_1_MAX,
// RANGE_2_MIN, RANGE_2_MAX, ...]
if (availableFpsRanges.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of available fps range configs');
}
const supportedFpsRanges = [];
for (let i = 0; i < availableFpsRanges.length; i += numElementPerEntry) {
const [rangeMin, rangeMax] =
availableFpsRanges.slice(i, i + numElementPerEntry);
supportedFpsRanges.push([rangeMin, rangeMax]);
}
return supportedFpsRanges;
}
/**
* Sets the fps range for target device.
* @param {string} deviceId
* @param {MediaStreamConstraints} constraints The constraints including fps
* range and the resolution. If frame rate range negotiation is needed,
* the caller should either set exact field or set both min and max fields
* for frame rate property.
* @throws {Error} Thrown when the input contains invalid values.
*/
async setFpsRange(deviceId, constraints) {
let /** number */ streamWidth = 0;
let /** number */ streamHeight = 0;
let /** number */ minFrameRate = 0;
let /** number */ maxFrameRate = 0;
if (constraints && constraints.video && constraints.video.frameRate) {
const frameRate = constraints.video.frameRate;
if (frameRate.exact) {
minFrameRate = frameRate.exact;
maxFrameRate = frameRate.exact;
} else if (frameRate.min && frameRate.max) {
minFrameRate = frameRate.min;
maxFrameRate = frameRate.max;
}
// TODO(wtlee): To set the fps range to the default value, we should
// remove the frameRate from constraints instead of using incomplete
// range.
// We only support number type for width and height. If width or height
// is other than a number (e.g. ConstrainLong, undefined, etc.), we should
// throw an error.
if (typeof constraints.video.width !== 'number') {
throw new Error('width in constraints is expected to be a number');
}
streamWidth = constraints.video.width;
if (typeof constraints.video.height !== 'number') {
throw new Error('height in constraints is expected to be a number');
}
streamHeight = constraints.video.height;
}
const hasSpecifiedFrameRateRange = minFrameRate > 0 && maxFrameRate > 0;
// If the frame rate range is specified in |constraints|, we should try to
// set the frame rate range and should report error if fails since it is
// unexpected.
//
// Otherwise, if the frame rate is incomplete or totally missing in
// |constraints| , we assume the app wants to use default frame rate range.
// We set the frame rate range to an invalid range (e.g. 0 fps) so that it
// will fallback to use the default one.
const device = this.getDevice(deviceId);
const {isSuccess} = await device.setFpsRange(
{'width': streamWidth, 'height': streamHeight},
{'start': minFrameRate, 'end': maxFrameRate});
if (!isSuccess && hasSpecifiedFrameRateRange) {
console.error('Failed to negotiate the frame rate range.');
}
}
/**
* Checks if portrait mode is supported.
* @param {string} deviceId The id for target device.
* @return {!Promise<boolean>} Promise of the boolean result.
*/
async isPortraitModeSupported(deviceId) {
// TODO(wtlee): Change to portrait mode tag.
// This should be 0x80000000 but mojo interface will convert the tag to
// int32.
const portraitModeTag =
/** @type{cros.mojom.CameraMetadataTag} */ (-0x80000000);
const device = this.getDevice(deviceId);
const {cameraInfo} = await device.getCameraInfo();
return cca.mojo
.getMetadataData_(
cameraInfo.staticCameraCharacteristics, portraitModeTag)
.length > 0;
}
};
/**
* Gets the data from Camera metadata by its tag.
* @param {!cros.mojom.CameraMetadata} metadata Camera metadata from which to
* query the data.
* @param {!cros.mojom.CameraMetadataTag} tag Camera metadata tag to query for.
* @return {!Array<number>} An array containing elements whose types correspond
* to the format of input |tag|. If nothing is found, returns an empty
* array.
* @private
*/
cca.mojo.getMetadataData_ = function(metadata, tag) {
for (let i = 0; i < metadata.entryCount; i++) {
const entry = metadata.entries[i];
if (entry.tag !== tag) {
continue;
}
const {buffer} = Uint8Array.from(entry.data);
switch (entry.type) {
case cros.mojom.EntryType.TYPE_BYTE:
return Array.from(new Uint8Array(buffer));
case cros.mojom.EntryType.TYPE_INT32:
return Array.from(new Int32Array(buffer));
case cros.mojom.EntryType.TYPE_FLOAT:
return Array.from(new Float32Array(buffer));
case cros.mojom.EntryType.TYPE_DOUBLE:
return Array.from(new Float64Array(buffer));
case cros.mojom.EntryType.TYPE_INT64:
return Array.from(new BigInt64Array(buffer), (bigIntVal) => {
const numVal = Number(bigIntVal);
if (!Number.isSafeInteger(numVal)) {
console.warn('The int64 value is not a safe integer');
}
return numVal;
});
default:
// TODO(wtlee): Supports rational type.
throw new Error('Unsupported type: ' + entry.type);
}
}
return [];
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for mojo.
*/
cca.mojo = cca.mojo || {};
/**
* Type definition for cca.mojo.PhotoCapabilities.
* @extends {PhotoCapabilities}
* @record
*/
cca.mojo.PhotoCapabilities = function() {};
/** @type {Array<string>} */
cca.mojo.PhotoCapabilities.prototype.supportedEffects;
/**
* Creates the wrapper of JS image-capture and Mojo image-capture.
* @param {!MediaStreamTrack} videoTrack A video track whose still images will
* be taken.
* @param {!cca.mojo.MojoConnector} mojoConnector A mojo connector that could
* used to communicate with video capture device.
* @constructor
*/
cca.mojo.ImageCapture = function(videoTrack, mojoConnector) {
/**
* @type {string} The id of target media device.
*/
this.deviceId_ = videoTrack.getSettings().deviceId;
/**
* @type {ImageCapture} The standard ImageCapture object.
* @private
*/
this.capture_ = new ImageCapture(videoTrack);
/**
* @type {cca.mojo.MojoConnector} The mojo connector that we used to negotiate
* with the video capture device.
* @private
*/
this.mojoConnector_ = mojoConnector;
// End of properties, seal the object.
Object.seal(this);
};
/**
* Gets the photo capabilities with the available options/effects.
* @return {!Promise<!cca.mojo.PhotoCapabilities>} Promise for the result.
*/
cca.mojo.ImageCapture.prototype.getPhotoCapabilities = async function() {
const deviceOperator = this.mojoConnector_.getDeviceOperator();
const supportedEffects = [cros.mojom.Effect.NO_EFFECT];
const isPortraitModeSupported =
await deviceOperator.isPortraitModeSupported(this.deviceId_);
if (isPortraitModeSupported) {
supportedEffects.push(cros.mojom.Effect.PORTRAIT_MODE);
}
const baseCapabilities = await this.capture_.getPhotoCapabilities();
let /** !cca.mojo.PhotoCapabilities */ extendedCapabilities;
Object.assign(extendedCapabilities, baseCapabilities, {supportedEffects});
return extendedCapabilities;
};
/**
* Takes single or multiple photo(s) with the specified settings and effects.
* The amount of result photo(s) depends on the specified settings and effects,
* and the first promise in the returned array will always resolve with the
* unreprocessed photo.
* @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's
* takePhoto().
* @param {!Array<cros.mojom.Effect>=} photoEffects Photo effects to be applied.
* @return {!Array<!Promise<!Blob>>} Array of promises for the result.
*/
cca.mojo.ImageCapture.prototype.takePhoto = function(
photoSettings, photoEffects = []) {
const takes = [];
if (photoEffects) {
for (const effect of photoEffects) {
const take = (async () => {
const deviceOperator = this.mojoConnector_.getDeviceOperator();
const device = deviceOperator.getDevice(this.deviceId_);
const {status, blob} = await device.setReprocessOption(effect);
if (status !== 0) {
throw new Error('Mojo image capture error: ' + status);
}
const {data, mimeType} = blob;
return new Blob([new Uint8Array(data)], {type: mimeType});
})();
takes.push(take);
}
}
takes.splice(0, 0, this.capture_.takePhoto(photoSettings));
return takes;
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for mojo.
*/
cca.mojo = cca.mojo || {};
/**
* Type definition for cca.mojo.PhotoCapabilities.
* @extends {PhotoCapabilities}
* @record
*/
cca.mojo.PhotoCapabilities = function() {};
/** @type {Array<string>} */
cca.mojo.PhotoCapabilities.prototype.supportedEffects;
/**
* The mojo interface of CrOS Camera App API. It provides a singleton
* instance.
*/
cca.mojo.MojoInterface = class {
/** @public */
constructor() {
/**
* @type {cros.mojom.CameraAppDeviceProviderRemote} An interface remote that
* used to construct the mojo interface.
*/
this.deviceProvider = cros.mojom.CameraAppDeviceProvider.getRemote();
/**
* @type {Map<string, Promise<cros.mojom.CameraAppDeviceRemote>>} A map
* that stores the promise of remote that could be used to communicate
* with camera device through mojo interface.
*/
this.devices_ = new Map();
}
/**
* Gets the singleton instance.
* @return {cca.mojo.MojoInterface} The singleton instance of this object.
*/
static getInstance() {
if (!cca.mojo.MojoInterface.instance_) {
/**
* @type {cca.mojo.MojoInterface} The singleton instance of this object.
*/
cca.mojo.MojoInterface.instance_ = new cca.mojo.MojoInterface();
}
return cca.mojo.MojoInterface.instance_;
}
/**
* Gets the mojo interface remote which could be used to get the
* CameraAppDevice from Chrome.
* @return {cros.mojom.CameraAppDeviceProviderRemote} The mojo interface
* remote.
*/
getDeviceProvider() {
return this.deviceProvider;
}
/**
* Gets the mojo interface remote which could be used to communicate with
* Camera device in Chrome.
* @param {string} deviceId The id of the target camera device.
* @return {Promise<?cros.mojom.CameraAppDeviceRemote>} The mojo interface
* remote of the camera device. For non-v3 devices, it will return a null
* remote.
* @throws {Error} Thrown for invalid input.
*/
getDevice(deviceId) {
if (this.devices_.has(deviceId)) {
return this.devices_.get(deviceId);
}
let device = this.deviceProvider.getCameraAppDevice(deviceId).then(
({status, device}) => {
if (status === cros.mojom.GetCameraAppDeviceStatus.ERROR_INVALID_ID) {
throw new Error('Invalid device id: ' + deviceId);
}
// For non-v3 devices, it will return the null remote. Otherwise, it
// should return a valid remote of camera device.
return device;
});
this.devices_.set(deviceId, device);
return device;
}
/**
* Closes all mojo connections to devices.
*/
closeConnections() {
this.devices_.clear();
}
};
/**
* Creates the wrapper of JS image-capture and Mojo image-capture.
* @param {!MediaStreamTrack} videoTrack A video track whose still images will
* be taken.
* @constructor
*/
cca.mojo.ImageCapture = function(videoTrack) {
/**
* @type {string} The id of target media device.
*/
this.deviceId_ = cca.mojo.ImageCapture.resolveDeviceId(videoTrack) || '';
/**
* @type {ImageCapture}
* @private
*/
this.capture_ = new ImageCapture(videoTrack);
// End of properties, seal the object.
Object.seal(this);
};
/**
* Gets the data from Camera metadata by its tag.
* @param {cros.mojom.CameraMetadata} metadata Camera metadata from which to
* query the data.
* @param {cros.mojom.CameraMetadataTag} tag Camera metadata tag to query for.
* @return {Array<number>} An array containing elements whose types correspond
* to the format of input |tag|. If nothing is found, returns an empty
* array.
* @private
*/
cca.mojo.getMetadataData_ = function(metadata, tag) {
for (let i = 0; i < metadata.entryCount; i++) {
let entry = metadata.entries[i];
if (entry.tag !== tag) {
continue;
}
const {buffer} = Uint8Array.from(entry.data);
switch (entry.type) {
case cros.mojom.EntryType.TYPE_BYTE:
return Array.from(new Uint8Array(buffer));
case cros.mojom.EntryType.TYPE_INT32:
return Array.from(new Int32Array(buffer));
case cros.mojom.EntryType.TYPE_FLOAT:
return Array.from(new Float32Array(buffer));
case cros.mojom.EntryType.TYPE_DOUBLE:
return Array.from(new Float64Array(buffer));
case cros.mojom.EntryType.TYPE_INT64:
return Array.from(new BigInt64Array(buffer), (bigIntVal) => {
let numVal = Number(bigIntVal);
if (!Number.isSafeInteger(numVal)) {
console.warn('The int64 value is not a safe integer');
}
return numVal;
});
default:
// TODO(wtlee): Supports rational type.
throw new Error('Unsupported type: ' + entry.type);
}
}
return [];
};
/**
* Resolves video device id from its video track.
* @param {MediaStreamTrack} videoTrack Video track of device to be resolved.
* @return {?string} Resolved video device id. Returns null for unable to
* resolve.
*/
cca.mojo.ImageCapture.resolveDeviceId = function(videoTrack) {
const trackSettings = videoTrack.getSettings && videoTrack.getSettings();
return trackSettings && trackSettings.deviceId || null;
};
/**
* Gets the photo capabilities with the available options/effects.
* @return {Promise<cca.mojo.PhotoCapabilities>} Promise for the result.
*/
cca.mojo.ImageCapture.prototype.getPhotoCapabilities = async function() {
// TODO(wtlee): Change to portrait mode tag.
// This should be 0x80000000 but mojo interface will convert the tag to int32.
const portraitModeTag =
/** @type{cros.mojom.CameraMetadataTag} */ (-0x80000000);
let device =
await cca.mojo.MojoInterface.getInstance().getDevice(this.deviceId_);
if (device === null) {
throw new Error('Fail to construct device remote.');
}
let [/** @type {cca.mojo.PhotoCapabilities} */capabilities,
{/** @type {cros.mojom.CameraInfo} */cameraInfo},
] = await Promise.all([
this.capture_.getPhotoCapabilities(),
device.getCameraInfo(),
]);
if (cameraInfo === null) {
throw new Error('No photo capabilities is found for given device id.');
}
const staticMetadata = cameraInfo.staticCameraCharacteristics;
let supportedEffects = [cros.mojom.Effect.NO_EFFECT];
if (cca.mojo.getMetadataData_(staticMetadata, portraitModeTag).length > 0) {
supportedEffects.push(cros.mojom.Effect.PORTRAIT_MODE);
}
capabilities.supportedEffects = supportedEffects;
return capabilities;
};
/**
* Takes single or multiple photo(s) with the specified settings and effects.
* The amount of result photo(s) depends on the specified settings and effects,
* and the first promise in the returned array will always resolve with the
* unreprocessed photo.
* @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's
* takePhoto().
* @param {?Array<cros.mojom.Effect>} photoEffects Photo effects to be applied.
* @return {!Array<!Promise<!Blob>>} Array of promises for the result.
*/
cca.mojo.ImageCapture.prototype.takePhoto = function(
photoSettings, photoEffects) {
const takes = [];
if (photoEffects) {
photoEffects.forEach((effect) => {
const take = (async () => {
let device = await cca.mojo.MojoInterface.getInstance().getDevice(
this.deviceId_);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
let {status, blob} = await device.setReprocessOption(effect);
if (status !== 0) {
throw new Error('Mojo image capture error: ' + status);
}
const {data, mimeType} = blob;
return new Blob([new Uint8Array(data)], {type: mimeType});
})();
takes.push(take);
});
}
takes.splice(0, 0, this.capture_.takePhoto(photoSettings));
return takes;
};
/**
* Gets supported photo resolutions for specific camera.
* @param {string} deviceId The renderer-facing device Id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {Promise<Array<Object>>} Promise of supported resolutions. Each
* photo resolution is represented as [width, height].
*/
cca.mojo.getPhotoResolutions = async function(deviceId) {
const formatBlob = 33;
const typeOutputStream = 0;
const numElementPerEntry = 4;
let device = await cca.mojo.MojoInterface.getInstance().getDevice(deviceId);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
let {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const streamConfigs = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_SCALER_AVAILABLE_STREAM_CONFIGURATIONS);
// The data of |streamConfigs| looks like:
// streamConfigs: [FORMAT_1, WIDTH_1, HEIGHT_1, TYPE_1,
// FORMAT_2, WIDTH_2, HEIGHT_2, TYPE_2, ...]
if (streamConfigs.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of stream configurations');
}
let supportedResolutions = [];
for (let i = 0; i < streamConfigs.length; i += numElementPerEntry) {
const [format, width, height, type] =
streamConfigs.slice(i, i + numElementPerEntry);
if (format === formatBlob && type === typeOutputStream) {
supportedResolutions.push([width, height]);
}
}
return supportedResolutions;
};
/**
* Gets supported video configurations for specific camera.
* @param {string} deviceId The renderer-facing device Id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {Promise<Array<Object>>} Promise of supported video configurations.
* Each configuration is represented as [width, height, maxFps].
*/
cca.mojo.getVideoConfigs = async function(deviceId) {
// Currently we use YUV format for both recording and previewing on Chrome.
const formatYuv = 35;
const oneSecondInNs = 1000000000;
const numElementPerEntry = 4;
let device = await cca.mojo.MojoInterface.getInstance().getDevice(deviceId);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
let {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const minFrameDurationConfigs = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_SCALER_AVAILABLE_MIN_FRAME_DURATIONS);
// The data of |minFrameDurationConfigs| looks like:
// minFrameDurationCOnfigs: [FORMAT_1, WIDTH_1, HEIGHT_1, DURATION_1,
// FORMAT_2, WIDTH_2, HEIGHT_2, DURATION_2,
// ...]
if (minFrameDurationConfigs.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of frame durations configs');
}
let supportedConfigs = [];
for (let i = 0; i < minFrameDurationConfigs.length; i += numElementPerEntry) {
const [format, width, height, minDuration] =
minFrameDurationConfigs.slice(i, i + numElementPerEntry);
if (format === formatYuv) {
const maxFps = Math.round(oneSecondInNs / minDuration);
supportedConfigs.push([width, height, maxFps]);
}
}
return supportedConfigs;
};
/**
* Gets camera facing for given device.
* @param {string} deviceId The renderer-facing device Id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {Promise<cros.mojom.CameraFacing>} Promise of device facing.
*/
cca.mojo.getCameraFacing = async function(deviceId) {
let device = await cca.mojo.MojoInterface.getInstance().getDevice(deviceId);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
let {cameraInfo} = await device.getCameraInfo();
return cameraInfo.facing;
};
/**
* Gets supported fps ranges for specific camera.
* @param {string} deviceId The renderer-facing device Id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @return {Promise<Array<Array<number>>>} Promise of supported fps ranges.
* Each range is represented as [min, max].
*/
cca.mojo.getSupportedFpsRanges = async function(deviceId) {
const numElementPerEntry = 2;
let device = await cca.mojo.MojoInterface.getInstance().getDevice(deviceId);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
let {cameraInfo} = await device.getCameraInfo();
const staticMetadata = cameraInfo.staticCameraCharacteristics;
const availableFpsRanges = cca.mojo.getMetadataData_(
staticMetadata,
cros.mojom.CameraMetadataTag
.ANDROID_CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
// The data of |availableFpsRanges| looks like:
// availableFpsRanges: [RANGE_1_MIN, RANGE_1_MAX,
// RANGE_2_MIN, RANGE_2_MAX, ...]
if (availableFpsRanges.length % numElementPerEntry != 0) {
throw new Error('Unexpected length of available fps range configs');
}
let supportedFpsRanges = [];
for (let i = 0; i < availableFpsRanges.length; i += numElementPerEntry) {
const [rangeMin, rangeMax] =
availableFpsRanges.slice(i, i + numElementPerEntry);
supportedFpsRanges.push([rangeMin, rangeMax]);
}
return supportedFpsRanges;
};
/**
* Gets user media with custom negotiation through CrOS Camera App API,
* such as frame rate range negotiation.
* @param {string} deviceId The renderer-facing device Id of the target camera
* which could be retrieved from MediaDeviceInfo.deviceId.
* @param {!MediaStreamConstraints} constraints The constraints that would be
* passed to get user media. If frame rate range negotiation is needed, the
* caller should either set exact field or set both min and max fields for
* frame rate property.
* @return {Promise<MediaStream>} Promise of the MediaStream that returned from
* MediaDevices.getUserMedia().
*/
cca.mojo.getUserMedia = async function(deviceId, constraints) {
let streamWidth = 0;
let streamHeight = 0;
let minFrameRate = 0;
let maxFrameRate = 0;
try {
if (constraints && constraints.video && constraints.video.frameRate) {
const frameRate = constraints.video.frameRate;
if (frameRate.exact) {
minFrameRate = frameRate.exact;
maxFrameRate = frameRate.exact;
} else if (frameRate.min && frameRate.max) {
minFrameRate = frameRate.min;
maxFrameRate = frameRate.max;
}
streamWidth = constraints.video.width;
if (typeof streamWidth !== 'number') {
throw new Error('streamWidth expected to be a number');
}
streamHeight = constraints.video.height;
if (typeof streamHeight !== 'number') {
throw new Error('streamHeight expected to be a number');
}
}
let hasSpecifiedFrameRateRange = minFrameRate > 0 && maxFrameRate > 0;
// If the frame rate range is specified in |constraints|, we should try to
// set the frame rate range and should report error if fails since it is
// unexpected.
//
// Otherwise, if the frame rate is incomplete or totally missing in
// |constraints| , we assume the app wants to use default frame rate range.
// We set the frame rate range to an invalid range (e.g. 0 fps) so that it
// will fallback to use the default one.
let device = await cca.mojo.MojoInterface.getInstance().getDevice(deviceId);
if (device === null) {
throw new Error('Failed to construct the device connection.');
}
const {isSuccess} = await device.setFpsRange(
{'width': streamWidth, 'height': streamHeight},
{'start': minFrameRate, 'end': maxFrameRate});
if (!isSuccess && hasSpecifiedFrameRateRange) {
console.error('Failed to negotiate the frame rate range.');
}
} catch (e) {
// Ignore HALv1 Error.
}
return navigator.mediaDevices.getUserMedia(constraints);
};
/**
* Closes all mojo devices connections.
*/
cca.mojo.closeConnections = function() {
cca.mojo.MojoInterface.getInstance().closeConnections();
};
// Copyright 2019 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
'use strict';
/**
* Namespace for the Camera app.
*/
var cca = cca || {};
/**
* Namespace for mojo.
*/
cca.mojo = cca.mojo || {};
/**
* Connector for using CrOS mojo interface.
*/
cca.mojo.MojoConnector = class {
/** @public */
constructor() {
/**
* @type {cros.mojom.CameraAppDeviceProviderRemote} An interface remote that
* used to construct the mojo interface.
* @private
*/
this.deviceProvider_ = cros.mojom.CameraAppDeviceProvider.getRemote();
/**
* @type {cca.mojo.DeviceOperator} The device operator that could
* operates video capture device through mojo interface.
* @private
*/
this.deviceOperator_ = null;
}
/**
* Enumerates the devices and construct mojo connection for each of them.
* @throws {Error} Thrown when there is any unexpected errors. Note that if it
* fails since it runs on camera hal v1 stack, no error will be thrown.
* @private
*/
async initDeviceOperator() {
const devices = await navigator.mediaDevices.enumerateDevices();
const videoDevices = devices.filter(({kind}) => kind === 'videoinput');
const connectedDevices = new Map();
for (const videoDevice of videoDevices) {
const result =
await this.deviceProvider_.getCameraAppDevice(videoDevice.deviceId);
switch (result.status) {
case cros.mojom.GetCameraAppDeviceStatus.SUCCESS:
connectedDevices.set(videoDevice.deviceId, result.device);
break;
case cros.mojom.GetCameraAppDeviceStatus.ERROR_NON_V3:
return;
default:
throw new Error(`Failed to connect to: ${
videoDevice.deviceId}, error: ${result.status}`);
}
}
this.deviceOperator_ = new cca.mojo.DeviceOperator(connectedDevices);
}
/**
* Resets the mojo connection. Once it is reset, the connector should be
* initialized again before being used.
*/
async reset() {
this.deviceOperator_ = null;
try {
await this.initDeviceOperator();
} catch (e) {
console.error(e);
}
}
/**
* Gets the device operator.
* @return {?cca.mojo.DeviceOperator} The video capture device operator. For
* non-v3 devices, it returns null.
*/
getDeviceOperator() {
return this.deviceOperator_;
}
};
......@@ -16,6 +16,7 @@ cca.views = cca.views || {};
/**
* Creates the camera-view controller.
* @param {cca.mojo.MojoConnector} mojoConnector
* @param {cca.models.ResultSaver} resultSaver
* @param {cca.device.DeviceInfoUpdater} infoUpdater
* @param {cca.device.PhotoResolPreferrer} photoPreferrer
......@@ -23,7 +24,7 @@ cca.views = cca.views || {};
* @constructor
*/
cca.views.Camera = function(
resultSaver, infoUpdater, photoPreferrer, videoPreferrer) {
mojoConnector, resultSaver, infoUpdater, photoPreferrer, videoPreferrer) {
cca.views.View.call(this, '#camera');
/**
......@@ -32,6 +33,12 @@ cca.views.Camera = function(
*/
this.infoUpdater_ = infoUpdater;
/**
* @type {cca.mojo.MojoConnector}
* @private
*/
this.mojoConnector_ = mojoConnector;
/**
* Layout handler for the camera view.
* @type {cca.views.camera.Layout}
......@@ -51,8 +58,8 @@ cca.views.Camera = function(
* @type {cca.views.camera.Options}
* @private
*/
this.options_ =
new cca.views.camera.Options(infoUpdater, this.restart.bind(this));
this.options_ = new cca.views.camera.Options(
infoUpdater, mojoConnector, this.restart.bind(this));
/**
* @type {HTMLElement}
......@@ -93,8 +100,8 @@ cca.views.Camera = function(
* @private
*/
this.modes_ = new cca.views.camera.Modes(
photoPreferrer, videoPreferrer, this.restart.bind(this), doSavePhoto,
createVideoSaver, doSaveVideo);
mojoConnector, photoPreferrer, videoPreferrer, this.restart.bind(this),
doSavePhoto, createVideoSaver, doSaveVideo);
/**
* @type {?string}
......@@ -252,11 +259,11 @@ cca.views.Camera.prototype.restart = function() {
this.started_,
Promise.resolve(!cca.state.get('taking') || this.endTake_()),
])
.finally(() => {
.finally(async () => {
// We should close all mojo connections since any communication to a
// closed stream should be avoided.
cca.mojo.closeConnections();
this.preview_.stop();
await this.mojoConnector_.reset();
this.start_();
return this.started_;
});
......@@ -270,6 +277,7 @@ cca.views.Camera.prototype.restart = function() {
*/
cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
let supportedModes = null;
const deviceOperator = this.mojoConnector_.getDeviceOperator();
for (const mode of this.modes_.getModeCandidates()) {
try {
if (!deviceId) {
......@@ -295,7 +303,10 @@ cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
}
for (const constraints of previewCandidates) {
try {
const stream = await cca.mojo.getUserMedia(deviceId, constraints);
if (deviceOperator) {
await deviceOperator.setFpsRange(deviceId, constraints);
}
const stream = await navigator.mediaDevices.getUserMedia(constraints);
if (!supportedModes) {
supportedModes = await this.modes_.getSupportedModes(stream);
if (!supportedModes.includes(mode)) {
......
......@@ -72,6 +72,7 @@ cca.views.camera.PhotoResult;
/**
* Mode controller managing capture sequence of different camera mode.
* @param {cca.mojo.MojoConnector} mojoConnector
* @param {cca.device.PhotoResolPreferrer} photoResolPreferrer
* @param {cca.device.VideoConstraintsPreferrer} videoPreferrer
* @param {!DoSwitchMode} doSwitchMode
......@@ -81,8 +82,8 @@ cca.views.camera.PhotoResult;
* @constructor
*/
cca.views.camera.Modes = function(
photoResolPreferrer, videoPreferrer, doSwitchMode, doSavePhoto,
createVideoSaver, doSaveVideo) {
mojoConnector, photoResolPreferrer, videoPreferrer, doSwitchMode,
doSavePhoto, createVideoSaver, doSaveVideo) {
/**
* @type {!DoSwitchMode}
* @private
......@@ -146,21 +147,14 @@ cca.views.camera.Modes = function(
},
'portrait-mode': {
captureFactory: () => new cca.views.camera.Portrait(
this.stream_, doSavePhoto, this.captureResolution_),
this.stream_, doSavePhoto, this.captureResolution_, mojoConnector),
isSupported: async (stream) => {
try {
const imageCapture =
new cca.mojo.ImageCapture(stream.getVideoTracks()[0]);
const capabilities = await imageCapture.getPhotoCapabilities();
return capabilities.supportedEffects &&
capabilities.supportedEffects.includes(
cros.mojom.Effect.PORTRAIT_MODE);
} catch (e) {
// The mode is considered unsupported for given stream. This includes
// the case where underlying camera HAL is V1 causing mojo connection
// unable to work.
const deviceOperator = mojoConnector.getDeviceOperator();
if (!deviceOperator) {
return false;
}
const deviceId = stream.getVideoTracks()[0].getSettings().deviceId;
return await deviceOperator.isPortraitModeSupported(deviceId);
},
resolutionConfig: photoResolPreferrer,
v1Config: cca.views.camera.Modes.getV1Constraints.bind(this, false),
......@@ -696,9 +690,11 @@ cca.views.camera.Square.prototype.cropSquare = async function(blob) {
* @param {MediaStream} stream
* @param {!DoSavePhoto} doSavePhoto
* @param {?[number, number]} captureResolution
* @param {cca.mojo.MojoConnector} mojoConnector
* @constructor
*/
cca.views.camera.Portrait = function(stream, doSavePhoto, captureResolution) {
cca.views.camera.Portrait = function(
stream, doSavePhoto, captureResolution, mojoConnector) {
cca.views.camera.Photo.call(this, stream, doSavePhoto, captureResolution);
/**
......@@ -708,6 +704,13 @@ cca.views.camera.Portrait = function(stream, doSavePhoto, captureResolution) {
*/
this.crosImageCapture_ = null;
/**
* Mojo connector that used to construct CrOS ImageCapture.
* @type {cca.mojo.MojoConnector}
* @private
*/
this.mojoConnector_ = mojoConnector;
// End of properties, seal the object.
Object.seal(this);
};
......@@ -722,8 +725,8 @@ cca.views.camera.Portrait.prototype = {
cca.views.camera.Portrait.prototype.start_ = async function() {
if (this.crosImageCapture_ == null) {
try {
this.crosImageCapture_ =
new cca.mojo.ImageCapture(this.stream_.getVideoTracks()[0]);
this.crosImageCapture_ = new cca.mojo.ImageCapture(
this.stream_.getVideoTracks()[0], this.mojoConnector_);
} catch (e) {
cca.toast.show('error_msg_take_photo_failed');
throw e;
......
......@@ -19,76 +19,27 @@ cca.views = cca.views || {};
*/
cca.views.camera = cca.views.camera || {};
/**
* Video device information queried from HALv3 mojo private API.
* @param {MediaDeviceInfo} deviceInfo Information of the video device.
* @param {cros.mojom.CameraFacing} facing Camera facing of the video device.
* @param {ResolList} photoResols Supported available photo resolutions of the
* video device.
* @param {Array<[number, number, number]>} videoResolFpses Supported available
* video resolutions and maximal capture fps of the video device.
* @param {FpsRangeInfo} fpsRanges Supported fps ranges of the video device.
*/
cca.views.camera.Camera3DeviceInfo = function(
deviceInfo, facing, photoResols, videoResolFpses, fpsRanges) {
/**
* @type {string}
* @public
*/
this.deviceId = deviceInfo.deviceId;
/**
* @type {cros.mojom.CameraFacing}
* @public
*/
this.facing = facing;
/**
* @type {ResolList}
* @public
*/
this.photoResols = photoResols;
/**
* @type {ResolList}
* @public
*/
this.videoResols = [];
/**
* @type {MaxFpsInfo}
* @public
*/
this.videoMaxFps = {};
/**
* @type {FpsRangeInfo}
* @public
*/
this.fpsRanges = fpsRanges;
// End of properties, seal the object.
Object.seal(this);
videoResolFpses.filter(([, , fps]) => fps >= 24).forEach(([w, h, fps]) => {
this.videoResols.push([w, h]);
this.videoMaxFps[[w, h]] = fps;
});
};
/**
* Creates a controller for the options of Camera view.
* @param {cca.device.DeviceInfoUpdater} infoUpdater
* @param {cca.mojo.MojoConnector} mojoConnector
* @param {function()} doSwitchDevice Callback to trigger device switching.
* @constructor
*/
cca.views.camera.Options = function(infoUpdater, doSwitchDevice) {
cca.views.camera.Options = function(
infoUpdater, mojoConnector, doSwitchDevice) {
/**
* @type {cca.device.DeviceInfoUpdater}
* @private
*/
this.infoUpdater_ = infoUpdater;
/**
* @type {cca.mojo.MojoConnector}
* @private
*/
this.mojoConnector_ = mojoConnector;
/**
* @type {function()}
* @private
......@@ -131,7 +82,7 @@ cca.views.camera.Options = function(infoUpdater, doSwitchDevice) {
/**
* Promise for querying Camera3DeviceInfo of all available video devices from
* mojo private API.
* @type {Promise<!Array<Camera3DeviceInfo>>}
* @type {Promise<!Array<cca.device.Camera3DeviceInfo>>}
* @private
*/
this.devicesPrivateInfo_ = null;
......@@ -334,20 +285,14 @@ cca.views.camera.Options.prototype.maybeRefreshVideoDeviceIds_ = function() {
this.devicesPrivateInfo_ = (async () => {
const devices = await this.videoDevices_;
try {
var privateInfos = await Promise.all(devices.map((d) => Promise.all([
d,
cca.mojo.getCameraFacing(d.deviceId),
cca.mojo.getPhotoResolutions(d.deviceId),
cca.mojo.getVideoConfigs(d.deviceId),
cca.mojo.getSupportedFpsRanges(d.deviceId),
])));
} catch (e) {
const deviceOperator = this.mojoConnector_.getDeviceOperator();
if (!deviceOperator) {
cca.state.set('no-resolution-settings', true);
throw new Error('HALv1-api');
}
return privateInfos.map(
(info) => new cca.views.camera.Camera3DeviceInfo(...info));
return await Promise.all(devices.map(
(d) => cca.device.Camera3DeviceInfo.create(d, deviceOperator)));
})();
(async () => {
......
......@@ -36,7 +36,9 @@
<script src="../js/mojo/geometry.mojom-lite.js"></script>
<script src="../js/mojo/range.mojom-lite.js"></script>
<script src="../js/mojo/camera_app.mojom-lite.js"></script>
<script src="../js/mojo/imagecapture.js"></script>
<script src="../js/mojo/device_operator.js"></script>
<script src="../js/mojo/mojo_connector.js"></script>
<script src="../js/mojo/image_capture.js"></script>
<script src="../js/views/view.js"></script>
<script src="../js/views/gallery_base.js"></script>
<script src="../js/views/camera.js"></script>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment