Commit e8a6c1ea authored by Shik Chen's avatar Shik Chen Committed by Commit Bot

CCA: Run getUserMedia() once before enumerateDevices()

Recently the spec is updated to require a successful getUserMedia()
before running enumerateDevices(). Otherwise the deviceId and label
fields would be empty. See https://crbug.com/1101860 for more details.

Bug: 1103303, 1101860
Test: tast run <nocturne> camera.CCAUI*

Change-Id: I61b4278d20720bf6424dada24a1d4e4b73f8c5d4
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2321907Reviewed-by: default avatarWei Lee <wtlee@chromium.org>
Commit-Queue: Shik Chen <shik@chromium.org>
Cr-Commit-Position: refs/heads/master@{#792156}
parent 37a21024
......@@ -12,6 +12,31 @@ import {BrowserProxy} from './browser_proxy_interface.js';
* @implements {BrowserProxy}
*/
class ChromeAppBrowserProxy {
/** @override */
async requestEnumerateDevicesPermission() {
// It's required to run getUserMedia() successfully once before running
// enumerateDevices(). Otherwise the deviceId and label fields would be
// empty. See https://crbug.com/1101860 for more details.
const doGetUserMedia = async (constraints) => {
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
stream.getTracks().forEach((track) => track.stop());
return true;
} catch (e) {
return false;
}
};
// Try audio stream first since it's usually much faster to open an audio
// stream than a video stream. Note that in some form factors such as
// Chromebox there might be no internal microphone, so the audio request
// might fail and we would fall back to video. If no external camera
// connected in that case, the video request might also fail and we need to
// try again later.
return (
await doGetUserMedia({audio: true}) ||
await doGetUserMedia({video: true}));
}
/** @override */
async getExternalDir() {
let volumes;
......
......@@ -13,6 +13,12 @@ import {
* @interface
*/
export class BrowserProxy {
/**
* @return {!Promise<boolean>}
* @abstract
*/
async requestEnumerateDevicesPermission() {}
/**
* @return {!Promise<?AbstractDirectoryEntry>}
* @abstract
......
......@@ -17,6 +17,13 @@ function NOTIMPLEMENTED() {
* @implements {BrowserProxy}
*/
class WebUIBrowserProxy {
/** @override */
async requestEnumerateDevicesPermission() {
// No operation here since the permission is automatically granted for
// the chrome:// scheme.
return true;
}
/** @override */
async getExternalDir() {
NOTIMPLEMENTED();
......
......@@ -2,12 +2,15 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import {browserProxy} from '../browser_proxy/browser_proxy.js';
import {DeviceOperator} from '../mojo/device_operator.js';
// eslint-disable-next-line no-unused-vars
import {ResolutionList} from '../type.js';
import {Camera3DeviceInfo} from './camera3_device_info.js';
import {PhotoConstraintsPreferrer, // eslint-disable-line no-unused-vars
VideoConstraintsPreferrer, // eslint-disable-line no-unused-vars
import {
PhotoConstraintsPreferrer, // eslint-disable-line no-unused-vars
VideoConstraintsPreferrer, // eslint-disable-line no-unused-vars
} from './constraints_preferrer.js';
import {LegacyVCDError} from './error.js';
......@@ -62,6 +65,13 @@ export class DeviceInfoUpdater {
*/
this.devicesInfo_ = this.enumerateDevices_();
/**
* Got the permission to run enumerateDevices() or not.
* @type {boolean}
* @private
*/
this.canEnumerateDevices_ = false;
/**
* Camera3DeviceInfo of all available video devices. Is null on HALv1 device
* without mojo api support.
......@@ -137,6 +147,13 @@ export class DeviceInfoUpdater {
* @private
*/
async enumerateDevices_() {
if (!this.canEnumerateDevices_) {
this.canEnumerateDevices_ =
await browserProxy.requestEnumerateDevicesPermission();
if (!this.canEnumerateDevices_) {
throw new Error('Failed to get the permission for enumerateDevices()');
}
}
const devices = (await navigator.mediaDevices.enumerateDevices())
.filter((device) => device.kind === 'videoinput');
if (devices.length === 0) {
......@@ -174,7 +191,7 @@ export class DeviceInfoUpdater {
* Requests to lock update of device information. This function is preserved
* for device information reader to lock the update capability so as to ensure
* getting consistent data between all information providers.
* @param {!function(!DeviceInfoUpdater): Promise} callback Called after
* @param {!function(): Promise} callback Called after
* update capability is locked. Getting information from all providers in
* callback are guaranteed to be consistent.
*/
......@@ -190,7 +207,7 @@ export class DeviceInfoUpdater {
}
this.lockingUpdate_ = (async () => {
try {
await callback(this);
await callback();
} finally {
this.lockingUpdate_ = 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