| // 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 "base/command_line.h" |
| #include "base/logging.h" |
| #include "base/metrics/field_trial.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/strings/utf_string_conversions.h" |
| #include "build/build_config.h" |
| #include "chrome/browser/media/desktop_streams_registry.h" |
| #include "chrome/browser/media/media_access_handler.h" |
| #include "chrome/browser/media/media_stream_capture_indicator.h" |
| #include "chrome/browser/media/permission_bubble_media_access_handler.h" |
| #include "chrome/browser/profiles/profile.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/pref_registry/pref_registry_syncable.h" |
| #include "components/prefs/pref_service.h" |
| #include "components/prefs/scoped_user_pref_update.h" |
| #include "content/public/browser/browser_thread.h" |
| #include "content/public/browser/media_capture_devices.h" |
| #include "content/public/browser/notification_source.h" |
| #include "content/public/browser/render_frame_host.h" |
| #include "content/public/browser/render_process_host.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/common/media_stream_request.h" |
| #include "extensions/common/constants.h" |
| #include "media/base/media_switches.h" |
| #include "net/base/net_util.h" |
| |
| #if defined(OS_CHROMEOS) |
| #include "ash/shell.h" |
| #endif // defined(OS_CHROMEOS) |
| |
| #if defined(ENABLE_EXTENSIONS) |
| #include "chrome/browser/media/desktop_capture_access_handler.h" |
| #include "chrome/browser/media/extension_media_access_handler.h" |
| #include "chrome/browser/media/tab_capture_access_handler.h" |
| #include "extensions/browser/extension_registry.h" |
| #include "extensions/common/extension.h" |
| #include "extensions/common/permissions/permissions_data.h" |
| #endif |
| |
| using content::BrowserThread; |
| using content::MediaCaptureDevices; |
| using content::MediaStreamDevices; |
| |
| namespace { |
| |
| // 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) { |
| content::MediaStreamDevices::const_iterator iter = devices.begin(); |
| for (; iter != devices.end(); ++iter) { |
| if (iter->id == device_id) { |
| return &(*iter); |
| } |
| } |
| return NULL; |
| } |
| |
| #if defined(ENABLE_EXTENSIONS) |
| inline DesktopCaptureAccessHandler* ToDesktopCaptureAccessHandler( |
| MediaAccessHandler* handler) { |
| return static_cast<DesktopCaptureAccessHandler*>(handler); |
| } |
| #endif |
| } // namespace |
| |
| MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() { |
| return base::Singleton<MediaCaptureDevicesDispatcher>::get(); |
| } |
| |
| MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() |
| : is_device_enumeration_disabled_(false), |
| media_stream_capture_indicator_(new MediaStreamCaptureIndicator()) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| #if defined(OS_MACOSX) |
| // AVFoundation is used for video/audio device monitoring and video capture. |
| if (!base::CommandLine::ForCurrentProcess()->HasSwitch( |
| switches::kForceQTKit)) { |
| base::CommandLine::ForCurrentProcess()->AppendSwitch( |
| switches::kEnableAVFoundation); |
| } |
| #endif |
| |
| #if defined(ENABLE_EXTENSIONS) |
| media_access_handlers_.push_back(new ExtensionMediaAccessHandler()); |
| media_access_handlers_.push_back(new DesktopCaptureAccessHandler()); |
| media_access_handlers_.push_back(new TabCaptureAccessHandler()); |
| #endif |
| media_access_handlers_.push_back(new PermissionBubbleMediaAccessHandler()); |
| } |
| |
| MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} |
| |
| void MediaCaptureDevicesDispatcher::RegisterProfilePrefs( |
| user_prefs::PrefRegistrySyncable* registry) { |
| registry->RegisterStringPref(prefs::kDefaultAudioCaptureDevice, |
| std::string()); |
| registry->RegisterStringPref(prefs::kDefaultVideoCaptureDevice, |
| std::string()); |
| } |
| |
| bool MediaCaptureDevicesDispatcher::IsOriginForCasting(const GURL& origin) { |
| // Whitelisted tab casting extensions. |
| return |
| // Dev |
| origin.spec() == "chrome-extension://enhhojjnijigcajfphajepfemndkmdlo/" || |
| // Canary |
| origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" || |
| // Beta (internal) |
| origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" || |
| // Google Cast Beta |
| origin.spec() == "chrome-extension://dliochdbjfkdbacpmhlcpmleaejidimm/" || |
| // Google Cast Stable |
| origin.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" || |
| // http://crbug.com/457908 |
| origin.spec() == "chrome-extension://ekpaaapppgpmolpcldedioblbkmijaca/" || |
| // http://crbug.com/574889 |
| origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/"; |
| } |
| |
| void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (!observers_.HasObserver(observer)) |
| observers_.AddObserver(observer); |
| } |
| |
| void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| observers_.RemoveObserver(observer); |
| } |
| |
| const MediaStreamDevices& |
| MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (is_device_enumeration_disabled_ || !test_audio_devices_.empty()) |
| return test_audio_devices_; |
| |
| return MediaCaptureDevices::GetInstance()->GetAudioCaptureDevices(); |
| } |
| |
| const MediaStreamDevices& |
| MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| if (is_device_enumeration_disabled_ || !test_video_devices_.empty()) |
| return test_video_devices_; |
| |
| return MediaCaptureDevices::GetInstance()->GetVideoCaptureDevices(); |
| } |
| |
| void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest( |
| content::WebContents* web_contents, |
| const content::MediaStreamRequest& request, |
| const content::MediaResponseCallback& callback, |
| const extensions::Extension* extension) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| |
| for (MediaAccessHandler* handler : media_access_handlers_) { |
| if (handler->SupportsStreamType(request.video_type, extension) || |
| handler->SupportsStreamType(request.audio_type, extension)) { |
| handler->HandleRequest(web_contents, request, callback, extension); |
| return; |
| } |
| } |
| callback.Run(content::MediaStreamDevices(), |
| content::MEDIA_DEVICE_NOT_SUPPORTED, nullptr); |
| } |
| |
| bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission( |
| content::WebContents* web_contents, |
| const GURL& security_origin, |
| content::MediaStreamType type) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| return CheckMediaAccessPermission(web_contents, security_origin, type, |
| nullptr); |
| } |
| |
| bool MediaCaptureDevicesDispatcher::CheckMediaAccessPermission( |
| content::WebContents* web_contents, |
| const GURL& security_origin, |
| content::MediaStreamType type, |
| const extensions::Extension* extension) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| for (MediaAccessHandler* handler : media_access_handlers_) { |
| if (handler->SupportsStreamType(type, extension)) { |
| return handler->CheckMediaAccessPermission(web_contents, security_origin, |
| type, extension); |
| } |
| } |
| return false; |
| } |
| |
| void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile( |
| Profile* profile, |
| bool audio, |
| bool video, |
| content::MediaStreamDevices* devices) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| DCHECK(audio || video); |
| |
| PrefService* prefs = profile->GetPrefs(); |
| std::string default_device; |
| if (audio) { |
| default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice); |
| 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); |
| const content::MediaStreamDevice* device = |
| GetRequestedVideoDevice(default_device); |
| if (!device) |
| device = GetFirstAvailableVideoDevice(); |
| if (device) |
| devices->push_back(*device); |
| } |
| } |
| |
| const content::MediaStreamDevice* |
| MediaCaptureDevicesDispatcher::GetRequestedAudioDevice( |
| const std::string& requested_audio_device_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); |
| const content::MediaStreamDevice* const device = |
| FindDeviceWithId(audio_devices, requested_audio_device_id); |
| return device; |
| } |
| |
| const content::MediaStreamDevice* |
| MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() { |
| DCHECK_CURRENTLY_ON(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_CURRENTLY_ON(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_CURRENTLY_ON(BrowserThread::UI); |
| const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); |
| if (video_devices.empty()) |
| return NULL; |
| return &(*video_devices.begin()); |
| } |
| |
| void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() { |
| is_device_enumeration_disabled_ = true; |
| } |
| |
| scoped_refptr<MediaStreamCaptureIndicator> |
| MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() { |
| return media_stream_capture_indicator_; |
| } |
| |
| DesktopStreamsRegistry* |
| MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() { |
| if (!desktop_streams_registry_) |
| desktop_streams_registry_.reset(new DesktopStreamsRegistry()); |
| return desktop_streams_registry_.get(); |
| } |
| |
| void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread, |
| base::Unretained(this))); |
| } |
| |
| void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged() { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread, |
| base::Unretained(this))); |
| } |
| |
| void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged( |
| int render_process_id, |
| int render_frame_id, |
| int page_request_id, |
| const GURL& security_origin, |
| content::MediaStreamType stream_type, |
| content::MediaRequestState state) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread, |
| base::Unretained(this), render_process_id, render_frame_id, |
| page_request_id, security_origin, stream_type, state)); |
| } |
| |
| void MediaCaptureDevicesDispatcher::OnCreatingAudioStream( |
| int render_process_id, |
| int render_frame_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::IO); |
| BrowserThread::PostTask( |
| BrowserThread::UI, FROM_HERE, |
| base::Bind( |
| &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread, |
| base::Unretained(this), render_process_id, render_frame_id)); |
| } |
| |
| void MediaCaptureDevicesDispatcher::NotifyAudioDevicesChangedOnUIThread() { |
| MediaStreamDevices devices = GetAudioCaptureDevices(); |
| FOR_EACH_OBSERVER(Observer, observers_, |
| OnUpdateAudioDevices(devices)); |
| } |
| |
| void MediaCaptureDevicesDispatcher::NotifyVideoDevicesChangedOnUIThread() { |
| MediaStreamDevices devices = GetVideoCaptureDevices(); |
| FOR_EACH_OBSERVER(Observer, observers_, |
| OnUpdateVideoDevices(devices)); |
| } |
| |
| void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread( |
| int render_process_id, |
| int render_frame_id, |
| int page_request_id, |
| const GURL& security_origin, |
| content::MediaStreamType stream_type, |
| content::MediaRequestState state) { |
| for (MediaAccessHandler* handler : media_access_handlers_) { |
| if (handler->SupportsStreamType(stream_type, nullptr)) { |
| handler->UpdateMediaRequestState(render_process_id, render_frame_id, |
| page_request_id, stream_type, state); |
| break; |
| } |
| } |
| |
| #if defined(OS_CHROMEOS) |
| if (IsOriginForCasting(security_origin) && IsVideoMediaType(stream_type)) { |
| // Notify ash that casting state has changed. |
| if (state == content::MEDIA_REQUEST_STATE_DONE) { |
| ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true); |
| } else if (state == content::MEDIA_REQUEST_STATE_CLOSING) { |
| ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false); |
| } |
| } |
| #endif |
| |
| FOR_EACH_OBSERVER(Observer, observers_, |
| OnRequestUpdate(render_process_id, |
| render_frame_id, |
| stream_type, |
| state)); |
| } |
| |
| void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread( |
| int render_process_id, |
| int render_frame_id) { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| FOR_EACH_OBSERVER(Observer, observers_, |
| OnCreatingAudioStream(render_process_id, render_frame_id)); |
| } |
| |
| bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() { |
| DCHECK_CURRENTLY_ON(BrowserThread::UI); |
| #if defined(ENABLE_EXTENSIONS) |
| for (MediaAccessHandler* handler : media_access_handlers_) { |
| if (handler->SupportsStreamType(content::MEDIA_DESKTOP_VIDEO_CAPTURE, |
| NULL)) { |
| return ToDesktopCaptureAccessHandler(handler)->IsCaptureInProgress(); |
| } |
| } |
| #endif |
| return false; |
| } |
| |
| void MediaCaptureDevicesDispatcher::SetTestAudioCaptureDevices( |
| const MediaStreamDevices& devices) { |
| test_audio_devices_ = devices; |
| } |
| |
| void MediaCaptureDevicesDispatcher::SetTestVideoCaptureDevices( |
| const MediaStreamDevices& devices) { |
| test_video_devices_ = devices; |
| } |