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) => {
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. * Modes for the camera.
* @type {cca.views.camera.Modes} * @type {cca.views.camera.Modes}
* @private * @private
*/ */
this.modes_ = new cca.views.camera.Modes( this.modes_ = new cca.views.camera.Modes(
photoPreferrer, videoPreferrer, this.restart.bind(this), photoPreferrer, videoPreferrer, this.restart.bind(this), doSavePhoto,
async (result, filename) => { createVideoSaver, doSaveVideo);
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;
}
});
/** /**
* @type {?string} * @type {?string}
......
...@@ -19,84 +19,72 @@ cca.views = cca.views || {}; ...@@ -19,84 +19,72 @@ cca.views = cca.views || {};
*/ */
cca.views.camera = cca.views.camera || {}; cca.views.camera = cca.views.camera || {};
/** /* eslint-disable no-unused-vars */
* Callback for saving photo capture result. It's called with parameter of photo
* capture result and filename to be saved to.
* @typedef {function(cca.views.camera.PhotoResult, string): Promise}
* DoSavePhoto
*/
/** /**
* Callback for saving video capture result. It's called with parameter of video * Contains video recording result.
* capture result and filename to be saved to. * @typedef {{
* @typedef {function(cca.views.camera.VideoResult, string): Promise} * resolution: {width: number, height: number},
* DoSaveVideo * duration: number,
* videoSaver: !cca.models.VideoSaver,
* }}
*/ */
cca.views.camera.VideoResult;
/** /**
* Object contains video recording result. * Contains photo taking result.
* @param {number} width Resolution width of the video. * @typedef {{
* @param {number} height Resolution height of the video. * resolution: {width: number, height: number},
* @param {number} duration Recorded time in minutes. * blob: !Blob,
* @param {FileEntry} chunkfile File saving recorded chunks. * }}
* @constructor
*/ */
cca.views.camera.VideoResult = function(width, height, duration, chunkfile) { cca.views.camera.PhotoResult;
/**
* @type {[number, number]} Resolution of the video.
*/
this.resolution = [width, height];
/** /* eslint-enable no-unused-vars */
* @type {number}
*/
this.duration = duration;
/**
* @type {FileEntry}
*/
this.chunkfile = chunkfile;
// End of properties, seal the object. /**
Object.seal(this); * Callback to trigger mode switching.
}; * @callback DoSwitchMode
* @return {!Promise}
*/
/** /**
* Object contains photo taking result. * Callback for saving photo capture result.
* @param {number} width Resolution width of the photo. * @callback DoSavePhoto
* @param {number} height Resolution height of the photo. * @param {!cca.views.camera.PhotoResult} Captured photo result.
* @param {?Blob} blob Blob saving photo result. * @param {string} Name of the photo result to be saved as.
* @constructor * @return {!Promise}
*/ */
cca.views.camera.PhotoResult = function(width, height, blob) {
/**
* @type {[number, number]} Resolution of the photo.
*/
this.resolution = [width, height];
/** /**
* @type {?Blob} * Callback for allocating VideoSaver to save video capture result.
*/ * @callback CreateVideoSaver
this.blob = blob; * @return {!Promise<!cca.models.VideoSaver>}
*/
// End of properties, seal the object. /**
Object.seal(this); * Callback for saving video capture result.
}; * @callback DoSaveVideo
* @param {!cca.views.camera.VideoResult} Captured video result.
* @param {string} Name of the video result to be saved as.
* @return {!Promise}
*/
/** /**
* Mode controller managing capture sequence of different camera mode. * Mode controller managing capture sequence of different camera mode.
* @param {cca.device.PhotoResolPreferrer} photoResolPreferrer * @param {cca.device.PhotoResolPreferrer} photoResolPreferrer
* @param {cca.device.VideoConstraintsPreferrer} videoPreferrer * @param {cca.device.VideoConstraintsPreferrer} videoPreferrer
* @param {function()} doSwitchMode Callback to trigger mode switching. * @param {!DoSwitchMode} doSwitchMode
* @param {DoSavePhoto} doSavePhoto * @param {!DoSavePhoto} doSavePhoto
* @param {DoSaveVideo} doSaveVideo * @param {!CreateVideoSaver} createVideoSaver
* @param {!DoSaveVideo} doSaveVideo
* @constructor * @constructor
*/ */
cca.views.camera.Modes = function( cca.views.camera.Modes = function(
photoResolPreferrer, videoPreferrer, doSwitchMode, doSavePhoto, photoResolPreferrer, videoPreferrer, doSwitchMode, doSavePhoto,
doSaveVideo) { createVideoSaver, doSaveVideo) {
/** /**
* @type {function()} * @type {!DoSwitchMode}
* @private * @private
*/ */
this.doSwitchMode_ = doSwitchMode; this.doSwitchMode_ = doSwitchMode;
...@@ -133,8 +121,8 @@ cca.views.camera.Modes = function( ...@@ -133,8 +121,8 @@ cca.views.camera.Modes = function(
*/ */
this.allModes_ = { this.allModes_ = {
'video-mode': { 'video-mode': {
captureFactory: () => captureFactory: () => new cca.views.camera.Video(
new cca.views.camera.Video(this.stream_, doSaveVideo), this.stream_, createVideoSaver, doSaveVideo),
isSupported: async () => true, isSupported: async () => true,
resolutionConfig: videoPreferrer, resolutionConfig: videoPreferrer,
v1Config: cca.views.camera.Modes.getV1Constraints.bind(this, true), v1Config: cca.views.camera.Modes.getV1Constraints.bind(this, true),
...@@ -429,16 +417,22 @@ cca.views.camera.Mode.prototype.stop_ = function() {}; ...@@ -429,16 +417,22 @@ cca.views.camera.Mode.prototype.stop_ = function() {};
/** /**
* Video mode capture controller. * Video mode capture controller.
* @param {MediaStream} stream * @param {MediaStream} stream
* @param {DoSaveVideo} doSaveVideo * @param {!CreateVideoSaver} createVideoSaver
* @param {!DoSaveVideo} doSaveVideo
* @constructor * @constructor
*/ */
cca.views.camera.Video = function(stream, doSaveVideo) { cca.views.camera.Video = function(stream, createVideoSaver, doSaveVideo) {
cca.views.camera.Mode.call(this, stream, null); cca.views.camera.Mode.call(this, stream, null);
/** /**
* Callback for saving video. * @type {!CreateVideoSaver}
* @type {DoSaveVideo} doSaveVideo * @private
* @protected */
this.createVideoSaver_ = createVideoSaver;
/**
* @type {!DoSaveVideo}
* @private
*/ */
this.doSaveVideo_ = doSaveVideo; this.doSaveVideo_ = doSaveVideo;
...@@ -458,7 +452,7 @@ cca.views.camera.Video = function(stream, doSaveVideo) { ...@@ -458,7 +452,7 @@ cca.views.camera.Video = function(stream, doSaveVideo) {
/** /**
* Record-time for the elapsed recording time. * Record-time for the elapsed recording time.
* @type {cca.views.camera.RecordTime} * @type {!cca.views.camera.RecordTime}
* @private * @private
*/ */
this.recordTime_ = new cca.views.camera.RecordTime(); this.recordTime_ = new cca.views.camera.RecordTime();
...@@ -498,7 +492,7 @@ cca.views.camera.Video.prototype.start_ = async function() { ...@@ -498,7 +492,7 @@ cca.views.camera.Video.prototype.start_ = async function() {
this.recordTime_.start(); this.recordTime_.start();
try { try {
var chunkfile = await this.createChunkfile_(); var videoSaver = await this.captureVideo_();
} catch (e) { } catch (e) {
cca.toast.show('error_msg_empty_recording'); cca.toast.show('error_msg_empty_recording');
throw e; throw e;
...@@ -509,7 +503,7 @@ cca.views.camera.Video.prototype.start_ = async function() { ...@@ -509,7 +503,7 @@ cca.views.camera.Video.prototype.start_ = async function() {
const {width, height} = this.stream_.getVideoTracks()[0].getSettings(); const {width, height} = this.stream_.getVideoTracks()[0].getSettings();
await this.doSaveVideo_( await this.doSaveVideo_(
new cca.views.camera.VideoResult(width, height, duration, chunkfile), {resolution: {width, height}, duration, videoSaver},
(new cca.models.Filenamer()).newVideoName()); (new cca.models.Filenamer()).newVideoName());
}; };
...@@ -536,47 +530,31 @@ cca.views.camera.Video.VIDEO_MIMETYPE = 'video/x-matroska;codecs=avc1'; ...@@ -536,47 +530,31 @@ cca.views.camera.Video.VIDEO_MIMETYPE = 'video/x-matroska;codecs=avc1';
/** /**
* Starts recording and waits for stop recording event triggered by stop * Starts recording and waits for stop recording event triggered by stop
* shutter. * shutter.
* @return {FileEntry} File saving recorded chunks. * @return {!Promise<!cca.models.VideoSaver>} Saves recorded video.
* @async
* @private * @private
*/ */
cca.views.camera.Video.prototype.createChunkfile_ = async function() { cca.views.camera.Video.prototype.captureVideo_ = async function() {
const chunkfile = await cca.models.FileSystem.createTempVideoFile(); const saver = await this.createVideoSaver_();
const writer = await new Promise(
(resolve, reject) => chunkfile.createWriter(resolve, reject));
return await new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
let noChunk = true; let noChunk = true;
let prevWrite = Promise.resolve();
var ondataavailable = (event) => { var ondataavailable = (event) => {
if (event.data && event.data.size > 0) { if (event.data && event.data.size > 0) {
noChunk = false; noChunk = false;
prevWrite = (async () => { saver.write(event.data);
await prevWrite;
await new Promise((resolve) => {
writer.onwriteend = resolve;
writer.write(event.data);
});
})();
} }
}; };
var onstop = (event) => { var onstop = (event) => {
this.mediaRecorder_.removeEventListener('dataavailable', ondataavailable); this.mediaRecorder_.removeEventListener('dataavailable', ondataavailable);
this.mediaRecorder_.removeEventListener('stop', onstop); this.mediaRecorder_.removeEventListener('stop', onstop);
prevWrite.then(() => { if (noChunk) {
if (noChunk) { reject(new Error('Video blob error.'));
reject(new Error('Video blob error.')); } else {
} else { // TODO(yuli): Handle insufficient storage.
resolve(chunkfile); resolve(saver);
} }
});
prevWrite.catch(
(e) => {
// TODO(yuli): Handle insufficient storage.
});
}; };
this.mediaRecorder_.addEventListener('dataavailable', ondataavailable); this.mediaRecorder_.addEventListener('dataavailable', ondataavailable);
this.mediaRecorder_.addEventListener('stop', onstop); this.mediaRecorder_.addEventListener('stop', onstop);
...@@ -587,7 +565,7 @@ cca.views.camera.Video.prototype.createChunkfile_ = async function() { ...@@ -587,7 +565,7 @@ cca.views.camera.Video.prototype.createChunkfile_ = async function() {
/** /**
* Photo mode capture controller. * Photo mode capture controller.
* @param {MediaStream} stream * @param {MediaStream} stream
* @param {DoSavePhoto} doSavePhoto * @param {!DoSavePhoto} doSavePhoto
* @param {?[number, number]} captureResolution * @param {?[number, number]} captureResolution
* @constructor * @constructor
*/ */
...@@ -596,7 +574,7 @@ cca.views.camera.Photo = function(stream, doSavePhoto, captureResolution) { ...@@ -596,7 +574,7 @@ cca.views.camera.Photo = function(stream, doSavePhoto, captureResolution) {
/** /**
* Callback for saving picture. * Callback for saving picture.
* @type {DoSavePhoto} * @type {!DoSavePhoto}
* @protected * @protected
*/ */
this.doSavePhoto_ = doSavePhoto; this.doSavePhoto_ = doSavePhoto;
...@@ -639,7 +617,7 @@ cca.views.camera.Photo.prototype.start_ = async function() { ...@@ -639,7 +617,7 @@ cca.views.camera.Photo.prototype.start_ = async function() {
/** /**
* Takes a photo and returns capture result. * Takes a photo and returns capture result.
* @async * @async
* @return {cca.views.camera.PhotoResult} Image capture result. * @return {!cca.views.camera.PhotoResult} Image capture result.
* @private * @private
*/ */
cca.views.camera.Photo.prototype.createPhotoResult_ = async function() { cca.views.camera.Photo.prototype.createPhotoResult_ = async function() {
...@@ -656,14 +634,14 @@ cca.views.camera.Photo.prototype.createPhotoResult_ = async function() { ...@@ -656,14 +634,14 @@ cca.views.camera.Photo.prototype.createPhotoResult_ = async function() {
}; };
} }
const blob = await this.imageCapture_.takePhoto(photoSettings); const blob = await this.imageCapture_.takePhoto(photoSettings);
const image = await cca.util.blobToImage(blob); const {width, height} = await cca.util.blobToImage(blob);
return new cca.views.camera.PhotoResult(image.width, image.height, blob); return {resolution: {width, height}, blob};
}; };
/** /**
* Square mode capture controller. * Square mode capture controller.
* @param {MediaStream} stream * @param {MediaStream} stream
* @param {DoSavePhoto} doSavePhoto * @param {!DoSavePhoto} doSavePhoto
* @param {?[number, number]} captureResolution * @param {?[number, number]} captureResolution
* @constructor * @constructor
*/ */
...@@ -672,7 +650,7 @@ cca.views.camera.Square = function(stream, doSavePhoto, captureResolution) { ...@@ -672,7 +650,7 @@ cca.views.camera.Square = function(stream, doSavePhoto, captureResolution) {
/** /**
* Photo saving callback from parent. * Photo saving callback from parent.
* @type {DoSavePhoto} * @type {!DoSavePhoto}
* @private * @private
*/ */
this.doAscentSave_ = this.doSavePhoto_; this.doAscentSave_ = this.doSavePhoto_;
...@@ -681,9 +659,7 @@ cca.views.camera.Square = function(stream, doSavePhoto, captureResolution) { ...@@ -681,9 +659,7 @@ cca.views.camera.Square = function(stream, doSavePhoto, captureResolution) {
Object.seal(this); Object.seal(this);
this.doSavePhoto_ = async (result, ...args) => { this.doSavePhoto_ = async (result, ...args) => {
if (result.blob) { result.blob = await this.cropSquare(result.blob);
result.blob = await this.cropSquare(result.blob);
}
await this.doAscentSave_(result, ...args); await this.doAscentSave_(result, ...args);
}; };
}; };
...@@ -694,8 +670,8 @@ cca.views.camera.Square.prototype = { ...@@ -694,8 +670,8 @@ cca.views.camera.Square.prototype = {
/** /**
* Crops out maximum possible centered square from the image blob. * Crops out maximum possible centered square from the image blob.
* @param {Blob} blob * @param {!Blob} blob
* @return {Blob} Promise with result cropped square image. * @return {!Blob} Promise with result cropped square image.
* @async * @async
*/ */
cca.views.camera.Square.prototype.cropSquare = async function(blob) { cca.views.camera.Square.prototype.cropSquare = async function(blob) {
...@@ -718,7 +694,7 @@ cca.views.camera.Square.prototype.cropSquare = async function(blob) { ...@@ -718,7 +694,7 @@ cca.views.camera.Square.prototype.cropSquare = async function(blob) {
/** /**
* Portrait mode capture controller. * Portrait mode capture controller.
* @param {MediaStream} stream * @param {MediaStream} stream
* @param {DoSavePhoto} doSavePhoto * @param {!DoSavePhoto} doSavePhoto
* @param {?[number, number]} captureResolution * @param {?[number, number]} captureResolution
* @constructor * @constructor
*/ */
...@@ -788,9 +764,9 @@ cca.views.camera.Portrait.prototype.start_ = async function() { ...@@ -788,9 +764,9 @@ cca.views.camera.Portrait.prototype.start_ = async function() {
playSound = true; playSound = true;
cca.sound.play('#sound-shutter'); cca.sound.play('#sound-shutter');
} }
const image = await cca.util.blobToImage(blob); const {width, height} = await cca.util.blobToImage(blob);
await this.doSavePhoto_( await this.doSavePhoto_(
new cca.views.camera.PhotoResult(image.width, image.height, blob), {resolution: {width, height}, blob},
filenamer.newBurstName(!isPortrait)); filenamer.newBurstName(!isPortrait));
}); });
try { try {
......
...@@ -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