diff options
author | vrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 04:06:32 +0000 |
---|---|---|
committer | vrk@chromium.org <vrk@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2013-07-17 04:06:32 +0000 |
commit | c129874dc579801d403c521fe0ebe38fbc1cf2f2 (patch) | |
tree | 13000674cd15260abb8f2a9f5310c91fcaa91a85 | |
parent | 9898e98903866d064ff4a30d72baa0222f6556e2 (diff) | |
download | chromium_src-c129874dc579801d403c521fe0ebe38fbc1cf2f2.zip chromium_src-c129874dc579801d403c521fe0ebe38fbc1cf2f2.tar.gz chromium_src-c129874dc579801d403c521fe0ebe38fbc1cf2f2.tar.bz2 |
Implement Chromium backend for getSourceInfos()
BUG=240710,https://code.google.com/p/webrtc/issues/detail?id=1571
Review URL: https://chromiumcodereview.appspot.com/16806002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@211922 0039d316-1c4b-4281-b951-d872f2087c98
26 files changed, 942 insertions, 114 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index 16d2d0d..87e5814 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -6780,6 +6780,12 @@ Keep your key file in a safe place. You will need it to create new versions of y <message name="IDS_FLAGS_ENABLE_SCTP_DATA_CHANNELS_DESCRIPTION" desc="Description of about:flags option to turn on SCTP data channels support"> When enabled, DataChannels created by WebRTC will use the SCTP wire protocol. </message> + <message name="IDS_FLAGS_ENABLE_DEVICE_ENUMERATION_NAME" desc="Name of about:flags option to turn on WebRTC device enumeration support."> + Enable WebRTC device enumeration. + </message> + <message name="IDS_FLAGS_ENABLE_DEVICE_ENUMERATION_DESCRIPTION" desc="Description of chrome:flags option to turn on WebRTC device enumeration support"> + When enabled, MediaStreamTrack can list the available video and audio input devices, and getUserMedia can open these input devices by referencing an opaque id. + </message> <message name="IDS_FLAGS_DISABLE_WEBAUDIO_NAME" desc="Name of the 'Disable WebAudio' lab."> Disable WebAudio </message> diff --git a/chrome/browser/about_flags.cc b/chrome/browser/about_flags.cc index 63db52c..fd0c851 100644 --- a/chrome/browser/about_flags.cc +++ b/chrome/browser/about_flags.cc @@ -464,6 +464,13 @@ const Experiment kExperiments[] = { kOsAll, SINGLE_VALUE_TYPE(switches::kEnableSCTPDataChannels) }, + { + "enable-device-enumeration", + IDS_FLAGS_ENABLE_DEVICE_ENUMERATION_NAME, + IDS_FLAGS_ENABLE_DEVICE_ENUMERATION_DESCRIPTION, + kOsAll, + SINGLE_VALUE_TYPE(switches::kEnableDeviceEnumeration) + }, #endif #if defined(OS_ANDROID) { diff --git a/chrome/browser/media/media_capture_devices_dispatcher.cc b/chrome/browser/media/media_capture_devices_dispatcher.cc index 340ef6b..6d49f98 100644 --- a/chrome/browser/media/media_capture_devices_dispatcher.cc +++ b/chrome/browser/media/media_capture_devices_dispatcher.cc @@ -40,20 +40,17 @@ using content::MediaStreamDevices; namespace { -const content::MediaStreamDevice* FindDefaultDeviceWithId( +// Finds a device in |devices| that has |device_id|, or NULL if not found. +const content::MediaStreamDevice* FindDeviceWithId( const content::MediaStreamDevices& devices, const std::string& device_id) { - if (devices.empty()) - return NULL; - content::MediaStreamDevices::const_iterator iter = devices.begin(); for (; iter != devices.end(); ++iter) { if (iter->id == device_id) { return &(*iter); } } - - return &(*devices.begin()); + return NULL; }; // This is a short-term solution to allow testing of the the Screen Capture API @@ -242,7 +239,6 @@ void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequestFromExtension( content::MediaStreamDevices devices; Profile* profile = Profile::FromBrowserContext(web_contents->GetBrowserContext()); - PrefService* prefs = profile->GetPrefs(); #if defined(OS_ANDROID) // Tab capture is not supported on Android. @@ -264,9 +260,7 @@ void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequestFromExtension( } else if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE && extension->HasAPIPermission( extensions::APIPermission::kAudioCapture)) { - std::string default_device = - prefs->GetString(prefs::kDefaultAudioCaptureDevice); - GetRequestedDevice(default_device, true, false, &devices); + GetDefaultDevicesForProfile(profile, true, false, &devices); } if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE && @@ -277,9 +271,7 @@ void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequestFromExtension( content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string())); } else if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE && extension->HasAPIPermission(extensions::APIPermission::kVideoCapture)) { - std::string default_device = prefs->GetString( - prefs::kDefaultVideoCaptureDevice); - GetRequestedDevice(default_device, false, true, &devices); + GetDefaultDevicesForProfile(profile, false, true, &devices); } scoped_ptr<content::MediaStreamUI> ui; @@ -369,37 +361,61 @@ void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile( std::string default_device; if (audio) { default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice); - GetRequestedDevice(default_device, true, false, devices); + const content::MediaStreamDevice* device = + GetRequestedAudioDevice(default_device); + if (!device) + device = GetFirstAvailableAudioDevice(); + if (device) + devices->push_back(*device); } if (video) { default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice); - GetRequestedDevice(default_device, false, true, devices); + const content::MediaStreamDevice* device = + GetRequestedVideoDevice(default_device); + if (!device) + device = GetFirstAvailableVideoDevice(); + if (device) + devices->push_back(*device); } } -void MediaCaptureDevicesDispatcher::GetRequestedDevice( - const std::string& requested_device_id, - bool audio, - bool video, - content::MediaStreamDevices* devices) { +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetRequestedAudioDevice( + const std::string& requested_audio_device_id) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(audio || video); + const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); + const content::MediaStreamDevice* const device = + FindDeviceWithId(audio_devices, requested_audio_device_id); + return device; +} - if (audio) { - const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); - const content::MediaStreamDevice* const device = - FindDefaultDeviceWithId(audio_devices, requested_device_id); - if (device) - devices->push_back(*device); - } - if (video) { - const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); - const content::MediaStreamDevice* const device = - FindDefaultDeviceWithId(video_devices, requested_device_id); - if (device) - devices->push_back(*device); - } +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); + if (audio_devices.empty()) + return NULL; + return &(*audio_devices.begin()); +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetRequestedVideoDevice( + const std::string& requested_video_device_id) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); + const content::MediaStreamDevice* const device = + FindDeviceWithId(video_devices, requested_video_device_id); + return device; +} + +const content::MediaStreamDevice* +MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); + if (video_devices.empty()) + return NULL; + return &(*video_devices.begin()); } void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() { diff --git a/chrome/browser/media/media_capture_devices_dispatcher.h b/chrome/browser/media/media_capture_devices_dispatcher.h index 801ab72..6054a30f 100644 --- a/chrome/browser/media/media_capture_devices_dispatcher.h +++ b/chrome/browser/media/media_capture_devices_dispatcher.h @@ -79,8 +79,9 @@ class MediaCaptureDevicesDispatcher : public content::MediaObserver, const content::MediaResponseCallback& callback, const extensions::Extension* extension); - // Helper to get the default devices which can be used by the media request, - // if the return list is empty, it means there is no available device on the + // Helper to get the default devices which can be used by the media request. + // Uses the first available devices if the default devices are not available. + // If the return list is empty, it means there is no available device on the // OS. // Called on the UI thread. void GetDefaultDevicesForProfile(Profile* profile, @@ -88,14 +89,17 @@ class MediaCaptureDevicesDispatcher : public content::MediaObserver, bool video, content::MediaStreamDevices* devices); - // Helper for picking the device that was requested for an OpenDevice request. - // If the device requested is not available it will revert to using the first - // available one instead or will return an empty list if no devices of the - // requested kind are present. - void GetRequestedDevice(const std::string& requested_device_id, - bool audio, - bool video, - content::MediaStreamDevices* devices); + // Helpers for picking particular requested devices, identified by raw id. + // If the device requested is not available it will return NULL. + const content::MediaStreamDevice* + GetRequestedAudioDevice(const std::string& requested_audio_device_id); + const content::MediaStreamDevice* + GetRequestedVideoDevice(const std::string& requested_video_device_id); + + // Returns the first available audio or video device, or NULL if no devices + // are available. + const content::MediaStreamDevice* GetFirstAvailableAudioDevice(); + const content::MediaStreamDevice* GetFirstAvailableVideoDevice(); // Unittests that do not require actual device enumeration should call this // API on the singleton. It is safe to call this multiple times on the diff --git a/chrome/browser/media/media_stream_devices_controller.cc b/chrome/browser/media/media_stream_devices_controller.cc index e7f932a..04c5cf6 100644 --- a/chrome/browser/media/media_stream_devices_controller.cc +++ b/chrome/browser/media/media_stream_devices_controller.cc @@ -166,18 +166,66 @@ void MediaStreamDevicesController::Accept(bool update_content_setting) { content::MediaStreamDevices devices; if (microphone_requested_ || webcam_requested_) { switch (request_.request_type) { - case content::MEDIA_OPEN_DEVICE: + case content::MEDIA_OPEN_DEVICE: { + const content::MediaStreamDevice* device = NULL; // For open device request pick the desired device or fall back to the // first available of the given type. - MediaCaptureDevicesDispatcher::GetInstance()->GetRequestedDevice( - request_.requested_device_id, - request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE, - request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE, - &devices); + if (request_.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE) { + device = MediaCaptureDevicesDispatcher::GetInstance()-> + GetRequestedAudioDevice(request_.requested_audio_device_id); + // TODO(wjia): Confirm this is the intended behavior. + if (!device) { + device = MediaCaptureDevicesDispatcher::GetInstance()-> + GetFirstAvailableAudioDevice(); + } + } else if (request_.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE) { + // Pepper API opens only one device at a time. + device = MediaCaptureDevicesDispatcher::GetInstance()-> + GetRequestedVideoDevice(request_.requested_video_device_id); + // TODO(wjia): Confirm this is the intended behavior. + if (!device) { + device = MediaCaptureDevicesDispatcher::GetInstance()-> + GetFirstAvailableVideoDevice(); + } + } + if (device) + devices.push_back(*device); break; - case content::MEDIA_DEVICE_ACCESS: - case content::MEDIA_GENERATE_STREAM: - case content::MEDIA_ENUMERATE_DEVICES: + } case content::MEDIA_GENERATE_STREAM: { + bool needs_audio_device = microphone_requested_; + bool needs_video_device = webcam_requested_; + + // Get the exact audio or video device if an id is specified. + if (!request_.requested_audio_device_id.empty()) { + const content::MediaStreamDevice* audio_device = + MediaCaptureDevicesDispatcher::GetInstance()-> + GetRequestedAudioDevice(request_.requested_audio_device_id); + if (audio_device) { + devices.push_back(*audio_device); + needs_audio_device = false; + } + } + if (!request_.requested_video_device_id.empty()) { + const content::MediaStreamDevice* video_device = + MediaCaptureDevicesDispatcher::GetInstance()-> + GetRequestedVideoDevice(request_.requested_video_device_id); + if (video_device) { + devices.push_back(*video_device); + needs_video_device = false; + } + } + + // If either or both audio and video devices were requested but not + // specified by id, get the default devices. + if (needs_audio_device || needs_video_device) { + MediaCaptureDevicesDispatcher::GetInstance()-> + GetDefaultDevicesForProfile(profile_, + needs_audio_device, + needs_video_device, + &devices); + } + break; + } case content::MEDIA_DEVICE_ACCESS: // Get the default devices for the request. MediaCaptureDevicesDispatcher::GetInstance()-> GetDefaultDevicesForProfile(profile_, @@ -185,6 +233,10 @@ void MediaStreamDevicesController::Accept(bool update_content_setting) { webcam_requested_, &devices); break; + case content::MEDIA_ENUMERATE_DEVICES: + // Do nothing. + NOTREACHED(); + break; } // TODO(raymes): We currently set the content permission for non-https diff --git a/chrome/browser/policy/policy_browsertest.cc b/chrome/browser/policy/policy_browsertest.cc index 2530479..87f9f7e 100644 --- a/chrome/browser/policy/policy_browsertest.cc +++ b/chrome/browser/policy/policy_browsertest.cc @@ -2296,9 +2296,10 @@ class MediaStreamDevicesControllerBrowserTest } void FinishAudioTest() { - content::MediaStreamRequest request(0, 0, 0, request_url_.GetOrigin(), + content::MediaStreamRequest request(0, 0, 0, std::string(), + request_url_.GetOrigin(), content::MEDIA_DEVICE_ACCESS, - "fake_dev", + std::string(), std::string(), content::MEDIA_DEVICE_AUDIO_CAPTURE, content::MEDIA_NO_SERVICE); // TODO(raymes): Test MEDIA_DEVICE_OPEN (Pepper) which grants both webcam @@ -2314,9 +2315,11 @@ class MediaStreamDevicesControllerBrowserTest void FinishVideoTest() { // TODO(raymes): Test MEDIA_DEVICE_OPEN (Pepper) which grants both webcam // and microphone permissions at the same time. - content::MediaStreamRequest request(0, 0, 0, request_url_.GetOrigin(), + content::MediaStreamRequest request(0, 0, 0, std::string(), + request_url_.GetOrigin(), content::MEDIA_DEVICE_ACCESS, - "fake_dev", + std::string(), + std::string(), content::MEDIA_NO_SERVICE, content::MEDIA_DEVICE_VIDEO_CAPTURE); MediaStreamDevicesController controller( diff --git a/content/browser/renderer_host/media/device_request_message_filter.cc b/content/browser/renderer_host/media/device_request_message_filter.cc new file mode 100644 index 0000000..bcd9dcf --- /dev/null +++ b/content/browser/renderer_host/media/device_request_message_filter.cc @@ -0,0 +1,195 @@ +// Copyright 2013 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/device_request_message_filter.h" + +#include "base/strings/string_number_conversions.h" +#include "base/strings/string_util.h" +#include "content/browser/browser_main_loop.h" +#include "content/browser/renderer_host/media/media_stream_manager.h" +#include "content/common/media/media_stream_messages.h" +#include "crypto/hmac.h" + +namespace content { + +DeviceRequestMessageFilter::DeviceRequestMessageFilter( + MediaStreamManager* media_stream_manager) + : media_stream_manager_(media_stream_manager) { + DCHECK(media_stream_manager); +} + +DeviceRequestMessageFilter::~DeviceRequestMessageFilter() { + DCHECK(requests_.empty()); +} + +struct DeviceRequestMessageFilter::DeviceRequest { + DeviceRequest(int request_id, + const GURL& origin, + const std::string& audio_devices_label, + const std::string& video_devices_label) + : request_id(request_id), + origin(origin), + has_audio_returned(false), + has_video_returned(false), + audio_devices_label(audio_devices_label), + video_devices_label(video_devices_label) {} + + int request_id; + GURL origin; + bool has_audio_returned; + bool has_video_returned; + std::string audio_devices_label; + std::string video_devices_label; + StreamDeviceInfoArray audio_devices; + StreamDeviceInfoArray video_devices; +}; + +void DeviceRequestMessageFilter::StreamGenerated( + const std::string& label, + const StreamDeviceInfoArray& audio_devices, + const StreamDeviceInfoArray& video_devices) { + NOTIMPLEMENTED(); +} + +void DeviceRequestMessageFilter::StreamGenerationFailed( + const std::string& label) { + NOTIMPLEMENTED(); +} + +void DeviceRequestMessageFilter::DevicesEnumerated( + const std::string& label, + const StreamDeviceInfoArray& new_devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + // Look up the DeviceRequest by id. + DeviceRequestList::iterator request_it = requests_.begin(); + for (; request_it != requests_.end(); ++request_it) { + if (label == request_it->audio_devices_label || + label == request_it->video_devices_label) { + break; + } + } + DCHECK(request_it != requests_.end()); + + StreamDeviceInfoArray* audio_devices = &request_it->audio_devices; + StreamDeviceInfoArray* video_devices = &request_it->video_devices; + + // Store hmac'd device ids instead of raw device ids. + if (label == request_it->audio_devices_label) { + request_it->has_audio_returned = true; + DCHECK(audio_devices->empty()); + HmacDeviceIds(request_it->origin, new_devices, audio_devices); + } else { + DCHECK(label == request_it->video_devices_label); + request_it->has_video_returned = true; + DCHECK(video_devices->empty()); + HmacDeviceIds(request_it->origin, new_devices, video_devices); + } + + if (!request_it->has_audio_returned || !request_it->has_video_returned) { + // Wait for the rest of the devices to complete. + return; + } + + // Both audio and video devices are ready. + StreamDeviceInfoArray all_devices = *audio_devices; + all_devices.insert( + all_devices.end(), video_devices->begin(), video_devices->end()); + + Send(new MediaStreamMsg_GetSourcesACK(request_it->request_id, all_devices)); + + // TODO(vrk): Rename StopGeneratedStream() to CancelDeviceRequest(). + media_stream_manager_->StopGeneratedStream(request_it->audio_devices_label); + media_stream_manager_->StopGeneratedStream(request_it->video_devices_label); + requests_.erase(request_it); +} + +void DeviceRequestMessageFilter::DeviceOpened( + const std::string& label, + const StreamDeviceInfo& video_device) { + NOTIMPLEMENTED(); +} + +bool DeviceRequestMessageFilter::OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP_EX(DeviceRequestMessageFilter, message, *message_was_ok) + IPC_MESSAGE_HANDLER(MediaStreamHostMsg_GetSources, OnGetSources) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP_EX() + return handled; +} + +void DeviceRequestMessageFilter::OnChannelClosing() { + BrowserMessageFilter::OnChannelClosing(); + + // Since the IPC channel is gone, cancel outstanding device requests. + for (DeviceRequestList::iterator it = requests_.begin(); + it != requests_.end(); + ++it) { + // TODO(vrk): Rename StopGeneratedStream() to CancelDeviceRequest(). + media_stream_manager_->StopGeneratedStream(it->audio_devices_label); + media_stream_manager_->StopGeneratedStream(it->video_devices_label); + } + requests_.clear(); +} + +void DeviceRequestMessageFilter::HmacDeviceIds( + const GURL& origin, + const StreamDeviceInfoArray& raw_devices, + StreamDeviceInfoArray* devices_with_guids) { + DCHECK(devices_with_guids); + + // Replace raw ids with hmac'd ids before returning to renderer process. + for (StreamDeviceInfoArray::const_iterator device_itr = raw_devices.begin(); + device_itr != raw_devices.end(); + ++device_itr) { + crypto::HMAC hmac(crypto::HMAC::SHA256); + const size_t digest_length = hmac.DigestLength(); + std::vector<uint8> digest(digest_length); + bool result = hmac.Init(origin.spec()) && + hmac.Sign(device_itr->device.id, &digest[0], digest.size()); + DCHECK(result); + if (result) { + StreamDeviceInfo current_device_info = *device_itr; + current_device_info.device.id = + StringToLowerASCII(base::HexEncode(&digest[0], digest.size())); + devices_with_guids->push_back(current_device_info); + } + } +} + +bool DeviceRequestMessageFilter::DoesRawIdMatchGuid( + const GURL& security_origin, + const std::string& device_guid, + const std::string& raw_device_id) { + crypto::HMAC hmac(crypto::HMAC::SHA256); + bool result = hmac.Init(security_origin.spec()); + DCHECK(result); + std::vector<uint8> converted_guid; + base::HexStringToBytes(device_guid, &converted_guid); + return hmac.Verify( + raw_device_id, + base::StringPiece(reinterpret_cast<const char*>(&converted_guid[0]), + converted_guid.size())); +} + +void DeviceRequestMessageFilter::OnGetSources(int request_id, + const GURL& security_origin) { + // Make request to get audio devices. + const std::string& audio_label = media_stream_manager_->EnumerateDevices( + this, -1, -1, -1, MEDIA_DEVICE_AUDIO_CAPTURE, security_origin); + DCHECK(!audio_label.empty()); + + // Make request for video devices. + const std::string& video_label = media_stream_manager_->EnumerateDevices( + this, -1, -1, -1, MEDIA_DEVICE_VIDEO_CAPTURE, security_origin); + DCHECK(!video_label.empty()); + + requests_.push_back(DeviceRequest( + request_id, security_origin, audio_label, video_label)); +} + +} // namespace content diff --git a/content/browser/renderer_host/media/device_request_message_filter.h b/content/browser/renderer_host/media/device_request_message_filter.h new file mode 100644 index 0000000..4e42186 --- /dev/null +++ b/content/browser/renderer_host/media/device_request_message_filter.h @@ -0,0 +1,73 @@ +// Copyright 2013 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. + +#ifndef CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_ +#define CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_ + +#include <map> +#include <string> + +#include "base/synchronization/lock.h" +#include "content/browser/renderer_host/media/media_stream_requester.h" +#include "content/common/content_export.h" +#include "content/public/browser/browser_message_filter.h" + +namespace content { + +class MediaStreamManager; + +// DeviceRequestMessageFilter used to delegate requests from the +// MediaStreamCenter. +class CONTENT_EXPORT DeviceRequestMessageFilter : public BrowserMessageFilter, + public MediaStreamRequester { + public: + explicit DeviceRequestMessageFilter(MediaStreamManager* media_stream_manager); + + // MediaStreamRequester implementation. + // TODO(vrk): Replace MediaStreamRequester interface with a single callback so + // we don't have to override all these callbacks we don't care about. + // (crbug.com/249476) + virtual void StreamGenerated( + const std::string& label, const StreamDeviceInfoArray& audio_devices, + const StreamDeviceInfoArray& video_devices) OVERRIDE; + virtual void StreamGenerationFailed(const std::string& label) OVERRIDE; + virtual void DeviceOpened(const std::string& label, + const StreamDeviceInfo& video_device) OVERRIDE; + // DevicesEnumerated() is the only callback we're interested in. + virtual void DevicesEnumerated(const std::string& label, + const StreamDeviceInfoArray& devices) OVERRIDE; + + // BrowserMessageFilter implementation. + virtual bool OnMessageReceived(const IPC::Message& message, + bool* message_was_ok) OVERRIDE; + virtual void OnChannelClosing() OVERRIDE; + + // Helper method that checks whether the GUID generated by + // DeviceRequestMessageFilter matches the given |raw_device_id|. + static bool DoesRawIdMatchGuid(const GURL& security_origin, + const std::string& device_guid, + const std::string& raw_device_id); + + protected: + virtual ~DeviceRequestMessageFilter(); + + private: + void OnGetSources(int request_id, const GURL& security_origin); + void HmacDeviceIds(const GURL& origin, + const StreamDeviceInfoArray& raw_devices, + StreamDeviceInfoArray* devices_with_guids); + + MediaStreamManager* media_stream_manager_; + + struct DeviceRequest; + typedef std::vector<DeviceRequest> DeviceRequestList; + // List of all requests. + DeviceRequestList requests_; + + DISALLOW_COPY_AND_ASSIGN(DeviceRequestMessageFilter); +}; + +} // namespace content + +#endif // CONTENT_BROWSER_RENDERER_HOST_MEDIA_MEDIA_STREAM_CENTER_HOST_H_ diff --git a/content/browser/renderer_host/media/device_request_message_filter_unittest.cc b/content/browser/renderer_host/media/device_request_message_filter_unittest.cc new file mode 100644 index 0000000..9043027 --- /dev/null +++ b/content/browser/renderer_host/media/device_request_message_filter_unittest.cc @@ -0,0 +1,258 @@ +// Copyright 2013 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 "base/strings/string_number_conversions.h" +#include "content/browser/renderer_host/media/device_request_message_filter.h" +#include "content/browser/renderer_host/media/media_stream_manager.h" +#include "content/common/media/media_stream_messages.h" +#include "content/public/test/test_browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Invoke; + +namespace content { + +static const std::string kAudioLabel = "audio_label"; +static const std::string kVideoLabel = "video_label"; + +class MockMediaStreamManager : public MediaStreamManager { + public: + MockMediaStreamManager() {} + + virtual ~MockMediaStreamManager() {} + + MOCK_METHOD6(EnumerateDevices, + std::string(MediaStreamRequester* requester, + int render_process_id, + int render_view_id, + int page_request_id, + MediaStreamType type, + const GURL& security_origin)); + MOCK_METHOD1(StopGeneratedStream, void(const std::string& label)); + + std::string DoEnumerateDevices(MediaStreamRequester* requester, + int render_process_id, + int render_view_id, + int page_request_id, + MediaStreamType type, + const GURL& security_origin) { + if (type == MEDIA_DEVICE_AUDIO_CAPTURE) { + return kAudioLabel; + } else { + return kVideoLabel; + } + } +}; + +class MockDeviceRequestMessageFilter : public DeviceRequestMessageFilter { + public: + MockDeviceRequestMessageFilter(MockMediaStreamManager* manager) + : DeviceRequestMessageFilter(manager), received_id_(-1) {} + StreamDeviceInfoArray requested_devices() { return requested_devices_; } + int received_id() { return received_id_; } + + private: + virtual ~MockDeviceRequestMessageFilter() {} + + // Override the Send() method to intercept the message that we're sending to + // the renderer. + virtual bool Send(IPC::Message* reply_msg) OVERRIDE { + CHECK(reply_msg); + + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MockDeviceRequestMessageFilter, *reply_msg) + IPC_MESSAGE_HANDLER(MediaStreamMsg_GetSourcesACK, SaveDevices) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + EXPECT_TRUE(handled); + + delete reply_msg; + return true; + } + + void SaveDevices(int request_id, const StreamDeviceInfoArray& devices) { + received_id_ = request_id; + requested_devices_ = devices; + } + + int received_id_; + StreamDeviceInfoArray requested_devices_; +}; + +class DeviceRequestMessageFilterTest : public testing::Test { + public: + DeviceRequestMessageFilterTest() : next_device_id_(0) {} + + void RunTest(int number_audio_devices, int number_video_devices) { + AddAudioDevices(number_audio_devices); + AddVideoDevices(number_video_devices); + GURL origin("https://test.com"); + EXPECT_CALL(*media_stream_manager_, + EnumerateDevices(_, _, _, _, MEDIA_DEVICE_AUDIO_CAPTURE, _)) + .Times(1); + EXPECT_CALL(*media_stream_manager_, + EnumerateDevices(_, _, _, _, MEDIA_DEVICE_VIDEO_CAPTURE, _)) + .Times(1); + // Send message to get devices. Should trigger 2 EnumerateDevice() requests. + const int kRequestId = 123; + SendGetSourcesMessage(kRequestId, origin); + + // Run audio callback. Because there's still an outstanding video request, + // this should not populate |message|. + FireAudioDeviceCallback(); + EXPECT_EQ(0u, host_->requested_devices().size()); + + // After the video device callback is fired, |message| should be populated. + EXPECT_CALL(*media_stream_manager_, StopGeneratedStream(kAudioLabel)) + .Times(1); + EXPECT_CALL(*media_stream_manager_, StopGeneratedStream(kVideoLabel)) + .Times(1); + FireVideoDeviceCallback(); + EXPECT_EQ(static_cast<size_t>(number_audio_devices + number_video_devices), + host_->requested_devices().size()); + + EXPECT_EQ(kRequestId, host_->received_id()); + // Check to make sure no devices have raw ids. + EXPECT_FALSE(DoesContainRawIds(host_->requested_devices())); + + // Check to make sure every GUID produced matches a raw device id. + EXPECT_TRUE(DoesEveryDeviceMapToRawId(host_->requested_devices(), origin)); + } + + protected: + virtual ~DeviceRequestMessageFilterTest() {} + + virtual void SetUp() OVERRIDE { + message_loop_.reset(new base::MessageLoop(base::MessageLoop::TYPE_IO)); + io_thread_.reset( + new TestBrowserThread(BrowserThread::IO, message_loop_.get())); + + media_stream_manager_.reset(new MockMediaStreamManager()); + ON_CALL(*media_stream_manager_, EnumerateDevices(_, _, _, _, _, _)) + .WillByDefault(Invoke(media_stream_manager_.get(), + &MockMediaStreamManager::DoEnumerateDevices)); + + host_ = new MockDeviceRequestMessageFilter(media_stream_manager_.get()); + } + + scoped_refptr<MockDeviceRequestMessageFilter> host_; + scoped_ptr<MockMediaStreamManager> media_stream_manager_; + StreamDeviceInfoArray physical_audio_devices_; + StreamDeviceInfoArray physical_video_devices_; + scoped_ptr<base::MessageLoop> message_loop_; + scoped_ptr<TestBrowserThread> io_thread_; + + private: + void AddAudioDevices(int number_of_devices) { + for (int i = 0; i < number_of_devices; i++) { + physical_audio_devices_.push_back( + StreamDeviceInfo(MEDIA_DEVICE_AUDIO_CAPTURE, + "/dev/audio/" + base::IntToString(next_device_id_), + "Audio Device" + base::IntToString(next_device_id_), + false)); + next_device_id_++; + } + } + + void AddVideoDevices(int number_of_devices) { + for (int i = 0; i < number_of_devices; i++) { + physical_video_devices_.push_back( + StreamDeviceInfo(MEDIA_DEVICE_VIDEO_CAPTURE, + "/dev/video/" + base::IntToString(next_device_id_), + "Video Device" + base::IntToString(next_device_id_), + false)); + next_device_id_++; + } + } + + void SendGetSourcesMessage(int request_id, const GURL& origin) { + // Since we're not actually sending IPC messages, this is a throw-away + // value. + bool message_was_ok; + host_->OnMessageReceived(MediaStreamHostMsg_GetSources(request_id, origin), + &message_was_ok); + } + + void FireAudioDeviceCallback() { + host_->DevicesEnumerated(kAudioLabel, physical_audio_devices_); + } + + void FireVideoDeviceCallback() { + host_->DevicesEnumerated(kVideoLabel, physical_video_devices_); + } + + bool DoesContainRawIds(const StreamDeviceInfoArray& devices) { + for (size_t i = 0; i < devices.size(); i++) { + for (size_t j = 0; j < physical_audio_devices_.size(); ++j) { + if (physical_audio_devices_[j].device.id == devices[i].device.id) + return true; + } + for (size_t j = 0; j < physical_video_devices_.size(); ++j) { + if (physical_video_devices_[j].device.id == devices[i].device.id) + return true; + } + } + return false; + } + + bool DoesEveryDeviceMapToRawId(const StreamDeviceInfoArray& devices, + const GURL& origin) { + for (size_t i = 0; i < devices.size(); i++) { + bool found_match = false; + for (size_t j = 0; j < physical_audio_devices_.size(); ++j) { + if (DeviceRequestMessageFilter::DoesRawIdMatchGuid( + origin, + devices[i].device.id, + physical_audio_devices_[j].device.id)) { + EXPECT_FALSE(found_match); + found_match = true; + } + } + for (size_t j = 0; j < physical_video_devices_.size(); ++j) { + if (DeviceRequestMessageFilter::DoesRawIdMatchGuid( + origin, + devices[i].device.id, + physical_video_devices_[j].device.id)) { + EXPECT_FALSE(found_match); + found_match = true; + } + } + if (!found_match) + return false; + } + return true; + } + + int next_device_id_; +}; + +TEST_F(DeviceRequestMessageFilterTest, TestGetSources_AudioAndVideoDevices) { + // Runs through test with 1 audio and 1 video device. + RunTest(1, 1); +} + +TEST_F(DeviceRequestMessageFilterTest, + TestGetSources_MultipleAudioAndVideoDevices) { + // Runs through test with 3 audio devices and 2 video devices. + RunTest(3, 2); +} + +TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoVideoDevices) { + // Runs through test with 4 audio devices and 0 video devices. + RunTest(4, 0); +} + +TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoAudioDevices) { + // Runs through test with 0 audio devices and 3 video devices. + RunTest(0, 3); +} + +TEST_F(DeviceRequestMessageFilterTest, TestGetSources_NoDevices) { + // Runs through test with no devices. + RunTest(0, 0); +} + +}; // namespace content diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc index ce15ee3..3e322c5 100644 --- a/content/browser/renderer_host/media/media_stream_manager.cc +++ b/content/browser/renderer_host/media/media_stream_manager.cc @@ -13,6 +13,7 @@ #include "base/rand_util.h" #include "base/threading/thread.h" #include "content/browser/renderer_host/media/audio_input_device_manager.h" +#include "content/browser/renderer_host/media/device_request_message_filter.h" #include "content/browser/renderer_host/media/media_stream_requester.h" #include "content/browser/renderer_host/media/media_stream_ui_proxy.h" #include "content/browser/renderer_host/media/video_capture_manager.h" @@ -99,7 +100,7 @@ class MediaStreamManager::DeviceRequest { // used internally within the content module. std::string device_id = WebContentsCaptureUtil::StripWebContentsDeviceScheme( - request.requested_device_id); + request.tab_capture_device_id); media_observer->OnMediaRequestStateChanged( request.render_process_id, request.render_view_id, @@ -134,6 +135,13 @@ MediaStreamManager::EnumerationCache::EnumerationCache() MediaStreamManager::EnumerationCache::~EnumerationCache() { } +MediaStreamManager::MediaStreamManager() + : audio_manager_(NULL), + monitoring_started_(false), + io_loop_(NULL), + screen_capture_active_(false), + use_fake_ui_(false) {} + MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager) : audio_manager_(audio_manager), monitoring_started_(false), @@ -183,8 +191,8 @@ std::string MediaStreamManager::MakeMediaAccessRequest( DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Create a new request based on options. MediaStreamRequest stream_request( - render_process_id, render_view_id, page_request_id, security_origin, - MEDIA_DEVICE_ACCESS, std::string(), + render_process_id, render_view_id, page_request_id, std::string(), + security_origin, MEDIA_DEVICE_ACCESS, std::string(), std::string(), options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(NULL, stream_request); const std::string& label = AddRequest(request); @@ -215,7 +223,7 @@ std::string MediaStreamManager::GenerateStream( int target_render_process_id = render_process_id; int target_render_view_id = render_view_id; - std::string requested_device_id; + std::string tab_capture_device_id; // Customize options for a WebContents based capture. if (options.audio_type == MEDIA_TAB_AUDIO_CAPTURE || @@ -223,13 +231,14 @@ std::string MediaStreamManager::GenerateStream( // TODO(justinlin): Can't plumb audio mirroring using stream type right // now, so plumbing by device_id. Will revisit once it's refactored. // http://crbug.com/163100 - requested_device_id = + tab_capture_device_id = WebContentsCaptureUtil::AppendWebContentsDeviceScheme( !options.video_device_id.empty() ? options.video_device_id : options.audio_device_id); bool has_valid_device_id = WebContentsCaptureUtil::ExtractTabCaptureTarget( - requested_device_id, &target_render_process_id, &target_render_view_id); + tab_capture_device_id, &target_render_process_id, + &target_render_view_id); if (!has_valid_device_id || (options.audio_type != MEDIA_TAB_AUDIO_CAPTURE && options.audio_type != MEDIA_NO_SERVICE) || @@ -240,6 +249,22 @@ std::string MediaStreamManager::GenerateStream( } } + std::string translated_audio_device_id; + std::string translated_video_device_id; + if (options.audio_type == MEDIA_DEVICE_AUDIO_CAPTURE) { + bool found_match = TranslateGUIDToRawId( + MEDIA_DEVICE_AUDIO_CAPTURE, security_origin, options.audio_device_id, + &translated_audio_device_id); + DCHECK(found_match || translated_audio_device_id.empty()); + } + + if (options.video_type == MEDIA_DEVICE_VIDEO_CAPTURE) { + bool found_match = TranslateGUIDToRawId( + MEDIA_DEVICE_VIDEO_CAPTURE, security_origin, options.video_device_id, + &translated_video_device_id); + DCHECK(found_match || translated_video_device_id.empty()); + } + if (options.video_type == MEDIA_SCREEN_VIDEO_CAPTURE) { if (options.audio_type != MEDIA_NO_SERVICE) { // TODO(sergeyu): Surface error message to the calling JS code. @@ -260,7 +285,8 @@ std::string MediaStreamManager::GenerateStream( // Create a new request based on options. MediaStreamRequest stream_request( target_render_process_id, target_render_view_id, page_request_id, - security_origin, MEDIA_GENERATE_STREAM, requested_device_id, + tab_capture_device_id, security_origin, MEDIA_GENERATE_STREAM, + translated_audio_device_id, translated_video_device_id, options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); @@ -370,8 +396,8 @@ std::string MediaStreamManager::EnumerateDevices( } MediaStreamRequest stream_request( - render_process_id, render_view_id, page_request_id, security_origin, - MEDIA_ENUMERATE_DEVICES, std::string(), + render_process_id, render_view_id, page_request_id, std::string(), + security_origin, MEDIA_ENUMERATE_DEVICES, std::string(), std::string(), options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); @@ -421,17 +447,19 @@ std::string MediaStreamManager::OpenDevice( StreamOptions options; if (IsAudioMediaType(type)) { options.audio_type = type; + options.audio_device_id = device_id; } else if (IsVideoMediaType(type)) { options.video_type = type; + options.video_device_id = device_id; } else { NOTREACHED(); return std::string(); } MediaStreamRequest stream_request( - render_process_id, render_view_id, page_request_id, security_origin, - MEDIA_OPEN_DEVICE, device_id, - options.audio_type, options.video_type); + render_process_id, render_view_id, page_request_id, std::string(), + security_origin, MEDIA_OPEN_DEVICE, options.audio_device_id, + options.video_device_id, options.audio_type, options.video_type); DeviceRequest* request = new DeviceRequest(requester, stream_request); const std::string& label = AddRequest(request); StartEnumeration(request); @@ -479,6 +507,35 @@ void MediaStreamManager::StopMonitoring() { } } +bool MediaStreamManager::TranslateGUIDToRawId(MediaStreamType stream_type, + const GURL& security_origin, + const std::string& device_guid, + std::string* raw_device_id) { + DCHECK(stream_type == MEDIA_DEVICE_AUDIO_CAPTURE || + stream_type == MEDIA_DEVICE_VIDEO_CAPTURE); + if (device_guid.empty()) + return false; + + EnumerationCache* cache = + stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ? + &audio_enumeration_cache_ : &video_enumeration_cache_; + + // If device monitoring hasn't started, the |device_guid| is not valid. + if (!cache->valid) + return false; + + for (StreamDeviceInfoArray::const_iterator it = cache->devices.begin(); + it != cache->devices.end(); + ++it) { + if (DeviceRequestMessageFilter::DoesRawIdMatchGuid( + security_origin, device_guid, it->device.id)) { + *raw_device_id = it->device.id; + return true; + } + } + return false; +} + void MediaStreamManager::ClearEnumerationCache(EnumerationCache* cache) { DCHECK_EQ(base::MessageLoop::current(), io_loop_); cache->valid = false; @@ -887,7 +944,7 @@ void MediaStreamManager::HandleAccessRequestResponse( // Re-append the device's id since we lost it when posting request to UI. if (device_info.device.type == content::MEDIA_TAB_VIDEO_CAPTURE || device_info.device.type == content::MEDIA_TAB_AUDIO_CAPTURE) { - device_info.device.id = request->request.requested_device_id; + device_info.device.id = request->request.tab_capture_device_id; // Initialize the sample_rate and channel_layout here since for audio // mirroring, we don't go through EnumerateDevices where these are usually diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h index 03d5751..6d0d2ec 100644 --- a/content/browser/renderer_host/media/media_stream_manager.h +++ b/content/browser/renderer_host/media/media_stream_manager.h @@ -104,7 +104,7 @@ class CONTENT_EXPORT MediaStreamManager void CancelRequest(const std::string& label); // Closes generated stream. - void StopGeneratedStream(const std::string& label); + virtual void StopGeneratedStream(const std::string& label); // Gets a list of devices of |type|, which must be MEDIA_DEVICE_AUDIO_CAPTURE // or MEDIA_DEVICE_VIDEO_CAPTURE. @@ -113,12 +113,12 @@ class CONTENT_EXPORT MediaStreamManager // and video devices and also start monitoring device changes, such as // plug/unplug. The new device lists will be delivered via media observer to // MediaCaptureDevicesDispatcher. - std::string EnumerateDevices(MediaStreamRequester* requester, - int render_process_id, - int render_view_id, - int page_request_id, - MediaStreamType type, - const GURL& security_origin); + virtual std::string EnumerateDevices(MediaStreamRequester* requester, + int render_process_id, + int render_view_id, + int page_request_id, + MediaStreamType type, + const GURL& security_origin); // Open a device identified by |device_id|. |type| must be either // MEDIA_DEVICE_AUDIO_CAPTURE or MEDIA_DEVICE_VIDEO_CAPTURE. @@ -163,6 +163,10 @@ class CONTENT_EXPORT MediaStreamManager // case (see http://crbug.com/247525#c14). virtual void WillDestroyCurrentMessageLoop() OVERRIDE; + protected: + // Used for testing. + MediaStreamManager(); + private: // Contains all data needed to keep track of requests. class DeviceRequest; @@ -213,6 +217,15 @@ class CONTENT_EXPORT MediaStreamManager void StartMonitoring(); void StopMonitoring(); + // Finds and returns the raw device id corresponding to the given + // |device_guid|. Returns true if there was a raw device id that matched the + // given |device_guid|, false if nothing matched it. + bool TranslateGUIDToRawId( + MediaStreamType stream_type, + const GURL& security_origin, + const std::string& device_guid, + std::string* raw_device_id); + // Device thread shared by VideoCaptureManager and AudioInputDeviceManager. scoped_ptr<base::Thread> device_thread_; diff --git a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc index 31d69e3..4e0eff8 100644 --- a/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc +++ b/content/browser/renderer_host/media/media_stream_ui_proxy_unittest.cc @@ -82,16 +82,19 @@ MATCHER_P(SameRequest, expected, "") { return expected.render_process_id == arg.render_process_id && expected.render_view_id == arg.render_view_id && + expected.tab_capture_device_id == arg.tab_capture_device_id && expected.security_origin == arg.security_origin && expected.request_type == arg.request_type && - expected.requested_device_id == arg.requested_device_id && + expected.requested_audio_device_id == arg.requested_audio_device_id && + expected.requested_video_device_id == arg.requested_video_device_id && expected.audio_type == arg.audio_type && expected.video_type == arg.video_type; } TEST_F(MediaStreamUIProxyTest, Deny) { - MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), + MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"), MEDIA_GENERATE_STREAM, std::string(), + std::string(), MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_DEVICE_VIDEO_CAPTURE); proxy_->RequestAccess( @@ -115,8 +118,9 @@ TEST_F(MediaStreamUIProxyTest, Deny) { } TEST_F(MediaStreamUIProxyTest, AcceptAndStart) { - MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), + MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"), MEDIA_GENERATE_STREAM, std::string(), + std::string(), MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_DEVICE_VIDEO_CAPTURE); proxy_->RequestAccess( @@ -148,8 +152,9 @@ TEST_F(MediaStreamUIProxyTest, AcceptAndStart) { // Verify that the proxy can be deleted before the request is processed. TEST_F(MediaStreamUIProxyTest, DeleteBeforeAccepted) { - MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), + MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"), MEDIA_GENERATE_STREAM, std::string(), + std::string(), MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_DEVICE_VIDEO_CAPTURE); proxy_->RequestAccess( @@ -169,8 +174,9 @@ TEST_F(MediaStreamUIProxyTest, DeleteBeforeAccepted) { } TEST_F(MediaStreamUIProxyTest, StopFromUI) { - MediaStreamRequest request(0, 0, 0, GURL("http://origin/"), + MediaStreamRequest request(0, 0, 0, std::string(), GURL("http://origin/"), MEDIA_GENERATE_STREAM, std::string(), + std::string(), MEDIA_DEVICE_AUDIO_CAPTURE, MEDIA_DEVICE_VIDEO_CAPTURE); proxy_->RequestAccess( diff --git a/content/browser/renderer_host/render_process_host_impl.cc b/content/browser/renderer_host/render_process_host_impl.cc index 9ea7ef9..009833d 100644 --- a/content/browser/renderer_host/render_process_host_impl.cc +++ b/content/browser/renderer_host/render_process_host_impl.cc @@ -73,6 +73,7 @@ #include "content/browser/renderer_host/media/audio_input_renderer_host.h" #include "content/browser/renderer_host/media/audio_mirroring_manager.h" #include "content/browser/renderer_host/media/audio_renderer_host.h" +#include "content/browser/renderer_host/media/device_request_message_filter.h" #include "content/browser/renderer_host/media/media_stream_dispatcher_host.h" #include "content/browser/renderer_host/media/midi_host.h" #include "content/browser/renderer_host/media/peer_connection_tracker_host.h" @@ -644,6 +645,7 @@ void RenderProcessHostImpl::CreateMessageFilters() { channel_->AddFilter(peer_connection_tracker_host_.get()); channel_->AddFilter(new MediaStreamDispatcherHost( GetID(), media_stream_manager)); + channel_->AddFilter(new DeviceRequestMessageFilter(media_stream_manager)); #endif #if defined(ENABLE_PLUGINS) // TODO(raymes): PepperMessageFilter should be removed from here. @@ -887,6 +889,7 @@ void RenderProcessHostImpl::PropagateBrowserCommandLineToRenderer( #endif switches::kDisableWebAudio, #if defined(ENABLE_WEBRTC) + switches::kEnableDeviceEnumeration, switches::kEnableSCTPDataChannels, #endif switches::kEnableWebAnimationsCSS, diff --git a/content/common/media/media_stream_messages.h b/content/common/media/media_stream_messages.h index 8678997..a388f85 100644 --- a/content/common/media/media_stream_messages.h +++ b/content/common/media/media_stream_messages.h @@ -50,6 +50,8 @@ IPC_MESSAGE_ROUTED1(MediaStreamMsg_StreamGenerationFailed, int /* request id */) // The browser has enumerated devices successfully. +// Used by Pepper. +// TODO(vrk,wjia): Move this to pepper code. IPC_MESSAGE_ROUTED3(MediaStreamMsg_DevicesEnumerated, int /* request id */, std::string /* label */, @@ -71,6 +73,11 @@ IPC_MESSAGE_ROUTED3(MediaStreamMsg_DeviceOpened, IPC_MESSAGE_ROUTED1(MediaStreamMsg_DeviceOpenFailed, int /* request id */) +// Response to enumerate devices request. +IPC_MESSAGE_CONTROL2(MediaStreamMsg_GetSourcesACK, + int /* request id */, + content::StreamDeviceInfoArray /* device_list */) + // Messages sent from the renderer to the browser. // Request a new media stream. @@ -91,6 +98,8 @@ IPC_MESSAGE_CONTROL2(MediaStreamHostMsg_StopGeneratedStream, std::string /* label */) // Request to enumerate devices. +// Used by Pepper. +// TODO(vrk,wjia): Move this to pepper code. IPC_MESSAGE_CONTROL4(MediaStreamHostMsg_EnumerateDevices, int /* render view id */, int /* request id */, @@ -104,3 +113,8 @@ IPC_MESSAGE_CONTROL5(MediaStreamHostMsg_OpenDevice, std::string /* device_id */, content::MediaStreamType /* type */, GURL /* security origin */) + +// Request to enumerate devices. +IPC_MESSAGE_CONTROL2(MediaStreamHostMsg_GetSources, + int /* request id */, + GURL /* origin */) diff --git a/content/common/media/media_stream_options.cc b/content/common/media/media_stream_options.cc index 001fb3e..d4dec11 100644 --- a/content/common/media/media_stream_options.cc +++ b/content/common/media/media_stream_options.cc @@ -10,6 +10,7 @@ namespace content { const char kMediaStreamSource[] = "chromeMediaSource"; const char kMediaStreamSourceId[] = "chromeMediaSourceId"; +const char kMediaStreamSourceInfoId[] = "sourceId"; const char kMediaStreamSourceTab[] = "tab"; const char kMediaStreamSourceScreen[] = "screen"; diff --git a/content/common/media/media_stream_options.h b/content/common/media/media_stream_options.h index 18f0379..7e6940e 100644 --- a/content/common/media/media_stream_options.h +++ b/content/common/media/media_stream_options.h @@ -16,6 +16,7 @@ namespace content { // MediaStreamConstraint keys for constraints that are passed to getUserMedia. CONTENT_EXPORT extern const char kMediaStreamSource[]; CONTENT_EXPORT extern const char kMediaStreamSourceId[]; +CONTENT_EXPORT extern const char kMediaStreamSourceInfoId[]; CONTENT_EXPORT extern const char kMediaStreamSourceTab[]; CONTENT_EXPORT extern const char kMediaStreamSourceScreen[]; diff --git a/content/content_browser.gypi b/content/content_browser.gypi index 98ff1d4..0b7dbf1 100644 --- a/content/content_browser.gypi +++ b/content/content_browser.gypi @@ -790,6 +790,8 @@ 'browser/renderer_host/media/audio_renderer_host.h', 'browser/renderer_host/media/audio_sync_reader.cc', 'browser/renderer_host/media/audio_sync_reader.h', + 'browser/renderer_host/media/device_request_message_filter.cc', + 'browser/renderer_host/media/device_request_message_filter.h', 'browser/renderer_host/media/media_stream_dispatcher_host.cc', 'browser/renderer_host/media/media_stream_dispatcher_host.h', 'browser/renderer_host/media/media_stream_manager.cc', diff --git a/content/content_tests.gypi b/content/content_tests.gypi index 1c065f6..7ecf15f 100644 --- a/content/content_tests.gypi +++ b/content/content_tests.gypi @@ -335,6 +335,7 @@ 'browser/renderer_host/media/audio_input_device_manager_unittest.cc', 'browser/renderer_host/media/audio_mirroring_manager_unittest.cc', 'browser/renderer_host/media/audio_renderer_host_unittest.cc', + 'browser/renderer_host/media/device_request_message_filter_unittest.cc', 'browser/renderer_host/media/media_stream_dispatcher_host_unittest.cc', 'browser/renderer_host/media/media_stream_manager_unittest.cc', 'browser/renderer_host/media/media_stream_ui_proxy_unittest.cc', diff --git a/content/public/common/content_switches.cc b/content/public/common/content_switches.cc index 7395f5b..96ddf06 100644 --- a/content/public/common/content_switches.cc +++ b/content/public/common/content_switches.cc @@ -224,6 +224,10 @@ const char kEnableWebRtcAecRecordings[] = "enable-webrtc-aec-recordings"; // Enable WebRTC DataChannels SCTP wire protocol support. const char kEnableSCTPDataChannels[] = "enable-sctp-data-channels"; + +// Enable WebRTC device enumeration. +const char kEnableDeviceEnumeration[] = "enable-device-enumeration"; + #endif // Enable WebRTC to open TCP server sockets. diff --git a/content/public/common/content_switches.h b/content/public/common/content_switches.h index 90a7a6b..2151085 100644 --- a/content/public/common/content_switches.h +++ b/content/public/common/content_switches.h @@ -85,8 +85,9 @@ CONTENT_EXPORT extern const char kEnableSpeechRecognition[]; #endif CONTENT_EXPORT extern const char kDisableWebAudio[]; #if defined(ENABLE_WEBRTC) -extern const char kEnableWebRtcAecRecordings[]; +CONTENT_EXPORT extern const char kEnableDeviceEnumeration[]; CONTENT_EXPORT extern const char kEnableSCTPDataChannels[]; +extern const char kEnableWebRtcAecRecordings[]; extern const char kEnableWebRtcHWDecoding[]; #endif extern const char kEnableWebRtcTcpServerSocket[]; diff --git a/content/public/common/media_stream_request.cc b/content/public/common/media_stream_request.cc index 19f3a30..efac33f 100644 --- a/content/public/common/media_stream_request.cc +++ b/content/public/common/media_stream_request.cc @@ -51,17 +51,21 @@ MediaStreamRequest::MediaStreamRequest( int render_process_id, int render_view_id, int page_request_id, + const std::string& tab_capture_device_id, const GURL& security_origin, MediaStreamRequestType request_type, - const std::string& requested_device_id, + const std::string& requested_audio_device_id, + const std::string& requested_video_device_id, MediaStreamType audio_type, MediaStreamType video_type) : render_process_id(render_process_id), render_view_id(render_view_id), page_request_id(page_request_id), + tab_capture_device_id(tab_capture_device_id), security_origin(security_origin), request_type(request_type), - requested_device_id(requested_device_id), + requested_audio_device_id(requested_audio_device_id), + requested_video_device_id(requested_video_device_id), audio_type(audio_type), video_type(video_type) { } diff --git a/content/public/common/media_stream_request.h b/content/public/common/media_stream_request.h index bdbc288a..0703088 100644 --- a/content/public/common/media_stream_request.h +++ b/content/public/common/media_stream_request.h @@ -94,14 +94,25 @@ typedef std::vector<MediaStreamDevice> MediaStreamDevices; typedef std::map<MediaStreamType, MediaStreamDevices> MediaStreamDeviceMap; // Represents a request for media streams (audio/video). +// It looks like the last 4 parameters should use StreamOptions instead, but +// StreamOption depends on media_stream_request.h because it needs +// MediaStreamDevice. +// TODO(vrk): Decouple MediaStreamDevice from this header file so that +// media_stream_options.h no longer depends on this file. +// TODO(vrk,justinlin,wjia): Figure out a way to share this code cleanly between +// vanilla WebRTC, Tab Capture, and Pepper Video Capture. Right now there is +// Tab-only stuff and Pepper-only stuff being passed around to all clients, +// which is icky. struct CONTENT_EXPORT MediaStreamRequest { MediaStreamRequest( int render_process_id, int render_view_id, int page_request_id, + const std::string& tab_capture_device_id, const GURL& security_origin, MediaStreamRequestType request_type, - const std::string& requested_device_id, + const std::string& requested_audio_device_id, + const std::string& requested_video_device_id, MediaStreamType audio_type, MediaStreamType video_type); @@ -117,6 +128,9 @@ struct CONTENT_EXPORT MediaStreamRequest { // identifying this request. This is used for cancelling request. int page_request_id; + // Used by tab capture. + std::string tab_capture_device_id; + // The WebKit security origin for the current request (e.g. "html5rocks.com"). GURL security_origin; @@ -126,10 +140,9 @@ struct CONTENT_EXPORT MediaStreamRequest { // Pepper requests are signified by the |MEDIA_OPEN_DEVICE| value. MediaStreamRequestType request_type; - // Stores the requested device id. Used only if the |request_type| filed is - // set to |MEDIA_OPEN_DEVICE| to indicate which device the request is for as - // in that case the decision is not left to the user but to the media client. - std::string requested_device_id; + // Stores the requested raw device id for physical audio or video devices. + std::string requested_audio_device_id; + std::string requested_video_device_id; // Flag to indicate if the request contains audio. MediaStreamType audio_type; diff --git a/content/renderer/media/media_stream_center.cc b/content/renderer/media/media_stream_center.cc index 3ae7a96..7919dc5 100644 --- a/content/renderer/media/media_stream_center.cc +++ b/content/renderer/media/media_stream_center.cc @@ -6,10 +6,14 @@ #include <string> +#include "base/command_line.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/strings/string_number_conversions.h" #include "base/strings/utf_string_conversions.h" +#include "content/common/media/media_stream_messages.h" +#include "content/public/common/content_switches.h" +#include "content/public/renderer/render_thread.h" #include "content/renderer/media/media_stream_dependency_factory.h" #include "content/renderer/media/media_stream_extra_data.h" #include "content/renderer/media/media_stream_impl.h" @@ -18,10 +22,15 @@ #include "third_party/WebKit/public/platform/WebMediaStreamCenterClient.h" #include "third_party/WebKit/public/platform/WebMediaStreamSource.h" #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" +#include "third_party/WebKit/public/platform/WebMediaStreamTrackSourcesRequest.h" +#include "third_party/WebKit/public/platform/WebSourceInfo.h" #include "third_party/WebKit/public/platform/WebVector.h" #include "third_party/WebKit/public/web/WebFrame.h" #include "third_party/libjingle/source/talk/app/webrtc/jsep.h" +using WebKit::WebFrame; +using WebKit::WebView; + namespace content { static webrtc::MediaStreamInterface* GetNativeMediaStream( @@ -50,7 +59,21 @@ static webrtc::MediaStreamTrackInterface* GetNativeMediaStreamTrack( MediaStreamCenter::MediaStreamCenter(WebKit::WebMediaStreamCenterClient* client, MediaStreamDependencyFactory* factory) - : rtc_factory_(factory) { + : rtc_factory_(factory), next_request_id_(0) {} + +MediaStreamCenter::~MediaStreamCenter() {} + +bool MediaStreamCenter::getMediaStreamTrackSources( + const WebKit::WebMediaStreamTrackSourcesRequest& request) { + if (CommandLine::ForCurrentProcess()->HasSwitch( + switches::kEnableDeviceEnumeration)) { + int request_id = next_request_id_++; + requests_.insert(std::make_pair(request_id, request)); + RenderThread::Get()->Send(new MediaStreamHostMsg_GetSources( + request_id, GURL(request.origin().utf8()))); + return true; + } + return false; } void MediaStreamCenter::didEnableMediaStreamTrack( @@ -109,4 +132,37 @@ bool MediaStreamCenter::didRemoveMediaStreamTrack( return rtc_factory_->RemoveNativeMediaStreamTrack(stream, track); } +bool MediaStreamCenter::OnControlMessageReceived(const IPC::Message& message) { + bool handled = true; + IPC_BEGIN_MESSAGE_MAP(MediaStreamCenter, message) + IPC_MESSAGE_HANDLER(MediaStreamMsg_GetSourcesACK, + OnGetSourcesComplete) + IPC_MESSAGE_UNHANDLED(handled = false) + IPC_END_MESSAGE_MAP() + return handled; +} + +void MediaStreamCenter::OnGetSourcesComplete( + int request_id, + const content::StreamDeviceInfoArray& devices) { + RequestMap::iterator request_it = requests_.find(request_id); + DCHECK(request_it != requests_.end()); + + WebKit::WebVector<WebKit::WebSourceInfo> sourceInfos(devices.size()); + for (size_t i = 0; i < devices.size(); ++i) { + DCHECK(devices[i].device.type == MEDIA_DEVICE_AUDIO_CAPTURE || + devices[i].device.type == MEDIA_DEVICE_VIDEO_CAPTURE); + // TODO(vrk): Hook this up to the device policy so that |device.name| is + // only populated when appropriate. + sourceInfos[i] + .initialize(WebKit::WebString::fromUTF8(devices[i].device.id), + devices[i].device.type == MEDIA_DEVICE_AUDIO_CAPTURE + ? WebKit::WebSourceInfo::SourceKindAudio + : WebKit::WebSourceInfo::SourceKindVideo, + WebKit::WebString::fromUTF8(devices[i].device.name), + WebKit::WebSourceInfo::VideoFacingModeNone); + } + request_it->second.requestSucceeded(sourceInfos); +} + } // namespace content diff --git a/content/renderer/media/media_stream_center.h b/content/renderer/media/media_stream_center.h index aca5e53..642cda5 100644 --- a/content/renderer/media/media_stream_center.h +++ b/content/renderer/media/media_stream_center.h @@ -5,9 +5,13 @@ #ifndef CONTENT_RENDERER_MEDIA_MEDIA_STREAM_CENTER_H_ #define CONTENT_RENDERER_MEDIA_MEDIA_STREAM_CENTER_H_ +#include <map> + #include "base/basictypes.h" #include "base/compiler_specific.h" #include "content/common/content_export.h" +#include "content/common/media/media_stream_options.h" +#include "content/public/renderer/render_process_observer.h" #include "third_party/WebKit/public/platform/WebMediaStream.h" #include "third_party/WebKit/public/platform/WebMediaStreamCenter.h" #include "third_party/WebKit/public/platform/WebMediaStreamTrack.h" @@ -20,38 +24,57 @@ namespace content { class MediaStreamDependencyFactory; class CONTENT_EXPORT MediaStreamCenter - : NON_EXPORTED_BASE(public WebKit::WebMediaStreamCenter) { + : NON_EXPORTED_BASE(public WebKit::WebMediaStreamCenter), + public RenderProcessObserver { public: MediaStreamCenter(WebKit::WebMediaStreamCenterClient* client, MediaStreamDependencyFactory* factory); + virtual ~MediaStreamCenter(); + + virtual bool getMediaStreamTrackSources( + const WebKit::WebMediaStreamTrackSourcesRequest& request); virtual void didEnableMediaStreamTrack( const WebKit::WebMediaStream& stream, - const WebKit::WebMediaStreamTrack& component) OVERRIDE; + const WebKit::WebMediaStreamTrack& component); virtual void didDisableMediaStreamTrack( const WebKit::WebMediaStream& stream, - const WebKit::WebMediaStreamTrack& component) OVERRIDE; + const WebKit::WebMediaStreamTrack& component); virtual void didStopLocalMediaStream( - const WebKit::WebMediaStream& stream) OVERRIDE; + const WebKit::WebMediaStream& stream); virtual void didCreateMediaStream( - WebKit::WebMediaStream& stream) OVERRIDE; + WebKit::WebMediaStream& stream); virtual bool didAddMediaStreamTrack( const WebKit::WebMediaStream& stream, - const WebKit::WebMediaStreamTrack& track) OVERRIDE; + const WebKit::WebMediaStreamTrack& track); virtual bool didRemoveMediaStreamTrack( const WebKit::WebMediaStream& stream, - const WebKit::WebMediaStreamTrack& track) OVERRIDE; + const WebKit::WebMediaStreamTrack& track); private: + // RenderProcessObserver implementation. + virtual bool OnControlMessageReceived(const IPC::Message& message) OVERRIDE; + + void OnGetSourcesComplete(int request_id, + const content::StreamDeviceInfoArray& devices); + // |rtc_factory_| is a weak pointer and is owned by the RenderThreadImpl. // It is valid as long as RenderThreadImpl exist. MediaStreamDependencyFactory* rtc_factory_; + // A strictly increasing id that's used to label incoming GetSources() + // requests. + int next_request_id_; + + typedef std::map<int, WebKit::WebMediaStreamTrackSourcesRequest> RequestMap; + // Maps request ids to request objects. + RequestMap requests_; + DISALLOW_COPY_AND_ASSIGN(MediaStreamCenter); }; diff --git a/content/renderer/media/media_stream_impl.cc b/content/renderer/media/media_stream_impl.cc index 14fb0327..7cf1887 100644 --- a/content/renderer/media/media_stream_impl.cc +++ b/content/renderer/media/media_stream_impl.cc @@ -31,13 +31,18 @@ namespace content { namespace { -std::string GetMandatoryStreamConstraint( - const WebKit::WebMediaConstraints& constraints, const std::string& key) { +std::string GetStreamConstraint( + const WebKit::WebMediaConstraints& constraints, const std::string& key, + bool is_mandatory) { if (constraints.isNull()) return std::string(); WebKit::WebString value; - constraints.getMandatoryConstraintValue(UTF8ToUTF16(key), value); + if (is_mandatory) { + constraints.getMandatoryConstraintValue(UTF8ToUTF16(key), value); + } else { + constraints.getOptionalConstraintValue(UTF8ToUTF16(key), value); + } return UTF16ToUTF8(value); } @@ -45,24 +50,24 @@ void UpdateRequestOptions( const WebKit::WebUserMediaRequest& user_media_request, StreamOptions* options) { if (options->audio_type != content::MEDIA_NO_SERVICE) { - std::string audio_stream_source = GetMandatoryStreamConstraint( - user_media_request.audioConstraints(), kMediaStreamSource); + std::string audio_stream_source = GetStreamConstraint( + user_media_request.audioConstraints(), kMediaStreamSource, true); if (audio_stream_source == kMediaStreamSourceTab) { options->audio_type = content::MEDIA_TAB_AUDIO_CAPTURE; - options->audio_device_id = GetMandatoryStreamConstraint( + options->audio_device_id = GetStreamConstraint( user_media_request.audioConstraints(), - kMediaStreamSourceId); + kMediaStreamSourceId, true); } } if (options->video_type != content::MEDIA_NO_SERVICE) { - std::string video_stream_source = GetMandatoryStreamConstraint( - user_media_request.videoConstraints(), kMediaStreamSource); + std::string video_stream_source = GetStreamConstraint( + user_media_request.videoConstraints(), kMediaStreamSource, true); if (video_stream_source == kMediaStreamSourceTab) { options->video_type = content::MEDIA_TAB_VIDEO_CAPTURE; - options->video_device_id = GetMandatoryStreamConstraint( + options->video_device_id = GetStreamConstraint( user_media_request.videoConstraints(), - kMediaStreamSourceId); + kMediaStreamSourceId, true); } else if (video_stream_source == kMediaStreamSourceScreen) { options->video_type = content::MEDIA_SCREEN_VIDEO_CAPTURE; } @@ -154,10 +159,18 @@ void MediaStreamImpl::requestUserMedia( options.audio_type = MEDIA_DEVICE_AUDIO_CAPTURE; options.video_type = MEDIA_DEVICE_VIDEO_CAPTURE; } else { - if (user_media_request.audio()) + if (user_media_request.audio()) { options.audio_type = MEDIA_DEVICE_AUDIO_CAPTURE; - if (user_media_request.video()) + options.audio_device_id = GetStreamConstraint( + user_media_request.audioConstraints(), + kMediaStreamSourceInfoId, false); + } + if (user_media_request.video()) { options.video_type = MEDIA_DEVICE_VIDEO_CAPTURE; + options.video_device_id = GetStreamConstraint( + user_media_request.videoConstraints(), + kMediaStreamSourceInfoId, false); + } security_origin = GURL(user_media_request.securityOrigin().toString()); // Get the WebFrame that requested a MediaStream. diff --git a/content/renderer/render_thread_impl.cc b/content/renderer/render_thread_impl.cc index 31cb7bb..62a33bf 100644 --- a/content/renderer/render_thread_impl.cc +++ b/content/renderer/render_thread_impl.cc @@ -1207,8 +1207,10 @@ WebKit::WebMediaStreamCenter* RenderThreadImpl::CreateMediaStreamCenter( media_stream_center_ = GetContentClient()->renderer() ->OverrideCreateWebMediaStreamCenter(client); if (!media_stream_center_) { - media_stream_center_ = new MediaStreamCenter( - client, GetMediaStreamDependencyFactory()); + scoped_ptr<MediaStreamCenter> media_stream_center( + new MediaStreamCenter(client, GetMediaStreamDependencyFactory())); + AddObserver(media_stream_center.get()); + media_stream_center_ = media_stream_center.release(); } } #endif |