blob: bde8b4196eb1ff47643688773b3e868b0991c83e [file] [log] [blame]
// Copyright 2016 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.
#include "content/browser/renderer_host/media/media_devices_dispatcher_host.h"
#include <stddef.h>
#include <utility>
#include <vector>
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "content/browser/bad_message.h"
#include "content/browser/media/media_devices_util.h"
#include "content/browser/renderer_host/media/media_stream_manager.h"
#include "content/browser/renderer_host/media/video_capture_manager.h"
#include "content/common/media/media_devices.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/media_device_id.h"
#include "content/public/browser/render_frame_host.h"
#include "content/public/common/media_stream_request.h"
#include "media/base/video_facing.h"
#include "mojo/public/cpp/bindings/strong_binding.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "url/origin.h"
namespace content {
namespace {
// Resolutions used if the source doesn't support capability enumeration.
struct {
int width;
int height;
} const kFallbackVideoResolutions[] = {{1920, 1080}, {1280, 720}, {960, 720},
{640, 480}, {640, 360}, {320, 240},
{320, 180}};
// Frame rates for sources with no support for capability enumeration.
const int kFallbackVideoFrameRates[] = {30, 60};
url::Origin GetOrigin(int process_id,
int frame_id,
const url::Origin& origin_for_testing) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
if (!origin_for_testing.unique())
return origin_for_testing;
RenderFrameHost* frame_host = RenderFrameHost::FromID(process_id, frame_id);
return frame_host ? frame_host->GetLastCommittedOrigin() : url::Origin();
}
MediaDeviceInfo TranslateDeviceInfo(bool has_permission,
const std::string& device_id_salt,
const std::string& group_id_salt,
const url::Origin& security_origin,
const MediaDeviceInfo& device_info) {
return MediaDeviceInfo(
GetHMACForMediaDeviceID(device_id_salt, security_origin,
device_info.device_id),
has_permission ? device_info.label : std::string(),
device_info.group_id.empty()
? std::string()
: GetHMACForMediaDeviceID(group_id_salt, security_origin,
device_info.group_id));
}
MediaDeviceInfoArray TranslateMediaDeviceInfoArray(
bool has_permission,
const std::string& device_id_salt,
const std::string& group_id_salt,
const url::Origin& security_origin,
const MediaDeviceInfoArray& input) {
MediaDeviceInfoArray result;
for (const auto& device_info : input) {
result.push_back(TranslateDeviceInfo(has_permission, device_id_salt,
group_id_salt, security_origin,
device_info));
}
return result;
}
::mojom::FacingMode ToFacingMode(media::VideoFacingMode facing_mode) {
switch (facing_mode) {
case media::MEDIA_VIDEO_FACING_NONE:
return ::mojom::FacingMode::NONE;
case media::MEDIA_VIDEO_FACING_USER:
return ::mojom::FacingMode::USER;
case media::MEDIA_VIDEO_FACING_ENVIRONMENT:
return ::mojom::FacingMode::ENVIRONMENT;
default:
NOTREACHED();
return ::mojom::FacingMode::NONE;
}
}
} // namespace
// static
void MediaDevicesDispatcherHost::Create(
int render_process_id,
int render_frame_id,
const std::string& device_id_salt,
MediaStreamManager* media_stream_manager,
const service_manager::BindSourceInfo& source_info,
::mojom::MediaDevicesDispatcherHostRequest request) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
mojo::MakeStrongBinding(base::MakeUnique<MediaDevicesDispatcherHost>(
render_process_id, render_frame_id,
device_id_salt, media_stream_manager),
std::move(request));
}
MediaDevicesDispatcherHost::MediaDevicesDispatcherHost(
int render_process_id,
int render_frame_id,
const std::string& device_id_salt,
MediaStreamManager* media_stream_manager)
: render_process_id_(render_process_id),
render_frame_id_(render_frame_id),
device_id_salt_(device_id_salt),
group_id_salt_(BrowserContext::CreateRandomMediaDeviceIDSalt()),
media_stream_manager_(media_stream_manager),
permission_checker_(base::MakeUnique<MediaDevicesPermissionChecker>()),
weak_factory_(this) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
}
MediaDevicesDispatcherHost::~MediaDevicesDispatcherHost() {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
// It may happen that media_devices_manager() is destroyed before MDDH on some
// shutdown scenarios.
if (!media_stream_manager_->media_devices_manager())
return;
for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) {
if (!device_change_subscriptions_[i].empty()) {
media_stream_manager_->media_devices_manager()
->UnsubscribeDeviceChangeNotifications(
static_cast<MediaDeviceType>(i), this);
}
}
}
void MediaDevicesDispatcherHost::EnumerateDevices(
bool request_audio_input,
bool request_video_input,
bool request_audio_output,
EnumerateDevicesCallback client_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
if (!request_audio_input && !request_video_input && !request_audio_output) {
bad_message::ReceivedBadMessage(
render_process_id_, bad_message::MDDH_INVALID_DEVICE_TYPE_REQUEST);
return;
}
MediaDevicesManager::BoolDeviceTypes devices_to_enumerate;
devices_to_enumerate[MEDIA_DEVICE_TYPE_AUDIO_INPUT] = request_audio_input;
devices_to_enumerate[MEDIA_DEVICE_TYPE_VIDEO_INPUT] = request_video_input;
devices_to_enumerate[MEDIA_DEVICE_TYPE_AUDIO_OUTPUT] = request_audio_output;
base::PostTaskAndReplyWithResult(
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI).get(), FROM_HERE,
base::Bind(GetOrigin, render_process_id_, render_frame_id_,
security_origin_for_testing_),
base::Bind(
&MediaDevicesDispatcherHost::CheckPermissionsForEnumerateDevices,
weak_factory_.GetWeakPtr(), devices_to_enumerate,
base::Passed(&client_callback)));
}
void MediaDevicesDispatcherHost::GetVideoInputCapabilities(
GetVideoInputCapabilitiesCallback client_callback) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
base::PostTaskAndReplyWithResult(
BrowserThread::GetTaskRunnerForThread(BrowserThread::UI).get(), FROM_HERE,
base::Bind(GetOrigin, render_process_id_, render_frame_id_,
security_origin_for_testing_),
base::Bind(&MediaDevicesDispatcherHost::GetDefaultVideoInputDeviceID,
weak_factory_.GetWeakPtr(), base::Passed(&client_callback)));
}
void MediaDevicesDispatcherHost::SubscribeDeviceChangeNotifications(
MediaDeviceType type,
uint32_t subscription_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(IsValidMediaDeviceType(type));
auto it =
std::find(device_change_subscriptions_[type].begin(),
device_change_subscriptions_[type].end(), subscription_id);
if (it != device_change_subscriptions_[type].end()) {
bad_message::ReceivedBadMessage(
render_process_id_, bad_message::MDDH_INVALID_SUBSCRIPTION_REQUEST);
return;
}
if (device_change_subscriptions_[type].empty()) {
media_stream_manager_->media_devices_manager()
->SubscribeDeviceChangeNotifications(type, this);
}
device_change_subscriptions_[type].push_back(subscription_id);
}
void MediaDevicesDispatcherHost::UnsubscribeDeviceChangeNotifications(
MediaDeviceType type,
uint32_t subscription_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(IsValidMediaDeviceType(type));
auto it =
std::find(device_change_subscriptions_[type].begin(),
device_change_subscriptions_[type].end(), subscription_id);
// Ignore invalid unsubscription requests.
if (it == device_change_subscriptions_[type].end())
return;
device_change_subscriptions_[type].erase(it);
if (device_change_subscriptions_[type].empty()) {
media_stream_manager_->media_devices_manager()
->UnsubscribeDeviceChangeNotifications(type, this);
}
}
void MediaDevicesDispatcherHost::OnDevicesChanged(
MediaDeviceType type,
const MediaDeviceInfoArray& device_infos) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(IsValidMediaDeviceType(type));
std::vector<uint32_t> subscriptions = device_change_subscriptions_[type];
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::BindOnce(&MediaDevicesDispatcherHost::NotifyDeviceChangeOnUIThread,
weak_factory_.GetWeakPtr(), std::move(subscriptions), type,
device_infos));
}
void MediaDevicesDispatcherHost::NotifyDeviceChangeOnUIThread(
const std::vector<uint32_t>& subscriptions,
MediaDeviceType type,
const MediaDeviceInfoArray& device_infos) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
DCHECK(IsValidMediaDeviceType(type));
::mojom::MediaDevicesListenerPtr media_devices_listener;
url::Origin security_origin;
if (device_change_listener_) {
media_devices_listener = std::move(device_change_listener_);
} else {
RenderFrameHost* render_frame_host =
RenderFrameHost::FromID(render_process_id_, render_frame_id_);
if (!render_frame_host)
return;
render_frame_host->GetRemoteInterfaces()->GetInterface(
mojo::MakeRequest(&media_devices_listener));
if (!media_devices_listener)
return;
security_origin = render_frame_host->GetLastCommittedOrigin();
}
for (uint32_t subscription_id : subscriptions) {
bool has_permission = permission_checker_->CheckPermissionOnUIThread(
type, render_process_id_, render_frame_id_);
media_devices_listener->OnDevicesChanged(
type, subscription_id,
TranslateMediaDeviceInfoArray(has_permission, device_id_salt_,
group_id_salt_, security_origin,
device_infos));
}
}
void MediaDevicesDispatcherHost::SetPermissionChecker(
std::unique_ptr<MediaDevicesPermissionChecker> permission_checker) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
DCHECK(permission_checker);
permission_checker_ = std::move(permission_checker);
}
void MediaDevicesDispatcherHost::SetDeviceChangeListenerForTesting(
::mojom::MediaDevicesListenerPtr listener) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
device_change_listener_ = std::move(listener);
}
void MediaDevicesDispatcherHost::SetSecurityOriginForTesting(
const url::Origin& origin) {
DCHECK_CURRENTLY_ON(BrowserThread::UI);
security_origin_for_testing_ = origin;
}
void MediaDevicesDispatcherHost::CheckPermissionsForEnumerateDevices(
const MediaDevicesManager::BoolDeviceTypes& requested_types,
EnumerateDevicesCallback client_callback,
const url::Origin& security_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
permission_checker_->CheckPermissions(
requested_types, render_process_id_, render_frame_id_,
base::Bind(&MediaDevicesDispatcherHost::DoEnumerateDevices,
weak_factory_.GetWeakPtr(), requested_types,
base::Passed(&client_callback), security_origin));
}
void MediaDevicesDispatcherHost::DoEnumerateDevices(
const MediaDevicesManager::BoolDeviceTypes& requested_types,
EnumerateDevicesCallback client_callback,
const url::Origin& security_origin,
const MediaDevicesManager::BoolDeviceTypes& has_permissions) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_stream_manager_->media_devices_manager()->EnumerateDevices(
requested_types,
base::Bind(&MediaDevicesDispatcherHost::DevicesEnumerated,
weak_factory_.GetWeakPtr(), requested_types,
base::Passed(&client_callback), security_origin,
has_permissions));
}
void MediaDevicesDispatcherHost::DevicesEnumerated(
const MediaDevicesManager::BoolDeviceTypes& requested_types,
EnumerateDevicesCallback client_callback,
const url::Origin& security_origin,
const MediaDevicesManager::BoolDeviceTypes& has_permissions,
const MediaDeviceEnumeration& enumeration) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::vector<std::vector<MediaDeviceInfo>> result(NUM_MEDIA_DEVICE_TYPES);
for (size_t i = 0; i < NUM_MEDIA_DEVICE_TYPES; ++i) {
if (!requested_types[i])
continue;
for (const auto& device_info : enumeration[i]) {
result[i].push_back(TranslateDeviceInfo(has_permissions[i],
device_id_salt_, group_id_salt_,
security_origin, device_info));
}
}
std::move(client_callback).Run(result);
}
void MediaDevicesDispatcherHost::GetDefaultVideoInputDeviceID(
GetVideoInputCapabilitiesCallback client_callback,
const url::Origin& security_origin) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
GetDefaultMediaDeviceID(
MEDIA_DEVICE_TYPE_VIDEO_INPUT, render_process_id_, render_frame_id_,
base::Bind(&MediaDevicesDispatcherHost::GotDefaultVideoInputDeviceID,
weak_factory_.GetWeakPtr(), base::Passed(&client_callback),
security_origin));
}
void MediaDevicesDispatcherHost::GotDefaultVideoInputDeviceID(
GetVideoInputCapabilitiesCallback client_callback,
const url::Origin& security_origin,
const std::string& default_device_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media_stream_manager_->video_capture_manager()->EnumerateDevices(
base::Bind(&MediaDevicesDispatcherHost::FinalizeGetVideoInputCapabilities,
weak_factory_.GetWeakPtr(), base::Passed(&client_callback),
security_origin, default_device_id));
}
void MediaDevicesDispatcherHost::FinalizeGetVideoInputCapabilities(
GetVideoInputCapabilitiesCallback client_callback,
const url::Origin& security_origin,
const std::string& default_device_id,
const media::VideoCaptureDeviceDescriptors& device_descriptors) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
std::vector<::mojom::VideoInputDeviceCapabilitiesPtr>
video_input_capabilities;
for (const auto& descriptor : device_descriptors) {
std::string hmac_device_id = GetHMACForMediaDeviceID(
device_id_salt_, security_origin, descriptor.device_id);
::mojom::VideoInputDeviceCapabilitiesPtr capabilities =
::mojom::VideoInputDeviceCapabilities::New();
capabilities->device_id = std::move(hmac_device_id);
capabilities->formats = GetVideoInputFormats(descriptor.device_id);
if (capabilities->formats.empty()) {
// The device does not seem to support capability enumeration. Compose
// a fallback list of capabilities.
for (const auto& resolution : kFallbackVideoResolutions) {
for (const auto frame_rate : kFallbackVideoFrameRates) {
capabilities->formats.push_back(media::VideoCaptureFormat(
gfx::Size(resolution.width, resolution.height), frame_rate,
media::PIXEL_FORMAT_I420));
}
}
}
capabilities->facing_mode = ToFacingMode(descriptor.facing);
#if defined(OS_ANDROID)
// On Android, the facing mode is not available in the |facing| field,
// but is available as part of the label.
// TODO(guidou): Remove this code once the |facing| field is supported
// on Android. See http://crbug.com/672856.
if (descriptor.GetNameAndModel().find("front") != std::string::npos)
capabilities->facing_mode = ::mojom::FacingMode::USER;
else if (descriptor.GetNameAndModel().find("back") != std::string::npos)
capabilities->facing_mode = ::mojom::FacingMode::ENVIRONMENT;
#endif
if (descriptor.device_id == default_device_id) {
video_input_capabilities.insert(video_input_capabilities.begin(),
std::move(capabilities));
} else {
video_input_capabilities.push_back(std::move(capabilities));
}
}
std::move(client_callback).Run(std::move(video_input_capabilities));
}
media::VideoCaptureFormats MediaDevicesDispatcherHost::GetVideoInputFormats(
const std::string& device_id) {
DCHECK_CURRENTLY_ON(BrowserThread::IO);
media::VideoCaptureFormats formats;
base::Optional<media::VideoCaptureFormat> format =
media_stream_manager_->video_capture_manager()->GetDeviceFormatInUse(
MEDIA_DEVICE_VIDEO_CAPTURE, device_id);
if (format.has_value()) {
formats.push_back(format.value());
return formats;
}
media_stream_manager_->video_capture_manager()->GetDeviceSupportedFormats(
device_id, &formats);
return formats;
}
} // namespace content