| // 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 |