blob: d208508ed274522d66be26717b7a6915a3faa7bd [file] [log] [blame]
// Copyright (c) 2012 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_stream_device_settings.h"
#include <algorithm>
#include "base/bind.h"
#include "base/callback.h"
#include "base/logging.h"
#include "base/stl_util.h"
#include "content/browser/renderer_host/media/media_stream_settings_requester.h"
#include "content/browser/renderer_host/render_view_host_delegate.h"
#include "content/browser/renderer_host/render_view_host_impl.h"
#include "content/common/media/media_stream_options.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/content_browser_client.h"
#include "content/public/common/media_stream_request.h"
#include "googleurl/src/gurl.h"
using content::BrowserThread;
using content::MediaStreamDevice;
using content::MediaStreamRequest;
namespace {
// Helper class to handle the callbacks to a MediaStreamDeviceSettings instance.
// This class will make sure that the call to PostResponse is executed on the IO
// thread (and that the instance of MediaStreamDeviceSettings still exists).
// This allows us to pass a simple base::Callback object to any class that needs
// to post a response to the MediaStreamDeviceSettings object. This logic cannot
// be implemented inside MediaStreamDeviceSettings::PostResponse since that
// would imply that the WeakPtr<MediaStreamDeviceSettings> pointer has been
// dereferenced already (which would cause an error in the ThreadChecker before
// we even get there).
class ResponseCallbackHelper
: public base::RefCountedThreadSafe<ResponseCallbackHelper> {
public:
explicit ResponseCallbackHelper(
base::WeakPtr<media_stream::MediaStreamDeviceSettings> settings)
: settings_(settings) {
}
void PostResponse(const std::string& label,
const content::MediaStreamDevices& devices) {
if (!BrowserThread::CurrentlyOn(BrowserThread::IO)) {
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&media_stream::MediaStreamDeviceSettings::PostResponse,
settings_, label, devices));
return;
} else if (settings_) {
settings_->PostResponse(label, devices);
}
}
private:
friend class base::RefCountedThreadSafe<ResponseCallbackHelper>;
~ResponseCallbackHelper() {}
base::WeakPtr<media_stream::MediaStreamDeviceSettings> settings_;
DISALLOW_COPY_AND_ASSIGN(ResponseCallbackHelper);
};
// Predicate used in find_if below to find a device with a given ID.
class DeviceIdEquals {
public:
explicit DeviceIdEquals(const std::string& device_id)
: device_id_(device_id) {
}
bool operator() (const media_stream::StreamDeviceInfo& device) {
return (device.device_id == device_id_);
}
private:
std::string device_id_;
};
} // namespace
namespace media_stream {
typedef std::map<MediaStreamType, StreamDeviceInfoArray> DeviceMap;
// Device request contains all data needed to keep track of requests between the
// different calls.
struct MediaStreamDeviceSettingsRequest : public MediaStreamRequest {
public:
MediaStreamDeviceSettingsRequest(
int render_pid,
int render_vid,
const GURL& origin,
const StreamOptions& request_options)
: MediaStreamRequest(render_pid, render_vid, origin),
options(request_options),
posted_task(false) {}
~MediaStreamDeviceSettingsRequest() {}
// Request options.
StreamOptions options;
// Map containing available devices for the requested capture types.
// Note, never call devices_full[stream_type].empty() before making sure
// that type of device has existed on the map, otherwise it will create an
// empty device entry on the map.
DeviceMap devices_full;
// Whether or not a task was posted to make the call to
// RequestMediaAccessPermission, to make sure that we never post twice to it.
bool posted_task;
};
namespace {
// Sends the request to the appropriate WebContents.
void DoDeviceRequest(const MediaStreamDeviceSettingsRequest& request,
const content::MediaResponseCallback& callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
// Send the permission request to the web contents.
content::RenderViewHostImpl* host =
content::RenderViewHostImpl::FromID(request.render_process_id,
request.render_view_id);
// Tab may have gone away.
if (!host || !host->GetDelegate()) {
callback.Run(content::MediaStreamDevices());
return;
}
host->GetDelegate()->RequestMediaAccessPermission(&request, callback);
}
bool IsRequestReadyForView(
media_stream::MediaStreamDeviceSettingsRequest* request) {
if ((content::IsAudioMediaType(request->options.audio_type) &&
request->devices_full.count(request->options.audio_type) == 0) ||
(content::IsVideoMediaType(request->options.video_type) &&
request->devices_full.count(request->options.video_type) == 0)) {
return false;
}
// We have got all the requested devices, it is ready if it has not
// been posted for UI yet.
return !request->posted_task;
}
} // namespace
MediaStreamDeviceSettings::MediaStreamDeviceSettings(
SettingsRequester* requester)
: requester_(requester),
use_fake_ui_(false),
weak_ptr_factory_(this) {
DCHECK(requester_);
}
MediaStreamDeviceSettings::~MediaStreamDeviceSettings() {
STLDeleteValues(&requests_);
}
void MediaStreamDeviceSettings::RequestCaptureDeviceUsage(
const std::string& label, int render_process_id, int render_view_id,
const StreamOptions& request_options, const GURL& security_origin) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
// Create a new request.
if (!requests_.insert(std::make_pair(label,
new MediaStreamDeviceSettingsRequest(
render_process_id,
render_view_id,
security_origin,
request_options))).second) {
NOTREACHED();
}
}
void MediaStreamDeviceSettings::RemovePendingCaptureRequest(
const std::string& label) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
SettingsRequests::iterator request_it = requests_.find(label);
if (request_it != requests_.end()) {
// Proceed the next pending request for the same page.
MediaStreamDeviceSettingsRequest* request = request_it->second;
int render_view_id = request->render_view_id;
int render_process_id = request->render_process_id;
bool was_posted = request->posted_task;
// TODO(xians): Post a cancel request on UI thread to dismiss the infobar
// if request has been sent to the UI.
// Remove the request from the queue.
requests_.erase(request_it);
delete request;
// Simply return if the canceled request has not been brought to UI.
if (!was_posted)
return;
// Process the next pending request to replace the old infobar on the same
// page.
ProcessNextRequestForView(render_view_id, render_process_id);
}
}
void MediaStreamDeviceSettings::AvailableDevices(
const std::string& label,
MediaStreamType stream_type,
const StreamDeviceInfoArray& devices) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
SettingsRequests::iterator request_it = requests_.find(label);
if (request_it == requests_.end()) {
NOTREACHED();
return;
}
// Add the answer for the request.
MediaStreamDeviceSettingsRequest* request = request_it->second;
DCHECK_EQ(request->devices_full.count(stream_type), static_cast<size_t>(0))
<< "This request already has a list of devices for this stream type.";
request->devices_full[stream_type] = devices;
if (IsRequestReadyForView(request)) {
if (use_fake_ui_) {
PostRequestToFakeUI(label);
return;
}
if (IsUIBusy(request->render_view_id, request->render_process_id)) {
// The UI can handle only one request at the time, do not post the
// request to the view if the UI is handling any other request.
return;
}
PostRequestToUI(label);
}
}
void MediaStreamDeviceSettings::PostResponse(
const std::string& label,
const content::MediaStreamDevices& devices) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
SettingsRequests::iterator req = requests_.find(label);
// Return if the request has been removed.
if (req == requests_.end())
return;
DCHECK(requester_);
scoped_ptr<MediaStreamDeviceSettingsRequest> request(req->second);
requests_.erase(req);
// Look for queued requests for the same view. If there is a pending request,
// post it for user approval.
ProcessNextRequestForView(request->render_view_id,
request->render_process_id);
if (devices.size() > 0) {
// Build a list of "full" device objects for the accepted devices.
StreamDeviceInfoArray deviceList;
for (content::MediaStreamDevices::const_iterator dev = devices.begin();
dev != devices.end(); ++dev) {
DeviceMap::iterator subList = request->devices_full.find(dev->type);
DCHECK(subList != request->devices_full.end());
deviceList.push_back(*std::find_if(subList->second.begin(),
subList->second.end(), DeviceIdEquals(dev->device_id)));
}
requester_->DevicesAccepted(label, deviceList);
} else {
requester_->SettingsError(label);
}
}
void MediaStreamDeviceSettings::UseFakeUI() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
use_fake_ui_ = true;
}
bool MediaStreamDeviceSettings::IsUIBusy(int render_view_id,
int render_process_id) {
for (SettingsRequests::iterator it = requests_.begin();
it != requests_.end(); ++it) {
if (it->second->render_process_id == render_process_id &&
it->second->render_view_id == render_view_id &&
it->second->posted_task) {
return true;
}
}
return false;
}
void MediaStreamDeviceSettings::ProcessNextRequestForView(
int render_view_id, int render_process_id) {
std::string new_label;
for (SettingsRequests::iterator it = requests_.begin(); it != requests_.end();
++it) {
if (it->second->render_process_id == render_process_id &&
it->second->render_view_id == render_view_id) {
// This request belongs to the given render view.
MediaStreamDeviceSettingsRequest* request = it->second;
if (IsRequestReadyForView(request)) {
new_label = it->first;
break;
}
}
}
if (new_label.empty())
return;
if (use_fake_ui_)
PostRequestToFakeUI(new_label);
else
PostRequestToUI(new_label);
}
void MediaStreamDeviceSettings::PostRequestToUI(const std::string& label) {
SettingsRequests::iterator request_it = requests_.find(label);
if (request_it == requests_.end()) {
NOTREACHED();
return;
}
MediaStreamDeviceSettingsRequest* request = request_it->second;
DCHECK(request != NULL);
request->posted_task = true;
// Create the simplified list of devices.
for (DeviceMap::iterator it = request->devices_full.begin();
it != request->devices_full.end(); ++it) {
request->devices[it->first].clear();
for (StreamDeviceInfoArray::iterator device = it->second.begin();
device != it->second.end(); ++device) {
request->devices[it->first].push_back(MediaStreamDevice(
it->first, device->device_id, device->name));
}
}
scoped_refptr<ResponseCallbackHelper> helper =
new ResponseCallbackHelper(weak_ptr_factory_.GetWeakPtr());
content::MediaResponseCallback callback =
base::Bind(&ResponseCallbackHelper::PostResponse,
helper.get(), label);
BrowserThread::PostTask(
BrowserThread::UI, FROM_HERE,
base::Bind(&DoDeviceRequest, *request, callback));
}
void MediaStreamDeviceSettings::PostRequestToFakeUI(const std::string& label) {
SettingsRequests::iterator request_it = requests_.find(label);
DCHECK(request_it != requests_.end());
MediaStreamDeviceSettingsRequest* request = request_it->second;
// Used to fake UI, which is needed for server based testing.
// Choose first non-opened device for each media type.
content::MediaStreamDevices devices_to_use;
for (DeviceMap::iterator it = request->devices_full.begin();
it != request->devices_full.end(); ++it) {
StreamDeviceInfoArray::iterator device_it = it->second.begin();
for (; device_it != it->second.end(); ++device_it) {
if (!device_it->in_use) {
devices_to_use.push_back(content::MediaStreamDevice(
device_it->stream_type, device_it->device_id, device_it->name));
break;
}
}
if (it->second.size() != 0 && device_it == it->second.end()) {
// Use the first capture device in the list if all the devices are
// being used.
devices_to_use.push_back(
content::MediaStreamDevice(it->second.begin()->stream_type,
it->second.begin()->device_id,
it->second.begin()->name));
}
}
BrowserThread::PostTask(
BrowserThread::IO, FROM_HERE,
base::Bind(&MediaStreamDeviceSettings::PostResponse,
weak_ptr_factory_.GetWeakPtr(), label, devices_to_use));
}
} // namespace media_stream