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. 4 5 #include "chrome/browser/media/media_capture_devices_dispatcher.h" 6 7 #include "base/command_line.h" 8 #include "base/logging.h" 9 #include "base/prefs/pref_service.h" 10 #include "base/sha1.h" 11 #include "base/strings/string_number_conversions.h" 12 #include "base/strings/string_util.h" 13 #include "base/strings/utf_string_conversions.h" 14 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry.h" 15 #include "chrome/browser/extensions/api/tab_capture/tab_capture_registry_factory.h" 16 #include "chrome/browser/media/audio_stream_indicator.h" 17 #include "chrome/browser/media/media_stream_capture_indicator.h" 18 #include "chrome/browser/media/media_stream_infobar_delegate.h" 19 #include "chrome/browser/prefs/scoped_user_pref_update.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/extensions/extension.h" 25 #include "chrome/common/pref_names.h" 26 #include "components/user_prefs/pref_registry_syncable.h" 27 #include "content/public/browser/browser_thread.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/desktop_media_id.h" 34 #include "content/public/common/media_stream_request.h" 35 #include "extensions/common/constants.h" 36 #include "grit/generated_resources.h" 37 #include "ui/base/l10n/l10n_util.h" 38 39 #if defined(USE_CRAS) 40 #include "media/audio/cras/audio_manager_cras.h" 41 #endif 42 43 using content::BrowserThread; 44 using content::MediaStreamDevices; 45 46 namespace { 47 48 // Finds a device in |devices| that has |device_id|, or NULL if not found. 49 const content::MediaStreamDevice* FindDeviceWithId( 50 const content::MediaStreamDevices& devices, 51 const std::string& device_id) { 52 content::MediaStreamDevices::const_iterator iter = devices.begin(); 53 for (; iter != devices.end(); ++iter) { 54 if (iter->id == device_id) { 55 return &(*iter); 56 } 57 } 58 return NULL; 59 }; 60 61 // This is a short-term solution to allow testing of the the Screen Capture API 62 // with Google Hangouts in M27. 63 // TODO(sergeyu): Remove this whitelist as soon as possible. 64 bool IsOriginWhitelistedForScreenCapture(const GURL& origin) { 65 #if defined(OFFICIAL_BUILD) 66 if (// Google Hangouts. 67 (origin.SchemeIs("https") && 68 EndsWith(origin.spec(), ".talkgadget.google.com/", true)) || 69 origin.spec() == "https://plus.google.com/" || 70 origin.spec() == "chrome-extension://pkedcjkdefgpdelpbcmbmeomcjbeemfm/" || 71 origin.spec() == "chrome-extension://fmfcbgogabcbclcofgocippekhfcmgfj/" || 72 origin.spec() == "chrome-extension://hfaagokkkhdbgiakmmlclaapfelnkoah/" || 73 origin.spec() == "chrome-extension://gfdkimpbcpahaombhbimeihdjnejgicl/") { 74 return true; 75 } 76 // Check against hashed origins. 77 const std::string origin_hash = base::SHA1HashString(origin.spec()); 78 DCHECK_EQ(origin_hash.length(), base::kSHA1Length); 79 const std::string hexencoded_origin_hash = 80 base::HexEncode(origin_hash.data(), origin_hash.length()); 81 return 82 hexencoded_origin_hash == "3C2705BC432E7C51CA8553FDC5BEE873FF2468EE" || 83 hexencoded_origin_hash == "50F02B8A668CAB274527D58356F07C2143080FCC"; 84 #else 85 return false; 86 #endif 87 } 88 89 } // namespace 90 91 MediaCaptureDevicesDispatcher::PendingAccessRequest::PendingAccessRequest( 92 const content::MediaStreamRequest& request, 93 const content::MediaResponseCallback& callback) 94 : request(request), 95 callback(callback) { 96 } 97 98 MediaCaptureDevicesDispatcher::PendingAccessRequest::~PendingAccessRequest() {} 99 100 MediaCaptureDevicesDispatcher* MediaCaptureDevicesDispatcher::GetInstance() { 101 return Singleton<MediaCaptureDevicesDispatcher>::get(); 102 } 103 104 MediaCaptureDevicesDispatcher::MediaCaptureDevicesDispatcher() 105 : devices_enumerated_(false), 106 is_device_enumeration_disabled_(false), 107 media_stream_capture_indicator_(new MediaStreamCaptureIndicator()), 108 audio_stream_indicator_(new AudioStreamIndicator()) { 109 // MediaCaptureDevicesDispatcher is a singleton. It should be created on 110 // UI thread. Otherwise, it will not receive 111 // content::NOTIFICATION_WEB_CONTENTS_DESTROYED, and that will result in 112 // possible use after free. 113 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 114 notifications_registrar_.Add( 115 this, content::NOTIFICATION_WEB_CONTENTS_DESTROYED, 116 content::NotificationService::AllSources()); 117 } 118 119 MediaCaptureDevicesDispatcher::~MediaCaptureDevicesDispatcher() {} 120 121 void MediaCaptureDevicesDispatcher::RegisterProfilePrefs( 122 user_prefs::PrefRegistrySyncable* registry) { 123 registry->RegisterStringPref( 124 prefs::kDefaultAudioCaptureDevice, 125 std::string(), 126 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 127 registry->RegisterStringPref( 128 prefs::kDefaultVideoCaptureDevice, 129 std::string(), 130 user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF); 131 } 132 133 void MediaCaptureDevicesDispatcher::AddObserver(Observer* observer) { 134 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 135 if (!observers_.HasObserver(observer)) 136 observers_.AddObserver(observer); 137 } 138 139 void MediaCaptureDevicesDispatcher::RemoveObserver(Observer* observer) { 140 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 141 observers_.RemoveObserver(observer); 142 } 143 144 const MediaStreamDevices& 145 MediaCaptureDevicesDispatcher::GetAudioCaptureDevices() { 146 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 147 if (!is_device_enumeration_disabled_ && !devices_enumerated_) { 148 content::EnsureMonitorCaptureDevices(); 149 devices_enumerated_ = true; 150 } 151 return audio_devices_; 152 } 153 154 const MediaStreamDevices& 155 MediaCaptureDevicesDispatcher::GetVideoCaptureDevices() { 156 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 157 if (!is_device_enumeration_disabled_ && !devices_enumerated_) { 158 content::EnsureMonitorCaptureDevices(); 159 devices_enumerated_ = true; 160 } 161 return video_devices_; 162 } 163 164 void MediaCaptureDevicesDispatcher::Observe( 165 int type, 166 const content::NotificationSource& source, 167 const content::NotificationDetails& details) { 168 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 169 if (type == content::NOTIFICATION_WEB_CONTENTS_DESTROYED) { 170 content::WebContents* web_contents = 171 content::Source<content::WebContents>(source).ptr(); 172 pending_requests_.erase(web_contents); 173 } 174 } 175 176 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequest( 177 content::WebContents* web_contents, 178 const content::MediaStreamRequest& request, 179 const content::MediaResponseCallback& callback, 180 const extensions::Extension* extension) { 181 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 182 183 if (request.video_type == content::MEDIA_DESKTOP_VIDEO_CAPTURE || 184 request.audio_type == content::MEDIA_SYSTEM_AUDIO_CAPTURE) { 185 ProcessScreenCaptureAccessRequest( 186 web_contents, request, callback, extension); 187 } else if (extension) { 188 // For extensions access is approved based on extension permissions. 189 ProcessMediaAccessRequestFromExtension( 190 web_contents, request, callback, extension); 191 } else { 192 ProcessRegularMediaAccessRequest(web_contents, request, callback); 193 } 194 } 195 196 void MediaCaptureDevicesDispatcher::ProcessScreenCaptureAccessRequest( 197 content::WebContents* web_contents, 198 const content::MediaStreamRequest& request, 199 const content::MediaResponseCallback& callback, 200 const extensions::Extension* extension) { 201 content::MediaStreamDevices devices; 202 scoped_ptr<content::MediaStreamUI> ui; 203 204 if (request.video_type != content::MEDIA_DESKTOP_VIDEO_CAPTURE) { 205 callback.Run(devices, ui.Pass()); 206 return; 207 } 208 209 content::DesktopMediaID media_id = 210 content::DesktopMediaID::Parse(request.requested_video_device_id); 211 if (media_id.is_null()) { 212 LOG(ERROR) << "Invalid desktop media ID: " 213 << request.requested_video_device_id; 214 callback.Run(devices, ui.Pass()); 215 return; 216 } 217 218 const bool system_audio_capture_requested = 219 request.audio_type == content::MEDIA_SYSTEM_AUDIO_CAPTURE; 220 221 #if defined(USE_CRAS) 222 const bool system_audio_capture_supported = true; 223 #else 224 const bool system_audio_capture_supported = false; 225 #endif 226 227 // Reject request when audio capture was requested but is not supported on 228 // this system. 229 if (system_audio_capture_requested && !system_audio_capture_supported) { 230 callback.Run(devices, ui.Pass()); 231 return; 232 } 233 234 const bool component_extension = 235 extension && extension->location() == extensions::Manifest::COMPONENT; 236 237 const bool screen_capture_enabled = 238 CommandLine::ForCurrentProcess()->HasSwitch( 239 switches::kEnableUserMediaScreenCapturing) || 240 IsOriginWhitelistedForScreenCapture(request.security_origin); 241 242 const bool origin_is_secure = 243 request.security_origin.SchemeIsSecure() || 244 request.security_origin.SchemeIs(extensions::kExtensionScheme) || 245 CommandLine::ForCurrentProcess()->HasSwitch( 246 switches::kAllowHttpScreenCapture); 247 248 // Approve request only when the following conditions are met: 249 // 1. Screen capturing is enabled via command line switch or white-listed for 250 // the given origin. 251 // 2. Request comes from a page with a secure origin or from an extension. 252 if (screen_capture_enabled && origin_is_secure) { 253 // For component extensions, bypass message box. 254 bool user_approved = false; 255 if (!component_extension) { 256 string16 application_name = UTF8ToUTF16(request.security_origin.spec()); 257 string16 confirmation_text = l10n_util::GetStringFUTF16( 258 request.audio_type == content::MEDIA_NO_SERVICE ? 259 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TEXT : 260 IDS_MEDIA_SCREEN_AND_AUDIO_CAPTURE_CONFIRMATION_TEXT, 261 application_name); 262 chrome::MessageBoxResult result = chrome::ShowMessageBox( 263 NULL, 264 l10n_util::GetStringFUTF16( 265 IDS_MEDIA_SCREEN_CAPTURE_CONFIRMATION_TITLE, application_name), 266 confirmation_text, 267 chrome::MESSAGE_BOX_TYPE_QUESTION); 268 user_approved = (result == chrome::MESSAGE_BOX_RESULT_YES); 269 } 270 271 if (user_approved || component_extension) { 272 devices.push_back(content::MediaStreamDevice( 273 content::MEDIA_DESKTOP_VIDEO_CAPTURE, media_id.ToString(), "Screen")); 274 if (system_audio_capture_requested) { 275 #if defined(USE_CRAS) 276 // Use the special loopback device ID for system audio capture. 277 devices.push_back(content::MediaStreamDevice( 278 content::MEDIA_SYSTEM_AUDIO_CAPTURE, 279 media::AudioManagerCras::kLoopbackDeviceId, 280 "System Audio")); 281 #endif 282 } 283 } 284 } 285 286 // Unless we're being invoked from a component extension, register to display 287 // the notification for stream capture. 288 if (!devices.empty() && !component_extension) { 289 // Use extension name as title for extensions and origin for drive-by web. 290 std::string title; 291 if (extension) { 292 title = extension->name(); 293 } else { 294 title = web_contents->GetURL().GetOrigin().spec(); 295 } 296 297 ui = ScreenCaptureNotificationUI::Create(l10n_util::GetStringFUTF16( 298 IDS_MEDIA_SCREEN_CAPTURE_NOTIFICATION_TEXT, UTF8ToUTF16(title))); 299 } 300 301 callback.Run(devices, ui.Pass()); 302 } 303 304 void MediaCaptureDevicesDispatcher::ProcessMediaAccessRequestFromExtension( 305 content::WebContents* web_contents, 306 const content::MediaStreamRequest& request, 307 const content::MediaResponseCallback& callback, 308 const extensions::Extension* extension) { 309 content::MediaStreamDevices devices; 310 Profile* profile = 311 Profile::FromBrowserContext(web_contents->GetBrowserContext()); 312 313 #if defined(OS_ANDROID) 314 // Tab capture is not supported on Android. 315 bool tab_capture_allowed = false; 316 #else 317 extensions::TabCaptureRegistry* tab_capture_registry = 318 extensions::TabCaptureRegistryFactory::GetForProfile(profile); 319 bool tab_capture_allowed = 320 tab_capture_registry->VerifyRequest(request.render_process_id, 321 request.render_view_id); 322 #endif 323 324 if (request.audio_type == content::MEDIA_TAB_AUDIO_CAPTURE && 325 tab_capture_allowed && 326 extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) { 327 devices.push_back(content::MediaStreamDevice( 328 content::MEDIA_TAB_AUDIO_CAPTURE, std::string(), std::string())); 329 } else if (request.audio_type == content::MEDIA_DEVICE_AUDIO_CAPTURE && 330 extension->HasAPIPermission( 331 extensions::APIPermission::kAudioCapture)) { 332 GetDefaultDevicesForProfile(profile, true, false, &devices); 333 } 334 335 if (request.video_type == content::MEDIA_TAB_VIDEO_CAPTURE && 336 tab_capture_allowed && 337 extension->HasAPIPermission(extensions::APIPermission::kTabCapture)) { 338 devices.push_back(content::MediaStreamDevice( 339 content::MEDIA_TAB_VIDEO_CAPTURE, std::string(), std::string())); 340 } else if (request.video_type == content::MEDIA_DEVICE_VIDEO_CAPTURE && 341 extension->HasAPIPermission(extensions::APIPermission::kVideoCapture)) { 342 GetDefaultDevicesForProfile(profile, false, true, &devices); 343 } 344 345 scoped_ptr<content::MediaStreamUI> ui; 346 if (!devices.empty()) { 347 ui = media_stream_capture_indicator_->RegisterMediaStream( 348 web_contents, devices); 349 } 350 callback.Run(devices, ui.Pass()); 351 } 352 353 void MediaCaptureDevicesDispatcher::ProcessRegularMediaAccessRequest( 354 content::WebContents* web_contents, 355 const content::MediaStreamRequest& request, 356 const content::MediaResponseCallback& callback) { 357 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 358 359 RequestsQueue& queue = pending_requests_[web_contents]; 360 queue.push_back(PendingAccessRequest(request, callback)); 361 362 // If this is the only request then show the infobar. 363 if (queue.size() == 1) 364 ProcessQueuedAccessRequest(web_contents); 365 } 366 367 void MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest( 368 content::WebContents* web_contents) { 369 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 370 371 std::map<content::WebContents*, RequestsQueue>::iterator it = 372 pending_requests_.find(web_contents); 373 374 if (it == pending_requests_.end() || it->second.empty()) { 375 // Don't do anything if the tab was was closed. 376 return; 377 } 378 379 DCHECK(!it->second.empty()); 380 381 MediaStreamInfoBarDelegate::Create( 382 web_contents, it->second.front().request, 383 base::Bind(&MediaCaptureDevicesDispatcher::OnAccessRequestResponse, 384 base::Unretained(this), web_contents)); 385 } 386 387 void MediaCaptureDevicesDispatcher::OnAccessRequestResponse( 388 content::WebContents* web_contents, 389 const content::MediaStreamDevices& devices, 390 scoped_ptr<content::MediaStreamUI> ui) { 391 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 392 393 std::map<content::WebContents*, RequestsQueue>::iterator it = 394 pending_requests_.find(web_contents); 395 if (it == pending_requests_.end()) { 396 // WebContents has been destroyed. Don't need to do anything. 397 return; 398 } 399 400 RequestsQueue& queue(it->second); 401 if (queue.empty()) 402 return; 403 404 content::MediaResponseCallback callback = queue.front().callback; 405 queue.pop_front(); 406 407 if (!queue.empty()) { 408 // Post a task to process next queued request. It has to be done 409 // asynchronously to make sure that calling infobar is not destroyed until 410 // after this function returns. 411 BrowserThread::PostTask( 412 BrowserThread::UI, FROM_HERE, 413 base::Bind(&MediaCaptureDevicesDispatcher::ProcessQueuedAccessRequest, 414 base::Unretained(this), web_contents)); 415 } 416 417 callback.Run(devices, ui.Pass()); 418 } 419 420 void MediaCaptureDevicesDispatcher::GetDefaultDevicesForProfile( 421 Profile* profile, 422 bool audio, 423 bool video, 424 content::MediaStreamDevices* devices) { 425 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 426 DCHECK(audio || video); 427 428 PrefService* prefs = profile->GetPrefs(); 429 std::string default_device; 430 if (audio) { 431 default_device = prefs->GetString(prefs::kDefaultAudioCaptureDevice); 432 const content::MediaStreamDevice* device = 433 GetRequestedAudioDevice(default_device); 434 if (!device) 435 device = GetFirstAvailableAudioDevice(); 436 if (device) 437 devices->push_back(*device); 438 } 439 440 if (video) { 441 default_device = prefs->GetString(prefs::kDefaultVideoCaptureDevice); 442 const content::MediaStreamDevice* device = 443 GetRequestedVideoDevice(default_device); 444 if (!device) 445 device = GetFirstAvailableVideoDevice(); 446 if (device) 447 devices->push_back(*device); 448 } 449 } 450 451 const content::MediaStreamDevice* 452 MediaCaptureDevicesDispatcher::GetRequestedAudioDevice( 453 const std::string& requested_audio_device_id) { 454 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 455 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); 456 const content::MediaStreamDevice* const device = 457 FindDeviceWithId(audio_devices, requested_audio_device_id); 458 return device; 459 } 460 461 const content::MediaStreamDevice* 462 MediaCaptureDevicesDispatcher::GetFirstAvailableAudioDevice() { 463 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 464 const content::MediaStreamDevices& audio_devices = GetAudioCaptureDevices(); 465 if (audio_devices.empty()) 466 return NULL; 467 return &(*audio_devices.begin()); 468 } 469 470 const content::MediaStreamDevice* 471 MediaCaptureDevicesDispatcher::GetRequestedVideoDevice( 472 const std::string& requested_video_device_id) { 473 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 474 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); 475 const content::MediaStreamDevice* const device = 476 FindDeviceWithId(video_devices, requested_video_device_id); 477 return device; 478 } 479 480 const content::MediaStreamDevice* 481 MediaCaptureDevicesDispatcher::GetFirstAvailableVideoDevice() { 482 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 483 const content::MediaStreamDevices& video_devices = GetVideoCaptureDevices(); 484 if (video_devices.empty()) 485 return NULL; 486 return &(*video_devices.begin()); 487 } 488 489 void MediaCaptureDevicesDispatcher::DisableDeviceEnumerationForTesting() { 490 is_device_enumeration_disabled_ = true; 491 } 492 493 scoped_refptr<MediaStreamCaptureIndicator> 494 MediaCaptureDevicesDispatcher::GetMediaStreamCaptureIndicator() { 495 return media_stream_capture_indicator_; 496 } 497 498 scoped_refptr<AudioStreamIndicator> 499 MediaCaptureDevicesDispatcher::GetAudioStreamIndicator() { 500 return audio_stream_indicator_; 501 } 502 503 void MediaCaptureDevicesDispatcher::OnAudioCaptureDevicesChanged( 504 const content::MediaStreamDevices& devices) { 505 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 506 BrowserThread::PostTask( 507 BrowserThread::UI, FROM_HERE, 508 base::Bind(&MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread, 509 base::Unretained(this), devices)); 510 } 511 512 void MediaCaptureDevicesDispatcher::OnVideoCaptureDevicesChanged( 513 const content::MediaStreamDevices& devices) { 514 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 515 BrowserThread::PostTask( 516 BrowserThread::UI, FROM_HERE, 517 base::Bind(&MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread, 518 base::Unretained(this), devices)); 519 } 520 521 void MediaCaptureDevicesDispatcher::OnMediaRequestStateChanged( 522 int render_process_id, 523 int render_view_id, 524 int page_request_id, 525 const content::MediaStreamDevice& device, 526 content::MediaRequestState state) { 527 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 528 BrowserThread::PostTask( 529 BrowserThread::UI, FROM_HERE, 530 base::Bind( 531 &MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread, 532 base::Unretained(this), render_process_id, render_view_id, 533 page_request_id, device, state)); 534 535 } 536 537 void MediaCaptureDevicesDispatcher::OnAudioStreamPlayingChanged( 538 int render_process_id, int render_view_id, int stream_id, 539 bool is_playing, float power_dbfs, bool clipped) { 540 audio_stream_indicator_->UpdateWebContentsStatus( 541 render_process_id, render_view_id, stream_id, 542 is_playing, power_dbfs, clipped); 543 } 544 545 void MediaCaptureDevicesDispatcher::OnCreatingAudioStream( 546 int render_process_id, 547 int render_view_id) { 548 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); 549 BrowserThread::PostTask( 550 BrowserThread::UI, FROM_HERE, 551 base::Bind( 552 &MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread, 553 base::Unretained(this), render_process_id, render_view_id)); 554 } 555 556 void MediaCaptureDevicesDispatcher::UpdateAudioDevicesOnUIThread( 557 const content::MediaStreamDevices& devices) { 558 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 559 devices_enumerated_ = true; 560 audio_devices_ = devices; 561 FOR_EACH_OBSERVER(Observer, observers_, 562 OnUpdateAudioDevices(audio_devices_)); 563 } 564 565 void MediaCaptureDevicesDispatcher::UpdateVideoDevicesOnUIThread( 566 const content::MediaStreamDevices& devices){ 567 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 568 devices_enumerated_ = true; 569 video_devices_ = devices; 570 FOR_EACH_OBSERVER(Observer, observers_, 571 OnUpdateVideoDevices(video_devices_)); 572 } 573 574 void MediaCaptureDevicesDispatcher::UpdateMediaRequestStateOnUIThread( 575 int render_process_id, 576 int render_view_id, 577 int page_request_id, 578 const content::MediaStreamDevice& device, 579 content::MediaRequestState state) { 580 // Cancel the request. 581 if (state == content::MEDIA_REQUEST_STATE_CLOSING) { 582 bool found = false; 583 for (RequestsQueues::iterator rqs_it = pending_requests_.begin(); 584 rqs_it != pending_requests_.end(); ++rqs_it) { 585 RequestsQueue& queue = rqs_it->second; 586 for (RequestsQueue::iterator it = queue.begin(); 587 it != queue.end(); ++it) { 588 if (it->request.render_process_id == render_process_id && 589 it->request.render_view_id == render_view_id && 590 it->request.page_request_id == page_request_id) { 591 queue.erase(it); 592 found = true; 593 break; 594 } 595 } 596 if (found) 597 break; 598 } 599 } 600 601 FOR_EACH_OBSERVER(Observer, observers_, 602 OnRequestUpdate(render_process_id, 603 render_view_id, 604 device, 605 state)); 606 } 607 608 void MediaCaptureDevicesDispatcher::OnCreatingAudioStreamOnUIThread( 609 int render_process_id, 610 int render_view_id) { 611 DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); 612 FOR_EACH_OBSERVER(Observer, observers_, 613 OnCreatingAudioStream(render_process_id, render_view_id)); 614 } 615