Commit 61c855af authored by Shik Chen's avatar Shik Chen Committed by Commit Bot

CCA: Add seek support in AsyncWriter and FileSystemEntry

The seek operation is required for non-fragmented mp4 recording. This CL
adds the seek support for the underlying file writers, except the
Android intent writer.

Bug: 1140852
Test: Pass camera.CCAUIRecordVideo and camera.CCAUIIntent
Change-Id: I1e111f9776b1c44905a5de203dbe8c95570ce490
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2513961Reviewed-by: default avatarInker Kuo <inker@chromium.org>
Commit-Queue: Shik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#823411}
parent 24f00a5f
...@@ -5,15 +5,25 @@ ...@@ -5,15 +5,25 @@
import {AsyncJobQueue} from '../async_job_queue.js'; import {AsyncJobQueue} from '../async_job_queue.js';
import {assert} from '../chrome_util.js'; import {assert} from '../chrome_util.js';
/**
* Represents a set of operations of a file-like writable stream. The seek and
* close operations are optional.
* @typedef {{
* write: function(!Blob): !Promise,
* seek: ((function(number): !Promise)|null),
* close: ((function(): !Promise)|null)
* }}
*/
export let AsyncOps;
/** /**
* Asynchronous writer. * Asynchronous writer.
*/ */
export class AsyncWriter { export class AsyncWriter {
/** /**
* @param {function(!Blob): !Promise} doWrite * @param {!AsyncOps} ops
* @param {{onClosed: ((function(): !Promise)|undefined)}=} callbacks
*/ */
constructor(doWrite, {onClosed = (async () => {})} = {}) { constructor(ops) {
/** /**
* @type {!AsyncJobQueue} * @type {!AsyncJobQueue}
* @private * @private
...@@ -21,16 +31,10 @@ export class AsyncWriter { ...@@ -21,16 +31,10 @@ export class AsyncWriter {
this.queue_ = new AsyncJobQueue(); this.queue_ = new AsyncJobQueue();
/** /**
* @type {function(!Blob): !Promise} * @type {!AsyncOps}
* @private
*/
this.doWrite_ = doWrite;
/**
* @type {function(): !Promise}
* @private * @private
*/ */
this.onClosed_ = onClosed; this.ops_ = ops;
/** /**
* @type {boolean} * @type {boolean}
...@@ -39,6 +43,14 @@ export class AsyncWriter { ...@@ -39,6 +43,14 @@ export class AsyncWriter {
this.closed_ = false; this.closed_ = false;
} }
/**
* Checks whether the writer supports seek operation.
* @return {boolean}
*/
seekable() {
return this.ops_.seek !== null;
}
/** /**
* Writes the blob asynchronously with |doWrite|. * Writes the blob asynchronously with |doWrite|.
* @param {!Blob} blob * @param {!Blob} blob
...@@ -46,7 +58,18 @@ export class AsyncWriter { ...@@ -46,7 +58,18 @@ export class AsyncWriter {
*/ */
async write(blob) { async write(blob) {
assert(!this.closed_); assert(!this.closed_);
await this.queue_.push(() => this.doWrite_(blob)); await this.queue_.push(() => this.ops_.write(blob));
}
/**
* Seeks to the specified |offset|.
* @param {number} offset
* @return {!Promise} Resolved when the seek operation is finished.
*/
async seek(offset) {
assert(!this.closed_);
assert(this.seekable());
await this.queue_.push(() => this.ops_.seek(offset));
} }
/** /**
...@@ -55,8 +78,10 @@ export class AsyncWriter { ...@@ -55,8 +78,10 @@ export class AsyncWriter {
*/ */
async close() { async close() {
this.closed_ = true; this.closed_ = true;
if (this.ops_.close !== null) {
this.queue_.push(() => this.ops_.close());
}
await this.queue_.flush(); await this.queue_.flush();
await this.onClosed_();
} }
/** /**
...@@ -66,12 +91,20 @@ export class AsyncWriter { ...@@ -66,12 +91,20 @@ export class AsyncWriter {
* @return {!AsyncWriter} The combined writer. * @return {!AsyncWriter} The combined writer.
*/ */
static combine(...writers) { static combine(...writers) {
const doWrite = (blob) => { const write = (blob) => {
return Promise.all(writers.map((writer) => writer.write(blob))); return Promise.all(writers.map((writer) => writer.write(blob)));
}; };
const onClosed = () => {
const allSeekable = writers.every((writer) => writer.seekable());
const seekAll = (offset) => {
return Promise.all(writers.map((writer) => writer.seek(offset)));
};
const seek = allSeekable ? seekAll : null;
const close = () => {
return Promise.all(writers.map((writer) => writer.close())); return Promise.all(writers.map((writer) => writer.close()));
}; };
return new AsyncWriter(doWrite, {onClosed});
return new AsyncWriter({write, seek, close});
} }
} }
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import {promisify, promisifyWithError} from '../chrome_util.js'; import {assert, promisify, promisifyWithError} from '../chrome_util.js';
import {AsyncWriter} from './async_writer.js'; import {AsyncWriter} from './async_writer.js';
import { import {
AbstractDirectoryEntry, // eslint-disable-line no-unused-vars AbstractDirectoryEntry, // eslint-disable-line no-unused-vars
...@@ -89,12 +89,16 @@ export class ChromeFileEntry extends ChromeFileSystemEntry { ...@@ -89,12 +89,16 @@ export class ChromeFileEntry extends ChromeFileSystemEntry {
*/ */
async getWriter() { async getWriter() {
const fileWriter = await this.entry_ops_.createWriter(); const fileWriter = await this.entry_ops_.createWriter();
const doWrite = (blob) => new Promise((resolve, reject) => { const write = (blob) => new Promise((resolve, reject) => {
fileWriter.onwriteend = resolve; fileWriter.onwriteend = resolve;
fileWriter.onerror = reject; fileWriter.onerror = reject;
fileWriter.write(blob); fileWriter.write(blob);
}); });
return new AsyncWriter(doWrite); const seek = async (offset) => {
fileWriter.seek(offset);
assert(fileWriter.position === offset);
};
return new AsyncWriter({write, seek, close: null});
} }
/** /**
......
...@@ -78,9 +78,10 @@ export class NativeFileEntry extends NativeFileSystemEntry { ...@@ -78,9 +78,10 @@ export class NativeFileEntry extends NativeFileSystemEntry {
// TODO(crbug.com/980846): We should write files in-place so that even the // TODO(crbug.com/980846): We should write files in-place so that even the
// app is accidentally closed or hit any unexpected exceptions, the captured // app is accidentally closed or hit any unexpected exceptions, the captured
// video will not be dropped entirely. // video will not be dropped entirely.
const doWrite = (blob) => writer.write(blob); return new AsyncWriter({
return new AsyncWriter(doWrite, { write: (blob) => writer.write(blob),
onClosed: () => writer.close(), seek: (offset) => writer.seek(offset),
close: () => writer.close(),
}); });
} }
......
...@@ -39,10 +39,11 @@ async function createVideoProcessor(output) { ...@@ -39,10 +39,11 @@ async function createVideoProcessor(output) {
* @return {!AsyncWriter} * @return {!AsyncWriter}
*/ */
function createWriterForIntent(intent) { function createWriterForIntent(intent) {
const doWrite = async (blob) => { const write = async (blob) => {
await intent.appendData(new Uint8Array(await blob.arrayBuffer())); await intent.appendData(new Uint8Array(await blob.arrayBuffer()));
}; };
return new AsyncWriter(doWrite); // TODO(crbug.com/1140852): Supports seek.
return new AsyncWriter({write, seek: null, close: null});
} }
/** /**
......
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