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") {
"src/js/models/filenamer.js",
"src/js/models/filesystem.js",
"src/js/models/gallery.js",
"src/js/models/result_saver.js",
"src/js/models/video_saver.js",
]
outputs = [
......
......@@ -128,6 +128,8 @@ RESOURCES = \
src/js/models/filenamer.js \
src/js/models/filesystem.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/nav.js \
src/js/resolution_event_broker.js \
......
......@@ -38,6 +38,7 @@
<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_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_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" />
......@@ -46,6 +47,7 @@
<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_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" />
......
......@@ -21,6 +21,8 @@ js_library("camera3_device_info") {
js_library("constraints_preferrer") {
deps = [
"..:resolution_event_broker",
"..:state",
":camera3_device_info",
"../browser_proxy:browser_proxy",
]
}
......
......@@ -18,7 +18,7 @@ cca.App = function() {
* @type {cca.models.Gallery}
* @private
*/
this.model_ = new cca.models.Gallery();
this.gallery_ = new cca.models.Gallery();
/**
* @type {cca.ResolutionEventBroker}
......@@ -51,20 +51,20 @@ cca.App = function() {
* @type {cca.GalleryButton}
* @private
*/
this.galleryButton_ = new cca.GalleryButton(this.model_);
this.galleryButton_ = new cca.GalleryButton(this.gallery_);
/**
* @type {cca.views.Browser}
* @private
*/
this.browserView_ = new cca.views.Browser(this.model_);
this.browserView_ = new cca.views.Browser(this.gallery_);
/**
* @type {cca.views.Camera}
* @private
*/
this.cameraView_ = new cca.views.Camera(
this.model_, this.infoUpdater_, this.photoPreferrer_,
this.gallery_, this.infoUpdater_, this.photoPreferrer_,
this.videoPreferrer_);
// End of properties. Seal the object.
......@@ -163,11 +163,11 @@ cca.App.prototype.start = function() {
});
}).then((external) => {
cca.state.set('ext-fs', external);
this.model_.addObserver(this.galleryButton_);
this.gallery_.addObserver(this.galleryButton_);
if (!cca.App.useGalleryApp()) {
this.model_.addObserver(this.browserView_);
this.gallery_.addObserver(this.browserView_);
}
this.model_.load();
this.gallery_.load();
cca.nav.open('camera');
}).catch((error) => {
console.error(error);
......
......@@ -94,13 +94,13 @@ cca.metrics.launchType_ = function(ackMigrate) {
/**
* 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 {number} length Length of 1 minute buckets for captured video.
* @param {number} width The width of the capture resolution.
* @param {number} height The height of the capture resolution.
* @return {analytics.EventBuilder}
* @private
*/
cca.metrics.captureType_ = function(facingMode, length, [width, height]) {
cca.metrics.captureType_ = function(facingMode, length, {width, height}) {
var condState = (states, cond, strict) => {
// Return the first existing state among the given states only if there is
// no gate condition or the condition is met.
......
......@@ -9,6 +9,8 @@ js_type_check("closure_compile") {
":filenamer",
":filesystem",
":gallery",
":result_saver",
":video_saver",
]
}
......@@ -26,3 +28,9 @@ js_library("gallery") {
":filesystem",
]
}
js_library("result_saver") {
}
js_library("video_saver") {
}
......@@ -288,13 +288,18 @@ cca.models.FileSystem.savePhoto = function(blob, filename) {
/**
* 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() {
const dir =
cca.models.FileSystem.externalDir || cca.models.FileSystem.internalDir;
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 || {};
/**
* Creates the gallery model controller.
* @constructor
* @implements {cca.models.ResultSaver}
*/
cca.models.Gallery = function() {
/**
......@@ -285,12 +286,9 @@ cca.models.Gallery.prototype.wrapPicture_ = function(
};
/**
* Saves photo capture result into persistent storage and adds it into gallery.
* @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.
* @override
*/
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
// and then add a new picture into the model.
var saved = new Promise((resolve) => {
......@@ -301,7 +299,7 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) {
cca.util.orientPhoto(blob, resolve, () => resolve(blob));
})
.then((blob) => {
return cca.models.FileSystem.savePhoto(blob, filename);
return cca.models.FileSystem.savePhoto(blob, name);
})
.then((pictureEntry) => {
return this.wrapPicture_(pictureEntry);
......@@ -311,12 +309,19 @@ cca.models.Gallery.prototype.savePhoto = function(blob, filename) {
};
/**
* Saves video capture result into persistent storage and adds it into gallery.
* @param {FileEntry} tempfile File saving temporary video recording result.
* @param {string} filename Filename of picture to be added.
* @override
*/
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) {
const savedFile = await cca.models.FileSystem.saveVideo(tempfile, filename);
cca.models.Gallery.prototype.finishSaveVideo = async function(video, name) {
const tempFile = await video.endWrite();
const savedFile = await cca.models.FileSystem.saveVideo(tempFile, name);
const picture = await this.wrapPicture_(savedFile);
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() {
* @param {!PhotoSettings} photoSettings Photo settings for ImageCapture's
* takePhoto().
* @param {?Array<cros.mojom.Effect>} photoEffects Photo effects to be applied.
* @return {Array<Promise<Blob>>} Array of promises for the result.
* @return {!Array<!Promise<!Blob>>} Array of promises for the result.
*/
cca.mojo.ImageCapture.prototype.takePhoto = function(
photoSettings, photoEffects) {
......
......@@ -892,7 +892,7 @@ cca.util.setupI18nElements = function(rootElement) {
/**
* Reads blob into Image.
* @param {!Blob} blob
* @return {Promise<HTMLImageElement>}
* @return {!Promise<!HTMLImageElement>}
* @throws {Error}
*/
cca.util.blobToImage = function(blob) {
......
......@@ -16,23 +16,16 @@ cca.views = cca.views || {};
/**
* 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.PhotoResolPreferrer} photoPreferrer
* @param {cca.device.VideoConstraintsPreferrer} videoPreferrer
* @constructor
*/
cca.views.Camera = function(
model, infoUpdater, photoPreferrer, videoPreferrer) {
resultSaver, infoUpdater, photoPreferrer, videoPreferrer) {
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}
* @private
......@@ -71,36 +64,37 @@ cca.views.Camera = function(
*/
this.bannerLearnMore_ = document.querySelector('#banner-learn-more');
const doSavePhoto = async (result, name) => {
cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, 0, result.resolution);
try {
await resultSaver.savePhoto(result.blob, name);
} catch (e) {
cca.toast.show('error_msg_save_file_failed');
throw e;
}
};
const createVideoSaver = async () => resultSaver.startSaveVideo();
const doSaveVideo = async (result, name) => {
cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, result.duration,
result.resolution);
try {
await resultSaver.finishSaveVideo(result.videoSaver, name);
} catch (e) {
cca.toast.show('error_msg_save_file_failed');
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),
async (result, filename) => {
if (result.blob) {
cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, 0, result.resolution);
try {
await this.model_.savePhoto(result.blob, filename);
} catch (e) {
cca.toast.show('error_msg_save_file_failed');
throw e;
}
}
},
async (result, filename) => {
cca.metrics.log(
cca.metrics.Type.CAPTURE, this.facingMode_, result.duration,
result.resolution);
try {
await this.model_.saveVideo(result.chunkfile, filename);
} catch (e) {
cca.toast.show('error_msg_save_file_failed');
throw e;
}
});
photoPreferrer, videoPreferrer, this.restart.bind(this), doSavePhoto,
createVideoSaver, doSaveVideo);
/**
* @type {?string}
......
......@@ -26,6 +26,8 @@
<script src="../js/models/filenamer.js"></script>
<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/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>
......
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