1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "chrome/browser/media/media_capture_devices_dispatcher.h"
7 #include "base/command_line.h"
8 #include "base/logging.h"
9 #include "base/prefs/pref_service.h"
10 #include "base/prefs/scoped_user_pref_update.h"
11 #include "base/sha1.h"
12 #include "base/strings/string_number_conversions.h"
13 #include "base/strings/string_util.h"
14 #include "base/strings/utf_string_conversions.h"
15 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h"
16 #include "chrome/browser/media/audio_stream_indicator.h"
17 #include "chrome/browser/media/desktop_streams_registry.h"
18 #include "chrome/browser/media/media_stream_capture_indicator.h"
19 #include "chrome/browser/media/media_stream_infobar_delegate.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/browser/ui/screen_capture_notification_ui.h"
22 #include "chrome/browser/ui/simple_message_box.h"
23 #include "chrome/common/chrome_switches.h"
24 #include "chrome/common/pref_names.h"
25 #include "components/user_prefs/pref_registry_syncable.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "content/public/browser/desktop_media_id.h"
28 #include "content/public/browser/media_devices_monitor.h"
29 #include "content/public/browser/notification_service.h"
30 #include "content/public/browser/notification_source.h"
31 #include "content/public/browser/notification_types.h"
32 #include "content/public/browser/web_contents.h"
33 #include "content/public/common/media_stream_request.h"
34 #include "extensions/common/constants.h"
35 #include "extensions/common/extension.h"
36 #include "grit/generated_resources.h"
37 #include "media/audio/audio_manager_base.h"
38 #include "ui/base/l10n/l10n_util.h"
40 #if defined(OS_CHROMEOS)
41 #include "ash/shell.h"
42 #endif // defined(OS_CHROMEOS)
44 using content::BrowserThread
;
45 using content::MediaStreamDevices
;
49 // Finds a device in |devices| that has |device_id|, or NULL if not found.
50 const content::MediaStreamDevice
* FindDeviceWithId(
51 const content::MediaStreamDevices
& devices
,
52 const std::string
& device_id
) {
53 content::MediaStreamDevices::const_iterator iter
= devices
.begin();
54 for (; iter
!= devices
.end(); ++iter
) {
55 if (iter
->id
== device_id
) {
62 // This is a short-term solution to grant camera and/or microphone access to
64 // 1. Virtual keyboard extension.
65 // 2. Google Voice Search Hotword extension.
66 // 3. Flutter gesture recognition extension.
67 // 4. TODO(smus): Airbender experiment 1.
68 // 5. TODO(smus): Airbender experiment 2.
69 // Once http://crbug.com/292856 is fixed, remove this whitelist.
70 bool IsMediaRequestWhitelistedForExtension(
71 const extensions::Extension
* extension
) {
72 return extension
->id() == "mppnpdlheglhdfmldimlhpnegondlapf" ||
73 extension
->id() == "bepbmhgboaologfdajaanbcjmnhjmhfn" ||
74 extension
->id() == "jokbpnebhdcladagohdnfgjcpejggllo" ||
75 extension
->id() == "clffjmdilanldobdnedchkdbofoimcgb" ||
76 extension
->id() == "nnckehldicaciogcbchegobnafnjkcne";
79 // This is a short-term solution to allow testing of the the Screen Capture API
80 // with Google Hangouts in M27.
81 // TODO(sergeyu): Remove this whitelist as soon as possible.
82 bool IsOriginWhitelistedForScreenCapture(const GURL
& origin
) {
83 #if defined(OFFICIAL_BUILD)
84 if (// Google Hangouts.
85 (origin
.SchemeIs("https") &&
86 EndsWith(origin
.spec(), ".talkgadget.google.com/", true)) ||
87 origin
.spec() == "https://talkgadget.google.com/" ||
88 origin
.spec() == "https://plus.google.com/" ||
89 origin
.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
90 origin
.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
91 origin
.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
92 origin
.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/" ||
93 origin
.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/") {
96 // Check against hashed origins.
97 // TODO(hshi): remove this when trusted tester becomes public.
98 const std::string origin_hash
= base::SHA1HashString(origin
.spec());
99 DCHECK_EQ(origin_hash
.length(), base::kSHA1Length
);
100 const std::string hexencoded_origin_hash
=
101 base::HexEncode(origin_hash
.data(), origin_hash
.length());
103 hexencoded_origin_hash
== "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE";
109 #if defined(OS_CHROMEOS)
110 // Returns true of the security origin is associated with casting.
111 bool IsOriginForCasting(const GURL
& origin
) {
112 #if defined(OFFICIAL_BUILD)
113 // Whitelisted tab casting extensions.
114 if (origin
.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" ||
115 origin
.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" ||
116 origin
.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" ||
117 origin
.spec() == "chrome-extension://boadgeojelhgndaghljhdicfkmllpafd/") {
120 // Check against hashed origins.
121 // TODO(hshi): remove this when trusted tester becomes public.
122 const std::string origin_hash
= base::SHA1HashString(origin
.spec());
123 DCHECK_EQ(origin_hash
.length(), base::kSHA1Length
);
124 const std::string hexencoded_origin_hash
=
125 base::HexEncode(origin_hash
.data(), origin_hash
.length());
127 hexencoded_origin_hash
== "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE";
134 // Helper to get title of the calling application shown in the screen capture
136 base::string16
GetApplicationTitle(content::WebContents
* web_contents
,
137 const extensions::Extension
* extension
) {
138 // Use extension name as title for extensions and origin for drive-by web.
141 title
= extension
->name();
143 title
= web_contents
->GetURL().GetOrigin().spec();
145 return base::UTF8ToUTF16(title
);
148 // Helper to get list of media stream devices for desktop capture in |devices|.
149 // Registers to display notification if |display_notification| is true.
150 // Returns an instance of MediaStreamUI to be passed to content layer.
151 scoped_ptr
<content::MediaStreamUI
> GetDevicesForDesktopCapture(
152 content::MediaStreamDevices
& devices
,
153 content::DesktopMediaID media_id
,
155 bool display_notification
,
156 base::string16 application_title
) {
157 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
158 scoped_ptr
<content::MediaStreamUI
> ui
;
160 // Add selected desktop source to the list.
161 devices
.push_back(content::MediaStreamDevice(
162 content::MEDIA_DESKTOP_VIDEO_CAPTURE
, media_id
.ToString(), "Screen"));
164 // Use the special loopback device ID for system audio capture.
165 devices
.push_back(content::MediaStreamDevice(
166 content::MEDIA_LOOPBACK_AUDIO_CAPTURE
,
167 media::AudioManagerBase::kLoopbackInputDeviceId
, "System Audio"));
170 // If required, register to display the notification for stream capture.
171 if (display_notification
) {
172 ui
= ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16(
173 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT
,
182 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest(
183 const content::MediaStreamRequest
& request
,
184 const content::MediaResponseCallback
& callback
)
189 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {}
191 MediaCaptureDevicesDispatcher
* MediaCaptureDevicesDispatcher::GetInstance() {
192 return Singleton
<MediaCaptureDevicesDispatcher
>::get();
195 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher()
196 : devices_enumerated_(false),
197 is_device_enumeration_disabled_(false),
198 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()),
199 audio_stream_indicator_(new AudioStreamIndicator()) {
200 // MediaCaptureDevicesDispatcher is a singleton. It should be created on
201 // UI thread. Otherwise, it will not receive
202 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in
203 // possible use after free.
204 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
205 notifications_registrar_
.Add(
206 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED
,
207 content::NotificationService::AllSources());
210 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {}
212 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs(
213 user_prefs::PrefRegistrySyncable
* registry
) {
214 registry
->RegisterStringPref(
215 prefs::kDefaultAudioCaptureDevice
,
217 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
218 registry
->RegisterStringPref(
219 prefs::kDefaultVideoCaptureDevice
,
221 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF
);
224 void MediaCaptureDevicesDispatcher::AddObserver(Observer
* observer
) {
225 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
226 if (!observers_
.HasObserver(observer
))
227 observers_
.AddObserver(observer
);
230 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer
* observer
) {
231 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
232 observers_
.RemoveObserver(observer
);
235 const MediaStreamDevices
&
236 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() {
237 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
238 if (!is_device_enumeration_disabled_
&& !devices_enumerated_
) {
239 content::EnsureMonitorCaptureDevices();
240 devices_enumerated_
= true;
242 return audio_devices_
;
245 const MediaStreamDevices
&
246 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() {
247 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
248 if (!is_device_enumeration_disabled_
&& !devices_enumerated_
) {
249 content::EnsureMonitorCaptureDevices();
250 devices_enumerated_
= true;
252 return video_devices_
;
255 void MediaCaptureDevicesDispatcher::Observe(
257 const content::NotificationSource
& source
,
258 const content::NotificationDetails
& details
) {
259 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
260 if (type
== content::NOTIFICATION_WEB_CONTENTS_DESTROYED
) {
261 content::WebContents
* web_contents
=
262 content::Source
<content::WebContents
>(source
).ptr();
263 pending_requests_
.erase(web_contents
);
267 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest(
268 content::WebContents
* web_contents
,
269 const content::MediaStreamRequest
& request
,
270 const content::MediaResponseCallback
& callback
,
271 const extensions::Extension
* extension
) {
272 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
274 if (request
.video_type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
||
275 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
) {
276 ProcessDesktopCaptureAccessRequest(
277 web_contents
, request
, callback
, extension
);
278 } else if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
||
279 request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
) {
280 ProcessTabCaptureAccessRequest(
281 web_contents
, request
, callback
, extension
);
282 } else if (extension
&& (extension
->is_platform_app() ||
283 IsMediaRequestWhitelistedForExtension(extension
))) {
284 // For extensions access is approved based on extension permissions.
285 ProcessMediaAccessRequestFromPlatformAppOrExtension(
286 web_contents
, request
, callback
, extension
);
288 ProcessRegularMediaAccessRequest(web_contents
, request
, callback
);
292 void MediaCaptureDevicesDispatcher::ProcessDesktopCaptureAccessRequest(
293 content::WebContents
* web_contents
,
294 const content::MediaStreamRequest
& request
,
295 const content::MediaResponseCallback
& callback
,
296 const extensions::Extension
* extension
) {
297 content::MediaStreamDevices devices
;
298 scoped_ptr
<content::MediaStreamUI
> ui
;
300 if (request
.video_type
!= content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
301 callback
.Run(devices
, ui
.Pass());
305 // If the device id wasn't specified then this is a screen capture request
306 // (i.e. chooseDesktopMedia() API wasn't used to generate device id).
307 if (request
.requested_video_device_id
.empty()) {
308 ProcessScreenCaptureAccessRequest(
309 web_contents
, request
, callback
, extension
);
313 // Resolve DesktopMediaID for the specified device id.
314 content::DesktopMediaID media_id
=
315 GetDesktopStreamsRegistry()->RequestMediaForStreamId(
316 request
.requested_video_device_id
, request
.render_process_id
,
317 request
.render_view_id
, request
.security_origin
);
319 // Received invalid device id.
320 if (media_id
.type
== content::DesktopMediaID::TYPE_NONE
) {
321 callback
.Run(devices
, ui
.Pass());
325 bool loopback_audio_supported
= false;
326 #if defined(USE_CRAS) || defined(OS_WIN)
327 // Currently loopback audio capture is supported only on Windows and ChromeOS.
328 loopback_audio_supported
= true;
331 // Audio is only supported for screen capture streams.
333 (media_id
.type
== content::DesktopMediaID::TYPE_SCREEN
&&
334 request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
335 loopback_audio_supported
);
337 ui
= GetDevicesForDesktopCapture(
338 devices
, media_id
, capture_audio
, true,
339 GetApplicationTitle(web_contents
, extension
));
341 callback
.Run(devices
, ui
.Pass());
344 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest(
345 content::WebContents
* web_contents
,
346 const content::MediaStreamRequest
& request
,
347 const content::MediaResponseCallback
& callback
,
348 const extensions::Extension
* extension
) {
349 content::MediaStreamDevices devices
;
350 scoped_ptr
<content::MediaStreamUI
> ui
;
352 DCHECK_EQ(request
.video_type
, content::MEDIA_DESKTOP_VIDEO_CAPTURE
);
354 bool loopback_audio_supported
= false;
355 #if defined(USE_CRAS) || defined(OS_WIN)
356 // Currently loopback audio capture is supported only on Windows and ChromeOS.
357 loopback_audio_supported
= true;
360 const bool component_extension
=
361 extension
&& extension
->location() == extensions::Manifest::COMPONENT
;
363 const bool screen_capture_enabled
=
364 CommandLine::ForCurrentProcess()->HasSwitch(
365 switches::kEnableUserMediaScreenCapturing
) ||
366 IsOriginWhitelistedForScreenCapture(request
.security_origin
);
368 const bool origin_is_secure
=
369 request
.security_origin
.SchemeIsSecure() ||
370 request
.security_origin
.SchemeIs(extensions::kExtensionScheme
) ||
371 CommandLine::ForCurrentProcess()->HasSwitch(
372 switches::kAllowHttpScreenCapture
);
374 // Approve request only when the following conditions are met:
375 // 1. Screen capturing is enabled via command line switch or white-listed for
377 // 2. Request comes from a page with a secure origin or from an extension.
378 if (screen_capture_enabled
&& origin_is_secure
) {
379 // Get title of the calling application prior to showing the message box.
380 // chrome::ShowMessageBox() starts a nested message loop which may allow
381 // |web_contents| to be destroyed on the UI thread before the message box
382 // is closed. See http://crbug.com/326690.
383 base::string16 application_title
=
384 GetApplicationTitle(web_contents
, extension
);
387 // For component extensions, bypass message box.
388 bool user_approved
= false;
389 if (!component_extension
) {
390 base::string16 application_name
= base::UTF8ToUTF16(
391 extension
? extension
->name() : request
.security_origin
.spec());
392 base::string16 confirmation_text
= l10n_util::GetStringFUTF16(
393 request
.audio_type
== content::MEDIA_NO_SERVICE
?
394 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT
:
395 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT
,
397 chrome::MessageBoxResult result
= chrome::ShowMessageBox(
399 l10n_util::GetStringFUTF16(
400 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE
, application_name
),
402 chrome::MESSAGE_BOX_TYPE_QUESTION
);
403 user_approved
= (result
== chrome::MESSAGE_BOX_RESULT_YES
);
406 if (user_approved
|| component_extension
) {
407 content::DesktopMediaID screen_id
;
408 #if defined(OS_CHROMEOS)
409 screen_id
= content::DesktopMediaID::RegisterAuraWindow(
410 ash::Shell::GetInstance()->GetPrimaryRootWindow());
411 #else // defined(OS_CHROMEOS)
413 content::DesktopMediaID(content::DesktopMediaID::TYPE_SCREEN
, 0);
414 #endif // !defined(OS_CHROMEOS)
417 (request
.audio_type
== content::MEDIA_LOOPBACK_AUDIO_CAPTURE
&&
418 loopback_audio_supported
);
420 // Unless we're being invoked from a component extension, register to
421 // display the notification for stream capture.
422 bool display_notification
= !component_extension
;
424 ui
= GetDevicesForDesktopCapture(devices
, screen_id
, capture_audio
,
425 display_notification
, application_title
);
429 callback
.Run(devices
, ui
.Pass());
432 void MediaCaptureDevicesDispatcher::ProcessTabCaptureAccessRequest(
433 content::WebContents
* web_contents
,
434 const content::MediaStreamRequest
& request
,
435 const content::MediaResponseCallback
& callback
,
436 const extensions::Extension
* extension
) {
437 content::MediaStreamDevices devices
;
438 scoped_ptr
<content::MediaStreamUI
> ui
;
440 #if defined(OS_ANDROID)
441 // Tab capture is not supported on Android.
442 callback
.Run(devices
, ui
.Pass());
443 #else // defined(OS_ANDROID)
445 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
446 extensions::TabCaptureRegistry
* tab_capture_registry
=
447 extensions::TabCaptureRegistry::Get(profile
);
448 if (!tab_capture_registry
) {
450 callback
.Run(devices
, ui
.Pass());
453 bool tab_capture_allowed
=
454 tab_capture_registry
->VerifyRequest(request
.render_process_id
,
455 request
.render_view_id
);
457 if (request
.audio_type
== content::MEDIA_TAB_AUDIO_CAPTURE
&&
458 tab_capture_allowed
&&
459 extension
->HasAPIPermission(extensions::APIPermission::kTabCapture
)) {
460 devices
.push_back(content::MediaStreamDevice(
461 content::MEDIA_TAB_AUDIO_CAPTURE
, std::string(), std::string()));
464 if (request
.video_type
== content::MEDIA_TAB_VIDEO_CAPTURE
&&
465 tab_capture_allowed
&&
466 extension
->HasAPIPermission(extensions::APIPermission::kTabCapture
)) {
467 devices
.push_back(content::MediaStreamDevice(
468 content::MEDIA_TAB_VIDEO_CAPTURE
, std::string(), std::string()));
471 if (!devices
.empty()) {
472 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
473 web_contents
, devices
);
475 callback
.Run(devices
, ui
.Pass());
476 #endif // !defined(OS_ANDROID)
479 void MediaCaptureDevicesDispatcher::
480 ProcessMediaAccessRequestFromPlatformAppOrExtension(
481 content::WebContents
* web_contents
,
482 const content::MediaStreamRequest
& request
,
483 const content::MediaResponseCallback
& callback
,
484 const extensions::Extension
* extension
) {
485 content::MediaStreamDevices devices
;
487 Profile::FromBrowserContext(web_contents
->GetBrowserContext());
489 if (request
.audio_type
== content::MEDIA_DEVICE_AUDIO_CAPTURE
&&
490 extension
->HasAPIPermission(extensions::APIPermission::kAudioCapture
)) {
491 GetDefaultDevicesForProfile(profile
, true, false, &devices
);
494 if (request
.video_type
== content::MEDIA_DEVICE_VIDEO_CAPTURE
&&
495 extension
->HasAPIPermission(extensions::APIPermission::kVideoCapture
)) {
496 GetDefaultDevicesForProfile(profile
, false, true, &devices
);
499 scoped_ptr
<content::MediaStreamUI
> ui
;
500 if (!devices
.empty()) {
501 ui
= media_stream_capture_indicator_
->RegisterMediaStream(
502 web_contents
, devices
);
504 callback
.Run(devices
, ui
.Pass());
507 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest(
508 content::WebContents
* web_contents
,
509 const content::MediaStreamRequest
& request
,
510 const content::MediaResponseCallback
& callback
) {
511 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
513 RequestsQueue
& queue
= pending_requests_
[web_contents
];
514 queue
.push_back(PendingAccessRequest(request
, callback
));
516 // If this is the only request then show the infobar.
517 if (queue
.size() == 1)
518 ProcessQueuedAccessRequest(web_contents
);
521 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest(
522 content::WebContents
* web_contents
) {
523 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
525 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
526 pending_requests_
.find(web_contents
);
528 if (it
== pending_requests_
.end() || it
->second
.empty()) {
529 // Don't do anything if the tab was was closed.
533 DCHECK(!it
->second
.empty());
535 MediaStreamInfoBarDelegate::Create(
536 web_contents
, it
->second
.front().request
,
537 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse
,
538 base::Unretained(this), web_contents
));
541 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse(
542 content::WebContents
* web_contents
,
543 const content::MediaStreamDevices
& devices
,
544 scoped_ptr
<content::MediaStreamUI
> ui
) {
545 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
547 std::map
<content::WebContents
*, RequestsQueue
>::iterator it
=
548 pending_requests_
.find(web_contents
);
549 if (it
== pending_requests_
.end()) {
550 // WebContents has been destroyed. Don't need to do anything.
554 RequestsQueue
& queue(it
->second
);
558 content::MediaResponseCallback callback
= queue
.front().callback
;
561 if (!queue
.empty()) {
562 // Post a task to process next queued request. It has to be done
563 // asynchronously to make sure that calling infobar is not destroyed until
564 // after this function returns.
565 BrowserThread::PostTask(
566 BrowserThread::UI
, FROM_HERE
,
567 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest
,
568 base::Unretained(this), web_contents
));
571 callback
.Run(devices
, ui
.Pass());
574 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile(
578 content::MediaStreamDevices
* devices
) {
579 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
580 DCHECK(audio
|| video
);
582 PrefService
* prefs
= profile
->GetPrefs();
583 std::string default_device
;
585 default_device
= prefs
->GetString(prefs::kDefaultAudioCaptureDevice
);
586 const content::MediaStreamDevice
* device
=
587 GetRequestedAudioDevice(default_device
);
589 device
= GetFirstAvailableAudioDevice();
591 devices
->push_back(*device
);
595 default_device
= prefs
->GetString(prefs::kDefaultVideoCaptureDevice
);
596 const content::MediaStreamDevice
* device
=
597 GetRequestedVideoDevice(default_device
);
599 device
= GetFirstAvailableVideoDevice();
601 devices
->push_back(*device
);
605 const content::MediaStreamDevice
*
606 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice(
607 const std::string
& requested_audio_device_id
) {
608 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
609 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
610 const content::MediaStreamDevice
* const device
=
611 FindDeviceWithId(audio_devices
, requested_audio_device_id
);
615 const content::MediaStreamDevice
*
616 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() {
617 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
618 const content::MediaStreamDevices
& audio_devices
= GetAudioCaptureDevices();
619 if (audio_devices
.empty())
621 return &(*audio_devices
.begin());
624 const content::MediaStreamDevice
*
625 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice(
626 const std::string
& requested_video_device_id
) {
627 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
628 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
629 const content::MediaStreamDevice
* const device
=
630 FindDeviceWithId(video_devices
, requested_video_device_id
);
634 const content::MediaStreamDevice
*
635 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() {
636 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
637 const content::MediaStreamDevices
& video_devices
= GetVideoCaptureDevices();
638 if (video_devices
.empty())
640 return &(*video_devices
.begin());
643 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() {
644 is_device_enumeration_disabled_
= true;
647 scoped_refptr
<MediaStreamCaptureIndicator
>
648 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() {
649 return media_stream_capture_indicator_
;
652 scoped_refptr
<AudioStreamIndicator
>
653 MediaCaptureDevicesDispatcher::GetAudioStreamIndicator() {
654 return audio_stream_indicator_
;
657 DesktopStreamsRegistry
*
658 MediaCaptureDevicesDispatcher::GetDesktopStreamsRegistry() {
659 if (!desktop_streams_registry_
)
660 desktop_streams_registry_
.reset(new DesktopStreamsRegistry());
661 return desktop_streams_registry_
.get();
664 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged(
665 const content::MediaStreamDevices
& devices
) {
666 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
667 BrowserThread::PostTask(
668 BrowserThread::UI
, FROM_HERE
,
669 base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread
,
670 base::Unretained(this), devices
));
673 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged(
674 const content::MediaStreamDevices
& devices
) {
675 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
676 BrowserThread::PostTask(
677 BrowserThread::UI
, FROM_HERE
,
678 base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread
,
679 base::Unretained(this), devices
));
682 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged(
683 int render_process_id
,
686 const GURL
& security_origin
,
687 const content::MediaStreamDevice
& device
,
688 content::MediaRequestState state
) {
689 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
690 BrowserThread::PostTask(
691 BrowserThread::UI
, FROM_HERE
,
693 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread
,
694 base::Unretained(this), render_process_id
, render_view_id
,
695 page_request_id
, security_origin
, device
, state
));
698 void MediaCaptureDevicesDispatcher::OnAudioStreamPlayingChanged(
699 int render_process_id
, int render_view_id
, int stream_id
,
700 bool is_playing
, float power_dbfs
, bool clipped
) {
701 audio_stream_indicator_
->UpdateWebContentsStatus(
702 render_process_id
, render_view_id
, stream_id
,
703 is_playing
, power_dbfs
, clipped
);
706 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream(
707 int render_process_id
,
708 int render_view_id
) {
709 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO
));
710 BrowserThread::PostTask(
711 BrowserThread::UI
, FROM_HERE
,
713 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread
,
714 base::Unretained(this), render_process_id
, render_view_id
));
717 void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread(
718 const content::MediaStreamDevices
& devices
) {
719 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
720 devices_enumerated_
= true;
721 audio_devices_
= devices
;
722 FOR_EACH_OBSERVER(Observer
, observers_
,
723 OnUpdateAudioDevices(audio_devices_
));
726 void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread(
727 const content::MediaStreamDevices
& devices
) {
728 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
729 devices_enumerated_
= true;
730 video_devices_
= devices
;
731 FOR_EACH_OBSERVER(Observer
, observers_
,
732 OnUpdateVideoDevices(video_devices_
));
735 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread(
736 int render_process_id
,
739 const GURL
& security_origin
,
740 const content::MediaStreamDevice
& device
,
741 content::MediaRequestState state
) {
742 // Track desktop capture sessions. Tracking is necessary to avoid unbalanced
743 // session counts since not all requests will reach MEDIA_REQUEST_STATE_DONE,
744 // but they will all reach MEDIA_REQUEST_STATE_CLOSING.
745 if (device
.type
== content::MEDIA_DESKTOP_VIDEO_CAPTURE
) {
746 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
747 DesktopCaptureSession session
= { render_process_id
, render_view_id
,
749 desktop_capture_sessions_
.push_back(session
);
750 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
751 for (DesktopCaptureSessions::iterator it
=
752 desktop_capture_sessions_
.begin();
753 it
!= desktop_capture_sessions_
.end();
755 if (it
->render_process_id
== render_process_id
&&
756 it
->render_view_id
== render_view_id
&&
757 it
->page_request_id
== page_request_id
) {
758 desktop_capture_sessions_
.erase(it
);
765 // Cancel the request.
766 if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
768 for (RequestsQueues::iterator rqs_it
= pending_requests_
.begin();
769 rqs_it
!= pending_requests_
.end(); ++rqs_it
) {
770 RequestsQueue
& queue
= rqs_it
->second
;
771 for (RequestsQueue::iterator it
= queue
.begin();
772 it
!= queue
.end(); ++it
) {
773 if (it
->request
.render_process_id
== render_process_id
&&
774 it
->request
.render_view_id
== render_view_id
&&
775 it
->request
.page_request_id
== page_request_id
) {
786 #if defined(OS_CHROMEOS)
787 if (IsOriginForCasting(security_origin
) && IsVideoMediaType(device
.type
)) {
788 // Notify ash that casting state has changed.
789 if (state
== content::MEDIA_REQUEST_STATE_DONE
) {
790 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(true);
791 } else if (state
== content::MEDIA_REQUEST_STATE_CLOSING
) {
792 ash::Shell::GetInstance()->OnCastingSessionStartedOrStopped(false);
797 FOR_EACH_OBSERVER(Observer
, observers_
,
798 OnRequestUpdate(render_process_id
,
804 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread(
805 int render_process_id
,
806 int render_view_id
) {
807 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
808 FOR_EACH_OBSERVER(Observer
, observers_
,
809 OnCreatingAudioStream(render_process_id
, render_view_id
));
812 bool MediaCaptureDevicesDispatcher::IsDesktopCaptureInProgress() {
813 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI
));
814 return desktop_capture_sessions_
.size() > 0;