diff options
author | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-16 17:03:12 +0000 |
---|---|---|
committer | xians@chromium.org <xians@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2012-11-16 17:03:12 +0000 |
commit | 0b6e4e0fe970154171fa633102704dbfa0ee4684 (patch) | |
tree | 219611af8eb7f1a10fbc835b5b050015406413bf | |
parent | b40a1272637611b8a091b11c7ec84611b5e01d66 (diff) | |
download | chromium_src-0b6e4e0fe970154171fa633102704dbfa0ee4684.zip chromium_src-0b6e4e0fe970154171fa633102704dbfa0ee4684.tar.gz chromium_src-0b6e4e0fe970154171fa633102704dbfa0ee4684.tar.bz2 |
This patch implements a device selection UI to chrome content setting, and the UI will be similar to that device UI in gmail-chat settings.
In order to achieve it, this patch:
# added a MediaDevicesSelectionHandler in UI options to handle the device selection.
# added a MediaCaptureDevicesDispatcher to chrome media, which will receive device changed notification from MediaStreamManager when a capture device is plugged in or unplugged, then it will cached the device lists and send the new lists to its observers.
# MediaDevicesSelectionHandler will register itself as the observer to MediaCaptureDevicesDispatcher when it is initialized, so that it will always get up-to-date device lists. And it will remove itself on destruction.
BUG=159398
TEST= go to content setting media section, select devices via the menus.
Review URL: https://codereview.chromium.org/11364048
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@168227 0039d316-1c4b-4281-b951-d872f2087c98
22 files changed, 584 insertions, 60 deletions
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd index f8a840d5..f41cd28 100644 --- a/chrome/app/generated_resources.grd +++ b/chrome/app/generated_resources.grd @@ -7898,6 +7898,12 @@ The following plug-in is unresponsive: <ph name="PLUGIN_NAME">$1 <message name="IDS_MEDIA_STREAM_ASK_RADIO" desc="A radio button in Content Settings dialog to allow site to query the permision to access capture devices."> Ask me when a site requires access to my camera and microphone (recommended) </message> + <message name="IDS_MEDIA_SELECTED_MIC_LABEL" desc="The label of the 'microphone device' select menu"> + Microphone: + </message> + <message name="IDS_MEDIA_SELECTED_CAMERA_LABEL" desc="The label of the 'camera device' select menu"> + Camera: + </message> <message name="IDS_MEDIA_GALLERY_SECTION_LABEL" desc="The label for the section of content settings that relates to media galleries."> Media galleries </message> diff --git a/chrome/browser/media/media_capture_devices_dispatcher.cc b/chrome/browser/media/media_capture_devices_dispatcher.cc new file mode 100644 index 0000000..d246059 --- /dev/null +++ b/chrome/browser/media/media_capture_devices_dispatcher.cc @@ -0,0 +1,90 @@ +// 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 "chrome/browser/media/media_capture_devices_dispatcher.h" + +#include "chrome/browser/prefs/scoped_user_pref_update.h" +#include "chrome/common/pref_names.h" +#include "content/public/browser/browser_thread.h" +#include "content/public/common/media_stream_request.h" + +using content::BrowserThread; +using content::MediaStreamDevices; + +MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() {} + +MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} + +void MediaCaptureDevicesDispatcher::RegisterUserPrefs(PrefService* user_prefs) { + if (!user_prefs->FindPreference(prefs::kDefaultAudioCaptureDevice)) { + user_prefs->RegisterStringPref(prefs::kDefaultAudioCaptureDevice, + std::string(), + PrefService::UNSYNCABLE_PREF); + } + if (!user_prefs->FindPreference(prefs::kDefaultVideoCaptureDevice)) { + user_prefs->RegisterStringPref(prefs::kDefaultVideoCaptureDevice, + std::string(), + PrefService::UNSYNCABLE_PREF); + } +} + +void MediaCaptureDevicesDispatcher::AudioCaptureDevicesChanged( + const MediaStreamDevices& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread, + this, devices)); +} + +void MediaCaptureDevicesDispatcher::VideoCaptureDevicesChanged( + const MediaStreamDevices& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + + BrowserThread::PostTask( + BrowserThread::UI, FROM_HERE, + base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread, + this, devices)); +} + +void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + if (!observers_.HasObserver(observer)) + observers_.AddObserver(observer); +} + +void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + observers_.RemoveObserver(observer); +} + +const MediaStreamDevices& +MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return audio_devices_; +} + +const MediaStreamDevices& +MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + return video_devices_; +} + +void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread( + const content::MediaStreamDevices& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + audio_devices_ = devices; + FOR_EACH_OBSERVER(Observer, observers_, + OnUpdateAudioDevices(audio_devices_)); +} + +void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread( + const content::MediaStreamDevices& devices){ + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + video_devices_ = devices; + FOR_EACH_OBSERVER(Observer, observers_, + OnUpdateVideoDevices(video_devices_)); +} + diff --git a/chrome/browser/media/media_capture_devices_dispatcher.h b/chrome/browser/media/media_capture_devices_dispatcher.h new file mode 100644 index 0000000..955c4af --- /dev/null +++ b/chrome/browser/media/media_capture_devices_dispatcher.h @@ -0,0 +1,72 @@ +// 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. + +#ifndef CHROME_BROWSER_MEDIA_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_ +#define CHROME_BROWSER_MEDIA_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_ + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "content/public/common/media_stream_request.h" + +class PrefService; + +// This observer is owned by MediaInternals and deleted when MediaInternals +// is deleted. +class MediaCaptureDevicesDispatcher + : public base::RefCountedThreadSafe<MediaCaptureDevicesDispatcher> { + public: + class Observer { + public: + // Handle an information update consisting of a up-to-date audio capture + // device lists. This happens when a microphone is plugged in or unplugged. + virtual void OnUpdateAudioDevices( + const content::MediaStreamDevices& devices) {} + + // Handle an information update consisting of a up-to-date video capture + // device lists. This happens when a camera is plugged in or unplugged. + virtual void OnUpdateVideoDevices( + const content::MediaStreamDevices& devices) {} + + virtual ~Observer() {} + }; + + MediaCaptureDevicesDispatcher(); + + // Registers the preferences related to Media Stream default devices. + static void RegisterUserPrefs(PrefService* user_prefs); + + // Called on IO thread when one audio device is plugged in or unplugged. + void AudioCaptureDevicesChanged(const content::MediaStreamDevices& devices); + + // Called on IO thread when one video device is plugged in or unplugged. + void VideoCaptureDevicesChanged(const content::MediaStreamDevices& devices); + + // Methods for observers. Called on UI thread. + // Observers should add themselves on construction and remove themselves + // on destruction. + void AddObserver(Observer* observer); + void RemoveObserver(Observer* observer); + const content::MediaStreamDevices& GetAudioCaptureDevices(); + const content::MediaStreamDevices& GetVideoCaptureDevices(); + + private: + friend class base::RefCountedThreadSafe<MediaCaptureDevicesDispatcher>; + virtual ~MediaCaptureDevicesDispatcher(); + + // Called by the public functions, executed on UI thread. + void UpdateAudioDevicesOnUIThread(const content::MediaStreamDevices& devices); + void UpdateVideoDevicesOnUIThread(const content::MediaStreamDevices& devices); + + // A list of cached audio capture devices. + content::MediaStreamDevices audio_devices_; + + // A list of cached video capture devices. + content::MediaStreamDevices video_devices_; + + // A list of observers for the device update notifications. + ObserverList<Observer> observers_; +}; + +#endif // CHROME_BROWSER_MEDIA_MEDIA_CAPTURE_DEVICES_DISPATCHER_H_ diff --git a/chrome/browser/media/media_internals.cc b/chrome/browser/media/media_internals.cc index 8584ae4..c57fb48 100644 --- a/chrome/browser/media/media_internals.cc +++ b/chrome/browser/media/media_internals.cc @@ -7,6 +7,7 @@ #include "base/memory/scoped_ptr.h" #include "base/string16.h" #include "base/stringprintf.h" +#include "chrome/browser/media/media_capture_devices_dispatcher.h" #include "chrome/browser/media/media_internals_observer.h" #include "chrome/browser/media/media_stream_capture_indicator.h" #include "content/public/browser/browser_thread.h" @@ -84,6 +85,18 @@ void MediaInternals::OnCaptureDevicesClosed( devices); } +void MediaInternals::OnAudioCaptureDevicesChanged( + const content::MediaStreamDevices& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + media_devices_dispatcher_->AudioCaptureDevicesChanged(devices); +} + +void MediaInternals::OnVideoCaptureDevicesChanged( + const content::MediaStreamDevices& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + media_devices_dispatcher_->VideoCaptureDevicesChanged(devices); +} + void MediaInternals::OnMediaRequestStateChanged( int render_process_id, int render_view_id, @@ -111,13 +124,19 @@ void MediaInternals::SendEverything() { SendUpdate("media.onReceiveEverything", &data_); } +scoped_refptr<MediaCaptureDevicesDispatcher> +MediaInternals::GetMediaCaptureDevicesDispatcher() { + return media_devices_dispatcher_; +} + scoped_refptr<MediaStreamCaptureIndicator> MediaInternals::GetMediaStreamCaptureIndicator() { return media_stream_capture_indicator_.get(); } MediaInternals::MediaInternals() - : media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) { + : media_stream_capture_indicator_(new MediaStreamCaptureIndicator()), + media_devices_dispatcher_(new MediaCaptureDevicesDispatcher()) { } void MediaInternals::UpdateAudioStream( diff --git a/chrome/browser/media/media_internals.h b/chrome/browser/media/media_internals.h index 9f15c5c..62bf9b5 100644 --- a/chrome/browser/media/media_internals.h +++ b/chrome/browser/media/media_internals.h @@ -12,6 +12,7 @@ #include "content/public/browser/media_observer.h" #include "content/public/common/media_stream_request.h" +class MediaCaptureDevicesDispatcher; class MediaInternalsObserver; class MediaStreamCaptureIndicator; @@ -49,6 +50,10 @@ class MediaInternals : public content::MediaObserver { int render_process_id, int render_view_id, const content::MediaStreamDevices& devices) OVERRIDE; + virtual void OnAudioCaptureDevicesChanged( + const content::MediaStreamDevices& devices) OVERRIDE; + virtual void OnVideoCaptureDevicesChanged( + const content::MediaStreamDevices& devices) OVERRIDE; virtual void OnMediaRequestStateChanged( int render_process_id, int render_view_id, @@ -63,6 +68,8 @@ class MediaInternals : public content::MediaObserver { void SendEverything(); scoped_refptr<MediaStreamCaptureIndicator> GetMediaStreamCaptureIndicator(); + scoped_refptr<MediaCaptureDevicesDispatcher> + GetMediaCaptureDevicesDispatcher(); private: friend class MediaInternalsTest; @@ -90,6 +97,7 @@ class MediaInternals : public content::MediaObserver { DictionaryValue data_; ObserverList<MediaInternalsObserver> observers_; scoped_refptr<MediaStreamCaptureIndicator> media_stream_capture_indicator_; + scoped_refptr<MediaCaptureDevicesDispatcher> media_devices_dispatcher_; DISALLOW_COPY_AND_ASSIGN(MediaInternals); }; diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc index b2145c7..8ec5f38 100644 --- a/chrome/browser/prefs/browser_prefs.cc +++ b/chrome/browser/prefs/browser_prefs.cc @@ -30,6 +30,7 @@ #include "chrome/browser/intents/web_intents_util.h" #include "chrome/browser/intranet_redirect_detector.h" #include "chrome/browser/managed_mode/managed_mode.h" +#include "chrome/browser/media/media_capture_devices_dispatcher.h" #include "chrome/browser/metrics/metrics_log.h" #include "chrome/browser/metrics/metrics_service.h" #include "chrome/browser/metrics/variations/variations_service.h" @@ -234,6 +235,7 @@ void RegisterUserPrefs(PrefService* user_prefs) { HostContentSettingsMap::RegisterUserPrefs(user_prefs); IncognitoModePrefs::RegisterUserPrefs(user_prefs); InstantController::RegisterUserPrefs(user_prefs); + MediaCaptureDevicesDispatcher::RegisterUserPrefs(user_prefs); NetPrefObserver::RegisterPrefs(user_prefs); NewTabUI::RegisterUserPrefs(user_prefs); PasswordManager::RegisterUserPrefs(user_prefs); diff --git a/chrome/browser/resources/options/content_settings.css b/chrome/browser/resources/options/content_settings.css index fa2768e..e17464f 100644 --- a/chrome/browser/resources/options/content_settings.css +++ b/chrome/browser/resources/options/content_settings.css @@ -74,3 +74,17 @@ div[role='listitem'][controlled-by] { .sublabel { -webkit-margin-start: 2em; } + +.media-device-control { + display: table-row; +} + +.media-device-control > span { + display: table-cell; + min-width: 145px; +} + +.media-device-control > select { + display: table-cell; + width: 12em; +}
\ No newline at end of file diff --git a/chrome/browser/resources/options/content_settings.html b/chrome/browser/resources/options/content_settings.html index f44f17d..a0655c4 100644 --- a/chrome/browser/resources/options/content_settings.html +++ b/chrome/browser/resources/options/content_settings.html @@ -434,6 +434,14 @@ <section> <h3 i18n-content="mediaStreamTabLabel"></h3> <div> + <div class="media-device-control"> + <span i18n-content="mediaSelectMicLabel"></span> + <select id="media-select-mic" class="weakrtl"></select> + </div> + <div class="media-device-control"> + <span i18n-content="mediaSelectCameraLabel"></span> + <select id="media-select-camera" class="weakrtl"></select> + </div> <div class="radio"> <span class="controlled-setting-with-label"> <input id="media-stream-ask" type="radio" name="media-stream" diff --git a/chrome/browser/resources/options/content_settings.js b/chrome/browser/resources/options/content_settings.js index 5b88c15..3958741 100644 --- a/chrome/browser/resources/options/content_settings.js +++ b/chrome/browser/resources/options/content_settings.js @@ -84,6 +84,11 @@ cr.define('options', function() { $('pepper-flash-cameramic-section').style.display = 'none'; $('pepper-flash-cameramic-exceptions-div').style.display = 'none'; + + $('media-select-mic').addEventListener('change', + ContentSettings.setDefaultMicrophone_); + $('media-select-camera').addEventListener('change', + ContentSettings.setDefaultCamera_); }, }; @@ -182,7 +187,58 @@ cr.define('options', function() { ContentSettings.enablePepperFlashCameraMicSettings = function() { $('pepper-flash-cameramic-section').style.display = ''; $('pepper-flash-cameramic-exceptions-div').style.display = ''; - } + }; + + /** + * Updates the microphone/camera devices menu with the given entries. + * @param {string} type The device type. + * @param {Array} devices List of available devices. + * @param {string} defaultdevice The unique id of the current default device. + */ + ContentSettings.updateDevicesMenu = function(type, devices, defaultdevice) { + var deviceSelect = ''; + if (type == 'mic') { + deviceSelect = $('media-select-mic'); + } else if (type == 'camera') { + deviceSelect = $('media-select-camera'); + } else { + console.error('Unknown device type for <device select> UI element: ' + + type); + return; + } + + deviceSelect.textContent = ''; + + var deviceCount = devices.length; + var defaultIndex = -1; + for (var i = 0; i < deviceCount; i++) { + var device = devices[i]; + var option = new Option(device.name, device.id); + if (option.value == defaultdevice) + defaultIndex = i; + deviceSelect.appendChild(option); + } + if (defaultIndex >= 0) + deviceSelect.selectedIndex = defaultIndex; + }; + + /** + * Set the default microphone device based on the popup selection. + * @private + */ + ContentSettings.setDefaultMicrophone_ = function() { + var deviceSelect = $('media-select-mic'); + chrome.send('setDefaultCaptureDevice', ['mic', deviceSelect.value]); + }; + + /** + * Set the default camera device based on the popup selection. + * @private + */ + ContentSettings.setDefaultCamera_ = function() { + var deviceSelect = $('media-select-camera'); + chrome.send('setDefaultCaptureDevice', ['camera', deviceSelect.value]); + }; // Export return { diff --git a/chrome/browser/ui/webui/options/media_devices_selection_handler.cc b/chrome/browser/ui/webui/options/media_devices_selection_handler.cc new file mode 100644 index 0000000..25277a2 --- /dev/null +++ b/chrome/browser/ui/webui/options/media_devices_selection_handler.cc @@ -0,0 +1,150 @@ +// 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 "chrome/browser/ui/webui/options/media_devices_selection_handler.h" + +#include "base/bind.h" +#include "chrome/browser/media/media_internals.h" +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/common/pref_names.h" + +namespace { + +const char kAudio[] = "mic"; +const char kVideo[] = "camera"; + +} // namespace + +namespace options { + +MediaDevicesSelectionHandler::MediaDevicesSelectionHandler() {} + +MediaDevicesSelectionHandler::~MediaDevicesSelectionHandler() { + // Register to the device observer list to get up-to-date device lists. + MediaCaptureDevicesDispatcher* dispatcher = + MediaInternals::GetInstance()->GetMediaCaptureDevicesDispatcher(); + dispatcher->RemoveObserver(this); +} + +void MediaDevicesSelectionHandler::GetLocalizedValues(DictionaryValue* values) { + DCHECK(values); + + static OptionsStringResource resources[] = { + { "mediaSelectMicLabel", IDS_MEDIA_SELECTED_MIC_LABEL }, + { "mediaSelectCameraLabel", IDS_MEDIA_SELECTED_CAMERA_LABEL }, + }; + + RegisterStrings(values, resources, arraysize(resources)); +} + +void MediaDevicesSelectionHandler::InitializePage() { + // Register to the device observer list to get up-to-date device lists. + MediaCaptureDevicesDispatcher* dispatcher = + MediaInternals::GetInstance()->GetMediaCaptureDevicesDispatcher(); + dispatcher->AddObserver(this); + + // Update the device selection menus. + UpdateDevicesMenuForType(AUDIO); + UpdateDevicesMenuForType(VIDEO); +} + +void MediaDevicesSelectionHandler::RegisterMessages() { + web_ui()->RegisterMessageCallback("setDefaultCaptureDevice", + base::Bind(&MediaDevicesSelectionHandler::SetDefaultCaptureDevice, + base::Unretained(this))); +} + +void MediaDevicesSelectionHandler::OnUpdateAudioDevices( + const content::MediaStreamDevices& devices) { + UpdateDevicesMenu(AUDIO, devices); +} + +void MediaDevicesSelectionHandler::OnUpdateVideoDevices( + const content::MediaStreamDevices& devices) { + UpdateDevicesMenu(VIDEO, devices); +} + +void MediaDevicesSelectionHandler::SetDefaultCaptureDevice( + const ListValue* args) { + DCHECK_EQ(2U, args->GetSize()); + std::string type, device; + if (!(args->GetString(0, &type) && args->GetString(1, &device))) { + NOTREACHED(); + return; + } + + DCHECK(!type.empty()); + DCHECK(!device.empty()); + + Profile* profile = Profile::FromWebUI(web_ui()); + PrefService* prefs = profile->GetPrefs(); + if (type == kAudio) + prefs->SetString(prefs::kDefaultAudioCaptureDevice, device); + else if (type == kVideo) + prefs->SetString(prefs::kDefaultVideoCaptureDevice, device); + else + NOTREACHED(); +} + +void MediaDevicesSelectionHandler::UpdateDevicesMenu( + DeviceType type, const content::MediaStreamDevices& devices) { + // Get the default device unique id from prefs. + Profile* profile = Profile::FromWebUI(web_ui()); + PrefService* prefs = profile->GetPrefs(); + std::string default_device; + std::string device_type; + switch (type) { + case AUDIO: + default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice); + device_type = kAudio; + break; + case VIDEO: + default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice); + device_type = kVideo; + break; + } + + // Build the list of devices to send to JS. + std::string default_id; + ListValue device_list; + for (size_t i = 0; i < devices.size(); ++i) { + DictionaryValue* entry = new DictionaryValue(); + entry->SetString("name", devices[i].name); + entry->SetString("id", devices[i].device_id); + device_list.Append(entry); + if (devices[i].device_id == default_device) + default_id = default_device; + } + + // Use the first device as the default device if the preferred default device + // does not exist in the OS. + if (!devices.empty() && default_id.empty()) + default_id = devices[0].device_id; + + StringValue default_value(default_id); + StringValue type_value(device_type); + web_ui()->CallJavascriptFunction("ContentSettings.updateDevicesMenu", + type_value, + device_list, + default_value); +} + +void MediaDevicesSelectionHandler::UpdateDevicesMenuForType(DeviceType type) { + scoped_refptr<MediaCaptureDevicesDispatcher> dispatcher = + MediaInternals::GetInstance()->GetMediaCaptureDevicesDispatcher(); + content::MediaStreamDevices devices; + switch (type) { + case AUDIO: + devices = dispatcher->GetAudioCaptureDevices(); + break; + case VIDEO: + devices = dispatcher->GetVideoCaptureDevices(); + break; + } + + UpdateDevicesMenu(type, devices); +} + +} // namespace options diff --git a/chrome/browser/ui/webui/options/media_devices_selection_handler.h b/chrome/browser/ui/webui/options/media_devices_selection_handler.h new file mode 100644 index 0000000..e060872 --- /dev/null +++ b/chrome/browser/ui/webui/options/media_devices_selection_handler.h @@ -0,0 +1,55 @@ +// 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. + +#ifndef CHROME_BROWSER_UI_WEBUI_OPTIONS_MEDIA_DEVICES_SELECTION_HANDLER_H_ +#define CHROME_BROWSER_UI_WEBUI_OPTIONS_MEDIA_DEVICES_SELECTION_HANDLER_H_ + +#include "chrome/browser/media/media_capture_devices_dispatcher.h" +#include "chrome/browser/ui/webui/options/options_ui.h" +#include "content/public/browser/web_contents.h" +#include "grit/generated_resources.h" + +namespace options { + +// Handler for media devices selection in content settings. +class MediaDevicesSelectionHandler + : public MediaCaptureDevicesDispatcher::Observer, + public OptionsPageUIHandler { + public: + MediaDevicesSelectionHandler(); + virtual ~MediaDevicesSelectionHandler(); + + // OptionsPageUIHandler implementation. + virtual void GetLocalizedValues(base::DictionaryValue* values) OVERRIDE; + virtual void InitializePage() OVERRIDE; + virtual void RegisterMessages() OVERRIDE; + + // MediaCaptureDevicesDispatcher::Observer implementation. + virtual void OnUpdateAudioDevices( + const content::MediaStreamDevices& devices) OVERRIDE; + virtual void OnUpdateVideoDevices( + const content::MediaStreamDevices& devices) OVERRIDE; + + private: + enum DeviceType { + AUDIO, + VIDEO, + }; + + // Sets the default audio/video capture device for media. |args| includes the + // media type (kAuudio/kVideo) and the unique id of the new default device + // that the user has chosen. + void SetDefaultCaptureDevice(const base::ListValue* args); + + // Helpers methods to update the device menus. + void UpdateDevicesMenuForType(DeviceType type); + void UpdateDevicesMenu(DeviceType type, + const content::MediaStreamDevices& devices); + + DISALLOW_COPY_AND_ASSIGN(MediaDevicesSelectionHandler); +}; + +} // namespace options + +#endif // CHROME_BROWSER_UI_WEBUI_OPTIONS_MEDIA_DEVICES_SELECTION_HANDLER_H_ diff --git a/chrome/browser/ui/webui/options/options_ui.cc b/chrome/browser/ui/webui/options/options_ui.cc index 3d0b6be..a719353 100644 --- a/chrome/browser/ui/webui/options/options_ui.cc +++ b/chrome/browser/ui/webui/options/options_ui.cc @@ -35,6 +35,7 @@ #include "chrome/browser/ui/webui/options/import_data_handler.h" #include "chrome/browser/ui/webui/options/language_options_handler.h" #include "chrome/browser/ui/webui/options/manage_profile_handler.h" +#include "chrome/browser/ui/webui/options/media_devices_selection_handler.h" #include "chrome/browser/ui/webui/options/media_galleries_handler.h" #include "chrome/browser/ui/webui/options/options_sync_setup_handler.h" #include "chrome/browser/ui/webui/options/password_manager_handler.h" @@ -245,6 +246,8 @@ OptionsUI::OptionsUI(content::WebUI* web_ui) AddOptionsPageUIHandler(localized_strings, new CookiesViewHandler()); AddOptionsPageUIHandler(localized_strings, new FontSettingsHandler()); AddOptionsPageUIHandler(localized_strings, new HomePageOverlayHandler()); + AddOptionsPageUIHandler(localized_strings, + new MediaDevicesSelectionHandler()); AddOptionsPageUIHandler(localized_strings, new MediaGalleriesHandler()); AddOptionsPageUIHandler(localized_strings, new WebIntentsSettingsHandler()); #if defined(OS_CHROMEOS) diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi index 7a4b2b3..f25281e 100644 --- a/chrome/chrome_browser.gypi +++ b/chrome/chrome_browser.gypi @@ -981,6 +981,8 @@ 'browser/managed_mode/managed_mode.h', 'browser/managed_mode/managed_mode_url_filter.cc', 'browser/managed_mode/managed_mode_url_filter.h', + 'browser/media/media_capture_devices_dispatcher.cc', + 'browser/media/media_capture_devices_dispatcher.h', 'browser/media/media_internals.cc', 'browser/media/media_internals.h', 'browser/media/media_stream_capture_indicator.cc', diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi index 46eeb7f0..6b08ef8 100644 --- a/chrome/chrome_browser_ui.gypi +++ b/chrome/chrome_browser_ui.gypi @@ -1992,6 +1992,8 @@ 'browser/ui/webui/options/language_options_handler_common.h', 'browser/ui/webui/options/manage_profile_handler.cc', 'browser/ui/webui/options/manage_profile_handler.h', + 'browser/ui/webui/options/media_devices_selection_handler.cc', + 'browser/ui/webui/options/media_devices_selection_handler.h', 'browser/ui/webui/options/media_galleries_handler.cc', 'browser/ui/webui/options/media_galleries_handler.h', 'browser/ui/webui/options/options_sync_setup_handler.cc', diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc index 07100c6..5e5f6ce 100644 --- a/chrome/common/pref_names.cc +++ b/chrome/common/pref_names.cc @@ -1056,6 +1056,12 @@ const char kGeolocationEnabled[] = "geolocation.enabled"; // Dictionary that maps [frame, toplevel] to their Geolocation content setting. const char kGeolocationContentSettings[] = "geolocation.content_settings"; +// The default audio capture device used by the Media content setting. +const char kDefaultAudioCaptureDevice[] = "media.default_audio_capture_device"; + +// The default video capture device used by the Media content setting. +const char kDefaultVideoCaptureDevice[] = "media.default_video_capture_Device"; + // Preference to disable 3D APIs (WebGL, Pepper 3D). const char kDisable3DAPIs[] = "disable_3d_apis"; diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h index 3af7178..a3e9496 100644 --- a/chrome/common/pref_names.h +++ b/chrome/common/pref_names.h @@ -622,6 +622,9 @@ extern const char kGeolocationContentSettings[]; extern const char kGeolocationEnabled[]; #endif +extern const char kDefaultAudioCaptureDevice[]; +extern const char kDefaultVideoCaptureDevice[]; + extern const char kRemoteAccessHostFirewallTraversal[]; extern const char kRemoteAccessHostRequireTwoFactor[]; extern const char kRemoteAccessHostDomain[]; diff --git a/content/browser/browser_main_loop.cc b/content/browser/browser_main_loop.cc index 777b31a..01dd2e7 100644 --- a/content/browser/browser_main_loop.cc +++ b/content/browser/browser_main_loop.cc @@ -348,7 +348,6 @@ void BrowserMainLoop::MainMessageLoopStart() { } online_state_observer_.reset(new BrowserOnlineStateObserver); - media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); // Prior to any processing happening on the io thread, we create the // plugin service as it is predominantly used from the io thread, @@ -669,6 +668,9 @@ void BrowserMainLoop::BrowserThreadsStarted() { // RDH needs the IO thread to be created. resource_dispatcher_host_.reset(new ResourceDispatcherHostImpl()); + // MediaStreamManager needs the IO thread to be created. + media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); + // Initialize the GpuDataManager before we set up the MessageLoops because // otherwise we'll trigger the assertion about doing IO on the UI thread. GpuDataManagerImpl::GetInstance()->Initialize(); diff --git a/content/browser/renderer_host/media/media_stream_manager.cc b/content/browser/renderer_host/media/media_stream_manager.cc index ca625e4..d040f0a 100644 --- a/content/browser/renderer_host/media/media_stream_manager.cc +++ b/content/browser/renderer_host/media/media_stream_manager.cc @@ -153,6 +153,17 @@ MediaStreamManager::MediaStreamManager(media::AudioManager* audio_manager) DCHECK(audio_manager_); memset(active_enumeration_ref_count_, 0, sizeof(active_enumeration_ref_count_)); + + // Some unit tests create the MSM in the IO thread and assumes the + // initialization is done synchronously. + if (BrowserThread::CurrentlyOn(BrowserThread::IO)) { + InitializeDeviceManagersOnIOThread(); + } else { + BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + base::Bind(&MediaStreamManager::InitializeDeviceManagersOnIOThread, + base::Unretained(this))); + } } MediaStreamManager::~MediaStreamManager() { @@ -163,14 +174,12 @@ MediaStreamManager::~MediaStreamManager() { VideoCaptureManager* MediaStreamManager::video_capture_manager() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - EnsureDeviceManagersStarted(); DCHECK(video_capture_manager_); return video_capture_manager_; } AudioInputDeviceManager* MediaStreamManager::audio_input_device_manager() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - EnsureDeviceManagersStarted(); DCHECK(audio_input_device_manager_); return audio_input_device_manager_; } @@ -392,7 +401,6 @@ void MediaStreamManager::EnumerateDevices( base::Unretained(this), cache, *label)); } else { StartEnumeration(&new_request, label); - StartMonitoring(); } } @@ -403,9 +411,6 @@ void MediaStreamManager::StopEnumerateDevices(const std::string& label) { if (it != requests_.end()) { DCHECK_EQ(it->second.type, DeviceRequest::ENUMERATE_DEVICES); requests_.erase(it); - if (!HasEnumerationRequest()) { - StopMonitoring(); - } } } @@ -476,15 +481,24 @@ void MediaStreamManager::SendCachedDeviceList( void MediaStreamManager::StartMonitoring() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + if (!base::SystemMonitor::Get()) + return; + if (!monitoring_started_) { monitoring_started_ = true; base::SystemMonitor::Get()->AddDevicesChangedObserver(this); + + // Enumerate the devices now to post the device lists to media observer. + ++active_enumeration_ref_count_[MEDIA_DEVICE_AUDIO_CAPTURE]; + audio_input_device_manager_->EnumerateDevices(); + ++active_enumeration_ref_count_[MEDIA_DEVICE_VIDEO_CAPTURE]; + video_capture_manager_->EnumerateDevices(); } } void MediaStreamManager::StopMonitoring() { DCHECK_EQ(MessageLoop::current(), io_loop_); - if (monitoring_started_ && !HasEnumerationRequest()) { + if (monitoring_started_) { base::SystemMonitor::Get()->RemoveDevicesChangedObserver(this); monitoring_started_ = false; ClearEnumerationCache(&audio_enumeration_cache_); @@ -546,7 +560,7 @@ void MediaStreamManager::PostRequestToUI(const std::string& label) { request.security_origin); } -void MediaStreamManager::EnsureDeviceManagersStarted() { +void MediaStreamManager::InitializeDeviceManagersOnIOThread() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (device_thread_.get()) return; @@ -568,6 +582,10 @@ void MediaStreamManager::EnsureDeviceManagersStarted() { // and the device managers. io_loop_ = MessageLoop::current(); io_loop_->AddDestructionObserver(this); + + // Start the devices monitoring since the media observer needs up-to-date + // device lists. + StartMonitoring(); } void MediaStreamManager::Opened(MediaStreamType stream_type, @@ -657,21 +675,22 @@ void MediaStreamManager::DevicesEnumerated( MediaStreamType stream_type, const StreamDeviceInfoArray& devices) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - // Only cache the device list when there is an EnumerateDevices request, since - // other requests don't turn on device monitoring. + // Only cache the device list when the device list has been changed. bool need_update_clients = false; EnumerationCache* cache = (stream_type == MEDIA_DEVICE_AUDIO_CAPTURE ? &audio_enumeration_cache_ : &video_enumeration_cache_); - if (HasEnumerationRequest(stream_type) && - (!cache->valid || + if (!cache->valid || devices.size() != cache->devices.size() || !std::equal(devices.begin(), devices.end(), cache->devices.begin(), - StreamDeviceInfo::IsEqual))) { + StreamDeviceInfo::IsEqual)) { cache->valid = true; cache->devices = devices; need_update_clients = true; } + if (need_update_clients && monitoring_started_) + NotifyDevicesChanged(stream_type, devices); + // Publish the result for all requests waiting for device list(s). // Find the requests waiting for this device list, store their labels and // release the iterator before calling device settings. We might get a call @@ -913,6 +932,32 @@ void MediaStreamManager::DevicesFromRequest( } } +void MediaStreamManager::NotifyDevicesChanged( + MediaStreamType stream_type, + const StreamDeviceInfoArray& devices) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + MediaObserver* media_observer = + GetContentClient()->browser()->GetMediaObserver(); + if (media_observer == NULL) + return; + + // Map the devices to MediaStreamDevices. + MediaStreamDevices new_devices; + for (StreamDeviceInfoArray::const_iterator it = devices.begin(); + it != devices.end(); ++it) { + new_devices.push_back(MediaStreamDevice( + it->stream_type, it->device_id, it->name)); + } + + if (IsAudioMediaType(stream_type)) { + media_observer->OnAudioCaptureDevicesChanged(new_devices); + } else if (IsVideoMediaType(stream_type)) { + media_observer->OnVideoCaptureDevicesChanged(new_devices); + } else { + NOTREACHED(); + } +} + bool MediaStreamManager::RequestDone(const DeviceRequest& request) const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); @@ -968,24 +1013,14 @@ void MediaStreamManager::OnDevicesChanged( // changes (from the operating system). MediaStreamType stream_type; - EnumerationCache* cache; if (device_type == base::SystemMonitor::DEVTYPE_AUDIO_CAPTURE) { stream_type = MEDIA_DEVICE_AUDIO_CAPTURE; - cache = &audio_enumeration_cache_; } else if (device_type == base::SystemMonitor::DEVTYPE_VIDEO_CAPTURE) { stream_type = MEDIA_DEVICE_VIDEO_CAPTURE; - cache = &video_enumeration_cache_; } else { return; // Uninteresting device change. } - if (!HasEnumerationRequest(stream_type)) { - // There is no request for that type, No need to enumerate devices. - // Therefore, invalidate the cache of that type. - ClearEnumerationCache(cache); - return; - } - // Always do enumeration even though some enumeration is in progress, // because those enumeration commands could be sent before these devices // change. @@ -993,28 +1028,4 @@ void MediaStreamManager::OnDevicesChanged( GetDeviceManager(stream_type)->EnumerateDevices(); } -bool MediaStreamManager::HasEnumerationRequest() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - for (DeviceRequests::iterator it = requests_.begin(); - it != requests_.end(); ++it) { - if (it->second.type == DeviceRequest::ENUMERATE_DEVICES) { - return true; - } - } - return false; -} - -bool MediaStreamManager::HasEnumerationRequest( - MediaStreamType stream_type) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - for (DeviceRequests::iterator it = requests_.begin(); - it != requests_.end(); ++it) { - if (it->second.type == DeviceRequest::ENUMERATE_DEVICES && - Requested(it->second.options, stream_type)) { - return true; - } - } - return false; -} - } // namespace content diff --git a/content/browser/renderer_host/media/media_stream_manager.h b/content/browser/renderer_host/media/media_stream_manager.h index 439e30d..3364520 100644 --- a/content/browser/renderer_host/media/media_stream_manager.h +++ b/content/browser/renderer_host/media/media_stream_manager.h @@ -186,6 +186,10 @@ class CONTENT_EXPORT MediaStreamManager StreamDeviceInfoArray devices; }; + // Initializes the device managers on IO thread. Auto-starts the device + // thread and registers this as a listener with the device managers. + void InitializeDeviceManagersOnIOThread(); + // Helpers for signaling the media observer that new capture devices are // opened/closed. void NotifyDevicesOpened(const DeviceRequest& request); @@ -193,21 +197,20 @@ class CONTENT_EXPORT MediaStreamManager void DevicesFromRequest(const DeviceRequest& request, MediaStreamDevices* devices); + // Helper for sending up-to-date device lists to media observer when a + // capture device is plugged in or unplugged. + void NotifyDevicesChanged(MediaStreamType stream_type, + const StreamDeviceInfoArray& devices); + // Helpers. bool RequestDone(const MediaStreamManager::DeviceRequest& request) const; MediaStreamProvider* GetDeviceManager(MediaStreamType stream_type); void StartEnumeration(DeviceRequest* new_request, std::string* label); void AddRequest(const DeviceRequest& new_request, std::string* label); - bool HasEnumerationRequest(MediaStreamType type); - bool HasEnumerationRequest(); void ClearEnumerationCache(EnumerationCache* cache); void PostRequestToUI(const std::string& label); - // Helper to create the device managers, if needed. Auto-starts the device - // thread and registers this as a listener with the device managers. - void EnsureDeviceManagersStarted(); - // Sends cached device list to a client corresponding to the request // identified by |label|. void SendCachedDeviceList(EnumerationCache* cache, const std::string& label); diff --git a/content/browser/renderer_host/media/mock_media_observer.h b/content/browser/renderer_host/media/mock_media_observer.h index 458a396..afcc70f 100644 --- a/content/browser/renderer_host/media/mock_media_observer.h +++ b/content/browser/renderer_host/media/mock_media_observer.h @@ -35,6 +35,10 @@ class MockMediaObserver : public MediaObserver { MOCK_METHOD3(OnCaptureDevicesClosed, void(int render_process_id, int render_view_id, const MediaStreamDevices& devices)); + MOCK_METHOD1(OnAudioCaptureDevicesChanged, + void(const MediaStreamDevices& devices)); + MOCK_METHOD1(OnVideoCaptureDevicesChanged, + void(const MediaStreamDevices& devices)); MOCK_METHOD4(OnMediaRequestStateChanged, void(int render_process_id, int render_view_id, const MediaStreamDevice& device, diff --git a/content/public/browser/media_observer.h b/content/public/browser/media_observer.h index 7e33d89..cfb0667 100644 --- a/content/public/browser/media_observer.h +++ b/content/public/browser/media_observer.h @@ -50,6 +50,14 @@ class MediaObserver { int render_view_id, const MediaStreamDevices& devices) = 0; + // Called when a audio capture device is plugged in or unplugged. + virtual void OnAudioCaptureDevicesChanged( + const MediaStreamDevices& devices) = 0; + + // Called when a video capture device is plugged in or unplugged. + virtual void OnVideoCaptureDevicesChanged( + const MediaStreamDevices& devices) = 0; + // Called when a media request changes state. virtual void OnMediaRequestStateChanged( int render_process_id, diff --git a/content/test/webrtc_audio_device_test.cc b/content/test/webrtc_audio_device_test.cc index 0fc3e2d..78013ad 100644 --- a/content/test/webrtc_audio_device_test.cc +++ b/content/test/webrtc_audio_device_test.cc @@ -132,10 +132,6 @@ void WebRTCAudioDeviceTest::SetUp() { ui_thread_.reset(new TestBrowserThread(BrowserThread::UI, MessageLoop::current())); - // Create our own AudioManager and MediaStreamManager. - audio_manager_.reset(media::AudioManager::Create()); - media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); - // Construct the resource context on the UI thread. resource_context_.reset(new MockRTCResourceContext); @@ -211,6 +207,10 @@ void WebRTCAudioDeviceTest::InitializeIOThread(const char* thread_name) { resource_context->set_request_context(test_request_context_.get()); media_observer_.reset(new MockMediaObserver()); + // Create our own AudioManager and MediaStreamManager. + audio_manager_.reset(media::AudioManager::Create()); + media_stream_manager_.reset(new MediaStreamManager(audio_manager_.get())); + has_input_devices_ = audio_manager_->HasAudioInputDevices(); has_output_devices_ = audio_manager_->HasAudioOutputDevices(); |