Commit c63865ac authored by Wei Lee's avatar Wei Lee Committed by Commit Bot

Add performance metrics in CCA

Design doc: go/cros-camera:dd:perf-test-metrics-cca

The CL on Tast should depends on this CL:
https://crrev.com/c/1880561

Bug: b/141518806
Test: tast run [DUT] camera.CCAUIPreview

Change-Id: I943d683106c80046abf507b63179e5885b4a19bc
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1880995
Commit-Queue: Wei Lee <wtlee@chromium.org>
Auto-Submit: Wei Lee <wtlee@chromium.org>
Reviewed-by: default avatarKuo Jen Wei <inker@chromium.org>
Reviewed-by: default avatarShik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#727337}
parent 047fcf16
......@@ -118,6 +118,7 @@ copy("chrome_camera_app_js") {
"src/js/metrics.js",
"src/js/namespace.js",
"src/js/nav.js",
"src/js/perf.js",
"src/js/sound.js",
"src/js/state.js",
"src/js/toast.js",
......
......@@ -40,6 +40,7 @@
<structure name="IDR_CAMERA_NAMESPACE_JS" file="src/js/namespace.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_PERF_JS" file="src/js/perf.js" type="chrome_html" />
<structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.js" type="chrome_html" />
<structure name="IDR_CAMERA_RECORDTIME_JS" file="src/js/views/camera/recordtime.js" type="chrome_html" />
<structure name="IDR_CAMERA_RESULT_SAVER_JS" file="src/js/models/result_saver.js" type="chrome_html" />
......
......@@ -26,6 +26,7 @@ js_type_check("compile_resources") {
":metrics",
":namespace",
":nav",
":perf",
":sound",
":state",
":toast",
......@@ -93,6 +94,9 @@ js_library("nav") {
]
}
js_library("perf") {
}
js_library("state") {
}
......@@ -107,6 +111,7 @@ js_library("background_ops") {
deps = [
":chrome_util",
":intent",
":perf",
]
}
......
......@@ -40,6 +40,13 @@ cca.bg.INITIAL_ASPECT_RATIO = 1.7777777777;
*/
cca.bg.TOPBAR_COLOR = '#000000';
/**
* The id of the test app used in Tast.
* @type {string}
* @const
*/
cca.bg.TEST_API_ID = 'behllobkkfkfnphdnhnkndlbkcpglgmj';
/**
* It's used in test to ensure that we won't connect to the main.html target
* before the window is created, otherwise the window might disappear.
......@@ -47,6 +54,13 @@ cca.bg.TOPBAR_COLOR = '#000000';
*/
cca.bg.onAppWindowCreatedForTesting = null;
/**
* It's used in test to catch the perf event before the creation of app window
* for time measurement before launch.
* @type {?cca.perf.PerfLogger}
*/
cca.bg.perfLoggerForTesting = null;
/**
* Background object for handling launch event.
* @type {?cca.bg.Background}
......@@ -80,10 +94,12 @@ cca.bg.Window = class {
* suspended state.
* @param {!function(cca.bg.Window)} onClosed Called when window become closed
* state.
* @param {?cca.perf.PerfLogger} perfLogger The logger for perf events. If it
* is null, we will create a new one for the window.
* @param {cca.intent.Intent=} intent Intent to be handled by the app window.
* Set to null for app window not launching from intent.
*/
constructor(onActive, onSuspended, onClosed, intent = null) {
constructor(onActive, onSuspended, onClosed, perfLogger, intent = null) {
/**
* @type {!function(!cca.bg.Window)}
* @private
......@@ -108,6 +124,12 @@ cca.bg.Window = class {
*/
this.intent_ = intent;
/**
* @type {!cca.perf.PerfLogger}
* @private
*/
this.perfLogger_ = perfLogger || new cca.perf.PerfLogger();
/**
* @type {?chrome.app.window.AppWindow}
* @private
......@@ -165,6 +187,8 @@ cca.bg.Window = class {
},
},
(appWindow) => {
this.perfLogger_.start(
cca.perf.PerfEvent.LAUNCHING_FROM_WINDOW_CREATION);
this.appWindow_ = appWindow;
this.appWindow_.onClosed.addListener(() => {
chrome.storage.local.set({maximized: appWindow.isMaximized()});
......@@ -218,6 +242,13 @@ cca.bg.Window = class {
this.onSuspended_(this);
}
/**
* @override
*/
getPerfLogger() {
return this.perfLogger_;
}
/**
* Suspends the app window.
*/
......@@ -338,7 +369,10 @@ cca.bg.Background = class {
this.processPendingIntent_();
}
};
return new cca.bg.Window(onActive, onSuspended, onClosed);
const wnd = new cca.bg.Window(
onActive, onSuspended, onClosed, cca.bg.perfLoggerForTesting);
cca.bg.perfLoggerForTesting = null;
return wnd;
}
/**
......@@ -378,7 +412,10 @@ cca.bg.Background = class {
this.launcherWindow_.resume();
}
};
return new cca.bg.Window(onActive, onSuspended, onClosed, intent);
const wnd = new cca.bg.Window(
onActive, onSuspended, onClosed, cca.bg.perfLoggerForTesting, intent);
cca.bg.perfLoggerForTesting = null;
return wnd;
}
/**
......@@ -468,7 +505,7 @@ cca.bg.Background = class {
* asynchronously.
*/
cca.bg.handleExternalMessageFromTest = function(message, sender, sendResponse) {
if (sender.id !== 'behllobkkfkfnphdnhnkndlbkcpglgmj') {
if (sender.id !== cca.bg.TEST_API_ID) {
console.warn(`Unknown sender id: ${sender.id}`);
return;
}
......@@ -481,6 +518,40 @@ cca.bg.handleExternalMessageFromTest = function(message, sender, sendResponse) {
}
};
/**
* Handles connection from the test extension used in Tast.
* @param {Port} port The port that used to do two-way communication.
*/
cca.bg.handleExternalConnectionFromTest = function(port) {
if (port.sender.id !== cca.bg.TEST_API_ID) {
console.warn(`Unknown sender id: ${port.sender.id}`);
return;
}
switch (port.name) {
case 'SET_PERF_CONNECTION':
port.onMessage.addListener((event) => {
if (cca.bg.perfLoggerForTesting === null) {
cca.bg.perfLoggerForTesting = new cca.perf.PerfLogger();
cca.bg.perfLoggerForTesting.addListener((event, duration, extras) => {
port.postMessage({event, duration, extras});
});
}
const {name} = event;
if (name !== cca.perf.PerfEvent.LAUNCHING_FROM_LAUNCH_APP_COLD &&
name !== cca.perf.PerfEvent.LAUNCHING_FROM_LAUNCH_APP_WARM) {
console.warn(`Unknown event name from test: ${name}`);
return;
}
cca.bg.perfLoggerForTesting.start(name);
});
return;
default:
console.warn(`Unknown port name: ${port.name}`);
}
};
chrome.app.runtime.onLaunched.addListener((launchData) => {
if (!cca.bg.background) {
cca.bg.background = new cca.bg.Background();
......@@ -499,3 +570,6 @@ chrome.app.runtime.onLaunched.addListener((launchData) => {
chrome.runtime.onMessageExternal.addListener(
cca.bg.handleExternalMessageFromTest);
chrome.runtime.onConnectExternal.addListener(
cca.bg.handleExternalConnectionFromTest);
......@@ -51,6 +51,13 @@ cca.bg.BackgroundOps = class {
*/
getIntent() {}
/**
* Gets the perf logger associate with cca.bg.Window object.
* @return {!cca.perf.PerfLogger}
* @abstract
*/
getPerfLogger() {}
/**
* Called by foreground window when it's active.
* @abstract
......
......@@ -233,8 +233,13 @@ cca.device.VideoConstraintsPreferrer =
/** @type {string} */ (this.deviceId_), this.resolution_,
this.toggleFps_.checked ? 60 : 30);
cca.state.set('mode-switching', true);
this.doReconfigureStream_().finally(
() => cca.state.set('mode-switching', false));
let hasError = false;
this.doReconfigureStream_()
.catch((error) => {
hasError = true;
throw error;
})
.finally(() => cca.state.set('mode-switching', false, {hasError}));
});
}
......
......@@ -39,14 +39,14 @@ cca.App = class {
* @private
*/
this.photoPreferrer_ = new cca.device.PhotoConstraintsPreferrer(
() => this.cameraView_.restart());
() => this.cameraView_.start());
/**
* @type {!cca.device.VideoConstraintsPreferrer}
* @private
*/
this.videoPreferrer_ = new cca.device.VideoConstraintsPreferrer(
() => this.cameraView_.restart());
() => this.cameraView_.start());
/**
* @type {!cca.device.DeviceInfoUpdater}
......@@ -184,9 +184,16 @@ cca.App = class {
.finally(() => {
cca.metrics.log(cca.metrics.Type.LAUNCH, ackMigrate);
});
await cca.util.fitWindow();
chrome.app.window.current().show();
this.backgroundOps_.notifyActivation();
const showWindow = (async () => {
await cca.util.fitWindow();
chrome.app.window.current().show();
this.backgroundOps_.notifyActivation();
})();
const startCamera = (async () => {
const isSuccess = await this.cameraView_.start();
this.backgroundOps_.getPerfLogger().stopLaunch({hasError: !isSuccess});
})();
return Promise.all([showWindow, startCamera]);
}
/**
......@@ -205,7 +212,7 @@ cca.App = class {
*/
async suspend() {
cca.state.set('suspend', true);
await this.cameraView_.restart();
await this.cameraView_.start();
chrome.app.window.current().hide();
this.backgroundOps_.notifySuspension();
}
......@@ -235,7 +242,36 @@ document.addEventListener('DOMContentLoaded', async () => {
return;
}
assert(window['backgroundOps'] !== undefined);
const /** !cca.bg.BackgroundOps */ bgOps = window['backgroundOps'];
const perfLogger = bgOps.getPerfLogger();
// Setup listener for performance events.
perfLogger.addListener((event, duration, extras) => {
cca.metrics.log(cca.metrics.Type.PERF, event, duration, extras);
});
const states = Object.values(cca.perf.PerfEvent);
states.push('taking');
states.forEach((state) => {
cca.state.addObserver(state, (val, extras) => {
let event = state;
if (state === 'taking') {
// 'taking' state indicates either taking photo or video. Skips for
// video-taking case since we only want to collect the metrics of
// photo-taking.
if (cca.state.get('video')) {
return;
}
event = cca.perf.PerfEvent.PHOTO_TAKING;
}
if (val) {
perfLogger.start(event);
} else {
perfLogger.stop(event, extras);
}
});
});
cca.App.instance_ = new cca.App(
/** @type {!cca.bg.BackgroundOps} */ (window['backgroundOps']));
/** @type {!cca.bg.BackgroundOps} */ (bgOps));
await cca.App.instance_.start();
});
......@@ -110,13 +110,13 @@ cca.metrics.IntentResultType = {
* Returns event builder for the metrics type: capture.
* @param {?string} facingMode Camera facing-mode of the capture.
* @param {number} length Length of 1 minute buckets for captured video.
* @param {!{width: number, height: number}} resolution Capture resolution.
* @param {!Resolution} resolution Capture resolution.
* @param {!cca.metrics.IntentResultType} intentResult
* @return {!analytics.EventBuilder}
* @private
*/
cca.metrics.captureType_ = function(
facingMode, length, {width, height}, intentResult) {
facingMode, length, resolution, intentResult) {
var condState = (states, cond = undefined, strict = undefined) => {
// Return the first existing state among the given states only if there is
// no gate condition or the condition is met.
......@@ -138,12 +138,30 @@ cca.metrics.captureType_ = function(
.dimen(7, condState(['mic'], Mode.VIDEO, true))
.dimen(8, condState(['max-wnd']))
.dimen(9, condState(['tall']))
.dimen(10, `${width}x${height}`)
.dimen(10, resolution.toString())
.dimen(11, condState(['_30fps', '_60fps'], Mode.VIDEO, true))
.dimen(12, intentResult)
.value(length || 0);
};
/**
* Returns event builder for the metrics type: perf.
* @param {string} event The target event type.
* @param {number} duration The duration of the event in ms.
* @param {Object=} extras Optional information for the event.
* @return {!analytics.EventBuilder}
* @private
*/
cca.metrics.perfType_ = function(event, duration, extras = {}) {
const {resolution = ''} = extras;
return cca.metrics.base_.category('perf')
.action(event)
// Round the duration here since GA expects that the value is an integer.
// Reference: https://support.google.com/analytics/answer/1033068
.value(Math.round(duration))
.dimen(3, `${resolution}`);
};
/**
* Metrics types.
* @enum {function(...): !analytics.EventBuilder}
......@@ -151,6 +169,7 @@ cca.metrics.captureType_ = function(
cca.metrics.Type = {
LAUNCH: cca.metrics.launchType_,
CAPTURE: cca.metrics.captureType_,
PERF: cca.metrics.perfType_,
};
/**
......
// 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 perf.
*/
cca.perf = cca.perf || {};
/**
* Type for performance event.
* @enum {string}
*/
cca.perf.PerfEvent = {
PHOTO_TAKING: 'photo-taking',
PHOTO_CAPTURE_POST_PROCESSING: 'photo-capture-post-processing',
VIDEO_CAPTURE_POST_PROCESSING: 'video-capture-post-processing',
PORTRAIT_MODE_CAPTURE_POST_PROCESSING:
'portrait-mode-capture-post-processing',
MODE_SWITCHING: 'mode-switching',
CAMERA_SWITCHING: 'camera-switching',
LAUNCHING_FROM_WINDOW_CREATION: 'launching-from-window-creation',
LAUNCHING_FROM_LAUNCH_APP_COLD: 'launching-from-launch-app-cold',
LAUNCHING_FROM_LAUNCH_APP_WARM: 'launching-from-launch-app-warm',
};
/* eslint-disable no-unused-vars */
/**
* @typedef {function(cca.perf.PerfEvent, number, Object=)}
*/
var PerfEventListener;
/* eslint-enable no-unused-vars */
/**
* Logger for performance events.
*/
cca.perf.PerfLogger = class {
/**
* @public
*/
constructor() {
/**
* Map to store events starting timestamp.
* @type {!Map<cca.perf.PerfEvent, number>}
* @private
*/
this.startTimeMap_ = new Map();
/**
* Set of the listeners for perf events.
* @type {!Set<PerfEventListener>}
*/
this.listeners_ = new Set();
/**
* The timestamp when the measurement is interrupted.
* @type {?number}
*/
this.interruptedTime_ = null;
}
/**
* Adds listener for perf events.
* @param {!PerfEventListener} listener
*/
addListener(listener) {
this.listeners_.add(listener);
}
/**
* Removes listener for perf events.
* @param {!PerfEventListener} listener
* @return {boolean} Returns true if remove successfully. False otherwise.
*/
removeListener(listener) {
return this.listeners_.delete(listener);
}
/**
* Starts the measurement for given event.
* @param {cca.perf.PerfEvent} event Target event.
*/
start(event) {
if (this.startTimeMap_.has(event)) {
console.error(`Failed to start event ${event} since the previous one is
not stopped.`);
return;
}
this.startTimeMap_.set(event, performance.now());
}
/**
* Stops the measurement for given event and returns the measurement result.
* @param {cca.perf.PerfEvent} event Target event.
* @param {PerfInformation=} perfInfo Optional information of this event for
* performance measurement.
*/
stop(event, perfInfo = {}) {
if (!this.startTimeMap_.has(event)) {
console.error(`Failed to stop event ${event} which is never started.`);
return;
}
const startTime = this.startTimeMap_.get(event);
this.startTimeMap_.delete(event);
// If there is error during performance measurement, drop it since it might
// be inaccurate.
if (perfInfo.hasError) {
return;
}
// If the measurement is interrupted, drop the measurement since the result
// might be inaccurate.
if (this.interruptedTime_ !== null && startTime < this.interruptedTime_) {
return;
}
const duration = performance.now() - startTime;
this.listeners_.forEach((listener) => listener(event, duration, perfInfo));
}
/**
* Stops the measurement of launch-related events.
* @param {PerfInformation=} perfInfo Optional information of this event for
* performance measurement.
*/
stopLaunch(perfInfo) {
const launchEvents = [
cca.perf.PerfEvent.LAUNCHING_FROM_WINDOW_CREATION,
cca.perf.PerfEvent.LAUNCHING_FROM_LAUNCH_APP_COLD,
cca.perf.PerfEvent.LAUNCHING_FROM_LAUNCH_APP_WARM,
];
launchEvents.forEach((event) => {
if (this.startTimeMap_.has(event)) {
this.stop(event, perfInfo);
}
});
}
/**
* Records the time of the interruption.
*/
interrupt() {
this.interruptedTime_ = performance.now();
}
};
......@@ -14,8 +14,17 @@ var cca = cca || {};
*/
cca.state = cca.state || {};
/* eslint-disable no-unused-vars */
/**
* @typedef {function(boolean, PerfInformation=)}
*/
var StateObserver;
/* eslint-enable no-unused-vars */
/**
* @type {!Map<string, Set<!function(boolean)>>}
* @type {!Map<string, Set<!StateObserver>>}
* @private
*/
cca.state.observers_ = new Map();
......@@ -23,8 +32,8 @@ cca.state.observers_ = new Map();
/**
* Adds observer function to be called on any state change.
* @param {string} state State to be observed.
* @param {!function(boolean)} observer Observer function called with newly
* changed value.
* @param {!StateObserver} observer Observer function called with
* newly changed value.
*/
cca.state.addObserver = function(state, observer) {
let observers = cca.state.observers_.get(state);
......@@ -38,7 +47,7 @@ cca.state.addObserver = function(state, observer) {
/**
* Removes observer function to be called on state change.
* @param {string} state State to remove observer from.
* @param {!function(boolean)} observer Observer function to be removed.
* @param {!StateObserver} observer Observer function to be removed.
* @return {boolean} Whether the observer is in the set and is removed
* successfully or not.
*/
......@@ -60,16 +69,20 @@ cca.state.get = function(state) {
};
/**
* Sets the specified state on or off.
* Sets the specified state on or off. Optionally, pass the information for
* performance measurement.
* @param {string} state State to be set.
* @param {boolean} val True to set the state on, false otherwise.
* @param {PerfInformation=} perfInfo Optional information of this state for
* performance measurement.
*/
cca.state.set = function(state, val) {
cca.state.set = function(state, val, perfInfo = {}) {
const oldVal = cca.state.get(state);
if (oldVal === val) {
return;
}
document.body.classList.toggle(state, val);
const observers = cca.state.observers_.get(state) || [];
observers.forEach((f) => f(val));
observers.forEach((f) => f(val, perfInfo));
};
......@@ -91,6 +91,14 @@ var Mode = {
PORTRAIT: 'portrait',
};
/**
* @typedef {{
* hasError: (boolean|undefined),
* resolution: (Resolution|undefined),
* }}
*/
var PerfInformation;
/**
* @typedef {{
* width: number,
......
......@@ -24,6 +24,7 @@ js_type_check("compile_resources") {
js_library("camera") {
deps = [
"..:background_ops",
"..:chrome_util",
"..:metrics",
"..:type",
......
......@@ -77,7 +77,7 @@ cca.views.Camera = class extends cca.views.View {
* @type {!cca.views.camera.Preview}
* @private
*/
this.preview_ = new cca.views.camera.Preview(this.restart.bind(this));
this.preview_ = new cca.views.camera.Preview(this.start.bind(this));
/**
* Options for the camera.
......@@ -85,7 +85,7 @@ cca.views.Camera = class extends cca.views.View {
* @private
*/
this.options_ =
new cca.views.camera.Options(infoUpdater, this.restart.bind(this));
new cca.views.camera.Options(infoUpdater, this.start.bind(this));
/**
* @type {!cca.models.ResultSaver}
......@@ -115,7 +115,7 @@ cca.views.Camera = class extends cca.views.View {
*/
this.modes_ = new cca.views.camera.Modes(
this.defaultMode_, photoPreferrer, videoPreferrer,
this.restart.bind(this), this.doSavePhoto_.bind(this), createVideoSaver,
this.start.bind(this), this.doSavePhoto_.bind(this), createVideoSaver,
this.doSaveVideo_.bind(this), playShutterEffect);
/**
......@@ -164,12 +164,12 @@ cca.views.Camera = class extends cca.views.View {
chrome.idle.onStateChanged.addListener((newState) => {
this.locked_ = (newState === 'locked');
if (this.locked_) {
this.restart();
this.start();
}
});
chrome.app.window.current().onMinimized.addListener(() => this.restart());
chrome.app.window.current().onMinimized.addListener(() => this.start());
this.configuring_ = this.start_();
this.configuring_ = null;
}
/**
......@@ -204,17 +204,19 @@ cca.views.Camera = class extends cca.views.View {
cca.state.set('taking', true);
this.focus(); // Refocus the visible shutter button for ChromeVox.
this.take_ = (async () => {
let hasError = false;
try {
await cca.views.camera.timertick.start();
await this.modes_.current.startCapture();
} catch (e) {
hasError = true;
if (e && e.message === 'cancel') {
return;
}
console.error(e);
} finally {
this.take_ = null;
cca.state.set('taking', false);
cca.state.set('taking', false, {hasError});
this.focus(); // Refocus the visible shutter button for ChromeVox.
}
})();
......@@ -290,10 +292,10 @@ cca.views.Camera = class extends cca.views.View {
/**
* Stops camera and tries to start camera stream again if possible.
* @return {!Promise<boolean>} Promise resolved to whether restart camera
* @return {!Promise<boolean>} Promise resolved to whether start camera
* successfully.
*/
async restart() {
async start() {
// To prevent multiple callers enter this function at the same time, wait
// until previous caller resets configuring to null.
while (this.configuring_ !== null) {
......@@ -418,6 +420,7 @@ cca.views.Camera = class extends cca.views.View {
throw new cca.views.CameraSuspendedError();
});
this.configuring_ = null;
return true;
} catch (error) {
this.activeDeviceId_ = null;
......@@ -433,6 +436,10 @@ cca.views.Camera = class extends cca.views.View {
this.retryStartTimeout_ = setTimeout(() => {
this.configuring_ = this.start_();
}, 100);
assert(window['backgroundOps'] !== undefined);
const /** !cca.bg.BackgroundOps */ bgOps = window['backgroundOps'];
bgOps.getPerfLogger().interrupt();
return false;
}
}
......
......@@ -36,7 +36,6 @@ js_library("modes") {
"../..:sound",
"../..:toast",
"../..:type",
"../..:type",
"../..:util",
"../../device:constraints_preferrer",
"../../models:filenamer",
......
......@@ -19,6 +19,11 @@ cca.views = cca.views || {};
*/
cca.views.camera = cca.views.camera || {};
/**
* import {Resolution} from '../type.js';
*/
var Resolution = Resolution || {};
/**
* import {Mode} from '../../type.js';
*/
......@@ -285,13 +290,13 @@ cca.views.camera.Modes = class {
event.preventDefault();
}
});
element.addEventListener('change', (event) => {
element.addEventListener('change', async (event) => {
if (element.checked) {
var mode = element.dataset.mode;
this.updateModeUI_(mode);
cca.state.set('mode-switching', true);
this.doSwitchMode_().then(
() => cca.state.set('mode-switching', false));
const isSuccess = await this.doSwitchMode_();
cca.state.set('mode-switching', false, {hasError: !isSuccess});
}
});
});
......@@ -639,10 +644,18 @@ cca.views.camera.Video = class extends cca.views.camera.ModeBase {
}
cca.sound.play('#sound-rec-end');
const {width, height} = this.stream_.getVideoTracks()[0].getSettings();
await this.doSaveVideo_(
{resolution: {width, height}, duration, videoSaver},
(new cca.models.Filenamer()).newVideoName());
const settings = this.stream_.getVideoTracks()[0].getSettings();
const resolution = new Resolution(settings.width, settings.height);
cca.state.set('video-capture-post-processing', true);
try {
await this.doSaveVideo_(
{resolution, duration, videoSaver},
(new cca.models.Filenamer()).newVideoName());
cca.state.set('video-capture-post-processing', false, {resolution});
} catch (e) {
cca.state.set('video-capture-post-processing', false, {hasError: true});
throw e;
}
}
/**
......@@ -760,26 +773,20 @@ cca.views.camera.Photo = class extends cca.views.camera.ModeBase {
new cca.mojo.ImageCapture(this.stream_.getVideoTracks()[0]);
}
const imageName = (new cca.models.Filenamer()).newImageName();
if (this.metadataObserverId_ !== null) {
this.metadataNames_.push(cca.models.Filenamer.getMetadataName(imageName));
}
try {
var result = await this.createPhotoResult_();
} catch (e) {
cca.toast.show('error_msg_take_photo_failed');
throw e;
}
await this.doSavePhoto_(result, imageName);
await this.takePhoto_();
}
/**
* Takes a photo and returns capture result.
* @return {!Promise<!cca.views.camera.PhotoResult>} Image capture result.
* Takes and saves a photo.
* @return {!Promise}
* @private
*/
async createPhotoResult_() {
async takePhoto_() {
const imageName = (new cca.models.Filenamer()).newImageName();
if (this.metadataObserverId_ !== null) {
this.metadataNames_.push(cca.models.Filenamer.getMetadataName(imageName));
}
let photoSettings;
if (this.captureResolution_) {
photoSettings = /** @type {!PhotoSettings} */ ({
......@@ -797,10 +804,15 @@ cca.views.camera.Photo = class extends cca.views.camera.ModeBase {
try {
const results = await this.crosImageCapture_.takePhoto(photoSettings);
this.playShutterEffect_();
cca.state.set('photo-capture-post-processing', true);
const blob = await results[0];
const {width, height} = await cca.util.blobToImage(blob);
return {resolution: {width, height}, blob};
const image = await cca.util.blobToImage(blob);
const resolution = new Resolution(image.width, image.height);
await this.doSavePhoto_({resolution, blob}, imageName);
cca.state.set('photo-capture-post-processing', false, {resolution});
} catch (e) {
cca.state.set('photo-capture-post-processing', false, {hasError: true});
cca.toast.show('error_msg_take_photo_failed');
throw e;
}
......@@ -983,6 +995,8 @@ cca.views.camera.Portrait = class extends cca.views.camera.Photo {
throw e;
}
cca.state.set('portrait-mode-capture-post-processing', true);
let hasError = false;
const [refSave, portraitSave] = [
[reference, refImageName],
[portrait, portraitImageName],
......@@ -991,6 +1005,7 @@ cca.views.camera.Portrait = class extends cca.views.camera.Photo {
try {
var blob = await p;
} catch (e) {
hasError = true;
cca.toast.show(
isPortrait ? 'error_msg_take_portrait_photo_failed' :
'error_msg_take_photo_failed');
......@@ -1002,9 +1017,11 @@ cca.views.camera.Portrait = class extends cca.views.camera.Photo {
try {
await portraitSave;
} catch (e) {
hasError = true;
// Portrait image may failed due to absence of human faces.
// TODO(inker): Log non-intended error.
}
await refSave;
cca.state.set('portrait-mode-capture-post-processing', false, {hasError});
}
};
......@@ -136,6 +136,7 @@ cca.views.camera.Options = class {
if (!cca.state.get('streaming') || cca.state.get('taking')) {
return;
}
cca.state.set('camera-switching', true);
const devices = await this.infoUpdater_.getDevicesInfo();
cca.util.animateOnce(
/** @type {!HTMLElement} */ (document.querySelector('#switch-device')));
......@@ -148,7 +149,8 @@ cca.views.camera.Options = class {
index = (index + 1) % devices.length;
this.videoDeviceId_ = devices[index].deviceId;
}
await this.doSwitchDevice_();
const isSuccess = await this.doSwitchDevice_();
cca.state.set('camera-switching', false, {hasError: !isSuccess});
}
/**
......
......@@ -135,7 +135,7 @@ cca.views.CameraIntent = class extends cca.views.Camera {
await take;
cca.state.set('suspend', true);
await this.restart();
await this.start();
const confirmed = await (() => {
if (this.photoResult_ !== null) {
return this.reviewResult_.openPhoto(this.photoResult_.blob);
......@@ -159,7 +159,7 @@ cca.views.CameraIntent = class extends cca.views.Camera {
this.focus(); // Refocus the visible shutter button for ChromeVox.
cca.state.set('suspend', false);
await this.intent_.clearData();
await this.restart();
await this.start();
})();
}
......
......@@ -10,6 +10,7 @@
<script src="../js/mojo/camera_intent.mojom-lite.js"></script>
<script src="../js/mojo/camera_app_helper.mojom-lite.js"></script>
<script type="module" src="../js/mojo/chrome_helper.js"></script>
<script src="../js/perf.js"></script>
<script src="../js/type.js"></script>
<script src="../js/intent.js"></script>
<script src="../js/background_ops.js"></script>
......
......@@ -14,6 +14,7 @@
<script src="../js/browser_proxy/browser_proxy.js"></script>
<script src="../js/google-analytics-bundle.js"></script>
<script src="../js/type.js"></script>
<script src="../js/perf.js"></script>
<script src="../js/metrics.js"></script>
<script src="../js/intent.js"></script>
<script src="../js/util.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