Commit f8f8df63 authored by Kuo Jen Wei's avatar Kuo Jen Wei Committed by Commit Bot

[CCA] Add camera intent view handling and returning intent result.

Bug: 980812
Test: Send intent from ARC++ app, see if result is correctly return to
app.

Change-Id: I2810a31879abf80657aa75becde3e2a99ddcf1ff
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1804940
Auto-Submit: Kuo Jen Wei <inker@chromium.org>
Reviewed-by: default avatarShik Chen <shik@chromium.org>
Commit-Queue: Shik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#701960}
parent af84347b
......@@ -79,6 +79,9 @@ copy("chrome_camera_app_images") {
"src/images/camera_button_timer_on_10s.svg",
"src/images/camera_button_timer_on_3s.svg",
"src/images/camera_focus_aim.svg",
"src/images/camera_intent_play_video.svg",
"src/images/camera_intent_result_cancel.svg",
"src/images/camera_intent_result_confirm.svg",
"src/images/camera_mode_photo.svg",
"src/images/camera_mode_portrait.svg",
"src/images/camera_mode_square.svg",
......@@ -145,6 +148,7 @@ copy("chrome_camera_app_js_device") {
"src/js/device/camera3_device_info.js",
"src/js/device/constraints_preferrer.js",
"src/js/device/device_info_updater.js",
"src/js/device/error.js",
]
outputs = [
......@@ -154,11 +158,13 @@ copy("chrome_camera_app_js_device") {
copy("chrome_camera_app_js_models") {
sources = [
"src/js/models/file_video_saver.js",
"src/js/models/filenamer.js",
"src/js/models/filesystem.js",
"src/js/models/gallery.js",
"src/js/models/intent_video_saver.js",
"src/js/models/result_saver.js",
"src/js/models/video_saver.js",
"src/js/models/video_saver_interface.js",
]
outputs = [
......@@ -182,6 +188,7 @@ copy("chrome_camera_app_js_views") {
sources = [
"src/js/views/browser.js",
"src/js/views/camera.js",
"src/js/views/camera_intent.js",
"src/js/views/dialog.js",
"src/js/views/gallery_base.js",
"src/js/views/settings.js",
......@@ -201,6 +208,7 @@ copy("chrome_camera_app_js_views_camera") {
"src/js/views/camera/options.js",
"src/js/views/camera/preview.js",
"src/js/views/camera/recordtime.js",
"src/js/views/camera/review_result.js",
"src/js/views/camera/timertick.js",
]
......
......@@ -93,6 +93,9 @@ RESOURCES = \
src/images/camera_button_timer_on_10s.svg \
src/images/camera_button_timer_on_3s.svg \
src/images/camera_focus_aim.svg \
src/images/camera_intent_play_video.svg \
src/images/camera_intent_result_cancel.svg \
src/images/camera_intent_result_confirm.svg \
src/images/camera_mode_photo.svg \
src/images/camera_mode_portrait.svg \
src/images/camera_mode_square.svg \
......@@ -119,16 +122,19 @@ RESOURCES = \
src/js/device/camera3_device_info.js \
src/js/device/constraints_preferrer.js \
src/js/device/device_info_updater.js \
src/js/device/error.js \
src/js/gallerybutton.js \
src/js/google-analytics-bundle.js \
src/js/intent.js \
src/js/main.js \
src/js/metrics.js \
src/js/models/file_video_saver.js \
src/js/models/filenamer.js \
src/js/models/filesystem.js \
src/js/models/gallery.js \
src/js/models/intent_video_saver.js \
src/js/models/result_saver.js \
src/js/models/video_saver.js \
src/js/models/video_saver_interface.js \
src/js/mojo/chrome_helper.js \
src/js/mojo/device_operator.js \
src/js/mojo/image_capture.js \
......@@ -142,11 +148,13 @@ RESOURCES = \
src/js/util.js \
src/js/views/browser.js \
src/js/views/camera.js \
src/js/views/camera_intent.js \
src/js/views/camera/layout.js \
src/js/views/camera/modes.js \
src/js/views/camera/options.js \
src/js/views/camera/preview.js \
src/js/views/camera/recordtime.js \
src/js/views/camera/review_result.js \
src/js/views/camera/timertick.js \
src/js/views/dialog.js \
src/js/views/gallery_base.js \
......
......@@ -17,11 +17,13 @@
<structure name="IDR_CAMERA_BUNDLE_JS" file="src/js/google-analytics-bundle.js" type="chrome_html" />
<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_CAMERA_INTENT_JS" file="src/js/views/camera_intent.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_CHROME_HELPER_JS" file="src/js/mojo/chrome_helper.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_ERROR_JS" file="src/js/device/error.js" type="chrome_html" />
<structure name="IDR_CAMERA_FILENAMER_JS" file="src/js/models/filenamer.js" type="chrome_html" />
<structure name="IDR_CAMERA_FILESYSTEM_JS" file="src/js/models/filesystem.js" type="chrome_html" />
<structure name="IDR_CAMERA_GALLERY_BASE_JS" file="src/js/views/gallery_base.js" type="chrome_html" />
......@@ -42,6 +44,7 @@
<structure name="IDR_CAMERA_RECORDTIME_JS" file="src/js/views/camera/recordtime.js" type="chrome_html" />
<structure name="IDR_CAMERA_RESOLUTION_EVENT_BROKER_JS" file="src/js/resolution_event_broker.js" type="chrome_html" />
<structure name="IDR_CAMERA_RESULT_SAVER_JS" file="src/js/models/result_saver.js" type="chrome_html" />
<structure name="IDR_CAMERA_REVIEW_RESULT_JS" file="src/js/views/camera/review_result.js" type="chrome_html" />
<structure name="IDR_CAMERA_SCROLLBAR_JS" file="src/js/scrollbar.js" type="chrome_html" />
<structure name="IDR_CAMERA_SETTINGS_JS" file="src/js/views/settings.js" type="chrome_html" />
<structure name="IDR_CAMERA_SOUND_JS" file="src/js/sound.js" type="chrome_html" />
......@@ -50,7 +53,9 @@
<structure name="IDR_CAMERA_TOAST_JS" file="src/js/toast.js" type="chrome_html" />
<structure name="IDR_CAMERA_TOOLTIP_JS" file="src/js/tooltip.js" type="chrome_html" />
<structure name="IDR_CAMERA_UTIL_JS" file="src/js/util.js" type="chrome_html" />
<structure name="IDR_CAMERA_VIDEO_SAVER_JS" file="src/js/models/video_saver.js" type="chrome_html" />
<structure name="IDR_CAMERA_VIDEO_SAVER_INTERFACE_JS" file="src/js/models/video_saver_interface.js" type="chrome_html" />
<structure name="IDR_CAMERA_INTENT_VIDEO_SAVER_JS" file="src/js/models/file_video_saver.js" type="chrome_html" />
<structure name="IDR_CAMERA_FILE_VIDEO_SAVER_JS" file="src/js/models/intent_video_saver.js" type="chrome_html" />
<structure name="IDR_CAMERA_VIEW_JS" file="src/js/views/view.js" type="chrome_html" />
<structure name="IDR_CAMERA_WARNING_JS" file="src/js/views/warning.js" type="chrome_html" />
<structure name="IDR_CAMERA_WEBUI_BROWSER_PROXY" file="src/js/browser_proxy/webui_browser_proxy.js" type="chrome_html" />
......
......@@ -214,6 +214,14 @@ body.tab-navigation .circle input:focus::after {
bottom: calc((var(--modes-bottom) + var(--modes-height)) + 16px);
}
body.review-result #shutters-group {
display: none;
}
body.should-handle-intent-result #shutters-group {
bottom: var(--modes-bottom);
}
body.tablet-landscape .actions-group {
flex-direction: column-reverse;
}
......@@ -239,6 +247,52 @@ body.taking #modes-group {
padding: var(--modes-gradient-padding) 8px;
}
body.should-handle-intent-result #modes-group {
display: none;
}
.preview-content {
height: 0; /* Calculate at runtime. */
width: 0; /* Calculate at runtime. */
}
#play-result-video {
background-image: url(../images/camera_intent_play_video.svg);
height: 80px;
width: 80px;
}
#confirm-result-groups {
bottom: calc(var(--bottom-line) - 36px);
display: flex;
flex-direction: column;
}
#confirm-result-groups>button {
flex: 0 0 76px;
height: 72px;
width: 72px;
}
body.review-result #preview-video,
body:not(.review-result) #review-photo-result,
body:not(.review-result) #review-video-result,
body:not(.review-photo-result) #review-photo-result,
body.review-photo-result #review-video-result,
body.review-photo-result #play-result-video,
body.playing-result-video #play-result-video,
body:not(.review-result) #confirm-result-groups {
display: none;
}
#confirm-result {
background-image: url(../images/camera_intent_result_confirm.svg);
}
#cancel-result {
background-image: url(../images/camera_intent_result_cancel.svg);
}
.mode-item {
flex: 0 0 var(--mode-item-height);
position: relative;
......@@ -415,6 +469,10 @@ body.mode-switching:not(.streaming) #camera-mode {
width: var(--big-icon);
}
body.should-handle-intent-result #gallery-enter {
display: none;
}
.centered-overlay {
left: 50%;
position: absolute;
......@@ -495,6 +553,10 @@ body._60fps #toggle-fps {
background-image: url(../images/camera_button_settings.svg);
}
body.should-handle-intent-result #open-settings {
display: none;
}
#camera,
#settings,
#gridsettings,
......@@ -606,7 +668,7 @@ body:not(.w-letterbox).preview-vertical-dock #camera {
}
#preview-wrapper,
#preview-video {
.preview-content {
flex-shrink: 0;
pointer-events: none;
position: relative;
......@@ -625,17 +687,17 @@ body.square-preview #preview-wrapper {
width: 0; /* Calculate at runtime. */
}
body.square-preview #preview-video {
body.square-preview .preview-content {
left: 0; /* Calculate at runtime. */
position: absolute;
top: 0; /* Calculate at runtime. */
}
body.streaming #preview-video {
body.streaming .preview-content {
pointer-events: auto;
}
body.mirror #preview-video,
body.mirror .preview-content ,
body.mirror #preview-focus {
transform: scaleX(-1);
}
......@@ -1270,6 +1332,6 @@ body.tab-navigation .dialog-buttons button:focus::after {
z-index: 1;
}
body:not(.mode-switching):not(.streaming) #spinner {
body:not(.mode-switching):not(.streaming):not(.review-result) #spinner {
visibility: visible;
}
<?xml version="1.0" encoding="UTF-8"?>
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
<title>btn_play_video_1x</title>
<desc>Created with Sketch.</desc>
<g id="btn_play_video_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="camera-button" transform="translate(6.127660, 6.127660)" opacity="0.9">
<circle id="Oval-3" fill="#FFFFFF" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
<circle id="Oval-3" fill-opacity="0.1" fill="#5F6368" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
</g>
<g id="dense/av/play" transform="translate(29.412766, 24.817021)" fill="#5F6368">
<polygon id="↳Color-fill" points="0 22.7345051 19.4867186 11.3672525 0 0"></polygon>
</g>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
<title>btn_stop_timer_1x</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="44 29.6114286 42.3885714 28 36 34.3885714 29.6114286 28 28 29.6114286 34.3885714 36 28 42.3885714 29.6114286 44 36 37.6114286 42.3885714 44 44 42.3885714 37.6114286 36"></polygon>
</defs>
<g id="btn_stop_timer_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<circle id="Oval-3" fill="#BDC1C6" opacity="0.9" cx="36" cy="36" r="30"></circle>
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use id="ic_close_24px" fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
</svg>
\ No newline at end of file
<?xml version="1.0" encoding="UTF-8"?>
<svg width="72px" height="72px" viewBox="0 0 72 72" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink">
<!-- Generator: Sketch 57.1 (83088) - https://sketch.com -->
<title>btn_confirm_1x</title>
<desc>Created with Sketch.</desc>
<defs>
<polygon id="path-1" points="14.3166667 25.9666667 7.36666667 19.0166667 5 21.3666667 14.3166667 30.6833333 34.3166667 10.6833333 31.9666667 8.33333333"></polygon>
</defs>
<g id="btn_confirm_1x" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd">
<g id="camera-button" transform="translate(6.127660, 6.127660)" fill="#1A73E8" opacity="0.9">
<circle id="Oval-3" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
<circle id="Oval-3" cx="30.0154866" cy="30.0154866" r="30.0154866"></circle>
</g>
<g id="ic_check_24px" transform="translate(16.000000, 16.000000)">
<mask id="mask-2" fill="white">
<use xlink:href="#path-1"></use>
</mask>
<use fill="#FFFFFF" fill-rule="nonzero" xlink:href="#path-1"></use>
</g>
</g>
</svg>
\ No newline at end of file
......@@ -9,6 +9,7 @@ js_type_check("closure_compile") {
":camera3_device_info",
":constraints_preferrer",
":device_info_updater",
":error",
]
}
......@@ -31,6 +32,10 @@ js_library("device_info_updater") {
deps = [
":camera3_device_info",
":constraints_preferrer",
":error",
"..:state",
]
}
js_library("error") {
}
......@@ -231,7 +231,7 @@ cca.device.DeviceInfoUpdater = class {
async getDeviceResolutions(deviceId) {
const devices = await this.getCamera3DevicesInfo();
if (!devices) {
throw new Error('HALv1-api');
throw new cca.device.LegacyVCDError();
}
const info = devices.find((info) => info.deviceId === deviceId);
return [info.photoResols, info.videoResols];
......
// 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 device.
*/
cca.device = cca.device || {};
/**
* Throws from calls to methods requiring mojo supporting VCD on HALv1 device
* equipped with legacy VCD implementation.
*/
cca.device.LegacyVCDError = class extends Error {
/**
* @param {string=} message
* @public
*/
constructor(
message =
'Call to unsupported mojo operation on legacy VCD implementation.') {
super(message);
this.name = 'LegacyVCDError';
}
};
......@@ -14,6 +14,10 @@ var cca = cca || {};
* @constructor
*/
cca.App = function() {
const shouldHandleIntentResult =
window.intent !== null && window.intent.shouldHandleResult;
cca.state.set('should-handle-intent-result', shouldHandleIntentResult);
/**
* @type {cca.models.Gallery}
* @private
......@@ -63,9 +67,13 @@ cca.App = function() {
* @type {cca.views.Camera}
* @private
*/
this.cameraView_ = new cca.views.Camera(
this.gallery_, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_);
this.cameraView_ = shouldHandleIntentResult ?
new cca.views.CameraIntent(
window.intent, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_) :
new cca.views.Camera(
this.gallery_, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_);
// End of properties. Seal the object.
Object.seal(this);
......@@ -110,8 +118,10 @@ cca.App.prototype.setupToggles_ = function() {
cca.proxy.browserProxy.localStorageGet(
{expert: false}, ({expert}) => cca.state.set('expert', expert));
document.querySelectorAll('input').forEach((element) => {
element.addEventListener('keypress', (event) =>
cca.util.getShortcutIdentifier(event) == 'Enter' && element.click());
element.addEventListener(
'keypress',
(event) => cca.util.getShortcutIdentifier(event) == 'Enter' &&
element.click());
var css = element.getAttribute('data-state');
var key = element.getAttribute('data-key');
......@@ -129,14 +139,15 @@ cca.App.prototype.setupToggles_ = function() {
if (element.type == 'radio' && element.checked) {
// Handle unchecked grouped sibling radios.
var grouped = `input[type=radio][name=${element.name}]:not(:checked)`;
document.querySelectorAll(grouped).forEach((radio) =>
radio.dispatchEvent(new Event('change')) && radio.save());
document.querySelectorAll(grouped).forEach(
(radio) =>
radio.dispatchEvent(new Event('change')) && radio.save());
}
}
});
element.toggleChecked = (checked) => {
element.checked = checked;
element.dispatchEvent(new Event('change')); // Trigger toggling css.
element.dispatchEvent(new Event('change')); // Trigger toggling css.
};
element.save = () => {
return key && cca.proxy.browserProxy.localStorageSet(payload());
......@@ -154,34 +165,38 @@ cca.App.prototype.setupToggles_ = function() {
*/
cca.App.prototype.start = function() {
var ackMigrate = false;
cca.models.FileSystem.initialize(() => {
// Prompt to migrate pictures if needed.
var message = chrome.i18n.getMessage('migrate_pictures_msg');
return cca.nav.open('message-dialog', {message, cancellable: false})
.then((acked) => {
if (!acked) {
throw new Error('no-migrate');
}
ackMigrate = true;
});
}).then((external) => {
cca.state.set('ext-fs', external);
this.gallery_.addObserver(this.galleryButton_);
if (!cca.App.useGalleryApp()) {
this.gallery_.addObserver(this.browserView_);
}
this.gallery_.load();
cca.nav.open('camera');
}).catch((error) => {
console.error(error);
if (error && error.message == 'no-migrate') {
chrome.app.window.current().close();
return;
}
cca.nav.open('warning', 'filesystem-failure');
}).finally(() => {
cca.metrics.log(cca.metrics.Type.LAUNCH, ackMigrate);
});
cca.models.FileSystem
.initialize(() => {
// Prompt to migrate pictures if needed.
var message = chrome.i18n.getMessage('migrate_pictures_msg');
return cca.nav.open('message-dialog', {message, cancellable: false})
.then((acked) => {
if (!acked) {
throw new Error('no-migrate');
}
ackMigrate = true;
});
})
.then((external) => {
cca.state.set('ext-fs', external);
this.gallery_.addObserver(this.galleryButton_);
if (!cca.App.useGalleryApp()) {
this.gallery_.addObserver(this.browserView_);
}
this.gallery_.load();
cca.nav.open('camera');
})
.catch((error) => {
console.error(error);
if (error && error.message == 'no-migrate') {
chrome.app.window.current().close();
return;
}
cca.nav.open('warning', 'filesystem-failure');
})
.finally(() => {
cca.metrics.log(cca.metrics.Type.LAUNCH, ackMigrate);
});
};
/**
......@@ -190,7 +205,7 @@ cca.App.prototype.start = function() {
* @private
*/
cca.App.prototype.onKeyPressed_ = function(event) {
cca.tooltip.hide(); // Hide shown tooltip on any keypress.
cca.tooltip.hide(); // Hide shown tooltip on any keypress.
cca.nav.onKeyPressed(event);
};
......
......@@ -33,4 +33,12 @@ js_library("result_saver") {
}
js_library("video_saver") {
sources = [
"file_video_saver.js",
"intent_video_saver.js",
"video_saver_interface.js",
]
deps = [
"..:intent",
]
}
......@@ -16,8 +16,9 @@ cca.models = cca.models || {};
/**
* Used to save captured video.
* @implements {cca.models.VideoSaver}
*/
cca.models.VideoSaver = class {
cca.models.FileVideoSaver = class {
/**
* @param {!FileEntry} file
* @param {!FileWriter} writer
......@@ -42,9 +43,7 @@ cca.models.VideoSaver = class {
}
/**
* Writes video data to result video.
* @param {!Blob} blob Video data to be written.
* @return {!Promise}
* @override
*/
async write(blob) {
this.curWrite_ = (async () => {
......@@ -58,8 +57,7 @@ cca.models.VideoSaver = class {
}
/**
* Finishes the write of video data parts and returns result video file.
* @return {!Promise<!FileEntry>} Result video file.
* @override
*/
async endWrite() {
await this.curWrite_;
......@@ -67,14 +65,14 @@ cca.models.VideoSaver = class {
}
/**
* Create VideoSaver.
* @param {!FileEntry} file The file which VideoSaver saves the result video
* into.
* @return {!Promise<!cca.models.VideoSaver>}
* Creates FileVideoSaver.
* @param {!FileEntry} file The file which FileVideoSaver saves the result
* video into.
* @return {!Promise<!cca.models.FileVideoSaver>}
*/
static async create(file) {
const writer = await new Promise(
(resolve, reject) => file.createWriter(resolve, reject));
return new cca.models.VideoSaver(file, writer);
return new cca.models.FileVideoSaver(file, writer);
}
};
......@@ -36,6 +36,12 @@ cca.models.FileSystem.THUMBNAIL_PREFIX = 'thumb-';
*/
cca.models.FileSystem.internalDir = null;
/**
* Temporary directory in the internal file system.
* @type {DirectoryEntry}
*/
cca.models.FileSystem.internalTempDir = null;
/**
* Directory in the external file system.
* @type {DirectoryEntry}
......@@ -49,7 +55,21 @@ cca.models.FileSystem.externalDir = null;
*/
cca.models.FileSystem.initInternalDir_ = function() {
return new Promise((resolve, reject) => {
webkitRequestFileSystem(window.PERSISTENT, 768 * 1024 * 1024 /* 768MB */,
webkitRequestFileSystem(
window.PERSISTENT, 768 * 1024 * 1024 /* 768MB */,
(fs) => resolve(fs.root), reject);
});
};
/**
* Initializes the temporary directory in the internal file system.
* @return {!Promise<DirectoryEntry>} Promise for the directory result.
* @private
*/
cca.models.FileSystem.initInternalTempDir_ = function() {
return new Promise((resolve, reject) => {
webkitRequestFileSystem(
window.TEMPORARY, 768 * 1024 * 1024 /* 768MB */,
(fs) => resolve(fs.root), reject);
});
};
......@@ -118,48 +138,59 @@ cca.models.FileSystem.initialize = function(promptMigrate) {
var doneMigrate = () => chrome.chromeosInfoPrivate &&
chrome.chromeosInfoPrivate.set('cameraMediaConsolidated', true);
return Promise.all([
cca.models.FileSystem.initInternalDir_(),
cca.models.FileSystem.initExternalDir_(),
checkAcked,
checkMigrated,
]).then(([internalDir, externalDir, acked, migrated]) => {
cca.models.FileSystem.internalDir = internalDir;
cca.models.FileSystem.externalDir = externalDir;
if (migrated && !externalDir) {
throw new Error('External file system should be available.');
}
// Check if acknowledge-prompt and migrate-pictures are needed.
if (migrated || !cca.models.FileSystem.externalDir) {
return [false, false];
}
// Check if any internal picture other than thumbnail needs migration.
// Pictures taken by old Camera App may not have IMG_ or VID_ prefix.
var dir = cca.models.FileSystem.internalDir;
return cca.models.FileSystem.readDir_(dir).then((entries) => {
return entries.some(
(entry) => !cca.models.FileSystem.hasThumbnailPrefix_(entry));
}).then((migrateNeeded) => {
if (migrateNeeded) {
return [!acked, true];
}
// If the external file system is supported and there is already no
// picture in the internal file system, it implies done migration and
// then doesn't need acknowledge-prompt.
ackMigrate();
doneMigrate();
return [false, false];
});
}).then(([promptNeeded, migrateNeeded]) => { // Prompt to migrate if needed.
return !promptNeeded ? migrateNeeded : promptMigrate().then(() => {
ackMigrate();
return migrateNeeded;
});
}).then((migrateNeeded) => { // Migrate pictures if needed.
const external = cca.models.FileSystem.externalDir != null;
return !migrateNeeded ? external : cca.models.FileSystem.migratePictures()
.then(doneMigrate).then(() => external);
});
return Promise
.all([
cca.models.FileSystem.initInternalDir_(),
cca.models.FileSystem.initInternalTempDir_(),
cca.models.FileSystem.initExternalDir_(),
checkAcked,
checkMigrated,
])
.then(([internalDir, internalTempDir, externalDir, acked, migrated]) => {
cca.models.FileSystem.internalDir = internalDir;
cca.models.FileSystem.internalTempDir = internalTempDir;
cca.models.FileSystem.externalDir = externalDir;
if (migrated && !externalDir) {
throw new Error('External file system should be available.');
}
// Check if acknowledge-prompt and migrate-pictures are needed.
if (migrated || !cca.models.FileSystem.externalDir) {
return [false, false];
}
// Check if any internal picture other than thumbnail needs migration.
// Pictures taken by old Camera App may not have IMG_ or VID_ prefix.
var dir = cca.models.FileSystem.internalDir;
return cca.models.FileSystem.readDir_(dir)
.then((entries) => {
return entries.some(
(entry) => !cca.models.FileSystem.hasThumbnailPrefix_(entry));
})
.then((migrateNeeded) => {
if (migrateNeeded) {
return [!acked, true];
}
// If the external file system is supported and there is already
// no picture in the internal file system, it implies done
// migration and then doesn't need acknowledge-prompt.
ackMigrate();
doneMigrate();
return [false, false];
});
})
.then(
([promptNeeded, migrateNeeded]) => { // Prompt to migrate if needed.
return !promptNeeded ? migrateNeeded : promptMigrate().then(() => {
ackMigrate();
return migrateNeeded;
});
})
.then((migrateNeeded) => { // Migrate pictures if needed.
const external = cca.models.FileSystem.externalDir != null;
return !migrateNeeded ? external :
cca.models.FileSystem.migratePictures()
.then(doneMigrate)
.then(() => external);
});
};
/**
......@@ -302,6 +333,26 @@ cca.models.FileSystem.createTempVideoFile = async function() {
return file;
};
/**
* @const {string}
*/
cca.models.FileSystem.PRIVATE_TEMPFILE_NAME = 'video-intent.mkv';
/**
* @return {!Promise<!FileEntry>} Newly created temporary file.
* @throws {Error} If failed to create video temp file.
*/
cca.models.FileSystem.createPrivateTempVideoFile = async function() {
// TODO(inker): Handles running out of space case.
const dir = cca.models.FileSystem.internalTempDir;
const file = await cca.models.FileSystem.getFile(
dir, cca.models.FileSystem.PRIVATE_TEMPFILE_NAME, true);
if (file === null) {
throw new Error('Failed to create private video temp file.');
}
return file;
};
/**
* Saves temporary video file to predefined default location.
* @param {FileEntry} tempfile Temporary video file to be saved.
......
......@@ -88,11 +88,15 @@ cca.models.Gallery.Picture.parseTimestamp_ = function(pictureEntry) {
var name = cca.models.FileSystem.regulatePictureName(pictureEntry);
// Match numeric parts from filenames, e.g. IMG_'yyyyMMdd_HHmmss (n)'.jpg.
// Assume no more than one picture taken within one millisecond.
// Use String.raw instead of /...regex.../ here to avoid breaking syntax
// highlight on gerrit.
var match = name.match(
/_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})(?: \((\d+)\))?/);
return match ? new Date(num(match[1]), num(match[2]) - 1, num(match[3]),
num(match[4]), num(match[5]), num(match[6]),
match[7] ? num(match[7]) : 0) : new Date(0);
String.raw`_(\d{4})(\d{2})(\d{2})_(\d{2})(\d{2})(\d{2})(?: \((\d+)\))?/`);
return match ?
new Date(
num(match[1]), num(match[2]) - 1, num(match[3]), num(match[4]),
num(match[5]), num(match[6]), match[7] ? num(match[7]) : 0) :
new Date(0);
};
cca.models.Gallery.Picture.prototype = {
......@@ -313,7 +317,7 @@ cca.models.Gallery.prototype.savePhoto = function(blob, name) {
*/
cca.models.Gallery.prototype.startSaveVideo = async function() {
const tempFile = await cca.models.FileSystem.createTempVideoFile();
return cca.models.VideoSaver.create(tempFile);
return cca.models.FileVideoSaver.create(tempFile);
};
/**
......
// 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 models.
*/
cca.models = cca.models || {};
/**
* Used to save captured video into a preview file and forward to intent result.
* @implements {cca.models.VideoSaver}
*/
cca.models.IntentVideoSaver = class {
/**
* @param {!cca.intent.Intent} intent
* @param {!cca.models.FileVideoSaver} fileSaver
* @private
*/
constructor(intent, fileSaver) {
/**
* @const {!cca.intent.Intent} intent
* @private
*/
this.intent_ = intent;
/**
* @const {!cca.models.FileVideoSaver}
* @private
*/
this.fileSaver_ = fileSaver;
}
/**
* @override
*/
async write(blob) {
await this.fileSaver_.write(blob);
const arrayBuffer = await blob.arrayBuffer();
this.intent_.appendData(new Uint8Array(arrayBuffer));
}
/**
* @override
*/
async endWrite() {
return this.fileSaver_.endWrite();
}
/**
* Creates IntentVideoSaver.
* @param {!cca.intent.Intent} intent
* @return {!Promise<!cca.models.IntentVideoSaver>}
*/
static async create(intent) {
const tmpFile = await cca.models.FileSystem.createPrivateTempVideoFile();
const fileSaver = await cca.models.FileVideoSaver.create(tmpFile);
return new cca.models.IntentVideoSaver(intent, fileSaver);
}
};
// 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 models.
*/
cca.models = cca.models || {};
/**
* Used to save captured video.
* @interface
*/
cca.models.VideoSaver = class {
/**
* Writes video data to result video.
* @param {!Blob} blob Video data to be written.
* @return {!Promise}
*/
async write(blob) {}
/**
* Finishes the write of video data parts and returns result video file.
* @return {!Promise<!FileEntry>} Result video file.
*/
async endWrite() {}
};
......@@ -7,6 +7,7 @@ import("//third_party/closure_compiler/compile_js.gni")
group("closure_compile") {
deps = [
":compile_resources",
"camera:compile_resources",
]
}
......
......@@ -129,13 +129,10 @@ cca.views.Camera = function(
/**
* Promise for the current take of photo or recording.
* @type {?Promise}
* @private
* @protected
*/
this.take_ = null;
// End of properties, seal the object.
Object.seal(this);
document.querySelectorAll('#start-takephoto, #start-recordvideo')
.forEach((btn) => btn.addEventListener('click', () => this.beginTake_()));
......@@ -189,11 +186,13 @@ cca.views.Camera.prototype.focus = function() {
/**
* Begins to take photo or recording with the current options, e.g. timer.
* @private
* @return {?Promise} Promise resolved when take action completes. Returns null
* if CCA can't start take action.
* @protected
*/
cca.views.Camera.prototype.beginTake_ = function() {
if (!cca.state.get('streaming') || cca.state.get('taking')) {
return;
return null;
}
cca.state.set('taking', true);
......@@ -214,6 +213,7 @@ cca.views.Camera.prototype.beginTake_ = function() {
this.focus(); // Refocus the visible shutter button for ChromeVox.
}
})();
return this.take_;
};
/**
......@@ -273,74 +273,75 @@ cca.views.Camera.prototype.restart = async function() {
};
/**
* Try start stream reconfiguration with specified device id.
* @async
* Try start stream reconfiguration with specified mode and device id.
* @param {?string} deviceId
* @return {boolean} If found suitable stream and reconfigure successfully.
* @param {string} mode
* @return {!Promise<boolean>} If found suitable stream and reconfigure
* successfully.
*/
cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
let supportedModes = null;
for (const mode of this.modes_.getModeCandidates()) {
try {
if (!deviceId) {
// Null for requesting default camera on HALv1.
throw new Error('HALv1-api');
}
cca.views.Camera.prototype.startWithMode_ = async function(deviceId, mode) {
const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
let resolCandidates = null;
if (deviceOperator !== null) {
if (deviceId !== null) {
const previewRs =
(await this.infoUpdater_.getDeviceResolutions(deviceId))[1];
var resolCandidates =
resolCandidates =
this.modes_.getResolutionCandidates(mode, deviceId, previewRs);
} catch (e) {
// Assume the exception here is thrown from error of HALv1 not support
// resolution query, fallback to use v1 constraints-candidates.
if (e.message == 'HALv1-api') {
resolCandidates =
await this.modes_.getResolutionCandidatesV1(mode, deviceId);
} else {
throw e;
}
} else {
console.error('Null device id present on HALv3 device. Fallback to v1.');
}
for (const [captureResolution, previewCandidates] of resolCandidates) {
if (supportedModes && !supportedModes.includes(mode)) {
break;
}
if (resolCandidates === null) {
resolCandidates =
await this.modes_.getResolutionCandidatesV1(mode, deviceId);
}
for (const [captureResolution, previewCandidates] of resolCandidates) {
for (const constraints of previewCandidates) {
if (this.suspended) {
throw new cca.views.CameraSuspendedError();
}
for (const constraints of previewCandidates) {
if (this.suspended) {
throw new cca.views.CameraSuspendedError();
}
try {
const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
if (deviceOperator) {
await deviceOperator.setFpsRange(deviceId, constraints);
await deviceOperator.setCaptureIntent(
deviceId, this.modes_.getCaptureIntent(mode));
}
const stream = await navigator.mediaDevices.getUserMedia(constraints);
if (!supportedModes) {
supportedModes = await this.modes_.getSupportedModes(stream);
if (!supportedModes.includes(mode)) {
stream.getTracks()[0].stop();
break;
}
}
await this.preview_.start(stream);
this.facingMode_ =
await this.options_.updateValues(constraints, stream);
await this.modes_.updateModeSelectionUI(supportedModes);
await this.modes_.updateMode(
mode, stream, deviceId, captureResolution);
cca.nav.close('warning', 'no-camera');
return true;
} catch (e) {
this.preview_.stop();
console.error(e);
try {
if (deviceOperator !== null) {
await deviceOperator.setFpsRange(deviceId, constraints);
await deviceOperator.setCaptureIntent(
deviceId, this.modes_.getCaptureIntent(mode));
}
const stream = await navigator.mediaDevices.getUserMedia(constraints);
await this.preview_.start(stream);
this.facingMode_ =
await this.options_.updateValues(constraints, stream);
await this.modes_.updateModeSelectionUI(deviceId);
await this.modes_.updateMode(mode, stream, deviceId, captureResolution);
cca.nav.close('warning', 'no-camera');
return true;
} catch (e) {
this.preview_.stop();
console.error(e);
}
}
}
return false;
};
/**
* Try start stream reconfiguration with specified device id.
* @param {?string} deviceId
* @return {!Promise<boolean>} If found suitable stream and reconfigure
* successfully.
*/
cca.views.Camera.prototype.startWithDevice_ = async function(deviceId) {
const supportedModes = await this.modes_.getSupportedModes(deviceId);
const modes =
this.modes_.getModeCandidates().filter((m) => supportedModes.includes(m));
for (const mode of modes) {
if (await this.startWithMode_(deviceId, mode)) {
return true;
}
}
return false;
};
/**
* Starts camera configuration process.
* @return {!Promise<boolean>} Resolved to boolean for whether the configuration
......
# 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.
import("//third_party/closure_compiler/compile_js.gni")
group("closure_compile") {
deps = [
":compile_resources",
]
}
js_type_check("compile_resources") {
deps = [
":review_result",
]
}
js_library("review_result") {
deps = [
"../..:state",
"../..:util",
]
}
......@@ -38,7 +38,14 @@ cca.views.camera.Layout = function() {
* @private
*/
this.squareVideo_ =
cca.views.camera.Layout.cssStyle_('body.square-preview #preview-video');
cca.views.camera.Layout.cssStyle_('body.square-preview .preview-content');
/**
* CSS style of what is currently put as camera preview.
* @type {CSSStyleDeclaration}
* @private
*/
this.previewContent_ = cca.views.camera.Layout.cssStyle_('.preview-content');
// End of properties, seal the object.
Object.seal(this);
......@@ -73,7 +80,9 @@ cca.views.camera.Layout.prototype.updatePreviewSize_ = function() {
// it may fill up the window or be letterboxed when fullscreen/maximized.
// Don't use app-window.innerBounds' width/height properties during resizing
// as they are not updated immediately.
var video = document.querySelector('#preview-video');
const video = document.querySelector('#preview-video');
let contentWidth = 0;
let contentHeight = 0;
if (video.videoHeight) {
var scale = cca.state.get('square-mode') ?
Math.min(window.innerHeight, window.innerWidth) /
......@@ -81,15 +90,19 @@ cca.views.camera.Layout.prototype.updatePreviewSize_ = function() {
Math.min(
window.innerHeight / video.videoHeight,
window.innerWidth / video.videoWidth);
video.width = scale * video.videoWidth;
video.height = scale * video.videoHeight;
contentWidth = scale * video.videoWidth;
contentHeight = scale * video.videoHeight;
this.previewContent_.setProperty('width', `${contentWidth}px`);
this.previewContent_.setProperty('height', `${contentHeight}px`);
}
var [viewportW, viewportH] = [video.width, video.height];
var [viewportW, viewportH] = [contentWidth, contentHeight];
cca.state.set('square-preview', cca.state.get('square-mode'));
if (cca.state.get('square-mode')) {
viewportW = viewportH = Math.min(video.width, video.height);
this.squareVideo_.setProperty('left', `${(viewportW - video.width) / 2}px`);
this.squareVideo_.setProperty('top', `${(viewportH - video.height) / 2}px`);
viewportW = viewportH = Math.min(contentWidth, contentHeight);
this.squareVideo_.setProperty(
'left', `${(viewportW - contentWidth) / 2}px`);
this.squareVideo_.setProperty(
'top', `${(viewportH - contentHeight) / 2}px`);
this.squareViewport_.setProperty('width', `${viewportW}px`);
this.squareViewport_.setProperty('height', `${viewportH}px`);
}
......
......@@ -162,12 +162,14 @@ cca.views.camera.Modes = function(
'portrait-mode': {
captureFactory: () => new cca.views.camera.Portrait(
this.stream_, doSavePhoto, this.captureResolution_),
isSupported: async (stream) => {
isSupported: async (deviceId) => {
if (deviceId === null) {
return false;
}
const deviceOperator = await cca.mojo.DeviceOperator.getInstance();
if (!deviceOperator) {
if (deviceOperator === null) {
return false;
}
const deviceId = stream.getVideoTracks()[0].getSettings().deviceId;
return await deviceOperator.isPortraitModeSupported(deviceId);
},
resolutionConfig: photoResolPreferrer,
......@@ -273,7 +275,7 @@ cca.views.camera.Modes.prototype.switchMode_ = function(mode) {
/**
* Gets all mode candidates. Desired trying sequence of candidate modes is
* reflected in the order of the returned array.
* @return {Array<string>} Mode candidates to be tried out.
* @return {!Array<string>} Mode candidates to be tried out.
*/
cca.views.camera.Modes.prototype.getModeCandidates = function() {
const tried = {};
......@@ -326,14 +328,15 @@ cca.views.camera.Modes.prototype.getCaptureIntent = function(mode) {
};
/**
* Gets supported modes for video device of the given stream.
* @param {MediaStream} stream Stream of the video device.
* @return {Array<string>} Names of all supported mode for the video device.
* Gets supported modes for video device of given device id.
* @param {?string} deviceId Device id of the video device.
* @return {!Promise<!Array<cca.views.camera.Mode>>} All supported mode for the
* video device.
*/
cca.views.camera.Modes.prototype.getSupportedModes = async function(stream) {
cca.views.camera.Modes.prototype.getSupportedModes = async function(deviceId) {
let supportedModes = [];
for (const [mode, obj] of Object.entries(this.allModes_)) {
if (await obj.isSupported(stream)) {
if (await obj.isSupported(deviceId)) {
supportedModes.push(mode);
}
}
......@@ -341,12 +344,13 @@ cca.views.camera.Modes.prototype.getSupportedModes = async function(stream) {
};
/**
* Updates mode selection UI according to given supported modes.
* @param {Array<string>} supportedModes Supported mode names to be updated
* with.
* Updates mode selection UI according to given device id.
* @param {?string} deviceId
* @return {!Promise}
*/
cca.views.camera.Modes.prototype.updateModeSelectionUI = function(
supportedModes) {
cca.views.camera.Modes.prototype.updateModeSelectionUI =
async function(deviceId) {
const supportedModes = await this.getSupportedModes(deviceId);
document.querySelectorAll('.mode-item').forEach((element) => {
const radio = element.querySelector('input[type=radio]');
element.classList.toggle(
......
......@@ -123,6 +123,7 @@ cca.views.camera.Preview.prototype.toString = function() {
cca.views.camera.Preview.prototype.setSource_ = function(stream) {
var video = document.createElement('video');
video.id = 'preview-video';
video.classList = this.video_.classList;
video.muted = true; // Mute to avoid echo from the captured audio.
return new Promise((resolve) => {
var handler = () => {
......@@ -186,27 +187,6 @@ cca.views.camera.Preview.prototype.stop = function() {
cca.state.set('streaming', false);
};
/**
* Creates an image blob of the current frame.
* @return {!Promise<Blob>} Promise for the result.
*/
cca.views.camera.Preview.prototype.toImage = function() {
var canvas = document.createElement('canvas');
var ctx = canvas.getContext('2d');
canvas.width = this.video_.videoWidth;
canvas.height = this.video_.videoHeight;
ctx.drawImage(this.video_, 0, 0);
return new Promise((resolve, reject) => {
canvas.toBlob((blob) => {
if (blob) {
resolve(blob);
} else {
reject(new Error('Photo blob error.'));
}
}, 'image/jpeg');
});
};
/**
* Checks preview whether to show preview metadata or not.
* @private
......
// Copyright (c) 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 views.
*/
cca.views = cca.views || {};
/**
* Namespace for Camera view.
*/
cca.views.camera = cca.views.camera || {};
/**
* Creates a controller for reviewing intent result in Camera view.
*/
cca.views.camera.ReviewResult = class {
/**
* @public
*/
constructor() {
/**
* @const {!HTMLImageElement}
* @private
*/
this.reviewPhotoResult_ = /** @type {!HTMLImageElement} */ (
document.querySelector('#review-photo-result'));
/**
* @const {!HTMLVideoElement}
* @private
*/
this.reviewVideoResult_ = /** @type {!HTMLVideoElement} */ (
document.querySelector('#review-video-result'));
/**
* Function resolving open result call called with whether user confirms
* after reviewing intent result.
* @type {?function(boolean)}
* @private
*/
this.resolveOpen_ = null;
this.reviewVideoResult_.onended = () => {
this.reviewVideoResult_.currentTime = 0;
cca.state.set('playing-result-video', false);
};
const addClickListener = (selector, handler) =>
document.querySelector(selector).addEventListener('click', handler);
addClickListener('#confirm-result', () => this.close_(true));
addClickListener('#cancel-result', () => this.close_(false));
addClickListener('#play-result-video', () => this.playResultVideo_());
}
/**
* Starts playing result video.
* @private
*/
playResultVideo_() {
if (cca.state.get('playing-result-video')) {
return;
}
cca.state.set('playing-result-video', true);
this.reviewVideoResult_.play();
}
/**
* Closes review result UI and resolves its open promise with whether user
* confirms after reviewing the result.
* @param {boolean} confirmed
* @private
*/
close_(confirmed) {
if (this.resolveOpen_ === null) {
console.error('Close review result with no unresolved open.');
return;
}
const resolve = this.resolveOpen_;
this.resolveOpen_ = null;
cca.state.set('review-result', false);
cca.state.set('playing-result-video', false);
this.reviewPhotoResult_.src = '';
this.reviewVideoResult_.src = '';
resolve(confirmed);
}
/**
* Opens photo result blob and shows photo on review result UI.
* @param {!Blob} blob Photo result blob.
* @return {!Promise<boolean>} Promise resolved with whether user confirms
* with the photo result.
*/
async openPhoto(blob) {
const img = await cca.util.blobToImage(blob);
this.reviewPhotoResult_.src = img.src;
cca.state.set('review-photo-result', true);
cca.state.set('review-result', true);
return new Promise((resolve) => {
this.resolveOpen_ = resolve;
});
}
/**
* Opens video result file and shows video on review result UI.
* @param {!FileEntry} fileEntry Video result file.
* @return {!Promise<boolean>} Promise resolved with whether user confirms
* with the video result.
*/
async openVideo(fileEntry) {
this.reviewVideoResult_.src = fileEntry.toURL();
cca.state.set('review-photo-result', false);
cca.state.set('review-result', true);
return new Promise((resolve) => {
this.resolveOpen_ = resolve;
});
}
};
// Copyright (c) 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 views.
*/
cca.views = cca.views || {};
/**
* Creates the camera-intent-view controller.
*/
cca.views.CameraIntent = class extends cca.views.Camera {
/**
* @param {!cca.intent.Intent} intent
* @param {!cca.device.DeviceInfoUpdater} infoUpdater
* @param {!cca.device.PhotoResolPreferrer} photoPreferrer
* @param {!cca.device.VideoConstraintsPreferrer} videoPreferrer
*/
constructor(intent, infoUpdater, photoPreferrer, videoPreferrer) {
const resultSaver = {
savePhoto: async (blob) => {
this.photoResult_ = blob;
const buf = await blob.arrayBuffer();
await this.intent_.appendData(new Uint8Array(buf));
},
startSaveVideo: async () => {
return await cca.models.IntentVideoSaver.create(intent);
},
finishSaveVideo: async (video, savedName) => {
this.videoResult_ = await video.endWrite();
},
};
super(resultSaver, infoUpdater, photoPreferrer, videoPreferrer);
/**
* @type {!cca.intent.Intent}
* @private
*/
this.intent_ = intent;
/**
* @type {?Blob}
* @private
*/
this.photoResult_ = null;
/**
* @type {?FileEntry}
* @private
*/
this.videoResult_ = null;
/**
* @type {!cca.views.camera.ReviewResult}
* @private
*/
this.reviewResult_ = new cca.views.camera.ReviewResult();
}
/**
* @override
*/
beginTake_() {
if (this.photoResult_ !== null) {
URL.revokeObjectURL(this.photoResult_);
}
this.photoResult_ = null;
this.videoResult_ = null;
const take = super.beginTake_();
if (take === null) {
return null;
}
return (async () => {
await take;
if (this.photoResult_ === null && this.videoResult_ === null) {
console.warn('End take without intent result.');
return;
}
cca.state.set('suspend', true);
await this.restart();
const confirmed = await (
this.photoResult_ !== null ?
this.reviewResult_.openPhoto(this.photoResult_) :
this.reviewResult_.openVideo(this.videoResult_));
if (confirmed) {
await this.intent_.finish();
window.close();
return;
}
cca.state.set('suspend', false);
await this.intent_.clearData();
await this.restart();
})();
}
/**
* @override
*/
async startWithDevice_(deviceId) {
return this.startWithMode_(deviceId, this.defaultMode);
}
};
......@@ -21,6 +21,7 @@
<script src="../js/sound.js"></script>
<script src="../js/scrollbar.js"></script>
<script src="../js/gallerybutton.js"></script>
<script src="../js/device/error.js"></script>
<script src="../js/device/camera3_device_info.js"></script>
<script src="../js/device/constraints_preferrer.js"></script>
<script src="../js/device/device_info_updater.js"></script>
......@@ -28,7 +29,9 @@
<script src="../js/models/gallery.js"></script>
<script src="../js/models/filesystem.js"></script>
<script src="../js/models/result_saver.js"></script>
<script src="../js/models/video_saver.js"></script>
<script src="../js/models/video_saver_interface.js"></script>
<script src="../js/models/file_video_saver.js"></script>
<script src="../js/models/intent_video_saver.js"></script>
<script src="../js/mojo/mojo_bindings_lite.js"></script>
<script src="../js/mojo/camera_metadata_tags.mojom-lite.js"></script>
<script src="../js/mojo/camera_metadata.mojom-lite.js"></script>
......@@ -45,10 +48,12 @@
<script src="../js/views/view.js"></script>
<script src="../js/views/gallery_base.js"></script>
<script src="../js/views/camera.js"></script>
<script src="../js/views/camera_intent.js"></script>
<script src="../js/views/camera/layout.js"></script>
<script src="../js/views/camera/options.js"></script>
<script src="../js/views/camera/preview.js"></script>
<script src="../js/views/camera/recordtime.js"></script>
<script src="../js/views/camera/review_result.js"></script>
<script src="../js/views/camera/timertick.js"></script>
<script src="../js/views/camera/modes.js"></script>
<script src="../js/views/dialog.js"></script>
......@@ -61,7 +66,12 @@
<body class="sound mirror mic _3x3">
<div id="camera">
<div id="preview-wrapper" aria-hidden="true">
<video id="preview-video"></video>
<img id="review-photo-result" class="preview-content">
<video id="review-video-result" class="preview-content"></video>
<div class="buttons circle centered-overlay">
<button id="play-result-video"></button>
</div>
<video id="preview-video" class="preview-content"></video>
<div id="preview-metadata">
<div id="preview-stat" class="metadata-row mode-on">
<span class="metadata-category">Stat</span>
......@@ -140,6 +150,10 @@
<button id="gallery-enter" tabindex="0"
i18n-label="gallery_button" hidden></button>
</div>
<div id="confirm-result-groups" class="buttons right-stripe circle">
<button id="confirm-result"></button>
<button id="cancel-result"></button>
</div>
<div class="bottom-stripe left-stripe buttons circle">
<button id="switch-device" tabindex="0"
i18n-label="switch_camera_button"></button>
......
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