Commit 8fe3241d authored by Kuo Jen Wei's avatar Kuo Jen Wei Committed by Commit Bot

[CCA] Extract ResultSaver interface.

Extract ResultSaver interface for saving captured photos and video.

Bug: 967611
Test: All CCA capture function work as expected.

Change-Id: Ifde8ee94d88357f4541e248819e0c3a1a98c0fd2
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1757720Reviewed-by: default avatarShik Chen <shik@chromium.org>
Commit-Queue: Kuo Jen Wei <inker@chromium.org>
Auto-Submit: Kuo Jen Wei <inker@chromium.org>
Cr-Commit-Position: refs/heads/master@{#689398}
parent 562c5482
...@@ -158,6 +158,8 @@ copy("chrome_camera_app_js_models") { ...@@ -158,6 +158,8 @@ copy("chrome_camera_app_js_models") {
"src/js/models/filenamer.js", "src/js/models/filenamer.js",
"src/js/models/filesystem.js", "src/js/models/filesystem.js",
"src/js/models/gallery.js", "src/js/models/gallery.js",
"src/js/models/result_saver.js",
"src/js/models/video_saver.js",
] ]
outputs = [ outputs = [
......
...@@ -128,6 +128,8 @@ RESOURCES = \ ...@@ -128,6 +128,8 @@ RESOURCES = \
src/js/models/filenamer.js \ src/js/models/filenamer.js \
src/js/models/filesystem.js \ src/js/models/filesystem.js \
src/js/models/gallery.js \ 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/imagecapture.js \
src/js/nav.js \ src/js/nav.js \
src/js/resolution_event_broker.js \ src/js/resolution_event_broker.js \
......
...@@ -38,6 +38,7 @@ ...@@ -38,6 +38,7 @@
<structure name="IDR_CAMERA_PREVIEW_JS" file="src/js/views/camera/preview.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_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_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_SCROLLBAR_JS" file="src/js/scrollbar.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_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" /> <structure name="IDR_CAMERA_SOUND_JS" file="src/js/sound.js" type="chrome_html" />
...@@ -46,6 +47,7 @@ ...@@ -46,6 +47,7 @@
<structure name="IDR_CAMERA_TOAST_JS" file="src/js/toast.js" type="chrome_html" /> <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_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_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_VIEW_JS" file="src/js/views/view.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_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" /> <structure name="IDR_CAMERA_WEBUI_BROWSER_PROXY" file="src/js/browser_proxy/webui_browser_proxy.js" type="chrome_html" />
......
...@@ -21,6 +21,8 @@ js_library("camera3_device_info") { ...@@ -21,6 +21,8 @@ js_library("camera3_device_info") {
js_library("constraints_preferrer") { js_library("constraints_preferrer") {
deps = [ deps = [
"..:resolution_event_broker", "..:resolution_event_broker",
"..:state",
":camera3_device_info",
"../browser_proxy:browser_proxy", "../browser_proxy:browser_proxy",
] ]
} }
......
...@@ -18,7 +18,7 @@ cca.App = function() { ...@@ -18,7 +18,7 @@ cca.App = function() {
* @type {cca.models.Gallery} * @type {cca.models.Gallery}
* @private * @private
*/ */
this.model_ = new cca.models.Gallery(); this.gallery_ = new cca.models.Gallery();
/** /**
* @type {cca.ResolutionEventBroker} * @type {cca.ResolutionEventBroker}
...@@ -51,20 +51,20 @@ cca.App = function() { ...@@ -51,20 +51,20 @@ cca.App = function() {
* @type {cca.GalleryButton} * @type {cca.GalleryButton}
* @private * @private
*/ */
this.galleryButton_ = new cca.GalleryButton(this.model_); this.galleryButton_ = new cca.GalleryButton(this.gallery_);
/** /**
* @type {cca.views.Browser} * @type {cca.views.Browser}
* @private * @private
*/ */
this.browserView_ = new cca.views.Browser(this.model_); this.browserView_ = new cca.views.Browser(this.gallery_);
/** /**
* @type {cca.views.Camera} * @type {cca.views.Camera}
* @private * @private
*/ */
this.cameraView_ = new cca.views.Camera( this.cameraView_ = new cca.views.Camera(
this.model_, this.infoUpdater_, this.photoPreferrer_, this.gallery_, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_); this.videoPreferrer_);
// End of properties. Seal the object. // End of properties. Seal the object.
...@@ -163,11 +163,11 @@ cca.App.prototype.start = function() { ...@@ -163,11 +163,11 @@ cca.App.prototype.start = function() {
}); });
}).then((external) => { }).then((external) => {
cca.state.set('ext-fs', external); cca.state.set('ext-fs', external);
this.model_.addObserver(this.galleryButton_); this.gallery_.addObserver(this.galleryButton_);
if (!cca.App.useGalleryApp()) { if (!cca.App.useGalleryApp()) {
this.model_.addObserver(this.browserView_); this.gallery_.addObserver(this.browserView_);
} }
this.model_.load(); this.gallery_.load();
cca.nav.open('camera'); cca.nav.open('camera');
}).catch((error) => { }).catch((error) => {
console.error(error); console.error(error);
......
...@@ -94,13 +94,13 @@ cca.metrics.launchType_ = function(ackMigrate) { ...@@ -94,13 +94,13 @@ cca.metrics.launchType_ = function(ackMigrate) {
/** /**
* Returns event builder for the metrics type: capture. * Returns event builder for the metrics type: capture.
* @param {?string} facingMode Camera facing-mode of the capture. * @param {?string} facingMode Camera facing-mode of the capture.
* @param {number=} length Length of 1 minute buckets for captured video. * @param {number} length Length of 1 minute buckets for captured video.
* @param {number} width The width of the capture resolution. * @param {number} width The width of the capture resolution.
* @param {number} height The height of the capture resolution. * @param {number} height The height of the capture resolution.
* @return {analytics.EventBuilder} * @return {analytics.EventBuilder}
* @private * @private
*/ */
cca.metrics.captureType_ = function(facingMode, length, [width, height]) { cca.metrics.captureType_ = function(facingMode, length, {width, height}) {
var condState = (states, cond, strict) => { var condState = (states, cond, strict) => {
// Return the first existing state among the given states only if there is // Return the first existing state among the given states only if there is
// no gate condition or the condition is met. // no gate condition or the condition is met.
......
...@@ -9,6 +9,8 @@ js_type_check("closure_compile") { ...@@ -9,6 +9,8 @@ js_type_check("closure_compile") {
":filenamer", ":filenamer",
":filesystem", ":filesystem",
":gallery", ":gallery",
":result_saver",
":video_saver",
] ]
} }
...@@ -26,3 +28,9 @@ js_library("gallery") { ...@@ -26,3 +28,9 @@ js_library("gallery") {
":filesystem", ":filesystem",
] ]
} }
js_library("result_saver") {
}
js_library("video_saver") {
}
...@@ -288,13 +288,18 @@ cca.models.FileSystem.savePhoto = function(blob, filename) { ...@@ -288,13 +288,18 @@ cca.models.FileSystem.savePhoto = function(blob, filename) {
/** /**
* Creates a file for saving temporary video recording result. * Creates a file for saving temporary video recording result.
* @return {Promise<?FileEntry>} Newly created temporary file. * @return {!Promise<!FileEntry>} Newly created temporary file.
* @throws {Error} If failed to create video temp file.
*/ */
cca.models.FileSystem.createTempVideoFile = async function() { cca.models.FileSystem.createTempVideoFile = async function() {
const dir = const dir =
cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir; cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir;
const filename = new cca.models.Filenamer().newVideoName(); const filename = new cca.models.Filenamer().newVideoName();
return await cca.models.FileSystem.getFile(dir, filename, true); const file = await cca.models.FileSystem.getFile(dir, filename, true);
if (file === null) {
throw new Error('Failed to create video temp file.');
}
return file;
}; };
/** /**
......
...@@ -17,6 +17,7 @@ cca.models = cca.models || {}; ...@@ -17,6 +17,7 @@ cca.models = cca.models || {};
/** /**
* Creates the gallery model controller. * Creates the gallery model controller.
* @constructor * @constructor
* @implements {cca.models.ResultSaver}
*/ */
cca.models.Gallery = function() { cca.models.Gallery = function() {
/** /**
...@@ -285,12 +286,9 @@ cca.models.Gallery.prototype.wrapPicture_ = function( ...@@ -285,12 +286,9 @@ cca.models.Gallery.prototype.wrapPicture_ = function(
}; };
/** /**
* Saves photo capture result into persistent storage and adds it into gallery. * @override
* @param {!Blob} blob Data of the photo to be added.
* @param {string} filename Filename of photo to be added.
* @return {!Promise} Promise for the operation.
*/ */
cca.models.Gallery.prototype.savePhoto = function(blob, filename) { cca.models.Gallery.prototype.savePhoto = function(blob, name) {
// TODO(yuli): models.Gallery listens to models.FileSystem's file-added event // TODO(yuli): models.Gallery listens to models.FileSystem's file-added event
// and then add a new picture into the model. // and then add a new picture into the model.
var saved = new Promise((resolve) => { var saved = new Promise((resolve) => {
...@@ -301,7 +299,7 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) { ...@@ -301,7 +299,7 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) {
cca.util.orientPhoto(blob, resolve, () => resolve(blob)); cca.util.orientPhoto(blob, resolve, () => resolve(blob));
}) })
.then((blob) => { .then((blob) => {
return cca.models.FileSystem.savePhoto(blob, filename); return cca.models.FileSystem.savePhoto(blob, name);
}) })
.then((pictureEntry) => { .then((pictureEntry) => {
return this.wrapPicture_(pictureEntry); return this.wrapPicture_(pictureEntry);
...@@ -311,12 +309,19 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) { ...@@ -311,12 +309,19 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) {
}; };
/** /**
* Saves video capture result into persistent storage and adds it into gallery. * @override
* @param {FileEntry} tempfile File saving temporary video recording result. */
* @param {string} filename Filename of picture to be added. cca.models.Gallery.prototype.startSaveVideo = async function() {
const tempFile = await cca.models.FileSystem.createTempVideoFile();
return cca.models.VideoSaver.create(tempFile);
};
/**
* @override
*/ */
cca.models.Gallery.prototype.saveVideo = async function(tempfile, filename) { cca.models.Gallery.prototype.finishSaveVideo = async function(video, name) {
const savedFile = await cca.models.FileSystem.saveVideo(tempfile, filename); const tempFile = await video.endWrite();
const savedFile = await cca.models.FileSystem.saveVideo(tempFile, name);
const picture = await this.wrapPicture_(savedFile); const picture = await this.wrapPicture_(savedFile);
await this.addPicture_(picture); await this.addPicture_(picture);
}; };
......
// 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 || {};
/**
* Handles captured result photos and video.
* @interface
*/
cca.models.ResultSaver = class {
/**
* Saves photo capture result.
* @param {!Blob} blob Data of the photo to be added.
* @param {string} name Name of the photo to be saved.
* @return {!Promise} Promise for the operation.
*/
async savePhoto(blob, name) {}
/**
* Returns a video saver to save captured result video.
* @return {!Promise<!cca.models.VideoSaver>}
*/
async startSaveVideo() {}
/**
* Saves captured video result.
* @param {!cca.models.VideoSaver} video Contains the video result to be
* saved.
* @param {string} name Name of the video to be saved.
* @return {!Promise}
*/
async finishSaveVideo(video, name) {}
};
// 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.
*/
cca.models.VideoSaver = class {
/**
* @param {!FileEntry} file
* @param {!FileWriter} writer
* @private
*/
constructor(file, writer) {
/**
* @const {!FileEntry}
*/
this.file_ = file;
/**
* @const {!FileWriter}
*/
this.writer_ = writer;
/**
* Promise of the ongoing write.
* @type {!Promise}
*/
this.curWrite_ = Promise.resolve();
}
/**
* Writes video data to result video.
* @param {!Blob} blob Video data to be written.
* @return {!Promise}
*/
async write(blob) {
this.curWrite_ = (async () => {
await this.curWrite_;
await new Promise((resolve) => {
this.writer_.onwriteend = resolve;
this.writer_.write(blob);
});
})();
await this.curWrite_;
}
/**
* Finishes the write of video data parts and returns result video file.
* @return {!Promise<!FileEntry>} Result video file.
*/
async endWrite() {
await this.curWrite_;
return this.file_;
}
/**
* Create VideoSaver.
* @param {!FileEntry} file The file which VideoSaver saves the result video
* into.
* @return {!Promise<!cca.models.VideoSaver>}
*/
static async create(file) {
const writer = await new Promise(
(resolve, reject) => file.createWriter(resolve, reject));
return new cca.models.VideoSaver(file, writer);
}
};
...@@ -222,7 +222,7 @@ cca.mojo.ImageCapture.prototype.getPhotoCapabilities = async function() { ...@@ -222,7 +222,7 @@ cca.mojo.ImageCapture.prototype.getPhotoCapabilities = async function() {
* @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's * @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's
* takePhoto(). * takePhoto().
* @param {?Array<cros.mojom.Effect>} photoEffects Photo effects to be applied. * @param {?Array<cros.mojom.Effect>} photoEffects Photo effects to be applied.
* @return {Array<Promise<Blob>>} Array of promises for the result. * @return {!Array<!Promise<!Blob>>} Array of promises for the result.
*/ */
cca.mojo.ImageCapture.prototype.takePhoto = function( cca.mojo.ImageCapture.prototype.takePhoto = function(
photoSettings, photoEffects) { photoSettings, photoEffects) {
......
...@@ -892,7 +892,7 @@ cca.util.setupI18nElements = function(rootElement) { ...@@ -892,7 +892,7 @@ cca.util.setupI18nElements = function(rootElement) {
/** /**
* Reads blob into Image. * Reads blob into Image.
* @param {!Blob} blob * @param {!Blob} blob
* @return {Promise<HTMLImageElement>} * @return {!Promise<!HTMLImageElement>}
* @throws {Error} * @throws {Error}
*/ */
cca.util.blobToImage = function(blob) { cca.util.blobToImage = function(blob) {
......
...@@ -16,23 +16,16 @@ cca.views = cca.views || {}; ...@@ -16,23 +16,16 @@ cca.views = cca.views || {};
/** /**
* Creates the camera-view controller. * Creates the camera-view controller.
* @param {cca.models.Gallery} model Model object. * @param {cca.models.ResultSaver} resultSaver
* @param {cca.device.DeviceInfoUpdater} infoUpdater * @param {cca.device.DeviceInfoUpdater} infoUpdater
* @param {cca.device.PhotoResolPreferrer} photoPreferrer * @param {cca.device.PhotoResolPreferrer} photoPreferrer
* @param {cca.device.VideoConstraintsPreferrer} videoPreferrer * @param {cca.device.VideoConstraintsPreferrer} videoPreferrer
* @constructor * @constructor
*/ */
cca.views.Camera = function( cca.views.Camera = function(
model, infoUpdater, photoPreferrer, videoPreferrer) { resultSaver, infoUpdater, photoPreferrer, videoPreferrer) {
cca.views.View.call(this, '#camera'); cca.views.View.call(this, '#camera');
/**
* Gallery model used to save taken pictures.
* @type {cca.models.Gallery}
* @private
*/
this.model_ = model;
/** /**
* @type {cca.device.DeviceInfoUpdater} * @type {cca.device.DeviceInfoUpdater}
* @private * @private
...@@ -71,36 +64,37 @@ cca.views.Camera = function( ...@@ -71,36 +64,37 @@ cca.views.Camera = function(
*/ */
this.bannerLearnMore_ = document.querySelector('#banner-learn-more'); this.bannerLearnMore_ = document.querySelector('#banner-learn-more');
/** const doSavePhoto = async (result, name) => {
* Modes for the camera.
* @type {cca.views.camera.Modes}
* @private
*/
this.modes_ = new cca.views.camera.Modes(
photoPreferrer, videoPreferrer, this.restart.bind(this),
async (result, filename) => {
if (result.blob) {
cca.metrics.log( cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, 0, result.resolution); cca.metrics.Type.CAPTURE, this.facingMode_, 0, result.resolution);
try { try {
await this.model_.savePhoto(result.blob, filename); await resultSaver.savePhoto(result.blob, name);
} catch (e) { } catch (e) {
cca.toast.show('error_msg_save_file_failed'); cca.toast.show('error_msg_save_file_failed');
throw e; throw e;
} }
} };
}, const createVideoSaver = async () => resultSaver.startSaveVideo();
async (result, filename) => { const doSaveVideo = async (result, name) => {
cca.metrics.log( cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, result.duration, cca.metrics.Type.CAPTURE, this.facingMode_, result.duration,
result.resolution); result.resolution);
try { try {
await this.model_.saveVideo(result.chunkfile, filename); await resultSaver.finishSaveVideo(result.videoSaver, name);
} catch (e) { } catch (e) {
cca.toast.show('error_msg_save_file_failed'); cca.toast.show('error_msg_save_file_failed');
throw e; throw e;
} }
}); };
/**
* Modes for the camera.
* @type {cca.views.camera.Modes}
* @private
*/
this.modes_ = new cca.views.camera.Modes(
photoPreferrer, videoPreferrer, this.restart.bind(this), doSavePhoto,
createVideoSaver, doSaveVideo);
/** /**
* @type {?string} * @type {?string}
......
...@@ -26,6 +26,8 @@ ...@@ -26,6 +26,8 @@
<script src="../js/models/filenamer.js"></script> <script src="../js/models/filenamer.js"></script>
<script src="../js/models/gallery.js"></script> <script src="../js/models/gallery.js"></script>
<script src="../js/models/filesystem.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/mojo/mojo_bindings_lite.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_tags.mojom-lite.js"></script>
<script src="../js/mojo/camera_metadata.mojom-lite.js"></script> <script src="../js/mojo/camera_metadata.mojom-lite.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