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 @@
import {AsyncJobQueue} from '../async_job_queue.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.
*/
export class AsyncWriter {
/**
* @param {function(!Blob): !Promise} doWrite
* @param {{onClosed: ((function(): !Promise)|undefined)}=} callbacks
* @param {!AsyncOps} ops
*/
constructor(doWrite, {onClosed = (async () => {})} = {}) {
constructor(ops) {
/**
* @type {!AsyncJobQueue}
* @private
......@@ -21,16 +31,10 @@ export class AsyncWriter {
this.queue_ = new AsyncJobQueue();
/**
* @type {function(!Blob): !Promise}
* @private
*/
this.doWrite_ = doWrite;
/**
* @type {function(): !Promise}
* @type {!AsyncOps}
* @private
*/
this.onClosed_ = onClosed;
this.ops_ = ops;
/**
* @type {boolean}
......@@ -39,6 +43,14 @@ export class AsyncWriter {
this.closed_ = false;
}
/**
* Checks whether the writer supports seek operation.
* @return {boolean}
*/
seekable() {
return this.ops_.seek !== null;
}
/**
* Writes the blob asynchronously with |doWrite|.
* @param {!Blob} blob
......@@ -46,7 +58,18 @@ export class AsyncWriter {
*/
async write(blob) {
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 {
*/
async close() {
this.closed_ = true;
if (this.ops_.close !== null) {
this.queue_.push(() => this.ops_.close());
}
await this.queue_.flush();
await this.onClosed_();
}
/**
......@@ -66,12 +91,20 @@ export class AsyncWriter {
* @return {!AsyncWriter} The combined writer.
*/
static combine(...writers) {
const doWrite = (blob) => {
const 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 new AsyncWriter(doWrite, {onClosed});
return new AsyncWriter({write, seek, close});
}
}
......@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// 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 {
AbstractDirectoryEntry, // eslint-disable-line no-unused-vars
......@@ -89,12 +89,16 @@ export class ChromeFileEntry extends ChromeFileSystemEntry {
*/
async getWriter() {
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.onerror = reject;
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 {
// 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
// video will not be dropped entirely.
const doWrite = (blob) => writer.write(blob);
return new AsyncWriter(doWrite, {
onClosed: () => writer.close(),
return new AsyncWriter({
write: (blob) => writer.write(blob),
seek: (offset) => writer.seek(offset),
close: () => writer.close(),
});
}
......
......@@ -39,10 +39,11 @@ async function createVideoProcessor(output) {
* @return {!AsyncWriter}
*/
function createWriterForIntent(intent) {
const doWrite = async (blob) => {
const write = async (blob) => {
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